diff --git a/docs/developer-guide/mapstore-migration-guide.md b/docs/developer-guide/mapstore-migration-guide.md
index 367ea2c0d0..8ecfca3373 100644
--- a/docs/developer-guide/mapstore-migration-guide.md
+++ b/docs/developer-guide/mapstore-migration-guide.md
@@ -22,6 +22,12 @@ This is a list of things to check if you want to update from a previous version
## Migration from 2024.02.00 to 2025.01.00
+### New width variable for side panel plugins
+
+Existing projects may need to update the width size of plugins implemented as right side panels.
+
+The new width value is 420 px and is stored in a constant property called `DEFAULT_PANEL_WIDTH` available inside the `web/client/utils/LayoutUtils.js` file.
+
### Removing Header from the Admin section and deprecating the old UserManager and GroupManagerPlugin
The old `UserManager` and `GroupManager` plugin has been removed and replace with new plugins under the `web/client/plugins/ResourcesCatalog/` folder. Also the `Header` plugin has been removed from Admin/Manager so the configuration in `localConfig.json` should be updated as follow:
diff --git a/web/client/components/data/identify/coordinates/Viewer.jsx b/web/client/components/data/identify/coordinates/Viewer.jsx
index 2ca4474c92..a5ba3546e6 100644
--- a/web/client/components/data/identify/coordinates/Viewer.jsx
+++ b/web/client/components/data/identify/coordinates/Viewer.jsx
@@ -51,7 +51,7 @@ export default ({
?
Lat: - Long:
:
}
diff --git a/web/client/components/details/DetailsPanel.jsx b/web/client/components/details/DetailsPanel.jsx
index b619e97526..094bd5ba59 100644
--- a/web/client/components/details/DetailsPanel.jsx
+++ b/web/client/components/details/DetailsPanel.jsx
@@ -12,7 +12,7 @@ import Message from '../I18N/Message';
import { Panel } from 'react-bootstrap';
import BorderLayout from '../layout/BorderLayout';
import ResponsivePanel from "../misc/panels/ResponsivePanel";
-
+import { DEFAULT_PANEL_WIDTH } from '../../utils/LayoutUtils';
class DetailsPanel extends React.Component {
static propTypes = {
id: PropTypes.string,
@@ -42,7 +42,7 @@ class DetailsPanel extends React.Component {
},
active: false,
panelClassName: "details-panel",
- width: 550,
+ width: DEFAULT_PANEL_WIDTH,
isDashboard: false
};
diff --git a/web/client/components/misc/panels/DockPanel.jsx b/web/client/components/misc/panels/DockPanel.jsx
index c92681d142..e15eac3df0 100644
--- a/web/client/components/misc/panels/DockPanel.jsx
+++ b/web/client/components/misc/panels/DockPanel.jsx
@@ -12,6 +12,7 @@ import Dock from 'react-dock';
import BorderLayout from '../../layout/BorderLayout';
import { withState } from 'recompose';
import PanelHeader from './PanelHeader';
+import { DEFAULT_PANEL_WIDTH } from '../../../utils/LayoutUtils';
/**
* Component for rendering a DockPanel
@@ -41,7 +42,7 @@ export default withState('fullscreen', 'onFullscreen', false)(
fullscreen = false,
position,
open,
- size = 550,
+ size = DEFAULT_PANEL_WIDTH,
style = {},
zIndex = 1030,
onClose,
diff --git a/web/client/epics/__tests__/maplayout-test.js b/web/client/epics/__tests__/maplayout-test.js
index a94a733718..39d7d29a8b 100644
--- a/web/client/epics/__tests__/maplayout-test.js
+++ b/web/client/epics/__tests__/maplayout-test.js
@@ -15,6 +15,7 @@ import { updateMapLayoutEpic } from '../maplayout';
import { testEpic, addTimeoutEpic, TEST_TIMEOUT } from './epicTestUtils';
import ConfigUtils from '../../utils/ConfigUtils';
import { openFeatureGrid } from "../../actions/featuregrid";
+import { DEFAULT_PANEL_WIDTH } from '../../utils/LayoutUtils';
describe('map layout epics', () => {
afterEach(() => {
@@ -27,11 +28,11 @@ describe('map layout epics', () => {
actions.map((action) => {
expect(action.type).toBe(UPDATE_MAP_LAYOUT);
expect(action.layout).toEqual(
- { left: 600, right: 548, bottom: 0, transform: 'none', height: 'calc(100% - 30px)',
+ { left: 600, right: DEFAULT_PANEL_WIDTH, bottom: 0, transform: 'none', height: 'calc(100% - 30px)',
boundingMapRect: {
bottom: 0,
left: 600,
- right: 548
+ right: DEFAULT_PANEL_WIDTH
},
boundingSidebarRect: { right: 0, left: 0, bottom: 0 },
leftPanel: true,
@@ -55,11 +56,11 @@ describe('map layout epics', () => {
actions.map((action) => {
expect(action.type).toBe(UPDATE_MAP_LAYOUT);
expect(action.layout).toEqual(
- { left: 600, right: 588, bottom: 0, transform: 'none', height: 'calc(100% - 30px)',
+ { left: 600, right: DEFAULT_PANEL_WIDTH + 40, bottom: 0, transform: 'none', height: 'calc(100% - 30px)',
boundingMapRect: {
bottom: 0,
left: 600,
- right: 588
+ right: DEFAULT_PANEL_WIDTH + 40
},
boundingSidebarRect: { right: 40, left: 0, bottom: 0 },
leftPanel: true,
@@ -188,7 +189,7 @@ describe('map layout epics', () => {
});
describe('tests layout updated for right panels', () => {
- const epicResult = (done, right = 548) => actions => {
+ const epicResult = (done, right = DEFAULT_PANEL_WIDTH) => actions => {
try {
expect(actions.length).toBe(1);
actions.map((action) => {
diff --git a/web/client/epics/geoProcessing.js b/web/client/epics/geoProcessing.js
index 5f4e7db85a..ba190cbf76 100644
--- a/web/client/epics/geoProcessing.js
+++ b/web/client/epics/geoProcessing.js
@@ -145,8 +145,9 @@ import {getFeatureInfo} from "../api/identify";
import {getFeatureSimple} from '../api/WFS';
import {findNonGeometryProperty, findGeometryProperty} from '../utils/ogc/WFS/base';
import toWKT from '../utils/ogc/WKT/toWKT';
+import { DEFAULT_PANEL_WIDTH } from '../utils/LayoutUtils';
-const OFFSET = 550;
+const OFFSET = DEFAULT_PANEL_WIDTH;
const DEACTIVATE_ACTIONS = [
changeDrawingStatus("stop"),
changeDrawingStatus("clean", '', GPT_CONTROL_NAME)
diff --git a/web/client/epics/identify.js b/web/client/epics/identify.js
index a8d58686a0..2beffbc4fc 100644
--- a/web/client/epics/identify.js
+++ b/web/client/epics/identify.js
@@ -52,7 +52,8 @@ import { createControlEnabledSelector, measureSelector } from '../selectors/cont
import { localizedLayerStylesEnvSelector } from '../selectors/localizedLayerStyles';
import { mouseOutSelector } from '../selectors/mousePosition';
import { hideEmptyPopupSelector } from '../selectors/mapPopups';
-import {getBbox, getCurrentResolution, parseLayoutValue} from '../utils/MapUtils';
+import {getBbox, getCurrentResolution} from '../utils/MapUtils';
+import { parseLayoutValue } from '../utils/LayoutUtils';
import {buildIdentifyRequest, defaultQueryableFilter, filterRequestParams} from '../utils/MapInfoUtils';
import { IDENTIFY_POPUP } from '../components/map/popups';
diff --git a/web/client/epics/longitudinalProfile.js b/web/client/epics/longitudinalProfile.js
index e372a2a11b..15ee4fbd77 100644
--- a/web/client/epics/longitudinalProfile.js
+++ b/web/client/epics/longitudinalProfile.js
@@ -89,8 +89,9 @@ import {selectLineFeature} from "../utils/LongitudinalProfileUtils";
import {buildIdentifyRequest} from "../utils/MapInfoUtils";
import {getFeatureInfo} from "../api/identify";
import { drawerOwnerSelector } from "../selectors/draw";
+import { DEFAULT_PANEL_WIDTH } from '../utils/LayoutUtils';
-const OFFSET = 550;
+const OFFSET = DEFAULT_PANEL_WIDTH;
const DEACTIVATE_ACTIONS = [
changeDrawingStatus("stop"),
diff --git a/web/client/epics/maplayout.js b/web/client/epics/maplayout.js
index 81f597ccab..05b9067e44 100644
--- a/web/client/epics/maplayout.js
+++ b/web/client/epics/maplayout.js
@@ -33,7 +33,7 @@ import { mapInfoDetailsSettingsFromIdSelector, isMouseMoveIdentifyActiveSelector
import {head, get, findIndex, keys} from 'lodash';
import { isFeatureGridOpen, getDockSize } from '../selectors/featuregrid';
-import {DEFAULT_MAP_LAYOUT} from "../utils/MapUtils";
+import {DEFAULT_MAP_LAYOUT} from "../utils/LayoutUtils";
import {dockPanelsSelector} from "../selectors/maplayout";
/**
diff --git a/web/client/plugins/Details.jsx b/web/client/plugins/Details.jsx
index a285c57311..0170d19565 100644
--- a/web/client/plugins/Details.jsx
+++ b/web/client/plugins/Details.jsx
@@ -32,6 +32,7 @@ import details from '../reducers/details';
import * as epics from '../epics/details';
import {createStructuredSelector} from "reselect";
import { getDashboardId } from '../selectors/dashboard';
+import { DEFAULT_PANEL_WIDTH } from '../utils/LayoutUtils';
/**
* Allow to show details for the map.
@@ -73,7 +74,7 @@ const DetailsPlugin = ({
: active &&
diff --git a/web/client/plugins/GeoProcessing/Panel.jsx b/web/client/plugins/GeoProcessing/Panel.jsx
index 63fb75a759..cac72a20f1 100644
--- a/web/client/plugins/GeoProcessing/Panel.jsx
+++ b/web/client/plugins/GeoProcessing/Panel.jsx
@@ -18,6 +18,7 @@ import { toggleControl } from '../../actions/controls';
import { initPlugin } from '../../actions/geoProcessing';
import { isGeoProcessingEnabledSelector } from '../../selectors/controls';
import { dockStyleSelector } from '../../selectors/maplayout';
+import { DEFAULT_PANEL_WIDTH } from '../../utils/LayoutUtils';
const PanelComp = ({
dockStyle,
@@ -36,7 +37,7 @@ const PanelComp = ({
containerClassName="dock-container"
containerId="GeoProcessing-root"
open={enabled}
- size={550}
+ size={DEFAULT_PANEL_WIDTH}
dock
position="right"
title={}
diff --git a/web/client/plugins/Identify.jsx b/web/client/plugins/Identify.jsx
index 9934212f27..ee54ad03eb 100644
--- a/web/client/plugins/Identify.jsx
+++ b/web/client/plugins/Identify.jsx
@@ -76,6 +76,7 @@ import { getDefaultInfoFormatValue, getValidator } from '../utils/MapInfoUtils';
import getFeatureButtons from './identify/featureButtons';
import getToolButtons from './identify/toolButtons';
import Message from './locale/Message';
+import { DEFAULT_PANEL_WIDTH } from '../utils/LayoutUtils';
const selector = createStructuredSelector({
enabled: (state) => mapInfoEnabledSelector(state) || state.controls && state.controls.info && state.controls.info.enabled || false,
@@ -165,7 +166,7 @@ const identifyDefaultProps = defaultProps({
showMoreInfo: true,
showEdit: false,
position: 'right',
- size: 550,
+ size: DEFAULT_PANEL_WIDTH,
getToolButtons,
getFeatureButtons,
showFullscreen: false,
diff --git a/web/client/plugins/MapCatalog.jsx b/web/client/plugins/MapCatalog.jsx
index 0ed71ed563..07b4aefaf9 100644
--- a/web/client/plugins/MapCatalog.jsx
+++ b/web/client/plugins/MapCatalog.jsx
@@ -34,6 +34,7 @@ import * as epics from '../epics/mapcatalog';
import {mapLayoutValuesSelector} from "../selectors/maplayout";
import * as PropTypes from "prop-types";
import ResponsivePanel from "../components/misc/panels/ResponsivePanel";
+import { DEFAULT_PANEL_WIDTH } from '../utils/LayoutUtils';
/**
* Allows users to existing maps directly on the map.
@@ -62,7 +63,7 @@ class MapCatalogComponent extends React.Component {
}, onDelete: () => {
}, onSave: () => {
}, dockStyle: {},
- size: 550
+ size: DEFAULT_PANEL_WIDTH
};
render() {
diff --git a/web/client/plugins/MapTemplates.jsx b/web/client/plugins/MapTemplates.jsx
index d902cd84d7..b4d5f22031 100644
--- a/web/client/plugins/MapTemplates.jsx
+++ b/web/client/plugins/MapTemplates.jsx
@@ -26,6 +26,7 @@ import * as epics from '../epics/maptemplates';
import {mapLayoutValuesSelector} from "../selectors/maplayout";
import PropTypes from "prop-types";
import ResponsivePanel from "../components/misc/panels/ResponsivePanel";
+import { DEFAULT_PANEL_WIDTH } from '../utils/LayoutUtils';
/**
* Provides a list of map templates available inside of a currently loaded context.
@@ -62,7 +63,7 @@ class MapTemplatesComponent extends React.Component {
onReplaceTemplate: () => {},
onToggleFavourite: () => {},
onSetAllowedTemplates: () => {},
- size: 550
+ size: DEFAULT_PANEL_WIDTH
};
componentDidUpdate(prevProps) {
diff --git a/web/client/plugins/MetadataExplorer.jsx b/web/client/plugins/MetadataExplorer.jsx
index 082f7ee677..08474334a0 100644
--- a/web/client/plugins/MetadataExplorer.jsx
+++ b/web/client/plugins/MetadataExplorer.jsx
@@ -85,6 +85,7 @@ import { isLocalizedLayerStylesEnabledSelector } from '../selectors/localizedLay
import { projectionSelector } from '../selectors/map';
import { mapLayoutValuesSelector } from '../selectors/maplayout';
import ResponsivePanel from "../components/misc/panels/ResponsivePanel";
+import { DEFAULT_PANEL_WIDTH } from '../utils/LayoutUtils';
import usePluginItems from '../hooks/usePluginItems';
import { setProtectedServices, setShowModalStatus } from '../actions/security';
@@ -205,7 +206,7 @@ class MetadataExplorerComponent extends React.Component {
zoomToLayer: true,
// side panel properties
- width: 550,
+ width: DEFAULT_PANEL_WIDTH,
dockProps: {
dimMode: "none",
fluid: false,
diff --git a/web/client/plugins/UserExtensions.jsx b/web/client/plugins/UserExtensions.jsx
index a4155de03b..f3cd0a3d91 100644
--- a/web/client/plugins/UserExtensions.jsx
+++ b/web/client/plugins/UserExtensions.jsx
@@ -22,7 +22,7 @@ import { setControlProperty, toggleControl } from '../actions/controls';
import * as epics from '../epics/userextensions';
import {mapLayoutValuesSelector} from "../selectors/maplayout";
import ResponsivePanel from "../components/misc/panels/ResponsivePanel";
-
+import { DEFAULT_PANEL_WIDTH } from '../utils/LayoutUtils';
class Extensions extends React.Component {
static propTypes = {
@@ -36,7 +36,7 @@ class Extensions extends React.Component {
active: false,
onClose: () => {},
dockStyle: {},
- size: 550
+ size: DEFAULT_PANEL_WIDTH
}
render() {
diff --git a/web/client/plugins/longitudinalProfile/Dock.jsx b/web/client/plugins/longitudinalProfile/Dock.jsx
index 0bdd243ff7..428dd6a096 100644
--- a/web/client/plugins/longitudinalProfile/Dock.jsx
+++ b/web/client/plugins/longitudinalProfile/Dock.jsx
@@ -27,6 +27,7 @@ import Toolbar from "../../components/misc/toolbar/Toolbar";
import Chart from "../../components/charts/WidgetChart";
import { reprojectGeoJson } from '../../utils/CoordinatesUtils';
import { getMessageById } from '../../utils/LocaleUtils';
+import { DEFAULT_PANEL_WIDTH } from '../../utils/LayoutUtils';
const NavItemT = tooltip(NavItem);
const Button = tooltip(ButtonRB);
@@ -453,7 +454,7 @@ const Dock = ({
position="right"
title={}
glyph="1-line"
- size={550}
+ size={DEFAULT_PANEL_WIDTH}
open={showDock}
onClose={onCloseDock}
style={dockStyle}
diff --git a/web/client/selectors/maplayout.js b/web/client/selectors/maplayout.js
index c0c35126a8..229f344895 100644
--- a/web/client/selectors/maplayout.js
+++ b/web/client/selectors/maplayout.js
@@ -8,7 +8,7 @@
import {head, memoize} from 'lodash';
import { mapSelector } from './map';
-import {DEFAULT_MAP_LAYOUT, parseLayoutValue} from '../utils/MapUtils';
+import {DEFAULT_MAP_LAYOUT, parseLayoutValue} from '../utils/LayoutUtils';
import ConfigUtils from "../utils/ConfigUtils";
diff --git a/web/client/themes/default/less/get-feature.less b/web/client/themes/default/less/get-feature.less
index 660ed1f2b9..5e3433d821 100644
--- a/web/client/themes/default/less/get-feature.less
+++ b/web/client/themes/default/less/get-feature.less
@@ -96,6 +96,13 @@
}
}
}
+ .ms-coordinates-aeronautical {
+ display: flex;
+ flex-direction: column;
+ .ms-coordinates-aeronautical-separator {
+ display: none;
+ }
+ }
.coordinateRow {
.coordinate {
.input-group {
@@ -113,6 +120,11 @@
}
}
.coordinateRow {
+ &.aeronautical {
+ .input-group-container {
+ margin-right: 0;
+ }
+ }
.coordinate {
width: 100%;
display: flex;
@@ -126,7 +138,7 @@
}
.input-group {
.input-group-addon {
- min-width: 45px;
+ padding: 4px;
}
width: 100%;
.form-group{
diff --git a/web/client/utils/LayoutUtils.js b/web/client/utils/LayoutUtils.js
new file mode 100644
index 0000000000..b84e82be4e
--- /dev/null
+++ b/web/client/utils/LayoutUtils.js
@@ -0,0 +1,34 @@
+/*
+* Copyright 2025, 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 {
+ isString,
+ trim,
+ isNumber
+} from 'lodash';
+
+export const DEFAULT_PANEL_WIDTH = 420;
+
+export const DEFAULT_MAP_LAYOUT = {left: {sm: 300, md: 500, lg: 600}, right: { md: DEFAULT_PANEL_WIDTH }, bottom: {sm: 30}};
+
+/**
+ * Return parsed number from layout value
+ * if percentage returns percentage of second argument that should be a number
+ * eg. 20% of map height parseLayoutValue(20%, map.size.height)
+ * but if value is stored as number it will return the number
+ * eg. parseLayoutValue(50, map.size.height) returns 50
+ * @param value {number|string} number or percentage value string
+ * @param size {number} only in case of percentage
+ * @return {number}
+ */
+export const parseLayoutValue = (value, size = 0) => {
+ if (isString(value) && value.indexOf('%') !== -1) {
+ return parseFloat(trim(value)) * size / 100;
+ }
+ return isNumber(value) ? value : 0;
+};
diff --git a/web/client/utils/MapUtils.js b/web/client/utils/MapUtils.js
index 6b2bd82475..62bd67fd33 100644
--- a/web/client/utils/MapUtils.js
+++ b/web/client/utils/MapUtils.js
@@ -7,9 +7,6 @@
*/
import {
- isString,
- trim,
- isNumber,
pick,
get,
find,
@@ -46,8 +43,6 @@ import {
} from './LayersUtils';
import assign from 'object-assign';
-export const DEFAULT_MAP_LAYOUT = {left: {sm: 300, md: 500, lg: 600}, right: {md: 548}, bottom: {sm: 30}};
-
export const DEFAULT_SCREEN_DPI = 96;
export const METERS_PER_UNIT = {
@@ -821,23 +816,6 @@ export const getIdFromUri = (uri, regex = /data\/(\d+)/) => {
return findDataDigit && findDataDigit.length && findDataDigit.length > 1 ? findDataDigit[1] : null;
};
-/**
- * Return parsed number from layout value
- * if percentage returns percentage of second argument that should be a number
- * eg. 20% of map height parseLayoutValue(20%, map.size.height)
- * but if value is stored as number it will return the number
- * eg. parseLayoutValue(50, map.size.height) returns 50
- * @param value {number|string} number or percentage value string
- * @param size {number} only in case of percentage
- * @return {number}
- */
-export const parseLayoutValue = (value, size = 0) => {
- if (isString(value) && value.indexOf('%') !== -1) {
- return parseFloat(trim(value)) * size / 100;
- }
- return isNumber(value) ? value : 0;
-};
-
/**
* Method for cleanup map object from uneseccary fields which
* updated map contains and were set on map render
@@ -1025,7 +1003,6 @@ export default {
isSimpleGeomType,
getSimpleGeomType,
getIdFromUri,
- parseLayoutValue,
prepareMapObjectToCompare,
updateObjectFieldKey,
compareMapChanges,
diff --git a/web/client/utils/__tests__/LayoutUtils-test.js b/web/client/utils/__tests__/LayoutUtils-test.js
new file mode 100644
index 0000000000..39cce47e55
--- /dev/null
+++ b/web/client/utils/__tests__/LayoutUtils-test.js
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2025, 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 {
+ parseLayoutValue
+} from '../LayoutUtils';
+
+describe('LayoutUtils', () => {
+ it('parseLayoutValue', () => {
+ const percentageValue = parseLayoutValue('20%', 500);
+ expect(percentageValue).toBe(100);
+
+ const numberValue = parseLayoutValue(20);
+ expect(numberValue).toBe(20);
+
+ const noNumberValue = parseLayoutValue('value');
+ expect(noNumberValue).toBe(0);
+ });
+});
diff --git a/web/client/utils/__tests__/MapUtils-test.js b/web/client/utils/__tests__/MapUtils-test.js
index c647bfc759..2ed962c303 100644
--- a/web/client/utils/__tests__/MapUtils-test.js
+++ b/web/client/utils/__tests__/MapUtils-test.js
@@ -32,7 +32,6 @@ import {
getIdFromUri,
getSimpleGeomType,
isSimpleGeomType,
- parseLayoutValue,
prepareMapObjectToCompare,
updateObjectFieldKey,
compareMapChanges,
@@ -1806,16 +1805,7 @@ describe('Test the MapUtils', () => {
expect(getSimpleGeomType(GEOMETRY_COLLECTION)).toBe(GEOMETRY_COLLECTION);
});
- it('test parseLayoutValue', () => {
- const percentageValue = parseLayoutValue('20%', 500);
- expect(percentageValue).toBe(100);
- const numberValue = parseLayoutValue(20);
- expect(numberValue).toBe(20);
-
- const noNumberValue = parseLayoutValue('value');
- expect(noNumberValue).toBe(0);
- });
it('test getSimpleGeomType', () => {
expect(getSimpleGeomType("Point")).toBe("Point");
expect(getSimpleGeomType("Marker")).toBe("Point");