@collabdt/core 0.3.1 → 0.3.2
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/authentication/ForgotPassword.d.ts.map +1 -1
- package/dist/core/components/authentication/ForgotPassword.js +0 -1
- package/dist/core/components/authentication/ForgotPassword.js.map +1 -1
- package/dist/core/components/ui/Comments/CollapsibleCommentItem.d.ts +12 -2
- package/dist/core/components/ui/Comments/CollapsibleCommentItem.d.ts.map +1 -1
- package/dist/core/components/ui/Comments/CollapsibleCommentItem.js +137 -139
- package/dist/core/components/ui/Comments/CollapsibleCommentItem.js.map +1 -1
- package/dist/core/components/ui/Comments/Comment.d.ts +18 -1
- package/dist/core/components/ui/Comments/Comment.d.ts.map +1 -1
- package/dist/core/components/ui/Comments/Comment.js +94 -23
- package/dist/core/components/ui/Comments/Comment.js.map +1 -1
- package/dist/core/components/ui/Comments/CommentActionButtons.d.ts +34 -0
- package/dist/core/components/ui/Comments/CommentActionButtons.d.ts.map +1 -0
- package/dist/core/components/ui/Comments/CommentActionButtons.js +106 -0
- package/dist/core/components/ui/Comments/CommentActionButtons.js.map +1 -0
- package/dist/core/components/ui/Comments/CommentInput.d.ts.map +1 -1
- package/dist/core/components/ui/Comments/CommentInput.js +5 -2
- package/dist/core/components/ui/Comments/CommentInput.js.map +1 -1
- package/dist/core/components/ui/Comments/CommentsSection.d.ts.map +1 -1
- package/dist/core/components/ui/Comments/CommentsSection.js +86 -42
- package/dist/core/components/ui/Comments/CommentsSection.js.map +1 -1
- package/dist/core/components/ui/Comments/commentUtils.d.ts.map +1 -1
- package/dist/core/components/ui/Comments/commentUtils.js +2 -1
- package/dist/core/components/ui/Comments/commentUtils.js.map +1 -1
- package/dist/core/components/ui/FilesManager/src/useFileUploadWithProgress.d.ts.map +1 -1
- package/dist/core/components/ui/FilesManager/src/useFileUploadWithProgress.js +0 -1
- package/dist/core/components/ui/FilesManager/src/useFileUploadWithProgress.js.map +1 -1
- package/dist/core/components/ui/InfoSidebar/Container.d.ts.map +1 -1
- package/dist/core/components/ui/InfoSidebar/Container.js +16 -12
- package/dist/core/components/ui/InfoSidebar/Container.js.map +1 -1
- package/dist/core/components/ui/Sensors/SensorInput.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/SensorInput.js +0 -1
- package/dist/core/components/ui/Sensors/SensorInput.js.map +1 -1
- package/dist/core/components/ui/Sensors/SensorsSection.js +0 -2
- package/dist/core/components/ui/Sensors/SensorsSection.js.map +1 -1
- package/dist/core/components/ui/Sidebar.d.ts.map +1 -1
- package/dist/core/components/ui/Sidebar.js +27 -4
- package/dist/core/components/ui/Sidebar.js.map +1 -1
- package/dist/core/components/ui/ToolbarButton.d.ts.map +1 -1
- package/dist/core/components/ui/ToolbarButton.js +0 -1
- package/dist/core/components/ui/ToolbarButton.js.map +1 -1
- package/dist/core/components/viewers/Data/advancedFilter/FiltersDialog.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/advancedFilter/FiltersDialog.js +0 -2
- package/dist/core/components/viewers/Data/advancedFilter/FiltersDialog.js.map +1 -1
- package/dist/core/components/viewers/Data/details/EnergySourcesGrid.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/details/EnergySourcesGrid.js +0 -10
- package/dist/core/components/viewers/Data/details/EnergySourcesGrid.js.map +1 -1
- package/dist/core/components/viewers/Data/files/DatatableFileAdder.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/files/DatatableFileAdder.js +0 -1
- package/dist/core/components/viewers/Data/files/DatatableFileAdder.js.map +1 -1
- package/dist/core/components/viewers/Data/files/utils/fuzzySearch.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/files/utils/fuzzySearch.js +0 -1
- package/dist/core/components/viewers/Data/files/utils/fuzzySearch.js.map +1 -1
- package/dist/core/components/viewers/Data/userDetails/UserMoreOptions.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/userDetails/UserMoreOptions.js +0 -1
- package/dist/core/components/viewers/Data/userDetails/UserMoreOptions.js.map +1 -1
- package/dist/core/components/viewers/Data/utils/filterHelper.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/utils/filterHelper.js +0 -2
- package/dist/core/components/viewers/Data/utils/filterHelper.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.js +0 -3
- package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.js +1 -7
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/ModelsSection.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/ModelsSection.js +1 -9
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/ModelsSection.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/TabSelector.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/TabSelector.js +4 -3
- package/dist/core/components/viewers/bim/src/BimSidebar/src/TabSelector.js.map +1 -1
- package/dist/core/components/viewers/bim/src/ClippingPlane.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/ClippingPlane.js +0 -1
- package/dist/core/components/viewers/bim/src/ClippingPlane.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.d.ts +16 -10
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.js +26 -6
- 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 +23 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.d.ts.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.js +108 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.js.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.d.ts +44 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.d.ts.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.js +77 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.js.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.d.ts +24 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.d.ts.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.js +27 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.js.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/renderCSS2DMarkers.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/renderCSS2DMarkers.js +2 -2
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/renderCSS2DMarkers.js.map +1 -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 +242 -61
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/ClippingTool/ClippingTool.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/ClippingTool/ClippingTool.js +1 -5
- package/dist/core/components/viewers/bim/src/tools/ClippingTool/ClippingTool.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/ExplodeByLevelTool.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/ExplodeByLevelTool.js +0 -1
- package/dist/core/components/viewers/bim/src/tools/ExplodeByLevelTool.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/measureBimTool.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/measureBimTool.js +1 -2
- package/dist/core/components/viewers/bim/src/tools/measureBimTool.js.map +1 -1
- package/dist/core/components/viewers/map/MapViewer.d.ts.map +1 -1
- package/dist/core/components/viewers/map/MapViewer.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/arcGISDatasets.d.ts.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/arcGISDatasets.js +11 -14
- package/dist/core/components/viewers/map/datasets/src/arcGISDatasets.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/ckanDatasets.d.ts.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/ckanDatasets.js +2 -5
- package/dist/core/components/viewers/map/datasets/src/ckanDatasets.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/localDatasets.d.ts.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/localDatasets.js +0 -3
- package/dist/core/components/viewers/map/datasets/src/localDatasets.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/opendatasoftDatasets.d.ts.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/opendatasoftDatasets.js +0 -11
- package/dist/core/components/viewers/map/datasets/src/opendatasoftDatasets.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/DatabaseBuildingPopover/src/BuildingsTools.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/DatabaseBuildingPopover/src/BuildingsTools.js +0 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/DatabaseBuildingPopover/src/BuildingsTools.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/NonDatabaseBuildingPopover.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/NonDatabaseBuildingPopover.js +0 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/NonDatabaseBuildingPopover.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/OpenDataFeaturePopover.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/OpenDataFeaturePopover.js +0 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/OpenDataFeaturePopover.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/CommentLayer/index.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/CommentLayer/index.js +174 -53
- package/dist/core/components/viewers/map/src/MapLayers/src/CommentLayer/index.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/FileLayer/components/MapFileManager.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/FileLayer/components/MapFileManager.js +2 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/FileLayer/components/MapFileManager.js.map +1 -1
- 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 +0 -3
- package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/TabSelector.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/TabSelector.js +4 -3
- package/dist/core/components/viewers/map/src/MapSidebar/TabSelector.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/FilesSection.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/FilesSection.js +0 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/FilesSection.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/ModelsSection.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/ModelsSection.js +1 -5
- package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/ModelsSection.js.map +1 -1
- package/dist/core/components/viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS.js +1 -3
- package/dist/core/components/viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS.js.map +1 -1
- package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.d.ts.map +1 -1
- package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.js +0 -1
- package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.js.map +1 -1
- package/dist/core/components/viewers/map/utils/flyToEach.d.ts.map +1 -1
- package/dist/core/components/viewers/map/utils/flyToEach.js +0 -1
- package/dist/core/components/viewers/map/utils/flyToEach.js.map +1 -1
- package/dist/core/components/viewers/map/utils/getOsmIdbyCoordinates.d.ts.map +1 -1
- package/dist/core/components/viewers/map/utils/getOsmIdbyCoordinates.js +0 -2
- package/dist/core/components/viewers/map/utils/getOsmIdbyCoordinates.js.map +1 -1
- package/dist/core/hooks/comments/comments.d.ts +11 -0
- package/dist/core/hooks/comments/comments.d.ts.map +1 -1
- package/dist/core/hooks/comments/comments.js +3 -1
- package/dist/core/hooks/comments/comments.js.map +1 -1
- package/dist/core/hooks/comments/createCommentHooks.d.ts +11 -0
- package/dist/core/hooks/comments/createCommentHooks.d.ts.map +1 -1
- package/dist/core/hooks/comments/createCommentHooks.js +30 -1
- package/dist/core/hooks/comments/createCommentHooks.js.map +1 -1
- package/dist/core/hooks/files/files.d.ts.map +1 -1
- package/dist/core/hooks/files/files.js +0 -1
- package/dist/core/hooks/files/files.js.map +1 -1
- package/dist/core/hooks/ui/useResizableSidebarWidth.d.ts +30 -0
- package/dist/core/hooks/ui/useResizableSidebarWidth.d.ts.map +1 -0
- package/dist/core/hooks/ui/useResizableSidebarWidth.js +75 -0
- package/dist/core/hooks/ui/useResizableSidebarWidth.js.map +1 -0
- package/dist/core/i18n/messages/en.json +5 -0
- package/dist/core/i18n/messages/es.json +6 -1
- package/dist/core/i18n/messages/fr.json +6 -1
- package/dist/core/store/Datasets/reducer.d.ts.map +1 -1
- package/dist/core/store/Datasets/reducer.js +0 -2
- package/dist/core/store/Datasets/reducer.js.map +1 -1
- package/dist/core/store/Files/reducer.js +0 -1
- package/dist/core/store/Files/reducer.js.map +1 -1
- package/dist/core/store/Menus/context.d.ts.map +1 -1
- package/dist/core/store/Menus/context.js +4 -1
- package/dist/core/store/Menus/context.js.map +1 -1
- package/dist/core/store/Menus/reducer.d.ts +20 -0
- 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/markerUtils.d.ts +10 -0
- package/dist/core/utils/markerUtils.d.ts.map +1 -1
- package/dist/core/utils/markerUtils.js +9 -1
- package/dist/core/utils/markerUtils.js.map +1 -1
- package/dist/core/utils/memcache.d.ts.map +1 -1
- package/dist/core/utils/memcache.js +0 -7
- package/dist/core/utils/memcache.js.map +1 -1
- package/package.json +4 -1
|
@@ -1,27 +1,38 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as LR from "lucide-react";
|
|
4
|
+
import { useSession } from "next-auth/react";
|
|
4
5
|
import { useTranslations } from "next-intl";
|
|
5
6
|
import * as React from "react";
|
|
6
7
|
import { toast } from "sonner";
|
|
7
|
-
import { useComment, useComments } from "../../../hooks/comments/comments";
|
|
8
|
+
import { useComment, useComments, useCreateComment, useDeleteComments } from "../../../hooks/comments/comments";
|
|
8
9
|
import { BuildingsContext, MenusContext, ToolsContext, usePermissions } from "../../../store";
|
|
9
10
|
import { CollapsibleSection } from "../CollapsibleSection";
|
|
10
11
|
import { SearchInput } from "../SearchInput";
|
|
11
12
|
import { CollapsibleCommentItem } from "./CollapsibleCommentItem";
|
|
12
13
|
function CommentsSection() {
|
|
13
|
-
var _a, _b;
|
|
14
|
+
var _a, _b, _c, _d;
|
|
14
15
|
const t = useTranslations("CommentsSection");
|
|
15
16
|
const { ability } = usePermissions();
|
|
16
17
|
const { dispatch: toolsDispatch } = React.useContext(ToolsContext);
|
|
17
18
|
const { state: menusState, dispatch: menusDispatch } = React.useContext(MenusContext);
|
|
18
|
-
const { currentViewer, commentsVisibleInViewer } = menusState.menus;
|
|
19
|
+
const { currentViewer, commentsVisibleInViewer, focusedCommentId, pendingCommentAction } = menusState.menus;
|
|
20
|
+
const focusComment = React.useCallback((commentId) => {
|
|
21
|
+
menusDispatch({ type: "SHOW_COMMENTS_IN_VIEWER", payload: { viewer: currentViewer } });
|
|
22
|
+
menusDispatch({ type: "SET_FOCUSED_COMMENT_ID", payload: { commentId } });
|
|
23
|
+
}, [menusDispatch, currentViewer]);
|
|
24
|
+
React.useEffect(() => {
|
|
25
|
+
if (pendingCommentAction) menusDispatch({ type: "CLEAR_PENDING_COMMENT_ACTION" });
|
|
26
|
+
}, [pendingCommentAction == null ? void 0 : pendingCommentAction.requestId, menusDispatch]);
|
|
19
27
|
const { comments } = useComments();
|
|
20
28
|
const { state: buildingsState } = React.useContext(BuildingsContext);
|
|
21
29
|
const buildingId = ((_b = (_a = buildingsState == null ? void 0 : buildingsState.buildings) == null ? void 0 : _a.building) == null ? void 0 : _b.id) || -1;
|
|
22
|
-
const
|
|
30
|
+
const user = (_c = useSession().data) == null ? void 0 : _c.user;
|
|
31
|
+
const { createComment } = useCreateComment();
|
|
32
|
+
const [commentToEdit, setCommentToEdit] = React.useState(null);
|
|
23
33
|
const [searchQuery, setSearchQuery] = React.useState("");
|
|
24
|
-
const {
|
|
34
|
+
const { updateComment } = useComment((_d = commentToEdit == null ? void 0 : commentToEdit.id) != null ? _d : null);
|
|
35
|
+
const { deleteComments } = useDeleteComments();
|
|
25
36
|
const handleAddComment = React.useCallback(() => {
|
|
26
37
|
if (!currentViewer) return;
|
|
27
38
|
toolsDispatch({
|
|
@@ -33,46 +44,72 @@ function CommentsSection() {
|
|
|
33
44
|
}, [toolsDispatch, currentViewer]);
|
|
34
45
|
const handleCommentAction = React.useCallback((action, id) => {
|
|
35
46
|
switch (action) {
|
|
36
|
-
case "
|
|
37
|
-
console.log(`View comment ${id}`);
|
|
38
|
-
break;
|
|
39
|
-
case "edit":
|
|
40
|
-
console.log(`Edit comment ${id}`);
|
|
41
|
-
break;
|
|
42
|
-
case "delete":
|
|
47
|
+
case "delete": {
|
|
43
48
|
toast.success(t("commentDeleted"));
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
case "reply":
|
|
47
|
-
console.log(`Reply to comment ${id}`);
|
|
49
|
+
const replyIds = comments.filter((c) => c.replyToId === id).map((c) => c.id);
|
|
50
|
+
void deleteComments({ ids: [id, ...replyIds] });
|
|
48
51
|
break;
|
|
52
|
+
}
|
|
53
|
+
// 'view', 'edit' and 'reply' are handled inline by CollapsibleCommentItem
|
|
54
|
+
// (edit -> onEdit, reply -> onReply)
|
|
49
55
|
default:
|
|
50
56
|
break;
|
|
51
57
|
}
|
|
52
|
-
}, [t]);
|
|
58
|
+
}, [t, comments, deleteComments]);
|
|
53
59
|
React.useEffect(() => {
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
if (commentToEdit !== null) {
|
|
61
|
+
void updateComment({ text: commentToEdit.text });
|
|
62
|
+
setCommentToEdit(null);
|
|
57
63
|
}
|
|
58
|
-
}, [
|
|
59
|
-
const
|
|
60
|
-
|
|
64
|
+
}, [commentToEdit, updateComment]);
|
|
65
|
+
const handleEdit = React.useCallback((id, text) => {
|
|
66
|
+
setCommentToEdit({ id, text });
|
|
61
67
|
}, []);
|
|
68
|
+
const handleReply = React.useCallback((parent, replyText) => {
|
|
69
|
+
var _a2;
|
|
70
|
+
if (!replyText.trim() || !user) return;
|
|
71
|
+
void createComment({
|
|
72
|
+
commentData: {
|
|
73
|
+
text: replyText,
|
|
74
|
+
authorId: Number(user.id),
|
|
75
|
+
organizationId: user.organizationId,
|
|
76
|
+
viewer: parent.viewer,
|
|
77
|
+
buildingId: parent.buildingId,
|
|
78
|
+
// Flat threading: a reply to a reply attaches to the top-level parent
|
|
79
|
+
replyToId: (_a2 = parent.replyToId) != null ? _a2 : parent.id,
|
|
80
|
+
visible: true
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}, [createComment, user]);
|
|
62
84
|
const handleFileUpload = React.useCallback((commentId, files) => {
|
|
63
|
-
console.log(`Upload files for comment ${commentId}:`, Array.from(files).map((f) => f.name));
|
|
64
85
|
}, []);
|
|
65
86
|
const currentComments = comments.filter((comment) => currentViewer === comment.viewer && (!comment.buildingId || comment.buildingId === buildingId));
|
|
87
|
+
const repliesByParent = React.useMemo(() => {
|
|
88
|
+
var _a2;
|
|
89
|
+
const map = /* @__PURE__ */ new Map();
|
|
90
|
+
for (const comment of currentComments) {
|
|
91
|
+
if (comment.replyToId != null) {
|
|
92
|
+
const list = (_a2 = map.get(comment.replyToId)) != null ? _a2 : [];
|
|
93
|
+
list.push(comment);
|
|
94
|
+
map.set(comment.replyToId, list);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return map;
|
|
98
|
+
}, [currentComments]);
|
|
99
|
+
const topLevelComments = React.useMemo(
|
|
100
|
+
() => currentComments.filter((comment) => comment.replyToId == null),
|
|
101
|
+
[currentComments]
|
|
102
|
+
);
|
|
66
103
|
const filteredComments = React.useMemo(() => {
|
|
67
|
-
if (!searchQuery.trim()) return
|
|
104
|
+
if (!searchQuery.trim()) return topLevelComments;
|
|
68
105
|
const query = searchQuery.toLowerCase();
|
|
69
|
-
return
|
|
106
|
+
return topLevelComments.filter(
|
|
70
107
|
(comment) => {
|
|
71
108
|
var _a2;
|
|
72
109
|
return (_a2 = comment.text) == null ? void 0 : _a2.toLowerCase().includes(query);
|
|
73
110
|
}
|
|
74
111
|
);
|
|
75
|
-
}, [
|
|
112
|
+
}, [topLevelComments, searchQuery]);
|
|
76
113
|
const commentsVisible = commentsVisibleInViewer.includes(currentViewer);
|
|
77
114
|
const toggleCommentsVisibility = React.useCallback(() => {
|
|
78
115
|
menusDispatch({
|
|
@@ -104,21 +141,28 @@ function CommentsSection() {
|
|
|
104
141
|
checked: commentsVisible,
|
|
105
142
|
onCheckedChange: toggleCommentsVisibility
|
|
106
143
|
},
|
|
107
|
-
children: /* @__PURE__ */ jsx("div", { className: "space-y-2 mx-2 pr-2", children: filteredComments.map((comment) =>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
144
|
+
children: /* @__PURE__ */ jsx("div", { className: "space-y-2 mx-2 pr-2 pb-2", children: filteredComments.map((comment) => {
|
|
145
|
+
var _a2;
|
|
146
|
+
return /* @__PURE__ */ jsx(
|
|
147
|
+
CollapsibleCommentItem,
|
|
148
|
+
{
|
|
149
|
+
comment,
|
|
150
|
+
onAction: handleCommentAction,
|
|
151
|
+
onReply: handleReply,
|
|
152
|
+
onEdit: handleEdit,
|
|
153
|
+
onFileUpload: handleFileUpload,
|
|
154
|
+
replies: (_a2 = repliesByParent.get(comment.id)) != null ? _a2 : [],
|
|
155
|
+
attachments: [],
|
|
156
|
+
isVisible: commentsVisible,
|
|
157
|
+
focused: focusedCommentId === comment.id,
|
|
158
|
+
onFocus: () => focusComment(comment.id),
|
|
159
|
+
pendingAction: pendingCommentAction != null ? pendingCommentAction : void 0,
|
|
160
|
+
onMouseEnter: () => menusDispatch({ type: "SET_CURRENT_COMMENT_ID", payload: { commentId: comment.id } }),
|
|
161
|
+
onMouseLeave: () => menusDispatch({ type: "SET_CURRENT_COMMENT_ID", payload: { commentId: null } })
|
|
162
|
+
},
|
|
163
|
+
comment.id
|
|
164
|
+
);
|
|
165
|
+
}) })
|
|
122
166
|
}
|
|
123
167
|
)
|
|
124
168
|
] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/core/components/ui/Comments/CommentsSection.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 { useTranslations } from 'next-intl'\r\nimport * as React from 'react'\r\nimport { toast } from 'sonner'\r\n\r\nimport { useComment, useComments } from '../../../hooks/comments/comments';\r\nimport { BuildingsContext, MenusContext, ToolsContext, usePermissions } from '../../../store'\r\nimport { CollapsibleSection } from '../CollapsibleSection'\r\nimport { SearchInput } from '../SearchInput'\r\n\r\n\r\nimport { CollapsibleCommentItem } from './CollapsibleCommentItem'\r\n\r\n\r\n\r\ntype CommentAction = 'view' | 'edit' | 'delete' | 'reply'\r\n\r\nexport function CommentsSection() {\r\n // Translation\r\n const t = useTranslations('CommentsSection')\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n const { dispatch: toolsDispatch } = React.useContext(ToolsContext)\r\n const { state: menusState, dispatch: menusDispatch } = React.useContext(MenusContext)\r\n const { currentViewer, commentsVisibleInViewer } = menusState.menus\r\n\r\n const {comments} = useComments()\r\n const {state: buildingsState} = React.useContext(BuildingsContext)\r\n const buildingId = buildingsState?.buildings?.building?.id || -1\r\n\r\n const [commentToDelete, setCommentToDelete] = React.useState<number | null>(null)\r\n const [searchQuery, setSearchQuery] = React.useState('')\r\n const { deleteComment } = useComment(commentToDelete)\r\n\r\n const handleAddComment = React.useCallback(() => {\r\n if (!currentViewer) return\r\n toolsDispatch({\r\n type: 'SET-TOOL',\r\n payload: {\r\n currentToolId:\r\n currentViewer === 'bim'\r\n ? 'bim-add-comment'\r\n : currentViewer === 'map'\r\n ? 'map-add-comment'\r\n : undefined,\r\n },\r\n })\r\n }, [toolsDispatch, currentViewer])\r\n\r\n const handleCommentAction = React.useCallback((action: CommentAction, id: number) => {\r\n switch (action) {\r\n case 'view':\r\n console.log(`View comment ${id}`)\r\n break\r\n case 'edit':\r\n console.log(`Edit comment ${id}`)\r\n break\r\n case 'delete':\r\n toast.success(t('commentDeleted'))\r\n setCommentToDelete(id)\r\n break\r\n case 'reply':\r\n console.log(`Reply to comment ${id}`)\r\n break\r\n default:\r\n break\r\n }\r\n }, [t])\r\n\r\n // Trigger deletion when commentToDelete changes\r\n React.useEffect(() => {\r\n if (commentToDelete !== null) {\r\n deleteComment()\r\n setCommentToDelete(null)\r\n }\r\n }, [commentToDelete, deleteComment])\r\n\r\n const handleReply = React.useCallback((id: number, replyText: string) => {\r\n console.log(`Reply to ${id}: ${replyText}`)\r\n // TODO: Implement reply functionality\r\n }, [])\r\n\r\n const handleFileUpload = React.useCallback((commentId: number, files: FileList) => {\r\n console.log(`Upload files for comment ${commentId}:`, Array.from(files).map(f => f.name))\r\n // TODO: Implement file upload functionality\r\n }, [])\r\n\r\n const currentComments = comments.filter((comment) => currentViewer === comment.viewer && (!comment.buildingId || comment.buildingId === buildingId))\r\n\r\n // Filter comments based on search query\r\n const filteredComments = React.useMemo(() => {\r\n if (!searchQuery.trim()) return currentComments\r\n\r\n const query = searchQuery.toLowerCase()\r\n return currentComments.filter((comment) =>\r\n comment.text?.toLowerCase().includes(query)\r\n )\r\n }, [currentComments, searchQuery])\r\n\r\n const commentsVisible = commentsVisibleInViewer.includes(currentViewer)\r\n\r\n const toggleCommentsVisibility = React.useCallback(() => {\r\n menusDispatch({\r\n type: commentsVisible ? 'HIDE_COMMENTS_IN_VIEWER' : 'SHOW_COMMENTS_IN_VIEWER',\r\n payload: {\r\n viewer: currentViewer,\r\n },\r\n })\r\n }, [currentViewer, commentsVisible, menusDispatch])\r\n\r\n return (\r\n <div className=\"flex-1 flex flex-col space-y-4 overflow-hidden\">\r\n <div className=\"px-4\">\r\n <SearchInput\r\n placeholder={t('searchComments')}\r\n value={searchQuery}\r\n onChange={e => setSearchQuery(e.target.value)}\r\n />\r\n </div>\r\n <CollapsibleSection\r\n title={t('commentsTitle')}\r\n icon={LR.MessageCircle}\r\n className=\"overflow-y-auto\"\r\n itemCount={filteredComments.length}\r\n onAddItem={handleAddComment}\r\n addItemTitle={t('addCommentTitle')}\r\n switchVariant={{\r\n checked: commentsVisible,\r\n onCheckedChange: toggleCommentsVisibility,\r\n }}\r\n >\r\n <div className=\"space-y-2 mx-2 pr-2\">\r\n {filteredComments.map((comment) => (\r\n <CollapsibleCommentItem\r\n key={comment.id}\r\n comment={comment}\r\n onAction={handleCommentAction}\r\n onReply={handleReply}\r\n onFileUpload={handleFileUpload}\r\n replies={[]}\r\n attachments={[]}\r\n isVisible={commentsVisible}\r\n onMouseEnter={() => menusDispatch({ type: 'SET_CURRENT_COMMENT_ID', payload: { commentId: comment.id } })}\r\n onMouseLeave={() => menusDispatch({ type: 'SET_CURRENT_COMMENT_ID', payload: { commentId: null } })}\r\n />\r\n ))}\r\n </div>\r\n </CollapsibleSection>\r\n </div>\r\n )\r\n}\r\n"],"mappings":";AAqHI,SAEI,KAFJ;AAhHJ,YAAY,QAAQ;AACpB,SAAS,uBAAuB;AAChC,YAAY,WAAW;AACvB,SAAS,aAAa;AAEtB,SAAS,YAAY,mBAAmB;AACxC,SAAS,kBAAkB,cAAc,cAAc,sBAAsB;AAC7E,SAAS,0BAA0B;AACnC,SAAS,mBAAmB;AAG5B,SAAS,8BAA8B;AAMhC,SAAS,kBAAkB;AAtBlC;AAwBE,QAAM,IAAI,gBAAgB,iBAAiB;AAE3C,QAAM,EAAE,QAAQ,IAAI,eAAe;AAEnC,QAAM,EAAE,UAAU,cAAc,IAAI,MAAM,WAAW,YAAY;AACjE,QAAM,EAAE,OAAO,YAAY,UAAU,cAAc,IAAI,MAAM,WAAW,YAAY;AACpF,QAAM,EAAE,eAAe,wBAAwB,IAAI,WAAW;AAE9D,QAAM,EAAC,SAAQ,IAAI,YAAY;AAC/B,QAAM,EAAC,OAAO,eAAc,IAAI,MAAM,WAAW,gBAAgB;AACjE,QAAM,eAAa,4DAAgB,cAAhB,mBAA2B,aAA3B,mBAAqC,OAAM;AAE9D,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAwB,IAAI;AAChF,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,EAAE;AACvD,QAAM,EAAE,cAAc,IAAI,WAAW,eAAe;AAEpD,QAAM,mBAAmB,MAAM,YAAY,MAAM;AAC/C,QAAI,CAAC,cAAe;AAClB,kBAAc;AAAA,MACZ,MAAM;AAAA,MACN,SAAS;AAAA,QACP,eACE,kBAAkB,QACd,oBACA,kBAAkB,QAChB,oBACA;AAAA,MACV;AAAA,IACF,CAAC;AAAA,EACL,GAAG,CAAC,eAAe,aAAa,CAAC;AAEjC,QAAM,sBAAsB,MAAM,YAAY,CAAC,QAAuB,OAAe;AACnF,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,gBAAQ,IAAI,gBAAgB,EAAE,EAAE;AAChC;AAAA,MACF,KAAK;AACH,gBAAQ,IAAI,gBAAgB,EAAE,EAAE;AAChC;AAAA,MACF,KAAK;AACC,cAAM,QAAQ,EAAE,gBAAgB,CAAC;AACjC,2BAAmB,EAAE;AACzB;AAAA,MACF,KAAK;AACH,gBAAQ,IAAI,oBAAoB,EAAE,EAAE;AACpC;AAAA,MACF;AACE;AAAA,IACJ;AAAA,EACF,GAAG,CAAC,CAAC,CAAC;AAGN,QAAM,UAAU,MAAM;AACpB,QAAI,oBAAoB,MAAM;AAC5B,oBAAc;AACd,yBAAmB,IAAI;AAAA,IACzB;AAAA,EACF,GAAG,CAAC,iBAAiB,aAAa,CAAC;AAEnC,QAAM,cAAc,MAAM,YAAY,CAAC,IAAY,cAAsB;AACvE,YAAQ,IAAI,YAAY,EAAE,KAAK,SAAS,EAAE;AAAA,EAE5C,GAAG,CAAC,CAAC;AAEL,QAAM,mBAAmB,MAAM,YAAY,CAAC,WAAmB,UAAoB;AACjF,YAAQ,IAAI,4BAA4B,SAAS,KAAK,MAAM,KAAK,KAAK,EAAE,IAAI,OAAK,EAAE,IAAI,CAAC;AAAA,EAE1F,GAAG,CAAC,CAAC;AAEL,QAAM,kBAAkB,SAAS,OAAO,CAAC,YAAY,kBAAkB,QAAQ,WAAW,CAAC,QAAQ,cAAc,QAAQ,eAAe,WAAW;AAGnJ,QAAM,mBAAmB,MAAM,QAAQ,MAAM;AAC3C,QAAI,CAAC,YAAY,KAAK,EAAG,QAAO;AAEhC,UAAM,QAAQ,YAAY,YAAY;AACtC,WAAO,gBAAgB;AAAA,MAAO,CAAC,YAAS;AApG5C,YAAAA;AAqGM,gBAAAA,MAAA,QAAQ,SAAR,gBAAAA,IAAc,cAAc,SAAS;AAAA;AAAA,IACvC;AAAA,EACF,GAAG,CAAC,iBAAiB,WAAW,CAAC;AAEjC,QAAM,kBAAkB,wBAAwB,SAAS,aAAa;AAEtE,QAAM,2BAA2B,MAAM,YAAY,MAAM;AACrD,kBAAc;AAAA,MACZ,MAAM,kBAAkB,4BAA4B;AAAA,MACpD,SAAS;AAAA,QACP,QAAQ;AAAA,MACV;AAAA,IACF,CAAC;AAAA,EACL,GAAG,CAAC,eAAe,iBAAiB,aAAa,CAAC;AAElD,SACE,qBAAC,SAAI,WAAU,kDACb;AAAA,wBAAC,SAAI,WAAU,QACb;AAAA,MAAC;AAAA;AAAA,QACC,aAAa,EAAE,gBAAgB;AAAA,QAC/B,OAAO;AAAA,QACP,UAAU,OAAK,eAAe,EAAE,OAAO,KAAK;AAAA;AAAA,IAC9C,GACF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,eAAe;AAAA,QACxB,MAAM,GAAG;AAAA,QACT,WAAU;AAAA,QACV,WAAW,iBAAiB;AAAA,QAC5B,WAAW;AAAA,QACX,cAAc,EAAE,iBAAiB;AAAA,QACjC,eAAe;AAAA,UACb,SAAS;AAAA,UACT,iBAAiB;AAAA,QACnB;AAAA,QAEA,8BAAC,SAAI,WAAU,uBACZ,2BAAiB,IAAI,CAAC,YACrB;AAAA,UAAC;AAAA;AAAA,YAEC;AAAA,YACA,UAAU;AAAA,YACV,SAAS;AAAA,YACT,cAAc;AAAA,YACd,SAAS,CAAC;AAAA,YACV,aAAa,CAAC;AAAA,YACd,WAAW;AAAA,YACX,cAAc,MAAM,cAAc,EAAE,MAAM,0BAA0B,SAAS,EAAE,WAAW,QAAQ,GAAG,EAAE,CAAC;AAAA,YACxG,cAAc,MAAM,cAAc,EAAE,MAAM,0BAA0B,SAAS,EAAE,WAAW,KAAK,EAAE,CAAC;AAAA;AAAA,UAT7F,QAAQ;AAAA,QAUf,CACD,GACH;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;","names":["_a"]}
|
|
1
|
+
{"version":3,"sources":["../../../../../src/core/components/ui/Comments/CommentsSection.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 { useSession } from 'next-auth/react'\r\nimport { useTranslations } from 'next-intl'\r\nimport * as React from 'react'\r\nimport { toast } from 'sonner'\r\n\r\nimport { useComment, useComments, useCreateComment, useDeleteComments } from '../../../hooks/comments/comments';\r\nimport { BuildingsContext, MenusContext, ToolsContext, usePermissions } from '../../../store'\r\nimport { CollapsibleSection } from '../CollapsibleSection'\r\nimport { SearchInput } from '../SearchInput'\r\n\r\n\r\nimport { CollapsibleCommentItem } from './CollapsibleCommentItem'\r\n\r\nimport type { Comment } from '../../../types/dbTypes'\r\n\r\n\r\n\r\ntype CommentAction = 'view' | 'edit' | 'delete' | 'reply'\r\n\r\nexport function CommentsSection() {\r\n // Translation\r\n const t = useTranslations('CommentsSection')\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n const { dispatch: toolsDispatch } = React.useContext(ToolsContext)\r\n const { state: menusState, dispatch: menusDispatch } = React.useContext(MenusContext)\r\n const { currentViewer, commentsVisibleInViewer, focusedCommentId, pendingCommentAction } = menusState.menus\r\n\r\n const focusComment = React.useCallback((commentId: number) => {\r\n // Ensure comments are visible in the active viewer so the zoom target is actually rendered.\r\n menusDispatch({ type: 'SHOW_COMMENTS_IN_VIEWER', payload: { viewer: currentViewer } })\r\n menusDispatch({ type: 'SET_FOCUSED_COMMENT_ID', payload: { commentId } })\r\n }, [menusDispatch, currentViewer])\r\n\r\n // A pending action (open editor/reply from a marker) is one-shot: child items consume it\r\n // on their mount/update effect (children run before this parent effect), then we clear it so\r\n // it does not re-fire when the sidebar remounts.\r\n React.useEffect(() => {\r\n if (pendingCommentAction) menusDispatch({ type: 'CLEAR_PENDING_COMMENT_ACTION' })\r\n }, [pendingCommentAction?.requestId, menusDispatch])\r\n\r\n const {comments} = useComments()\r\n const {state: buildingsState} = React.useContext(BuildingsContext)\r\n const buildingId = buildingsState?.buildings?.building?.id || -1\r\n\r\n const user = useSession().data?.user\r\n const { createComment } = useCreateComment()\r\n\r\n const [commentToEdit, setCommentToEdit] = React.useState<{ id: number; text: string } | null>(null)\r\n const [searchQuery, setSearchQuery] = React.useState('')\r\n const { updateComment } = useComment(commentToEdit?.id ?? null)\r\n const { deleteComments } = useDeleteComments()\r\n\r\n const handleAddComment = React.useCallback(() => {\r\n if (!currentViewer) return\r\n toolsDispatch({\r\n type: 'SET-TOOL',\r\n payload: {\r\n currentToolId:\r\n currentViewer === 'bim'\r\n ? 'bim-add-comment'\r\n : currentViewer === 'map'\r\n ? 'map-add-comment'\r\n : undefined,\r\n },\r\n })\r\n }, [toolsDispatch, currentViewer])\r\n\r\n const handleCommentAction = React.useCallback((action: CommentAction, id: number) => {\r\n switch (action) {\r\n case 'delete': {\r\n toast.success(t('commentDeleted'))\r\n // Cascade: delete the comment together with its replies (DB does not cascade).\r\n const replyIds = comments.filter((c) => c.replyToId === id).map((c) => c.id)\r\n void deleteComments({ ids: [id, ...replyIds] })\r\n break\r\n }\r\n // 'view', 'edit' and 'reply' are handled inline by CollapsibleCommentItem\r\n // (edit -> onEdit, reply -> onReply)\r\n default:\r\n break\r\n }\r\n }, [t, comments, deleteComments])\r\n\r\n // Trigger edit when commentToEdit changes\r\n React.useEffect(() => {\r\n if (commentToEdit !== null) {\r\n void updateComment({ text: commentToEdit.text })\r\n setCommentToEdit(null)\r\n }\r\n }, [commentToEdit, updateComment])\r\n\r\n const handleEdit = React.useCallback((id: number, text: string) => {\r\n setCommentToEdit({ id, text })\r\n }, [])\r\n\r\n const handleReply = React.useCallback((parent: Comment, replyText: string) => {\r\n if (!replyText.trim() || !user) return\r\n void createComment({\r\n commentData: {\r\n text: replyText,\r\n authorId: Number(user.id),\r\n organizationId: user.organizationId,\r\n viewer: parent.viewer,\r\n buildingId: parent.buildingId,\r\n // Flat threading: a reply to a reply attaches to the top-level parent\r\n replyToId: parent.replyToId ?? parent.id,\r\n visible: true,\r\n },\r\n })\r\n }, [createComment, user])\r\n\r\n const handleFileUpload = React.useCallback((commentId: number, files: FileList) => {\r\n // ⚠️ Comment file upload not implemented (files are discarded)\r\n }, [])\r\n\r\n const currentComments = comments.filter((comment) => currentViewer === comment.viewer && (!comment.buildingId || comment.buildingId === buildingId))\r\n\r\n // Group replies under their parent (flat, single level)\r\n const repliesByParent = React.useMemo(() => {\r\n const map = new Map<number, Comment[]>()\r\n for (const comment of currentComments) {\r\n if (comment.replyToId != null) {\r\n const list = map.get(comment.replyToId) ?? []\r\n list.push(comment)\r\n map.set(comment.replyToId, list)\r\n }\r\n }\r\n return map\r\n }, [currentComments])\r\n\r\n // Top-level comments only (replies are nested under their parent)\r\n const topLevelComments = React.useMemo(\r\n () => currentComments.filter((comment) => comment.replyToId == null),\r\n [currentComments]\r\n )\r\n\r\n // Filter comments based on search query\r\n const filteredComments = React.useMemo(() => {\r\n if (!searchQuery.trim()) return topLevelComments\r\n\r\n const query = searchQuery.toLowerCase()\r\n return topLevelComments.filter((comment) =>\r\n comment.text?.toLowerCase().includes(query)\r\n )\r\n }, [topLevelComments, searchQuery])\r\n\r\n const commentsVisible = commentsVisibleInViewer.includes(currentViewer)\r\n\r\n const toggleCommentsVisibility = React.useCallback(() => {\r\n menusDispatch({\r\n type: commentsVisible ? 'HIDE_COMMENTS_IN_VIEWER' : 'SHOW_COMMENTS_IN_VIEWER',\r\n payload: {\r\n viewer: currentViewer,\r\n },\r\n })\r\n }, [currentViewer, commentsVisible, menusDispatch])\r\n\r\n return (\r\n <div className=\"flex-1 flex flex-col space-y-4 overflow-hidden\">\r\n <div className=\"px-4\">\r\n <SearchInput\r\n placeholder={t('searchComments')}\r\n value={searchQuery}\r\n onChange={e => setSearchQuery(e.target.value)}\r\n />\r\n </div>\r\n <CollapsibleSection\r\n title={t('commentsTitle')}\r\n icon={LR.MessageCircle}\r\n className=\"overflow-y-auto\"\r\n itemCount={filteredComments.length}\r\n onAddItem={handleAddComment}\r\n addItemTitle={t('addCommentTitle')}\r\n switchVariant={{\r\n checked: commentsVisible,\r\n onCheckedChange: toggleCommentsVisibility,\r\n }}\r\n >\r\n <div className=\"space-y-2 mx-2 pr-2 pb-2\">\r\n {filteredComments.map((comment) => (\r\n <CollapsibleCommentItem\r\n key={comment.id}\r\n comment={comment}\r\n onAction={handleCommentAction}\r\n onReply={handleReply}\r\n onEdit={handleEdit}\r\n onFileUpload={handleFileUpload}\r\n replies={repliesByParent.get(comment.id) ?? []}\r\n attachments={[]}\r\n isVisible={commentsVisible}\r\n focused={focusedCommentId === comment.id}\r\n onFocus={() => focusComment(comment.id)}\r\n pendingAction={pendingCommentAction ?? undefined}\r\n onMouseEnter={() => menusDispatch({ type: 'SET_CURRENT_COMMENT_ID', payload: { commentId: comment.id } })}\r\n onMouseLeave={() => menusDispatch({ type: 'SET_CURRENT_COMMENT_ID', payload: { commentId: null } })}\r\n />\r\n ))}\r\n </div>\r\n </CollapsibleSection>\r\n </div>\r\n )\r\n}\r\n"],"mappings":"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","names":["_a"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commentUtils.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/ui/Comments/commentUtils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAErD,KAAK,kBAAkB,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,GACrE,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,CAAC,GACxD;IAAE,GAAG,CAAC,EAAE,IAAI,CAAA;CAAE,CAAA;AAGlB,wBAAgB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"commentUtils.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/ui/Comments/commentUtils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAErD,KAAK,kBAAkB,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,GACrE,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,CAAC,GACxD;IAAE,GAAG,CAAC,EAAE,IAAI,CAAA;CAAE,CAAA;AAGlB,wBAAgB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC,CAmBzE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/core/components/ui/Comments/commentUtils.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport type { Comment } from '../../../types/dbTypes'\r\n\r\ntype BuildCommentParams = Omit<Comment, 'id' | 'createdAt' | 'updatedAt'>\r\n & Partial<Pick<Comment, 'id' | 'createdAt' | 'updatedAt'>>\r\n & { now?: Date }\r\n\r\n\r\nexport function buildComment(params: BuildCommentParams): Partial<Comment> {\r\n const now = params.now ?? new Date()\r\n\r\n return {\r\n visible: true,\r\n authorId: params.authorId,\r\n organizationId: params.organizationId,\r\n image: params.image,\r\n text: params.text,\r\n longitude: params.longitude,\r\n latitude: params.latitude,\r\n elevation: params.elevation,\r\n x: params.x,\r\n y: params.y,\r\n z: params.z,\r\n viewer: params.viewer,\r\n buildingId: params.buildingId\r\n }\r\n}\r\n"],"mappings":"AAUO,SAAS,aAAa,QAA8C;AAV3E;AAWE,QAAM,OAAM,YAAO,QAAP,YAAc,oBAAI,KAAK;AAEnC,SAAO;AAAA,IACL,SAAS;AAAA,IACT,UAAU,OAAO;AAAA,IACjB,gBAAgB,OAAO;AAAA,IACvB,OAAO,OAAO;AAAA,IACd,MAAM,OAAO;AAAA,IACb,WAAW,OAAO;AAAA,IAClB,UAAU,OAAO;AAAA,IACjB,WAAW,OAAO;AAAA,IAClB,GAAG,OAAO;AAAA,IACV,GAAG,OAAO;AAAA,IACV,GAAG,OAAO;AAAA,IACV,QAAQ,OAAO;AAAA,IACf,YAAY,OAAO;AAAA,
|
|
1
|
+
{"version":3,"sources":["../../../../../src/core/components/ui/Comments/commentUtils.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport type { Comment } from '../../../types/dbTypes'\r\n\r\ntype BuildCommentParams = Omit<Comment, 'id' | 'createdAt' | 'updatedAt'>\r\n & Partial<Pick<Comment, 'id' | 'createdAt' | 'updatedAt'>>\r\n & { now?: Date }\r\n\r\n\r\nexport function buildComment(params: BuildCommentParams): Partial<Comment> {\r\n const now = params.now ?? new Date()\r\n\r\n return {\r\n visible: true,\r\n authorId: params.authorId,\r\n organizationId: params.organizationId,\r\n image: params.image,\r\n text: params.text,\r\n longitude: params.longitude,\r\n latitude: params.latitude,\r\n elevation: params.elevation,\r\n x: params.x,\r\n y: params.y,\r\n z: params.z,\r\n viewer: params.viewer,\r\n buildingId: params.buildingId,\r\n replyToId: params.replyToId\r\n }\r\n}\r\n"],"mappings":"AAUO,SAAS,aAAa,QAA8C;AAV3E;AAWE,QAAM,OAAM,YAAO,QAAP,YAAc,oBAAI,KAAK;AAEnC,SAAO;AAAA,IACL,SAAS;AAAA,IACT,UAAU,OAAO;AAAA,IACjB,gBAAgB,OAAO;AAAA,IACvB,OAAO,OAAO;AAAA,IACd,MAAM,OAAO;AAAA,IACb,WAAW,OAAO;AAAA,IAClB,UAAU,OAAO;AAAA,IACjB,WAAW,OAAO;AAAA,IAClB,GAAG,OAAO;AAAA,IACV,GAAG,OAAO;AAAA,IACV,GAAG,OAAO;AAAA,IACV,QAAQ,OAAO;AAAA,IACf,YAAY,OAAO;AAAA,IACnB,WAAW,OAAO;AAAA,EACpB;AACF;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFileUploadWithProgress.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/ui/FilesManager/src/useFileUploadWithProgress.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,8BAA8B;IAC7C,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,eAAe,CAAC,EAAE,MAAM,IAAI,CAAA;IAC5B,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CACvC;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,OAAO,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,wBAAgB,yBAAyB,CAAC,EACxC,iBAAuB,EACvB,eAAe,EACf,aAAa,EACd,GAAE,8BAAmC;;6BASoB,IAAI;;
|
|
1
|
+
{"version":3,"file":"useFileUploadWithProgress.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/ui/FilesManager/src/useFileUploadWithProgress.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,8BAA8B;IAC7C,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,eAAe,CAAC,EAAE,MAAM,IAAI,CAAA;IAC5B,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CACvC;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,OAAO,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,wBAAgB,yBAAyB,CAAC,EACxC,iBAAuB,EACvB,eAAe,EACf,aAAa,EACd,GAAE,8BAAmC;;6BASoB,IAAI;;EAkG7D"}
|
|
@@ -54,7 +54,6 @@ function useFileUploadWithProgress({
|
|
|
54
54
|
if (!metadataResponse.ok) {
|
|
55
55
|
throw new Error(`Failed to upload file: ${metadataResponse.statusText}`);
|
|
56
56
|
}
|
|
57
|
-
console.log("File successfully uploaded to API");
|
|
58
57
|
mutate(["files"]);
|
|
59
58
|
onUploadSuccess == null ? void 0 : onUploadSuccess();
|
|
60
59
|
} catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../src/core/components/ui/FilesManager/src/useFileUploadWithProgress.ts"],"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 { mutate } from 'swr'\r\n\r\nimport { uploadFileWithProgress } from '../../../viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS'\r\n\r\nfunction getFileExtension(file: File): string {\r\n const parts = file.name.split('.')\r\n if (parts.length <= 1) return ''\r\n return parts.pop()!.toLowerCase()\r\n}\r\n\r\nexport interface UseFileUploadWithProgressProps {\r\n acceptedFileTypes?: string\r\n onUploadSuccess?: () => void\r\n onUploadError?: (error: Error) => void\r\n}\r\n\r\nexport interface UploadState {\r\n uploading: boolean\r\n progress: number\r\n}\r\n\r\nexport function useFileUploadWithProgress({\r\n acceptedFileTypes = '*',\r\n onUploadSuccess,\r\n onUploadError\r\n}: UseFileUploadWithProgressProps = {}) {\r\n\r\n const [uploadState, setUploadState] = React.useState<UploadState>({\r\n uploading: false,\r\n progress: 0\r\n })\r\n\r\n const inputRef = React.useRef<HTMLInputElement | null>(null)\r\n\r\n const handleFileUpload = React.useCallback(async (file: File) => {\r\n setUploadState({ uploading: true, progress: 0 })\r\n\r\n try {\r\n // Generate a unique fileID for minio\r\n const fileId = crypto.randomUUID()\r\n\r\n // Get presigned upload URL for bucket\r\n const response = await fetch(`/api/presigned-url-upload?asset=${fileId}`)\r\n if (!response.ok) {\r\n throw new Error('Failed to fetch presigned URL')\r\n }\r\n const { presignedUrl } = await response.json()\r\n\r\n // Upload file to bucket with progress tracking\r\n await uploadFileWithProgress(presignedUrl, file,\r\n (progress: number) => {\r\n setUploadState({ uploading: true, progress })\r\n },\r\n )\r\n\r\n // Create a new file object with the unique id\r\n const newFile = {\r\n type: 'system',\r\n name: file.name,\r\n url: '',\r\n mimeType: file.type,\r\n extension: getFileExtension(file),\r\n sizeBytes: file.size,\r\n uploadedAt: new Date(),\r\n description: '',\r\n assetId: fileId,\r\n }\r\n\r\n // Upload metadata to API\r\n const metadataResponse = await fetch('/api/files/create', {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify(newFile),\r\n })\r\n\r\n if (!metadataResponse.ok) {\r\n throw new Error(`Failed to upload file: ${metadataResponse.statusText}`)\r\n }\r\n\r\n
|
|
1
|
+
{"version":3,"sources":["../../../../../../src/core/components/ui/FilesManager/src/useFileUploadWithProgress.ts"],"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 { mutate } from 'swr'\r\n\r\nimport { uploadFileWithProgress } from '../../../viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS'\r\n\r\nfunction getFileExtension(file: File): string {\r\n const parts = file.name.split('.')\r\n if (parts.length <= 1) return ''\r\n return parts.pop()!.toLowerCase()\r\n}\r\n\r\nexport interface UseFileUploadWithProgressProps {\r\n acceptedFileTypes?: string\r\n onUploadSuccess?: () => void\r\n onUploadError?: (error: Error) => void\r\n}\r\n\r\nexport interface UploadState {\r\n uploading: boolean\r\n progress: number\r\n}\r\n\r\nexport function useFileUploadWithProgress({\r\n acceptedFileTypes = '*',\r\n onUploadSuccess,\r\n onUploadError\r\n}: UseFileUploadWithProgressProps = {}) {\r\n\r\n const [uploadState, setUploadState] = React.useState<UploadState>({\r\n uploading: false,\r\n progress: 0\r\n })\r\n\r\n const inputRef = React.useRef<HTMLInputElement | null>(null)\r\n\r\n const handleFileUpload = React.useCallback(async (file: File) => {\r\n setUploadState({ uploading: true, progress: 0 })\r\n\r\n try {\r\n // Generate a unique fileID for minio\r\n const fileId = crypto.randomUUID()\r\n\r\n // Get presigned upload URL for bucket\r\n const response = await fetch(`/api/presigned-url-upload?asset=${fileId}`)\r\n if (!response.ok) {\r\n throw new Error('Failed to fetch presigned URL')\r\n }\r\n const { presignedUrl } = await response.json()\r\n\r\n // Upload file to bucket with progress tracking\r\n await uploadFileWithProgress(presignedUrl, file,\r\n (progress: number) => {\r\n setUploadState({ uploading: true, progress })\r\n },\r\n )\r\n\r\n // Create a new file object with the unique id\r\n const newFile = {\r\n type: 'system',\r\n name: file.name,\r\n url: '',\r\n mimeType: file.type,\r\n extension: getFileExtension(file),\r\n sizeBytes: file.size,\r\n uploadedAt: new Date(),\r\n description: '',\r\n assetId: fileId,\r\n }\r\n\r\n // Upload metadata to API\r\n const metadataResponse = await fetch('/api/files/create', {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify(newFile),\r\n })\r\n\r\n if (!metadataResponse.ok) {\r\n throw new Error(`Failed to upload file: ${metadataResponse.statusText}`)\r\n }\r\n\r\n // Revalidate files list\r\n mutate(['files'])\r\n\r\n onUploadSuccess?.()\r\n } catch (error) {\r\n console.error('Error uploading file:', error)\r\n onUploadError?.(error as Error)\r\n } finally {\r\n setUploadState({ uploading: false, progress: 0 })\r\n }\r\n }, [onUploadSuccess, onUploadError])\r\n\r\n const handleAddFile = React.useCallback(() => {\r\n if (uploadState.uploading) return\r\n\r\n // Create or use existing file input element\r\n if (!inputRef.current) {\r\n const input = document.createElement('input')\r\n input.type = 'file'\r\n input.accept = acceptedFileTypes\r\n input.style.display = 'none'\r\n\r\n input.addEventListener('change', async (e) => {\r\n const file = (e.target as HTMLInputElement).files?.[0]\r\n if (!file) return\r\n await handleFileUpload(file)\r\n })\r\n\r\n inputRef.current = input\r\n document.body.appendChild(input)\r\n } else {\r\n inputRef.current.accept = acceptedFileTypes\r\n }\r\n\r\n inputRef.current.click()\r\n }, [acceptedFileTypes, handleFileUpload, uploadState.uploading])\r\n\r\n // Cleanup on unmount\r\n React.useEffect(() => {\r\n return () => {\r\n if (inputRef.current && document.body.contains(inputRef.current)) {\r\n document.body.removeChild(inputRef.current)\r\n }\r\n }\r\n }, [])\r\n\r\n return {\r\n handleAddFile,\r\n handleFileUpload,\r\n uploadState\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Container.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/ui/InfoSidebar/Container.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAQ9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAE1D,UAAU,gBAAgB;IACxB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,YAAY,CAAC,EAAE,YAAY,CAAA;CAC5B;AAED,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,WAAW,EACX,YAAY,GACb,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"Container.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/ui/InfoSidebar/Container.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAQ9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAE1D,UAAU,gBAAgB;IACxB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,YAAY,CAAC,EAAE,YAAY,CAAA;CAC5B;AAED,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,WAAW,EACX,YAAY,GACb,EAAE,gBAAgB,qBAwBlB"}
|
|
@@ -11,18 +11,22 @@ function InfoSidebarContainer({
|
|
|
11
11
|
const { state: buildingState } = React.useContext(BuildingsContext);
|
|
12
12
|
const { building: currentBuilding } = buildingState.buildings;
|
|
13
13
|
const [loadingBuildingInfo] = React.useState(false);
|
|
14
|
-
return
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
return (
|
|
15
|
+
// Fills the InfoSidebar overlay wrapper (Sidebar.tsx), which owns the resizable width.
|
|
16
|
+
// pr-2 keeps a consistent right gutter so content never sits flush against the edge/handle.
|
|
17
|
+
/* @__PURE__ */ jsxs("div", { className: "w-full flex flex-col h-full min-h-0 bg-background border-r border-border pr-2", children: [
|
|
18
|
+
/* @__PURE__ */ jsx(
|
|
19
|
+
Header,
|
|
20
|
+
{
|
|
21
|
+
currentBuilding,
|
|
22
|
+
loadingBuildingInfo,
|
|
23
|
+
organization
|
|
24
|
+
}
|
|
25
|
+
),
|
|
26
|
+
tabSelector,
|
|
27
|
+
children
|
|
28
|
+
] })
|
|
29
|
+
);
|
|
26
30
|
}
|
|
27
31
|
export {
|
|
28
32
|
InfoSidebarContainer
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/core/components/ui/InfoSidebar/Container.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\n\r\n// Icons\r\nimport { BuildingsContext } from '../../../store'\r\n\r\n// New structured components\r\nimport { Header } from './Header'\r\n\r\nimport type { Organization } from '../../../types/dbTypes'\r\n\r\ninterface InfoSidebarProps {\r\n children?: React.ReactNode\r\n tabSelector?: React.ReactNode\r\n organization?: Organization\r\n}\r\n\r\nexport function InfoSidebarContainer({\r\n children,\r\n tabSelector,\r\n organization,\r\n}: InfoSidebarProps) {\r\n const { state: buildingState } = React.useContext(BuildingsContext);\r\n const { building: currentBuilding } = buildingState.buildings;\r\n\r\n // State\r\n const [loadingBuildingInfo] = React.useState(false)\r\n\r\n return (\r\n <div className=\"w-full
|
|
1
|
+
{"version":3,"sources":["../../../../../src/core/components/ui/InfoSidebar/Container.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\n\r\n// Icons\r\nimport { BuildingsContext } from '../../../store'\r\n\r\n// New structured components\r\nimport { Header } from './Header'\r\n\r\nimport type { Organization } from '../../../types/dbTypes'\r\n\r\ninterface InfoSidebarProps {\r\n children?: React.ReactNode\r\n tabSelector?: React.ReactNode\r\n organization?: Organization\r\n}\r\n\r\nexport function InfoSidebarContainer({\r\n children,\r\n tabSelector,\r\n organization,\r\n}: InfoSidebarProps) {\r\n const { state: buildingState } = React.useContext(BuildingsContext);\r\n const { building: currentBuilding } = buildingState.buildings;\r\n\r\n // State\r\n const [loadingBuildingInfo] = React.useState(false)\r\n\r\n return (\r\n // Fills the InfoSidebar overlay wrapper (Sidebar.tsx), which owns the resizable width.\r\n // pr-2 keeps a consistent right gutter so content never sits flush against the edge/handle.\r\n <div className=\"w-full flex flex-col h-full min-h-0 bg-background border-r border-border pr-2\">\r\n <Header\r\n currentBuilding={currentBuilding}\r\n loadingBuildingInfo={loadingBuildingInfo}\r\n organization={organization}\r\n />\r\n\r\n {/* Tab Selector */}\r\n {tabSelector}\r\n\r\n {/* Tab Content */}\r\n {children}\r\n </div>\r\n )\r\n}\r\n"],"mappings":";AAmCI,SACE,KADF;AA9BJ,YAAY,WAAW;AAGvB,SAAS,wBAAwB;AAGjC,SAAS,cAAc;AAUhB,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AACF,GAAqB;AACnB,QAAM,EAAE,OAAO,cAAc,IAAI,MAAM,WAAW,gBAAgB;AAClE,QAAM,EAAE,UAAU,gBAAgB,IAAI,cAAc;AAGpD,QAAM,CAAC,mBAAmB,IAAI,MAAM,SAAS,KAAK;AAElD;AAAA;AAAA;AAAA,IAGE,qBAAC,SAAI,WAAU,iFACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA;AAAA;AAAA,MACF;AAAA,MAGC;AAAA,MAGE;AAAA,OACL;AAAA;AAEJ;","names":[]}
|
|
@@ -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;AAuBtE,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;CACjB,CAAA;AAYD,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,MAAiB,EACjB,QAAQ,EACR,GAAG,EACH,MAAc,GACf,EAAE,gBAAgB,
|
|
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;AAuBtE,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;CACjB,CAAA;AAYD,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,MAAiB,EACjB,QAAQ,EACR,GAAG,EACH,MAAc,GACf,EAAE,gBAAgB,qBAkdlB"}
|
|
@@ -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 console.log('[SensorInput] onPlaceSensor called, index:', index)\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 } 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"]}
|
|
@@ -56,10 +56,8 @@ function SensorsSection({ minioBaseUrl }) {
|
|
|
56
56
|
const handleSensorAction = React.useCallback((action, id) => {
|
|
57
57
|
switch (action) {
|
|
58
58
|
case "view":
|
|
59
|
-
console.log(`View sensor ${id}`);
|
|
60
59
|
break;
|
|
61
60
|
case "edit":
|
|
62
|
-
console.log(`Edit sensor ${id}`);
|
|
63
61
|
break;
|
|
64
62
|
case "delete":
|
|
65
63
|
toast.success(t("sensorDeleted"));
|