Skip to content

Commit bdeb006

Browse files
authored
Fix/mlwmsloader (#254)
1 parent 2c1e438 commit bdeb006

6 files changed

Lines changed: 235 additions & 63 deletions

File tree

packages/react-maplibre/src/components/MlMarker/MlMarker.tsx

Lines changed: 140 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,18 @@ import React, { useRef, useEffect, useState } from 'react';
22
import { createPortal } from 'react-dom';
33
import useMap from '../../hooks/useMap';
44
import maplibregl from 'maplibre-gl';
5-
import { Box } from '@mui/material';
5+
import { Box, Paper, IconButton } from '@mui/material';
6+
import CloseIcon from '@mui/icons-material/Close';
7+
8+
// Constants for popup styling
9+
const POPUP_PADDING_VERTICAL = 12;
10+
const POPUP_PADDING_HORIZONTAL = 16;
11+
const CLOSE_BUTTON_SPACING = 4;
12+
const SCROLLBAR_WIDTH = 16; // Typical scrollbar width
13+
const CLOSE_BUTTON_OFFSET = SCROLLBAR_WIDTH + CLOSE_BUTTON_SPACING;
14+
const POPUP_MIN_WIDTH = 200;
15+
const POPUP_MAX_WIDTH = 750;
16+
const POPUP_MAX_HEIGHT = 500;
617

718
export interface MlMarkerProps {
819
/** ID of the map to add the marker to */
@@ -27,6 +38,10 @@ export interface MlMarkerProps {
2738
contentOffset?: number;
2839
/** Whether mouse events pass through the marker content */
2940
passEventsThrough?: boolean;
41+
/** Whether to show a close button to remove the marker */
42+
showCloseButton?: boolean;
43+
/** Callback function when the close button is clicked */
44+
onClose?: () => void;
3045
/** Anchor position of the marker relative to its coordinates */
3146
anchor?:
3247
| 'top'
@@ -103,16 +118,33 @@ function getBoxMargins(
103118
return m;
104119
}
105120

106-
const MlMarker = ({ passEventsThrough = true, contentOffset = 5, ...props }: MlMarkerProps) => {
121+
const MlMarker = ({
122+
passEventsThrough = true,
123+
contentOffset = 5,
124+
showCloseButton = true,
125+
...props
126+
}: MlMarkerProps) => {
107127
const mapHook = useMap({
108128
mapId: props.mapId,
109129
waitForLayer: props.insertBeforeLayer,
110130
});
111131

112132
const [marker, setMarker] = useState<maplibregl.Marker | null>(null);
133+
const [contentWidth, setContentWidth] = useState<number>(300);
113134
const container = useRef<HTMLDivElement | null>(null);
114135
const iframeRef = useRef<HTMLIFrameElement | null>(null);
115136

137+
const handleClose = (event: React.MouseEvent) => {
138+
event.stopPropagation();
139+
if (props.onClose) {
140+
props.onClose();
141+
} else {
142+
// Default behavior: remove the marker
143+
marker?.remove();
144+
container.current?.remove();
145+
}
146+
};
147+
116148
useEffect(() => {
117149
if (!mapHook.map) return;
118150

@@ -161,9 +193,17 @@ const MlMarker = ({ passEventsThrough = true, contentOffset = 5, ...props }: MlM
161193

162194
function handleIframeLoad() {
163195
const iframeDoc = iframeRef.current?.contentWindow?.document;
164-
if (iframeDoc && iframeRef.current?.parentElement) {
196+
if (iframeDoc && iframeRef.current) {
165197
const scrollHeight = iframeDoc.documentElement.scrollHeight;
166-
iframeRef.current.parentElement.style.height = `${scrollHeight}px`;
198+
const scrollWidth = iframeDoc.documentElement.scrollWidth;
199+
iframeRef.current.style.height = `${scrollHeight}px`;
200+
201+
// Set width based on content, with min and max constraints
202+
const calculatedWidth = Math.max(
203+
POPUP_MIN_WIDTH,
204+
Math.min(scrollWidth + POPUP_PADDING_HORIZONTAL * 2, POPUP_MAX_WIDTH)
205+
);
206+
setContentWidth(calculatedWidth);
167207
}
168208
}
169209

@@ -173,41 +213,117 @@ const MlMarker = ({ passEventsThrough = true, contentOffset = 5, ...props }: MlM
173213
<Box
174214
sx={{
175215
position: 'absolute',
176-
display: 'flex',
177-
width: '300px',
178-
maxHeight: '500px',
179-
opacity: passEventsThrough ? 1 : 0.7,
180-
zIndex: -1,
181216
transform: getBoxTransform(props.anchor),
182217
...getBoxMargins(props.anchor, contentOffset, props.markerStyle),
183-
pointerEvents: passEventsThrough ? 'none' : 'auto',
184-
'&:hover': {
185-
opacity: 1,
186-
},
218+
zIndex: -1,
187219
...props.containerStyle,
188220
}}
189221
>
190-
<iframe
191-
ref={iframeRef}
192-
onLoad={handleIframeLoad}
193-
style={{
194-
width: '100%',
195-
borderStyle: 'none',
196-
...props.iframeStyle,
222+
<Paper
223+
elevation={8}
224+
sx={{
225+
width: `${contentWidth}px`,
226+
maxWidth: '90vw',
227+
opacity: passEventsThrough ? 1 : 0.85,
228+
pointerEvents: 'auto',
229+
overflow: 'hidden',
230+
position: 'relative',
231+
transition: 'opacity 0.2s ease-in-out, width 0.2s ease-in-out',
232+
'&:hover': {
233+
opacity: 1,
234+
},
197235
}}
198-
srcDoc={`<div>
236+
>
237+
{showCloseButton && (
238+
<IconButton
239+
onClick={handleClose}
240+
sx={{
241+
position: 'absolute',
242+
top: CLOSE_BUTTON_SPACING,
243+
right: CLOSE_BUTTON_OFFSET,
244+
zIndex: 1,
245+
padding: '4px',
246+
backgroundColor: 'rgba(255, 255, 255, 0.9)',
247+
'&:hover': {
248+
backgroundColor: 'rgba(255, 255, 255, 1)',
249+
},
250+
}}
251+
size="small"
252+
>
253+
<CloseIcon fontSize="small" />
254+
</IconButton>
255+
)}
256+
<Box
257+
sx={{
258+
maxHeight: `${POPUP_MAX_HEIGHT}px`,
259+
overflowY: 'auto',
260+
overflowX: 'hidden',
261+
}}
262+
>
263+
<iframe
264+
ref={iframeRef}
265+
onLoad={handleIframeLoad}
266+
style={{
267+
width: '100%',
268+
border: 'none',
269+
display: 'block',
270+
...props.iframeStyle,
271+
}}
272+
srcDoc={`<div>
199273
<style>
274+
* {
275+
box-sizing: border-box;
276+
}
200277
body {
278+
margin: 0;
279+
padding: ${POPUP_PADDING_VERTICAL}px ${POPUP_PADDING_HORIZONTAL}px;
280+
${showCloseButton ? 'padding-top: 40px;' : ''}
281+
background: transparent;
282+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
283+
font-size: 14px;
284+
line-height: 1.6;
285+
color: rgba(0, 0, 0, 0.87);
286+
-webkit-font-smoothing: antialiased;
287+
-moz-osx-font-smoothing: grayscale;
288+
overflow-x: hidden;
201289
${Object.entries(props.iframeBodyStyle || {})
202290
.map(([key, val]) => `${key.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${val};`)
203291
.join(' ')}
204292
}
293+
h1, h2, h3, h4, h5, h6 {
294+
margin: 0 0 8px 0;
295+
font-weight: 500;
296+
}
297+
p {
298+
margin: 0 0 8px 0;
299+
}
300+
table {
301+
border-collapse: collapse;
302+
width: 100%;
303+
max-width: 100%;
304+
}
305+
th, td {
306+
padding: 4px 8px;
307+
text-align: left;
308+
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
309+
word-wrap: break-word;
310+
}
311+
th {
312+
font-weight: 500;
313+
color: rgba(0, 0, 0, 0.6);
314+
}
315+
img {
316+
max-width: 100%;
317+
height: auto;
318+
}
205319
</style>
206320
${props.content || ''}
207321
</div>`}
208-
sandbox="allow-same-origin allow-popups-to-escape-sandbox allow-scripts"
209-
title={mapHook.componentId}
210-
/>
322+
sandbox="allow-same-origin allow-popups-to-escape-sandbox allow-scripts"
323+
title={mapHook.componentId}
324+
/>
325+
</Box>
326+
</Paper>
211327
</Box>,
212328
container.current
213329
)

packages/react-maplibre/src/components/MlSketchTool/MlSketchTool.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,9 @@ const MlSketchTool = (props: MlSketchToolProps) => {
330330
if (!updatedGeometries[index].properties) {
331331
updatedGeometries[index].properties = {};
332332
}
333-
updatedGeometries[index].properties!.name = newName;
333+
if (updatedGeometries[index].properties) {
334+
updatedGeometries[index].properties.name = newName;
335+
}
334336
return {
335337
..._sketchState,
336338
geometries: updatedGeometries,
@@ -351,7 +353,9 @@ const MlSketchTool = (props: MlSketchToolProps) => {
351353
if (!updatedGeometries[index].properties) {
352354
updatedGeometries[index].properties = {};
353355
}
354-
updatedGeometries[index].properties!.customName = true;
356+
if (updatedGeometries[index].properties) {
357+
updatedGeometries[index].properties.customName = true;
358+
}
355359
return {
356360
..._sketchState,
357361
geometries: updatedGeometries,
@@ -386,7 +390,9 @@ const MlSketchTool = (props: MlSketchToolProps) => {
386390
if (!updatedGeometries[index].properties) {
387391
updatedGeometries[index].properties = {};
388392
}
389-
updatedGeometries[index].properties!.customName = false;
393+
if (updatedGeometries[index].properties) {
394+
updatedGeometries[index].properties.customName = false;
395+
}
390396
return {
391397
..._sketchState,
392398
geometries: updatedGeometries,

packages/react-maplibre/src/components/MlWmsLayer/MlWmsLayer.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useMemo, useRef, useEffect, useCallback } from 'react';
22
import useMap from '../../hooks/useMap';
33
import { RasterLayerSpecification, RasterSourceSpecification } from 'maplibre-gl';
4+
import { normalizeWmsParams } from '../../utils/wmsUtils';
45

56
const defaultProps: MlWmsLayerProps = {
67
url: '',
@@ -11,6 +12,7 @@ const defaultProps: MlWmsLayerProps = {
1112
version: '1.1.1',
1213
request: 'GetMap',
1314
srs: 'EPSG:3857',
15+
crs: 'EPSG:3857',
1416
width: '256',
1517
height: '256',
1618
Transparent: 'true',
@@ -62,11 +64,12 @@ const MlWmsLayer = (props: MlWmsLayerProps) => {
6264
_wmsUrl = _propsUrlParams[0];
6365
}
6466
const _urlParamsFromUrl = new URLSearchParams(_propsUrlParams?.[1]);
67+
6568
// first spread in default props manually to enable overriding a single parameter without replacing the whole default urlParameters object
6669
const urlParamsObj = {
67-
...defaultProps.urlParameters,
68-
...Object.fromEntries(_urlParamsFromUrl),
69-
...props.urlParameters,
70+
...normalizeWmsParams(defaultProps.urlParameters),
71+
...normalizeWmsParams(_urlParamsFromUrl),
72+
...normalizeWmsParams(props.urlParameters),
7073
};
7174
const urlParams = new URLSearchParams(urlParamsObj as unknown as Record<string, string>);
7275
const urlParamsStr =

0 commit comments

Comments
 (0)