diff --git a/web/client/actions/__tests__/featuregrid-test.js b/web/client/actions/__tests__/featuregrid-test.js index cb01e42339..27eb6321e5 100644 --- a/web/client/actions/__tests__/featuregrid-test.js +++ b/web/client/actions/__tests__/featuregrid-test.js @@ -28,6 +28,9 @@ const { deleteGeometryFeature, DELETE_GEOMETRY_FEATURE, clearChangeConfirmed, CLEAR_CHANGES_CONFIRMED, deleteFeaturesConfirm, DELETE_SELECTED_FEATURES_CONFIRM, + openFeatureGrid, OPEN_FEATURE_GRID, + closeFeatureGrid, CLOSE_FEATURE_GRID, + closeFeatureGridConfirm, CLOSE_FEATURE_GRID_CONFIRM, closeFeatureGridConfirmed, FEATURE_GRID_CLOSE_CONFIRMED } = require('../featuregrid'); @@ -175,6 +178,21 @@ describe('Test correctness of featurgrid actions', () => { expect(retval).toExist(); expect(retval.type).toBe(DELETE_SELECTED_FEATURES_CONFIRM); }); + it('Test openFeatureGrid', () => { + const retval = openFeatureGrid(); + expect(retval).toExist(); + expect(retval.type).toBe(OPEN_FEATURE_GRID); + }); + it('Test closeFeatureGrid', () => { + const retval = closeFeatureGrid(); + expect(retval).toExist(); + expect(retval.type).toBe(CLOSE_FEATURE_GRID); + }); + it('Test closeFeatureGridConfirm', () => { + const retval = closeFeatureGridConfirm(); + expect(retval).toExist(); + expect(retval.type).toBe(CLOSE_FEATURE_GRID_CONFIRM); + }); it('Test saveError', () => { const retval = saveError(); expect(retval).toExist(); diff --git a/web/client/actions/__tests__/layers-test.js b/web/client/actions/__tests__/layers-test.js index eb431be3f0..2f8c02d72a 100644 --- a/web/client/actions/__tests__/layers-test.js +++ b/web/client/actions/__tests__/layers-test.js @@ -24,6 +24,7 @@ var { REFRESH_LAYERS, LAYERS_REFRESHED, LAYERS_REFRESH_ERROR, + BROWSE_DATA, changeLayerProperties, toggleNode, sortNode, @@ -39,7 +40,8 @@ var { updateSettings, refreshLayers, layersRefreshed, - layersRefreshError + layersRefreshError, + browseData } = require('../layers'); var {getLayerCapabilities} = require('../layerCapabilities'); @@ -223,4 +225,10 @@ describe('Test correctness of the layers actions', () => { } }); }); + it('browseData', () => { + const layer = {id: "TEST", name: "test", url: "test"}; + const action = browseData(layer); + expect(action.type).toBe(BROWSE_DATA); + expect(action.layer).toBe(layer); + }); }); diff --git a/web/client/actions/__tests__/wfsquery-test.js b/web/client/actions/__tests__/wfsquery-test.js index d6a6d474bd..b19610d1d0 100644 --- a/web/client/actions/__tests__/wfsquery-test.js +++ b/web/client/actions/__tests__/wfsquery-test.js @@ -19,7 +19,6 @@ var { QUERY_CREATE, QUERY, RESET_QUERY, - FEATURE_CLOSE, layerSelectedForSearch, featureTypeSelected, featureTypeError, @@ -30,8 +29,7 @@ var { queryError, createQuery, query, - resetQuery, - featureClose + resetQuery } = require('../wfsquery'); describe('wfsquery actions', () => { @@ -97,8 +95,4 @@ describe('wfsquery actions', () => { let {type} = resetQuery(); expect(type).toBe(RESET_QUERY); }); - it('featureClose', () => { - let {type} = featureClose(); - expect(type).toBe(FEATURE_CLOSE); - }); }); diff --git a/web/client/actions/featuregrid.js b/web/client/actions/featuregrid.js index f5ddaae835..c3e511c199 100644 --- a/web/client/actions/featuregrid.js +++ b/web/client/actions/featuregrid.js @@ -33,11 +33,14 @@ const GEOMETRY_CHANGED = 'FEATUREGRID:GEOMETRY_CHANGED'; const DOCK_SIZE_FEATURES = 'DOCK_SIZE_FEATURES'; const TOGGLE_TOOL = 'FEATUREGRID:TOGGLE_TOOL'; const CUSTOMIZE_ATTRIBUTE = 'FEATUREGRID:CUSTOMIZE_ATTRIBUTE'; -const CLOSE_GRID = 'FEATUREGRID:CLOSE_GRID'; +const CLOSE_FEATURE_GRID_CONFIRM = 'ASK_CLOSE_FEATURE_GRID_CONFIRM'; +const OPEN_FEATURE_GRID = 'FEATUREGRID:OPEN_GRID'; +const CLOSE_FEATURE_GRID = 'FEATUREGRID:CLOSE_GRID'; const CLEAR_CHANGES_CONFIRMED = 'FEATUREGRID:CLEAR_CHANGES_CONFIRMED'; const FEATURE_GRID_CLOSE_CONFIRMED = 'FEATUREGRID:FEATURE_GRID_CLOSE_CONFIRMED'; const SET_PERMISSION = 'FEATUREGRID:SET_PERMISSION'; const DISABLE_TOOLBAR = 'FEATUREGRID:DISABLE_TOOLBAR'; +const OPEN_ADVANCED_SEARCH = 'FEATUREGRID:ADVANCED_SEARCH'; const MODES = { EDIT: "EDIT", VIEW: "VIEW" @@ -220,9 +223,19 @@ function saveError() { type: SAVE_ERROR }; } +function closeFeatureGridConfirm() { + return { + type: CLOSE_FEATURE_GRID_CONFIRM + }; +} function closeFeatureGrid() { return { - type: CLOSE_GRID + type: CLOSE_FEATURE_GRID + }; +} +function openFeatureGrid() { + return { + type: OPEN_FEATURE_GRID }; } @@ -238,6 +251,11 @@ function setPermission(permission) { permission }; } +function openAdvancedSearch() { + return { + type: OPEN_ADVANCED_SEARCH + }; +} module.exports = { SELECT_FEATURES, @@ -270,9 +288,12 @@ module.exports = { DELETE_GEOMETRY, deleteGeometry, DELETE_GEOMETRY_FEATURE, deleteGeometryFeature, CLEAR_CHANGES_CONFIRMED, clearChangeConfirmed, - CLOSE_GRID, closeFeatureGrid, + CLOSE_FEATURE_GRID, closeFeatureGrid, + OPEN_FEATURE_GRID, openFeatureGrid, + CLOSE_FEATURE_GRID_CONFIRM, closeFeatureGridConfirm, FEATURE_GRID_CLOSE_CONFIRMED, closeFeatureGridConfirmed, DISABLE_TOOLBAR, disableToolbar, + OPEN_ADVANCED_SEARCH, openAdvancedSearch, setLayer, selectFeatures, deselectFeatures, diff --git a/web/client/actions/layers.js b/web/client/actions/layers.js index 4ae0199c06..9933d0118d 100644 --- a/web/client/actions/layers.js +++ b/web/client/actions/layers.js @@ -24,6 +24,7 @@ const UPDATE_SETTINGS = 'UPDATE_SETTINGS'; const REFRESH_LAYERS = 'REFRESH_LAYERS'; const LAYERS_REFRESHED = 'LAYERS_REFRESHED'; const LAYERS_REFRESH_ERROR = 'LAYERS_REFRESH_ERROR'; +const BROWSE_DATA = 'LAYERS:BROWSE_DATA'; function showSettings(node, nodeType, options) { return { @@ -175,11 +176,17 @@ function layersRefreshError(layers, error) { error }; } +function browseData(layer) { + return { + type: BROWSE_DATA, + layer + }; +} module.exports = {changeLayerProperties, changeGroupProperties, toggleNode, sortNode, removeNode, contextNode, updateNode, layerLoading, layerLoad, layerError, addLayer, removeLayer, showSettings, hideSettings, updateSettings, refreshLayers, - layersRefreshed, layersRefreshError, refreshLayerVersion, + layersRefreshed, layersRefreshError, refreshLayerVersion, browseData, CHANGE_LAYER_PROPERTIES, CHANGE_GROUP_PROPERTIES, TOGGLE_NODE, SORT_NODE, REMOVE_NODE, UPDATE_NODE, LAYER_LOADING, LAYER_LOAD, LAYER_ERROR, ADD_LAYER, REMOVE_LAYER, - SHOW_SETTINGS, HIDE_SETTINGS, UPDATE_SETTINGS, CONTEXT_NODE, REFRESH_LAYERS, LAYERS_REFRESHED, LAYERS_REFRESH_ERROR + SHOW_SETTINGS, HIDE_SETTINGS, UPDATE_SETTINGS, CONTEXT_NODE, REFRESH_LAYERS, LAYERS_REFRESHED, LAYERS_REFRESH_ERROR, BROWSE_DATA }; diff --git a/web/client/actions/wfsquery.js b/web/client/actions/wfsquery.js index d3d674c987..c158bd138c 100644 --- a/web/client/actions/wfsquery.js +++ b/web/client/actions/wfsquery.js @@ -12,7 +12,6 @@ const FEATURE_LOADED = 'FEATURE_LOADED'; const FEATURE_LOADING = 'FEATURE_LOADING'; const FEATURE_TYPE_ERROR = 'FEATURE_TYPE_ERROR'; const FEATURE_ERROR = 'FEATURE_ERROR'; -const FEATURE_CLOSE = 'FEATURE_CLOSE'; const QUERY_CREATE = 'QUERY_CREATE'; const QUERY_RESULT = 'QUERY_RESULT'; const QUERY_ERROR = 'QUERY_ERROR'; @@ -20,9 +19,6 @@ const RESET_QUERY = 'RESET_QUERY'; const QUERY = 'QUERY'; const axios = require('../libs/ajax'); -const {toggleControl, setControlProperty} = require('./controls'); -const {changeDrawingStatus} = require('./draw'); -const {reset} = require('./queryform'); function layerSelectedForSearch(id) { return { @@ -132,43 +128,12 @@ function resetQuery() { }; } - -function toggleQueryPanel(url, name, id) { - return (dispatch, getState) => { - if (getState().query.typeName !== name) { - dispatch(reset()); - } - dispatch(changeDrawingStatus('clean', null, "queryform", [])); - dispatch(featureTypeSelected(url, name)); - dispatch(toggleControl('queryPanel', null)); - dispatch(layerSelectedForSearch(id)); - dispatch(setControlProperty('drawer', 'width', getState().controls.queryPanel.enabled ? 700 : 300)); - }; -} -function featureClose() { - return { - type: FEATURE_CLOSE - }; -} - -function closeResponse() { - return (dispatch, getState) => { - dispatch(featureClose()); - let state = getState(); - if (state.controls && state.controls.queryPanel && state.controls.drawer && !state.controls.drawer.enabled) { - dispatch(setControlProperty('drawer', 'enabled', true)); - dispatch(setControlProperty('drawer', 'disabled', false)); - } - }; -} - module.exports = { LAYER_SELECTED_FOR_SEARCH, layerSelectedForSearch, FEATURE_TYPE_SELECTED, featureTypeSelected, FEATURE_TYPE_LOADED, featureTypeLoaded, FEATURE_TYPE_ERROR, featureTypeError, FEATURE_ERROR, featureError, - FEATURE_CLOSE, featureClose, QUERY_CREATE, createQuery, QUERY_RESULT, querySearchResponse, QUERY_ERROR, queryError, @@ -176,7 +141,5 @@ module.exports = { QUERY, query, FEATURE_LOADING, featureLoading, FEATURE_LOADED, featureLoaded, - loadFeature, - toggleQueryPanel, - closeResponse + loadFeature }; diff --git a/web/client/components/TOC/DefaultLayer.jsx b/web/client/components/TOC/DefaultLayer.jsx index 8e47d6165f..10ddb9de0f 100644 --- a/web/client/components/TOC/DefaultLayer.jsx +++ b/web/client/components/TOC/DefaultLayer.jsx @@ -26,7 +26,7 @@ class DefaultLayer extends React.Component { retrieveLayerData: PropTypes.func, onToggle: PropTypes.func, onContextMenu: PropTypes.func, - onToggleQuerypanel: PropTypes.func, + onBrowseData: PropTypes.func, onZoom: PropTypes.func, onSettings: PropTypes.func, onRefresh: PropTypes.func, @@ -71,7 +71,7 @@ class DefaultLayer extends React.Component { onSettings: () => {}, onRefresh: () => {}, retrieveLayerData: () => {}, - onToggleQuerypanel: () => {}, + onBrowseData: () => {}, activateRemoveLayer: false, activateLegendTool: false, activateSettingsTool: false, @@ -150,13 +150,17 @@ class DefaultLayer extends React.Component { if (this.props.activateQueryTool && this.props.node.search) { tools.push( this.props.onToggleQuerypanel(node.search.url || node.url, node.name, node.id)}/> + glyph="features-grid" + onClick={(node) => this.props.onBrowseData({ + url: node.search.url || node.url, + name: node.name, + id: node.id + })}/> ); } return (
diff --git a/web/client/components/data/featuregrid/toolbars/Toolbar.jsx b/web/client/components/data/featuregrid/toolbars/Toolbar.jsx index 7770d74c90..ef6179c826 100644 --- a/web/client/components/data/featuregrid/toolbars/Toolbar.jsx +++ b/web/client/components/data/featuregrid/toolbars/Toolbar.jsx @@ -25,6 +25,13 @@ module.exports = ({events = {}, mode = "VIEW", selectedCount, hasChanges, hasGeo visible={mode === "VIEW" && isEditingAllowed} onClick={events.switchEditMode} glyph="pencil"/> + } + disabled={disableToolbar} + visible={mode === "VIEW"} + onClick={events.showQueryPanel} + glyph="filter"/> } diff --git a/web/client/epics/featuregrid.js b/web/client/epics/featuregrid.js index 9abea8b72d..6f81d110e0 100644 --- a/web/client/epics/featuregrid.js +++ b/web/client/epics/featuregrid.js @@ -17,14 +17,17 @@ const {changeDrawingStatus, GEOMETRY_CHANGED} = require('../actions/draw'); const requestBuilder = require('../utils/ogc/WFST/RequestBuilder'); const {findGeometryProperty} = require('../utils/ogc/WFS/base'); const {setControlProperty} = require('../actions/controls'); -const {query, QUERY_CREATE, QUERY_RESULT, LAYER_SELECTED_FOR_SEARCH, closeResponse} = require('../actions/wfsquery'); +const {query, QUERY_CREATE, QUERY_RESULT, LAYER_SELECTED_FOR_SEARCH, FEATURE_TYPE_LOADED, featureTypeSelected, createQuery} = require('../actions/wfsquery'); +const {reset, QUERY_FORM_RESET} = require('../actions/queryform'); +const {BROWSE_DATA} = require('../actions/layers'); const {parseString} = require('xml2js'); const {stripPrefix} = require('xml2js/lib/processors'); const {SORT_BY, CHANGE_PAGE, SAVE_CHANGES, SAVE_SUCCESS, DELETE_SELECTED_FEATURES, featureSaving, changePage, saveSuccess, saveError, clearChanges, setLayer, clearSelection, toggleViewMode, toggleTool, CLEAR_CHANGES, START_EDITING_FEATURE, TOGGLE_MODE, MODES, geometryChanged, DELETE_GEOMETRY, deleteGeometryFeature, - SELECT_FEATURES, DESELECT_FEATURES, START_DRAWING_FEATURE, CREATE_NEW_FEATURE, CLOSE_GRID, closeFeatureGrid, - CLEAR_CHANGES_CONFIRMED, FEATURE_GRID_CLOSE_CONFIRMED} = require('../actions/featuregrid'); + SELECT_FEATURES, DESELECT_FEATURES, START_DRAWING_FEATURE, CREATE_NEW_FEATURE, + CLEAR_CHANGES_CONFIRMED, FEATURE_GRID_CLOSE_CONFIRMED, + openFeatureGrid, closeFeatureGrid, OPEN_FEATURE_GRID, CLOSE_FEATURE_GRID, CLOSE_FEATURE_GRID_CONFIRM, OPEN_ADVANCED_SEARCH} = require('../actions/featuregrid'); const {TOGGLE_CONTROL} = require('../actions/controls'); const {setHighlightFeaturesPath} = require('../actions/highlight'); @@ -34,7 +37,7 @@ const {selectedFeaturesSelector, changesMapSelector, newFeaturesSelector, hasCha isFeatureGridOpen} = require('../selectors/featuregrid'); const {error} = require('../actions/notifications'); -const {describeSelector, getFeatureById, wfsURL, wfsFilter} = require('../selectors/query'); +const {describeSelector, isDescribeLoaded, getFeatureById, wfsURL, wfsFilter} = require('../selectors/query'); const drawSupportReset = () => changeDrawingStatus("clean", "", "featureGrid", [], {}); /** * Intercept OGC Exception (200 response with exceptionReport) to throw error in the stream @@ -142,7 +145,49 @@ const createDeleteFlow = (features, describeFeatureType, url) => save( createDeleteTransaction(features, requestBuilder(describeFeatureType)) ); +const createLoadPageFlow = (store) => ({page, size} = {}) => { + const state = store.getState(); + return Rx.Observable.of( query( + wfsURL(state), + addPagination({ + ...wfsFilter(state) + }, + getPagination(state, {page, size}) + ) + )); +}; + +const createInitialQueryFlow = (action$, store, {url, name} = {}) => { + const createInitialQuery = () => createQuery(url, { + featureTypeName: name, + filterType: 'OGC', + ogcVersion: '1.1.0' + }); + + if (isDescribeLoaded(store.getState(), name)) { + return Rx.Observable.of(createInitialQuery(), featureTypeSelected(url, name)); + } + return Rx.Observable.of(featureTypeSelected(url, name)).merge( + action$.ofType(FEATURE_TYPE_LOADED).filter(({typeName} = {}) => typeName === name) + .map(createInitialQuery) + ); +}; module.exports = { + featureGridBrowseData: (action$, store) => + action$.ofType(BROWSE_DATA).switchMap( ({layer}) => + Rx.Observable.of( + setControlProperty('drawer', 'enabled', false), + setLayer(layer.id), + openFeatureGrid() + ).merge( + createInitialQueryFlow(action$, store, layer) + ).merge( + get(store.getState(), "query.typeName") !== name + ? Rx.Observable.of(reset()) + : Rx.Observable.empty() + + ) + ), /** * Intercepts layer selection to set it's id in the status and retrieve it later */ @@ -152,12 +197,10 @@ module.exports = { /** * Intercepts query creation to perform the real query, setting page to 0 */ - featureGridStartupQuery: (action$) => + featureGridStartupQuery: (action$, store) => action$.ofType(QUERY_CREATE) - .switchMap(() => Rx.Observable.of( - changePage(0), - toggleViewMode() - )), + .switchMap(() => Rx.Observable.of(changePage(0)) + .concat(modeSelector(store.getState()) === MODES.VIEW ? Rx.Observable.of(toggleViewMode()) : Rx.Observable.empty())), /** * Create sorted queries on sort action */ @@ -177,16 +220,7 @@ module.exports = { * perform paginated query on page change */ featureGridChangePage: (action$, store) => - action$.ofType(CHANGE_PAGE).switchMap( ({page, size} = {}) => - Rx.Observable.of( query( - wfsURL(store.getState()), - addPagination({ - ...wfsFilter(store.getState()) - }, - getPagination(store.getState(), {page, size}) - ) - )) - ), + action$.ofType(CHANGE_PAGE).switchMap(createLoadPageFlow(store)), /** * Reload the page on save success. * NOTE: The page is in the action. @@ -302,6 +336,16 @@ module.exports = { }; return Rx.Observable.of(changeDrawingStatus("drawOrEdit", geomType, "featureGrid", [feature], drawOptions)); }), + resetEditingOnFeatureGridClose: (action$, store) => action$.ofType(OPEN_FEATURE_GRID).switchMap( () => + action$.ofType(TOGGLE_MODE) + .filter(() => modeSelector(store.getState()) === MODES.EDIT) + .take(1) + .switchMap( () => + action$.ofType(LOCATION_CHANGE, CLOSE_FEATURE_GRID) + .take(1) + .switchMap(() => Rx.Observable.of(drawSupportReset()))) + + ), /** * intercept geomertry changed events in draw support to update current * modified geometry in featuregrid @@ -364,26 +408,69 @@ module.exports = { * This forces to view mode and turn all tools to initial state. */ resetGridOnLocationChange: action$ => - action$.ofType(LOCATION_CHANGE).switchMap( () =>Rx.Observable.of( - closeResponse() - )), + action$.ofType(OPEN_FEATURE_GRID).switchMap( () => + action$.ofType(LOCATION_CHANGE) + .take(1) + .switchMap(() => + Rx.Observable.of( + toggleViewMode(), + closeFeatureGrid() + ) + ) + .takeUntil(action$.ofType(CLOSE_FEATURE_GRID)) + ), /** * Closes the feature grid when the drawer menu button has been toggled */ - autoCloseFeatureGridEpicOnDrowerOpen: (action$, store) => action$.ofType(TOGGLE_CONTROL) - .filter(action => action.control && action.control === 'drawer' && isFeatureGridOpen(store.getState())) - .switchMap(() => Rx.Observable.of(closeFeatureGrid())), - askChangesConfirmOnFeatureGridClose: (action$, store) => action$.ofType(CLOSE_GRID).switchMap( () => { + autoCloseFeatureGridEpicOnDrowerOpen: (action$, store) => + action$.ofType(OPEN_FEATURE_GRID).switchMap(() => + action$.ofType(TOGGLE_CONTROL) + .filter(action => action.control && action.control === 'drawer' && isFeatureGridOpen(store.getState())) + .switchMap(() => Rx.Observable.of(closeFeatureGrid())) + .takeUntil(action$.ofType(CLOSE_FEATURE_GRID, LOCATION_CHANGE)) + ), + askChangesConfirmOnFeatureGridClose: (action$, store) => action$.ofType(CLOSE_FEATURE_GRID_CONFIRM).switchMap( () => { const state = store.getState(); if (hasChangesSelector(state) || hasNewFeaturesSelector(state)) { return Rx.Observable.of(toggleTool("featureCloseConfirm", true)); } - return Rx.Observable.of(closeResponse()); + return Rx.Observable.of(closeFeatureGrid()); }), onClearChangeConfirmedFeatureGrid: (action$) => action$.ofType(CLEAR_CHANGES_CONFIRMED) .switchMap( () => Rx.Observable.of(clearChanges(), toggleTool("clearConfirm", false))), onCloseFeatureGridConfirmed: (action$) => action$.ofType(FEATURE_GRID_CLOSE_CONFIRMED) .switchMap( () => { return Rx.Observable.of(setControlProperty("drawer", "enabled", false), toggleTool("featureCloseConfirm", false)); - }) + }), + onOpenAdvancedSearch: (action$, store) => + action$.ofType(OPEN_ADVANCED_SEARCH).switchMap(() => + Rx.Observable.of( + setControlProperty('queryPanel', "enabled", true), + closeFeatureGrid() + ).merge( + action$.ofType(QUERY_FORM_RESET) // ON RESET YOU HAVE TO PERFORM A SEARCH AGAIN WHEN BACK + .switchMap(() => action$.ofType(TOGGLE_CONTROL) + .filter(({control, property} = {}) => control === "queryPanel" && (!property || property === "enabled")) + .switchMap( () => createInitialQueryFlow(action$, store, { + url: get(store.getState(), "query.url"), + name: get(store.getState(), "query.typeName") + })))) + .merge( + Rx.Observable.race( + action$.ofType(QUERY_CREATE).mergeMap(() => + Rx.Observable.of( + setControlProperty('queryPanel', "enabled", false), + openFeatureGrid() + ) + ), + action$.ofType(TOGGLE_CONTROL) + .filter(({control, property} = {}) => control === "queryPanel" && (!property || property === "enabled")) + .mergeMap(() => + Rx.Observable.of( + openFeatureGrid() + ) + ) + ).takeUntil(action$.ofType(OPEN_FEATURE_GRID, LOCATION_CHANGE)) + ) + ) }; diff --git a/web/client/epics/wfsquery.js b/web/client/epics/wfsquery.js index c790012597..3b5c902721 100644 --- a/web/client/epics/wfsquery.js +++ b/web/client/epics/wfsquery.js @@ -12,12 +12,11 @@ const Url = require('url'); const {changeSpatialAttribute, SELECT_VIEWPORT_SPATIAL_METHOD, updateGeometrySpatialField} = require('../actions/queryform'); const {CHANGE_MAP_VIEW} = require('../actions/map'); const {FEATURE_TYPE_SELECTED, QUERY, featureLoading, featureTypeLoaded, featureTypeError, querySearchResponse, queryError} = require('../actions/wfsquery'); -const {paginationInfo} = require('../selectors/query'); +const {paginationInfo, isDescribeLoaded, describeSelector} = require('../selectors/query'); const FilterUtils = require('../utils/FilterUtils'); const assign = require('object-assign'); const {isString, isObject} = require('lodash'); -const {setControlProperty} = require('../actions/controls'); // this is a workaround for https://osgeo-org.atlassian.net/browse/GEOS-7233. can be removed when fixed const workaroundGEOS7233 = ({totalFeatures, features, ...rest}, {startIndex, maxFeatures}, originalSize) => { if (originalSize > totalFeatures && originalSize === startIndex + features.length && totalFeatures === features.length) { @@ -197,10 +196,16 @@ const retryWithForcedSortOptions = (action, store) => { * @return {external:Observable} */ -const featureTypeSelectedEpic = action$ => +const featureTypeSelectedEpic = (action$, store) => action$.ofType(FEATURE_TYPE_SELECTED) .filter(action => action.url && action.typeName) .switchMap(action => { + const state = store.getState(); + if (isDescribeLoaded(state, action.typeName)) { + const info = extractInfo(describeSelector(state)); + const geometry = info.geometry[0] && info.geometry[0].attribute ? info.geometry[0].attribute : 'the_geom'; + return Rx.Observable.of(changeSpatialAttribute(geometry)); + } return Rx.Observable.defer( () => axios.get(action.url + '?service=WFS&version=1.1.0&request=DescribeFeatureType&typeName=' + action.typeName + '&outputFormat=application/json')) .map((response) => { if (typeof response.data === 'object' && response.data.featureTypes && response.data.featureTypes[0]) { @@ -231,7 +236,6 @@ const wfsQueryEpic = (action$, store) => action$.ofType(QUERY) .switchMap(action => { return Rx.Observable.merge( - Rx.Observable.of(setControlProperty('drawer', 'enabled', false)), getWFSFeature(action.searchUrl, action.filterObj) .switchMap((response) => { // try to guess if it was a missing id error and try to search again with forced sortOptions diff --git a/web/client/examples/featuregrid/localConfig.json b/web/client/examples/featuregrid/localConfig.json index 090d24b4cd..67477eb53c 100644 --- a/web/client/examples/featuregrid/localConfig.json +++ b/web/client/examples/featuregrid/localConfig.json @@ -7,12 +7,31 @@ "cfg": { "tools": ["draw", "highlight"] } + },{ + "name": "QueryPanel", + "cfg": { + "activateQueryTool": true, + "spatialOperations": [ + {"id": "INTERSECTS", "name": "queryform.spatialfilter.operations.intersects"}, + {"id": "BBOX", "name": "queryform.spatialfilter.operations.bbox"}, + {"id": "CONTAINS", "name": "queryform.spatialfilter.operations.contains"}, + {"id": "WITHIN", "name": "queryform.spatialfilter.operations.within"} + ], + "spatialMethodOptions": [ + {"id": "Viewport", "name": "queryform.spatialfilter.methods.viewport"}, + {"id": "BBOX", "name": "queryform.spatialfilter.methods.box"}, + {"id": "Circle", "name": "queryform.spatialfilter.methods.circle"}, + {"id": "Polygon", "name": "queryform.spatialfilter.methods.poly"} + ] + } + }, "FeatureEditor", "WFSDownload", "LayerSelector", "Notifications" ], + "mobile": [ { "cfg": { diff --git a/web/client/examples/featuregrid/plugins.js b/web/client/examples/featuregrid/plugins.js index 371b3fb6f9..84f87d9632 100644 --- a/web/client/examples/featuregrid/plugins.js +++ b/web/client/examples/featuregrid/plugins.js @@ -12,6 +12,7 @@ module.exports = { MapPlugin: require('../../plugins/Map'), WFSDownload: require('../../plugins/WFSDownload'), FeatureEditor: require('../../plugins/FeatureEditor'), + QueryPanel: require('../../plugins/QueryPanel'), Notifications: require('../../plugins/Notifications') }, requires: { diff --git a/web/client/examples/featuregrid/stores/store.js b/web/client/examples/featuregrid/stores/store.js index 5eeb41f679..c91a37bb87 100644 --- a/web/client/examples/featuregrid/stores/store.js +++ b/web/client/examples/featuregrid/stores/store.js @@ -9,7 +9,8 @@ const Rx = require('rxjs'); const {featureTypeSelectedEpic, wfsQueryEpic, viewportSelectedEpic} = require('../../../epics/wfsquery'); const {getLayerFromId} = require('../../../selectors/layers'); -const {createQuery, featureTypeSelected, layerSelectedForSearch, LAYER_SELECTED_FOR_SEARCH, FEATURE_TYPE_LOADED, FEATURE_CLOSE} = require('../../../actions/wfsquery'); +const {layerSelectedForSearch, LAYER_SELECTED_FOR_SEARCH, CLOSE_FEATURE_GRID} = require('../../../actions/wfsquery'); +const {browseData} = require('../../../actions/layers'); const {clearChanges, setPermission, toggleTool} = require('../../../actions/featuregrid'); const {hasChangesSelector, hasNewFeaturesSelector} = require('../../../selectors/featuregrid'); module.exports = (plugins) => { @@ -29,39 +30,24 @@ module.exports = (plugins) => { const state = store.getState(); if (hasChangesSelector(state) || hasNewFeaturesSelector(state)) { return Rx.Observable.of(toggleTool("featureCloseConfirm", true)) - .merge(action$.ofType(FEATURE_CLOSE).switchMap( () => Rx.Observable.of( + .merge(action$.ofType(CLOSE_FEATURE_GRID).switchMap( () => Rx.Observable.of( layerSelectedForSearch(id), - setPermission({canEdit: true}), - featureTypeSelected( 'http://demo.geo-solutions.it:80/geoserver/wfs', id), + setPermission({canEdit: true}) ))); } return Rx.Observable.of( layerSelectedForSearch(id), - setPermission({canEdit: true}), - featureTypeSelected( 'http://demo.geo-solutions.it:80/geoserver/wfs', id), + setPermission({canEdit: true}) ); }), createFeatureGridDemoQuery: (action$, store) => - Rx.Observable.zip( - action$.ofType(LAYER_SELECTED_FOR_SEARCH), - action$.ofType(FEATURE_TYPE_LOADED) - ).switchMap(([layer]) => Rx.Observable.of( - clearChanges(), - createQuery("http://demo.geo-solutions.it:80/geoserver/wfs", { - featureTypeName: getLayerFromId(store.getState(), layer.id).name, - groupFields: [ - - ], - filterFields: [], - pagination: { - maxFeatures: 20, - startIndex: 0 - }, - filterType: 'OGC', - ogcVersion: '1.1.0', - sortOptions: null, - hits: false - }) + action$.ofType(LAYER_SELECTED_FOR_SEARCH) + .switchMap((layer) => Rx.Observable.of( + clearChanges(), + browseData({ + ...getLayerFromId(store.getState(), layer.id), + url: 'http://demo.geo-solutions.it:80/geoserver/wfs' + }) )) }, plugins); }; diff --git a/web/client/examples/queryform/components/SmartQueryForm.jsx b/web/client/examples/queryform/components/SmartQueryForm.jsx index 57e6700712..70cf58fb9c 100644 --- a/web/client/examples/queryform/components/SmartQueryForm.jsx +++ b/web/client/examples/queryform/components/SmartQueryForm.jsx @@ -68,6 +68,18 @@ const SmartQueryForm = connect((state) => { filterFields: state.queryform.filterFields, attributes: attributesSelector(state), spatialField: state.queryform.spatialField, + spatialOperations: [ + {"id": "INTERSECTS", "name": "queryform.spatialfilter.operations.intersects"}, + {"id": "BBOX", "name": "queryform.spatialfilter.operations.bbox"}, + {"id": "CONTAINS", "name": "queryform.spatialfilter.operations.contains"}, + {"id": "WITHIN", "name": "queryform.spatialfilter.operations.within"} + ], + spatialMethodOptions: [ + {"id": "Viewport", "name": "queryform.spatialfilter.methods.viewport"}, + {"id": "BBOX", "name": "queryform.spatialfilter.methods.box"}, + {"id": "Circle", "name": "queryform.spatialfilter.methods.circle"}, + {"id": "Polygon", "name": "queryform.spatialfilter.methods.poly"} + ], showDetailsPanel: state.queryform.showDetailsPanel, toolbarEnabled: state.queryform.toolbarEnabled, attributePanelExpanded: state.queryform.attributePanelExpanded, diff --git a/web/client/examples/queryform/containers/QueryForm.jsx b/web/client/examples/queryform/containers/QueryForm.jsx index d2d6972ec1..f4199dfdc8 100644 --- a/web/client/examples/queryform/containers/QueryForm.jsx +++ b/web/client/examples/queryform/containers/QueryForm.jsx @@ -44,7 +44,7 @@ class QueryForm extends React.Component {
- +
diff --git a/web/client/localConfig.json b/web/client/localConfig.json index 452881788d..a12d1e59cd 100644 --- a/web/client/localConfig.json +++ b/web/client/localConfig.json @@ -158,7 +158,26 @@ "alwaysVisible": true } } - }, "Home", "FeatureEditor", "WFSDownload", { + }, "Home", "FeatureEditor", "WFSDownload", + { + "name": "QueryPanel", + "cfg": { + "activateQueryTool": true, + "spatialOperations": [ + {"id": "INTERSECTS", "name": "queryform.spatialfilter.operations.intersects"}, + {"id": "BBOX", "name": "queryform.spatialfilter.operations.bbox"}, + {"id": "CONTAINS", "name": "queryform.spatialfilter.operations.contains"}, + {"id": "WITHIN", "name": "queryform.spatialfilter.operations.within"} + ], + "spatialMethodOptions": [ + {"id": "Viewport", "name": "queryform.spatialfilter.methods.viewport"}, + {"id": "BBOX", "name": "queryform.spatialfilter.methods.box"}, + {"id": "Circle", "name": "queryform.spatialfilter.methods.circle"}, + {"id": "Polygon", "name": "queryform.spatialfilter.methods.poly"} + ] + } + + }, { "name": "TOC", "cfg": { "activateQueryTool": true, diff --git a/web/client/plugins/FeatureEditor.jsx b/web/client/plugins/FeatureEditor.jsx index 89d224d3b7..be34447e4c 100644 --- a/web/client/plugins/FeatureEditor.jsx +++ b/web/client/plugins/FeatureEditor.jsx @@ -64,7 +64,7 @@ const FeatureDock = (props = { ); }; const selector = createSelector( - state => get(state, "query.open"), + state => get(state, "featuregrid.open"), resultsSelector, describeSelector, state => get(state, "featuregrid.attributes"), diff --git a/web/client/plugins/FeatureGrid.jsx b/web/client/plugins/FeatureGrid.jsx index d5a17cd28b..91a0063991 100644 --- a/web/client/plugins/FeatureGrid.jsx +++ b/web/client/plugins/FeatureGrid.jsx @@ -7,7 +7,7 @@ */ const {connect} = require('react-redux'); const {selectFeatures, dockSizeFeatures} = require('../actions/featuregrid'); -const {query, closeResponse} = require('../actions/wfsquery'); +const {query, queryClose} = require('../actions/wfsquery'); const {changeMapView} = require('../actions/map'); const {toggleControl} = require('../actions/controls'); @@ -39,7 +39,7 @@ module.exports = { exportAction: () => toggleControl("wfsdownload"), changeMapView, onQuery: query, - onBackToSearch: closeResponse, + onBackToSearch: queryClose, setDockSize: dockSizeFeatures })(require('../components/data/featuregrid_ag/DockedFeatureGrid')), reducers: { diff --git a/web/client/plugins/QueryPanel.jsx b/web/client/plugins/QueryPanel.jsx index 38a04db504..ca97eb94ce 100644 --- a/web/client/plugins/QueryPanel.jsx +++ b/web/client/plugins/QueryPanel.jsx @@ -8,10 +8,12 @@ const PropTypes = require('prop-types'); */ const React = require('react'); const {connect} = require('react-redux'); +const {Button, Glyphicon} = require('react-bootstrap'); const Sidebar = require('react-sidebar').default; const {createSelector} = require('reselect'); const {changeLayerProperties, changeGroupProperties, toggleNode, sortNode, showSettings, hideSettings, updateSettings, updateNode, removeNode} = require('../actions/layers'); +const Message = require('./locale/Message'); const {getLayerCapabilities} = require('../actions/layerCapabilities'); @@ -26,7 +28,7 @@ const LayersUtils = require('../utils/LayersUtils'); const QueryBuilder = require('../components/data/query/QueryBuilder'); const {featureTypeSelectedEpic, wfsQueryEpic, viewportSelectedEpic} = require('../epics/wfsquery'); - +const autocompleteEpics = require('../epics/autocomplete'); const {bindActionCreators} = require('redux'); const { // QueryBuilder action functions @@ -49,7 +51,8 @@ const { changeDwithinValue, zoneGetValues, zoneSearch, - zoneChange + zoneChange, + toggleMenu } = require('../actions/queryform'); const {createQuery} = require('../actions/wfsquery'); @@ -70,19 +73,29 @@ const SmartQueryForm = connect((state) => { groupLevels: state.queryform.groupLevels, groupFields: state.queryform.groupFields, filterFields: state.queryform.filterFields, + attributes: state.query && state.query.typeName && state.query.featureTypes && state.query.featureTypes[state.query.typeName] && state.query.featureTypes[state.query.typeName].attributes, + featureTypeError: state.query && state.query.typeName && state.query.featureTypes && state.query.featureTypes[state.query.typeName] && state.query.featureTypes[state.query.typeName].error, spatialField: state.queryform.spatialField, showDetailsPanel: state.queryform.showDetailsPanel, toolbarEnabled: state.queryform.toolbarEnabled, attributePanelExpanded: state.queryform.attributePanelExpanded, + autocompleteEnabled: state.queryform.autocompleteEnabled, + maxFeaturesWPS: state.queryform.maxFeaturesWPS, spatialPanelExpanded: state.queryform.spatialPanelExpanded, - searchUrl: "http://demo.geo-solutions.it/geoserver/ows?service=WFS", - featureTypeName: "topp:states", + featureTypeConfigUrl: state.query && state.query.url, + searchUrl: state.query && state.query.url, + featureTypeName: state.query && state.query.typeName, ogcVersion: "1.1.0", + params: {typeName: state.query && state.query.typeName}, resultTitle: "Query Result", - showGeneratedFilter: false + showGeneratedFilter: false, + allowEmptyFilter: true, + emptyFilterWarning: true, + maxHeight: state.map && state.map.present && state.map.present.size && state.map.present.size.height }; }, dispatch => { return { + attributeFilterActions: bindActionCreators({ onAddGroupField: addGroupField, onAddFilterField: addFilterField, @@ -92,6 +105,7 @@ const SmartQueryForm = connect((state) => { onUpdateLogicCombo: updateLogicCombo, onRemoveGroupField: removeGroupField, onChangeCascadingValue: changeCascadingValue, + toggleMenu: toggleMenu, onExpandAttributeFilterPanel: expandAttributeFilterPanel }, dispatch), spatialFilterActions: bindActionCreators({ @@ -130,7 +144,7 @@ const tocSelector = createSelector( }) ); -class LayerTree extends React.Component { +class QueryPanel extends React.Component { static propTypes = { id: PropTypes.number, buttonContent: PropTypes.node, @@ -212,7 +226,11 @@ class LayerTree extends React.Component { renderQueryPanel = () => { return (
- + + }/>
); }; @@ -235,7 +253,7 @@ const QueryPanelPlugin = connect(tocSelector, { updateSettings, updateNode, removeNode -})(LayerTree); +})(QueryPanel); module.exports = { QueryPanelPlugin, @@ -243,5 +261,5 @@ module.exports = { queryform: require('../reducers/queryform'), query: require('../reducers/query') }, - epics: {featureTypeSelectedEpic, wfsQueryEpic, viewportSelectedEpic} + epics: {featureTypeSelectedEpic, wfsQueryEpic, viewportSelectedEpic, ...autocompleteEpics} }; diff --git a/web/client/plugins/TOC.jsx b/web/client/plugins/TOC.jsx index 43a381d981..e21cf189b3 100644 --- a/web/client/plugins/TOC.jsx +++ b/web/client/plugins/TOC.jsx @@ -10,10 +10,9 @@ const React = require('react'); const {connect} = require('react-redux'); const {createSelector} = require('reselect'); const {Button, Glyphicon} = require('react-bootstrap'); -const autocompleteEpics = require('../epics/autocomplete'); const {changeLayerProperties, changeGroupProperties, toggleNode, contextNode, - sortNode, showSettings, hideSettings, updateSettings, updateNode, removeNode} = require('../actions/layers'); + sortNode, showSettings, hideSettings, updateSettings, updateNode, removeNode, browseData} = require('../actions/layers'); const {getLayerCapabilities} = require('../actions/layerCapabilities'); const {zoomToExtent} = require('../actions/map'); const {groupsSelector, layersSelector} = require('../selectors/layers'); @@ -27,116 +26,12 @@ const assign = require('object-assign'); const layersIcon = require('./toolbar/assets/img/layers.png'); -// include application component -const QueryBuilder = require('../components/data/query/QueryBuilder'); - const {isObject} = require('lodash'); -const {bindActionCreators} = require('redux'); -const { - // QueryBuilder action functions - addGroupField, - addFilterField, - removeFilterField, - updateFilterField, - updateExceptionField, - updateLogicCombo, - removeGroupField, - changeCascadingValue, - expandAttributeFilterPanel, - expandSpatialFilterPanel, - selectSpatialMethod, - selectViewportSpatialMethod, - selectSpatialOperation, - removeSpatialSelection, - showSpatialSelectionDetails, - reset, - changeDwithinValue, - zoneGetValues, - zoneSearch, - zoneChange, - toggleMenu -} = require('../actions/queryform'); - const {toggleControl, setControlProperty} = require('../actions/controls'); const {refreshLayers} = require('../actions/layers'); -const {createQuery, toggleQueryPanel} = require('../actions/wfsquery'); - -const { - changeDrawingStatus, - endDrawing -} = require('../actions/draw'); - -// connecting a Dumb component to the store -// makes it a smart component -// we both connect state => props -// and actions to event handlers -const SmartQueryForm = connect((state) => { - return { - // QueryBuilder props - useMapProjection: state.queryform.useMapProjection, - groupLevels: state.queryform.groupLevels, - groupFields: state.queryform.groupFields, - filterFields: state.queryform.filterFields, - attributes: state.query && state.query.typeName && state.query.featureTypes && state.query.featureTypes[state.query.typeName] && state.query.featureTypes[state.query.typeName].attributes, - featureTypeError: state.query && state.query.typeName && state.query.featureTypes && state.query.featureTypes[state.query.typeName] && state.query.featureTypes[state.query.typeName].error, - spatialField: state.queryform.spatialField, - showDetailsPanel: state.queryform.showDetailsPanel, - toolbarEnabled: state.queryform.toolbarEnabled, - attributePanelExpanded: state.queryform.attributePanelExpanded, - autocompleteEnabled: state.queryform.autocompleteEnabled, - maxFeaturesWPS: state.queryform.maxFeaturesWPS, - spatialPanelExpanded: state.queryform.spatialPanelExpanded, - featureTypeConfigUrl: state.query && state.query.url, - searchUrl: state.query && state.query.url, - featureTypeName: state.query && state.query.typeName, - ogcVersion: "1.1.0", - params: {typeName: state.query && state.query.typeName}, - resultTitle: "Query Result", - showGeneratedFilter: false, - allowEmptyFilter: true, - emptyFilterWarning: true, - maxHeight: state.map && state.map.present && state.map.present.size && state.map.present.size.height - }; -}, dispatch => { - return { - - attributeFilterActions: bindActionCreators({ - onAddGroupField: addGroupField, - onAddFilterField: addFilterField, - onRemoveFilterField: removeFilterField, - onUpdateFilterField: updateFilterField, - onUpdateExceptionField: updateExceptionField, - onUpdateLogicCombo: updateLogicCombo, - onRemoveGroupField: removeGroupField, - onChangeCascadingValue: changeCascadingValue, - toggleMenu: toggleMenu, - onExpandAttributeFilterPanel: expandAttributeFilterPanel - }, dispatch), - spatialFilterActions: bindActionCreators({ - onExpandSpatialFilterPanel: expandSpatialFilterPanel, - onSelectSpatialMethod: selectSpatialMethod, - onSelectViewportSpatialMethod: selectViewportSpatialMethod, - onSelectSpatialOperation: selectSpatialOperation, - onChangeDrawingStatus: changeDrawingStatus, - onRemoveSpatialSelection: removeSpatialSelection, - onShowSpatialSelectionDetails: showSpatialSelectionDetails, - onEndDrawing: endDrawing, - onChangeDwithinValue: changeDwithinValue, - zoneFilter: zoneGetValues, - zoneSearch, - zoneChange - }, dispatch), - queryToolbarActions: bindActionCreators({ - onQuery: createQuery, - onReset: reset, - onChangeDrawingStatus: changeDrawingStatus - }, dispatch) - }; -})(QueryBuilder); - const refreshSelector = createSelector([ (state) => state.controls && state.controls.RefreshLayers || {}, layersSelector, @@ -179,13 +74,11 @@ const tocSelector = createSelector( (state) => state.controls && state.controls.toolbar && state.controls.toolbar.active === 'toc', groupsSelector, (state) => state.layers && state.layers.settings || {expanded: false, options: {opacity: 1}}, - (state) => state.controls && state.controls.queryPanel && state.controls.queryPanel.enabled || false, mapSelector - ], (enabled, groups, settings, querypanelEnabled, map) => ({ + ], (enabled, groups, settings, map) => ({ enabled, groups, settings, - querypanelEnabled, currentZoomLvl: map && map.zoom, scales: mapUtils.getScales( map && map.projection || 'EPSG:3857', @@ -205,7 +98,6 @@ class LayerTree extends React.Component { buttonContent: PropTypes.node, groups: PropTypes.array, settings: PropTypes.object, - querypanelEnabled: PropTypes.bool, refreshMapEnabled: PropTypes.bool, groupStyle: PropTypes.object, groupPropertiesChangeHandler: PropTypes.func, @@ -213,7 +105,7 @@ class LayerTree extends React.Component { onToggleGroup: PropTypes.func, onToggleLayer: PropTypes.func, onContextMenu: PropTypes.func, - onToggleQuery: PropTypes.func, + onBrowseData: PropTypes.func, onZoomToExtent: PropTypes.func, retrieveLayerData: PropTypes.func, onSort: PropTypes.func, @@ -267,7 +159,6 @@ class LayerTree extends React.Component { closeGlyph: "1-close", buttonSize: "small" }, - querypanelEnabled: false, refreshMapEnabled: false, layerOptions: {}, groupOptions: {}, @@ -313,7 +204,7 @@ class LayerTree extends React.Component { settingsOptions={this.props.settingsOptions} onToggle={this.props.onToggleLayer} onContextMenu={this.props.onContextMenu} - onToggleQuerypanel={this.props.onToggleQuery } + onBrowseData={this.props.onBrowseData} onZoom={this.props.onZoomToExtent} onSettings={this.props.onSettings} onRefresh={this.props.onRefreshLayer} @@ -351,25 +242,10 @@ class LayerTree extends React.Component { ); }; - renderQueryPanel = () => { - return ( -
- - }/> -
- ); - }; - render() { if (!this.props.groups) { return
; } - if (this.props.querypanelEnabled) { - return this.renderQueryPanel(); - } return this.renderTOC(); } } @@ -403,7 +279,7 @@ const TOCPlugin = connect(tocSelector, { onToggleGroup: LayersUtils.toggleByType('groups', toggleNode), onToggleLayer: LayersUtils.toggleByType('layers', toggleNode), onContextMenu: contextNode, - onToggleQuery: toggleQueryPanel, + onBrowseData: browseData, onSort: LayersUtils.sortUsing(LayersUtils.sortLayers, sortNode), onSettings: showSettings, onRefresh: toggleControl.bind(null, 'RefreshLayers', 'enabled'), @@ -448,5 +324,5 @@ module.exports = { queryform: require('../reducers/queryform'), query: require('../reducers/query') }, - epics: assign({}, {refresh}, autocompleteEpics) + epics: assign({}, {refresh}) }; diff --git a/web/client/plugins/featuregrid/panels/index.jsx b/web/client/plugins/featuregrid/panels/index.jsx index 2a6141a887..a1ac9f8b51 100644 --- a/web/client/plugins/featuregrid/panels/index.jsx +++ b/web/client/plugins/featuregrid/panels/index.jsx @@ -5,8 +5,7 @@ const {createSelector, createStructuredSelector} = require('reselect'); const {paginationInfo, featureLoadingSelector} = require('../../../selectors/query'); const {getTitleSelector, modeSelector, selectedFeaturesCount, hasChangesSelector, hasGeometrySelector, isSimpleGeomSelector, hasNewFeaturesSelector, isSavingSelector, isSavedSelector, isDrawingSelector, canEditSelector} = require('../../../selectors/featuregrid'); const {isAdminUserSelector} = require('../../../selectors/security'); -const {deleteFeatures, toggleTool, clearChangeConfirmed, closeFeatureGridConfirmed} = require('../../../actions/featuregrid'); -const {closeResponse} = require('../../../actions/wfsquery'); +const {deleteFeatures, toggleTool, clearChangeConfirmed, closeFeatureGridConfirmed, closeFeatureGrid} = require('../../../actions/featuregrid'); const {toolbarEvents, pageEvents} = require('../index'); const EmptyRowsView = connect(createStructuredSelector({ @@ -65,7 +64,7 @@ const ClearDialog = connect( const FeatureCloseDialog = connect(() => {} , { onClose: () => closeFeatureGridConfirmed(), - onConfirm: () => closeResponse() + onConfirm: () => closeFeatureGrid() })(require('../../../components/data/featuregrid/dialog/ConfirmFeatureClose')); const panels = { diff --git a/web/client/plugins/featuregrid/toolbarEvents.js b/web/client/plugins/featuregrid/toolbarEvents.js index fdf4c6ec61..fada9952fa 100644 --- a/web/client/plugins/featuregrid/toolbarEvents.js +++ b/web/client/plugins/featuregrid/toolbarEvents.js @@ -2,12 +2,13 @@ const {toggleControl} = require('../../actions/controls'); const {toggleTool, toggleEditMode, toggleViewMode, - closeFeatureGrid, + closeFeatureGridConfirm, saveChanges, createNewFeatures, startEditingFeature, startDrawingFeature, - deleteGeometry + deleteGeometry, + openAdvancedSearch } = require('../../actions/featuregrid'); module.exports = { @@ -22,5 +23,6 @@ module.exports = { startEditingFeature: () => startEditingFeature(), startDrawingFeature: () => startDrawingFeature(), switchViewMode: () => toggleViewMode(), - onClose: () => closeFeatureGrid() + onClose: () => closeFeatureGridConfirm(), + showQueryPanel: () => openAdvancedSearch() }; diff --git a/web/client/reducers/__tests__/featuregrid-test.js b/web/client/reducers/__tests__/featuregrid-test.js index 50c2009a97..e1ddb91926 100644 --- a/web/client/reducers/__tests__/featuregrid-test.js +++ b/web/client/reducers/__tests__/featuregrid-test.js @@ -46,8 +46,8 @@ const expect = require('expect'); const featuregrid = require('../featuregrid'); const {setFeatures, dockSizeFeatures, setLayer, toggleTool, customizeAttribute, selectFeatures, deselectFeatures, createNewFeatures, featureSaving, toggleSelection, clearSelection, MODES, toggleEditMode, toggleViewMode, saveSuccess, clearChanges, saveError, startDrawingFeature, - deleteGeometryFeature, geometryChanged, setSelectionOptions, changePage, featureModified, setPermission, disableToolbar} = require('../../actions/featuregrid'); -const {featureTypeLoaded, featureClose} = require('../../actions/wfsquery'); + deleteGeometryFeature, geometryChanged, setSelectionOptions, changePage, featureModified, setPermission, disableToolbar, openFeatureGrid, closeFeatureGrid} = require('../../actions/featuregrid'); +const {featureTypeLoaded} = require('../../actions/wfsquery'); const {changeDrawingStatus} = require('../../actions/draw'); const museam = require('json-loader!../../test-resources/wfs/museam.json'); @@ -64,6 +64,17 @@ describe('Test the featuregrid reducer', () => { expect(state.select).toExist(); expect(state.features).toExist(); }); + it('openFeatureGrid', () => { + let state = featuregrid(undefined, openFeatureGrid()); + expect(state).toExist(); + expect(state.open).toBe(true); + }); + it('closeFeatureGrid', () => { + let state = featuregrid(undefined, closeFeatureGrid()); + expect(state).toExist(); + expect(state.open).toBe(false); + expect(state.mode).toBe(MODES.VIEW); + }); it('selectFeature', () => { // TODO FIX this test or the reducer @@ -219,15 +230,7 @@ describe('Test the featuregrid reducer', () => { let state = featuregrid( {}, setPermission({canEdit: true})); expect(state.canEdit).toBe(true); }); - it('featureClose', () => { - let state = featuregrid( {pagination: {size: 3}}, featureClose()); - expect(state.drawing).toBe(false); - expect(state.deleteConfirm).toBe(false); - expect(state.pagination.size).toBe(3); - expect(state.newFeatures.length).toBe(0); - expect(state.changes.length).toBe(0); - expect(state.select.length).toBe(0); - }); + it('CHANGE_DRAWING_STATUS', () => { let state = featuregrid( {}, changeDrawingStatus("clean")); expect(state.drawing).toBe(false); diff --git a/web/client/reducers/featuregrid.js b/web/client/reducers/featuregrid.js index f133f4422a..fa47ef06c2 100644 --- a/web/client/reducers/featuregrid.js +++ b/web/client/reducers/featuregrid.js @@ -6,7 +6,7 @@ * LICENSE file in the root directory of this source tree. */ const assign = require("object-assign"); -const {head} = require("lodash"); +const {head, get} = require("lodash"); const { SELECT_FEATURES, DESELECT_FEATURES, @@ -30,11 +30,12 @@ const { DELETE_GEOMETRY_FEATURE, START_DRAWING_FEATURE, SET_PERMISSION, - DISABLE_TOOLBAR + DISABLE_TOOLBAR, + OPEN_FEATURE_GRID, + CLOSE_FEATURE_GRID } = require('../actions/featuregrid'); const{ - FEATURE_TYPE_LOADED, - FEATURE_CLOSE + FEATURE_TYPE_LOADED } = require('../actions/wfsquery'); const{ CHANGE_DRAWING_STATUS @@ -42,6 +43,7 @@ const{ const uuid = require('uuid'); const emptyResultsState = { + open: false, canEdit: false, focusOnEdit: true, mode: MODES.VIEW, @@ -221,7 +223,7 @@ function featuregrid(state = emptyResultsState, action) { } case FEATURE_TYPE_LOADED: { return assign({}, state, { - localType: action.featureType.original.featureTypes[0].properties[1].localType + localType: get(action, "featureType.original.featureTypes[0].properties[1].localType") }); } case START_DRAWING_FEATURE: { @@ -229,8 +231,14 @@ function featuregrid(state = emptyResultsState, action) { drawing: !state.drawing }); } - case FEATURE_CLOSE: { + case OPEN_FEATURE_GRID: { return assign({}, state, { + open: true + }); + } + case CLOSE_FEATURE_GRID: { + return assign({}, state, { + open: false, pagination: { page: 0, size: state.pagination.size @@ -263,6 +271,7 @@ function featuregrid(state = emptyResultsState, action) { } return state; } + default: return state; } diff --git a/web/client/reducers/query.js b/web/client/reducers/query.js index 967ea57e29..f8549665f6 100644 --- a/web/client/reducers/query.js +++ b/web/client/reducers/query.js @@ -16,8 +16,7 @@ const { QUERY_CREATE, QUERY_RESULT, QUERY_ERROR, - RESET_QUERY, - FEATURE_CLOSE + RESET_QUERY } = require('../actions/wfsquery'); const {QUERY_FORM_RESET} = require('../actions/queryform'); @@ -88,7 +87,6 @@ function query(state = initialState, action) { } case QUERY_CREATE: { return assign({}, state, { - open: true, isNew: true, searchUrl: action.searchUrl, filterObj: action.filterObj @@ -113,7 +111,6 @@ function query(state = initialState, action) { case RESET_CONTROLS: case QUERY_FORM_RESET: return assign({}, state, { - open: false, isNew: false, result: null, filterObj: null, @@ -125,11 +122,6 @@ function query(state = initialState, action) { resultError: null }); } - case FEATURE_CLOSE: { - return assign({}, state, { - open: false - }); - } default: return state; } diff --git a/web/client/selectors/__tests__/featuregrid-test.js b/web/client/selectors/__tests__/featuregrid-test.js index fcc136187b..e44fef435f 100644 --- a/web/client/selectors/__tests__/featuregrid-test.js +++ b/web/client/selectors/__tests__/featuregrid-test.js @@ -250,7 +250,6 @@ let initialState = { } }, resultError: null, - open: true, isNew: false, filterObj: { featureTypeName: 'editing:polygons', @@ -283,6 +282,7 @@ let initialState = { featureLoading: false }, featuregrid: { + open: true, selectedLayer: "TEST_LAYER", mode: modeEdit, select: [feature1, feature2], @@ -304,6 +304,7 @@ describe('Test featuregrid selectors', () => { afterEach(() => { initialState = assign({}, initialState, { featuregrid: { + open: true, saving: false, saved: false, selectedLayer: "TEST_LAYER", diff --git a/web/client/selectors/__tests__/query-test.js b/web/client/selectors/__tests__/query-test.js index c80be10acd..6d50497e25 100644 --- a/web/client/selectors/__tests__/query-test.js +++ b/web/client/selectors/__tests__/query-test.js @@ -13,6 +13,7 @@ const { resultsSelector, paginationInfo, featureLoadingSelector, + isDescribeLoaded, describeSelector, getFeatureById, attributesSelector @@ -322,6 +323,10 @@ describe('Test query selectors', () => { const describe = describeSelector(initialState); expect(describe.elementFormDefault).toBe("qualified"); }); + it('test isDescribeLoaded', () => { + const isLoaded = isDescribeLoaded(initialState, "editing:polygons"); + expect(isLoaded).toBe(true); + }); it('test getFeatureById selector', () => { const ft = getFeatureById(initialState, "poligoni.7"); expect(ft).toExist(); diff --git a/web/client/selectors/featuregrid.js b/web/client/selectors/featuregrid.js index be546e3c53..5810cc4092 100644 --- a/web/client/selectors/featuregrid.js +++ b/web/client/selectors/featuregrid.js @@ -46,7 +46,7 @@ const hasGeometrySelectedFeature = (state) => { const hasChangesSelector = state => changesSelector(state) && changesSelector(state).length > 0; const hasNewFeaturesSelector = state => newFeaturesSelector(state) && newFeaturesSelector(state).length > 0; module.exports = { - isFeatureGridOpen: state => state && state.query && state.query.open, // TODO move open in featuregrid from query reducer + isFeatureGridOpen: state => state && state.featuregrid && state.featuregrid.open, selectedLayerIdSelector, getTitleSelector: state => getTitle( getLayerById( diff --git a/web/client/selectors/query.js b/web/client/selectors/query.js index 716039fe08..07cb0f8909 100644 --- a/web/client/selectors/query.js +++ b/web/client/selectors/query.js @@ -14,6 +14,7 @@ module.exports = { resultSize: (state) =>get(state, "query.result.features.length"), totalFeatures: (state) => get(state, "query.result.totalFeatures") }, + isDescribeLoaded: (state, name) => !!get(state, `query.featureTypes.${name}`), describeSelector: (state) => get(state, `query.featureTypes.${get(state, "query.filterObj.featureTypeName")}.original`), featureLoadingSelector: (state) => get(state, "query.featureLoading") }; diff --git a/web/client/translations/data.de-DE b/web/client/translations/data.de-DE index cf38c1e0a9..145025639a 100644 --- a/web/client/translations/data.de-DE +++ b/web/client/translations/data.de-DE @@ -216,6 +216,7 @@ "displayLegendAndTools": "Legende und Werkzeuge zeigen", "zoomToLayerExtent": "Zoome auf Ausdehung der Ebene", "editLayerProperties": "Ändere Ebenen Eigenschaften", + "browseData": "öffnen Sie die Attributtabelle", "searchFeatures": "Auf dieser Ebene suchen...", "removeLayer": "Ebene entfernen", "loadingerror": "Diese Ebene wurde nicht korrekt geladen", @@ -830,6 +831,7 @@ "missingGeometry": "Missing geometry", "toolbar": { "editMode": "Bearbeitungsmodus", + "advancedFilter": "Erweiterte Suche", "quitEditMode": "Beenden Sie den Bearbeitungsmodus", "addNewFeatures": "Neue Funktion hinzufügen", "editFeature": "Bearbeiten Sie die Funktion", diff --git a/web/client/translations/data.en-US b/web/client/translations/data.en-US index 325adac603..080b555489 100644 --- a/web/client/translations/data.en-US +++ b/web/client/translations/data.en-US @@ -216,7 +216,7 @@ "displayLegendAndTools": "Display legend and tools", "zoomToLayerExtent": "Zoom to layer extent", "editLayerProperties": "Edit layer properties", - "searchFeatures": "Search on this layer...", + "browseData": "Open Attribute Table", "removeLayer": "Remove layer", "loadingerror": "The layer has not been loaded correctly", "measure": "Measure", @@ -830,6 +830,7 @@ "missingGeometry": "Missing geometry", "toolbar": { "editMode": "Edit mode", + "advancedFilter": "Advanced Search", "quitEditMode": "Quit edit mode", "addNewFeatures": "Add New feature", "editFeature": "Edit feature", diff --git a/web/client/translations/data.es-ES b/web/client/translations/data.es-ES index c9a1d3eba4..e5c3214d62 100644 --- a/web/client/translations/data.es-ES +++ b/web/client/translations/data.es-ES @@ -218,6 +218,7 @@ "displayLegendAndTools": "Mostrar la leyenda y las herramientas", "zoomToLayerExtent": "Zoom sobre la extensión del mapa", "editLayerProperties": "Modificar las propiedades de la capa", + "browseData": "Abrir la tabla de atributos", "searchFeatures": "Buscar en el mapa ...", "removeLayer": "Borrar la capa", "loadingerror": "La capa se ha cargado correctamente", @@ -833,6 +834,7 @@ "missingGeometry": "Missing geometry", "toolbar": { "editMode": "Modo de edición", + "advancedFilter": "Búsqueda avanzada", "quitEditMode": "Salir del modo de edición", "addNewFeatures": "Añadir nueva función", "editFeature": "Función de edición", diff --git a/web/client/translations/data.fr-FR b/web/client/translations/data.fr-FR index bc4e0ceedb..81f299e454 100644 --- a/web/client/translations/data.fr-FR +++ b/web/client/translations/data.fr-FR @@ -218,6 +218,7 @@ "displayLegendAndTools": "Afficher la légende et les outils", "zoomToLayerExtent": "Zoomer sur l'étendue du calque", "editLayerProperties": "Modifier les propriétés de la couche", + "browseData": "Ouvrir la table d'attribut", "searchFeatures": "Recherche sur ce calque ...", "removeLayer": "Supprimer la couche", "loadingerror": "La couche n'a pas été chargée correctement", @@ -833,6 +834,7 @@ "missingGeometry": "Missing geometry", "toolbar": { "editMode": "Mode édition", + "advancedFilter": "Recherche Avancée", "quitEditMode": "Quitter le mode d'édition", "addNewFeatures": "Ajouter un nouvel élément", "editFeature": "Élément d'édition", diff --git a/web/client/translations/data.it-IT b/web/client/translations/data.it-IT index 5a8be1f4fc..cb321b7b5c 100644 --- a/web/client/translations/data.it-IT +++ b/web/client/translations/data.it-IT @@ -216,6 +216,7 @@ "displayLegendAndTools": "Visualizza legenda e strumenti aggiuntivi", "zoomToLayerExtent": "Zoom all' estensione del livello", "editLayerProperties": "Modifica proprietà dal livello", + "browseData": "Apri tabella degli attributi", "searchFeatures": "Effettua una ricerca su questo livello...", "removeLayer": "Rimuovi livello", "loadingerror": "Il livello non è stato caricato correttamente", @@ -830,6 +831,7 @@ "missingGeometry": "Geometria mancante", "toolbar": { "editMode": "Entra in modalità editing", + "advancedFilter": "Ricerca avanzata", "quitEditMode": "Esci dalla modalità editing", "addNewFeatures": "Aggiungi una nuova feature", "editFeature": "Modifica la feature",