@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.
Files changed (145) hide show
  1. package/dist/core/components/ui/Comments/CollapsibleCommentItem.js.map +1 -1
  2. package/dist/core/components/ui/Comments/Comment.js.map +1 -1
  3. package/dist/core/components/ui/Comments/CommentActionButtons.d.ts +5 -1
  4. package/dist/core/components/ui/Comments/CommentActionButtons.d.ts.map +1 -1
  5. package/dist/core/components/ui/Comments/CommentActionButtons.js +37 -9
  6. package/dist/core/components/ui/Comments/CommentActionButtons.js.map +1 -1
  7. package/dist/core/components/ui/Sensors/CollapsibleSensorItem.d.ts +1 -2
  8. package/dist/core/components/ui/Sensors/CollapsibleSensorItem.d.ts.map +1 -1
  9. package/dist/core/components/ui/Sensors/CollapsibleSensorItem.js +66 -94
  10. package/dist/core/components/ui/Sensors/CollapsibleSensorItem.js.map +1 -1
  11. package/dist/core/components/ui/Sensors/Sensor.d.ts +16 -3
  12. package/dist/core/components/ui/Sensors/Sensor.d.ts.map +1 -1
  13. package/dist/core/components/ui/Sensors/Sensor.js +76 -37
  14. package/dist/core/components/ui/Sensors/Sensor.js.map +1 -1
  15. package/dist/core/components/ui/Sensors/SensorChart.d.ts +12 -2
  16. package/dist/core/components/ui/Sensors/SensorChart.d.ts.map +1 -1
  17. package/dist/core/components/ui/Sensors/SensorChart.js +49 -12
  18. package/dist/core/components/ui/Sensors/SensorChart.js.map +1 -1
  19. package/dist/core/components/ui/Sensors/SensorDetailDialog.d.ts +9 -0
  20. package/dist/core/components/ui/Sensors/SensorDetailDialog.d.ts.map +1 -0
  21. package/dist/core/components/ui/Sensors/SensorDetailDialog.js +128 -0
  22. package/dist/core/components/ui/Sensors/SensorDetailDialog.js.map +1 -0
  23. package/dist/core/components/ui/Sensors/SensorInput.d.ts +6 -1
  24. package/dist/core/components/ui/Sensors/SensorInput.d.ts.map +1 -1
  25. package/dist/core/components/ui/Sensors/SensorInput.js +94 -8
  26. package/dist/core/components/ui/Sensors/SensorInput.js.map +1 -1
  27. package/dist/core/components/ui/Sensors/SensorTagsSection.d.ts +3 -1
  28. package/dist/core/components/ui/Sensors/SensorTagsSection.d.ts.map +1 -1
  29. package/dist/core/components/ui/Sensors/SensorTagsSection.js +4 -1
  30. package/dist/core/components/ui/Sensors/SensorTagsSection.js.map +1 -1
  31. package/dist/core/components/ui/Sensors/SensorsSection.d.ts +1 -3
  32. package/dist/core/components/ui/Sensors/SensorsSection.d.ts.map +1 -1
  33. package/dist/core/components/ui/Sensors/SensorsSection.js +33 -8
  34. package/dist/core/components/ui/Sensors/SensorsSection.js.map +1 -1
  35. package/dist/core/components/ui/Sensors/TimeZoneSelect.d.ts +7 -0
  36. package/dist/core/components/ui/Sensors/TimeZoneSelect.d.ts.map +1 -0
  37. package/dist/core/components/ui/Sensors/TimeZoneSelect.js +56 -0
  38. package/dist/core/components/ui/Sensors/TimeZoneSelect.js.map +1 -0
  39. package/dist/core/components/ui/Sensors/sensorData.d.ts +42 -0
  40. package/dist/core/components/ui/Sensors/sensorData.d.ts.map +1 -0
  41. package/dist/core/components/ui/Sensors/sensorData.js +118 -0
  42. package/dist/core/components/ui/Sensors/sensorData.js.map +1 -0
  43. package/dist/core/components/ui/Sensors/sensorRange.d.ts +24 -0
  44. package/dist/core/components/ui/Sensors/sensorRange.d.ts.map +1 -0
  45. package/dist/core/components/ui/Sensors/sensorRange.js +34 -0
  46. package/dist/core/components/ui/Sensors/sensorRange.js.map +1 -0
  47. package/dist/core/components/ui/Sensors/useSensorSeries.d.ts +15 -0
  48. package/dist/core/components/ui/Sensors/useSensorSeries.d.ts.map +1 -0
  49. package/dist/core/components/ui/Sensors/useSensorSeries.js +63 -0
  50. package/dist/core/components/ui/Sensors/useSensorSeries.js.map +1 -0
  51. package/dist/core/components/viewers/Viewer.js +1 -1
  52. package/dist/core/components/viewers/Viewer.js.map +1 -1
  53. package/dist/core/components/viewers/bim/src/BimSidebar/src/SensorsTab/index.d.ts +1 -3
  54. package/dist/core/components/viewers/bim/src/BimSidebar/src/SensorsTab/index.d.ts.map +1 -1
  55. package/dist/core/components/viewers/bim/src/BimSidebar/src/SensorsTab/index.js +2 -2
  56. package/dist/core/components/viewers/bim/src/BimSidebar/src/SensorsTab/index.js.map +1 -1
  57. package/dist/core/components/viewers/bim/src/BimSidebar/src/index.js +1 -1
  58. package/dist/core/components/viewers/bim/src/BimSidebar/src/index.js.map +1 -1
  59. package/dist/core/components/viewers/bim/src/tools/AddToBim/index.d.ts +1 -2
  60. package/dist/core/components/viewers/bim/src/tools/AddToBim/index.d.ts.map +1 -1
  61. package/dist/core/components/viewers/bim/src/tools/AddToBim/index.js +25 -2
  62. package/dist/core/components/viewers/bim/src/tools/AddToBim/index.js.map +1 -1
  63. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.js.map +1 -1
  64. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.d.ts +1 -22
  65. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.d.ts.map +1 -1
  66. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.js +2 -105
  67. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.js.map +1 -1
  68. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimSensor.d.ts +8 -1
  69. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimSensor.d.ts.map +1 -1
  70. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimSensor.js +15 -3
  71. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimSensor.js.map +1 -1
  72. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.js.map +1 -1
  73. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.d.ts +2 -23
  74. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.d.ts.map +1 -1
  75. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.js +2 -24
  76. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.js.map +1 -1
  77. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentMarkerConfig.d.ts +33 -0
  78. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentMarkerConfig.d.ts.map +1 -0
  79. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentMarkerConfig.js +90 -0
  80. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentMarkerConfig.js.map +1 -0
  81. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.d.ts +7 -1
  82. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.d.ts.map +1 -1
  83. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.js +26 -230
  84. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.js.map +1 -1
  85. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.d.ts +7 -1
  86. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.d.ts.map +1 -1
  87. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.js +39 -11
  88. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.js.map +1 -1
  89. package/dist/core/components/viewers/map/MapViewer.d.ts +1 -2
  90. package/dist/core/components/viewers/map/MapViewer.d.ts.map +1 -1
  91. package/dist/core/components/viewers/map/MapViewer.js +2 -2
  92. package/dist/core/components/viewers/map/MapViewer.js.map +1 -1
  93. package/dist/core/components/viewers/map/src/MapLayers/index.d.ts +1 -2
  94. package/dist/core/components/viewers/map/src/MapLayers/index.d.ts.map +1 -1
  95. package/dist/core/components/viewers/map/src/MapLayers/index.js +2 -2
  96. package/dist/core/components/viewers/map/src/MapLayers/index.js.map +1 -1
  97. package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.d.ts +1 -3
  98. package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.d.ts.map +1 -1
  99. package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.js +74 -39
  100. package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.js.map +1 -1
  101. package/dist/core/components/viewers/map/src/MapSidebar/index.js +1 -1
  102. package/dist/core/components/viewers/map/src/MapSidebar/index.js.map +1 -1
  103. package/dist/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.d.ts +1 -3
  104. package/dist/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.d.ts.map +1 -1
  105. package/dist/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.js +2 -2
  106. package/dist/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.js.map +1 -1
  107. package/dist/core/components/viewers/shared/markers/BimMarkerCluster.d.ts +19 -0
  108. package/dist/core/components/viewers/shared/markers/BimMarkerCluster.d.ts.map +1 -0
  109. package/dist/core/components/viewers/shared/markers/BimMarkerCluster.js +108 -0
  110. package/dist/core/components/viewers/shared/markers/BimMarkerCluster.js.map +1 -0
  111. package/dist/core/components/viewers/shared/markers/computeMarkerLookAt.d.ts +24 -0
  112. package/dist/core/components/viewers/shared/markers/computeMarkerLookAt.d.ts.map +1 -0
  113. package/dist/core/components/viewers/shared/markers/computeMarkerLookAt.js +27 -0
  114. package/dist/core/components/viewers/shared/markers/computeMarkerLookAt.js.map +1 -0
  115. package/dist/core/components/viewers/shared/markers/types.d.ts +66 -0
  116. package/dist/core/components/viewers/shared/markers/types.d.ts.map +1 -0
  117. package/dist/core/components/viewers/shared/markers/types.js +1 -0
  118. package/dist/core/components/viewers/shared/markers/types.js.map +1 -0
  119. package/dist/core/components/viewers/shared/markers/useMarkerLayerBim.d.ts +22 -0
  120. package/dist/core/components/viewers/shared/markers/useMarkerLayerBim.d.ts.map +1 -0
  121. package/dist/core/components/viewers/shared/markers/useMarkerLayerBim.js +233 -0
  122. package/dist/core/components/viewers/shared/markers/useMarkerLayerBim.js.map +1 -0
  123. package/dist/core/hooks/ui/useResizableSidebarWidth.js.map +1 -1
  124. package/dist/core/i18n/messages/en.json +5 -1
  125. package/dist/core/i18n/messages/es.json +5 -1
  126. package/dist/core/i18n/messages/fr.json +5 -1
  127. package/dist/core/store/AppConfig/context.d.ts.map +1 -1
  128. package/dist/core/store/AppConfig/context.js +4 -1
  129. package/dist/core/store/AppConfig/context.js.map +1 -1
  130. package/dist/core/store/AppConfig/reducer.d.ts +10 -0
  131. package/dist/core/store/AppConfig/reducer.d.ts.map +1 -1
  132. package/dist/core/store/AppConfig/reducer.js +10 -0
  133. package/dist/core/store/AppConfig/reducer.js.map +1 -1
  134. package/dist/core/store/Menus/context.d.ts.map +1 -1
  135. package/dist/core/store/Menus/context.js +5 -2
  136. package/dist/core/store/Menus/context.js.map +1 -1
  137. package/dist/core/store/Menus/reducer.d.ts +45 -1
  138. package/dist/core/store/Menus/reducer.d.ts.map +1 -1
  139. package/dist/core/store/Menus/reducer.js +21 -3
  140. package/dist/core/store/Menus/reducer.js.map +1 -1
  141. package/dist/core/utils/timeUtils.d.ts +24 -1
  142. package/dist/core/utils/timeUtils.d.ts.map +1 -1
  143. package/dist/core/utils/timeUtils.js +52 -3
  144. package/dist/core/utils/timeUtils.js.map +1 -1
  145. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/core/components/ui/Comments/CollapsibleCommentItem.tsx"],"sourcesContent":["'use client'\n\n// SPDX-License-Identifier: AGPL-3.0-or-later\n// Copyright (C) 2025 Collab Digital Twins\n\nimport * as LR from 'lucide-react'\nimport { useSession } from 'next-auth/react'\nimport { useTranslations } from 'next-intl'\nimport * as React from 'react'\n\nimport { useUser } from '../../../hooks/users/users'\nimport { usePermissions } from '../../../store'\nimport { commentRingShadow } from '../../../utils/markerUtils'\nimport { formatTimestamp } from '../../../utils/timeUtils'\nimport { cn } from '../../../utils/utils'\nimport { Avatar } from '../Avatar'\nimport { Badge } from '../Badge'\nimport { Button } from '../Button'\nimport { Input } from '../Input'\nimport { UserAvatar } from '../UserAvatar'\n\nimport { CommentActionButtons } from './CommentActionButtons'\n\nimport type { Comment } from '../../../types/dbTypes'\n\ntype CommentAction = 'view' | 'edit' | 'delete' | 'reply'\n\n/** Request from a 3D/map marker to auto-open a comment's editor or reply box. */\nexport interface PendingCommentAction {\n commentId: number\n action: 'edit' | 'reply'\n requestId: number\n}\n\ninterface CollapsibleCommentItemProps {\n comment: Comment\n onAction?: (action: CommentAction, id: number) => void\n replies?: Comment[]\n onReply?: (comment: Comment, replyText: string) => void\n onEdit?: (id: number, text: string) => void\n onFileUpload?: (id: number, files: FileList) => void\n attachments?: Array<{ name: string; url: string; size: number }>\n depth?: number\n isVisible?: boolean\n focused?: boolean\n onFocus?: () => void\n pendingAction?: PendingCommentAction\n onMouseEnter?: () => void\n onMouseLeave?: () => void\n}\n\nexport function CollapsibleCommentItem({\n comment,\n onAction,\n replies = [],\n onReply,\n onEdit,\n onFileUpload,\n depth = 0,\n isVisible = true,\n focused = false,\n onFocus,\n pendingAction,\n onMouseEnter,\n onMouseLeave,\n}: CollapsibleCommentItemProps) {\n const t = useTranslations('CommentsSection')\n const { ability } = usePermissions()\n\n const isReply = depth > 0\n const [isExpanded, setIsExpanded] = React.useState(false)\n const [replyText, setReplyText] = React.useState('')\n const [isEditing, setIsEditing] = React.useState(false)\n const [editText, setEditText] = React.useState(comment.text)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const replyInputRef = React.useRef<HTMLInputElement>(null)\n const prevVisibleRef = React.useRef(isVisible)\n\n const user = useSession().data?.user\n const isAuthor = user?.id === String(comment.authorId)\n const canReply = ability.can('create', 'Comment')\n const canEdit = ability.can('update', 'Comment')\n const canDelete = ability.can('delete', 'Comment')\n\n const { user: author } = useUser(String(comment.authorId))\n const authorName: string = author?.name ?? 'Unknown User'\n\n // Auto-collapse when visibility turns off\n React.useEffect(() => {\n if (prevVisibleRef.current && !isVisible) setIsExpanded(false)\n prevVisibleRef.current = isVisible\n }, [isVisible])\n\n const handleEditStart = React.useCallback(() => {\n setEditText(comment.text)\n setIsEditing(true)\n }, [comment.text])\n\n // Reply happens in one place: the input at the end of the thread. The reply button just\n // expands the thread and focuses that input, so users never hunt for a per-comment control.\n const openReply = React.useCallback(() => {\n setIsExpanded(true)\n requestAnimationFrame(() => replyInputRef.current?.focus())\n }, [])\n\n // Open the editor / reply box when a marker requests it, then scroll into view.\n React.useEffect(() => {\n if (!pendingAction) return\n const matchesSelf = pendingAction.commentId === comment.id\n const matchesReply = replies.some((r) => r.id === pendingAction.commentId)\n if (matchesSelf) {\n if (pendingAction.action === 'edit') handleEditStart()\n else openReply()\n containerRef.current?.scrollIntoView({ behavior: 'smooth', block: 'nearest' })\n }\n if (matchesReply) setIsExpanded(true)\n }, [pendingAction?.requestId])\n\n const handleReplySubmit = () => {\n if (replyText.trim() && onReply) {\n onReply(comment, replyText)\n setReplyText('')\n }\n }\n\n const handleEditSubmit = () => {\n const next = editText.trim()\n if (next && next !== comment.text && onEdit) onEdit(comment.id, next)\n setIsEditing(false)\n }\n\n return (\n <div\n ref={containerRef}\n className={cn(\n 'transition-colors',\n !isReply && 'rounded-md border',\n isReply && 'pt-2'\n )}\n style={focused ? { boxShadow: commentRingShadow({ focused: true }), borderColor: 'transparent' } : undefined}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n onDoubleClick={(e) => {\n e.stopPropagation()\n onFocus?.()\n }}\n title={onFocus ? t('doubleClickZoom') : undefined}\n >\n <div className={cn('flex flex-col gap-1.5 p-3', !isReply && 'hover:bg-accent/50 transition-colors rounded-md', onFocus && 'cursor-pointer')}>\n {/* Header: avatar, author, timestamp, actions */}\n <div className=\"flex items-start gap-2\">\n <Avatar className={cn('mt-0.5 rounded-full overflow-hidden shrink-0', isReply ? 'h-6 w-6' : 'h-8 w-8')}>\n <UserAvatar imageFileId={author?.imageFileId} name={authorName} />\n </Avatar>\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm font-medium text-foreground truncate\">{authorName}</span>\n {comment.updatedAt !== comment.createdAt && (\n <Badge variant=\"outline\" className=\"text-xs\">{t('edited')}</Badge>\n )}\n <span className=\"text-xs text-muted-foreground ml-auto whitespace-nowrap\">\n {formatTimestamp(comment.createdAt)}\n </span>\n </div>\n </div>\n\n {onAction && (\n <CommentActionButtons\n // Replies are single-level, so only top-level comments expose a reply control.\n onReply={!isReply && onReply ? openReply : undefined}\n onEdit={isAuthor ? handleEditStart : undefined}\n onDelete={isAuthor ? () => onAction('delete', comment.id) : undefined}\n canReply={canReply}\n canEdit={canEdit}\n canDelete={canDelete}\n labels={{ reply: t('replyToComment'), edit: t('editComment'), delete: t('deleteComment') }}\n />\n )}\n </div>\n\n {/* Body: full text (shown once) or inline editor */}\n {isEditing ? (\n <div className=\"flex gap-2\" onDoubleClick={(e) => e.stopPropagation()}>\n <Input\n value={editText}\n onChange={(e) => setEditText(e.target.value)}\n className=\"flex-1 h-8\"\n autoFocus\n onKeyDown={(e) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault()\n handleEditSubmit()\n }\n if (e.key === 'Escape') setIsEditing(false)\n }}\n disabled={!canEdit}\n />\n <Button size=\"icon\" className=\"h-8 w-8 p-0\" onClick={handleEditSubmit} disabled={!editText.trim() || !canEdit}>\n <LR.Check className=\"h-4 w-4\" />\n </Button>\n <Button size=\"icon\" variant=\"ghost\" className=\"h-8 w-8 p-0\" onClick={() => setIsEditing(false)}>\n <LR.X className=\"h-4 w-4\" />\n </Button>\n </div>\n ) : (\n <p className=\"text-sm text-foreground whitespace-pre-wrap break-words\">{comment.text}</p>\n )}\n\n {/* Replies toggle (top-level only) */}\n {!isReply && replies.length > 0 && (\n <button\n type=\"button\"\n className=\"flex items-center gap-1 w-fit text-xs text-muted-foreground hover:text-foreground transition-colors\"\n onClick={() => setIsExpanded((v) => !v)}\n >\n <LR.ChevronRight className={cn('h-3.5 w-3.5 transition-transform', isExpanded && 'rotate-90')} />\n {replies.length} {replies.length === 1 ? t('reply') : t('replies')}\n </button>\n )}\n </div>\n\n {/* Nested replies thread + single reply input at the end */}\n {!isReply && isExpanded && (\n <div className=\"ml-5 border-l border-border pl-1 pr-2 pb-2\">\n {replies.map((reply) => (\n <CollapsibleCommentItem\n key={reply.id}\n comment={reply}\n onAction={onAction}\n onReply={onReply}\n onEdit={onEdit}\n onFileUpload={onFileUpload}\n depth={depth + 1}\n pendingAction={pendingAction}\n />\n ))}\n\n {onReply && (\n <div className=\"flex gap-2 px-1 pt-2\" onDoubleClick={(e) => e.stopPropagation()}>\n <Input\n ref={replyInputRef}\n value={replyText}\n onChange={(e) => setReplyText(e.target.value)}\n placeholder={t('writeReply')}\n className=\"flex-1 h-8\"\n onKeyDown={(e) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault()\n handleReplySubmit()\n }\n }}\n disabled={!canReply}\n />\n <Button size=\"icon\" className=\"h-8 w-8 p-0\" onClick={handleReplySubmit} disabled={!replyText.trim() || !canReply}>\n <LR.Send className=\"h-4 w-4\" />\n </Button>\n </div>\n )}\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":["_a"]}
1
+ {"version":3,"sources":["../../../../../src/core/components/ui/Comments/CollapsibleCommentItem.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\n\r\nimport { useUser } from '../../../hooks/users/users'\r\nimport { usePermissions } from '../../../store'\r\nimport { commentRingShadow } from '../../../utils/markerUtils'\r\nimport { formatTimestamp } from '../../../utils/timeUtils'\r\nimport { cn } from '../../../utils/utils'\r\nimport { Avatar } from '../Avatar'\r\nimport { Badge } from '../Badge'\r\nimport { Button } from '../Button'\r\nimport { Input } from '../Input'\r\nimport { UserAvatar } from '../UserAvatar'\r\n\r\nimport { CommentActionButtons } from './CommentActionButtons'\r\n\r\nimport type { Comment } from '../../../types/dbTypes'\r\n\r\ntype CommentAction = 'view' | 'edit' | 'delete' | 'reply'\r\n\r\n/** Request from a 3D/map marker to auto-open a comment's editor or reply box. */\r\nexport interface PendingCommentAction {\r\n commentId: number\r\n action: 'edit' | 'reply'\r\n requestId: number\r\n}\r\n\r\ninterface CollapsibleCommentItemProps {\r\n comment: Comment\r\n onAction?: (action: CommentAction, id: number) => void\r\n replies?: Comment[]\r\n onReply?: (comment: Comment, replyText: string) => void\r\n onEdit?: (id: number, text: string) => void\r\n onFileUpload?: (id: number, files: FileList) => void\r\n attachments?: Array<{ name: string; url: string; size: number }>\r\n depth?: number\r\n isVisible?: boolean\r\n focused?: boolean\r\n onFocus?: () => void\r\n pendingAction?: PendingCommentAction\r\n onMouseEnter?: () => void\r\n onMouseLeave?: () => void\r\n}\r\n\r\nexport function CollapsibleCommentItem({\r\n comment,\r\n onAction,\r\n replies = [],\r\n onReply,\r\n onEdit,\r\n onFileUpload,\r\n depth = 0,\r\n isVisible = true,\r\n focused = false,\r\n onFocus,\r\n pendingAction,\r\n onMouseEnter,\r\n onMouseLeave,\r\n}: CollapsibleCommentItemProps) {\r\n const t = useTranslations('CommentsSection')\r\n const { ability } = usePermissions()\r\n\r\n const isReply = depth > 0\r\n const [isExpanded, setIsExpanded] = React.useState(false)\r\n const [replyText, setReplyText] = React.useState('')\r\n const [isEditing, setIsEditing] = React.useState(false)\r\n const [editText, setEditText] = React.useState(comment.text)\r\n const containerRef = React.useRef<HTMLDivElement>(null)\r\n const replyInputRef = React.useRef<HTMLInputElement>(null)\r\n const prevVisibleRef = React.useRef(isVisible)\r\n\r\n const user = useSession().data?.user\r\n const isAuthor = user?.id === String(comment.authorId)\r\n const canReply = ability.can('create', 'Comment')\r\n const canEdit = ability.can('update', 'Comment')\r\n const canDelete = ability.can('delete', 'Comment')\r\n\r\n const { user: author } = useUser(String(comment.authorId))\r\n const authorName: string = author?.name ?? 'Unknown User'\r\n\r\n // Auto-collapse when visibility turns off\r\n React.useEffect(() => {\r\n if (prevVisibleRef.current && !isVisible) setIsExpanded(false)\r\n prevVisibleRef.current = isVisible\r\n }, [isVisible])\r\n\r\n const handleEditStart = React.useCallback(() => {\r\n setEditText(comment.text)\r\n setIsEditing(true)\r\n }, [comment.text])\r\n\r\n // Reply happens in one place: the input at the end of the thread. The reply button just\r\n // expands the thread and focuses that input, so users never hunt for a per-comment control.\r\n const openReply = React.useCallback(() => {\r\n setIsExpanded(true)\r\n requestAnimationFrame(() => replyInputRef.current?.focus())\r\n }, [])\r\n\r\n // Open the editor / reply box when a marker requests it, then scroll into view.\r\n React.useEffect(() => {\r\n if (!pendingAction) return\r\n const matchesSelf = pendingAction.commentId === comment.id\r\n const matchesReply = replies.some((r) => r.id === pendingAction.commentId)\r\n if (matchesSelf) {\r\n if (pendingAction.action === 'edit') handleEditStart()\r\n else openReply()\r\n containerRef.current?.scrollIntoView({ behavior: 'smooth', block: 'nearest' })\r\n }\r\n if (matchesReply) setIsExpanded(true)\r\n }, [pendingAction?.requestId])\r\n\r\n const handleReplySubmit = () => {\r\n if (replyText.trim() && onReply) {\r\n onReply(comment, replyText)\r\n setReplyText('')\r\n }\r\n }\r\n\r\n const handleEditSubmit = () => {\r\n const next = editText.trim()\r\n if (next && next !== comment.text && onEdit) onEdit(comment.id, next)\r\n setIsEditing(false)\r\n }\r\n\r\n return (\r\n <div\r\n ref={containerRef}\r\n className={cn(\r\n 'transition-colors',\r\n !isReply && 'rounded-md border',\r\n isReply && 'pt-2'\r\n )}\r\n style={focused ? { boxShadow: commentRingShadow({ focused: true }), borderColor: 'transparent' } : undefined}\r\n onMouseEnter={onMouseEnter}\r\n onMouseLeave={onMouseLeave}\r\n onDoubleClick={(e) => {\r\n e.stopPropagation()\r\n onFocus?.()\r\n }}\r\n title={onFocus ? t('doubleClickZoom') : undefined}\r\n >\r\n <div className={cn('flex flex-col gap-1.5 p-3', !isReply && 'hover:bg-accent/50 transition-colors rounded-md', onFocus && 'cursor-pointer')}>\r\n {/* Header: avatar, author, timestamp, actions */}\r\n <div className=\"flex items-start gap-2\">\r\n <Avatar className={cn('mt-0.5 rounded-full overflow-hidden shrink-0', isReply ? 'h-6 w-6' : 'h-8 w-8')}>\r\n <UserAvatar imageFileId={author?.imageFileId} name={authorName} />\r\n </Avatar>\r\n\r\n <div className=\"flex-1 min-w-0\">\r\n <div className=\"flex items-center gap-2\">\r\n <span className=\"text-sm font-medium text-foreground truncate\">{authorName}</span>\r\n {comment.updatedAt !== comment.createdAt && (\r\n <Badge variant=\"outline\" className=\"text-xs\">{t('edited')}</Badge>\r\n )}\r\n <span className=\"text-xs text-muted-foreground ml-auto whitespace-nowrap\">\r\n {formatTimestamp(comment.createdAt)}\r\n </span>\r\n </div>\r\n </div>\r\n\r\n {onAction && (\r\n <CommentActionButtons\r\n // Replies are single-level, so only top-level comments expose a reply control.\r\n onReply={!isReply && onReply ? openReply : undefined}\r\n onEdit={isAuthor ? handleEditStart : undefined}\r\n onDelete={isAuthor ? () => onAction('delete', comment.id) : undefined}\r\n canReply={canReply}\r\n canEdit={canEdit}\r\n canDelete={canDelete}\r\n labels={{ reply: t('replyToComment'), edit: t('editComment'), delete: t('deleteComment') }}\r\n />\r\n )}\r\n </div>\r\n\r\n {/* Body: full text (shown once) or inline editor */}\r\n {isEditing ? (\r\n <div className=\"flex gap-2\" onDoubleClick={(e) => e.stopPropagation()}>\r\n <Input\r\n value={editText}\r\n onChange={(e) => setEditText(e.target.value)}\r\n className=\"flex-1 h-8\"\r\n autoFocus\r\n onKeyDown={(e) => {\r\n if (e.key === 'Enter' && !e.shiftKey) {\r\n e.preventDefault()\r\n handleEditSubmit()\r\n }\r\n if (e.key === 'Escape') setIsEditing(false)\r\n }}\r\n disabled={!canEdit}\r\n />\r\n <Button size=\"icon\" className=\"h-8 w-8 p-0\" onClick={handleEditSubmit} disabled={!editText.trim() || !canEdit}>\r\n <LR.Check className=\"h-4 w-4\" />\r\n </Button>\r\n <Button size=\"icon\" variant=\"ghost\" className=\"h-8 w-8 p-0\" onClick={() => setIsEditing(false)}>\r\n <LR.X className=\"h-4 w-4\" />\r\n </Button>\r\n </div>\r\n ) : (\r\n <p className=\"text-sm text-foreground whitespace-pre-wrap break-words\">{comment.text}</p>\r\n )}\r\n\r\n {/* Replies toggle (top-level only) */}\r\n {!isReply && replies.length > 0 && (\r\n <button\r\n type=\"button\"\r\n className=\"flex items-center gap-1 w-fit text-xs text-muted-foreground hover:text-foreground transition-colors\"\r\n onClick={() => setIsExpanded((v) => !v)}\r\n >\r\n <LR.ChevronRight className={cn('h-3.5 w-3.5 transition-transform', isExpanded && 'rotate-90')} />\r\n {replies.length} {replies.length === 1 ? t('reply') : t('replies')}\r\n </button>\r\n )}\r\n </div>\r\n\r\n {/* Nested replies thread + single reply input at the end */}\r\n {!isReply && isExpanded && (\r\n <div className=\"ml-5 border-l border-border pl-1 pr-2 pb-2\">\r\n {replies.map((reply) => (\r\n <CollapsibleCommentItem\r\n key={reply.id}\r\n comment={reply}\r\n onAction={onAction}\r\n onReply={onReply}\r\n onEdit={onEdit}\r\n onFileUpload={onFileUpload}\r\n depth={depth + 1}\r\n pendingAction={pendingAction}\r\n />\r\n ))}\r\n\r\n {onReply && (\r\n <div className=\"flex gap-2 px-1 pt-2\" onDoubleClick={(e) => e.stopPropagation()}>\r\n <Input\r\n ref={replyInputRef}\r\n value={replyText}\r\n onChange={(e) => setReplyText(e.target.value)}\r\n placeholder={t('writeReply')}\r\n className=\"flex-1 h-8\"\r\n onKeyDown={(e) => {\r\n if (e.key === 'Enter' && !e.shiftKey) {\r\n e.preventDefault()\r\n handleReplySubmit()\r\n }\r\n }}\r\n disabled={!canReply}\r\n />\r\n <Button size=\"icon\" className=\"h-8 w-8 p-0\" onClick={handleReplySubmit} disabled={!replyText.trim() || !canReply}>\r\n <LR.Send className=\"h-4 w-4\" />\r\n </Button>\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n )\r\n}\r\n"],"mappings":"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","names":["_a"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/core/components/ui/Comments/Comment.tsx"],"sourcesContent":["'use client'\n\n// SPDX-License-Identifier: AGPL-3.0-or-later\n// Copyright (C) 2025 Collab Digital Twins\n\nimport * as LR from 'lucide-react'\nimport * as React from 'react'\n\nimport { useFile } from '../../../hooks/files/files'\nimport { commentRingShadow } from '../../../utils/markerUtils'\nimport { formatTimestamp } from '../../../utils/timeUtils'\nimport { Avatar } from '../Avatar'\nimport { Button } from '../Button'\nimport { Card } from '../Card'\nimport { UserAvatar } from '../UserAvatar'\n\nimport { CommentActionButtons, type CommentActionLabels } from './CommentActionButtons'\n\nexport type CommentProps = {\n userName: string\n userImage: number | null\n userImageFileId?: number | null\n buildingId?: number\n text: string\n createdAt: string | Date\n onRemove?: () => void\n onClose?: () => void\n /** Called when the collapsed avatar is single-clicked (select the comment). */\n onSelect?: () => void\n /** Called on double-click — used to zoom/focus the comment in the viewer. */\n onFocus?: () => void\n /** Reply to the comment (in-viewer card opens the reply box in the sidebar). */\n onReply?: () => void\n /** Edit the comment (in-viewer card opens the editor in the sidebar). */\n onEdit?: () => void\n enableCollapse?: boolean\n defaultCollapsed?: boolean\n isPending?: boolean\n highlight?: boolean\n /** Double-clicked/zoomed comment: renders the thicker focus ring. */\n focused?: boolean\n /** Render the close/reply/edit/delete action row (used by the in-viewer BIM card). */\n showActions?: boolean\n canReply?: boolean\n canEdit?: boolean\n canDelete?: boolean\n actionLabels?: CommentActionLabels\n}\n\nexport default function Comment({\n text,\n createdAt,\n userName,\n userImage,\n userImageFileId,\n onRemove,\n onClose,\n onSelect,\n onFocus,\n onReply,\n onEdit,\n enableCollapse = false,\n defaultCollapsed = false,\n isPending = false,\n highlight = false,\n focused = false,\n showActions = false,\n canReply = true,\n canEdit = true,\n canDelete = true,\n actionLabels,\n}: CommentProps) {\n const [isCollapsed, setIsCollapsed] = React.useState(defaultCollapsed)\n // Timestamp of the last press, shared across the puck→card transition so a quick second press\n // (double-click) zooms even though the first press already opened the card.\n const lastTap = React.useRef(0)\n const resolvedImageFileId = userImageFileId ?? userImage\n const { file } = useFile(resolvedImageFileId)\n const previewSrc = file?.url\n\n // Zooming to a comment (focus) should also open its card in the 3D scene.\n React.useEffect(() => {\n if (focused && enableCollapse) setIsCollapsed(false)\n }, [focused, enableCollapse])\n\n const handleClose = () => {\n if (enableCollapse) setIsCollapsed(true)\n else onClose?.()\n }\n\n if (enableCollapse && isCollapsed) {\n return (\n <div\n className=\"h-9 w-9 rounded-full overflow-hidden flex-shrink-0 pointer-events-auto cursor-pointer transition-transform relative\"\n style={{ boxShadow: commentRingShadow({ highlight, focused }) }}\n // Open on pointerdown, not click: the CSS2D renderer re-transforms this element every\n // frame, so a browser \"click\" (down+up on the same stable spot) often never fires in the\n // 3D scene. stopPropagation keeps the press from reaching the canvas/orbit controls.\n onPointerDown={(e) => {\n e.stopPropagation()\n lastTap.current = performance.now()\n onSelect?.()\n setIsCollapsed(false)\n }}\n >\n {isPending ? (\n <div className=\"w-full h-full rounded-full bg-primary-light flex items-center justify-center\">\n <LR.Loader2 className=\"h-4 w-4 animate-spin text-muted-foreground\" />\n </div>\n ) : (\n <Avatar className=\"h-full w-full\">\n <UserAvatar\n imageFileId={resolvedImageFileId}\n previewSrc={previewSrc}\n name={userName}\n className=\"h-full w-full rounded-full object-cover\"\n />\n </Avatar>\n )}\n </div>\n )\n }\n\n return (\n <div\n className=\"relative inline-block group\"\n style={{ pointerEvents: 'auto', boxShadow: focused ? commentRingShadow({ focused }) : undefined, borderRadius: focused ? 12 : undefined, zIndex: focused ? 30 : highlight ? 20 : undefined }}\n title={onFocus ? 'Double click to zoom' : enableCollapse ? 'Double click to collapse' : undefined}\n // In the 3D card, detect a double-press here (dblclick is unreliable under the CSS2D\n // renderer) to trigger zoom. The map popup keeps the normal onDoubleClick path below.\n onPointerDown={(e) => {\n e.stopPropagation()\n if (!enableCollapse) return\n const now = performance.now()\n if (now - lastTap.current < 300) onFocus?.()\n lastTap.current = now\n }}\n onDoubleClick={(e) => {\n e.stopPropagation()\n if (!enableCollapse) onClose?.()\n }}\n >\n <Card className=\"w-48 px-3 py-2.5 shadow-md group-hover:shadow-lg transition-shadow duration-200\">\n <div className=\"flex items-start gap-3\">\n <div className=\"h-9 w-9 rounded-full shadow-sm flex items-center justify-center select-none flex-shrink-0 bg-card\">\n {isPending ? (\n <div className=\"h-10 w-10 rounded-full bg-muted flex items-center justify-center\">\n <LR.Loader2 className=\"h-5 w-5 animate-spin text-muted-foreground\" />\n </div>\n ) : (\n <Avatar className=\"h-10 w-10\">\n <UserAvatar\n imageFileId={resolvedImageFileId}\n previewSrc={previewSrc}\n name={userName}\n className=\"h-full w-full rounded-full object-cover\"\n />\n </Avatar>\n )}\n </div>\n\n <div className=\"flex-1 min-w-0 flex flex-col gap-1\">\n <span className=\"font-medium text-sm text-foreground leading-none truncate\">\n {userName}\n </span>\n <span className=\"text-xs text-muted-foreground leading-none\">\n {formatTimestamp(createdAt)}\n </span>\n </div>\n\n {showActions && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n title={actionLabels?.close ?? 'Close'}\n aria-label={actionLabels?.close ?? 'Close'}\n className=\"h-7 w-7 shrink-0 -mr-1 -mt-1\"\n // In the CSS2D 3D card (enableCollapse) activate on pointerdown for reliability;\n // the map popup is normal DOM, so keep click for keyboard accessibility.\n {...(enableCollapse\n ? { onPointerDown: (e: React.PointerEvent) => { e.stopPropagation(); handleClose() } }\n : { onClick: (e: React.MouseEvent) => { e.stopPropagation(); handleClose() } })}\n >\n <LR.X className=\"h-4 w-4\" />\n </Button>\n )}\n </div>\n\n <div className=\"mt-2 flex items-start gap-2\">\n <p className=\"min-w-0 flex-1 text-sm leading-relaxed text-foreground whitespace-pre-wrap break-words\">\n {text}\n </p>\n\n {/* Map popup keeps the inline delete; the BIM card uses the action row below instead. */}\n {!showActions && onRemove && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n title={actionLabels?.delete ?? 'Remove comment'}\n className=\"z-50 h-7 w-7 shrink-0\"\n aria-label={actionLabels?.delete ?? 'Remove comment'}\n onClick={(e) => {\n e.stopPropagation()\n onRemove()\n }}\n >\n <LR.Trash2 className=\"h-4 w-4\" />\n </Button>\n )}\n </div>\n\n {showActions && (onReply || onEdit || onRemove) && (\n <div className=\"mt-2 flex justify-end border-t pt-1\">\n <CommentActionButtons\n onReply={onReply}\n onEdit={onEdit}\n onDelete={onRemove}\n canReply={canReply}\n canEdit={canEdit}\n canDelete={canDelete}\n labels={actionLabels}\n buttonClassName=\"h-7 w-7\"\n activateOnPointerDown={enableCollapse}\n />\n </div>\n )}\n </Card>\n </div>\n )\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../../src/core/components/ui/Comments/Comment.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 { useFile } from '../../../hooks/files/files'\r\nimport { commentRingShadow } from '../../../utils/markerUtils'\r\nimport { formatTimestamp } from '../../../utils/timeUtils'\r\nimport { Avatar } from '../Avatar'\r\nimport { Button } from '../Button'\r\nimport { Card } from '../Card'\r\nimport { UserAvatar } from '../UserAvatar'\r\n\r\nimport { CommentActionButtons, type CommentActionLabels } from './CommentActionButtons'\r\n\r\nexport type CommentProps = {\r\n userName: string\r\n userImage: number | null\r\n userImageFileId?: number | null\r\n buildingId?: number\r\n text: string\r\n createdAt: string | Date\r\n onRemove?: () => void\r\n onClose?: () => void\r\n /** Called when the collapsed avatar is single-clicked (select the comment). */\r\n onSelect?: () => void\r\n /** Called on double-click — used to zoom/focus the comment in the viewer. */\r\n onFocus?: () => void\r\n /** Reply to the comment (in-viewer card opens the reply box in the sidebar). */\r\n onReply?: () => void\r\n /** Edit the comment (in-viewer card opens the editor in the sidebar). */\r\n onEdit?: () => void\r\n enableCollapse?: boolean\r\n defaultCollapsed?: boolean\r\n isPending?: boolean\r\n highlight?: boolean\r\n /** Double-clicked/zoomed comment: renders the thicker focus ring. */\r\n focused?: boolean\r\n /** Render the close/reply/edit/delete action row (used by the in-viewer BIM card). */\r\n showActions?: boolean\r\n canReply?: boolean\r\n canEdit?: boolean\r\n canDelete?: boolean\r\n actionLabels?: CommentActionLabels\r\n}\r\n\r\nexport default function Comment({\r\n text,\r\n createdAt,\r\n userName,\r\n userImage,\r\n userImageFileId,\r\n onRemove,\r\n onClose,\r\n onSelect,\r\n onFocus,\r\n onReply,\r\n onEdit,\r\n enableCollapse = false,\r\n defaultCollapsed = false,\r\n isPending = false,\r\n highlight = false,\r\n focused = false,\r\n showActions = false,\r\n canReply = true,\r\n canEdit = true,\r\n canDelete = true,\r\n actionLabels,\r\n}: CommentProps) {\r\n const [isCollapsed, setIsCollapsed] = React.useState(defaultCollapsed)\r\n // Timestamp of the last press, shared across the puck→card transition so a quick second press\r\n // (double-click) zooms even though the first press already opened the card.\r\n const lastTap = React.useRef(0)\r\n const resolvedImageFileId = userImageFileId ?? userImage\r\n const { file } = useFile(resolvedImageFileId)\r\n const previewSrc = file?.url\r\n\r\n // Zooming to a comment (focus) should also open its card in the 3D scene.\r\n React.useEffect(() => {\r\n if (focused && enableCollapse) setIsCollapsed(false)\r\n }, [focused, enableCollapse])\r\n\r\n const handleClose = () => {\r\n if (enableCollapse) setIsCollapsed(true)\r\n else onClose?.()\r\n }\r\n\r\n if (enableCollapse && isCollapsed) {\r\n return (\r\n <div\r\n className=\"h-9 w-9 rounded-full overflow-hidden flex-shrink-0 pointer-events-auto cursor-pointer transition-transform relative\"\r\n style={{ boxShadow: commentRingShadow({ highlight, focused }) }}\r\n // Open on pointerdown, not click: the CSS2D renderer re-transforms this element every\r\n // frame, so a browser \"click\" (down+up on the same stable spot) often never fires in the\r\n // 3D scene. stopPropagation keeps the press from reaching the canvas/orbit controls.\r\n onPointerDown={(e) => {\r\n e.stopPropagation()\r\n lastTap.current = performance.now()\r\n onSelect?.()\r\n setIsCollapsed(false)\r\n }}\r\n >\r\n {isPending ? (\r\n <div className=\"w-full h-full rounded-full bg-primary-light flex items-center justify-center\">\r\n <LR.Loader2 className=\"h-4 w-4 animate-spin text-muted-foreground\" />\r\n </div>\r\n ) : (\r\n <Avatar className=\"h-full w-full\">\r\n <UserAvatar\r\n imageFileId={resolvedImageFileId}\r\n previewSrc={previewSrc}\r\n name={userName}\r\n className=\"h-full w-full rounded-full object-cover\"\r\n />\r\n </Avatar>\r\n )}\r\n </div>\r\n )\r\n }\r\n\r\n return (\r\n <div\r\n className=\"relative inline-block group\"\r\n style={{ pointerEvents: 'auto', boxShadow: focused ? commentRingShadow({ focused }) : undefined, borderRadius: focused ? 12 : undefined, zIndex: focused ? 30 : highlight ? 20 : undefined }}\r\n title={onFocus ? 'Double click to zoom' : enableCollapse ? 'Double click to collapse' : undefined}\r\n // In the 3D card, detect a double-press here (dblclick is unreliable under the CSS2D\r\n // renderer) to trigger zoom. The map popup keeps the normal onDoubleClick path below.\r\n onPointerDown={(e) => {\r\n e.stopPropagation()\r\n if (!enableCollapse) return\r\n const now = performance.now()\r\n if (now - lastTap.current < 300) onFocus?.()\r\n lastTap.current = now\r\n }}\r\n onDoubleClick={(e) => {\r\n e.stopPropagation()\r\n if (!enableCollapse) onClose?.()\r\n }}\r\n >\r\n <Card className=\"w-48 px-3 py-2.5 shadow-md group-hover:shadow-lg transition-shadow duration-200\">\r\n <div className=\"flex items-start gap-3\">\r\n <div className=\"h-9 w-9 rounded-full shadow-sm flex items-center justify-center select-none flex-shrink-0 bg-card\">\r\n {isPending ? (\r\n <div className=\"h-10 w-10 rounded-full bg-muted flex items-center justify-center\">\r\n <LR.Loader2 className=\"h-5 w-5 animate-spin text-muted-foreground\" />\r\n </div>\r\n ) : (\r\n <Avatar className=\"h-10 w-10\">\r\n <UserAvatar\r\n imageFileId={resolvedImageFileId}\r\n previewSrc={previewSrc}\r\n name={userName}\r\n className=\"h-full w-full rounded-full object-cover\"\r\n />\r\n </Avatar>\r\n )}\r\n </div>\r\n\r\n <div className=\"flex-1 min-w-0 flex flex-col gap-1\">\r\n <span className=\"font-medium text-sm text-foreground leading-none truncate\">\r\n {userName}\r\n </span>\r\n <span className=\"text-xs text-muted-foreground leading-none\">\r\n {formatTimestamp(createdAt)}\r\n </span>\r\n </div>\r\n\r\n {showActions && (\r\n <Button\r\n type=\"button\"\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n title={actionLabels?.close ?? 'Close'}\r\n aria-label={actionLabels?.close ?? 'Close'}\r\n className=\"h-7 w-7 shrink-0 -mr-1 -mt-1\"\r\n // In the CSS2D 3D card (enableCollapse) activate on pointerdown for reliability;\r\n // the map popup is normal DOM, so keep click for keyboard accessibility.\r\n {...(enableCollapse\r\n ? { onPointerDown: (e: React.PointerEvent) => { e.stopPropagation(); handleClose() } }\r\n : { onClick: (e: React.MouseEvent) => { e.stopPropagation(); handleClose() } })}\r\n >\r\n <LR.X className=\"h-4 w-4\" />\r\n </Button>\r\n )}\r\n </div>\r\n\r\n <div className=\"mt-2 flex items-start gap-2\">\r\n <p className=\"min-w-0 flex-1 text-sm leading-relaxed text-foreground whitespace-pre-wrap break-words\">\r\n {text}\r\n </p>\r\n\r\n {/* Map popup keeps the inline delete; the BIM card uses the action row below instead. */}\r\n {!showActions && onRemove && (\r\n <Button\r\n type=\"button\"\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n title={actionLabels?.delete ?? 'Remove comment'}\r\n className=\"z-50 h-7 w-7 shrink-0\"\r\n aria-label={actionLabels?.delete ?? 'Remove comment'}\r\n onClick={(e) => {\r\n e.stopPropagation()\r\n onRemove()\r\n }}\r\n >\r\n <LR.Trash2 className=\"h-4 w-4\" />\r\n </Button>\r\n )}\r\n </div>\r\n\r\n {showActions && (onReply || onEdit || onRemove) && (\r\n <div className=\"mt-2 flex justify-end border-t pt-1\">\r\n <CommentActionButtons\r\n onReply={onReply}\r\n onEdit={onEdit}\r\n onDelete={onRemove}\r\n canReply={canReply}\r\n canEdit={canEdit}\r\n canDelete={canDelete}\r\n labels={actionLabels}\r\n buttonClassName=\"h-7 w-7\"\r\n activateOnPointerDown={enableCollapse}\r\n />\r\n </div>\r\n )}\r\n </Card>\r\n </div>\r\n )\r\n}\r\n"],"mappings":"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","names":[]}
@@ -4,9 +4,13 @@ export interface CommentActionLabels {
4
4
  edit?: string;
5
5
  delete?: string;
6
6
  close?: string;
7
+ expand?: string;
8
+ tag?: string;
7
9
  }
8
10
  interface CommentActionButtonsProps {
9
11
  onReply?: () => void;
12
+ onExpand?: () => void;
13
+ onTag?: () => void;
10
14
  onEdit?: () => void;
11
15
  onDelete?: () => void;
12
16
  onClose?: () => void;
@@ -29,6 +33,6 @@ interface CommentActionButtonsProps {
29
33
  * card renders in a detached React root without the next-intl provider, so labels are
30
34
  * passed in and gating is expressed by which handlers/`can*` flags the caller supplies.
31
35
  */
32
- export declare function CommentActionButtons({ onReply, onEdit, onDelete, onClose, canReply, canEdit, canDelete, labels, className, buttonClassName, activateOnPointerDown, }: CommentActionButtonsProps): React.ReactElement;
36
+ export declare function CommentActionButtons({ onReply, onExpand, onTag, onEdit, onDelete, onClose, canReply, canEdit, canDelete, labels, className, buttonClassName, activateOnPointerDown, }: CommentActionButtonsProps): React.ReactElement;
33
37
  export {};
34
38
  //# sourceMappingURL=CommentActionButtons.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CommentActionButtons.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/ui/Comments/CommentActionButtons.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,UAAU,yBAAyB;IACjC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,MAAM,CAAC,EAAE,mBAAmB,CAAA;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4FAA4F;IAC5F,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAChC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EACP,MAAM,EACN,QAAQ,EACR,OAAO,EACP,QAAe,EACf,OAAc,EACd,SAAgB,EAChB,MAAM,EACN,SAAS,EACT,eAAe,EACf,qBAA6B,GAC9B,EAAE,yBAAyB,GAAG,KAAK,CAAC,YAAY,CAuEhD"}
1
+ {"version":3,"file":"CommentActionButtons.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/ui/Comments/CommentActionButtons.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,UAAU,yBAAyB;IACjC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,KAAK,CAAC,EAAE,MAAM,IAAI,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,MAAM,CAAC,EAAE,mBAAmB,CAAA;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4FAA4F;IAC5F,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAChC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EACP,QAAQ,EACR,KAAK,EACL,MAAM,EACN,QAAQ,EACR,OAAO,EACP,QAAe,EACf,OAAc,EACd,SAAgB,EAChB,MAAM,EACN,SAAS,EACT,eAAe,EACf,qBAA6B,GAC9B,EAAE,yBAAyB,GAAG,KAAK,CAAC,YAAY,CAiGhD"}
@@ -24,6 +24,8 @@ import { cn } from "../../../utils/utils";
24
24
  import { Button } from "../Button";
25
25
  function CommentActionButtons({
26
26
  onReply,
27
+ onExpand,
28
+ onTag,
27
29
  onEdit,
28
30
  onDelete,
29
31
  onClose,
@@ -35,7 +37,7 @@ function CommentActionButtons({
35
37
  buttonClassName,
36
38
  activateOnPointerDown = false
37
39
  }) {
38
- var _a, _b, _c, _d, _e, _f, _g, _h;
40
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
39
41
  const activate = (handler) => (e) => {
40
42
  e.stopPropagation();
41
43
  handler == null ? void 0 : handler();
@@ -43,6 +45,32 @@ function CommentActionButtons({
43
45
  const bind = (handler) => activateOnPointerDown ? { onPointerDown: activate(handler) } : { onClick: activate(handler) };
44
46
  const btn = cn("h-8 w-8 p-0", buttonClassName);
45
47
  return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-0 flex-shrink-0", className), children: [
48
+ onExpand && /* @__PURE__ */ jsx(
49
+ Button,
50
+ __spreadProps(__spreadValues({
51
+ type: "button",
52
+ variant: "ghost",
53
+ size: "icon",
54
+ className: btn
55
+ }, bind(onExpand)), {
56
+ title: (_a = labels == null ? void 0 : labels.expand) != null ? _a : "Expand",
57
+ "aria-label": (_b = labels == null ? void 0 : labels.expand) != null ? _b : "Expand",
58
+ children: /* @__PURE__ */ jsx(LR.Maximize2, { className: "h-4 w-4" })
59
+ })
60
+ ),
61
+ onTag && /* @__PURE__ */ jsx(
62
+ Button,
63
+ __spreadProps(__spreadValues({
64
+ type: "button",
65
+ variant: "ghost",
66
+ size: "icon",
67
+ className: btn
68
+ }, bind(onTag)), {
69
+ title: (_c = labels == null ? void 0 : labels.tag) != null ? _c : "Add tag",
70
+ "aria-label": (_d = labels == null ? void 0 : labels.tag) != null ? _d : "Add tag",
71
+ children: /* @__PURE__ */ jsx(LR.Tag, { className: "h-4 w-4" })
72
+ })
73
+ ),
46
74
  onReply && /* @__PURE__ */ jsx(
47
75
  Button,
48
76
  __spreadProps(__spreadValues({
@@ -51,8 +79,8 @@ function CommentActionButtons({
51
79
  size: "icon",
52
80
  className: btn
53
81
  }, bind(onReply)), {
54
- title: (_a = labels == null ? void 0 : labels.reply) != null ? _a : "Reply",
55
- "aria-label": (_b = labels == null ? void 0 : labels.reply) != null ? _b : "Reply",
82
+ title: (_e = labels == null ? void 0 : labels.reply) != null ? _e : "Reply",
83
+ "aria-label": (_f = labels == null ? void 0 : labels.reply) != null ? _f : "Reply",
56
84
  disabled: !canReply,
57
85
  children: /* @__PURE__ */ jsx(LR.Reply, { className: "h-4 w-4" })
58
86
  })
@@ -65,8 +93,8 @@ function CommentActionButtons({
65
93
  size: "icon",
66
94
  className: btn
67
95
  }, bind(onEdit)), {
68
- title: (_c = labels == null ? void 0 : labels.edit) != null ? _c : "Edit",
69
- "aria-label": (_d = labels == null ? void 0 : labels.edit) != null ? _d : "Edit",
96
+ title: (_g = labels == null ? void 0 : labels.edit) != null ? _g : "Edit",
97
+ "aria-label": (_h = labels == null ? void 0 : labels.edit) != null ? _h : "Edit",
70
98
  disabled: !canEdit,
71
99
  children: /* @__PURE__ */ jsx(LR.Pencil, { className: "h-4 w-4" })
72
100
  })
@@ -79,8 +107,8 @@ function CommentActionButtons({
79
107
  size: "icon",
80
108
  className: btn
81
109
  }, bind(onDelete)), {
82
- title: (_e = labels == null ? void 0 : labels.delete) != null ? _e : "Delete",
83
- "aria-label": (_f = labels == null ? void 0 : labels.delete) != null ? _f : "Delete",
110
+ title: (_i = labels == null ? void 0 : labels.delete) != null ? _i : "Delete",
111
+ "aria-label": (_j = labels == null ? void 0 : labels.delete) != null ? _j : "Delete",
84
112
  disabled: !canDelete,
85
113
  children: /* @__PURE__ */ jsx(LR.Trash2, { className: "h-4 w-4" })
86
114
  })
@@ -93,8 +121,8 @@ function CommentActionButtons({
93
121
  size: "icon",
94
122
  className: btn
95
123
  }, bind(onClose)), {
96
- title: (_g = labels == null ? void 0 : labels.close) != null ? _g : "Close",
97
- "aria-label": (_h = labels == null ? void 0 : labels.close) != null ? _h : "Close",
124
+ title: (_k = labels == null ? void 0 : labels.close) != null ? _k : "Close",
125
+ "aria-label": (_l = labels == null ? void 0 : labels.close) != null ? _l : "Close",
98
126
  children: /* @__PURE__ */ jsx(LR.X, { className: "h-4 w-4" })
99
127
  })
100
128
  )
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/core/components/ui/Comments/CommentActionButtons.tsx"],"sourcesContent":["'use client'\n\n// SPDX-License-Identifier: AGPL-3.0-or-later\n// Copyright (C) 2025 Collab Digital Twins\n\nimport * as LR from 'lucide-react'\nimport * as React from 'react'\n\nimport { cn } from '../../../utils/utils'\nimport { Button } from '../Button'\n\nexport interface CommentActionLabels {\n reply?: string\n edit?: string\n delete?: string\n close?: string\n}\n\ninterface CommentActionButtonsProps {\n onReply?: () => void\n onEdit?: () => void\n onDelete?: () => void\n onClose?: () => void\n canReply?: boolean\n canEdit?: boolean\n canDelete?: boolean\n labels?: CommentActionLabels\n className?: string\n /** Applied to each icon button; lets the caller size the row (sidebar h-8, 3D card h-7). */\n buttonClassName?: string\n /**\n * Fire on pointerdown instead of click. Needed inside the 3D comment card: the CSS2D renderer\n * re-transforms the element every frame, so a browser \"click\" frequently never fires there.\n */\n activateOnPointerDown?: boolean\n}\n\n/**\n * Presentational reply/edit/delete/close row shared by the sidebar comment item and the\n * in-viewer comment card. Intentionally free of i18n and permission hooks: the in-viewer\n * card renders in a detached React root without the next-intl provider, so labels are\n * passed in and gating is expressed by which handlers/`can*` flags the caller supplies.\n */\nexport function CommentActionButtons({\n onReply,\n onEdit,\n onDelete,\n onClose,\n canReply = true,\n canEdit = true,\n canDelete = true,\n labels,\n className,\n buttonClassName,\n activateOnPointerDown = false,\n}: CommentActionButtonsProps): React.ReactElement {\n const activate = (handler?: () => void) => (e: React.MouseEvent | React.PointerEvent) => {\n e.stopPropagation()\n handler?.()\n }\n\n // Bind the action to pointerdown (3D card) or click (sidebar). Returns the right prop object.\n const bind = (handler?: () => void) =>\n activateOnPointerDown ? { onPointerDown: activate(handler) } : { onClick: activate(handler) }\n\n const btn = cn('h-8 w-8 p-0', buttonClassName)\n\n return (\n <div className={cn('flex items-center gap-0 flex-shrink-0', className)}>\n {onReply && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className={btn}\n {...bind(onReply)}\n title={labels?.reply ?? 'Reply'}\n aria-label={labels?.reply ?? 'Reply'}\n disabled={!canReply}\n >\n <LR.Reply className=\"h-4 w-4\" />\n </Button>\n )}\n {onEdit && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className={btn}\n {...bind(onEdit)}\n title={labels?.edit ?? 'Edit'}\n aria-label={labels?.edit ?? 'Edit'}\n disabled={!canEdit}\n >\n <LR.Pencil className=\"h-4 w-4\" />\n </Button>\n )}\n {onDelete && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className={btn}\n {...bind(onDelete)}\n title={labels?.delete ?? 'Delete'}\n aria-label={labels?.delete ?? 'Delete'}\n disabled={!canDelete}\n >\n <LR.Trash2 className=\"h-4 w-4\" />\n </Button>\n )}\n {onClose && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className={btn}\n {...bind(onClose)}\n title={labels?.close ?? 'Close'}\n aria-label={labels?.close ?? 'Close'}\n >\n <LR.X className=\"h-4 w-4\" />\n </Button>\n )}\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoEI,SAYM,KAZN;AA/DJ,YAAY,QAAQ;AAGpB,SAAS,UAAU;AACnB,SAAS,cAAc;AAkChB,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,UAAU;AAAA,EACV,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA,wBAAwB;AAC1B,GAAkD;AAvDlD;AAwDE,QAAM,WAAW,CAAC,YAAyB,CAAC,MAA6C;AACvF,MAAE,gBAAgB;AAClB;AAAA,EACF;AAGA,QAAM,OAAO,CAAC,YACZ,wBAAwB,EAAE,eAAe,SAAS,OAAO,EAAE,IAAI,EAAE,SAAS,SAAS,OAAO,EAAE;AAE9F,QAAM,MAAM,GAAG,eAAe,eAAe;AAE7C,SACE,qBAAC,SAAI,WAAW,GAAG,yCAAyC,SAAS,GAClE;AAAA,eACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,WAAW;AAAA,SACP,KAAK,OAAO,IALjB;AAAA,QAMC,QAAO,sCAAQ,UAAR,YAAiB;AAAA,QACxB,eAAY,sCAAQ,UAAR,YAAiB;AAAA,QAC7B,UAAU,CAAC;AAAA,QAEX,8BAAC,GAAG,OAAH,EAAS,WAAU,WAAU;AAAA;AAAA,IAChC;AAAA,IAED,UACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,WAAW;AAAA,SACP,KAAK,MAAM,IALhB;AAAA,QAMC,QAAO,sCAAQ,SAAR,YAAgB;AAAA,QACvB,eAAY,sCAAQ,SAAR,YAAgB;AAAA,QAC5B,UAAU,CAAC;AAAA,QAEX,8BAAC,GAAG,QAAH,EAAU,WAAU,WAAU;AAAA;AAAA,IACjC;AAAA,IAED,YACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,WAAW;AAAA,SACP,KAAK,QAAQ,IALlB;AAAA,QAMC,QAAO,sCAAQ,WAAR,YAAkB;AAAA,QACzB,eAAY,sCAAQ,WAAR,YAAkB;AAAA,QAC9B,UAAU,CAAC;AAAA,QAEX,8BAAC,GAAG,QAAH,EAAU,WAAU,WAAU;AAAA;AAAA,IACjC;AAAA,IAED,WACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,WAAW;AAAA,SACP,KAAK,OAAO,IALjB;AAAA,QAMC,QAAO,sCAAQ,UAAR,YAAiB;AAAA,QACxB,eAAY,sCAAQ,UAAR,YAAiB;AAAA,QAE7B,8BAAC,GAAG,GAAH,EAAK,WAAU,WAAU;AAAA;AAAA,IAC5B;AAAA,KAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../../src/core/components/ui/Comments/CommentActionButtons.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 { cn } from '../../../utils/utils'\r\nimport { Button } from '../Button'\r\n\r\nexport interface CommentActionLabels {\r\n reply?: string\r\n edit?: string\r\n delete?: string\r\n close?: string\r\n expand?: string\r\n tag?: string\r\n}\r\n\r\ninterface CommentActionButtonsProps {\r\n onReply?: () => void\r\n onExpand?: () => void\r\n onTag?: () => void\r\n onEdit?: () => void\r\n onDelete?: () => void\r\n onClose?: () => void\r\n canReply?: boolean\r\n canEdit?: boolean\r\n canDelete?: boolean\r\n labels?: CommentActionLabels\r\n className?: string\r\n /** Applied to each icon button; lets the caller size the row (sidebar h-8, 3D card h-7). */\r\n buttonClassName?: string\r\n /**\r\n * Fire on pointerdown instead of click. Needed inside the 3D comment card: the CSS2D renderer\r\n * re-transforms the element every frame, so a browser \"click\" frequently never fires there.\r\n */\r\n activateOnPointerDown?: boolean\r\n}\r\n\r\n/**\r\n * Presentational reply/edit/delete/close row shared by the sidebar comment item and the\r\n * in-viewer comment card. Intentionally free of i18n and permission hooks: the in-viewer\r\n * card renders in a detached React root without the next-intl provider, so labels are\r\n * passed in and gating is expressed by which handlers/`can*` flags the caller supplies.\r\n */\r\nexport function CommentActionButtons({\r\n onReply,\r\n onExpand,\r\n onTag,\r\n onEdit,\r\n onDelete,\r\n onClose,\r\n canReply = true,\r\n canEdit = true,\r\n canDelete = true,\r\n labels,\r\n className,\r\n buttonClassName,\r\n activateOnPointerDown = false,\r\n}: CommentActionButtonsProps): React.ReactElement {\r\n const activate = (handler?: () => void) => (e: React.MouseEvent | React.PointerEvent) => {\r\n e.stopPropagation()\r\n handler?.()\r\n }\r\n\r\n // Bind the action to pointerdown (3D card) or click (sidebar). Returns the right prop object.\r\n const bind = (handler?: () => void) =>\r\n activateOnPointerDown ? { onPointerDown: activate(handler) } : { onClick: activate(handler) }\r\n\r\n const btn = cn('h-8 w-8 p-0', buttonClassName)\r\n\r\n return (\r\n <div className={cn('flex items-center gap-0 flex-shrink-0', className)}>\r\n {onExpand && (\r\n <Button\r\n type=\"button\"\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n className={btn}\r\n {...bind(onExpand)}\r\n title={labels?.expand ?? 'Expand'}\r\n aria-label={labels?.expand ?? 'Expand'}\r\n >\r\n <LR.Maximize2 className=\"h-4 w-4\" />\r\n </Button>\r\n )}\r\n {onTag && (\r\n <Button\r\n type=\"button\"\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n className={btn}\r\n {...bind(onTag)}\r\n title={labels?.tag ?? 'Add tag'}\r\n aria-label={labels?.tag ?? 'Add tag'}\r\n >\r\n <LR.Tag className=\"h-4 w-4\" />\r\n </Button>\r\n )}\r\n {onReply && (\r\n <Button\r\n type=\"button\"\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n className={btn}\r\n {...bind(onReply)}\r\n title={labels?.reply ?? 'Reply'}\r\n aria-label={labels?.reply ?? 'Reply'}\r\n disabled={!canReply}\r\n >\r\n <LR.Reply className=\"h-4 w-4\" />\r\n </Button>\r\n )}\r\n {onEdit && (\r\n <Button\r\n type=\"button\"\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n className={btn}\r\n {...bind(onEdit)}\r\n title={labels?.edit ?? 'Edit'}\r\n aria-label={labels?.edit ?? 'Edit'}\r\n disabled={!canEdit}\r\n >\r\n <LR.Pencil className=\"h-4 w-4\" />\r\n </Button>\r\n )}\r\n {onDelete && (\r\n <Button\r\n type=\"button\"\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n className={btn}\r\n {...bind(onDelete)}\r\n title={labels?.delete ?? 'Delete'}\r\n aria-label={labels?.delete ?? 'Delete'}\r\n disabled={!canDelete}\r\n >\r\n <LR.Trash2 className=\"h-4 w-4\" />\r\n </Button>\r\n )}\r\n {onClose && (\r\n <Button\r\n type=\"button\"\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n className={btn}\r\n {...bind(onClose)}\r\n title={labels?.close ?? 'Close'}\r\n aria-label={labels?.close ?? 'Close'}\r\n >\r\n <LR.X className=\"h-4 w-4\" />\r\n </Button>\r\n )}\r\n </div>\r\n )\r\n}\r\n"],"mappings":"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","names":[]}
@@ -9,8 +9,7 @@ interface CollapsibleSensorItemProps {
9
9
  isVisible?: boolean;
10
10
  onMouseEnter?: () => void;
11
11
  onMouseLeave?: () => void;
12
- minioBaseUrl?: string;
13
12
  }
14
- export declare function CollapsibleSensorItem({ sensor, sensorType, onAction, depth, isVisible, onMouseEnter, onMouseLeave, minioBaseUrl }: CollapsibleSensorItemProps): React.JSX.Element;
13
+ export declare function CollapsibleSensorItem({ sensor, sensorType, onAction, depth, isVisible, onMouseEnter, onMouseLeave, }: CollapsibleSensorItemProps): React.JSX.Element;
15
14
  export {};
16
15
  //# sourceMappingURL=CollapsibleSensorItem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CollapsibleSensorItem.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/ui/Sensors/CollapsibleSensorItem.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAc9B,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAGhE,KAAK,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAA;AAExD,UAAU,0BAA0B;IAClC,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IACrD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAA;IACzB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAA;IACzB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,wBAAgB,qBAAqB,CAAC,EACpC,MAAM,EACN,UAAU,EACV,QAAQ,EACR,KAAS,EACT,SAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,YAAY,EACb,EAAE,0BAA0B,qBAwO5B"}
1
+ {"version":3,"file":"CollapsibleSensorItem.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/ui/Sensors/CollapsibleSensorItem.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAmB9B,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAGhE,KAAK,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAA;AAExD,UAAU,0BAA0B;IAClC,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IACrD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAA;IACzB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAA;CAC1B;AAED,wBAAgB,qBAAqB,CAAC,EACpC,MAAM,EACN,UAAU,EACV,QAAQ,EACR,KAAS,EACT,SAAgB,EAChB,YAAY,EACZ,YAAY,GACb,EAAE,0BAA0B,qBAyL5B"}
@@ -1,20 +1,24 @@
1
1
  "use client";
2
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import { format } from "date-fns";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
4
3
  import * as LR from "lucide-react";
5
4
  import { useSession } from "next-auth/react";
6
5
  import { useTranslations } from "next-intl";
7
6
  import * as React from "react";
8
7
  import { useCoreHooks } from "../../../hooks/provider";
9
8
  import { useUser } from "../../../hooks/users/users";
9
+ import { AppConfigContext } from "../../../store";
10
+ import { formatInZone } from "../../../utils/timeUtils";
10
11
  import { cn } from "../../../utils/utils";
11
12
  import { Avatar } from "../Avatar";
12
13
  import { Badge } from "../Badge";
13
14
  import { Button } from "../Button";
15
+ import { CommentActionButtons } from "../Comments/CommentActionButtons";
14
16
  import { UserAvatar } from "../UserAvatar";
15
17
  import { SensorChart } from "./SensorChart";
18
+ import { SensorDetailDialog } from "./SensorDetailDialog";
16
19
  import { SensorTagsSection } from "./SensorTagsSection";
17
20
  import { resolveLucideIcon } from "./sensorUtils";
21
+ import { useSensorSeries } from "./useSensorSeries";
18
22
  function CollapsibleSensorItem({
19
23
  sensor,
20
24
  sensorType,
@@ -22,17 +26,18 @@ function CollapsibleSensorItem({
22
26
  depth = 0,
23
27
  isVisible = true,
24
28
  onMouseEnter,
25
- onMouseLeave,
26
- minioBaseUrl
29
+ onMouseLeave
27
30
  }) {
28
- var _a, _b, _c, _d;
31
+ var _a, _b, _c, _d, _e;
29
32
  const t = useTranslations("SensorsSection");
30
33
  const [isExpanded, setIsExpanded] = React.useState(false);
31
- const [sensorData, setSensorData] = React.useState([]);
32
- const [isLoadingData, setIsLoadingData] = React.useState(false);
34
+ const { state: appConfigState } = React.useContext(AppConfigContext);
35
+ const timeZone = appConfigState.appConfig.displayTimeZone;
36
+ const [detailOpen, setDetailOpen] = React.useState(false);
37
+ const [tagAddNonce, setTagAddNonce] = React.useState(0);
38
+ const { points: sensorData, unit, valueLabels, isLoading: isLoadingData } = useSensorSeries((_a = sensor.url) != null ? _a : "", sensor.dataFormat, sensor.updateFrequency, { enabled: isExpanded });
33
39
  const prevVisibleRef = React.useRef(isVisible);
34
- const dataPath = minioBaseUrl ? `${minioBaseUrl}/sensors/${sensor.url}` : "";
35
- const typeName = (_a = sensorType == null ? void 0 : sensorType.name.replace(/_/g, " ")) != null ? _a : "Unknown";
40
+ const typeName = (_b = sensorType == null ? void 0 : sensorType.name.replace(/_/g, " ")) != null ? _b : "Unknown";
36
41
  const SensorIcon = resolveLucideIcon(sensorType == null ? void 0 : sensorType.icon);
37
42
  React.useEffect(() => {
38
43
  const prevVisible = prevVisibleRef.current;
@@ -41,42 +46,16 @@ function CollapsibleSensorItem({
41
46
  }
42
47
  prevVisibleRef.current = isVisible;
43
48
  }, [isVisible]);
44
- React.useEffect(() => {
45
- const fetchCSVData = async () => {
46
- setIsLoadingData(true);
47
- try {
48
- const response = await fetch(dataPath);
49
- const csvText = await response.text();
50
- const lines = csvText.trim().split("\n");
51
- const parsed = lines.map((line) => {
52
- const [time, valueStr] = line.split(",");
53
- return {
54
- time: time.trim(),
55
- value: parseFloat(valueStr.trim())
56
- };
57
- }).filter((item) => !isNaN(item.value));
58
- setSensorData(parsed);
59
- } catch (error) {
60
- console.error("Error fetching sensor data:", error);
61
- setSensorData([]);
62
- } finally {
63
- setIsLoadingData(false);
64
- }
65
- };
66
- if (isExpanded) {
67
- fetchCSVData();
68
- }
69
- }, [dataPath, isExpanded]);
70
49
  const chartConfig = {
71
50
  value: {
72
51
  label: typeName,
73
52
  color: "hsl(var(--chart-1))"
74
53
  }
75
54
  };
76
- const user = (_b = useSession().data) == null ? void 0 : _b.user;
55
+ const user = (_c = useSession().data) == null ? void 0 : _c.user;
77
56
  const indentClass = depth > 0 ? `ml-${Math.min(depth * 4, 12)}` : "";
78
57
  const { user: author } = useUser(String(sensor.authorId));
79
- const authorName = (_c = author == null ? void 0 : author.name) != null ? _c : "Unknown User";
58
+ const authorName = (_d = author == null ? void 0 : author.name) != null ? _d : "Unknown User";
80
59
  const { sensor: sensorHooks } = useCoreHooks();
81
60
  const { updateSensor } = sensorHooks.useSensor(sensor.id);
82
61
  const handleAddTag = async (tag) => {
@@ -100,65 +79,39 @@ function CollapsibleSensorItem({
100
79
  onMouseEnter,
101
80
  onMouseLeave,
102
81
  children: [
103
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between p-3 hover:bg-accent/50 transition-colors", children: [
104
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 flex-1 min-w-0", children: [
105
- /* @__PURE__ */ jsx(
106
- Button,
107
- {
108
- variant: "ghost",
109
- size: "sm",
110
- className: cn("h-6 w-6 p-0 mt-1", !isVisible && "opacity-70"),
111
- onClick: () => setIsExpanded(!isExpanded),
112
- children: /* @__PURE__ */ jsx(
113
- LR.ChevronRight,
114
- {
115
- className: cn(
116
- "h-4 w-4 transition-transform",
117
- isExpanded && "rotate-90"
118
- )
119
- }
120
- )
121
- }
122
- ),
123
- /* @__PURE__ */ jsx("div", { className: cn(
124
- "h-8 w-8 mt-1 rounded-full bg-primary flex items-center justify-center flex-shrink-0",
125
- !isVisible && "opacity-70 grayscale"
126
- ), children: /* @__PURE__ */ jsx(SensorIcon, { className: cn("h-4 w-4 text-primary-foreground", !isVisible && "opacity-70") }) }),
127
- /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
128
- /* @__PURE__ */ jsx("span", { className: cn("text-sm font-medium", isVisible ? "text-foreground" : "text-muted-foreground"), children: sensor.name }),
129
- sensor.updatedAt !== sensor.createdAt && /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "text-xs", children: t("edited") })
130
- ] }) })
131
- ] }),
132
- onAction && /* @__PURE__ */ jsx("div", { className: cn("flex items-center gap-0 flex-shrink-0", !isVisible && "opacity-70"), children: (user == null ? void 0 : user.id) === String(sensor.authorId) && /* @__PURE__ */ jsxs(Fragment, { children: [
133
- /* @__PURE__ */ jsx(
134
- Button,
135
- {
136
- variant: "ghost",
137
- size: "icon",
138
- className: "h-8 w-8 p-0",
139
- onClick: () => onAction("edit", sensor.id),
140
- title: t("editSensor"),
141
- children: /* @__PURE__ */ jsx(LR.Pencil, { className: "h-4 w-4" })
142
- }
143
- ),
144
- /* @__PURE__ */ jsx(
145
- Button,
146
- {
147
- variant: "ghost",
148
- size: "icon",
149
- className: "h-8 w-8 p-0",
150
- onClick: () => onAction("delete", sensor.id),
151
- title: t("deleteSensor"),
152
- children: /* @__PURE__ */ jsx(LR.Trash2, { className: "h-4 w-4" })
153
- }
154
- )
82
+ /* @__PURE__ */ jsx("div", { className: "flex items-start justify-between p-3 hover:bg-accent/50 transition-colors", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 flex-1 min-w-0", children: [
83
+ /* @__PURE__ */ jsx(
84
+ Button,
85
+ {
86
+ variant: "ghost",
87
+ size: "sm",
88
+ className: cn("h-6 w-6 p-0 mt-1", !isVisible && "opacity-70"),
89
+ onClick: () => setIsExpanded(!isExpanded),
90
+ children: /* @__PURE__ */ jsx(
91
+ LR.ChevronRight,
92
+ {
93
+ className: cn(
94
+ "h-4 w-4 transition-transform",
95
+ isExpanded && "rotate-90"
96
+ )
97
+ }
98
+ )
99
+ }
100
+ ),
101
+ /* @__PURE__ */ jsx("div", { className: cn(
102
+ "h-8 w-8 mt-1 rounded-full bg-primary flex items-center justify-center flex-shrink-0",
103
+ !isVisible && "opacity-70 grayscale"
104
+ ), children: /* @__PURE__ */ jsx(SensorIcon, { className: cn("h-4 w-4 text-primary-foreground", !isVisible && "opacity-70") }) }),
105
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
106
+ /* @__PURE__ */ jsx("span", { className: cn("text-sm font-medium", isVisible ? "text-foreground" : "text-muted-foreground"), children: sensor.name }),
107
+ sensor.updatedAt !== sensor.createdAt && /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "text-xs", children: t("edited") })
155
108
  ] }) })
156
- ] }),
109
+ ] }) }),
157
110
  isExpanded && /* @__PURE__ */ jsxs("div", { className: "border-t bg-muted/30", children: [
158
111
  /* @__PURE__ */ jsxs("div", { className: "p-4 space-y-4", children: [
159
112
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
160
113
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
161
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: format(new Date(sensor.createdAt), "PPp") }),
114
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: formatInZone(new Date(sensor.createdAt).getTime(), timeZone, { dateStyle: "medium", timeStyle: "short" }) }),
162
115
  /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-xs", children: typeName })
163
116
  ] }),
164
117
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2 text-sm", children: [
@@ -185,16 +138,20 @@ function CollapsibleSensorItem({
185
138
  sensorName: sensor.name,
186
139
  sensorType,
187
140
  updateFrequency: sensor.updateFrequency,
188
- chartConfig
141
+ chartConfig,
142
+ unit,
143
+ valueLabels,
144
+ timeZone
189
145
  }
190
146
  )
191
147
  ] }),
192
148
  /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
193
149
  SensorTagsSection,
194
150
  {
195
- tags: (_d = sensor.tags) != null ? _d : [],
151
+ tags: (_e = sensor.tags) != null ? _e : [],
196
152
  onAdd: handleAddTag,
197
153
  onDelete: handleDeleteTag,
154
+ openAddSignal: tagAddNonce,
198
155
  translations: {
199
156
  addTag: t("addTag"),
200
157
  removeTag: t("removeTag"),
@@ -203,7 +160,22 @@ function CollapsibleSensorItem({
203
160
  }
204
161
  }
205
162
  ) })
206
- ] })
163
+ ] }),
164
+ /* @__PURE__ */ jsx("div", { className: cn("flex items-center justify-end gap-0 border-t px-2 py-1", !isVisible && "opacity-70"), children: /* @__PURE__ */ jsx(
165
+ CommentActionButtons,
166
+ {
167
+ onExpand: () => setDetailOpen(true),
168
+ onTag: () => {
169
+ setIsExpanded(true);
170
+ setTagAddNonce((n) => n + 1);
171
+ },
172
+ onEdit: onAction && (user == null ? void 0 : user.id) === String(sensor.authorId) ? () => onAction("edit", sensor.id) : void 0,
173
+ onDelete: onAction && (user == null ? void 0 : user.id) === String(sensor.authorId) ? () => onAction("delete", sensor.id) : void 0,
174
+ buttonClassName: "h-8 w-8",
175
+ labels: { expand: t("expandSensor"), tag: t("addTag"), edit: t("editSensor"), delete: t("deleteSensor") }
176
+ }
177
+ ) }),
178
+ /* @__PURE__ */ jsx(SensorDetailDialog, { open: detailOpen, onOpenChange: setDetailOpen, sensor, sensorType })
207
179
  ]
208
180
  }
209
181
  );