@biodiv-platform/naksha-maplibre-draw 5.0.7 → 5.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -49,10 +49,14 @@ module.exports = __toCommonJS(src_exports);
49
49
 
50
50
  // src/components/map.tsx
51
51
  var import_maplibre_gl2 = __toESM(require("maplibre-gl"));
52
+ var import_react3 = require("react");
52
53
 
53
54
  // src/components/setup-draw.tsx
54
55
  var import_terra_draw = require("terra-draw");
55
56
  var import_terra_draw_maplibre_gl_adapter = require("terra-draw-maplibre-gl-adapter");
57
+ var import_twind = require("twind");
58
+ var import_react = __toESM(require("react"));
59
+ var import_jsx_runtime = require("react/jsx-runtime");
56
60
  function setupDraw(map) {
57
61
  return new import_terra_draw.TerraDraw({
58
62
  tracked: true,
@@ -143,9 +147,50 @@ function setupDraw(map) {
143
147
  ]
144
148
  });
145
149
  }
146
-
147
- // src/components/map.tsx
148
- var import_react2 = require("react");
150
+ var ensureModeProperty = (features) => {
151
+ return features.map((feature) => {
152
+ var _a, _b;
153
+ if ((_a = feature.properties) == null ? void 0 : _a.mode)
154
+ return feature;
155
+ const type = (_b = feature.geometry) == null ? void 0 : _b.type;
156
+ const mode = type === "Point" ? "point" : type === "Polygon" ? "polygon" : type === "LineString" ? "linestring" : "unknown";
157
+ return __spreadProps(__spreadValues({}, feature), {
158
+ properties: __spreadProps(__spreadValues({}, feature.properties), { mode })
159
+ });
160
+ });
161
+ };
162
+ var IconButton = import_react.default.forwardRef(
163
+ (props, ref) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", __spreadValues({
164
+ ref,
165
+ type: "button",
166
+ className: import_twind.tw`h-8 px-3 py-2 rounded-md focus:outline-none cursor-pointer text-md flex align-middle items-center gap-2 bg-gray-100 hover:bg-gray-200 text-gray-700 disabled:cursor-not-allowed disabled:bg-gray-100! disabled:opacity-50`
167
+ }, props))
168
+ );
169
+ var MapStyleSwitcher = ({
170
+ currentStyleIdx,
171
+ onStyleChange,
172
+ mapStyles
173
+ }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
174
+ style: {
175
+ position: "absolute",
176
+ left: 12,
177
+ bottom: 12,
178
+ background: "rgba(255,255,255,0.95)",
179
+ padding: "8px",
180
+ borderRadius: "6px",
181
+ zIndex: 1e3,
182
+ boxShadow: "0 2px 4px rgba(0,0,0,0.2)"
183
+ },
184
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", {
185
+ value: currentStyleIdx,
186
+ onChange: (e) => onStyleChange(Number(e.target.value)),
187
+ style: { fontSize: 14, padding: "4px" },
188
+ children: mapStyles.map((style, idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
189
+ value: idx,
190
+ children: style.text || `Style ${idx + 1}`
191
+ }, style.key || idx))
192
+ })
193
+ });
149
194
 
150
195
  // src/components/setup-maplibre.tsx
151
196
  var import_maplibre_gl = __toESM(require("maplibre-gl"));
@@ -174,9 +219,12 @@ function setupMaplibreMap({
174
219
  });
175
220
  }
176
221
 
222
+ // src/components/map-buttons/map-buttons.tsx
223
+ var import_react2 = require("react");
224
+
177
225
  // src/components/icon-buttons/icons.tsx
178
- var import_jsx_runtime = require("react/jsx-runtime");
179
- var SelectIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", {
226
+ var import_jsx_runtime2 = require("react/jsx-runtime");
227
+ var PencilOpenIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", {
180
228
  xmlns: "http://www.w3.org/2000/svg",
181
229
  width: "24",
182
230
  height: "24",
@@ -187,15 +235,101 @@ var SelectIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", {
187
235
  strokeLinecap: "round",
188
236
  strokeLinejoin: "round",
189
237
  children: [
190
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
238
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", {
239
+ d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"
240
+ }),
241
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", {
242
+ d: "m15 5 4 4"
243
+ })
244
+ ]
245
+ });
246
+ var PencilCloseIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", {
247
+ xmlns: "http://www.w3.org/2000/svg",
248
+ width: "24",
249
+ height: "24",
250
+ viewBox: "0 0 24 24",
251
+ fill: "none",
252
+ stroke: "currentColor",
253
+ strokeWidth: "1.5",
254
+ strokeLinecap: "round",
255
+ strokeLinejoin: "round",
256
+ children: [
257
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", {
258
+ d: "m10 10-6.157 6.162a2 2 0 0 0-.5.833l-1.322 4.36a.5.5 0 0 0 .622.624l4.358-1.323a2 2 0 0 0 .83-.5L14 13.982"
259
+ }),
260
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", {
261
+ d: "m12.829 7.172 4.359-4.346a1 1 0 1 1 3.986 3.986l-4.353 4.353"
262
+ }),
263
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", {
264
+ d: "m15 5 4 4"
265
+ }),
266
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", {
267
+ d: "m2 2 20 20"
268
+ })
269
+ ]
270
+ });
271
+ var LocateIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", {
272
+ xmlns: "http://www.w3.org/2000/svg",
273
+ width: "24",
274
+ height: "24",
275
+ viewBox: "0 0 24 24",
276
+ fill: "none",
277
+ stroke: "currentColor",
278
+ strokeWidth: "1.5",
279
+ strokeLinecap: "round",
280
+ strokeLinejoin: "round",
281
+ children: [
282
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", {
283
+ x1: "2",
284
+ x2: "5",
285
+ y1: "12",
286
+ y2: "12"
287
+ }),
288
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", {
289
+ x1: "19",
290
+ x2: "22",
291
+ y1: "12",
292
+ y2: "12"
293
+ }),
294
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", {
295
+ x1: "12",
296
+ x2: "12",
297
+ y1: "2",
298
+ y2: "5"
299
+ }),
300
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", {
301
+ x1: "12",
302
+ x2: "12",
303
+ y1: "19",
304
+ y2: "22"
305
+ }),
306
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", {
307
+ cx: "12",
308
+ cy: "12",
309
+ r: "7"
310
+ })
311
+ ]
312
+ });
313
+ var SelectIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", {
314
+ xmlns: "http://www.w3.org/2000/svg",
315
+ width: "24",
316
+ height: "24",
317
+ viewBox: "0 0 24 24",
318
+ fill: "none",
319
+ stroke: "currentColor",
320
+ strokeWidth: "1.5",
321
+ strokeLinecap: "round",
322
+ strokeLinejoin: "round",
323
+ children: [
324
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", {
191
325
  d: "M12.586 12.586 19 19"
192
326
  }),
193
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
327
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", {
194
328
  d: "M3.688 3.037a.497.497 0 0 0-.651.651l6.5 15.999a.501.501 0 0 0 .947-.062l1.569-6.083a2 2 0 0 1 1.448-1.479l6.124-1.579a.5.5 0 0 0 .063-.947z"
195
329
  })
196
330
  ]
197
331
  });
198
- var PointIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", {
332
+ var PointIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", {
199
333
  xmlns: "http://www.w3.org/2000/svg",
200
334
  width: "24",
201
335
  height: "24",
@@ -206,150 +340,143 @@ var PointIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", {
206
340
  strokeLinecap: "round",
207
341
  strokeLinejoin: "round",
208
342
  children: [
209
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
343
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", {
210
344
  d: "M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"
211
345
  }),
212
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", {
346
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", {
213
347
  cx: "12",
214
348
  cy: "10",
215
349
  r: "3"
216
350
  })
217
351
  ]
218
352
  });
219
- var LineIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
353
+ var LineIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", {
220
354
  width: "16",
221
355
  height: "16",
222
356
  viewBox: "0 0 24 24",
223
357
  fill: "none",
224
358
  stroke: "currentColor",
225
359
  strokeWidth: "2",
226
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", {
360
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", {
227
361
  x1: "4",
228
362
  y1: "20",
229
363
  x2: "20",
230
364
  y2: "4"
231
365
  })
232
366
  });
233
- var PolygonIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
367
+ var PolygonIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", {
234
368
  width: "16",
235
369
  height: "16",
236
370
  viewBox: "0 0 24 24",
237
371
  fill: "none",
238
372
  stroke: "currentColor",
239
373
  strokeWidth: "2",
240
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polygon", {
374
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polygon", {
241
375
  points: "12,2 22,8 18,22 6,22 2,8"
242
376
  })
243
377
  });
244
- var DeleteIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
378
+ var RectangleIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", {
379
+ width: "16",
380
+ height: "16",
381
+ viewBox: "0 0 24 24",
382
+ fill: "none",
383
+ stroke: "currentColor",
384
+ strokeWidth: "2",
385
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", {
386
+ x: "4",
387
+ y: "4",
388
+ width: "16",
389
+ height: "16"
390
+ })
391
+ });
392
+ var CircleIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", {
245
393
  width: "16",
246
394
  height: "16",
247
395
  viewBox: "0 0 24 24",
248
396
  fill: "none",
249
397
  stroke: "currentColor",
250
398
  strokeWidth: "2",
251
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
399
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", {
400
+ cx: "12",
401
+ cy: "12",
402
+ r: "10"
403
+ })
404
+ });
405
+ var FreeHandIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", {
406
+ width: "16",
407
+ height: "16",
408
+ viewBox: "0 0 24 24",
409
+ fill: "none",
410
+ stroke: "currentColor",
411
+ strokeWidth: "2",
412
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", {
413
+ d: "M3 16c1-3 4-5 7-4 3 1 5 3 7 2 2-1 3-4 1-6-1-2-4-2-6-1"
414
+ })
415
+ });
416
+ var DeleteIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", {
417
+ width: "16",
418
+ height: "16",
419
+ viewBox: "0 0 24 24",
420
+ fill: "none",
421
+ stroke: "currentColor",
422
+ strokeWidth: "2",
423
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", {
252
424
  d: "M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"
253
425
  })
254
426
  });
255
427
 
256
- // src/components/map-button/MapButton.tsx
257
- var import_jsx_runtime2 = require("react/jsx-runtime");
428
+ // src/components/map-button/map-button.tsx
429
+ var import_jsx_runtime3 = require("react/jsx-runtime");
430
+ var BUTTON_STYLE = {
431
+ display: "flex",
432
+ justifyContent: "center",
433
+ alignItems: "center",
434
+ width: 30,
435
+ height: 30,
436
+ padding: 0,
437
+ borderRadius: 4
438
+ };
258
439
  var MapButton = ({
259
440
  mode,
260
441
  currentMode,
261
- changeMode,
442
+ onClick,
262
443
  icon,
263
- tooltip
264
- }) => {
265
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", {
266
- onClick: () => changeMode(mode),
267
- title: tooltip,
268
- style: {
269
- display: "flex",
270
- justifyContent: "center",
271
- alignItems: "center",
272
- width: "30px",
273
- height: "30px",
274
- padding: "0",
275
- borderRadius: "4px",
276
- backgroundColor: currentMode === mode ? "#02cf87" : "#f0f0f0",
277
- cursor: "pointer"
278
- },
279
- children: icon
280
- });
281
- };
282
- var MapButton_default = MapButton;
283
-
284
- // src/components/map-buttons/MapButtons.tsx
285
- var import_jsx_runtime3 = require("react/jsx-runtime");
286
- var MapButtons = ({
287
- mode,
288
- changeMode
289
- }) => {
290
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", {
291
- style: {
292
- position: "absolute",
293
- top: "10px",
294
- left: "10px",
295
- display: "flex",
296
- flexDirection: "column",
297
- gap: "6px",
298
- zIndex: 1e3,
299
- backgroundColor: "rgba(255, 255, 255, 0.9)",
300
- padding: "8px",
301
- borderRadius: "6px",
302
- boxShadow: "0 2px 6px rgba(0,0,0,0.2)"
303
- },
304
- children: [
305
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MapButton_default, {
306
- mode: "select",
307
- icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SelectIcon, {}),
308
- currentMode: mode,
309
- changeMode,
310
- tooltip: "Select"
311
- }),
312
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MapButton_default, {
313
- mode: "point",
314
- icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PointIcon, {}),
315
- currentMode: mode,
316
- changeMode,
317
- tooltip: "Point"
318
- }),
319
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MapButton_default, {
320
- mode: "linestring",
321
- icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LineIcon, {}),
322
- currentMode: mode,
323
- changeMode,
324
- tooltip: "Line"
325
- }),
326
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MapButton_default, {
327
- mode: "polygon",
328
- icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PolygonIcon, {}),
329
- currentMode: mode,
330
- changeMode,
331
- tooltip: "Polygon"
332
- })
333
- ]
334
- });
335
- };
336
- var MapButtons_default = MapButtons;
444
+ tooltip,
445
+ disabled
446
+ }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IconButton, {
447
+ type: "button",
448
+ title: tooltip,
449
+ disabled,
450
+ onClick: () => !disabled && onClick(mode != null ? mode : ""),
451
+ style: __spreadProps(__spreadValues({}, BUTTON_STYLE), {
452
+ backgroundColor: disabled ? "#d3d3d3" : currentMode === mode ? "#02cf87" : "#f0f0f0",
453
+ cursor: disabled ? "not-allowed" : "pointer",
454
+ opacity: disabled ? 0.6 : 1
455
+ }),
456
+ children: icon
457
+ });
458
+ var map_button_default = MapButton;
337
459
 
338
- // src/components/icon-buttons/ClearButton.tsx
339
- var import_react = require("react");
460
+ // src/components/map-buttons/map-buttons.tsx
340
461
  var import_jsx_runtime4 = require("react/jsx-runtime");
341
- var ClearButton = ({
462
+ function MapButtons({
463
+ mode,
464
+ setMode,
465
+ onClick,
342
466
  draw,
343
- onFeaturesChange
344
- }) => {
345
- const [hasFeatures, setHasFeatures] = (0, import_react.useState)(false);
346
- (0, import_react.useEffect)(() => {
467
+ onFeaturesChange,
468
+ isMultiple = true,
469
+ autoFocus
470
+ }) {
471
+ const [expanded, setExpanded] = (0, import_react2.useState)(false);
472
+ const [hasFeatures, setHasFeatures] = (0, import_react2.useState)(false);
473
+ const updateHasFeatures = (0, import_react2.useCallback)(
474
+ () => setHasFeatures(draw.getSnapshot().length > 0),
475
+ [draw]
476
+ );
477
+ (0, import_react2.useEffect)(() => {
347
478
  if (!draw)
348
479
  return;
349
- const updateHasFeatures = () => {
350
- const snapshot = draw.getSnapshot();
351
- setHasFeatures(snapshot.length > 0);
352
- };
353
480
  draw.on("finish", updateHasFeatures);
354
481
  draw.on("change", updateHasFeatures);
355
482
  updateHasFeatures();
@@ -357,174 +484,312 @@ var ClearButton = ({
357
484
  draw.off("finish", updateHasFeatures);
358
485
  draw.off("change", updateHasFeatures);
359
486
  };
360
- }, [draw]);
361
- const handleClear = () => {
362
- if (!draw)
487
+ }, [draw, updateHasFeatures]);
488
+ const handleGeolocation = (0, import_react2.useCallback)(() => {
489
+ if (!("geolocation" in navigator)) {
490
+ alert("Geolocation is not supported in this browser.");
363
491
  return;
492
+ }
493
+ navigator.geolocation.getCurrentPosition(
494
+ (pos) => {
495
+ const coords = [
496
+ pos.coords.longitude,
497
+ pos.coords.latitude
498
+ ];
499
+ const pt = {
500
+ type: "Feature",
501
+ geometry: { type: "Point", coordinates: coords },
502
+ properties: { mode: "point", source: "geolocation" }
503
+ };
504
+ if (!isMultiple)
505
+ draw.clear();
506
+ draw.addFeatures([pt]);
507
+ onFeaturesChange == null ? void 0 : onFeaturesChange(draw.getSnapshot());
508
+ autoFocus == null ? void 0 : autoFocus([pt]);
509
+ },
510
+ (error) => {
511
+ alert("Error getting geolocation");
512
+ console.error(error);
513
+ }
514
+ );
515
+ setMode("current-location");
516
+ }, [draw, isMultiple, onFeaturesChange, autoFocus]);
517
+ const handleClear = (0, import_react2.useCallback)(() => {
364
518
  draw.clear();
365
519
  setHasFeatures(false);
366
- if (onFeaturesChange)
367
- onFeaturesChange([]);
368
- };
369
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", {
370
- onClick: handleClear,
371
- title: "Clear all features",
372
- disabled: !hasFeatures,
520
+ setMode("static");
521
+ onFeaturesChange == null ? void 0 : onFeaturesChange([]);
522
+ }, [draw, onFeaturesChange]);
523
+ const buttons = (0, import_react2.useMemo)(
524
+ () => [
525
+ {
526
+ icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(LocateIcon, {}),
527
+ mode: "current-location",
528
+ onClick: handleGeolocation,
529
+ tooltip: "Use current location"
530
+ },
531
+ {
532
+ icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SelectIcon, {}),
533
+ mode: "select",
534
+ onClick,
535
+ tooltip: "Select"
536
+ },
537
+ {
538
+ icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PointIcon, {}),
539
+ mode: "point",
540
+ onClick,
541
+ tooltip: "Point"
542
+ },
543
+ {
544
+ icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(LineIcon, {}),
545
+ mode: "linestring",
546
+ onClick,
547
+ tooltip: "Linestring"
548
+ },
549
+ {
550
+ icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PolygonIcon, {}),
551
+ mode: "polygon",
552
+ onClick,
553
+ tooltip: "Polygon"
554
+ },
555
+ {
556
+ icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RectangleIcon, {}),
557
+ mode: "rectangle",
558
+ onClick,
559
+ tooltip: "Reactangle"
560
+ },
561
+ {
562
+ icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CircleIcon, {}),
563
+ mode: "circle",
564
+ onClick,
565
+ tooltip: "Circle"
566
+ },
567
+ {
568
+ icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FreeHandIcon, {}),
569
+ mode: "freehand",
570
+ onClick,
571
+ tooltip: "Freehand"
572
+ },
573
+ {
574
+ icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DeleteIcon, {}),
575
+ mode: "clear",
576
+ onClick: handleClear,
577
+ tooltip: "Clear all features",
578
+ disabled: !hasFeatures
579
+ }
580
+ ],
581
+ [onClick, handleGeolocation, handleClear, hasFeatures]
582
+ );
583
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", {
373
584
  style: {
585
+ position: "absolute",
586
+ top: 10,
587
+ left: 10,
374
588
  display: "flex",
375
- justifyContent: "center",
376
- alignItems: "center",
377
- width: "30px",
378
- height: "30px",
379
- padding: "0",
380
- borderRadius: "4px",
381
- backgroundColor: hasFeatures ? "#f0f0f0" : "#e0e0e0",
382
- cursor: hasFeatures ? "pointer" : "not-allowed"
589
+ flexDirection: "column",
590
+ gap: 6,
591
+ zIndex: 1e3,
592
+ backgroundColor: "rgba(255,255,255,0.9)",
593
+ padding: 8,
594
+ borderRadius: 6,
595
+ boxShadow: "0 2px 6px rgba(0,0,0,0.2)"
383
596
  },
384
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DeleteIcon, {})
597
+ children: [
598
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconButton, {
599
+ onClick: () => setExpanded((v) => !v),
600
+ style: {
601
+ display: "flex",
602
+ justifyContent: "center",
603
+ alignItems: "center",
604
+ width: 30,
605
+ height: 30,
606
+ padding: 0,
607
+ borderRadius: 4,
608
+ backgroundColor: "#d3d3d3"
609
+ },
610
+ title: "Expand or collapse tools",
611
+ children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PencilCloseIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PencilOpenIcon, {})
612
+ }),
613
+ expanded && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", {
614
+ style: { display: "flex", flexDirection: "column", gap: 6 },
615
+ children: buttons.map((btn, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(map_button_default, {
616
+ icon: btn.icon,
617
+ mode: btn.mode,
618
+ currentMode: mode,
619
+ onClick: btn.onClick,
620
+ tooltip: btn.tooltip,
621
+ disabled: btn.disabled
622
+ }, i))
623
+ })
624
+ ]
385
625
  });
386
- };
387
- var ClearButton_default = ClearButton;
626
+ }
627
+ var map_buttons_default = MapButtons;
388
628
 
389
629
  // src/components/map.tsx
390
- var import_naksha_commons = require("@biodiv-platform/naksha-commons");
391
630
  var import_bbox = __toESM(require("@turf/bbox"));
631
+ var import_naksha_commons = require("@biodiv-platform/naksha-commons");
392
632
  var import_jsx_runtime5 = require("react/jsx-runtime");
393
- var ensureModeProperty = (features) => {
394
- return features.map((feature) => {
395
- var _a, _b;
396
- if ((_a = feature.properties) == null ? void 0 : _a.mode)
397
- return feature;
398
- const geometryType = (_b = feature.geometry) == null ? void 0 : _b.type;
399
- const mode = geometryType === "Point" ? "point" : geometryType === "Polygon" ? "polygon" : geometryType === "LineString" ? "linestring" : "unknown";
400
- return __spreadProps(__spreadValues({}, feature), {
401
- properties: __spreadProps(__spreadValues({}, feature.properties), { mode })
402
- });
403
- });
404
- };
405
633
  var Map = (props) => {
406
- const mapRef = (0, import_react2.useRef)(null);
407
- const [map, setMap] = (0, import_react2.useState)();
408
- const [mode, setMode] = (0, import_react2.useState)("static");
409
- const [draw, setDraw] = (0, import_react2.useState)();
410
- const mapStyle = import_naksha_commons.defaultMapStyles[(props == null ? void 0 : props.mapStyle) || 0].style;
411
- const { isMultiple = true } = props;
412
- (0, import_react2.useEffect)(() => {
413
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
634
+ var _a;
635
+ const mapRef = (0, import_react3.useRef)(null);
636
+ const [map, setMap] = (0, import_react3.useState)();
637
+ const [mode, setMode] = (0, import_react3.useState)("static");
638
+ const [draw, setDraw] = (0, import_react3.useState)();
639
+ const drawRef = (0, import_react3.useRef)();
640
+ const { isMultiple, features = [], onFeaturesChange } = props;
641
+ const [selectedMapStyleIdx, setSelectedMapStyleIdx] = (0, import_react3.useState)(
642
+ (props == null ? void 0 : props.mapStyle) || 0
643
+ );
644
+ const mapTile = props.mapStyles ? props.mapStyles : import_naksha_commons.defaultMapStyles;
645
+ const mapStyle = (_a = mapTile[selectedMapStyleIdx]) == null ? void 0 : _a.style;
646
+ const mapStyleConfig = mapTile[selectedMapStyleIdx];
647
+ const storedFeaturesRef = (0, import_react3.useRef)([]);
648
+ const autoFocus = (0, import_react3.useCallback)(
649
+ (features2) => {
650
+ if (!(features2 == null ? void 0 : features2.length) || !map)
651
+ return;
652
+ const _bbox = (0, import_bbox.default)({ type: "FeatureCollection", features: features2 });
653
+ map.fitBounds(_bbox, {
654
+ padding: 40,
655
+ duration: 1e3,
656
+ maxZoom: 15
657
+ });
658
+ },
659
+ [map]
660
+ );
661
+ (0, import_react3.useEffect)(() => {
662
+ var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j;
663
+ if (features && features.length > 0) {
664
+ storedFeaturesRef.current = ensureModeProperty(features);
665
+ }
414
666
  const maplibreMap = setupMaplibreMap({
415
667
  containerId: "maplibre-map",
416
- latitude: (_b = (_a = props.defaultViewState) == null ? void 0 : _a.latitude) != null ? _b : 0,
668
+ latitude: (_b = (_a2 = props.defaultViewState) == null ? void 0 : _a2.latitude) != null ? _b : 0,
417
669
  longitude: (_d = (_c = props.defaultViewState) == null ? void 0 : _c.longitude) != null ? _d : 0,
418
670
  zoom: (_f = (_e = props.defaultViewState) == null ? void 0 : _e.zoom) != null ? _f : 0,
419
671
  pitch: (_h = (_g = props.defaultViewState) == null ? void 0 : _g.pitch) != null ? _h : 0,
420
672
  bearing: (_j = (_i = props.defaultViewState) == null ? void 0 : _i.bearing) != null ? _j : 0,
421
673
  mapstyle: mapStyle
422
674
  });
675
+ if ("maxZoom" in mapStyleConfig && mapStyleConfig.maxZoom) {
676
+ maplibreMap.setMaxZoom(mapStyleConfig.maxZoom);
677
+ }
423
678
  const navControl = new import_maplibre_gl2.default.NavigationControl({
424
679
  showCompass: true,
425
680
  showZoom: true,
426
681
  visualizePitch: true
427
682
  });
683
+ const fullscreen = new import_maplibre_gl2.default.FullscreenControl({});
428
684
  maplibreMap.addControl(navControl, "bottom-right");
429
- maplibreMap.once("style.load", () => setMap(maplibreMap));
685
+ maplibreMap.addControl(fullscreen, "bottom-right");
686
+ maplibreMap.once("style.load", () => {
687
+ setMap(maplibreMap);
688
+ });
430
689
  return () => {
431
- if (navControl && maplibreMap) {
690
+ if (drawRef.current) {
691
+ storedFeaturesRef.current = drawRef.current.getSnapshot();
692
+ }
693
+ if (drawRef.current) {
694
+ try {
695
+ drawRef.current.stop();
696
+ } catch (e) {
697
+ console.warn("terraDraw stop failed", e);
698
+ }
699
+ drawRef.current = void 0;
700
+ setDraw(void 0);
701
+ }
702
+ if (maplibreMap) {
432
703
  maplibreMap.removeControl(navControl);
704
+ maplibreMap.removeControl(fullscreen);
705
+ maplibreMap.remove();
433
706
  }
434
707
  };
435
- }, []);
436
- const autoFocus = (0, import_react2.useCallback)(
437
- (features) => {
438
- if (!(features == null ? void 0 : features.length) || !map)
439
- return;
440
- const _bbox = (0, import_bbox.default)({
441
- type: "FeatureCollection",
442
- features
443
- });
444
- map.fitBounds(_bbox, { padding: 40, duration: 1e3 });
445
- },
446
- [map]
708
+ }, [mapStyle]);
709
+ const filterUserFeatures = (features2) => features2.filter(
710
+ (f) => f.geometry.type !== "Point" || f.properties.mode !== "static" && f.properties.mode !== "select"
447
711
  );
448
- (0, import_react2.useEffect)(() => {
449
- var _a;
712
+ (0, import_react3.useEffect)(() => {
450
713
  if (!map)
451
714
  return;
452
715
  const terraDraw = setupDraw(map);
453
716
  terraDraw.start();
717
+ drawRef.current = terraDraw;
454
718
  setDraw(terraDraw);
455
719
  const handleChange = () => {
456
- var _a2;
457
- const snapshot = terraDraw.getSnapshot();
458
- let filteredFeatures = snapshot;
459
- if (!isMultiple && snapshot.length > 1) {
460
- const latestFeature = snapshot[snapshot.length - 1];
461
- filteredFeatures = [latestFeature];
462
- const featuresToRemove = snapshot.slice(0, -1);
463
- const featureIds = featuresToRemove.map((f) => f.id).filter((id) => id !== void 0);
464
- if (featureIds.length > 0) {
465
- terraDraw.removeFeatures(featureIds);
720
+ let snapshot = terraDraw.getSnapshot();
721
+ let userFeatures = filterUserFeatures(snapshot);
722
+ if (!isMultiple && userFeatures.length > 1) {
723
+ const lastFeature = userFeatures.at(-1);
724
+ const toRemove = userFeatures.slice(0, -1).map((f) => f.id).filter((id) => Boolean(id));
725
+ if (toRemove.length > 0) {
726
+ try {
727
+ terraDraw.removeFeatures(toRemove);
728
+ } catch (e) {
729
+ console.warn("removeFeatures failed", e);
730
+ }
466
731
  }
732
+ userFeatures = [lastFeature];
467
733
  }
468
- (_a2 = props.onFeaturesChange) == null ? void 0 : _a2.call(props, snapshot);
469
- autoFocus(filteredFeatures);
734
+ onFeaturesChange == null ? void 0 : onFeaturesChange(userFeatures);
735
+ autoFocus(userFeatures);
470
736
  };
471
737
  terraDraw.on("finish", handleChange);
472
- if ((_a = props.features) == null ? void 0 : _a.length) {
473
- setTimeout(() => {
474
- if (terraDraw.getSnapshot().length === 0) {
475
- const featuresWithMode = ensureModeProperty(props.features);
476
- let initialFeatures = featuresWithMode;
477
- if (!isMultiple && featuresWithMode.length > 1) {
478
- initialFeatures = [featuresWithMode[featuresWithMode.length - 1]];
479
- }
480
- terraDraw.addFeatures(initialFeatures);
481
- autoFocus(initialFeatures);
738
+ setTimeout(() => {
739
+ if (terraDraw.getSnapshot().length === 0) {
740
+ let featuresToAdd = [];
741
+ if (storedFeaturesRef.current.length > 0) {
742
+ featuresToAdd = storedFeaturesRef.current;
743
+ } else if (features && features.length > 0) {
744
+ featuresToAdd = ensureModeProperty(features);
482
745
  }
483
- });
484
- }
746
+ if (!isMultiple && featuresToAdd.length > 1) {
747
+ featuresToAdd = [featuresToAdd.at(-1)];
748
+ }
749
+ if (featuresToAdd.length > 0) {
750
+ terraDraw.addFeatures(featuresToAdd);
751
+ autoFocus(featuresToAdd);
752
+ }
753
+ }
754
+ }, 100);
485
755
  return () => {
486
756
  terraDraw.off("finish", handleChange);
487
- terraDraw.stop();
488
757
  };
489
- }, [map, isMultiple, autoFocus]);
490
- const changeMode = (0, import_react2.useCallback)(
758
+ }, [map]);
759
+ const changeMode = (0, import_react3.useCallback)(
491
760
  (newMode) => {
492
761
  draw == null ? void 0 : draw.setMode(newMode);
493
762
  setMode(newMode);
494
763
  },
495
764
  [draw]
496
765
  );
497
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", {
766
+ const handleStyleChange = (idx) => {
767
+ if (drawRef.current) {
768
+ storedFeaturesRef.current = drawRef.current.getSnapshot();
769
+ }
770
+ setSelectedMapStyleIdx(idx);
771
+ setMode("static");
772
+ };
773
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", {
498
774
  ref: mapRef,
499
775
  id: "maplibre-map",
500
- style: { width: "100%", height: "100%" },
501
- children: draw && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, {
502
- children: [
503
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MapButtons_default, {
504
- mode,
505
- changeMode
506
- }),
507
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", {
508
- style: {
509
- position: "absolute",
510
- top: "180px",
511
- left: "10px",
512
- display: "flex",
513
- flexDirection: "column",
514
- gap: "6px",
515
- zIndex: 1e3,
516
- backgroundColor: "rgba(255, 255, 255, 0.9)",
517
- padding: "8px",
518
- borderRadius: "6px",
519
- boxShadow: "0 2px 6px rgba(0,0,0,0.2)"
520
- },
521
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ClearButton_default, {
522
- draw,
523
- onFeaturesChange: props.onFeaturesChange
524
- })
525
- })
526
- ]
527
- })
776
+ style: { width: "100%", height: "100%", position: "relative" },
777
+ children: [
778
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MapStyleSwitcher, {
779
+ currentStyleIdx: selectedMapStyleIdx,
780
+ onStyleChange: handleStyleChange,
781
+ mapStyles: mapTile
782
+ }),
783
+ draw && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(map_buttons_default, {
784
+ mode,
785
+ setMode,
786
+ onClick: changeMode,
787
+ draw,
788
+ onFeaturesChange,
789
+ isMultiple,
790
+ autoFocus
791
+ })
792
+ ]
528
793
  });
529
794
  };
530
795
  var map_default = Map;