diff --git a/web/client/actions/__tests__/mapPopups-test.js b/web/client/actions/__tests__/mapPopups-test.js
index 18296feba6..1af4ecc32a 100644
--- a/web/client/actions/__tests__/mapPopups-test.js
+++ b/web/client/actions/__tests__/mapPopups-test.js
@@ -26,5 +26,9 @@ describe('test map popups action creators', () => {
const action = POPUP.cleanPopups();
expect(action.type).toEqual(POPUP.CLEAN_MAP_POPUPS);
});
+ it('enable hide empty popup option', () => {
+ const action = POPUP.enableHideEmptyPopupOption();
+ expect(action.type).toEqual(POPUP.ENABLE_HIDE_EMPTY_POPUP);
+ });
});
diff --git a/web/client/actions/mapPopups.js b/web/client/actions/mapPopups.js
index ed001897fc..66ab51c315 100644
--- a/web/client/actions/mapPopups.js
+++ b/web/client/actions/mapPopups.js
@@ -10,6 +10,7 @@
export const ADD_MAP_POPUP = 'MAP:ADD_POPUP';
export const REMOVE_MAP_POPUP = 'MAP:REMOVE_POPUP';
export const CLEAN_MAP_POPUPS = 'MAP:CLEAN_POPUPS';
+export const ENABLE_HIDE_EMPTY_POPUP = 'MAP:ENABLE_HIDE_EMPTY_POPUP';
export const addPopup = (id, options, single = true) => ({
type: ADD_MAP_POPUP,
@@ -26,3 +27,7 @@ export const removePopup = (id) => ({
export const cleanPopups = () => ({
type: CLEAN_MAP_POPUPS
});
+
+export const enableHideEmptyPopupOption = () => ({
+ type: ENABLE_HIDE_EMPTY_POPUP
+});
diff --git a/web/client/components/data/identify/DefaultViewer.jsx b/web/client/components/data/identify/DefaultViewer.jsx
index 8a18d5330c..ff02bfbc6d 100644
--- a/web/client/components/data/identify/DefaultViewer.jsx
+++ b/web/client/components/data/identify/DefaultViewer.jsx
@@ -40,7 +40,8 @@ class DefaultViewer extends React.Component {
renderValidOnly: PropTypes.bool,
loaded: PropTypes.bool,
isMobile: PropTypes.bool,
- disableInfoAlert: PropTypes.bool
+ disableInfoAlert: PropTypes.bool,
+ hidePopupIfNoResults: PropTypes.bool
};
static defaultProps = {
@@ -64,7 +65,8 @@ class DefaultViewer extends React.Component {
onPrevious: () => {},
setIndex: () => {},
isMobile: false,
- disableInfoAlert: false
+ disableInfoAlert: false,
+ hidePopupIfNoResults: false
};
shouldComponentUpdate(nextProps) {
@@ -147,6 +149,9 @@ class DefaultViewer extends React.Component {
renderEmptyPages = () => {
const {emptyResponses} = this.getResponseProperties();
if (this.props.missingResponses === 0 && emptyResponses) {
+ if (this.props.hidePopupIfNoResults) {
+ return ;
+ }
return (
diff --git a/web/client/components/data/identify/PopupViewer.jsx b/web/client/components/data/identify/PopupViewer.jsx
index 399467a0e1..1f16a19afa 100644
--- a/web/client/components/data/identify/PopupViewer.jsx
+++ b/web/client/components/data/identify/PopupViewer.jsx
@@ -17,6 +17,7 @@ import Viewer from './DefaultViewer';
import {isArray, isUndefined} from 'lodash';
import SwipeHeader from './SwipeHeader';
import { identifyFloatingToolSelector } from '../../../selectors/map';
+import { hideEmptyPopupSelector } from '../../../selectors/mapPopups';
/**
* Container that render only the selected result
@@ -46,8 +47,8 @@ const selector = createSelector([
generalInfoFormatSelector,
showEmptyMessageGFISelector,
identifyFloatingToolSelector,
- isLoadedResponseSelector],
-(responses, validResponses, requests, format, showEmptyMessageGFI, renderValidOnly, loaded) => ({
+ isLoadedResponseSelector, hideEmptyPopupSelector],
+(responses, validResponses, requests, format, showEmptyMessageGFI, renderValidOnly, loaded, hidePopupIfNoResults ) => ({
responses,
validResponses,
requests,
@@ -55,7 +56,8 @@ const selector = createSelector([
showEmptyMessageGFI,
missingResponses: (requests || []).length - (responses || []).length,
renderValidOnly,
- loaded
+ loaded,
+ hidePopupIfNoResults
}));
diff --git a/web/client/components/data/identify/__tests__/DefaultViewer-test.jsx b/web/client/components/data/identify/__tests__/DefaultViewer-test.jsx
index 9fbe93fffd..3c8f19a47e 100644
--- a/web/client/components/data/identify/__tests__/DefaultViewer-test.jsx
+++ b/web/client/components/data/identify/__tests__/DefaultViewer-test.jsx
@@ -269,4 +269,18 @@ describe('DefaultViewer', () => {
expect(gfiViewer.childNodes[1].childNodes.length).toBe(1);
});
+ it('test DefaultViewer component with hover identify if hidePopupIfNoResults = true', () => {
+ const responses = [];
+ ReactDOM.render(
+ ,
+ document.getElementById("container")
+ );
+
+ const container = document.getElementById('container');
+ let gfiViewer = container.querySelector('.mapstore-identify-viewer');
+ expect(gfiViewer).toBeTruthy();
+ expect(gfiViewer.childNodes.length).toBe(1);
+ expect(document.querySelector(".hidePopupIfNoResults")).toBeTruthy();
+ expect(document.querySelector(".hidePopupIfNoResults").innerHTML).toBeFalsy();
+ });
});
diff --git a/web/client/components/data/identify/enhancers/__tests__/identify-test.jsx b/web/client/components/data/identify/enhancers/__tests__/identify-test.jsx
index 1ba1708dc8..27369942cb 100644
--- a/web/client/components/data/identify/enhancers/__tests__/identify-test.jsx
+++ b/web/client/components/data/identify/enhancers/__tests__/identify-test.jsx
@@ -187,6 +187,18 @@ describe("test identify enhancers", () => {
);
expect(spyIdentifyIsMounted.calls.length).toEqual(1);
});
+ it("test identifyLifecycle component for call enableHideEmptyPopupOption if hidePopupIfNoResults prop = true", () => {
+ const Component = identifyLifecycle(() => );
+ const testHandlers = {
+ enableHideEmptyPopupOption: () => {}
+ };
+ const spyEnableHideEmptyPopupOption = expect.spyOn(testHandlers, 'enableHideEmptyPopupOption');
+ ReactDOM.render(
+ ,
+ document.getElementById("container")
+ );
+ expect(spyEnableHideEmptyPopupOption.calls.length).toEqual(1);
+ });
it("Identify should run when enabled prop is true and showInMapPopup prop is false", () => {
let run = sampleComponentDidMount({enabled: true, showInMapPopup: false});
expect(run.checkIdentifyIsMounted).toBe(true);
diff --git a/web/client/components/data/identify/enhancers/identify.js b/web/client/components/data/identify/enhancers/identify.js
index 03724ec45e..fd66c04056 100644
--- a/web/client/components/data/identify/enhancers/identify.js
+++ b/web/client/components/data/identify/enhancers/identify.js
@@ -79,7 +79,9 @@ export const identifyLifecycle = compose(
setShowInMapPopup = () => {},
checkIdentifyIsMounted = () => {},
onInitPlugin = () => {},
- pluginCfg = {}
+ pluginCfg = {},
+ enableHideEmptyPopupOption = () => {},
+ hidePopupIfNoResults = false
} = this.props;
// Initialize plugin configuration
@@ -91,6 +93,9 @@ export const identifyLifecycle = compose(
showAllResponses,
highlight: pluginCfg?.highlightEnabledFromTheStart || false
});
+ if (hidePopupIfNoResults) {
+ enableHideEmptyPopupOption(true);
+ }
if (enabled || showInMapPopup) {
changeMousePointer('pointer');
checkIdentifyIsMounted(true);
diff --git a/web/client/epics/__tests__/identify-test.js b/web/client/epics/__tests__/identify-test.js
index 613bb39779..7850e04d0f 100644
--- a/web/client/epics/__tests__/identify-test.js
+++ b/web/client/epics/__tests__/identify-test.js
@@ -965,6 +965,54 @@ describe('identify Epics', () => {
testEpic(zoomToVisibleAreaEpic, 3, sentActions, expectedAction, state);
});
+ it('test zoomToVisibleAreaEpic remove shown marker of identify if no results + existing hideEmptyPopupOption flag = true', (done) => {
+ // remove previous hook
+ registerHook('RESOLUTION_HOOK', undefined);
+
+ const state = {
+ mapInfo: {
+ centerToMarker: true
+ },
+ mapPopups: {
+ hideEmptyPopupOption: true
+ },
+ map: {present: {...TEST_MAP_STATE.present, eventListeners: {mousemove: ["identifyFloatingTool"]}}},
+ maplayout: {
+ boundingMapRect: {
+ left: 500,
+ bottom: 250
+ }
+ }
+ };
+
+ const sentActions = [
+ featureInfoClick({ latlng: { lat: 36.95, lng: -79.84 } }),
+ loadFeatureInfo(1, "no features were found")
+ ];
+
+ const expectedAction = actions => {
+ try {
+ expect(actions.length).toBe(2);
+ actions.map((action) => {
+ switch (action.type) {
+ case HIDE_MAPINFO_MARKER:
+ done();
+ break;
+ case UPDATE_CENTER_TO_MARKER:
+ expect(action.status).toBe('disabled');
+ break;
+ default:
+ expect(true).toBe(false);
+ }
+ });
+ } catch (ex) {
+ done(ex);
+ }
+ done();
+ };
+
+ testEpic(zoomToVisibleAreaEpic, 2, sentActions, expectedAction, state);
+ });
it('onMapClick triggers featureinfo when selected', done => {
registerHook(GET_COORDINATES_FROM_PIXEL_HOOK, undefined);
diff --git a/web/client/epics/identify.js b/web/client/epics/identify.js
index e375336595..2ce441c643 100644
--- a/web/client/epics/identify.js
+++ b/web/client/epics/identify.js
@@ -51,6 +51,7 @@ import { floatingIdentifyDelaySelector } from '../selectors/localConfig';
import { createControlEnabledSelector, measureSelector } from '../selectors/controls';
import { localizedLayerStylesEnvSelector } from '../selectors/localizedLayerStyles';
import { mouseOutSelector } from '../selectors/mousePosition';
+import { hideEmptyPopupSelector } from '../selectors/mapPopups';
import {getBbox, getCurrentResolution, parseLayoutValue} from '../utils/MapUtils';
import {buildIdentifyRequest, defaultQueryableFilter, filterRequestParams} from '../utils/MapInfoUtils';
import { IDENTIFY_POPUP } from '../components/map/popups';
@@ -250,8 +251,15 @@ export const zoomToVisibleAreaEpic = (action$, store) =>
.filter(() => centerToMarkerSelector(store.getState()))
.switchMap((action) =>
action$.ofType(LOAD_FEATURE_INFO, ERROR_FEATURE_INFO)
- .mergeMap(() => {
+ .mergeMap((loadFeatInfoAction) => {
const state = store.getState();
+ const hideIdentifyPopupIfNoResults = hideEmptyPopupSelector(state);
+ const hoverIdentifyActive = isMouseMoveIdentifyActiveSelector(state);
+ const noResultFeatures = loadFeatInfoAction.type === LOAD_FEATURE_INFO && loadFeatInfoAction?.data?.includes("no features were found");
+ // remove marker in case activated identify hover mode and no fetched results plus existing hideIdentifyPopupIfNoResults = true
+ if (noResultFeatures && hideIdentifyPopupIfNoResults && hoverIdentifyActive) {
+ return Rx.Observable.from([updateCenterToMarker('disabled'), hideMapinfoMarker()]);
+ }
const map = mapSelector(state);
const mapProjection = projectionSelector(state);
const projectionDefs = projectionDefsSelector(state);
diff --git a/web/client/plugins/Identify.jsx b/web/client/plugins/Identify.jsx
index 30479ae208..fdce0a4edb 100644
--- a/web/client/plugins/Identify.jsx
+++ b/web/client/plugins/Identify.jsx
@@ -38,6 +38,7 @@ import {
checkIdentifyIsMounted,
onInitPlugin
} from '../actions/mapInfo';
+import { enableHideEmptyPopupOption } from '../actions/mapPopups';
import DefaultViewerComp from '../components/data/identify/DefaultViewer';
import { defaultViewerDefaultProps, defaultViewerHandlers } from '../components/data/identify/enhancers/defaultViewer';
import { identifyLifecycle } from '../components/data/identify/enhancers/identify';
@@ -196,6 +197,7 @@ const identifyDefaultProps = defaultProps({
* @prop cfg.dock {bool} true shows dock panel, false shows modal
* @prop cfg.draggable {boolean} draggable info window, when modal
* @prop cfg.showHighlightFeatureButton {boolean} show the highlight feature button if the interrogation returned valid features (openlayers only)
+ * @prop cfg.hidePopupIfNoResults {boolean} hide/show the identify popup in case of no results
* @prop cfg.highlightEnabledFromTheStart {boolean} the highlight feature button will be activated by default if true
* @prop cfg.viewerOptions.container {expression} the container of the viewer, expression from the context
* @prop cfg.viewerOptions.header {expression} the header of the viewer, expression from the context{expression}
@@ -265,7 +267,8 @@ const IdentifyPlugin = compose(
identifyIndex,
defaultViewerHandlers,
connect(() => ({}), {
- setShowInMapPopup
+ setShowInMapPopup,
+ enableHideEmptyPopupOption
}),
identifyLifecycle
)(IdentifyContainer);
diff --git a/web/client/plugins/map/index.js b/web/client/plugins/map/index.js
index ce519d58a3..6ca6c8765c 100644
--- a/web/client/plugins/map/index.js
+++ b/web/client/plugins/map/index.js
@@ -35,6 +35,7 @@ import { projectionDefsSelector, isMouseMoveActiveSelector } from '../../selecto
import {
snappingLayerSelector
} from "../../selectors/draw";
+import { mapPopupsSelector } from '../../selectors/mapPopups';
const Empty = () => { return ; };
@@ -101,13 +102,11 @@ const pluginsCreator = (mapType, actions) => {
const LLayer = connect(null, {onWarning: warning})( components.Layer || Empty);
- const EMPTY_POPUPS = [];
const PopupSupport = connect(
createSelector(
- (state) => state.mapPopups && state.mapPopups.popups || EMPTY_POPUPS,
- (popups) => ({
- popups
- })), {
+ mapPopupsSelector,
+ (popups) => ({popups})
+ ), {
onPopupClose: removePopup
}
)(components.PopupSupport || Empty);
diff --git a/web/client/reducers/__tests__/mapPopups-test.js b/web/client/reducers/__tests__/mapPopups-test.js
index 71d4fc480a..eb31f6385f 100644
--- a/web/client/reducers/__tests__/mapPopups-test.js
+++ b/web/client/reducers/__tests__/mapPopups-test.js
@@ -37,5 +37,9 @@ describe('mapPopups reducer', () => {
expect(state.popups).toExist();
expect(state.popups.length).toBe(0);
});
-
+ it('ENABLE_HIDE_EMPTY_POPUP ', () => {
+ const state = reducer(initialState, ACTIONS.enableHideEmptyPopupOption());
+ expect(state.popups).toExist();
+ expect(state.hideEmptyPopupOption).toEqual(true);
+ });
});
diff --git a/web/client/reducers/mapPopups.js b/web/client/reducers/mapPopups.js
index b6cbce9b06..1d8103664b 100644
--- a/web/client/reducers/mapPopups.js
+++ b/web/client/reducers/mapPopups.js
@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
-import {ADD_MAP_POPUP, REMOVE_MAP_POPUP, CLEAN_MAP_POPUPS} from '../actions/mapPopups';
+import {ADD_MAP_POPUP, REMOVE_MAP_POPUP, CLEAN_MAP_POPUPS, ENABLE_HIDE_EMPTY_POPUP} from '../actions/mapPopups';
import {arrayDelete} from '../utils/ImmutableUtils';
const initialState = {popups: []};
@@ -23,6 +23,11 @@ export default function(state = initialState, action) {
case CLEAN_MAP_POPUPS: {
return {...state, popups: []};
}
+ case ENABLE_HIDE_EMPTY_POPUP: {
+ return {
+ ...state, hideEmptyPopupOption: true
+ };
+ }
default:
return state;
}
diff --git a/web/client/selectors/__tests__/mapPopups-test.js b/web/client/selectors/__tests__/mapPopups-test.js
new file mode 100644
index 0000000000..ffa0c75278
--- /dev/null
+++ b/web/client/selectors/__tests__/mapPopups-test.js
@@ -0,0 +1,36 @@
+/*
+* Copyright 2024, 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 expect from 'expect';
+
+import {
+ mapPopupsSelector,
+ hideEmptyPopupSelector
+} from '../mapPopups';
+
+describe('Test mapPopups', () => {
+ it('test mapPopupsSelector', () => {
+ const popups = mapPopupsSelector({mapPopups: {popups: [{"key": "value"}]}});
+
+ expect(popups).toExist();
+ expect(popups).toEqual([{key: "value"}]);
+ });
+
+ it('test hideEmptyPopupSelector true', () => {
+ const hideEmptyPopupOption = hideEmptyPopupSelector({mapPopups: {popups: [{"key": "value"}], hideEmptyPopupOption: true}});
+
+ expect(hideEmptyPopupOption).toExist();
+ expect(hideEmptyPopupOption).toBe(true);
+ });
+ it('test hideEmptyPopupSelector false', () => {
+ const hideEmptyPopupOption = hideEmptyPopupSelector({mapPopups: {popups: [{"key": "value"}], hideEmptyPopupOption: false}});
+
+ expect(hideEmptyPopupOption).toBeFalsy();
+ });
+});
diff --git a/web/client/selectors/mapPopups.js b/web/client/selectors/mapPopups.js
new file mode 100644
index 0000000000..2515c2f069
--- /dev/null
+++ b/web/client/selectors/mapPopups.js
@@ -0,0 +1,18 @@
+/*
+* Copyright 2024, 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.
+*/
+
+
+/**
+ * selects mapPopups state
+ * @name mapPopups
+ * @memberof selectors
+ * @static
+ */
+
+export const mapPopupsSelector = (state) => state?.mapPopups && state?.mapPopups?.popups || [];
+export const hideEmptyPopupSelector = (state) => state?.mapPopups && state?.mapPopups?.hideEmptyPopupOption || false;
diff --git a/web/client/themes/default/less/map-popup.less b/web/client/themes/default/less/map-popup.less
index 06fde5d26f..c703a04b8d 100644
--- a/web/client/themes/default/less/map-popup.less
+++ b/web/client/themes/default/less/map-popup.less
@@ -33,7 +33,9 @@
.map-popup-ol:before {
.border-top-color-var(@theme-vars[main-border-color]);
}
-
+ .map-popup-ol:has(.hidePopupIfNoResults), .ms-leaflet-popup:has(.hidePopupIfNoResults) {
+ display: none;
+ }
.ol-popup-closer {
.ms-popup-close-button();
}