@collabdt/core 0.3.2 → 0.3.3
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/core/components/ui/Comments/CollapsibleCommentItem.js.map +1 -1
- package/dist/core/components/ui/Comments/Comment.js.map +1 -1
- package/dist/core/components/ui/Comments/CommentActionButtons.d.ts +5 -1
- package/dist/core/components/ui/Comments/CommentActionButtons.d.ts.map +1 -1
- package/dist/core/components/ui/Comments/CommentActionButtons.js +37 -9
- package/dist/core/components/ui/Comments/CommentActionButtons.js.map +1 -1
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.d.ts +1 -2
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.js +66 -94
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.js.map +1 -1
- package/dist/core/components/ui/Sensors/Sensor.d.ts +16 -3
- package/dist/core/components/ui/Sensors/Sensor.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/Sensor.js +76 -37
- package/dist/core/components/ui/Sensors/Sensor.js.map +1 -1
- package/dist/core/components/ui/Sensors/SensorChart.d.ts +12 -2
- package/dist/core/components/ui/Sensors/SensorChart.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/SensorChart.js +49 -12
- package/dist/core/components/ui/Sensors/SensorChart.js.map +1 -1
- package/dist/core/components/ui/Sensors/SensorDetailDialog.d.ts +9 -0
- package/dist/core/components/ui/Sensors/SensorDetailDialog.d.ts.map +1 -0
- package/dist/core/components/ui/Sensors/SensorDetailDialog.js +128 -0
- package/dist/core/components/ui/Sensors/SensorDetailDialog.js.map +1 -0
- package/dist/core/components/ui/Sensors/SensorInput.d.ts +6 -1
- package/dist/core/components/ui/Sensors/SensorInput.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/SensorInput.js +94 -8
- package/dist/core/components/ui/Sensors/SensorInput.js.map +1 -1
- package/dist/core/components/ui/Sensors/SensorTagsSection.d.ts +3 -1
- package/dist/core/components/ui/Sensors/SensorTagsSection.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/SensorTagsSection.js +4 -1
- package/dist/core/components/ui/Sensors/SensorTagsSection.js.map +1 -1
- package/dist/core/components/ui/Sensors/SensorsSection.d.ts +1 -3
- package/dist/core/components/ui/Sensors/SensorsSection.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/SensorsSection.js +33 -8
- package/dist/core/components/ui/Sensors/SensorsSection.js.map +1 -1
- package/dist/core/components/ui/Sensors/TimeZoneSelect.d.ts +7 -0
- package/dist/core/components/ui/Sensors/TimeZoneSelect.d.ts.map +1 -0
- package/dist/core/components/ui/Sensors/TimeZoneSelect.js +56 -0
- package/dist/core/components/ui/Sensors/TimeZoneSelect.js.map +1 -0
- package/dist/core/components/ui/Sensors/sensorData.d.ts +42 -0
- package/dist/core/components/ui/Sensors/sensorData.d.ts.map +1 -0
- package/dist/core/components/ui/Sensors/sensorData.js +118 -0
- package/dist/core/components/ui/Sensors/sensorData.js.map +1 -0
- package/dist/core/components/ui/Sensors/sensorRange.d.ts +24 -0
- package/dist/core/components/ui/Sensors/sensorRange.d.ts.map +1 -0
- package/dist/core/components/ui/Sensors/sensorRange.js +34 -0
- package/dist/core/components/ui/Sensors/sensorRange.js.map +1 -0
- package/dist/core/components/ui/Sensors/useSensorSeries.d.ts +15 -0
- package/dist/core/components/ui/Sensors/useSensorSeries.d.ts.map +1 -0
- package/dist/core/components/ui/Sensors/useSensorSeries.js +63 -0
- package/dist/core/components/ui/Sensors/useSensorSeries.js.map +1 -0
- package/dist/core/components/viewers/Viewer.js +1 -1
- package/dist/core/components/viewers/Viewer.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/SensorsTab/index.d.ts +1 -3
- package/dist/core/components/viewers/bim/src/BimSidebar/src/SensorsTab/index.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/SensorsTab/index.js +2 -2
- package/dist/core/components/viewers/bim/src/BimSidebar/src/SensorsTab/index.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/index.js +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/index.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/index.d.ts +1 -2
- package/dist/core/components/viewers/bim/src/tools/AddToBim/index.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/index.js +25 -2
- package/dist/core/components/viewers/bim/src/tools/AddToBim/index.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.d.ts +1 -22
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.js +2 -105
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimSensor.d.ts +8 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimSensor.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimSensor.js +15 -3
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimSensor.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.d.ts +2 -23
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.js +2 -24
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentMarkerConfig.d.ts +33 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentMarkerConfig.d.ts.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentMarkerConfig.js +90 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentMarkerConfig.js.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.d.ts +7 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.js +26 -230
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.d.ts +7 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.js +39 -11
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.js.map +1 -1
- package/dist/core/components/viewers/map/MapViewer.d.ts +1 -2
- package/dist/core/components/viewers/map/MapViewer.d.ts.map +1 -1
- package/dist/core/components/viewers/map/MapViewer.js +2 -2
- package/dist/core/components/viewers/map/MapViewer.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/index.d.ts +1 -2
- package/dist/core/components/viewers/map/src/MapLayers/index.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/index.js +2 -2
- package/dist/core/components/viewers/map/src/MapLayers/index.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.d.ts +1 -3
- package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.js +74 -39
- package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/index.js +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/index.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.d.ts +1 -3
- package/dist/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.js +2 -2
- package/dist/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.js.map +1 -1
- package/dist/core/components/viewers/shared/markers/BimMarkerCluster.d.ts +19 -0
- package/dist/core/components/viewers/shared/markers/BimMarkerCluster.d.ts.map +1 -0
- package/dist/core/components/viewers/shared/markers/BimMarkerCluster.js +108 -0
- package/dist/core/components/viewers/shared/markers/BimMarkerCluster.js.map +1 -0
- package/dist/core/components/viewers/shared/markers/computeMarkerLookAt.d.ts +24 -0
- package/dist/core/components/viewers/shared/markers/computeMarkerLookAt.d.ts.map +1 -0
- package/dist/core/components/viewers/shared/markers/computeMarkerLookAt.js +27 -0
- package/dist/core/components/viewers/shared/markers/computeMarkerLookAt.js.map +1 -0
- package/dist/core/components/viewers/shared/markers/types.d.ts +66 -0
- package/dist/core/components/viewers/shared/markers/types.d.ts.map +1 -0
- package/dist/core/components/viewers/shared/markers/types.js +1 -0
- package/dist/core/components/viewers/shared/markers/types.js.map +1 -0
- package/dist/core/components/viewers/shared/markers/useMarkerLayerBim.d.ts +22 -0
- package/dist/core/components/viewers/shared/markers/useMarkerLayerBim.d.ts.map +1 -0
- package/dist/core/components/viewers/shared/markers/useMarkerLayerBim.js +233 -0
- package/dist/core/components/viewers/shared/markers/useMarkerLayerBim.js.map +1 -0
- package/dist/core/hooks/ui/useResizableSidebarWidth.js.map +1 -1
- package/dist/core/i18n/messages/en.json +5 -1
- package/dist/core/i18n/messages/es.json +5 -1
- package/dist/core/i18n/messages/fr.json +5 -1
- package/dist/core/store/AppConfig/context.d.ts.map +1 -1
- package/dist/core/store/AppConfig/context.js +4 -1
- package/dist/core/store/AppConfig/context.js.map +1 -1
- package/dist/core/store/AppConfig/reducer.d.ts +10 -0
- package/dist/core/store/AppConfig/reducer.d.ts.map +1 -1
- package/dist/core/store/AppConfig/reducer.js +10 -0
- package/dist/core/store/AppConfig/reducer.js.map +1 -1
- package/dist/core/store/Menus/context.d.ts.map +1 -1
- package/dist/core/store/Menus/context.js +5 -2
- package/dist/core/store/Menus/context.js.map +1 -1
- package/dist/core/store/Menus/reducer.d.ts +45 -1
- package/dist/core/store/Menus/reducer.d.ts.map +1 -1
- package/dist/core/store/Menus/reducer.js +21 -3
- package/dist/core/store/Menus/reducer.js.map +1 -1
- package/dist/core/utils/timeUtils.d.ts +24 -1
- package/dist/core/utils/timeUtils.d.ts.map +1 -1
- package/dist/core/utils/timeUtils.js +52 -3
- package/dist/core/utils/timeUtils.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Radio } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { AppConfigContext } from "../../../store";
|
|
6
|
+
import { formatInZone } from "../../../utils/timeUtils";
|
|
7
|
+
import { AddItemDialog } from "../AddItemDialog";
|
|
8
|
+
import { Button } from "../Button";
|
|
9
|
+
import { indicesForBounds, rangeBounds } from "./sensorRange";
|
|
10
|
+
import { SensorChart } from "./SensorChart";
|
|
11
|
+
import { useSensorSeries } from "./useSensorSeries";
|
|
12
|
+
const PRESETS = [
|
|
13
|
+
{ key: "all", label: "All" },
|
|
14
|
+
{ key: "day", label: "Last day" },
|
|
15
|
+
{ key: "hour", label: "Last hour" },
|
|
16
|
+
{ key: "custom", label: "Custom" }
|
|
17
|
+
];
|
|
18
|
+
function MetaRow({ label, children }) {
|
|
19
|
+
if (children == null || children === "") return null;
|
|
20
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex justify-between gap-4 py-1 text-sm", children: [
|
|
21
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
|
|
22
|
+
/* @__PURE__ */ jsx("span", { className: "text-right font-medium break-all", children })
|
|
23
|
+
] });
|
|
24
|
+
}
|
|
25
|
+
function MetaLink({ href }) {
|
|
26
|
+
if (!href) return null;
|
|
27
|
+
return /* @__PURE__ */ jsx("a", { href, target: "_blank", rel: "noopener noreferrer", className: "text-primary underline", children: href });
|
|
28
|
+
}
|
|
29
|
+
function MetadataSection({ meta, timeZone }) {
|
|
30
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
31
|
+
if (!meta || Object.keys(meta).length === 0) {
|
|
32
|
+
return /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "No metadata available." });
|
|
33
|
+
}
|
|
34
|
+
const phenomenon = (_a = meta.phenomenonTime) == null ? void 0 : _a.split("/").map((iso) => formatInZone(Date.parse(iso), timeZone, { dateStyle: "medium", timeStyle: "short" })).join(" \u2192 ");
|
|
35
|
+
return /* @__PURE__ */ jsxs("div", { className: "divide-y", children: [
|
|
36
|
+
/* @__PURE__ */ jsx(MetaRow, { label: "Datastream", children: meta.name }),
|
|
37
|
+
/* @__PURE__ */ jsx(MetaRow, { label: "Description", children: meta.description }),
|
|
38
|
+
/* @__PURE__ */ jsx(MetaRow, { label: "Observation type", children: meta.observationType }),
|
|
39
|
+
/* @__PURE__ */ jsx(MetaRow, { label: "Observed property", children: (_b = meta.observedProperty) == null ? void 0 : _b.name }),
|
|
40
|
+
/* @__PURE__ */ jsx(MetaRow, { label: "Property definition", children: /* @__PURE__ */ jsx(MetaLink, { href: (_c = meta.observedProperty) == null ? void 0 : _c.definition }) }),
|
|
41
|
+
/* @__PURE__ */ jsx(MetaRow, { label: "Unit", children: meta.unit ? `${(_d = meta.unit.name) != null ? _d : ""} ${meta.unit.symbol ? `(${meta.unit.symbol})` : ""}`.trim() : void 0 }),
|
|
42
|
+
/* @__PURE__ */ jsx(MetaRow, { label: "Unit definition", children: /* @__PURE__ */ jsx(MetaLink, { href: (_e = meta.unit) == null ? void 0 : _e.definition }) }),
|
|
43
|
+
/* @__PURE__ */ jsx(MetaRow, { label: "Phenomenon time", children: phenomenon }),
|
|
44
|
+
/* @__PURE__ */ jsx(MetaRow, { label: "Observations", children: meta.observationCount }),
|
|
45
|
+
/* @__PURE__ */ jsx(MetaRow, { label: "Generator", children: (_f = meta.properties) == null ? void 0 : _f.generator }),
|
|
46
|
+
/* @__PURE__ */ jsx(MetaRow, { label: "Seed", children: (_g = meta.properties) == null ? void 0 : _g.seed }),
|
|
47
|
+
/* @__PURE__ */ jsx(MetaRow, { label: "Frequency (ms)", children: (_h = meta.properties) == null ? void 0 : _h.frequency }),
|
|
48
|
+
/* @__PURE__ */ jsx(MetaRow, { label: "Sensor metadata", children: /* @__PURE__ */ jsx(MetaLink, { href: (_i = meta.sensor) == null ? void 0 : _i.metadata }) }),
|
|
49
|
+
/* @__PURE__ */ jsx(MetaRow, { label: "Self link", children: /* @__PURE__ */ jsx(MetaLink, { href: meta.selfLink }) })
|
|
50
|
+
] });
|
|
51
|
+
}
|
|
52
|
+
function SensorDetailDialog({
|
|
53
|
+
open,
|
|
54
|
+
onOpenChange,
|
|
55
|
+
sensor,
|
|
56
|
+
sensorType
|
|
57
|
+
}) {
|
|
58
|
+
var _a, _b;
|
|
59
|
+
const { state } = React.useContext(AppConfigContext);
|
|
60
|
+
const timeZone = state.appConfig.displayTimeZone;
|
|
61
|
+
const { points, unit, valueLabels, meta, isLoading } = useSensorSeries(
|
|
62
|
+
(_a = sensor.url) != null ? _a : "",
|
|
63
|
+
sensor.dataFormat,
|
|
64
|
+
sensor.updateFrequency,
|
|
65
|
+
{ enabled: open }
|
|
66
|
+
);
|
|
67
|
+
const [preset, setPreset] = React.useState("all");
|
|
68
|
+
const [brush, setBrush] = React.useState({});
|
|
69
|
+
React.useEffect(() => {
|
|
70
|
+
if (preset === "custom") return;
|
|
71
|
+
const bounds = rangeBounds(preset, points);
|
|
72
|
+
setBrush(indicesForBounds(points, bounds));
|
|
73
|
+
}, [preset, points]);
|
|
74
|
+
const chartConfig = { value: { label: (_b = sensorType == null ? void 0 : sensorType.name) != null ? _b : "Value", color: "hsl(var(--chart-1))" } };
|
|
75
|
+
return /* @__PURE__ */ jsx(
|
|
76
|
+
AddItemDialog,
|
|
77
|
+
{
|
|
78
|
+
open,
|
|
79
|
+
onOpenChange,
|
|
80
|
+
title: sensor.name,
|
|
81
|
+
icon: Radio,
|
|
82
|
+
contentClassName: "max-w-3xl max-h-[90vh] overflow-y-auto",
|
|
83
|
+
children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
84
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-1", children: PRESETS.map((p) => /* @__PURE__ */ jsx(
|
|
85
|
+
Button,
|
|
86
|
+
{
|
|
87
|
+
type: "button",
|
|
88
|
+
size: "sm",
|
|
89
|
+
variant: preset === p.key ? "default" : "outline",
|
|
90
|
+
onClick: () => setPreset(p.key),
|
|
91
|
+
children: p.label
|
|
92
|
+
},
|
|
93
|
+
p.key
|
|
94
|
+
)) }),
|
|
95
|
+
/* @__PURE__ */ jsx(
|
|
96
|
+
SensorChart,
|
|
97
|
+
{
|
|
98
|
+
sensorData: points,
|
|
99
|
+
isLoadingData: isLoading,
|
|
100
|
+
sensorName: sensor.name,
|
|
101
|
+
sensorType,
|
|
102
|
+
updateFrequency: sensor.updateFrequency,
|
|
103
|
+
chartConfig,
|
|
104
|
+
size: "lg",
|
|
105
|
+
unit,
|
|
106
|
+
valueLabels,
|
|
107
|
+
timeZone,
|
|
108
|
+
showBrush: true,
|
|
109
|
+
brushStartIndex: brush.startIndex,
|
|
110
|
+
brushEndIndex: brush.endIndex,
|
|
111
|
+
onBrushChange: (range) => {
|
|
112
|
+
setPreset("custom");
|
|
113
|
+
setBrush(range);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
),
|
|
117
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
118
|
+
/* @__PURE__ */ jsx("h4", { className: "mb-2 text-sm font-semibold", children: "Metadata" }),
|
|
119
|
+
/* @__PURE__ */ jsx(MetadataSection, { meta, timeZone })
|
|
120
|
+
] })
|
|
121
|
+
] })
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
export {
|
|
126
|
+
SensorDetailDialog
|
|
127
|
+
};
|
|
128
|
+
//# sourceMappingURL=SensorDetailDialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../src/core/components/ui/Sensors/SensorDetailDialog.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 { Radio } from 'lucide-react'\r\nimport * as React from 'react'\r\n\r\nimport { AppConfigContext } from '../../../store'\r\nimport { formatInZone } from '../../../utils/timeUtils'\r\nimport { AddItemDialog } from '../AddItemDialog'\r\nimport { Button } from '../Button'\r\n\r\nimport { indicesForBounds, rangeBounds, type RangePreset } from './sensorRange'\r\nimport { SensorChart } from './SensorChart'\r\n// TimeZoneSelect is intentionally not rendered for now (kept as a standalone component).\r\nimport { useSensorSeries } from './useSensorSeries'\r\n\r\nimport type { SensorSeriesMeta } from './sensorData'\r\nimport type { Sensor, SensorType } from '../../../types/dbTypes'\r\nimport type { ChartConfig } from '../chart'\r\n\r\nconst PRESETS: { key: RangePreset; label: string }[] = [\r\n { key: 'all', label: 'All' },\r\n { key: 'day', label: 'Last day' },\r\n { key: 'hour', label: 'Last hour' },\r\n { key: 'custom', label: 'Custom' },\r\n]\r\n\r\nfunction MetaRow({ label, children }: { label: string; children: React.ReactNode }) {\r\n if (children == null || children === '') return null\r\n return (\r\n <div className=\"flex justify-between gap-4 py-1 text-sm\">\r\n <span className=\"text-muted-foreground\">{label}</span>\r\n <span className=\"text-right font-medium break-all\">{children}</span>\r\n </div>\r\n )\r\n}\r\n\r\nfunction MetaLink({ href }: { href?: string }) {\r\n if (!href) return null\r\n return (\r\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-primary underline\">\r\n {href}\r\n </a>\r\n )\r\n}\r\n\r\nfunction MetadataSection({ meta, timeZone }: { meta?: SensorSeriesMeta; timeZone: string }) {\r\n if (!meta || Object.keys(meta).length === 0) {\r\n return <p className=\"text-sm text-muted-foreground\">No metadata available.</p>\r\n }\r\n const phenomenon = meta.phenomenonTime\r\n ?.split('/')\r\n .map(iso => formatInZone(Date.parse(iso), timeZone, { dateStyle: 'medium', timeStyle: 'short' }))\r\n .join(' → ')\r\n return (\r\n <div className=\"divide-y\">\r\n <MetaRow label=\"Datastream\">{meta.name}</MetaRow>\r\n <MetaRow label=\"Description\">{meta.description}</MetaRow>\r\n <MetaRow label=\"Observation type\">{meta.observationType}</MetaRow>\r\n <MetaRow label=\"Observed property\">{meta.observedProperty?.name}</MetaRow>\r\n <MetaRow label=\"Property definition\"><MetaLink href={meta.observedProperty?.definition} /></MetaRow>\r\n <MetaRow label=\"Unit\">{meta.unit ? `${meta.unit.name ?? ''} ${meta.unit.symbol ? `(${meta.unit.symbol})` : ''}`.trim() : undefined}</MetaRow>\r\n <MetaRow label=\"Unit definition\"><MetaLink href={meta.unit?.definition} /></MetaRow>\r\n <MetaRow label=\"Phenomenon time\">{phenomenon}</MetaRow>\r\n <MetaRow label=\"Observations\">{meta.observationCount}</MetaRow>\r\n <MetaRow label=\"Generator\">{meta.properties?.generator as React.ReactNode}</MetaRow>\r\n <MetaRow label=\"Seed\">{meta.properties?.seed as React.ReactNode}</MetaRow>\r\n <MetaRow label=\"Frequency (ms)\">{meta.properties?.frequency as React.ReactNode}</MetaRow>\r\n <MetaRow label=\"Sensor metadata\"><MetaLink href={meta.sensor?.metadata} /></MetaRow>\r\n <MetaRow label=\"Self link\"><MetaLink href={meta.selfLink} /></MetaRow>\r\n </div>\r\n )\r\n}\r\n\r\nexport function SensorDetailDialog({\r\n open,\r\n onOpenChange,\r\n sensor,\r\n sensorType,\r\n}: {\r\n open: boolean\r\n onOpenChange: (open: boolean) => void\r\n sensor: Sensor\r\n sensorType?: SensorType\r\n}): React.ReactElement {\r\n const { state } = React.useContext(AppConfigContext)\r\n const timeZone = state.appConfig.displayTimeZone\r\n\r\n const { points, unit, valueLabels, meta, isLoading } = useSensorSeries(\r\n sensor.url ?? '',\r\n sensor.dataFormat,\r\n sensor.updateFrequency,\r\n { enabled: open },\r\n )\r\n\r\n const [preset, setPreset] = React.useState<RangePreset>('all')\r\n const [brush, setBrush] = React.useState<{ startIndex?: number; endIndex?: number }>({})\r\n\r\n // When a preset is picked (not custom), derive brush indices from its bounds.\r\n React.useEffect(() => {\r\n if (preset === 'custom') return\r\n const bounds = rangeBounds(preset, points)\r\n setBrush(indicesForBounds(points, bounds))\r\n }, [preset, points])\r\n\r\n const chartConfig: ChartConfig = { value: { label: sensorType?.name ?? 'Value', color: 'hsl(var(--chart-1))' } }\r\n\r\n return (\r\n <AddItemDialog\r\n open={open}\r\n onOpenChange={onOpenChange}\r\n title={sensor.name}\r\n icon={Radio}\r\n contentClassName=\"max-w-3xl max-h-[90vh] overflow-y-auto\"\r\n >\r\n <div className=\"space-y-4\">\r\n <div className=\"flex flex-wrap items-center gap-1\">\r\n {PRESETS.map(p => (\r\n <Button\r\n key={p.key}\r\n type=\"button\"\r\n size=\"sm\"\r\n variant={preset === p.key ? 'default' : 'outline'}\r\n onClick={() => setPreset(p.key)}\r\n >\r\n {p.label}\r\n </Button>\r\n ))}\r\n </div>\r\n\r\n <SensorChart\r\n sensorData={points}\r\n isLoadingData={isLoading}\r\n sensorName={sensor.name}\r\n sensorType={sensorType as SensorType}\r\n updateFrequency={sensor.updateFrequency}\r\n chartConfig={chartConfig}\r\n size=\"lg\"\r\n unit={unit}\r\n valueLabels={valueLabels}\r\n timeZone={timeZone}\r\n showBrush\r\n brushStartIndex={brush.startIndex}\r\n brushEndIndex={brush.endIndex}\r\n onBrushChange={range => { setPreset('custom'); setBrush(range) }}\r\n />\r\n\r\n <div>\r\n <h4 className=\"mb-2 text-sm font-semibold\">Metadata</h4>\r\n <MetadataSection meta={meta} timeZone={timeZone} />\r\n </div>\r\n </div>\r\n </AddItemDialog>\r\n )\r\n}\r\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { ViewerNames } from '../../../types/dbTypes';
|
|
3
|
+
import type { Sensor } from '../../../types/dbTypes';
|
|
3
4
|
export type SensorInputLayout = 'inline' | 'dialog';
|
|
4
5
|
export type BimSensorPlacementContext = {
|
|
5
6
|
world: any;
|
|
@@ -23,6 +24,10 @@ export type SensorInputProps = {
|
|
|
23
24
|
onCancel?: () => void;
|
|
24
25
|
bim?: BimSensorPlacementContext;
|
|
25
26
|
isOpen?: boolean;
|
|
27
|
+
/** When provided, the dialog becomes a single-sensor EDIT form (no multi-add, no placement). */
|
|
28
|
+
editSensor?: Sensor;
|
|
29
|
+
/** Called after a successful edit save (in addition to onCancel). */
|
|
30
|
+
onSaved?: () => void;
|
|
26
31
|
};
|
|
27
|
-
export declare function SensorInput({ viewer, layout, onCancel, bim, isOpen, }: SensorInputProps): React.JSX.Element;
|
|
32
|
+
export declare function SensorInput({ viewer, layout, onCancel, bim, isOpen, editSensor, onSaved, }: SensorInputProps): React.JSX.Element;
|
|
28
33
|
//# sourceMappingURL=SensorInput.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SensorInput.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/ui/Sensors/SensorInput.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,OAAO,EAAE,WAAW,EAAoB,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"SensorInput.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/ui/Sensors/SensorInput.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,OAAO,EAAE,WAAW,EAAoB,MAAM,wBAAwB,CAAA;AAoBtE,OAAO,KAAK,EAAE,MAAM,EAAC,MAAM,wBAAwB,CAAC;AAIpD,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAEnD,MAAM,MAAM,yBAAyB,GAAG;IACtC,KAAK,EAAE,GAAG,CAAA;IACV,OAAO,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,GAAG,CAAC;QAAC,KAAK,EAAE,GAAG,CAAC;QAAC,GAAG,EAAE,GAAG,CAAA;KAAE,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;IACtE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IACpC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAA;IAC5E,mBAAmB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CAC3C,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,WAAW,CAAA;IACnB,MAAM,CAAC,EAAE,iBAAiB,CAAA;IAC1B,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,GAAG,CAAC,EAAE,yBAAyB,CAAA;IAC/B,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,gGAAgG;IAChG,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB,CAAA;AAwCD,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,MAAiB,EACjB,QAAQ,EACR,GAAG,EACH,MAAc,EACd,UAAU,EACV,OAAO,GACR,EAAE,gBAAgB,qBA2hBlB"}
|
|
@@ -23,7 +23,7 @@ import { Radio } from "lucide-react";
|
|
|
23
23
|
import { useSession } from "next-auth/react";
|
|
24
24
|
import { useTranslations } from "next-intl";
|
|
25
25
|
import * as React from "react";
|
|
26
|
-
import { useCreateSensor } from "../../../hooks/sensors/sensors";
|
|
26
|
+
import { useCreateSensor, useSensor } from "../../../hooks/sensors/sensors";
|
|
27
27
|
import { useSensorTypes } from "../../../hooks/sensorTypes/sensorTypes";
|
|
28
28
|
import { AppConfigContext, MapContext, BuildingsContext, MenusContext, ToolsContext } from "../../../store";
|
|
29
29
|
import { ViewerNames, SensorDataFormat } from "../../../types/dbTypes";
|
|
@@ -42,16 +42,42 @@ import {
|
|
|
42
42
|
} from "../Select";
|
|
43
43
|
import { Separator } from "../Separator";
|
|
44
44
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../Tooltip";
|
|
45
|
+
import { parseSensorSeries } from "./sensorData";
|
|
45
46
|
import { SensorTagsSection } from "./SensorTagsSection";
|
|
47
|
+
function splitUpdateFrequencyMs(updateFrequencyMs) {
|
|
48
|
+
const sortedDesc = [...frequencyUnits].sort((a, b) => b.value - a.value);
|
|
49
|
+
for (const unit of sortedDesc) {
|
|
50
|
+
if (unit.value > 0 && updateFrequencyMs % unit.value === 0) {
|
|
51
|
+
return { updateFrequency: updateFrequencyMs / unit.value, updateFrequencyUnit: unit.value };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return { updateFrequency: updateFrequencyMs, updateFrequencyUnit: 1 };
|
|
55
|
+
}
|
|
56
|
+
function sensorFormFromEditSensor(editSensor) {
|
|
57
|
+
var _a, _b, _c, _d;
|
|
58
|
+
const { updateFrequency, updateFrequencyUnit } = splitUpdateFrequencyMs(editSensor.updateFrequency);
|
|
59
|
+
return {
|
|
60
|
+
typeId: (_a = editSensor.typeId) != null ? _a : -1,
|
|
61
|
+
name: editSensor.name,
|
|
62
|
+
data: (_c = (_b = editSensor.url) != null ? _b : editSensor.data) != null ? _c : "",
|
|
63
|
+
dataFormat: editSensor.dataFormat,
|
|
64
|
+
updateFrequency,
|
|
65
|
+
updateFrequencyUnit,
|
|
66
|
+
tags: (_d = editSensor.tags) != null ? _d : []
|
|
67
|
+
};
|
|
68
|
+
}
|
|
46
69
|
function SensorInput({
|
|
47
70
|
viewer,
|
|
48
71
|
layout = "dialog",
|
|
49
72
|
onCancel,
|
|
50
73
|
bim,
|
|
51
|
-
isOpen = false
|
|
74
|
+
isOpen = false,
|
|
75
|
+
editSensor,
|
|
76
|
+
onSaved
|
|
52
77
|
}) {
|
|
53
|
-
var _a, _b, _c, _d, _e;
|
|
78
|
+
var _a, _b, _c, _d, _e, _f;
|
|
54
79
|
const t = useTranslations("SensorInput");
|
|
80
|
+
const tf = React.useCallback((key, fallback) => t.has(key) ? t(key) : fallback, [t]);
|
|
55
81
|
const session = useSession();
|
|
56
82
|
const user = (_a = session.data) == null ? void 0 : _a.user;
|
|
57
83
|
const organizationId = user == null ? void 0 : user.organizationId;
|
|
@@ -64,7 +90,7 @@ function SensorInput({
|
|
|
64
90
|
const { state: menusState, dispatch: menusDispatch } = React.useContext(MenusContext);
|
|
65
91
|
const { currentSensorTypeId } = menusState.menus;
|
|
66
92
|
const [sensors, setSensors] = React.useState([
|
|
67
|
-
{
|
|
93
|
+
editSensor ? sensorFormFromEditSensor(editSensor) : {
|
|
68
94
|
typeId: currentSensorTypeId != null ? currentSensorTypeId : -1,
|
|
69
95
|
name: "",
|
|
70
96
|
data: "",
|
|
@@ -76,10 +102,38 @@ function SensorInput({
|
|
|
76
102
|
]);
|
|
77
103
|
const [currentSensorIndex, setCurrentSensorIndex] = React.useState(0);
|
|
78
104
|
const [isPlacing, setIsPlacing] = React.useState(false);
|
|
105
|
+
const [preview, setPreview] = React.useState({});
|
|
106
|
+
const checkDataUrl = React.useCallback(async (index, url, dataFormat) => {
|
|
107
|
+
if (!url.trim()) {
|
|
108
|
+
setPreview((prev) => {
|
|
109
|
+
const next = __spreadValues({}, prev);
|
|
110
|
+
delete next[index];
|
|
111
|
+
return next;
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const res = await fetch(url);
|
|
117
|
+
if (!res.ok) {
|
|
118
|
+
setPreview((prev) => __spreadProps(__spreadValues({}, prev), { [index]: { ok: false, text: t("previewError") } }));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const raw = await res.text();
|
|
122
|
+
const { points, unit } = parseSensorSeries(raw, dataFormat);
|
|
123
|
+
if (points.length === 0) {
|
|
124
|
+
setPreview((prev) => __spreadProps(__spreadValues({}, prev), { [index]: { ok: false, text: t("previewNoData") } }));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const unitText = unit ? ` \xB7 ${unit}` : "";
|
|
128
|
+
setPreview((prev) => __spreadProps(__spreadValues({}, prev), { [index]: { ok: true, text: `${points.length} ${t("previewPoints")}${unitText}` } }));
|
|
129
|
+
} catch (e) {
|
|
130
|
+
setPreview((prev) => __spreadProps(__spreadValues({}, prev), { [index]: { ok: false, text: t("previewError") } }));
|
|
131
|
+
}
|
|
132
|
+
}, [t]);
|
|
79
133
|
React.useEffect(() => {
|
|
80
134
|
if (!isOpen) return;
|
|
81
135
|
setSensors([
|
|
82
|
-
{
|
|
136
|
+
editSensor ? sensorFormFromEditSensor(editSensor) : {
|
|
83
137
|
typeId: currentSensorTypeId != null ? currentSensorTypeId : -1,
|
|
84
138
|
name: "",
|
|
85
139
|
data: "",
|
|
@@ -90,8 +144,10 @@ function SensorInput({
|
|
|
90
144
|
}
|
|
91
145
|
]);
|
|
92
146
|
setCurrentSensorIndex(0);
|
|
93
|
-
|
|
147
|
+
setPreview({});
|
|
148
|
+
}, [isOpen, editSensor]);
|
|
94
149
|
const { createSensor } = useCreateSensor();
|
|
150
|
+
const { updateSensor: updateSensorMutation } = useSensor((_f = editSensor == null ? void 0 : editSensor.id) != null ? _f : null);
|
|
95
151
|
const setCursor = React.useCallback(
|
|
96
152
|
(cursor) => {
|
|
97
153
|
var _a2, _b2, _c2;
|
|
@@ -122,6 +178,7 @@ function SensorInput({
|
|
|
122
178
|
}
|
|
123
179
|
]);
|
|
124
180
|
setCurrentSensorIndex(0);
|
|
181
|
+
setPreview({});
|
|
125
182
|
setCursor("");
|
|
126
183
|
menusDispatch({ type: "SET_CURRENT_SENSOR_TYPE_ID", payload: { currentSensorTypeId: null } });
|
|
127
184
|
}, [setCursor, currentSensorTypeId]);
|
|
@@ -140,8 +197,25 @@ function SensorInput({
|
|
|
140
197
|
reset();
|
|
141
198
|
onCancel == null ? void 0 : onCancel();
|
|
142
199
|
};
|
|
200
|
+
const onSaveEdit = async () => {
|
|
201
|
+
if (!editSensor) return;
|
|
202
|
+
const form = sensors[0];
|
|
203
|
+
const partial = {
|
|
204
|
+
typeId: form.typeId,
|
|
205
|
+
name: form.name,
|
|
206
|
+
data: form.data,
|
|
207
|
+
dataFormat: form.dataFormat,
|
|
208
|
+
url: form.data,
|
|
209
|
+
updateFrequency: form.updateFrequency * form.updateFrequencyUnit,
|
|
210
|
+
tags: form.tags
|
|
211
|
+
};
|
|
212
|
+
await updateSensorMutation(partial);
|
|
213
|
+
onSaved == null ? void 0 : onSaved();
|
|
214
|
+
onCancel == null ? void 0 : onCancel();
|
|
215
|
+
};
|
|
143
216
|
React.useEffect(() => {
|
|
144
217
|
var _a2, _b2;
|
|
218
|
+
if (editSensor) return;
|
|
145
219
|
if (!isPlacing) return;
|
|
146
220
|
if (viewer === ViewerNames.map) {
|
|
147
221
|
const map = (_b2 = (_a2 = mapContext == null ? void 0 : mapContext.state) == null ? void 0 : _a2.map) == null ? void 0 : _b2.map;
|
|
@@ -252,6 +326,7 @@ function SensorInput({
|
|
|
252
326
|
}, [
|
|
253
327
|
appConfigState,
|
|
254
328
|
bim,
|
|
329
|
+
editSensor,
|
|
255
330
|
isPlacing,
|
|
256
331
|
mapContext,
|
|
257
332
|
viewer,
|
|
@@ -349,9 +424,11 @@ function SensorInput({
|
|
|
349
424
|
type: "text",
|
|
350
425
|
value: sensor.data,
|
|
351
426
|
onChange: (e) => updateSensor(index, "data", e.target.value),
|
|
427
|
+
onBlur: (e) => checkDataUrl(index, e.target.value, sensor.dataFormat),
|
|
352
428
|
placeholder: t("dataUrlPlaceholder")
|
|
353
429
|
}
|
|
354
|
-
)
|
|
430
|
+
),
|
|
431
|
+
preview[index] && /* @__PURE__ */ jsx("p", { className: cn("text-xs", preview[index].ok ? "text-muted-foreground" : "text-destructive"), children: preview[index].text })
|
|
355
432
|
] }),
|
|
356
433
|
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
357
434
|
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
@@ -409,7 +486,16 @@ function SensorInput({
|
|
|
409
486
|
}
|
|
410
487
|
)
|
|
411
488
|
] }),
|
|
412
|
-
|
|
489
|
+
editSensor ? /* @__PURE__ */ jsx(
|
|
490
|
+
Button,
|
|
491
|
+
{
|
|
492
|
+
type: "button",
|
|
493
|
+
onClick: onSaveEdit,
|
|
494
|
+
disabled: !sensor.name.trim() || !sensor.data.trim(),
|
|
495
|
+
className: "w-full",
|
|
496
|
+
children: tf("save", "Save")
|
|
497
|
+
}
|
|
498
|
+
) : /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
413
499
|
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
414
500
|
Button,
|
|
415
501
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/core/components/ui/Sensors/SensorInput.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 { Radio } from 'lucide-react'\r\nimport { useSession } from 'next-auth/react'\r\nimport { useTranslations } from 'next-intl'\r\nimport * as React from 'react'\r\n\r\nimport { useCreateSensor } from '../../../hooks/sensors/sensors'\r\nimport { useSensorTypes } from '../../../hooks/sensorTypes/sensorTypes'\r\nimport { AppConfigContext, MapContext, BuildingsContext, MenusContext, ToolsContext } from '../../../store'\r\nimport { ViewerNames, SensorDataFormat } from '../../../types/dbTypes'\r\nimport { frequencyUnits } from '../../../utils/timeUtils'\r\nimport { cn } from '../../../utils/utils'\r\nimport { AddItemDialog } from '../AddItemDialog'\r\nimport { Button } from '../Button'\r\nimport { Input } from '../Input'\r\nimport { Label } from '../Label'\r\nimport {\r\n Select,\r\n SelectContent,\r\n SelectItem,\r\n SelectTrigger,\r\n SelectValue,\r\n} from '../Select'\r\nimport { Separator } from '../Separator'\r\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../Tooltip'\r\n\r\nimport { SensorTagsSection } from './SensorTagsSection'\r\n\r\nimport type { Sensor} from '../../../types/dbTypes';\r\n\r\n\r\n\r\nexport type SensorInputLayout = 'inline' | 'dialog'\r\n\r\nexport type BimSensorPlacementContext = {\r\n world: any\r\n raycast: (data: { camera: any; mouse: any; dom: any }) => Promise<any>\r\n buildingId?: number\r\n setCursor?: (cursor: string) => void\r\n addPendingMarker?: (position: { x: number; y: number; z: number }) => number\r\n removePendingMarker?: (id: number) => void\r\n}\r\n\r\nexport type SensorInputProps = {\r\n viewer: ViewerNames\r\n layout?: SensorInputLayout\r\n onCancel?: () => void\r\n bim?: BimSensorPlacementContext\r\n isOpen?: boolean\r\n}\r\n\r\ntype SensorFormData = {\r\n typeId: number\r\n name: string\r\n data: string\r\n dataFormat: string\r\n updateFrequency: number\r\n updateFrequencyUnit: number\r\n tags: string[]\r\n}\r\n\r\nexport function SensorInput({\r\n viewer,\r\n layout = 'dialog',\r\n onCancel,\r\n bim,\r\n isOpen = false,\r\n}: SensorInputProps) {\r\n const t = useTranslations('SensorInput')\r\n const session = useSession()\r\n\r\n const user = session.data?.user\r\n const organizationId = user?.organizationId\r\n const authorId = session.data?.user?.id || -1\r\n\r\n const { state: appConfigState } = React.useContext(AppConfigContext)\r\n const mapContext = React.useContext(MapContext)\r\n\r\n const { state: buildingsState } = React.useContext(BuildingsContext)\r\n const buildingId = buildingsState?.buildings?.building?.id || -1\r\n\r\n const { state: toolsState } = React.useContext(ToolsContext)\r\n\r\n const { state: menusState, dispatch: menusDispatch } = React.useContext(MenusContext)\r\n const { currentSensorTypeId } = menusState.menus\r\n\r\n const [sensors, setSensors] = React.useState<SensorFormData[]>([\r\n {\r\n typeId: currentSensorTypeId ?? -1,\r\n name: '',\r\n data: '',\r\n dataFormat: SensorDataFormat.Json,\r\n updateFrequency: 1,\r\n updateFrequencyUnit: 1000,\r\n tags: [],\r\n },\r\n ])\r\n const [currentSensorIndex, setCurrentSensorIndex] = React.useState(0)\r\n const [isPlacing, setIsPlacing] = React.useState(false)\r\n\r\n // Re-initialize form with pre-selected type each time the dialog opens\r\n React.useEffect(() => {\r\n if (!isOpen) return\r\n setSensors([\r\n {\r\n typeId: currentSensorTypeId ?? -1,\r\n name: '',\r\n data: '',\r\n dataFormat: SensorDataFormat.Json,\r\n updateFrequency: 1,\r\n updateFrequencyUnit: 1000,\r\n tags: [],\r\n },\r\n ])\r\n setCurrentSensorIndex(0)\r\n }, [isOpen])\r\n\r\n const { createSensor } = useCreateSensor()\r\n\r\n const setCursor = React.useCallback(\r\n (cursor: string) => {\r\n if (viewer === ViewerNames.map) {\r\n const map = mapContext?.state?.map?.map\r\n if (!map) return\r\n if (map.getCanvas().style.cursor === cursor) return\r\n map.getCanvas().style.cursor = cursor\r\n return\r\n }\r\n\r\n if (viewer === ViewerNames.bim) {\r\n bim?.setCursor?.(cursor)\r\n }\r\n },\r\n [bim, mapContext, viewer]\r\n )\r\n\r\n const reset = React.useCallback(() => {\r\n setIsPlacing(false)\r\n setSensors([\r\n {\r\n typeId: currentSensorTypeId ?? -1,\r\n name: '',\r\n data: '',\r\n dataFormat: SensorDataFormat.Json,\r\n updateFrequency: 1,\r\n updateFrequencyUnit: 1000,\r\n tags: [],\r\n },\r\n ])\r\n setCurrentSensorIndex(0)\r\n setCursor('')\r\n menusDispatch({type: 'SET_CURRENT_SENSOR_TYPE_ID', payload: { currentSensorTypeId: null }})\r\n }, [setCursor, currentSensorTypeId])\r\n\r\n const onCreateSensor = async (sensorData: Partial<Sensor>) => {\r\n await createSensor({ sensorData })\r\n\r\n // Ensure the sensor type is visible after creation\r\n if (sensorData.typeId) {\r\n menusDispatch({\r\n type: 'TOGGLE_SENSOR_TYPE_VISIBILITY',\r\n payload: {\r\n viewer: viewer,\r\n sensorTypeId: sensorData.typeId,\r\n force: true,\r\n },\r\n })\r\n }\r\n\r\n reset()\r\n onCancel?.()\r\n }\r\n\r\n React.useEffect(() => {\r\n if (!isPlacing) return\r\n\r\n if (viewer === ViewerNames.map) {\r\n const map = mapContext?.state?.map?.map\r\n if (!map) return\r\n\r\n const mouseMoveCrosshairHandler = () => {\r\n setCursor('crosshair')\r\n }\r\n\r\n const dbClickAddSensorHandler = (e: any): void => {\r\n const longitude = e?.lngLat?.lng\r\n const latitude = e?.lngLat?.lat\r\n if (typeof longitude !== 'number' || typeof latitude !== 'number') return\r\n\r\n const elevation = map.queryTerrainElevation([latitude, longitude]) || 0\r\n\r\n const currentSensor = sensors[currentSensorIndex]\r\n const updateFrequencyMs = currentSensor.updateFrequency * currentSensor.updateFrequencyUnit\r\n\r\n const newSensor: any = {\r\n authorId: Number(user?.id),\r\n organizationId,\r\n typeId: currentSensor.typeId,\r\n name: currentSensor.name,\r\n data: currentSensor.data,\r\n dataFormat: currentSensor.dataFormat,\r\n updateFrequency: updateFrequencyMs,\r\n url: currentSensor.data,\r\n visible: true,\r\n longitude,\r\n latitude,\r\n elevation,\r\n viewer: ViewerNames.map,\r\n tags: currentSensor.tags,\r\n }\r\n onCreateSensor(newSensor as Partial<Sensor>)\r\n }\r\n\r\n map.on('mousemove', mouseMoveCrosshairHandler)\r\n map.on('dblclick', dbClickAddSensorHandler)\r\n\r\n return () => {\r\n setCursor('')\r\n map.off('mousemove', mouseMoveCrosshairHandler)\r\n map.off('dblclick', dbClickAddSensorHandler)\r\n }\r\n }\r\n\r\n if (viewer === ViewerNames.bim) {\r\n const world = bim?.world\r\n const raycast = bim?.raycast\r\n if (!world || !raycast) return\r\n\r\n let disposed = false\r\n let removeListeners: (() => void) | null = null\r\n\r\n ;(async () => {\r\n const THREE = await import('three')\r\n if (disposed) return\r\n\r\n const mouse = new THREE.Vector2()\r\n\r\n const handleMouseMove = (e: MouseEvent) => {\r\n mouse.x = e.clientX\r\n mouse.y = e.clientY\r\n }\r\n\r\n const handleDblClick = async (e: MouseEvent) => {\r\n mouse.x = e.clientX\r\n mouse.y = e.clientY\r\n\r\n const result = await raycast({\r\n camera: world.camera?.three,\r\n mouse,\r\n dom: world.renderer?.three?.domElement,\r\n })\r\n\r\n if (!result?.point) return\r\n const p = result.point\r\n\r\n const currentSensor = sensors[currentSensorIndex]\r\n const updateFrequencyMs = currentSensor.updateFrequency * currentSensor.updateFrequencyUnit\r\n\r\n // Add pending marker immediately\r\n const pendingId = bim?.addPendingMarker?.({ x: p.x, y: p.y, z: p.z })\r\n\r\n const newSensor: any = {\r\n authorId: Number(authorId),\r\n organizationId,\r\n typeId: currentSensor.typeId,\r\n name: currentSensor.name,\r\n data: currentSensor.data,\r\n dataFormat: currentSensor.dataFormat,\r\n updateFrequency: updateFrequencyMs,\r\n url: currentSensor.data,\r\n x: p.x,\r\n y: p.y,\r\n z: p.z,\r\n visible: true,\r\n buildingId,\r\n viewer,\r\n tags: currentSensor.tags,\r\n }\r\n\r\n try {\r\n await onCreateSensor(newSensor as Partial<Sensor>)\r\n } finally {\r\n // Remove pending marker after creation completes (or fails)\r\n if (pendingId) {\r\n bim?.removePendingMarker?.(pendingId)\r\n }\r\n }\r\n }\r\n\r\n document.addEventListener('mousemove', handleMouseMove)\r\n document.addEventListener('dblclick', handleDblClick)\r\n\r\n removeListeners = () => {\r\n document.removeEventListener('mousemove', handleMouseMove)\r\n document.removeEventListener('dblclick', handleDblClick)\r\n }\r\n })()\r\n\r\n return () => {\r\n disposed = true\r\n setCursor('')\r\n removeListeners?.()\r\n }\r\n }\r\n }, [\r\n appConfigState,\r\n bim,\r\n isPlacing,\r\n mapContext,\r\n viewer,\r\n onCancel,\r\n reset,\r\n setCursor,\r\n sensors,\r\n currentSensorIndex,\r\n authorId,\r\n organizationId,\r\n buildingId,\r\n user?.id,\r\n ])\r\n\r\n const updateSensor = (index: number, field: keyof SensorFormData, value: any) => {\r\n setSensors(prev =>\r\n prev.map((sensor, i) => (i === index ? { ...sensor, [field]: value } : sensor))\r\n )\r\n }\r\n\r\n const addAnotherSensor = () => {\r\n setSensors(prev => [\r\n ...prev,\r\n {\r\n typeId: -1,\r\n name: '',\r\n data: '',\r\n dataFormat: SensorDataFormat.Json,\r\n updateFrequency: 1,\r\n updateFrequencyUnit: 1000,\r\n tags: [],\r\n },\r\n ])\r\n }\r\n\r\n const addTag = (index: number, tag: string) => {\r\n setSensors(prev =>\r\n prev.map((sensor, i) =>\r\n i === index ? { ...sensor, tags: [...sensor.tags, tag] } : sensor\r\n )\r\n )\r\n }\r\n\r\n const removeTag = (index: number, tag: string) => {\r\n setSensors(prev =>\r\n prev.map((sensor, i) =>\r\n i === index ? { ...sensor, tags: sensor.tags.filter(t => t !== tag) } : sensor\r\n )\r\n )\r\n }\r\n\r\n const onPlaceSensor = (index: number) => {\r\n const sensor = sensors[index]\r\n if (!sensor.name.trim() || !sensor.data.trim()) {\r\n return\r\n }\r\n setCurrentSensorIndex(index)\r\n setIsPlacing(true)\r\n setCursor('crosshair')\r\n }\r\n\r\n const { sensorTypes } = useSensorTypes()\r\n\r\n const placeSensorTooltip = viewer === ViewerNames.map\r\n ? t('placeSensorTooltipMap')\r\n : t('placeSensorTooltipBim')\r\n\r\n const sensorForm = (\r\n <div className=\"space-y-6\">\r\n {sensors.map((sensor, index) => (\r\n <div key={index} className=\"space-y-4\">\r\n {index > 0 && <Separator />}\r\n\r\n <div className=\"grid grid-cols-2 gap-4\">\r\n <div className=\"space-y-2\">\r\n <Label htmlFor={`type-${index}`}>{t('type')}</Label>\r\n <Select\r\n value={sensor.typeId === -1 ? undefined : String(sensor.typeId)}\r\n onValueChange={value => updateSensor(index, 'typeId', Number(value))}\r\n >\r\n <SelectTrigger id={`type-${index}`}>\r\n <SelectValue placeholder={t('selectType')} />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {sensorTypes.map(type => (\r\n <SelectItem key={type.id} value={String(type.id)}>\r\n {type.name.replace(/_/g, ' ')}\r\n </SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n </div>\r\n\r\n <div className=\"space-y-2\">\r\n <Label htmlFor={`name-${index}`}>{t('name')}</Label>\r\n <Input\r\n id={`name-${index}`}\r\n type=\"text\"\r\n value={sensor.name}\r\n onChange={e => updateSensor(index, 'name', e.target.value)}\r\n placeholder={t('namePlaceholder')}\r\n />\r\n </div>\r\n </div>\r\n\r\n <div className=\"space-y-2\">\r\n <Label htmlFor={`data-${index}`}>{t('dataUrl')}</Label>\r\n <Input\r\n id={`data-${index}`}\r\n type=\"text\"\r\n value={sensor.data}\r\n onChange={e => updateSensor(index, 'data', e.target.value)}\r\n placeholder={t('dataUrlPlaceholder')}\r\n />\r\n </div>\r\n\r\n <div className=\"grid grid-cols-2 gap-4\">\r\n <div className=\"space-y-2\">\r\n <Label htmlFor={`dataFormat-${index}`}>{t('dataFormat')}</Label>\r\n <Select\r\n value={sensor.dataFormat}\r\n onValueChange={value => updateSensor(index, 'dataFormat', value)}\r\n >\r\n <SelectTrigger id={`dataFormat-${index}`}>\r\n <SelectValue placeholder={t('selectFormat')} />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {Object.values(SensorDataFormat).map(format => (\r\n <SelectItem key={format} value={format}>\r\n {format}\r\n </SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n </div>\r\n\r\n <div className=\"space-y-2\">\r\n <Label htmlFor={`frequency-${index}`}>{t('updateFrequency')}</Label>\r\n <div className=\"flex gap-2\">\r\n <Input\r\n id={`frequency-${index}`}\r\n type=\"number\"\r\n min=\"1\"\r\n value={sensor.updateFrequency}\r\n onChange={e =>\r\n updateSensor(index, 'updateFrequency', parseFloat(e.target.value) || 1)\r\n }\r\n placeholder={t('frequencyPlaceholder')}\r\n className=\"flex-1\"\r\n />\r\n <Select\r\n value={String(sensor.updateFrequencyUnit)}\r\n onValueChange={value => updateSensor(index, 'updateFrequencyUnit', Number(value))}\r\n >\r\n <SelectTrigger className=\"w-[140px]\">\r\n <SelectValue />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {frequencyUnits.map(unit => (\r\n <SelectItem key={unit.value} value={String(unit.value)}>\r\n {unit.label}\r\n </SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div className=\"space-y-2\">\r\n <Label>{t('tags')}</Label>\r\n <SensorTagsSection\r\n tags={sensor.tags}\r\n variant=\"edit\"\r\n onAdd={async (tag) => addTag(index, tag)}\r\n onDelete={async (tag) => removeTag(index, tag)}\r\n />\r\n </div>\r\n\r\n <TooltipProvider>\r\n <Tooltip>\r\n <TooltipTrigger asChild>\r\n <Button\r\n type=\"button\"\r\n onClick={() => onPlaceSensor(index)}\r\n disabled={!sensor.name.trim() || !sensor.data.trim()}\r\n className=\"w-full\"\r\n >\r\n {t('placeSensor')}\r\n </Button>\r\n </TooltipTrigger>\r\n <TooltipContent>\r\n <p>{placeSensorTooltip}</p>\r\n </TooltipContent>\r\n </Tooltip>\r\n </TooltipProvider>\r\n </div>\r\n ))}\r\n </div>\r\n )\r\n\r\n if (layout === 'dialog') {\r\n // Render nothing visible when placing, but component stays mounted for useEffect\r\n if (isPlacing) {\r\n return <div style={{ display: 'none' }} />\r\n }\r\n\r\n return (\r\n <AddItemDialog\r\n open={isOpen}\r\n onOpenChange={open => !open && onCancel?.()}\r\n title={t('title')}\r\n icon={Radio}\r\n contentClassName=\"max-w-2xl max-h-[90vh] overflow-y-auto\"\r\n >\r\n {sensorForm}\r\n </AddItemDialog>\r\n )\r\n }\r\n\r\n return (\r\n <div className={cn('flex flex-col gap-4 p-4')}>\r\n <h3 className=\"text-lg font-semibold\">{t('title')}</h3>\r\n {sensorForm}\r\n </div>\r\n )\r\n}\r\n"],"mappings":"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","names":["_a","_b","_c","_d","_e","t"]}
|
|
1
|
+
{"version":3,"sources":["../../../../../src/core/components/ui/Sensors/SensorInput.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 { Radio } from 'lucide-react'\r\nimport { useSession } from 'next-auth/react'\r\nimport { useTranslations } from 'next-intl'\r\nimport * as React from 'react'\r\n\r\nimport { useCreateSensor, useSensor } from '../../../hooks/sensors/sensors'\r\nimport { useSensorTypes } from '../../../hooks/sensorTypes/sensorTypes'\r\nimport { AppConfigContext, MapContext, BuildingsContext, MenusContext, ToolsContext } from '../../../store'\r\nimport { ViewerNames, SensorDataFormat } from '../../../types/dbTypes'\r\nimport { frequencyUnits } from '../../../utils/timeUtils'\r\nimport { cn } from '../../../utils/utils'\r\nimport { AddItemDialog } from '../AddItemDialog'\r\nimport { Button } from '../Button'\r\nimport { Input } from '../Input'\r\nimport { Label } from '../Label'\r\nimport {\r\n Select,\r\n SelectContent,\r\n SelectItem,\r\n SelectTrigger,\r\n SelectValue,\r\n} from '../Select'\r\nimport { Separator } from '../Separator'\r\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../Tooltip'\r\n\r\nimport { parseSensorSeries } from './sensorData'\r\nimport { SensorTagsSection } from './SensorTagsSection'\r\n\r\nimport type { Sensor} from '../../../types/dbTypes';\r\n\r\n\r\n\r\nexport type SensorInputLayout = 'inline' | 'dialog'\r\n\r\nexport type BimSensorPlacementContext = {\r\n world: any\r\n raycast: (data: { camera: any; mouse: any; dom: any }) => Promise<any>\r\n buildingId?: number\r\n setCursor?: (cursor: string) => void\r\n addPendingMarker?: (position: { x: number; y: number; z: number }) => number\r\n removePendingMarker?: (id: number) => void\r\n}\r\n\r\nexport type SensorInputProps = {\r\n viewer: ViewerNames\r\n layout?: SensorInputLayout\r\n onCancel?: () => void\r\n bim?: BimSensorPlacementContext\r\n isOpen?: boolean\r\n /** When provided, the dialog becomes a single-sensor EDIT form (no multi-add, no placement). */\r\n editSensor?: Sensor\r\n /** Called after a successful edit save (in addition to onCancel). */\r\n onSaved?: () => void\r\n}\r\n\r\ntype SensorFormData = {\r\n typeId: number\r\n name: string\r\n data: string\r\n dataFormat: string\r\n updateFrequency: number\r\n updateFrequencyUnit: number\r\n tags: string[]\r\n}\r\n\r\n/**\r\n * Split a stored updateFrequency (ms) back into a {value, unit} pair for the form,\r\n * picking the largest frequencyUnits entry that divides evenly. Falls back to\r\n * expressing the value in milliseconds.\r\n */\r\nfunction splitUpdateFrequencyMs(updateFrequencyMs: number): { updateFrequency: number; updateFrequencyUnit: number } {\r\n const sortedDesc = [...frequencyUnits].sort((a, b) => b.value - a.value)\r\n for (const unit of sortedDesc) {\r\n if (unit.value > 0 && updateFrequencyMs % unit.value === 0) {\r\n return { updateFrequency: updateFrequencyMs / unit.value, updateFrequencyUnit: unit.value }\r\n }\r\n }\r\n return { updateFrequency: updateFrequencyMs, updateFrequencyUnit: 1 }\r\n}\r\n\r\nfunction sensorFormFromEditSensor(editSensor: Sensor): SensorFormData {\r\n const { updateFrequency, updateFrequencyUnit } = splitUpdateFrequencyMs(editSensor.updateFrequency)\r\n return {\r\n typeId: editSensor.typeId ?? -1,\r\n name: editSensor.name,\r\n data: editSensor.url ?? editSensor.data ?? '',\r\n dataFormat: editSensor.dataFormat,\r\n updateFrequency,\r\n updateFrequencyUnit,\r\n tags: editSensor.tags ?? [],\r\n }\r\n}\r\n\r\nexport function SensorInput({\r\n viewer,\r\n layout = 'dialog',\r\n onCancel,\r\n bim,\r\n isOpen = false,\r\n editSensor,\r\n onSaved,\r\n}: SensorInputProps) {\r\n const t = useTranslations('SensorInput')\r\n // Fallback for i18n keys that may not exist yet in every catalog (edit-mode additions).\r\n const tf = React.useCallback((key: string, fallback: string) => (t.has(key) ? t(key) : fallback), [t])\r\n const session = useSession()\r\n\r\n const user = session.data?.user\r\n const organizationId = user?.organizationId\r\n const authorId = session.data?.user?.id || -1\r\n\r\n const { state: appConfigState } = React.useContext(AppConfigContext)\r\n const mapContext = React.useContext(MapContext)\r\n\r\n const { state: buildingsState } = React.useContext(BuildingsContext)\r\n const buildingId = buildingsState?.buildings?.building?.id || -1\r\n\r\n const { state: toolsState } = React.useContext(ToolsContext)\r\n\r\n const { state: menusState, dispatch: menusDispatch } = React.useContext(MenusContext)\r\n const { currentSensorTypeId } = menusState.menus\r\n\r\n const [sensors, setSensors] = React.useState<SensorFormData[]>([\r\n editSensor\r\n ? sensorFormFromEditSensor(editSensor)\r\n : {\r\n typeId: currentSensorTypeId ?? -1,\r\n name: '',\r\n data: '',\r\n dataFormat: SensorDataFormat.Json,\r\n updateFrequency: 1,\r\n updateFrequencyUnit: 1000,\r\n tags: [],\r\n },\r\n ])\r\n const [currentSensorIndex, setCurrentSensorIndex] = React.useState(0)\r\n const [isPlacing, setIsPlacing] = React.useState(false)\r\n\r\n // Lightweight \"does this URL work\" preview, keyed by sensor-form index.\r\n const [preview, setPreview] = React.useState<Record<number, { ok: boolean; text: string }>>({})\r\n\r\n const checkDataUrl = React.useCallback(async (index: number, url: string, dataFormat: string) => {\r\n if (!url.trim()) {\r\n setPreview(prev => { const next = { ...prev }; delete next[index]; return next })\r\n return\r\n }\r\n try {\r\n const res = await fetch(url)\r\n if (!res.ok) {\r\n setPreview(prev => ({ ...prev, [index]: { ok: false, text: t('previewError') } }))\r\n return\r\n }\r\n const raw = await res.text()\r\n const { points, unit } = parseSensorSeries(raw, dataFormat as SensorDataFormat)\r\n if (points.length === 0) {\r\n setPreview(prev => ({ ...prev, [index]: { ok: false, text: t('previewNoData') } }))\r\n return\r\n }\r\n const unitText = unit ? ` · ${unit}` : ''\r\n setPreview(prev => ({ ...prev, [index]: { ok: true, text: `${points.length} ${t('previewPoints')}${unitText}` } }))\r\n } catch {\r\n setPreview(prev => ({ ...prev, [index]: { ok: false, text: t('previewError') } }))\r\n }\r\n }, [t])\r\n\r\n // Re-initialize form with pre-selected type (or the sensor being edited) each time the dialog opens\r\n React.useEffect(() => {\r\n if (!isOpen) return\r\n setSensors([\r\n editSensor\r\n ? sensorFormFromEditSensor(editSensor)\r\n : {\r\n typeId: currentSensorTypeId ?? -1,\r\n name: '',\r\n data: '',\r\n dataFormat: SensorDataFormat.Json,\r\n updateFrequency: 1,\r\n updateFrequencyUnit: 1000,\r\n tags: [],\r\n },\r\n ])\r\n setCurrentSensorIndex(0)\r\n setPreview({})\r\n }, [isOpen, editSensor])\r\n\r\n const { createSensor } = useCreateSensor()\r\n // Hook order must stay stable regardless of whether editSensor is provided.\r\n const { updateSensor: updateSensorMutation } = useSensor(editSensor?.id ?? null)\r\n\r\n const setCursor = React.useCallback(\r\n (cursor: string) => {\r\n if (viewer === ViewerNames.map) {\r\n const map = mapContext?.state?.map?.map\r\n if (!map) return\r\n if (map.getCanvas().style.cursor === cursor) return\r\n map.getCanvas().style.cursor = cursor\r\n return\r\n }\r\n\r\n if (viewer === ViewerNames.bim) {\r\n bim?.setCursor?.(cursor)\r\n }\r\n },\r\n [bim, mapContext, viewer]\r\n )\r\n\r\n const reset = React.useCallback(() => {\r\n setIsPlacing(false)\r\n setSensors([\r\n {\r\n typeId: currentSensorTypeId ?? -1,\r\n name: '',\r\n data: '',\r\n dataFormat: SensorDataFormat.Json,\r\n updateFrequency: 1,\r\n updateFrequencyUnit: 1000,\r\n tags: [],\r\n },\r\n ])\r\n setCurrentSensorIndex(0)\r\n setPreview({})\r\n setCursor('')\r\n menusDispatch({type: 'SET_CURRENT_SENSOR_TYPE_ID', payload: { currentSensorTypeId: null }})\r\n }, [setCursor, currentSensorTypeId])\r\n\r\n const onCreateSensor = async (sensorData: Partial<Sensor>) => {\r\n await createSensor({ sensorData })\r\n\r\n // Ensure the sensor type is visible after creation\r\n if (sensorData.typeId) {\r\n menusDispatch({\r\n type: 'TOGGLE_SENSOR_TYPE_VISIBILITY',\r\n payload: {\r\n viewer: viewer,\r\n sensorTypeId: sensorData.typeId,\r\n force: true,\r\n },\r\n })\r\n }\r\n\r\n reset()\r\n onCancel?.()\r\n }\r\n\r\n const onSaveEdit = async () => {\r\n if (!editSensor) return\r\n const form = sensors[0]\r\n const partial: Partial<Sensor> = {\r\n typeId: form.typeId,\r\n name: form.name,\r\n data: form.data,\r\n dataFormat: form.dataFormat as SensorDataFormat,\r\n url: form.data,\r\n updateFrequency: form.updateFrequency * form.updateFrequencyUnit,\r\n tags: form.tags,\r\n }\r\n await updateSensorMutation(partial)\r\n onSaved?.()\r\n onCancel?.()\r\n }\r\n\r\n React.useEffect(() => {\r\n if (editSensor) return\r\n if (!isPlacing) return\r\n\r\n if (viewer === ViewerNames.map) {\r\n const map = mapContext?.state?.map?.map\r\n if (!map) return\r\n\r\n const mouseMoveCrosshairHandler = () => {\r\n setCursor('crosshair')\r\n }\r\n\r\n const dbClickAddSensorHandler = (e: any): void => {\r\n const longitude = e?.lngLat?.lng\r\n const latitude = e?.lngLat?.lat\r\n if (typeof longitude !== 'number' || typeof latitude !== 'number') return\r\n\r\n const elevation = map.queryTerrainElevation([latitude, longitude]) || 0\r\n\r\n const currentSensor = sensors[currentSensorIndex]\r\n const updateFrequencyMs = currentSensor.updateFrequency * currentSensor.updateFrequencyUnit\r\n\r\n const newSensor: any = {\r\n authorId: Number(user?.id),\r\n organizationId,\r\n typeId: currentSensor.typeId,\r\n name: currentSensor.name,\r\n data: currentSensor.data,\r\n dataFormat: currentSensor.dataFormat,\r\n updateFrequency: updateFrequencyMs,\r\n url: currentSensor.data,\r\n visible: true,\r\n longitude,\r\n latitude,\r\n elevation,\r\n viewer: ViewerNames.map,\r\n tags: currentSensor.tags,\r\n }\r\n onCreateSensor(newSensor as Partial<Sensor>)\r\n }\r\n\r\n map.on('mousemove', mouseMoveCrosshairHandler)\r\n map.on('dblclick', dbClickAddSensorHandler)\r\n\r\n return () => {\r\n setCursor('')\r\n map.off('mousemove', mouseMoveCrosshairHandler)\r\n map.off('dblclick', dbClickAddSensorHandler)\r\n }\r\n }\r\n\r\n if (viewer === ViewerNames.bim) {\r\n const world = bim?.world\r\n const raycast = bim?.raycast\r\n if (!world || !raycast) return\r\n\r\n let disposed = false\r\n let removeListeners: (() => void) | null = null\r\n\r\n ;(async () => {\r\n const THREE = await import('three')\r\n if (disposed) return\r\n\r\n const mouse = new THREE.Vector2()\r\n\r\n const handleMouseMove = (e: MouseEvent) => {\r\n mouse.x = e.clientX\r\n mouse.y = e.clientY\r\n }\r\n\r\n const handleDblClick = async (e: MouseEvent) => {\r\n mouse.x = e.clientX\r\n mouse.y = e.clientY\r\n\r\n const result = await raycast({\r\n camera: world.camera?.three,\r\n mouse,\r\n dom: world.renderer?.three?.domElement,\r\n })\r\n\r\n if (!result?.point) return\r\n const p = result.point\r\n\r\n const currentSensor = sensors[currentSensorIndex]\r\n const updateFrequencyMs = currentSensor.updateFrequency * currentSensor.updateFrequencyUnit\r\n\r\n // Add pending marker immediately\r\n const pendingId = bim?.addPendingMarker?.({ x: p.x, y: p.y, z: p.z })\r\n\r\n const newSensor: any = {\r\n authorId: Number(authorId),\r\n organizationId,\r\n typeId: currentSensor.typeId,\r\n name: currentSensor.name,\r\n data: currentSensor.data,\r\n dataFormat: currentSensor.dataFormat,\r\n updateFrequency: updateFrequencyMs,\r\n url: currentSensor.data,\r\n x: p.x,\r\n y: p.y,\r\n z: p.z,\r\n visible: true,\r\n buildingId,\r\n viewer,\r\n tags: currentSensor.tags,\r\n }\r\n\r\n try {\r\n await onCreateSensor(newSensor as Partial<Sensor>)\r\n } finally {\r\n // Remove pending marker after creation completes (or fails)\r\n if (pendingId) {\r\n bim?.removePendingMarker?.(pendingId)\r\n }\r\n }\r\n }\r\n\r\n document.addEventListener('mousemove', handleMouseMove)\r\n document.addEventListener('dblclick', handleDblClick)\r\n\r\n removeListeners = () => {\r\n document.removeEventListener('mousemove', handleMouseMove)\r\n document.removeEventListener('dblclick', handleDblClick)\r\n }\r\n })()\r\n\r\n return () => {\r\n disposed = true\r\n setCursor('')\r\n removeListeners?.()\r\n }\r\n }\r\n }, [\r\n appConfigState,\r\n bim,\r\n editSensor,\r\n isPlacing,\r\n mapContext,\r\n viewer,\r\n onCancel,\r\n reset,\r\n setCursor,\r\n sensors,\r\n currentSensorIndex,\r\n authorId,\r\n organizationId,\r\n buildingId,\r\n user?.id,\r\n ])\r\n\r\n const updateSensor = (index: number, field: keyof SensorFormData, value: any) => {\r\n setSensors(prev =>\r\n prev.map((sensor, i) => (i === index ? { ...sensor, [field]: value } : sensor))\r\n )\r\n }\r\n\r\n const addAnotherSensor = () => {\r\n setSensors(prev => [\r\n ...prev,\r\n {\r\n typeId: -1,\r\n name: '',\r\n data: '',\r\n dataFormat: SensorDataFormat.Json,\r\n updateFrequency: 1,\r\n updateFrequencyUnit: 1000,\r\n tags: [],\r\n },\r\n ])\r\n }\r\n\r\n const addTag = (index: number, tag: string) => {\r\n setSensors(prev =>\r\n prev.map((sensor, i) =>\r\n i === index ? { ...sensor, tags: [...sensor.tags, tag] } : sensor\r\n )\r\n )\r\n }\r\n\r\n const removeTag = (index: number, tag: string) => {\r\n setSensors(prev =>\r\n prev.map((sensor, i) =>\r\n i === index ? { ...sensor, tags: sensor.tags.filter(t => t !== tag) } : sensor\r\n )\r\n )\r\n }\r\n\r\n const onPlaceSensor = (index: number) => {\r\n const sensor = sensors[index]\r\n if (!sensor.name.trim() || !sensor.data.trim()) {\r\n return\r\n }\r\n setCurrentSensorIndex(index)\r\n setIsPlacing(true)\r\n setCursor('crosshair')\r\n }\r\n\r\n const { sensorTypes } = useSensorTypes()\r\n\r\n const placeSensorTooltip = viewer === ViewerNames.map\r\n ? t('placeSensorTooltipMap')\r\n : t('placeSensorTooltipBim')\r\n\r\n const sensorForm = (\r\n <div className=\"space-y-6\">\r\n {sensors.map((sensor, index) => (\r\n <div key={index} className=\"space-y-4\">\r\n {index > 0 && <Separator />}\r\n\r\n <div className=\"grid grid-cols-2 gap-4\">\r\n <div className=\"space-y-2\">\r\n <Label htmlFor={`type-${index}`}>{t('type')}</Label>\r\n <Select\r\n value={sensor.typeId === -1 ? undefined : String(sensor.typeId)}\r\n onValueChange={value => updateSensor(index, 'typeId', Number(value))}\r\n >\r\n <SelectTrigger id={`type-${index}`}>\r\n <SelectValue placeholder={t('selectType')} />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {sensorTypes.map(type => (\r\n <SelectItem key={type.id} value={String(type.id)}>\r\n {type.name.replace(/_/g, ' ')}\r\n </SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n </div>\r\n\r\n <div className=\"space-y-2\">\r\n <Label htmlFor={`name-${index}`}>{t('name')}</Label>\r\n <Input\r\n id={`name-${index}`}\r\n type=\"text\"\r\n value={sensor.name}\r\n onChange={e => updateSensor(index, 'name', e.target.value)}\r\n placeholder={t('namePlaceholder')}\r\n />\r\n </div>\r\n </div>\r\n\r\n <div className=\"space-y-2\">\r\n <Label htmlFor={`data-${index}`}>{t('dataUrl')}</Label>\r\n <Input\r\n id={`data-${index}`}\r\n type=\"text\"\r\n value={sensor.data}\r\n onChange={e => updateSensor(index, 'data', e.target.value)}\r\n onBlur={e => checkDataUrl(index, e.target.value, sensor.dataFormat)}\r\n placeholder={t('dataUrlPlaceholder')}\r\n />\r\n {preview[index] && (\r\n <p className={cn('text-xs', preview[index].ok ? 'text-muted-foreground' : 'text-destructive')}>\r\n {preview[index].text}\r\n </p>\r\n )}\r\n </div>\r\n\r\n <div className=\"grid grid-cols-2 gap-4\">\r\n <div className=\"space-y-2\">\r\n <Label htmlFor={`dataFormat-${index}`}>{t('dataFormat')}</Label>\r\n <Select\r\n value={sensor.dataFormat}\r\n onValueChange={value => updateSensor(index, 'dataFormat', value)}\r\n >\r\n <SelectTrigger id={`dataFormat-${index}`}>\r\n <SelectValue placeholder={t('selectFormat')} />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {Object.values(SensorDataFormat).map(format => (\r\n <SelectItem key={format} value={format}>\r\n {format}\r\n </SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n </div>\r\n\r\n <div className=\"space-y-2\">\r\n <Label htmlFor={`frequency-${index}`}>{t('updateFrequency')}</Label>\r\n <div className=\"flex gap-2\">\r\n <Input\r\n id={`frequency-${index}`}\r\n type=\"number\"\r\n min=\"1\"\r\n value={sensor.updateFrequency}\r\n onChange={e =>\r\n updateSensor(index, 'updateFrequency', parseFloat(e.target.value) || 1)\r\n }\r\n placeholder={t('frequencyPlaceholder')}\r\n className=\"flex-1\"\r\n />\r\n <Select\r\n value={String(sensor.updateFrequencyUnit)}\r\n onValueChange={value => updateSensor(index, 'updateFrequencyUnit', Number(value))}\r\n >\r\n <SelectTrigger className=\"w-[140px]\">\r\n <SelectValue />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {frequencyUnits.map(unit => (\r\n <SelectItem key={unit.value} value={String(unit.value)}>\r\n {unit.label}\r\n </SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div className=\"space-y-2\">\r\n <Label>{t('tags')}</Label>\r\n <SensorTagsSection\r\n tags={sensor.tags}\r\n variant=\"edit\"\r\n onAdd={async (tag) => addTag(index, tag)}\r\n onDelete={async (tag) => removeTag(index, tag)}\r\n />\r\n </div>\r\n\r\n {editSensor ? (\r\n <Button\r\n type=\"button\"\r\n onClick={onSaveEdit}\r\n disabled={!sensor.name.trim() || !sensor.data.trim()}\r\n className=\"w-full\"\r\n >\r\n {tf('save', 'Save')}\r\n </Button>\r\n ) : (\r\n <TooltipProvider>\r\n <Tooltip>\r\n <TooltipTrigger asChild>\r\n <Button\r\n type=\"button\"\r\n onClick={() => onPlaceSensor(index)}\r\n disabled={!sensor.name.trim() || !sensor.data.trim()}\r\n className=\"w-full\"\r\n >\r\n {t('placeSensor')}\r\n </Button>\r\n </TooltipTrigger>\r\n <TooltipContent>\r\n <p>{placeSensorTooltip}</p>\r\n </TooltipContent>\r\n </Tooltip>\r\n </TooltipProvider>\r\n )}\r\n </div>\r\n ))}\r\n </div>\r\n )\r\n\r\n if (layout === 'dialog') {\r\n // Render nothing visible when placing, but component stays mounted for useEffect\r\n if (isPlacing) {\r\n return <div style={{ display: 'none' }} />\r\n }\r\n\r\n return (\r\n <AddItemDialog\r\n open={isOpen}\r\n onOpenChange={open => !open && onCancel?.()}\r\n title={t('title')}\r\n icon={Radio}\r\n contentClassName=\"max-w-2xl max-h-[90vh] overflow-y-auto\"\r\n >\r\n {sensorForm}\r\n </AddItemDialog>\r\n )\r\n }\r\n\r\n return (\r\n <div className={cn('flex flex-col gap-4 p-4')}>\r\n <h3 className=\"text-lg font-semibold\">{t('title')}</h3>\r\n {sensorForm}\r\n </div>\r\n )\r\n}\r\n"],"mappings":"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","names":["_a","_b","_c","_d","_e","t"]}
|
|
@@ -11,7 +11,9 @@ interface SensorTagsSectionProps {
|
|
|
11
11
|
onAdd?: (tag: string) => Promise<void>;
|
|
12
12
|
onDelete?: (tag: string) => Promise<void>;
|
|
13
13
|
translations?: SensorTagsTranslations;
|
|
14
|
+
/** Increment to open the add-tag input from an external control (e.g. a card tools row). */
|
|
15
|
+
openAddSignal?: number;
|
|
14
16
|
}
|
|
15
|
-
export declare function SensorTagsSection({ tags, variant, onAdd, onDelete, translations }: SensorTagsSectionProps): React.JSX.Element;
|
|
17
|
+
export declare function SensorTagsSection({ tags, variant, onAdd, onDelete, translations, openAddSignal }: SensorTagsSectionProps): React.JSX.Element;
|
|
16
18
|
export {};
|
|
17
19
|
//# sourceMappingURL=SensorTagsSection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SensorTagsSection.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/ui/Sensors/SensorTagsSection.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAO9B,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AASD,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,CAAA;IAC9B,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACtC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACzC,YAAY,CAAC,EAAE,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"SensorTagsSection.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/ui/Sensors/SensorTagsSection.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAO9B,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AASD,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,CAAA;IAC9B,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACtC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACzC,YAAY,CAAC,EAAE,sBAAsB,CAAA;IACrC,4FAA4F;IAC5F,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,wBAAgB,iBAAiB,CAAC,EAAE,IAAI,EAAE,OAAgB,EAAE,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,EAAE,sBAAsB,qBAwFjI"}
|
|
@@ -28,10 +28,13 @@ const DEFAULT_TRANSLATIONS = {
|
|
|
28
28
|
cancel: "Cancel",
|
|
29
29
|
newTagPlaceholder: "New tag\u2026"
|
|
30
30
|
};
|
|
31
|
-
function SensorTagsSection({ tags, variant = "edit", onAdd, onDelete, translations }) {
|
|
31
|
+
function SensorTagsSection({ tags, variant = "edit", onAdd, onDelete, translations, openAddSignal }) {
|
|
32
32
|
const tr = __spreadValues(__spreadValues({}, DEFAULT_TRANSLATIONS), translations);
|
|
33
33
|
const [addingTag, setAddingTag] = React.useState(false);
|
|
34
34
|
const [newTagValue, setNewTagValue] = React.useState("");
|
|
35
|
+
React.useEffect(() => {
|
|
36
|
+
if (openAddSignal && onAdd) setAddingTag(true);
|
|
37
|
+
}, [openAddSignal, onAdd]);
|
|
35
38
|
const handleAdd = async () => {
|
|
36
39
|
const trimmed = newTagValue.trim();
|
|
37
40
|
if (!trimmed || !onAdd) return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/core/components/ui/Sensors/SensorTagsSection.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 LR from 'lucide-react'\r\nimport * as React from 'react'\r\n\r\nimport { stringToColour } from '../../viewers/map/utils/stringToColour'\r\nimport { Badge } from '../Badge'\r\nimport { Button } from '../Button'\r\nimport { Input } from '../Input'\r\n\r\nexport interface SensorTagsTranslations {\r\n addTag?: string\r\n removeTag?: string\r\n cancel?: string\r\n newTagPlaceholder?: string\r\n}\r\n\r\nconst DEFAULT_TRANSLATIONS: Required<SensorTagsTranslations> = {\r\n addTag: 'Add tag',\r\n removeTag: 'Remove tag',\r\n cancel: 'Cancel',\r\n newTagPlaceholder: 'New tag…',\r\n}\r\n\r\ninterface SensorTagsSectionProps {\r\n tags: string[]\r\n variant?: 'edit' | 'read-only'\r\n onAdd?: (tag: string) => Promise<void>\r\n onDelete?: (tag: string) => Promise<void>\r\n translations?: SensorTagsTranslations\r\n}\r\n\r\nexport function SensorTagsSection({ tags, variant = 'edit', onAdd, onDelete, translations }: SensorTagsSectionProps) {\r\n const tr = { ...DEFAULT_TRANSLATIONS, ...translations }\r\n const [addingTag, setAddingTag] = React.useState(false)\r\n const [newTagValue, setNewTagValue] = React.useState('')\r\n\r\n const handleAdd = async () => {\r\n const trimmed = newTagValue.trim()\r\n if (!trimmed || !onAdd) return\r\n if (tags.includes(trimmed)) return\r\n await onAdd(trimmed)\r\n setNewTagValue('')\r\n setAddingTag(false)\r\n }\r\n\r\n if (variant === 'read-only') {\r\n if (tags.length === 0) return null\r\n return (\r\n <div className=\"flex items-center gap-2 px-4 py-2 border-t flex-wrap\">\r\n <span className=\"text-muted-foreground text-sm\">Tags:</span>\r\n {tags.map((tag, index) => {\r\n const tagColor = stringToColour(tag, 'max')\r\n return (\r\n <Badge key={index} variant=\"secondary\" className=\"text-xs\" style={{ backgroundColor: tagColor, color: 'white' }}>\r\n {tag}\r\n </Badge>\r\n )\r\n })}\r\n </div>\r\n )\r\n }\r\n\r\n return (\r\n <div className=\"flex items-center gap-2 px-4 py-2 border-t flex-wrap\">\r\n <span className=\"text-muted-foreground text-sm\">Tags:</span>\r\n {tags.length > 0 && (\r\n <>\r\n {tags.map((tag, index) => {\r\n const tagColor = stringToColour(tag, 'max')\r\n return (\r\n <Badge key={index} variant=\"secondary\" className=\"text-xs\" style={{ backgroundColor: tagColor, color: 'white' }}>\r\n {tag}{' '}\r\n {onDelete && (\r\n <span title={tr.removeTag}>\r\n <LR.X\r\n className=\"h-3 w-3 cursor-pointer ml-2 -mr-1\"\r\n onClick={() => onDelete(tag)}\r\n />\r\n </span>\r\n )}\r\n </Badge>\r\n )\r\n })}\r\n </>\r\n )}\r\n {onAdd && (\r\n addingTag ? (\r\n <div className=\"flex items-center gap-1 ml-auto\">\r\n <Input\r\n autoFocus\r\n value={newTagValue}\r\n onChange={e => setNewTagValue(e.target.value)}\r\n onKeyDown={e => {\r\n if (e.key === 'Enter') handleAdd()\r\n if (e.key === 'Escape') { setAddingTag(false); setNewTagValue('') }\r\n }}\r\n placeholder={tr.newTagPlaceholder}\r\n className=\"h-6 text-xs w-28 px-2\"\r\n />\r\n <Button title={tr.addTag} variant=\"ghost\" size=\"icon\" className=\"h-6 w-6 p-0\" onClick={handleAdd}>\r\n <LR.Plus className=\"h-3 w-3\" />\r\n </Button>\r\n <Button title={tr.cancel} variant=\"ghost\" size=\"icon\" className=\"h-6 w-6 p-0\" onClick={() => { setAddingTag(false); setNewTagValue('') }}>\r\n <LR.X className=\"h-3 w-3\" />\r\n </Button>\r\n </div>\r\n ) : (\r\n <Button title={tr.addTag} variant=\"ghost\" size=\"icon\" className=\"h-6 w-6 p-0 ml-auto\" onClick={() => setAddingTag(true)}>\r\n <LR.Plus className=\"h-3 w-3\" />\r\n </Button>\r\n )\r\n )}\r\n </div>\r\n )\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"sources":["../../../../../src/core/components/ui/Sensors/SensorTagsSection.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 LR from 'lucide-react'\r\nimport * as React from 'react'\r\n\r\nimport { stringToColour } from '../../viewers/map/utils/stringToColour'\r\nimport { Badge } from '../Badge'\r\nimport { Button } from '../Button'\r\nimport { Input } from '../Input'\r\n\r\nexport interface SensorTagsTranslations {\r\n addTag?: string\r\n removeTag?: string\r\n cancel?: string\r\n newTagPlaceholder?: string\r\n}\r\n\r\nconst DEFAULT_TRANSLATIONS: Required<SensorTagsTranslations> = {\r\n addTag: 'Add tag',\r\n removeTag: 'Remove tag',\r\n cancel: 'Cancel',\r\n newTagPlaceholder: 'New tag…',\r\n}\r\n\r\ninterface SensorTagsSectionProps {\r\n tags: string[]\r\n variant?: 'edit' | 'read-only'\r\n onAdd?: (tag: string) => Promise<void>\r\n onDelete?: (tag: string) => Promise<void>\r\n translations?: SensorTagsTranslations\r\n /** Increment to open the add-tag input from an external control (e.g. a card tools row). */\r\n openAddSignal?: number\r\n}\r\n\r\nexport function SensorTagsSection({ tags, variant = 'edit', onAdd, onDelete, translations, openAddSignal }: SensorTagsSectionProps) {\r\n const tr = { ...DEFAULT_TRANSLATIONS, ...translations }\r\n const [addingTag, setAddingTag] = React.useState(false)\r\n const [newTagValue, setNewTagValue] = React.useState('')\r\n\r\n // An external tools-row \"add tag\" control bumps openAddSignal to reveal the input.\r\n React.useEffect(() => {\r\n if (openAddSignal && onAdd) setAddingTag(true)\r\n }, [openAddSignal, onAdd])\r\n\r\n const handleAdd = async () => {\r\n const trimmed = newTagValue.trim()\r\n if (!trimmed || !onAdd) return\r\n if (tags.includes(trimmed)) return\r\n await onAdd(trimmed)\r\n setNewTagValue('')\r\n setAddingTag(false)\r\n }\r\n\r\n if (variant === 'read-only') {\r\n if (tags.length === 0) return null\r\n return (\r\n <div className=\"flex items-center gap-2 px-4 py-2 border-t flex-wrap\">\r\n <span className=\"text-muted-foreground text-sm\">Tags:</span>\r\n {tags.map((tag, index) => {\r\n const tagColor = stringToColour(tag, 'max')\r\n return (\r\n <Badge key={index} variant=\"secondary\" className=\"text-xs\" style={{ backgroundColor: tagColor, color: 'white' }}>\r\n {tag}\r\n </Badge>\r\n )\r\n })}\r\n </div>\r\n )\r\n }\r\n\r\n return (\r\n <div className=\"flex items-center gap-2 px-4 py-2 border-t flex-wrap\">\r\n <span className=\"text-muted-foreground text-sm\">Tags:</span>\r\n {tags.length > 0 && (\r\n <>\r\n {tags.map((tag, index) => {\r\n const tagColor = stringToColour(tag, 'max')\r\n return (\r\n <Badge key={index} variant=\"secondary\" className=\"text-xs\" style={{ backgroundColor: tagColor, color: 'white' }}>\r\n {tag}{' '}\r\n {onDelete && (\r\n <span title={tr.removeTag}>\r\n <LR.X\r\n className=\"h-3 w-3 cursor-pointer ml-2 -mr-1\"\r\n onClick={() => onDelete(tag)}\r\n />\r\n </span>\r\n )}\r\n </Badge>\r\n )\r\n })}\r\n </>\r\n )}\r\n {onAdd && (\r\n addingTag ? (\r\n <div className=\"flex items-center gap-1 ml-auto\">\r\n <Input\r\n autoFocus\r\n value={newTagValue}\r\n onChange={e => setNewTagValue(e.target.value)}\r\n onKeyDown={e => {\r\n if (e.key === 'Enter') handleAdd()\r\n if (e.key === 'Escape') { setAddingTag(false); setNewTagValue('') }\r\n }}\r\n placeholder={tr.newTagPlaceholder}\r\n className=\"h-6 text-xs w-28 px-2\"\r\n />\r\n <Button title={tr.addTag} variant=\"ghost\" size=\"icon\" className=\"h-6 w-6 p-0\" onClick={handleAdd}>\r\n <LR.Plus className=\"h-3 w-3\" />\r\n </Button>\r\n <Button title={tr.cancel} variant=\"ghost\" size=\"icon\" className=\"h-6 w-6 p-0\" onClick={() => { setAddingTag(false); setNewTagValue('') }}>\r\n <LR.X className=\"h-3 w-3\" />\r\n </Button>\r\n </div>\r\n ) : (\r\n <Button title={tr.addTag} variant=\"ghost\" size=\"icon\" className=\"h-6 w-6 p-0 ml-auto\" onClick={() => setAddingTag(true)}>\r\n <LR.Plus className=\"h-3 w-3\" />\r\n </Button>\r\n )\r\n )}\r\n </div>\r\n )\r\n}\r\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
/** Stable key used in state for sensors with no tags — locale-independent */
|
|
3
3
|
export declare const UNTAGGED_TAG = "__untagged__";
|
|
4
|
-
export declare function SensorsSection(
|
|
5
|
-
minioBaseUrl?: string;
|
|
6
|
-
}): React.JSX.Element;
|
|
4
|
+
export declare function SensorsSection(): React.JSX.Element;
|
|
7
5
|
//# sourceMappingURL=SensorsSection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SensorsSection.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/ui/Sensors/SensorsSection.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"SensorsSection.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/ui/Sensors/SensorsSection.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AA+B9B,6EAA6E;AAC7E,eAAO,MAAM,YAAY,iBAAiB,CAAA;AAE1C,wBAAgB,cAAc,sBA+S7B"}
|