@collabdt/core 0.0.57 → 0.2.0

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.
Files changed (85) hide show
  1. package/README.md +4 -26
  2. package/dist/core/components/support/BugReportDialog.d.ts.map +1 -1
  3. package/dist/core/components/support/BugReportDialog.js +32 -0
  4. package/dist/core/components/support/BugReportDialog.js.map +1 -1
  5. package/dist/core/components/support/FeatureRequestDialog.d.ts.map +1 -1
  6. package/dist/core/components/support/FeatureRequestDialog.js +32 -0
  7. package/dist/core/components/support/FeatureRequestDialog.js.map +1 -1
  8. package/dist/core/components/viewers/Data/DataMenu.d.ts.map +1 -1
  9. package/dist/core/components/viewers/Data/DataMenu.js +9 -1
  10. package/dist/core/components/viewers/Data/DataMenu.js.map +1 -1
  11. package/dist/core/components/viewers/Data/HeaderButtons.d.ts.map +1 -1
  12. package/dist/core/components/viewers/Data/HeaderButtons.js +5 -12
  13. package/dist/core/components/viewers/Data/HeaderButtons.js.map +1 -1
  14. package/dist/core/components/viewers/Data/MoreOptions.d.ts.map +1 -1
  15. package/dist/core/components/viewers/Data/MoreOptions.js +14 -27
  16. package/dist/core/components/viewers/Data/MoreOptions.js.map +1 -1
  17. package/dist/core/components/viewers/Data/buildingDetails/FieldRenderer.d.ts.map +1 -1
  18. package/dist/core/components/viewers/Data/buildingDetails/FieldRenderer.js +13 -18
  19. package/dist/core/components/viewers/Data/buildingDetails/FieldRenderer.js.map +1 -1
  20. package/dist/core/components/viewers/Data/details/FieldValue.d.ts.map +1 -1
  21. package/dist/core/components/viewers/Data/details/FieldValue.js +13 -3
  22. package/dist/core/components/viewers/Data/details/FieldValue.js.map +1 -1
  23. package/dist/core/components/viewers/Data/siteDetails/AssociatedBuildings.d.ts +2 -0
  24. package/dist/core/components/viewers/Data/siteDetails/AssociatedBuildings.d.ts.map +1 -1
  25. package/dist/core/components/viewers/Data/siteDetails/AssociatedBuildings.js +84 -3
  26. package/dist/core/components/viewers/Data/siteDetails/AssociatedBuildings.js.map +1 -1
  27. package/dist/core/components/viewers/Data/siteDetails/SiteDetails.d.ts.map +1 -1
  28. package/dist/core/components/viewers/Data/siteDetails/SiteDetails.js +23 -2
  29. package/dist/core/components/viewers/Data/siteDetails/SiteDetails.js.map +1 -1
  30. package/dist/core/components/viewers/map/src/MapLayers/index.d.ts.map +1 -1
  31. package/dist/core/components/viewers/map/src/MapLayers/index.js +2 -0
  32. package/dist/core/components/viewers/map/src/MapLayers/index.js.map +1 -1
  33. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteContextMenu.d.ts +19 -0
  34. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteContextMenu.d.ts.map +1 -0
  35. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteContextMenu.js +163 -0
  36. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteContextMenu.js.map +1 -0
  37. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteLayer.d.ts +3 -0
  38. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteLayer.d.ts.map +1 -0
  39. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteLayer.js +310 -0
  40. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteLayer.js.map +1 -0
  41. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/siteGeometry.d.ts +30 -0
  42. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/siteGeometry.d.ts.map +1 -0
  43. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/siteGeometry.js +122 -0
  44. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/siteGeometry.js.map +1 -0
  45. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/useShowSitesOnMap.d.ts +13 -0
  46. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/useShowSitesOnMap.d.ts.map +1 -0
  47. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/useShowSitesOnMap.js +58 -0
  48. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/useShowSitesOnMap.js.map +1 -0
  49. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/AssociateBuildingsDialog.d.ts +18 -0
  50. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/AssociateBuildingsDialog.d.ts.map +1 -0
  51. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/AssociateBuildingsDialog.js +102 -0
  52. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/AssociateBuildingsDialog.js.map +1 -0
  53. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/SiteAdder.d.ts.map +1 -1
  54. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/SiteAdder.js +243 -176
  55. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/SiteAdder.js.map +1 -1
  56. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/persistSite.d.ts +24 -0
  57. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/persistSite.d.ts.map +1 -0
  58. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/persistSite.js +46 -0
  59. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/persistSite.js.map +1 -0
  60. package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.d.ts +1 -0
  61. package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.d.ts.map +1 -1
  62. package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.js +1 -0
  63. package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.js.map +1 -1
  64. package/dist/core/hooks/sites/createSiteHooks.d.ts.map +1 -1
  65. package/dist/core/hooks/sites/createSiteHooks.js +0 -1
  66. package/dist/core/hooks/sites/createSiteHooks.js.map +1 -1
  67. package/dist/core/hooks/useCaptureScreenshot.d.ts.map +1 -1
  68. package/dist/core/hooks/useCaptureScreenshot.js +77 -30
  69. package/dist/core/hooks/useCaptureScreenshot.js.map +1 -1
  70. package/dist/core/store/CombineProviders.d.ts.map +1 -1
  71. package/dist/core/store/CombineProviders.js +2 -0
  72. package/dist/core/store/CombineProviders.js.map +1 -1
  73. package/dist/core/store/MapSites/context.d.ts +16 -0
  74. package/dist/core/store/MapSites/context.d.ts.map +1 -0
  75. package/dist/core/store/MapSites/context.js +29 -0
  76. package/dist/core/store/MapSites/context.js.map +1 -0
  77. package/dist/core/store/MapSites/reducer.d.ts +52 -0
  78. package/dist/core/store/MapSites/reducer.d.ts.map +1 -0
  79. package/dist/core/store/MapSites/reducer.js +70 -0
  80. package/dist/core/store/MapSites/reducer.js.map +1 -0
  81. package/dist/core/store/index.d.ts +1 -0
  82. package/dist/core/store/index.d.ts.map +1 -1
  83. package/dist/core/store/index.js +1 -0
  84. package/dist/core/store/index.js.map +1 -1
  85. package/package.json +1 -1
@@ -0,0 +1,163 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import * as LR from "lucide-react";
5
+ import { useTranslations } from "next-intl";
6
+ import { Input } from "../../../../../../ui/Input";
7
+ import ConfirmDialog from "../../../../../../ConfirmDialog";
8
+ const SiteContextMenu = ({
9
+ x,
10
+ y,
11
+ site,
12
+ isEditing,
13
+ canUpdate,
14
+ canDelete,
15
+ canRead,
16
+ onClose,
17
+ onRename,
18
+ onToggleEdit,
19
+ onHide,
20
+ onInfo,
21
+ onDelete
22
+ }) => {
23
+ const t = useTranslations("SiteContextMenu");
24
+ const tf = React.useCallback(
25
+ (key, fallback) => t.has(key) ? t(key) : fallback,
26
+ [t]
27
+ );
28
+ const ref = React.useRef(null);
29
+ const [name, setName] = React.useState(site.name);
30
+ const [isDeleteOpen, setIsDeleteOpen] = React.useState(false);
31
+ const [isDeleting, setIsDeleting] = React.useState(false);
32
+ React.useEffect(() => {
33
+ setName(site.name);
34
+ }, [site.id, site.name]);
35
+ React.useEffect(() => {
36
+ const onPointerDown = (e) => {
37
+ if (isDeleteOpen || isEditing) return;
38
+ if (ref.current && !ref.current.contains(e.target)) onClose();
39
+ };
40
+ const onKey = (e) => {
41
+ if (isDeleteOpen || isEditing) return;
42
+ if (e.key === "Escape") onClose();
43
+ };
44
+ document.addEventListener("mousedown", onPointerDown);
45
+ document.addEventListener("keydown", onKey);
46
+ return () => {
47
+ document.removeEventListener("mousedown", onPointerDown);
48
+ document.removeEventListener("keydown", onKey);
49
+ };
50
+ }, [isDeleteOpen, isEditing, onClose]);
51
+ const commitName = () => {
52
+ const trimmed = name.trim();
53
+ if (trimmed && trimmed !== site.name) onRename(trimmed);
54
+ };
55
+ const handleConfirmDelete = async (e) => {
56
+ e.preventDefault();
57
+ setIsDeleting(true);
58
+ try {
59
+ await onDelete();
60
+ } finally {
61
+ setIsDeleting(false);
62
+ setIsDeleteOpen(false);
63
+ }
64
+ };
65
+ return /* @__PURE__ */ jsxs(
66
+ "div",
67
+ {
68
+ ref,
69
+ className: "fixed z-50 w-56 rounded-md border bg-popover text-popover-foreground shadow-md p-1",
70
+ style: { left: x, top: y },
71
+ onClick: (e) => e.stopPropagation(),
72
+ children: [
73
+ /* @__PURE__ */ jsx("div", { className: "px-2 pt-1.5 pb-2", children: /* @__PURE__ */ jsx(
74
+ Input,
75
+ {
76
+ value: name,
77
+ disabled: !canUpdate,
78
+ onChange: (e) => setName(e.target.value),
79
+ onBlur: commitName,
80
+ onKeyDown: (e) => {
81
+ if (e.key === "Enter") {
82
+ e.preventDefault();
83
+ commitName();
84
+ e.target.blur();
85
+ }
86
+ },
87
+ placeholder: tf("namePlaceholder", "Site name"),
88
+ className: "h-8 text-sm"
89
+ }
90
+ ) }),
91
+ /* @__PURE__ */ jsx("div", { className: "h-px bg-border my-1" }),
92
+ /* @__PURE__ */ jsxs(
93
+ "button",
94
+ {
95
+ type: "button",
96
+ onClick: onToggleEdit,
97
+ disabled: !canUpdate,
98
+ className: "flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-accent disabled:opacity-50 disabled:pointer-events-none",
99
+ children: [
100
+ isEditing ? /* @__PURE__ */ jsx(LR.Check, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(LR.PencilRuler, { className: "h-4 w-4" }),
101
+ isEditing ? tf("doneEditing", "Done editing shape") : tf("editShape", "Edit shape")
102
+ ]
103
+ }
104
+ ),
105
+ /* @__PURE__ */ jsxs(
106
+ "button",
107
+ {
108
+ type: "button",
109
+ onClick: onHide,
110
+ disabled: !canRead,
111
+ className: "flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-accent disabled:opacity-50 disabled:pointer-events-none",
112
+ children: [
113
+ /* @__PURE__ */ jsx(LR.EyeOff, { className: "h-4 w-4" }),
114
+ tf("hide", "Hide")
115
+ ]
116
+ }
117
+ ),
118
+ /* @__PURE__ */ jsxs(
119
+ "button",
120
+ {
121
+ type: "button",
122
+ onClick: onInfo,
123
+ disabled: !canRead,
124
+ className: "flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-accent disabled:opacity-50 disabled:pointer-events-none",
125
+ children: [
126
+ /* @__PURE__ */ jsx(LR.Info, { className: "h-4 w-4" }),
127
+ tf("info", "Site details")
128
+ ]
129
+ }
130
+ ),
131
+ /* @__PURE__ */ jsx("div", { className: "h-px bg-border my-1" }),
132
+ /* @__PURE__ */ jsxs(
133
+ "button",
134
+ {
135
+ type: "button",
136
+ onClick: () => setIsDeleteOpen(true),
137
+ disabled: !canDelete,
138
+ className: "flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-destructive hover:bg-accent disabled:opacity-50 disabled:pointer-events-none",
139
+ children: [
140
+ /* @__PURE__ */ jsx(LR.Trash2, { className: "h-4 w-4" }),
141
+ tf("delete", "Delete site")
142
+ ]
143
+ }
144
+ ),
145
+ /* @__PURE__ */ jsx(
146
+ ConfirmDialog,
147
+ {
148
+ isOpen: isDeleteOpen,
149
+ isDeleting,
150
+ onOpenChange: setIsDeleteOpen,
151
+ handleConfirm: handleConfirmDelete,
152
+ itemName: site.name,
153
+ dataType: tf("siteDataType", "site")
154
+ }
155
+ )
156
+ ]
157
+ }
158
+ );
159
+ };
160
+ export {
161
+ SiteContextMenu
162
+ };
163
+ //# sourceMappingURL=SiteContextMenu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../../src/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteContextMenu.tsx"],"sourcesContent":["'use client'\r\n\r\n// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport * as React from 'react'\r\nimport * as LR from 'lucide-react'\r\nimport { useTranslations } from 'next-intl'\r\nimport { Input } from '../../../../../../ui/Input'\r\nimport { Button } from '../../../../../../ui/Button'\r\nimport ConfirmDialog from '../../../../../../ConfirmDialog'\r\nimport type { ShownSite } from '../../../../../../../store/MapSites/reducer'\r\n\r\nexport interface SiteContextMenuProps {\r\n x: number\r\n y: number\r\n site: ShownSite\r\n isEditing: boolean\r\n canUpdate: boolean\r\n canDelete: boolean\r\n canRead: boolean\r\n onClose: () => void\r\n onRename: (name: string) => void\r\n onToggleEdit: () => void\r\n onHide: () => void\r\n onInfo: () => void\r\n onDelete: () => void | Promise<void>\r\n}\r\n\r\nexport const SiteContextMenu: React.FC<SiteContextMenuProps> = ({\r\n x, y, site, isEditing, canUpdate, canDelete, canRead,\r\n onClose, onRename, onToggleEdit, onHide, onInfo, onDelete,\r\n}) => {\r\n const t = useTranslations('SiteContextMenu')\r\n // Translate with an English fallback so the menu is usable before the host\r\n // app's message catalog is updated (mirrors SiteAdder's approach).\r\n const tf = React.useCallback(\r\n (key: string, fallback: string) => (t.has(key) ? t(key) : fallback),\r\n [t],\r\n )\r\n\r\n const ref = React.useRef<HTMLDivElement>(null)\r\n const [name, setName] = React.useState(site.name)\r\n const [isDeleteOpen, setIsDeleteOpen] = React.useState(false)\r\n const [isDeleting, setIsDeleting] = React.useState(false)\r\n\r\n React.useEffect(() => { setName(site.name) }, [site.id, site.name])\r\n\r\n // Close on outside click / Escape, unless the delete confirmation is open or\r\n // the user is editing the polygon (dragging a vertex is an \"outside\" click).\r\n React.useEffect(() => {\r\n const onPointerDown = (e: MouseEvent) => {\r\n if (isDeleteOpen || isEditing) return\r\n if (ref.current && !ref.current.contains(e.target as Node)) onClose()\r\n }\r\n const onKey = (e: KeyboardEvent) => {\r\n if (isDeleteOpen || isEditing) return\r\n if (e.key === 'Escape') onClose()\r\n }\r\n document.addEventListener('mousedown', onPointerDown)\r\n document.addEventListener('keydown', onKey)\r\n return () => {\r\n document.removeEventListener('mousedown', onPointerDown)\r\n document.removeEventListener('keydown', onKey)\r\n }\r\n }, [isDeleteOpen, isEditing, onClose])\r\n\r\n const commitName = () => {\r\n const trimmed = name.trim()\r\n if (trimmed && trimmed !== site.name) onRename(trimmed)\r\n }\r\n\r\n const handleConfirmDelete = async (e: React.MouseEvent) => {\r\n e.preventDefault()\r\n setIsDeleting(true)\r\n try {\r\n await onDelete()\r\n }\r\n finally {\r\n setIsDeleting(false)\r\n setIsDeleteOpen(false)\r\n }\r\n }\r\n\r\n return (\r\n <div\r\n ref={ref}\r\n className=\"fixed z-50 w-56 rounded-md border bg-popover text-popover-foreground shadow-md p-1\"\r\n style={{ left: x, top: y }}\r\n onClick={e => e.stopPropagation()}\r\n >\r\n {/* Editable site name */}\r\n <div className=\"px-2 pt-1.5 pb-2\">\r\n <Input\r\n value={name}\r\n disabled={!canUpdate}\r\n onChange={e => setName(e.target.value)}\r\n onBlur={commitName}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Enter') { e.preventDefault(); commitName(); (e.target as HTMLInputElement).blur() }\r\n }}\r\n placeholder={tf('namePlaceholder', 'Site name')}\r\n className=\"h-8 text-sm\"\r\n />\r\n </div>\r\n\r\n <div className=\"h-px bg-border my-1\" />\r\n\r\n <button\r\n type=\"button\"\r\n onClick={onToggleEdit}\r\n disabled={!canUpdate}\r\n className=\"flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-accent disabled:opacity-50 disabled:pointer-events-none\"\r\n >\r\n {isEditing\r\n ? <LR.Check className=\"h-4 w-4\" />\r\n : <LR.PencilRuler className=\"h-4 w-4\" />}\r\n {isEditing ? tf('doneEditing', 'Done editing shape') : tf('editShape', 'Edit shape')}\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n onClick={onHide}\r\n disabled={!canRead}\r\n className=\"flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-accent disabled:opacity-50 disabled:pointer-events-none\"\r\n >\r\n <LR.EyeOff className=\"h-4 w-4\" />\r\n {tf('hide', 'Hide')}\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n onClick={onInfo}\r\n disabled={!canRead}\r\n className=\"flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-accent disabled:opacity-50 disabled:pointer-events-none\"\r\n >\r\n <LR.Info className=\"h-4 w-4\" />\r\n {tf('info', 'Site details')}\r\n </button>\r\n\r\n <div className=\"h-px bg-border my-1\" />\r\n\r\n <button\r\n type=\"button\"\r\n onClick={() => setIsDeleteOpen(true)}\r\n disabled={!canDelete}\r\n className=\"flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-destructive hover:bg-accent disabled:opacity-50 disabled:pointer-events-none\"\r\n >\r\n <LR.Trash2 className=\"h-4 w-4\" />\r\n {tf('delete', 'Delete site')}\r\n </button>\r\n\r\n <ConfirmDialog\r\n isOpen={isDeleteOpen}\r\n isDeleting={isDeleting}\r\n onOpenChange={setIsDeleteOpen}\r\n handleConfirm={handleConfirmDelete}\r\n itemName={site.name}\r\n dataType={tf('siteDataType', 'site')}\r\n />\r\n </div>\r\n )\r\n}\r\n"],"mappings":"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","names":[]}
@@ -0,0 +1,3 @@
1
+ import * as React from 'react';
2
+ export declare const SiteLayer: () => React.JSX.Element;
3
+ //# sourceMappingURL=SiteLayer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SiteLayer.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteLayer.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AA8D9B,eAAO,MAAM,SAAS,yBA+QrB,CAAA"}
@@ -0,0 +1,310 @@
1
+ "use client";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
6
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
7
+ var __spreadValues = (a, b) => {
8
+ for (var prop in b || (b = {}))
9
+ if (__hasOwnProp.call(b, prop))
10
+ __defNormalProp(a, prop, b[prop]);
11
+ if (__getOwnPropSymbols)
12
+ for (var prop of __getOwnPropSymbols(b)) {
13
+ if (__propIsEnum.call(b, prop))
14
+ __defNormalProp(a, prop, b[prop]);
15
+ }
16
+ return a;
17
+ };
18
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
19
+ import * as React from "react";
20
+ import { Source, Layer, Marker } from "react-map-gl/maplibre";
21
+ import { toast } from "sonner";
22
+ import {
23
+ MapContext,
24
+ usePermissions,
25
+ useMenusContext,
26
+ useMapSitesContext
27
+ } from "../../../../../../../store";
28
+ import { MapLayerClickPriority } from "../../../../utils/MapEventManager/MapClickManager";
29
+ import { ViewerNames } from "../../../../../../../types/dbTypes";
30
+ import { useSite, useDeleteSite } from "../../../../../../../hooks/sites/sites";
31
+ import { useDeleteFile } from "../../../../../../../hooks/files/files";
32
+ import { polygonCentroid, uploadGeoJsonToAsset, pointToSegmentDistance } from "./siteGeometry";
33
+ import { SiteContextMenu } from "./SiteContextMenu";
34
+ const SITE_FILL_ID = "site-fill";
35
+ const SITE_OUTLINE_ID = "site-outline";
36
+ const SITE_LABEL_ID = "site-label";
37
+ const VERTEX_HIT_PX = 12;
38
+ const EDGE_HIT_PX = 8;
39
+ const MIN_RING_POINTS = 3;
40
+ const fillLayer = {
41
+ id: SITE_FILL_ID,
42
+ type: "fill",
43
+ paint: { "fill-color": "#3b82f6", "fill-opacity": 0.35 },
44
+ filter: ["==", ["get", "type"], "site"]
45
+ };
46
+ const outlineLayer = {
47
+ id: SITE_OUTLINE_ID,
48
+ type: "line",
49
+ layout: { "line-cap": "round", "line-join": "round" },
50
+ paint: { "line-color": "#1c4587", "line-width": 2 },
51
+ filter: ["==", ["get", "type"], "site"]
52
+ };
53
+ const labelLayer = {
54
+ id: SITE_LABEL_ID,
55
+ type: "symbol",
56
+ layout: {
57
+ "text-field": ["get", "name"],
58
+ "text-font": ["Inter"],
59
+ "text-size": 16,
60
+ "text-anchor": "center",
61
+ "text-allow-overlap": false
62
+ },
63
+ paint: {
64
+ "text-color": "#ffffff",
65
+ "text-halo-color": "#000000",
66
+ "text-halo-width": 1.5
67
+ },
68
+ filter: ["==", ["get", "type"], "site-label"]
69
+ };
70
+ const SiteLayer = () => {
71
+ var _a, _b, _c, _d;
72
+ const { state: mapState } = React.useContext(MapContext);
73
+ const { map, mapClickManager } = mapState.map;
74
+ const { state: mapSitesState, dispatch } = useMapSitesContext();
75
+ const { sites, editingSiteId } = mapSitesState.mapSites;
76
+ const { ability } = usePermissions();
77
+ const canUpdate = ability.can("update", "Site");
78
+ const canDelete = ability.can("delete", "Site");
79
+ const canRead = ability.can("read", "Site");
80
+ const { dispatch: menusDispatch, setSelectedSite, setView } = useMenusContext();
81
+ const [menu, setMenu] = React.useState(null);
82
+ const activeSiteId = (_b = (_a = menu == null ? void 0 : menu.siteId) != null ? _a : editingSiteId) != null ? _b : null;
83
+ const { updateSite } = useSite(activeSiteId ? String(activeSiteId) : "");
84
+ const { deleteSite } = useDeleteSite(activeSiteId != null ? activeSiteId : void 0);
85
+ const { deleteFile } = useDeleteFile(void 0, activeSiteId != null ? activeSiteId : void 0);
86
+ const menuSite = menu ? (_c = sites.find((s) => s.id === menu.siteId)) != null ? _c : null : null;
87
+ const sitesRef = React.useRef(sites);
88
+ sitesRef.current = sites;
89
+ const editingSiteIdRef = React.useRef(editingSiteId);
90
+ editingSiteIdRef.current = editingSiteId;
91
+ const featureCollection = React.useMemo(() => {
92
+ const features = [];
93
+ for (const s of sites) {
94
+ if (s.ring.length < 3) continue;
95
+ features.push({
96
+ type: "Feature",
97
+ geometry: { type: "Polygon", coordinates: [[...s.ring, s.ring[0]]] },
98
+ properties: { type: "site", siteId: s.id, name: s.name }
99
+ });
100
+ features.push({
101
+ type: "Feature",
102
+ geometry: { type: "Point", coordinates: polygonCentroid(s.ring) },
103
+ properties: { type: "site-label", siteId: s.id, name: s.name }
104
+ });
105
+ }
106
+ return { type: "FeatureCollection", features };
107
+ }, [sites]);
108
+ React.useEffect(() => {
109
+ if (!map || !mapClickManager) return;
110
+ const handleSiteClick = (e, features) => {
111
+ var _a2, _b2, _c2;
112
+ if (editingSiteIdRef.current != null) return;
113
+ const feature = features == null ? void 0 : features[0];
114
+ const siteId = Number((_a2 = feature == null ? void 0 : feature.properties) == null ? void 0 : _a2.siteId);
115
+ if (!siteId || Number.isNaN(siteId)) return;
116
+ const native = e.originalEvent;
117
+ setMenu({
118
+ x: (_b2 = native == null ? void 0 : native.clientX) != null ? _b2 : e.point.x,
119
+ y: (_c2 = native == null ? void 0 : native.clientY) != null ? _c2 : e.point.y,
120
+ siteId
121
+ });
122
+ };
123
+ const onEnter = () => {
124
+ map.getCanvas().style.cursor = "pointer";
125
+ };
126
+ const onLeave = () => {
127
+ map.getCanvas().style.cursor = "";
128
+ };
129
+ mapClickManager.register(SITE_FILL_ID, MapLayerClickPriority.SiteLayerClickPriority, handleSiteClick);
130
+ map.on("mouseenter", SITE_FILL_ID, onEnter);
131
+ map.on("mouseleave", SITE_FILL_ID, onLeave);
132
+ return () => {
133
+ mapClickManager.unregister(SITE_FILL_ID);
134
+ map.off("mouseenter", SITE_FILL_ID, onEnter);
135
+ map.off("mouseleave", SITE_FILL_ID, onLeave);
136
+ };
137
+ }, [map, mapClickManager]);
138
+ const updateVertex = (index, lng, lat) => {
139
+ if (editingSiteId == null) return;
140
+ const s = sites.find((x) => x.id === editingSiteId);
141
+ if (!s) return;
142
+ const ring = s.ring.map((p, i) => i === index ? [lng, lat] : p);
143
+ dispatch({ type: "UPDATE_SITE_RING", payload: { id: editingSiteId, ring } });
144
+ };
145
+ const deleteVertex = (index) => {
146
+ if (editingSiteId == null) return;
147
+ const s = sites.find((x) => x.id === editingSiteId);
148
+ if (!s) return;
149
+ if (s.ring.length <= MIN_RING_POINTS) {
150
+ toast.warning("A site needs at least 3 points.");
151
+ return;
152
+ }
153
+ const ring = s.ring.filter((_, i) => i !== index);
154
+ dispatch({ type: "UPDATE_SITE_RING", payload: { id: editingSiteId, ring } });
155
+ };
156
+ React.useEffect(() => {
157
+ if (!map || editingSiteId == null) return;
158
+ const onEditClick = (e) => {
159
+ const site = sitesRef.current.find((s) => s.id === editingSiteId);
160
+ const ring = site == null ? void 0 : site.ring;
161
+ if (!ring || ring.length < 2) return;
162
+ const click = e.point;
163
+ for (const v of ring) {
164
+ const vp = map.project(v);
165
+ if (Math.hypot(vp.x - click.x, vp.y - click.y) <= VERTEX_HIT_PX) return;
166
+ }
167
+ let bestDist = Infinity;
168
+ let bestIdx = -1;
169
+ for (let i = 0; i < ring.length; i++) {
170
+ const a = map.project(ring[i]);
171
+ const b = map.project(ring[(i + 1) % ring.length]);
172
+ const d = pointToSegmentDistance(click.x, click.y, a.x, a.y, b.x, b.y);
173
+ if (d < bestDist) {
174
+ bestDist = d;
175
+ bestIdx = i;
176
+ }
177
+ }
178
+ if (bestIdx >= 0 && bestDist <= EDGE_HIT_PX) {
179
+ const next = [...ring];
180
+ next.splice(bestIdx + 1, 0, [e.lngLat.lng, e.lngLat.lat]);
181
+ dispatch({ type: "UPDATE_SITE_RING", payload: { id: editingSiteId, ring: next } });
182
+ }
183
+ };
184
+ map.doubleClickZoom.disable();
185
+ map.on("click", onEditClick);
186
+ return () => {
187
+ map.off("click", onEditClick);
188
+ map.doubleClickZoom.enable();
189
+ };
190
+ }, [map, editingSiteId, dispatch]);
191
+ const handleRename = async (name) => {
192
+ if (!menu) return;
193
+ dispatch({ type: "UPDATE_SITE_NAME", payload: { id: menu.siteId, name } });
194
+ try {
195
+ await updateSite({ siteName: name });
196
+ } catch (e) {
197
+ toast.error("Failed to rename the site.");
198
+ }
199
+ };
200
+ const handleToggleEdit = async () => {
201
+ if (editingSiteId === activeSiteId && activeSiteId != null) {
202
+ const s = sites.find((x) => x.id === activeSiteId);
203
+ dispatch({ type: "SET_EDITING_SITE", payload: { id: null } });
204
+ if ((s == null ? void 0 : s.assetId) && s.ring.length >= 3) {
205
+ const toastId = toast.loading("Saving site shape\u2026");
206
+ try {
207
+ await uploadGeoJsonToAsset(s.assetId, s.ring, s.name);
208
+ const c = polygonCentroid(s.ring);
209
+ await updateSite({ siteLongitude: c[0], siteLatitude: c[1] });
210
+ toast.success("Site shape saved.", { id: toastId });
211
+ } catch (e) {
212
+ toast.error("Failed to save the site shape.", { id: toastId });
213
+ }
214
+ }
215
+ } else {
216
+ dispatch({ type: "SET_EDITING_SITE", payload: { id: activeSiteId } });
217
+ }
218
+ };
219
+ const handleHide = () => {
220
+ if (!menu) return;
221
+ dispatch({ type: "HIDE_SITE", payload: { id: menu.siteId } });
222
+ setMenu(null);
223
+ };
224
+ const handleInfo = () => {
225
+ if (!menu) return;
226
+ const s = sites.find((x) => x.id === menu.siteId);
227
+ setSelectedSite({ id: menu.siteId, siteName: s == null ? void 0 : s.name });
228
+ setView("detail");
229
+ menusDispatch({ type: "SET_VIEWER", payload: { currentViewer: ViewerNames.sites } });
230
+ setMenu(null);
231
+ };
232
+ const handleDelete = async () => {
233
+ if (!menu) return;
234
+ const id = menu.siteId;
235
+ const s = sites.find((x) => x.id === id);
236
+ try {
237
+ await deleteSite(id);
238
+ if (s == null ? void 0 : s.fileId) {
239
+ try {
240
+ await deleteFile(s.fileId);
241
+ } catch (e) {
242
+ }
243
+ }
244
+ dispatch({ type: "HIDE_SITE", payload: { id } });
245
+ setMenu(null);
246
+ toast.success("Site deleted.");
247
+ } catch (e) {
248
+ toast.error("Failed to delete the site.");
249
+ }
250
+ };
251
+ const editingSite = editingSiteId != null ? (_d = sites.find((s) => s.id === editingSiteId)) != null ? _d : null : null;
252
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
253
+ /* @__PURE__ */ jsxs(Source, { id: "sites-source", type: "geojson", data: featureCollection, children: [
254
+ /* @__PURE__ */ jsx(Layer, __spreadValues({}, fillLayer)),
255
+ /* @__PURE__ */ jsx(Layer, __spreadValues({}, outlineLayer)),
256
+ /* @__PURE__ */ jsx(Layer, __spreadValues({}, labelLayer))
257
+ ] }),
258
+ editingSite == null ? void 0 : editingSite.ring.map((vertex, index) => /* @__PURE__ */ jsx(
259
+ Marker,
260
+ {
261
+ longitude: vertex[0],
262
+ latitude: vertex[1],
263
+ draggable: true,
264
+ onDrag: (e) => updateVertex(index, e.lngLat.lng, e.lngLat.lat),
265
+ children: /* @__PURE__ */ jsx(
266
+ "div",
267
+ {
268
+ title: "Drag to move \xB7 double-click to delete",
269
+ onDoubleClick: (ev) => {
270
+ ev.stopPropagation();
271
+ deleteVertex(index);
272
+ },
273
+ style: {
274
+ width: 14,
275
+ height: 14,
276
+ borderRadius: "50%",
277
+ background: "#22c55e",
278
+ border: "2px solid #ffffff",
279
+ boxShadow: "0 0 0 1px #15803d",
280
+ cursor: "grab"
281
+ }
282
+ }
283
+ )
284
+ },
285
+ `${editingSite.id}-vertex-${index}`
286
+ )),
287
+ menu && menuSite && /* @__PURE__ */ jsx(
288
+ SiteContextMenu,
289
+ {
290
+ x: menu.x,
291
+ y: menu.y,
292
+ site: menuSite,
293
+ isEditing: editingSiteId === menu.siteId,
294
+ canUpdate,
295
+ canDelete,
296
+ canRead,
297
+ onClose: () => setMenu(null),
298
+ onRename: handleRename,
299
+ onToggleEdit: handleToggleEdit,
300
+ onHide: handleHide,
301
+ onInfo: handleInfo,
302
+ onDelete: handleDelete
303
+ }
304
+ )
305
+ ] });
306
+ };
307
+ export {
308
+ SiteLayer
309
+ };
310
+ //# sourceMappingURL=SiteLayer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../../src/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteLayer.tsx"],"sourcesContent":["'use client'\r\n\r\n// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport * as React from 'react'\r\nimport { Source, Layer, Marker } from 'react-map-gl/maplibre'\r\nimport type { LayerProps } from 'react-map-gl/maplibre'\r\nimport type { MapGeoJSONFeature } from 'maplibre-gl'\r\nimport { toast } from 'sonner'\r\n\r\nimport {\r\n MapContext,\r\n usePermissions,\r\n useMenusContext,\r\n useMapSitesContext,\r\n} from '../../../../../../../store'\r\nimport { MapLayerClickPriority, type ClickCallback } from '../../../../utils/MapEventManager/MapClickManager'\r\nimport { ViewerNames, type Site } from '../../../../../../../types/dbTypes'\r\nimport { useSite, useDeleteSite } from '../../../../../../../hooks/sites/sites'\r\nimport { useDeleteFile } from '../../../../../../../hooks/files/files'\r\nimport { polygonCentroid, uploadGeoJsonToAsset, pointToSegmentDistance } from './siteGeometry'\r\nimport { SiteContextMenu } from './SiteContextMenu'\r\n\r\nconst SITE_FILL_ID = 'site-fill'\r\nconst SITE_OUTLINE_ID = 'site-outline'\r\nconst SITE_LABEL_ID = 'site-label'\r\n\r\n// How close (screen px) a click must be to count as \"on a vertex\" / \"on an edge\".\r\nconst VERTEX_HIT_PX = 12\r\nconst EDGE_HIT_PX = 8\r\n// A polygon needs at least 3 corners; deleting below that is blocked.\r\nconst MIN_RING_POINTS = 3\r\n\r\nconst fillLayer: LayerProps = {\r\n id: SITE_FILL_ID,\r\n type: 'fill',\r\n paint: { 'fill-color': '#3b82f6', 'fill-opacity': 0.35 },\r\n filter: ['==', ['get', 'type'], 'site'],\r\n}\r\n\r\nconst outlineLayer: LayerProps = {\r\n id: SITE_OUTLINE_ID,\r\n type: 'line',\r\n layout: { 'line-cap': 'round', 'line-join': 'round' },\r\n paint: { 'line-color': '#1c4587', 'line-width': 2 },\r\n filter: ['==', ['get', 'type'], 'site'],\r\n}\r\n\r\nconst labelLayer: LayerProps = {\r\n id: SITE_LABEL_ID,\r\n type: 'symbol',\r\n layout: {\r\n 'text-field': ['get', 'name'],\r\n 'text-font': ['Inter'],\r\n 'text-size': 16,\r\n 'text-anchor': 'center',\r\n 'text-allow-overlap': false,\r\n },\r\n paint: {\r\n 'text-color': '#ffffff',\r\n 'text-halo-color': '#000000',\r\n 'text-halo-width': 1.5,\r\n },\r\n filter: ['==', ['get', 'type'], 'site-label'],\r\n}\r\n\r\nexport const SiteLayer = () => {\r\n const { state: mapState } = React.useContext(MapContext)\r\n const { map, mapClickManager } = mapState.map\r\n const { state: mapSitesState, dispatch } = useMapSitesContext()\r\n const { sites, editingSiteId } = mapSitesState.mapSites\r\n\r\n const { ability } = usePermissions()\r\n const canUpdate = ability.can('update', 'Site')\r\n const canDelete = ability.can('delete', 'Site')\r\n const canRead = ability.can('read', 'Site')\r\n\r\n const { dispatch: menusDispatch, setSelectedSite, setView } = useMenusContext()\r\n\r\n const [menu, setMenu] = React.useState<{ x: number, y: number, siteId: number } | null>(null)\r\n\r\n // The site the user is currently acting on (menu open) or editing.\r\n const activeSiteId = menu?.siteId ?? editingSiteId ?? null\r\n const { updateSite } = useSite(activeSiteId ? String(activeSiteId) : '')\r\n const { deleteSite } = useDeleteSite(activeSiteId ?? undefined)\r\n const { deleteFile } = useDeleteFile(undefined, activeSiteId ?? undefined)\r\n\r\n const menuSite = menu ? sites.find(s => s.id === menu.siteId) ?? null : null\r\n\r\n // Live mirrors so the once-bound map click handlers read current values.\r\n const sitesRef = React.useRef(sites)\r\n sitesRef.current = sites\r\n const editingSiteIdRef = React.useRef(editingSiteId)\r\n editingSiteIdRef.current = editingSiteId\r\n\r\n // Build the rendered FeatureCollection: a polygon + a single centroid label\r\n // per shown site.\r\n const featureCollection = React.useMemo<GeoJSON.FeatureCollection>(() => {\r\n const features: GeoJSON.Feature[] = []\r\n for (const s of sites) {\r\n if (s.ring.length < 3) continue\r\n features.push({\r\n type: 'Feature',\r\n geometry: { type: 'Polygon', coordinates: [[...s.ring, s.ring[0]]] },\r\n properties: { type: 'site', siteId: s.id, name: s.name },\r\n })\r\n features.push({\r\n type: 'Feature',\r\n geometry: { type: 'Point', coordinates: polygonCentroid(s.ring) },\r\n properties: { type: 'site-label', siteId: s.id, name: s.name },\r\n })\r\n }\r\n return { type: 'FeatureCollection', features }\r\n }, [sites])\r\n\r\n // Lowest-priority click handler: opens the site menu only when no other\r\n // interactive layer was hit at the click point.\r\n React.useEffect(() => {\r\n if (!map || !mapClickManager) return\r\n\r\n const handleSiteClick: ClickCallback = (e, features: MapGeoJSONFeature[]) => {\r\n // While editing a shape, fill clicks are for adding vertices (see the\r\n // edit-click effect), not for (re)opening the menu.\r\n if (editingSiteIdRef.current != null) return\r\n const feature = features?.[0]\r\n const siteId = Number(feature?.properties?.siteId)\r\n if (!siteId || Number.isNaN(siteId)) return\r\n const native = e.originalEvent as MouseEvent | undefined\r\n setMenu({\r\n x: native?.clientX ?? e.point.x,\r\n y: native?.clientY ?? e.point.y,\r\n siteId,\r\n })\r\n }\r\n\r\n const onEnter = () => { map.getCanvas().style.cursor = 'pointer' }\r\n const onLeave = () => { map.getCanvas().style.cursor = '' }\r\n\r\n mapClickManager.register(SITE_FILL_ID, MapLayerClickPriority.SiteLayerClickPriority, handleSiteClick)\r\n map.on('mouseenter', SITE_FILL_ID, onEnter)\r\n map.on('mouseleave', SITE_FILL_ID, onLeave)\r\n\r\n return () => {\r\n mapClickManager.unregister(SITE_FILL_ID)\r\n map.off('mouseenter', SITE_FILL_ID, onEnter)\r\n map.off('mouseleave', SITE_FILL_ID, onLeave)\r\n }\r\n }, [map, mapClickManager])\r\n\r\n const updateVertex = (index: number, lng: number, lat: number) => {\r\n if (editingSiteId == null) return\r\n const s = sites.find(x => x.id === editingSiteId)\r\n if (!s) return\r\n const ring = s.ring.map((p, i) => (i === index ? [lng, lat] as [number, number] : p))\r\n dispatch({ type: 'UPDATE_SITE_RING', payload: { id: editingSiteId, ring } })\r\n }\r\n\r\n // Double-click a vertex to delete it (a polygon keeps at least 3 corners).\r\n const deleteVertex = (index: number) => {\r\n if (editingSiteId == null) return\r\n const s = sites.find(x => x.id === editingSiteId)\r\n if (!s) return\r\n if (s.ring.length <= MIN_RING_POINTS) {\r\n toast.warning('A site needs at least 3 points.')\r\n return\r\n }\r\n const ring = s.ring.filter((_, i) => i !== index)\r\n dispatch({ type: 'UPDATE_SITE_RING', payload: { id: editingSiteId, ring } })\r\n }\r\n\r\n // While editing, a click on an edge (not a vertex) inserts a new vertex there.\r\n React.useEffect(() => {\r\n if (!map || editingSiteId == null) return\r\n\r\n const onEditClick = (e: maplibregl.MapMouseEvent) => {\r\n const site = sitesRef.current.find(s => s.id === editingSiteId)\r\n const ring = site?.ring\r\n if (!ring || ring.length < 2) return\r\n\r\n const click = e.point\r\n // Ignore clicks on/near an existing vertex — those drag (or, on a\r\n // double-click, delete) via the markers.\r\n for (const v of ring) {\r\n const vp = map.project(v as [number, number])\r\n if (Math.hypot(vp.x - click.x, vp.y - click.y) <= VERTEX_HIT_PX) return\r\n }\r\n\r\n // Find the nearest edge segment in screen space.\r\n let bestDist = Infinity\r\n let bestIdx = -1\r\n for (let i = 0; i < ring.length; i++) {\r\n const a = map.project(ring[i] as [number, number])\r\n const b = map.project(ring[(i + 1) % ring.length] as [number, number])\r\n const d = pointToSegmentDistance(click.x, click.y, a.x, a.y, b.x, b.y)\r\n if (d < bestDist) { bestDist = d; bestIdx = i }\r\n }\r\n\r\n if (bestIdx >= 0 && bestDist <= EDGE_HIT_PX) {\r\n const next = [...ring]\r\n next.splice(bestIdx + 1, 0, [e.lngLat.lng, e.lngLat.lat])\r\n dispatch({ type: 'UPDATE_SITE_RING', payload: { id: editingSiteId, ring: next } })\r\n }\r\n }\r\n\r\n // Avoid the map zooming when a vertex is double-clicked to delete it.\r\n map.doubleClickZoom.disable()\r\n map.on('click', onEditClick)\r\n return () => {\r\n map.off('click', onEditClick)\r\n map.doubleClickZoom.enable()\r\n }\r\n }, [map, editingSiteId, dispatch])\r\n\r\n const handleRename = async (name: string) => {\r\n if (!menu) return\r\n dispatch({ type: 'UPDATE_SITE_NAME', payload: { id: menu.siteId, name } })\r\n try {\r\n await updateSite({ siteName: name })\r\n }\r\n catch {\r\n toast.error('Failed to rename the site.')\r\n }\r\n }\r\n\r\n const handleToggleEdit = async () => {\r\n if (editingSiteId === activeSiteId && activeSiteId != null) {\r\n // Finishing an edit: persist the new geometry + centroid.\r\n const s = sites.find(x => x.id === activeSiteId)\r\n dispatch({ type: 'SET_EDITING_SITE', payload: { id: null } })\r\n if (s?.assetId && s.ring.length >= 3) {\r\n const toastId = toast.loading('Saving site shape…')\r\n try {\r\n await uploadGeoJsonToAsset(s.assetId, s.ring, s.name)\r\n const c = polygonCentroid(s.ring)\r\n await updateSite({ siteLongitude: c[0], siteLatitude: c[1] })\r\n toast.success('Site shape saved.', { id: toastId })\r\n }\r\n catch {\r\n toast.error('Failed to save the site shape.', { id: toastId })\r\n }\r\n }\r\n }\r\n else {\r\n // Keep the menu open so the name can be edited alongside the vertices.\r\n dispatch({ type: 'SET_EDITING_SITE', payload: { id: activeSiteId } })\r\n }\r\n }\r\n\r\n const handleHide = () => {\r\n if (!menu) return\r\n dispatch({ type: 'HIDE_SITE', payload: { id: menu.siteId } })\r\n setMenu(null)\r\n }\r\n\r\n const handleInfo = () => {\r\n if (!menu) return\r\n const s = sites.find(x => x.id === menu.siteId)\r\n setSelectedSite({ id: menu.siteId, siteName: s?.name } as Site)\r\n setView('detail')\r\n menusDispatch({ type: 'SET_VIEWER', payload: { currentViewer: ViewerNames.sites } })\r\n setMenu(null)\r\n }\r\n\r\n const handleDelete = async () => {\r\n if (!menu) return\r\n const id = menu.siteId\r\n const s = sites.find(x => x.id === id)\r\n try {\r\n await deleteSite(id)\r\n if (s?.fileId) {\r\n try { await deleteFile(s.fileId) }\r\n catch { /* geometry file orphaned; site itself is gone */ }\r\n }\r\n dispatch({ type: 'HIDE_SITE', payload: { id } })\r\n setMenu(null)\r\n toast.success('Site deleted.')\r\n }\r\n catch {\r\n toast.error('Failed to delete the site.')\r\n }\r\n }\r\n\r\n const editingSite = editingSiteId != null ? sites.find(s => s.id === editingSiteId) ?? null : null\r\n\r\n return (\r\n <>\r\n <Source id=\"sites-source\" type=\"geojson\" data={featureCollection}>\r\n <Layer {...fillLayer} />\r\n <Layer {...outlineLayer} />\r\n <Layer {...labelLayer} />\r\n </Source>\r\n\r\n {/* Draggable vertex handles while editing a site's shape */}\r\n {editingSite?.ring.map((vertex, index) => (\r\n <Marker\r\n key={`${editingSite.id}-vertex-${index}`}\r\n longitude={vertex[0]}\r\n latitude={vertex[1]}\r\n draggable\r\n onDrag={(e: { lngLat: { lng: number, lat: number } }) =>\r\n updateVertex(index, e.lngLat.lng, e.lngLat.lat)}\r\n >\r\n <div\r\n title=\"Drag to move · double-click to delete\"\r\n onDoubleClick={(ev) => { ev.stopPropagation(); deleteVertex(index) }}\r\n style={{\r\n width: 14,\r\n height: 14,\r\n borderRadius: '50%',\r\n background: '#22c55e',\r\n border: '2px solid #ffffff',\r\n boxShadow: '0 0 0 1px #15803d',\r\n cursor: 'grab',\r\n }}\r\n />\r\n </Marker>\r\n ))}\r\n\r\n {menu && menuSite && (\r\n <SiteContextMenu\r\n x={menu.x}\r\n y={menu.y}\r\n site={menuSite}\r\n isEditing={editingSiteId === menu.siteId}\r\n canUpdate={canUpdate}\r\n canDelete={canDelete}\r\n canRead={canRead}\r\n onClose={() => setMenu(null)}\r\n onRename={handleRename}\r\n onToggleEdit={handleToggleEdit}\r\n onHide={handleHide}\r\n onInfo={handleInfo}\r\n onDelete={handleDelete}\r\n />\r\n )}\r\n </>\r\n )\r\n}\r\n"],"mappings":"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","names":["_a","_b","_c"]}
@@ -0,0 +1,30 @@
1
+ export type Ring = [number, number][];
2
+ /** Area-weighted centroid of a polygon ring; used to place a single label. */
3
+ export declare const polygonCentroid: (pts: Ring) => [number, number];
4
+ /** Bounding box of a ring as [[minLng, minLat], [maxLng, maxLat]]. */
5
+ export declare const ringBounds: (ring: Ring) => [[number, number], [number, number]];
6
+ /** Shortest distance from point (px,py) to the segment (ax,ay)-(bx,by). */
7
+ export declare const pointToSegmentDistance: (px: number, py: number, ax: number, ay: number, bx: number, by: number) => number;
8
+ /** Build the GeoJSON FeatureCollection stored in minio for a site polygon. */
9
+ export declare const ringToFeatureCollection: (ring: Ring, name: string) => GeoJSON.FeatureCollection;
10
+ /**
11
+ * Return the items whose [lng, lat] point falls inside the polygon ring.
12
+ * The Turf polygon is built once, so this is cheap for many points.
13
+ */
14
+ export declare const filterPointsInRing: <T>(ring: Ring, items: T[], getLngLat: (item: T) => [number, number] | null) => T[];
15
+ /**
16
+ * Extract the outer ring (open, no repeated closing point) from a stored
17
+ * GeoJSON value, accepting a FeatureCollection, Feature, or bare geometry.
18
+ */
19
+ export declare const ringFromGeoJson: (geo: any) => Ring | null;
20
+ /**
21
+ * Load a site's saved boundary ring from minio (via its attached geometry
22
+ * file). Returns null if the site has no saved boundary or it can't be read.
23
+ */
24
+ export declare const fetchSiteBoundaryRing: (siteId: number) => Promise<Ring | null>;
25
+ /**
26
+ * (Over)write a site's GeoJSON object in minio under the given asset key.
27
+ * Mirrors the presigned-upload flow used by FileAdder/DatasetAdder.
28
+ */
29
+ export declare const uploadGeoJsonToAsset: (assetId: string, ring: Ring, name: string) => Promise<void>;
30
+ //# sourceMappingURL=siteGeometry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"siteGeometry.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/components/viewers/map/src/MapLayers/src/SiteLayer/siteGeometry.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAA;AAErC,8EAA8E;AAC9E,eAAO,MAAM,eAAe,GAAI,KAAK,IAAI,KAAG,CAAC,MAAM,EAAE,MAAM,CAgB1D,CAAA;AAED,sEAAsE;AACtE,eAAO,MAAM,UAAU,GAAI,MAAM,IAAI,KAAG,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAS1E,CAAA;AAED,2EAA2E;AAC3E,eAAO,MAAM,sBAAsB,GACjC,IAAI,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,MAAM,KACrE,MAOF,CAAA;AAED,8EAA8E;AAC9E,eAAO,MAAM,uBAAuB,GAAI,MAAM,IAAI,EAAE,MAAM,MAAM,KAAG,OAAO,CAAC,iBASzE,CAAA;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,EAClC,MAAM,IAAI,EACV,OAAO,CAAC,EAAE,EACV,WAAW,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,KAC9C,CAAC,EAYH,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,GAAG,KAAG,IAAI,GAAG,IA0BjD,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,GAAU,QAAQ,MAAM,KAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAe/E,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAC/B,SAAS,MAAM,EACf,MAAM,IAAI,EACV,MAAM,MAAM,KACX,OAAO,CAAC,IAAI,CASd,CAAA"}