From 0270c010277327f38bfd50ea899cb970d5a00ea2 Mon Sep 17 00:00:00 2001 From: Sovas Tiwari Date: Wed, 10 Dec 2025 14:19:57 +0000 Subject: [PATCH 1/3] Update the layout of the map viewer --- .../components/layout/MapViewerLayout.jsx | 37 ++++ web/client/configs/localConfig.json | 8 +- web/client/containers/MapViewer.jsx | 6 +- .../plugins/featuregrid/FeatureEditor.jsx | 159 ++++++++---------- .../plugins/featuregrid/hoc/withResize.jsx | 115 +++++++++++++ web/client/product/assets/css/viewer.css | 11 ++ web/client/reducers/featuregrid.js | 2 +- 7 files changed, 242 insertions(+), 96 deletions(-) create mode 100644 web/client/components/layout/MapViewerLayout.jsx create mode 100644 web/client/plugins/featuregrid/hoc/withResize.jsx diff --git a/web/client/components/layout/MapViewerLayout.jsx b/web/client/components/layout/MapViewerLayout.jsx new file mode 100644 index 00000000000..f9bfc428b1f --- /dev/null +++ b/web/client/components/layout/MapViewerLayout.jsx @@ -0,0 +1,37 @@ +import React from "react"; +import FlexBox, { FlexFill } from "./FlexBox"; + +export default ({ + id, + header, + footer, + background, + leftColumn, + rightColumn, + columns, + className, + top, + bottom, + children, + bodyClassName +}) => { + return ( + + {header} + +
{background}
+
{top}
+ +
{leftColumn}
+ + {children} + +
{rightColumn}
+
{columns}
+
+
{bottom}
+
+ {footer} +
+ ); +}; diff --git a/web/client/configs/localConfig.json b/web/client/configs/localConfig.json index 556e4dc4b6e..6f391b7ba56 100644 --- a/web/client/configs/localConfig.json +++ b/web/client/configs/localConfig.json @@ -409,6 +409,7 @@ { "name": "Map", "cfg": { + "containerPosition": "background", "mapOptions": { "openlayers": { "interactions": { @@ -475,7 +476,12 @@ } }, "Home", - "FeatureEditor", + { + "name": "FeatureEditor", + "cfg": { + "containerPosition": "bottom" + } + }, "LayerDownload", { "name": "QueryPanel", diff --git a/web/client/containers/MapViewer.jsx b/web/client/containers/MapViewer.jsx index fe8e816f939..fda38f9902a 100644 --- a/web/client/containers/MapViewer.jsx +++ b/web/client/containers/MapViewer.jsx @@ -16,9 +16,9 @@ const urlQuery = url.parse(window.location.href, true).query; import ConfigUtils from '../utils/ConfigUtils'; import { getMonitoredState } from '../utils/PluginsUtils'; import ModulePluginsContainer from "../product/pages/containers/ModulePluginsContainer"; -import { createShallowSelectorCreator } from '../utils/ReselectUtils'; -import BorderLayout from '../components/layout/BorderLayout'; +import MapViewerLayout from '../components/layout/MapViewerLayout'; +import { createShallowSelectorCreator } from '../utils/ReselectUtils'; const PluginsContainer = connect( createShallowSelectorCreator(isEqual)( state => state.plugins, @@ -66,7 +66,7 @@ class MapViewer extends React.Component { params={this.props.params} loaderComponent={this.props.loaderComponent} onLoaded={this.props.onLoaded} - component={this.props.component || BorderLayout} + component={this.props.component || MapViewerLayout} />); } } diff --git a/web/client/plugins/featuregrid/FeatureEditor.jsx b/web/client/plugins/featuregrid/FeatureEditor.jsx index 835d7940491..72415d5181f 100644 --- a/web/client/plugins/featuregrid/FeatureEditor.jsx +++ b/web/client/plugins/featuregrid/FeatureEditor.jsx @@ -7,37 +7,27 @@ */ import React, { useMemo } from 'react'; import {connect} from 'react-redux'; -import {createSelector, createStructuredSelector} from 'reselect'; +import {createStructuredSelector} from 'reselect'; import {bindActionCreators} from 'redux'; import { get, pick, isEqual } from 'lodash'; import {compose, lifecycle, defaultProps } from 'recompose'; -import ReactDock from 'react-dock'; import ContainerDimensions from 'react-container-dimensions'; import Grid from '../../components/data/featuregrid/FeatureGrid'; import BorderLayout from '../../components/layout/BorderLayout'; import { toChangesMap} from '../../utils/FeatureGridUtils'; import { sizeChange, setUp, setSyncTool } from '../../actions/featuregrid'; -import {mapLayoutValuesSelector} from '../../selectors/maplayout'; import {paginationInfo, describeSelector, attributesJSONSchemaSelector, wfsURLSelector, typeNameSelector, isSyncWmsActive} from '../../selectors/query'; -import {modeSelector, changesSelector, newFeaturesSelector, hasChangesSelector, selectedLayerFieldsSelector, selectedFeaturesSelector, getDockSize} from '../../selectors/featuregrid'; +import {modeSelector, changesSelector, newFeaturesSelector, hasChangesSelector, selectedLayerFieldsSelector, selectedFeaturesSelector} from '../../selectors/featuregrid'; import {getPanels, getHeader, getFooter, getDialogs, getEmptyRowsView, getFilterRenderers} from './panels/index'; import {gridTools, gridEvents, pageEvents, toolbarEvents} from './index'; import useFeatureValidation from './hooks/useFeatureValidation'; +import withResize from './hoc/withResize'; const EMPTY_ARR = []; const EMPTY_OBJ = {}; -const Dock = connect(createSelector( - getDockSize, - state => mapLayoutValuesSelector(state, {transform: true}), - (size, dockStyle) => ({ - size, - dockStyle - }) -) -)(ReactDock); /** * @name FeatureEditor * @memberof plugins @@ -171,24 +161,13 @@ const Dock = connect(createSelector( * ``` * */ -const FeatureDock = (props = { +const Editor = (props = { tools: EMPTY_OBJ, dialogs: EMPTY_OBJ, select: EMPTY_ARR }) => { const virtualScroll = props.virtualScroll ?? true; const maxZoom = props?.pluginCfg?.maxZoom; - const dockProps = { - dimMode: "none", - defaultSize: 0.35, - fluid: true, - isVisible: props.open, - maxDockSize: 0.7, - minDockSize: 0.1, - position: "bottom", - setDockSize: () => {}, - zIndex: 1060 - }; const items = props?.items ?? []; const toolbarItems = items.filter(({target}) => target === 'toolbar'); const filterRenderers = useMemo(() => { @@ -208,72 +187,70 @@ const FeatureDock = (props = { }); return ( -
- { props.onSizeChange(size, dockProps); }}> - {props.open && - ( - { ({ height }) => - // added height to solve resize issue in firefox, edge and ie - - {getDialogs(props.tools)} - - } - - ) - } - -
); + + { ({ height }) => + // added height to solve resize issue in firefox, edge and ie + + {getDialogs(props.tools)} + + } + + ); }; + +// Wrap Editor with resize HOC +const ResizableEditor = withResize(Editor); + export const selector = createStructuredSelector({ open: state => get(state, "featuregrid.open"), customEditorsOptions: state => get(state, "featuregrid.customEditorsOptions"), @@ -344,6 +321,6 @@ const EditorPlugin = compose( onSizeChange: (...params) => dispatch(sizeChange(...params)) }) ) -)(FeatureDock); +)(ResizableEditor); export default EditorPlugin; diff --git a/web/client/plugins/featuregrid/hoc/withResize.jsx b/web/client/plugins/featuregrid/hoc/withResize.jsx new file mode 100644 index 00000000000..18a070cb694 --- /dev/null +++ b/web/client/plugins/featuregrid/hoc/withResize.jsx @@ -0,0 +1,115 @@ +import React, { useEffect, useRef, useState } from "react"; + +/** + * HOC that wraps a component in a resizable container. + * @param {React.Component} Component - The component to wrap + * @returns {React.Component} A component wrapped in a resizable div + * + * Props: + * @prop {boolean} resizeContainer - If true, enables resize functionality (default: true) + * @prop {number} defaultHeight - Initial height in pixels (default: 300) + * @prop {number} minHeight - Minimum height in pixels (default: 75) + * @prop {number} maxHeight - Maximum height in pixels (default: 70% of the window inner height) + */ +const withResize = (Component) => { + return (props) => { + const { resizeContainer = true, defaultHeight = 300, minHeight = 75, maxHeight = '70%' } = props; + const [height, setHeight] = useState(defaultHeight); + const [isResizing, setIsResizing] = useState(false); + const containerRef = useRef(null); + const startYRef = useRef(0); + const startHeightRef = useRef(0); + + useEffect(() => { + const maxAllowedHeight = typeof maxHeight === 'number' + ? maxHeight + : (window.innerHeight * (maxHeight.replace('%', '')) / 100); + + const handleMouseMove = (e) => { + if (!isResizing) return; + + const deltaY = e.clientY - startYRef.current; + const newHeight = startHeightRef.current - deltaY; + const clampedHeight = Math.max(minHeight, Math.min(newHeight, maxAllowedHeight)); + setHeight(clampedHeight); + }; + + const handleMouseUp = () => { + setIsResizing(false); + }; + + if (isResizing) { + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + document.body.style.cursor = 'row-resize'; + document.body.style.userSelect = 'none'; + } + + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + }; + }, [isResizing, minHeight, maxHeight]); + + const handleMouseDown = (e) => { + e.preventDefault(); + setIsResizing(true); + startYRef.current = e.clientY; + startHeightRef.current = height; + }; + + // If resizeContainer is false, just render in a normal div + if (!resizeContainer) { + return ( +
+ +
+ ); + } + + // Render with resize functionality + return ( +
+
{ + e.currentTarget.style.borderTopColor = '#ccc'; + }} + onMouseLeave={(e) => { + if (!isResizing) { + e.currentTarget.style.borderTopColor = 'transparent'; + } + }} + /> +
+ +
+
+ ); + }; +}; + +export default withResize; diff --git a/web/client/product/assets/css/viewer.css b/web/client/product/assets/css/viewer.css index fb66444ef1b..688935ae24a 100644 --- a/web/client/product/assets/css/viewer.css +++ b/web/client/product/assets/css/viewer.css @@ -154,3 +154,14 @@ html, body, #container, .fill { max-height: 250px; overflow-y: auto; } + +/* Disable pointer events on the main content container but enable them for all its children */ +.ms2-layout-main-content { + pointer-events: none; +} +.ms2-layout-main-content .ms2-layout-content > *, +.ms2-layout-main-content .ms2-layout-left-column > *, +.ms2-layout-main-content .ms2-layout-right-column > *, +.ms2-layout-main-content .ms2-layout-columns > * { + pointer-events: auto; +} diff --git a/web/client/reducers/featuregrid.js b/web/client/reducers/featuregrid.js index 13a20138850..26e04a987b0 100644 --- a/web/client/reducers/featuregrid.js +++ b/web/client/reducers/featuregrid.js @@ -77,7 +77,7 @@ const emptyResultsState = { drawing: false, newFeatures: [], features: [], - dockSize: 0.35, + dockSize: 0, customEditorsOptions: { "rules": [] }, From 3ba59cff20eeefbb69106d11de7f2dbbec28d555 Mon Sep 17 00:00:00 2001 From: Sovas Tiwari Date: Mon, 5 Jan 2026 20:31:00 +0545 Subject: [PATCH 2/3] Update the Map viewer css and classnames Separate the js and css for the withResize hoc Remove the dockSize implementation for feature frid and remove it logic from updated map layout epic --- .../actions/__tests__/featuregrid-test.js | 11 -- web/client/actions/featuregrid.js | 15 --- .../featuregrid/FeatureEditorFallback.jsx | 20 +--- .../data/featuregrid/toolbars/Toolbar.jsx | 7 +- .../components/layout/MapViewerLayout.jsx | 24 ++-- .../layout/__tests__/MapViewerLayout-test.jsx | 110 ++++++++++++++++++ .../hooks/useResizeObserver-test.jsx | 93 +++++++++++++++ .../layout/hooks/useResizeObserver.js | 71 +++++++++++ web/client/containers/MapViewer.jsx | 27 ++++- web/client/epics/__tests__/maplayout-test.js | 6 +- web/client/epics/maplayout.js | 15 +-- .../plugins/featuregrid/FeatureEditor.jsx | 5 +- .../__tests__/FeatureEditor-test.jsx | 1 - web/client/plugins/featuregrid/hoc/style.less | 27 +++++ .../plugins/featuregrid/hoc/withResize.jsx | 49 ++------ .../plugins/featuregrid/panels/index.jsx | 6 +- web/client/product/assets/css/viewer.css | 11 -- .../reducers/__tests__/featuregrid-test.js | 16 --- web/client/reducers/featuregrid.js | 19 +-- .../selectors/__tests__/featuregrid-test.js | 6 - web/client/selectors/featuregrid.js | 1 - .../geostore/data/context_1.json | 3 +- web/client/themes/default/less/common.less | 13 +++ web/client/utils/ContextCreatorUtils.js | 24 ++++ .../__tests__/ContextCreatorUtils-test.js | 2 +- 25 files changed, 410 insertions(+), 172 deletions(-) create mode 100644 web/client/components/layout/__tests__/MapViewerLayout-test.jsx create mode 100644 web/client/components/layout/__tests__/hooks/useResizeObserver-test.jsx create mode 100644 web/client/components/layout/hooks/useResizeObserver.js create mode 100644 web/client/plugins/featuregrid/hoc/style.less diff --git a/web/client/actions/__tests__/featuregrid-test.js b/web/client/actions/__tests__/featuregrid-test.js index 995a17cf300..c54e54a1880 100644 --- a/web/client/actions/__tests__/featuregrid-test.js +++ b/web/client/actions/__tests__/featuregrid-test.js @@ -71,8 +71,6 @@ import { OPEN_ADVANCED_SEARCH, initPlugin, INIT_PLUGIN, - sizeChange, - SIZE_CHANGE, START_SYNC_WMS, startSyncWMS, storeAdvancedSearchFilter, @@ -310,15 +308,6 @@ describe('Test correctness of featurgrid actions', () => { expect(retval.type).toBe(UPDATE_FILTER); expect(retval.update).toBe(update); }); - it('Test sizeChange', () => { - const size = 0.5; - const dockProps = {maxDockSize: 0.7, minDockSize: 0.1}; - const retval = sizeChange(size, dockProps); - expect(retval).toExist(); - expect(retval.type).toBe(SIZE_CHANGE); - expect(retval.size).toBe(size); - expect(retval.dockProps).toEqual(dockProps); - }); it('Test storeAdvancedSearchFilter', () => { const filterObj = {name: "A"}; const retval = storeAdvancedSearchFilter(filterObj); diff --git a/web/client/actions/featuregrid.js b/web/client/actions/featuregrid.js index a90803c6bd2..c1645bb2cf0 100644 --- a/web/client/actions/featuregrid.js +++ b/web/client/actions/featuregrid.js @@ -32,7 +32,6 @@ export const SET_LAYER = 'FEATUREGRID:SET_LAYER'; export const UPDATE_FILTER = 'QUERY:UPDATE_FILTER'; export const CHANGE_PAGE = 'FEATUREGRID:CHANGE_PAGE'; export const GEOMETRY_CHANGED = 'FEATUREGRID:GEOMETRY_CHANGED'; -export const DOCK_SIZE_FEATURES = 'DOCK_SIZE_FEATURES'; export const TOGGLE_TOOL = 'FEATUREGRID:TOGGLE_TOOL'; export const CUSTOMIZE_ATTRIBUTE = 'FEATUREGRID:CUSTOMIZE_ATTRIBUTE'; export const CLOSE_FEATURE_GRID_CONFIRM = 'ASK_CLOSE_FEATURE_GRID_CONFIRM'; @@ -47,7 +46,6 @@ export const DEACTIVATE_GEOMETRY_FILTER = 'FEATUREGRID:DEACTIVATE_GEOMETRY_FILTE export const OPEN_ADVANCED_SEARCH = 'FEATUREGRID:ADVANCED_SEARCH'; export const ZOOM_ALL = 'FEATUREGRID:ZOOM_ALL'; export const INIT_PLUGIN = 'FEATUREGRID:INIT_PLUGIN'; -export const SIZE_CHANGE = 'FEATUREGRID:SIZE_CHANGE'; export const TOGGLE_SHOW_AGAIN_FLAG = 'FEATUREGRID:TOGGLE_SHOW_AGAIN_FLAG'; export const UPDATE_EDITORS_OPTIONS = 'FEATUREGRID:UPDATE_EDITORS_OPTIONS'; export const LAUNCH_UPDATE_FILTER_FUNC = 'FEATUREGRID:LAUNCH_UPDATE_FILTER_FUNC'; @@ -194,12 +192,6 @@ export function setFeatures(features) { }; } -export function dockSizeFeatures(dockSize) { - return { - type: DOCK_SIZE_FEATURES, - dockSize: dockSize - }; -} export function sort(sortBy, sortOrder) { return { type: SORT_BY, @@ -346,13 +338,6 @@ export function startSyncWMS() { type: START_SYNC_WMS }; } -export function sizeChange(size, dockProps) { - return { - type: SIZE_CHANGE, - size, - dockProps - }; -} export const moreFeatures = (pages) => { return { type: LOAD_MORE_FEATURES, diff --git a/web/client/components/data/featuregrid/FeatureEditorFallback.jsx b/web/client/components/data/featuregrid/FeatureEditorFallback.jsx index 146ac5f7d42..d21e941739b 100644 --- a/web/client/components/data/featuregrid/FeatureEditorFallback.jsx +++ b/web/client/components/data/featuregrid/FeatureEditorFallback.jsx @@ -6,27 +6,15 @@ * LICENSE file in the root directory of this source tree. */ -import React, { useMemo } from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import { mapLayoutValuesSelector } from '../../../selectors/maplayout'; +import React from 'react'; import Loader from '../../misc/Loader'; -const FeatureEditorFallback = ({ size, dockStyle }) => { - const containerStyle = useMemo(() => { - return { height: `${size * 100}%`, ...dockStyle }; - }, [size, dockStyle]); +const FeatureEditorFallback = () => { return ( -
+
); }; -export default connect(createSelector( - state => state?.featuregrid?.dockSize, - state => mapLayoutValuesSelector(state, { transform: true }), - (size, dockStyle) => ({ - size, - dockStyle - })))(FeatureEditorFallback); +export default FeatureEditorFallback; diff --git a/web/client/components/data/featuregrid/toolbars/Toolbar.jsx b/web/client/components/data/featuregrid/toolbars/Toolbar.jsx index a9571203f14..37fe00ab3b9 100644 --- a/web/client/components/data/featuregrid/toolbars/Toolbar.jsx +++ b/web/client/components/data/featuregrid/toolbars/Toolbar.jsx @@ -128,7 +128,7 @@ const standardButtons = { visible={selectedCount <= 1 && mode === "VIEW"} onClick={events.settings} glyph="features-grid-set"/>), - syncGridFilterToMap: ({disabled, isSyncActive = false, showSyncOnMapButton = true, events = {}, syncPopover = { dockSize: "32.2%" }, showPopoverSync, hideSyncPopover}) => ( ( -
, - style: { - bottom: syncPopover.dockSize - } +
}} } />), syncTimeParameter: ({timeSync, showTimeSyncButton = false, events = {}}) => ( { + const contentResizeRef = useResizeObserver({ + onResize, + watch: ['bottom'] + }); return ( {header} - +
{background}
{top}
- -
{leftColumn}
- + +
{leftColumn}
+ {children} -
{rightColumn}
-
{columns}
+
{rightColumn}
+
{columns}
{bottom}
@@ -35,3 +41,5 @@ export default ({
); }; + +export default MapViewerLayout; diff --git a/web/client/components/layout/__tests__/MapViewerLayout-test.jsx b/web/client/components/layout/__tests__/MapViewerLayout-test.jsx new file mode 100644 index 00000000000..1119cead730 --- /dev/null +++ b/web/client/components/layout/__tests__/MapViewerLayout-test.jsx @@ -0,0 +1,110 @@ +/* + * Copyright 2026, 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 expect from 'expect'; +import ReactDOM from 'react-dom'; +import MapViewerLayout from '../MapViewerLayout'; + +describe("Test MapViewerLayout Component", () => { + beforeEach((done) => { + document.body.innerHTML = '
'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById("container")); + document.body.innerHTML = ''; + setTimeout(done); + }); + + it('renders with basic props', () => { + ReactDOM.render( + +
+
, + document.getElementById("container") + ); + + expect(document.getElementById('MAPVIEWER')).toExist(); + expect(document.getElementsByClassName('MAP_CLASS')[0]).toExist(); + expect(document.getElementsByClassName('ms-map-viewer-layout-body')[0]).toExist(); + expect(document.getElementsByClassName('ms-map-viewer-layout-content')[0]).toExist(); + expect(document.getElementsByClassName('content')[0]).toExist(); + }); + + it('renders header and footer', () => { + ReactDOM.render( + } + footer={
} + > +
+
, + document.getElementById("container") + ); + + expect(document.getElementsByClassName('header')[0]).toExist(); + expect(document.getElementsByClassName('footer')[0]).toExist(); + expect(document.getElementsByClassName('content')[0]).toExist(); + }); + + it('renders background, top and bottom containers', () => { + ReactDOM.render( + } + top={
} + bottom={
} + > +
+
, + document.getElementById("container") + ); + + // background is inside a _fill _absolute container + expect(document.getElementsByClassName('background')[0]).toExist(); + // top and bottom containers + expect(document.getElementsByClassName('top')[0]).toExist(); + expect(document.getElementsByClassName('bottom')[0]).toExist(); + }); + + it('renders left and right columns', () => { + ReactDOM.render( + } + rightColumn={
} + > +
+
, + document.getElementById("container") + ); + + expect(document.getElementsByClassName('ms-map-viewer-layout-left-column')[0]).toExist(); + expect(document.getElementsByClassName('left-column')[0]).toExist(); + expect(document.getElementsByClassName('ms-map-viewer-layout-right-column')[0]).toExist(); + expect(document.getElementsByClassName('right-column')[0]).toExist(); + }); + + it('renders additional columns container', () => { + ReactDOM.render( + , +
+ ]} + > +
+
, + document.getElementById("container") + ); + + expect(document.getElementsByClassName('ms-map-viewer-layout-columns')[0]).toExist(); + expect(document.getElementsByClassName('extra-col-1')[0]).toExist(); + expect(document.getElementsByClassName('extra-col-2')[0]).toExist(); + }); +}); + diff --git a/web/client/components/layout/__tests__/hooks/useResizeObserver-test.jsx b/web/client/components/layout/__tests__/hooks/useResizeObserver-test.jsx new file mode 100644 index 00000000000..ed815778104 --- /dev/null +++ b/web/client/components/layout/__tests__/hooks/useResizeObserver-test.jsx @@ -0,0 +1,93 @@ +/* + * Copyright 2026, 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 React from 'react'; +import ReactDOM from 'react-dom'; +import { act } from 'react-dom/test-utils'; +import useResizeObserver from '../../hooks/useResizeObserver'; + +describe('useResizeObserver', () => { + beforeEach((done) => { + document.body.innerHTML = '
'; + setTimeout(done); + }); + + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById("container")); + document.body.innerHTML = ''; + setTimeout(done); + }); + + it('should observe element and call onResize when size changes', (done) => { + const onResizeSpy = expect.createSpy(); + + const Component = () => { + const elementRef = useResizeObserver({ + onResize: onResizeSpy + }); + + return
Test
; + }; + + act(() => { + ReactDOM.render(, document.getElementById('container')); + }); + + // Wait for ResizeObserver to trigger and debounce + setTimeout(() => { + expect(onResizeSpy).toHaveBeenCalled(); + done(); + }, 200); + }); + + it('should debounce resize callbacks', (done) => { + const onResizeSpy = expect.createSpy(); + + const Component = () => { + const elementRef = useResizeObserver({ + onResize: onResizeSpy, + debounceTime: 200 + }); + + return
Test
; + }; + + act(() => { + ReactDOM.render(, document.getElementById('container')); + }); + + // Wait for debounce + setTimeout(() => { + expect(onResizeSpy).toHaveBeenCalled(); + done(); + }, 250); + }); + + it('should disconnect observer on unmount', () => { + const Component = () => { + const elementRef = useResizeObserver({ + onResize: () => {} + }); + + return
Test
; + }; + + act(() => { + ReactDOM.render(, document.getElementById('container')); + }); + + act(() => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + }); + + // Test passes if unmount completes without errors + expect(true).toBe(true); + }); +}); + diff --git a/web/client/components/layout/hooks/useResizeObserver.js b/web/client/components/layout/hooks/useResizeObserver.js new file mode 100644 index 00000000000..693fb05b456 --- /dev/null +++ b/web/client/components/layout/hooks/useResizeObserver.js @@ -0,0 +1,71 @@ +import { useRef, useEffect, useMemo } from 'react'; +import { debounce } from 'lodash'; + +const DEFAULT_KEYS = ['width', 'height']; + +const useResizeObserver = ({ + onResize = () => {}, + watch = DEFAULT_KEYS, + debounceTime = 100 +} = {}) => { + const elementRef = useRef(null); + const prevRef = useRef({}); + + const debouncedResize = useMemo( + () => + debounce((element) => { + if (!element) return; + + const rect = element.getBoundingClientRect(); + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + + const values = { + width: rect.width, + height: rect.height, + top: rect.top, + left: rect.left, + right: viewportWidth - rect.right, + bottom: viewportHeight - rect.bottom + }; + + const changed = {}; + + watch.forEach((key) => { + const value = values[key]; + if (prevRef.current[key] !== value) { + changed[key] = value; + } + }); + + if (Object.keys(changed).length > 0) { + prevRef.current = { + ...prevRef.current, + ...changed + }; + onResize(changed); + } + }, debounceTime), + [onResize, watch, debounceTime] + ); + + useEffect(() => { + const element = elementRef.current; + if (!element || watch.length === 0) return null; + + const observer = new ResizeObserver(() => { + debouncedResize(element); + }); + + observer.observe(element); + + return () => { + observer.disconnect(); + debouncedResize.cancel(); + }; + }, [debouncedResize, watch]); + + return elementRef; +}; + +export default useResizeObserver; diff --git a/web/client/containers/MapViewer.jsx b/web/client/containers/MapViewer.jsx index fda38f9902a..c7f9159d2fc 100644 --- a/web/client/containers/MapViewer.jsx +++ b/web/client/containers/MapViewer.jsx @@ -17,6 +17,7 @@ import ConfigUtils from '../utils/ConfigUtils'; import { getMonitoredState } from '../utils/PluginsUtils'; import ModulePluginsContainer from "../product/pages/containers/ModulePluginsContainer"; import MapViewerLayout from '../components/layout/MapViewerLayout'; +import { updateMapLayout } from '../actions/maplayout'; import { createShallowSelectorCreator } from '../utils/ReselectUtils'; const PluginsContainer = connect( @@ -45,7 +46,9 @@ class MapViewer extends React.Component { plugins: PropTypes.object, loaderComponent: PropTypes.func, onLoaded: PropTypes.func, - component: PropTypes.any + component: PropTypes.any, + onContentResize: PropTypes.func, + mapLayout: PropTypes.object }; static defaultProps = { @@ -59,6 +62,22 @@ class MapViewer extends React.Component { this.props.loadMapConfig(); } + handleContentResize = (changed) => { + if (changed.bottom !== undefined) { + const bottomOffset = Math.max(0, changed.bottom - 35); + const {boundingMapRect, layout, boundingSidebarRect} = this.props.mapLayout; + + this.props.onContentResize({ + ...layout, + ...boundingSidebarRect, + boundingMapRect: { + ...boundingMapRect, + bottom: bottomOffset + } + }); + } + }; + render() { return ( )} />); } } -export default MapViewer; +export default connect((state) => ({ + mapLayout: state.maplayout +}), { onContentResize: updateMapLayout })(MapViewer); diff --git a/web/client/epics/__tests__/maplayout-test.js b/web/client/epics/__tests__/maplayout-test.js index 47da19f9576..4c7f256c425 100644 --- a/web/client/epics/__tests__/maplayout-test.js +++ b/web/client/epics/__tests__/maplayout-test.js @@ -277,8 +277,8 @@ describe('map layout epics', () => { actions.map((action) => { expect(action.type).toBe(UPDATE_MAP_LAYOUT); expect(action.layout).toEqual({ - left: 0, right: 0, bottom: '100%', dockSize: 100, transform: "translate(0, -0px)", height: "calc(100% - 0px)", - boundingMapRect: {bottom: "100%", dockSize: 100, left: 0, right: 0}, + bottom: 0, left: 0, right: 0, transform: "translate(0, -0px)", height: "calc(100% - 0px)", + boundingMapRect: {bottom: 0, left: 0, right: 0}, boundingSidebarRect: { right: 0, left: 0, bottom: 0 }, leftPanel: false, rightPanel: false @@ -289,7 +289,7 @@ describe('map layout epics', () => { } done(); }; - const state = {featuregrid: {open: true, dockSize: 1}}; + const state = {featuregrid: {open: true}}; testEpic(updateMapLayoutEpic, 1, openFeatureGrid(), epicResult, state); }); }); diff --git a/web/client/epics/maplayout.js b/web/client/epics/maplayout.js index 05b9067e448..04c946a4b7d 100644 --- a/web/client/epics/maplayout.js +++ b/web/client/epics/maplayout.js @@ -10,7 +10,7 @@ import Rx from 'rxjs'; import {UPDATE_DOCK_PANELS, updateMapLayout, FORCE_UPDATE_MAP_LAYOUT} from '../actions/maplayout'; import {TOGGLE_CONTROL, SET_CONTROL_PROPERTY, SET_CONTROL_PROPERTIES, setControlProperty} from '../actions/controls'; import { MAP_CONFIG_LOADED } from '../actions/config'; -import {SIZE_CHANGE, CLOSE_FEATURE_GRID, OPEN_FEATURE_GRID, closeFeatureGrid} from '../actions/featuregrid'; +import {CLOSE_FEATURE_GRID, OPEN_FEATURE_GRID, closeFeatureGrid} from '../actions/featuregrid'; import { CLOSE_IDENTIFY, @@ -32,14 +32,14 @@ import { mapInfoDetailsSettingsFromIdSelector, isMouseMoveIdentifyActiveSelector import {head, get, findIndex, keys} from 'lodash'; -import { isFeatureGridOpen, getDockSize } from '../selectors/featuregrid'; +import { isFeatureGridOpen } from '../selectors/featuregrid'; import {DEFAULT_MAP_LAYOUT} from "../utils/LayoutUtils"; import {dockPanelsSelector} from "../selectors/maplayout"; /** * Capture that cause layout change to update the proper object. * Configures a map layout based on state of panels. - * @param {external:Observable} action$ manages `MAP_CONFIG_LOADED`, `SIZE_CHANGE`, `CLOSE_FEATURE_GRID`, `OPEN_FEATURE_GRID`, `CLOSE_IDENTIFY`, `NO_QUERYABLE_LAYERS`, `LOAD_FEATURE_INFO`, `TOGGLE_MAPINFO_STATE`, `TOGGLE_CONTROL`, `SET_CONTROL_PROPERTY`. + * @param {external:Observable} action$ manages `MAP_CONFIG_LOADED`, `CLOSE_FEATURE_GRID`, `OPEN_FEATURE_GRID`, `CLOSE_IDENTIFY`, `NO_QUERYABLE_LAYERS`, `LOAD_FEATURE_INFO`, `TOGGLE_MAPINFO_STATE`, `TOGGLE_CONTROL`, `SET_CONTROL_PROPERTY`. * @param store * @memberof epics.mapLayout * @return {external:Observable} emitting {@link #actions.map.updateMapLayout} action @@ -49,7 +49,6 @@ export const updateMapLayoutEpic = (action$, store) => action$.ofType( MAP_CONFIG_LOADED, - SIZE_CHANGE, CLOSE_FEATURE_GRID, OPEN_FEATURE_GRID, CLOSE_IDENTIFY, @@ -125,21 +124,17 @@ export const updateMapLayoutEpic = (action$, store) => mapInfoEnabledSelector(state) && isMapInfoOpen(state) && !isMouseMoveIdentifyActiveSelector(state) && {right: mapLayout.right.md} || null ].filter(panel => panel)) || {right: 0}; - const dockSize = getDockSize(state) * 100; - const bottom = isFeatureGridOpen(state) && {bottom: dockSize + '%', dockSize} - || {bottom: 0}; // To avoid map from de-centering when performing scale zoom - const transform = isFeatureGridOpen(state) && {transform: 'translate(0, -' + mapLayout.bottom.sm + 'px)'} || {transform: 'none'}; const height = {height: 'calc(100% - ' + mapLayout.bottom.sm + 'px)'}; const boundingMapRect = { - ...bottom, + bottom: 0, // To avoid map from de-centering when performing scale zoom ...leftPanels, ...rightPanels }; Object.keys(boundingMapRect).forEach(key => { - if (['left', 'right', 'dockSize'].includes(key)) { + if (['left', 'right'].includes(key)) { boundingMapRect[key] = boundingMapRect[key] + (boundingSidebarRect[key] ?? 0); } else { const totalOffset = (parseFloat(boundingMapRect[key]) + parseFloat(boundingSidebarRect[key] ?? 0)); diff --git a/web/client/plugins/featuregrid/FeatureEditor.jsx b/web/client/plugins/featuregrid/FeatureEditor.jsx index 72415d5181f..17c1105f896 100644 --- a/web/client/plugins/featuregrid/FeatureEditor.jsx +++ b/web/client/plugins/featuregrid/FeatureEditor.jsx @@ -16,7 +16,7 @@ import ContainerDimensions from 'react-container-dimensions'; import Grid from '../../components/data/featuregrid/FeatureGrid'; import BorderLayout from '../../components/layout/BorderLayout'; import { toChangesMap} from '../../utils/FeatureGridUtils'; -import { sizeChange, setUp, setSyncTool } from '../../actions/featuregrid'; +import { setUp, setSyncTool } from '../../actions/featuregrid'; import {paginationInfo, describeSelector, attributesJSONSchemaSelector, wfsURLSelector, typeNameSelector, isSyncWmsActive} from '../../selectors/query'; import {modeSelector, changesSelector, newFeaturesSelector, hasChangesSelector, selectedLayerFieldsSelector, selectedFeaturesSelector} from '../../selectors/featuregrid'; @@ -317,8 +317,7 @@ const EditorPlugin = compose( gridTools: gridTools.map((t) => ({ ...t, events: bindActionCreators(t.events, dispatch) - })), - onSizeChange: (...params) => dispatch(sizeChange(...params)) + })) }) ) )(ResizableEditor); diff --git a/web/client/plugins/featuregrid/__tests__/FeatureEditor-test.jsx b/web/client/plugins/featuregrid/__tests__/FeatureEditor-test.jsx index d748c5ae538..563542beacd 100644 --- a/web/client/plugins/featuregrid/__tests__/FeatureEditor-test.jsx +++ b/web/client/plugins/featuregrid/__tests__/FeatureEditor-test.jsx @@ -24,7 +24,6 @@ describe('FeatureEditor plugin component', () => { drawing: false, newFeatures: [], features: [], - dockSize: 0.35 } }; const props = { diff --git a/web/client/plugins/featuregrid/hoc/style.less b/web/client/plugins/featuregrid/hoc/style.less new file mode 100644 index 00000000000..6a020479a20 --- /dev/null +++ b/web/client/plugins/featuregrid/hoc/style.less @@ -0,0 +1,27 @@ +.ms-resize-container { + position: relative; + display: flex; + flex-direction: column; + overflow: hidden; + + .ms-resize-handle { + height: 4px; + cursor: row-resize; + background-color: transparent; + position: absolute; + top: 0; + left: 0; + right: 0; + z-index: 10; + border-top: 2px solid transparent; + transition: border-color 0.2s; + &:hover { + border-top-color: #ccc; + } + } + + .ms-resize-content { + flex: 1; + overflow: hidden; + } +} \ No newline at end of file diff --git a/web/client/plugins/featuregrid/hoc/withResize.jsx b/web/client/plugins/featuregrid/hoc/withResize.jsx index 18a070cb694..a53ff2044a1 100644 --- a/web/client/plugins/featuregrid/hoc/withResize.jsx +++ b/web/client/plugins/featuregrid/hoc/withResize.jsx @@ -1,4 +1,5 @@ import React, { useEffect, useRef, useState } from "react"; +import './style.less'; /** * HOC that wraps a component in a resizable container. @@ -25,7 +26,7 @@ const withResize = (Component) => { ? maxHeight : (window.innerHeight * (maxHeight.replace('%', '')) / 100); - const handleMouseMove = (e) => { + const handlePointerMove = (e) => { if (!isResizing) return; const deltaY = e.clientY - startYRef.current; @@ -34,22 +35,18 @@ const withResize = (Component) => { setHeight(clampedHeight); }; - const handleMouseUp = () => { + const handlePointerUp = () => { setIsResizing(false); }; if (isResizing) { - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleMouseUp); - document.body.style.cursor = 'row-resize'; - document.body.style.userSelect = 'none'; + document.addEventListener('pointermove', handlePointerMove); + document.addEventListener('pointerup', handlePointerUp); } return () => { - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUp); - document.body.style.cursor = ''; - document.body.style.userSelect = ''; + document.removeEventListener('pointermove', handlePointerMove); + document.removeEventListener('pointerup', handlePointerUp); }; }, [isResizing, minHeight, maxHeight]); @@ -73,38 +70,14 @@ const withResize = (Component) => { return (
{ - e.currentTarget.style.borderTopColor = '#ccc'; - }} - onMouseLeave={(e) => { - if (!isResizing) { - e.currentTarget.style.borderTopColor = 'transparent'; - } - }} + className="ms-resize-handle" /> -
+
diff --git a/web/client/plugins/featuregrid/panels/index.jsx b/web/client/plugins/featuregrid/panels/index.jsx index beb296a2020..f0414fba343 100644 --- a/web/client/plugins/featuregrid/panels/index.jsx +++ b/web/client/plugins/featuregrid/panels/index.jsx @@ -28,7 +28,6 @@ import HeaderComp from '../../../components/data/featuregrid/Header'; import ToolbarComp from '../../../components/data/featuregrid/toolbars/Toolbar'; import { getAttributeFilter, - getDockSize, getTitleSelector, hasChangesSelector, hasGeometrySelector, @@ -49,7 +48,6 @@ import { isFilterByViewportSupported, selectedLayerSelector } from '../../../selectors/featuregrid'; -import { mapLayoutValuesSelector } from '../../../selectors/maplayout'; import {isCesium, mapTypeSelector} from '../../../selectors/maptype'; import { featureCollectionResultSelector, @@ -82,8 +80,7 @@ const Toolbar = connect( hasNewFeatures: hasNewFeaturesSelector, hasGeometry: hasGeometrySelector, syncPopover: (state) => ({ - showAgain: showAgainSelector(state), - dockSize: mapLayoutValuesSelector(state, {dockSize: true}).dockSize + 3.2 + "%" + showAgain: showAgainSelector(state) }), isDrawing: isDrawingSelector, isSimpleGeom: isSimpleGeomSelector, @@ -104,7 +101,6 @@ const Toolbar = connect( isSnappingLoading, snappingConfig, mapType: mapTypeSelector, - editorHeight: getDockSize, viewportFilter: isViewportFilterActive, isFilterByViewportSupported, layer: selectedLayerSelector diff --git a/web/client/product/assets/css/viewer.css b/web/client/product/assets/css/viewer.css index 688935ae24a..fb66444ef1b 100644 --- a/web/client/product/assets/css/viewer.css +++ b/web/client/product/assets/css/viewer.css @@ -154,14 +154,3 @@ html, body, #container, .fill { max-height: 250px; overflow-y: auto; } - -/* Disable pointer events on the main content container but enable them for all its children */ -.ms2-layout-main-content { - pointer-events: none; -} -.ms2-layout-main-content .ms2-layout-content > *, -.ms2-layout-main-content .ms2-layout-left-column > *, -.ms2-layout-main-content .ms2-layout-right-column > *, -.ms2-layout-main-content .ms2-layout-columns > * { - pointer-events: auto; -} diff --git a/web/client/reducers/__tests__/featuregrid-test.js b/web/client/reducers/__tests__/featuregrid-test.js index 7b7d20a22ef..83657ad1469 100644 --- a/web/client/reducers/__tests__/featuregrid-test.js +++ b/web/client/reducers/__tests__/featuregrid-test.js @@ -47,7 +47,6 @@ import featuregrid from '../featuregrid'; import { setFeatures, - dockSizeFeatures, setLayer, toggleTool, customizeAttribute, @@ -76,7 +75,6 @@ import { closeFeatureGrid, toggleShowAgain, initPlugin, - sizeChange, storeAdvancedSearchFilter, setUp, setTimeSync, @@ -215,10 +213,6 @@ describe('Test the featuregrid reducer', () => { expect(state.features).toExist(); expect(state.features.length).toBe(1); }); - it('dockSizeFeatures', () => { - let state = featuregrid( {}, dockSizeFeatures(200)); - expect(state.dockSize).toBe(200); - }); it('toggleEditMode edit', () => { let state = featuregrid( {}, toggleEditMode()); expect(state.multiselect).toBeTruthy(); @@ -378,16 +372,6 @@ describe('Test the featuregrid reducer', () => { expect(state.localType).toBe("Point"); }); - it('SIZE_CHANGE', () => { - let state = featuregrid({}, sizeChange(0.5, {maxDockSize: 0.7, minDockSize: 0.1})); - expect(state.dockSize).toBe(0.5); - state = featuregrid({}, sizeChange(0.8, {maxDockSize: 0.7, minDockSize: 0.1})); - expect(state.dockSize).toBe(0.7); - state = featuregrid({}, sizeChange(0.05, {maxDockSize: 0.7, minDockSize: 0.1})); - expect(state.dockSize).toBe(0.1); - state = featuregrid({}, sizeChange(0.5)); - expect(state.dockSize).toBe(0.5); - }); it("storeAdvancedSearchFilter", () => { const filterObj = {test: 'test'}; let state = featuregrid({selectedLayer: "test_layer"}, storeAdvancedSearchFilter(filterObj)); diff --git a/web/client/reducers/featuregrid.js b/web/client/reducers/featuregrid.js index 26e04a987b0..3c84642fca2 100644 --- a/web/client/reducers/featuregrid.js +++ b/web/client/reducers/featuregrid.js @@ -21,7 +21,6 @@ import { SAVE_ERROR, CLEAR_CHANGES, CHANGE_PAGE, - DOCK_SIZE_FEATURES, SET_LAYER, TOGGLE_TOOL, CUSTOMIZE_ATTRIBUTE, @@ -38,7 +37,6 @@ import { CLOSE_FEATURE_GRID, UPDATE_FILTER, INIT_PLUGIN, - SIZE_CHANGE, STORE_ADVANCED_SEARCH_FILTER, GRID_QUERY_RESULT, LOAD_MORE_FEATURES, @@ -77,7 +75,6 @@ const emptyResultsState = { drawing: false, newFeatures: [], features: [], - dockSize: 0, customEditorsOptions: { "rules": [] }, @@ -142,8 +139,7 @@ const applyNewChanges = (features, changedFeatures, updates, updatesGeom) => * multiselect: false, * drawing: false, * newFeatures: [], - * features: [], - * dockSize: 0.35 + * features: [] * } * * @memberof reducers @@ -208,8 +204,6 @@ function featuregrid(state = emptyResultsState, action) { return Object.assign({}, state, {select: [], changes: []}); case SET_FEATURES: return Object.assign({}, state, {features: action.features}); - case DOCK_SIZE_FEATURES: - return Object.assign({}, state, {dockSize: action.dockSize}); case SET_LAYER: return Object.assign({}, state, {selectedLayer: action.id}); case TOGGLE_TOOL: @@ -411,17 +405,6 @@ function featuregrid(state = emptyResultsState, action) { useLayerFilter: action.useLayerFilter ?? state.useLayerFilter // if not present, keep current }; } - case SIZE_CHANGE : { - const maxDockSize = action.dockProps && action.dockProps.maxDockSize; - const minDockSize = action.dockProps && action.dockProps.minDockSize; - const size = maxDockSize && minDockSize && minDockSize <= action.size && maxDockSize >= action.size && action.size - || maxDockSize && maxDockSize < action.size && maxDockSize - || minDockSize && minDockSize > action.size && minDockSize - || action.size; - return Object.assign({}, state, { - dockSize: size - }); - } case STORE_ADVANCED_SEARCH_FILTER : { return Object.assign({}, state, {advancedFilters: Object.assign({}, state.advancedFilters, {[state.selectedLayer]: action.filterObj})}); } diff --git a/web/client/selectors/__tests__/featuregrid-test.js b/web/client/selectors/__tests__/featuregrid-test.js index 54a0f7fce93..6b69e8864fc 100644 --- a/web/client/selectors/__tests__/featuregrid-test.js +++ b/web/client/selectors/__tests__/featuregrid-test.js @@ -29,7 +29,6 @@ import { canEditSelector, showAgainSelector, hasSupportedGeometry, - getDockSize, selectedLayerNameSelector, queryOptionsSelector, showTimeSync, @@ -474,11 +473,6 @@ describe('Test featuregrid selectors', () => { }); - it('test getDockSize', () => { - expect(getDockSize({ featuregrid: {dockSize: 0.5} })).toBe(0.5); - expect(getDockSize({})).toBe(undefined); - }); - it('showTimeSync', () => { expect(showTimeSync({featuregrid: initialState.featuregrid})).toBeFalsy(); const state = { diff --git a/web/client/selectors/featuregrid.js b/web/client/selectors/featuregrid.js index 8f460fa4831..67d9e5dc95c 100644 --- a/web/client/selectors/featuregrid.js +++ b/web/client/selectors/featuregrid.js @@ -166,7 +166,6 @@ export const isSimpleGeomSelector = state => isSimpleGeomType(geomTypeSelectedFe * @param {object} state applications state * @return {boolean} true if the geometry is supported, false otherwise */ -export const getDockSize = state => state.featuregrid && state.featuregrid.dockSize; /** * get selected layer name * @function diff --git a/web/client/test-resources/geostore/data/context_1.json b/web/client/test-resources/geostore/data/context_1.json index f1d0a33c5f4..8bb36391605 100644 --- a/web/client/test-resources/geostore/data/context_1.json +++ b/web/client/test-resources/geostore/data/context_1.json @@ -27,7 +27,8 @@ "scalebar": { "container": "#footer-scalebar-container" } - } + }, + "containerPosition": "background" } }, { diff --git a/web/client/themes/default/less/common.less b/web/client/themes/default/less/common.less index cdd0a48df47..6344ea47d74 100644 --- a/web/client/themes/default/less/common.less +++ b/web/client/themes/default/less/common.less @@ -136,6 +136,19 @@ box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22); } +// Map Viewer Layout - Disable pointer events on the main content container but enable them for all its children +// This allows the map underneath to receive pointer events while still allowing interaction with layout children +.ms-map-viewer-layout-main-content { + pointer-events: none; + + .ms-map-viewer-layout-content > *, + .ms-map-viewer-layout-left-column > *, + .ms-map-viewer-layout-right-column > *, + .ms-map-viewer-layout-columns > * { + pointer-events: auto; + } +} + .no-border { border: none !important; border-top: none !important; diff --git a/web/client/utils/ContextCreatorUtils.js b/web/client/utils/ContextCreatorUtils.js index 37b69713b19..0b9e5ff5559 100644 --- a/web/client/utils/ContextCreatorUtils.js +++ b/web/client/utils/ContextCreatorUtils.js @@ -53,6 +53,30 @@ export const migrateContextConfiguration = (context) => { }; } } + // migration for FeatureEditor to add containerPosition: 'footer' if not present + if (plugin.name === 'FeatureEditor') { + if (plugin?.cfg?.containerPosition !== 'footer') { + return { + ...plugin, + cfg: { + ...plugin.cfg, + containerPosition: 'footer' + } + }; + } + } + // migrate for Map to add containerPosition: 'background' if not present + if (plugin.name === 'Map') { + if (plugin?.cfg?.containerPosition !== 'background') { + return { + ...plugin, + cfg: { + ...plugin.cfg, + containerPosition: 'background' + } + }; + } + } return plugin; })]; })) diff --git a/web/client/utils/__tests__/ContextCreatorUtils-test.js b/web/client/utils/__tests__/ContextCreatorUtils-test.js index 4e9c2c27c93..b82d734701e 100644 --- a/web/client/utils/__tests__/ContextCreatorUtils-test.js +++ b/web/client/utils/__tests__/ContextCreatorUtils-test.js @@ -30,7 +30,7 @@ describe('Test the ContextCreatorUtils', () => { }); expect(newContext).toEqual({ plugins: { - desktop: [{ name: 'Map' }, { name: 'DeleteResource' }, { name: 'MapFooter', cfg: { containerPosition: 'footer' }}] + desktop: [{ name: 'Map', cfg: { containerPosition: 'background' } }, { name: 'DeleteResource' }, { name: 'MapFooter', cfg: { containerPosition: 'footer' }}] } }); }); From 2946790529ec83f3eeba44bf4c2d061a6e657450 Mon Sep 17 00:00:00 2001 From: Sovas Tiwari Date: Thu, 8 Jan 2026 16:17:03 +0545 Subject: [PATCH 3/3] fix the eslint error --- web/client/plugins/featuregrid/__tests__/FeatureEditor-test.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/client/plugins/featuregrid/__tests__/FeatureEditor-test.jsx b/web/client/plugins/featuregrid/__tests__/FeatureEditor-test.jsx index 563542beacd..8edea45a05f 100644 --- a/web/client/plugins/featuregrid/__tests__/FeatureEditor-test.jsx +++ b/web/client/plugins/featuregrid/__tests__/FeatureEditor-test.jsx @@ -23,7 +23,7 @@ describe('FeatureEditor plugin component', () => { multiselect: false, drawing: false, newFeatures: [], - features: [], + features: [] } }; const props = {