@collabdt/core 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/components/ui/Comments/CollapsibleCommentItem.js.map +1 -1
- package/dist/core/components/ui/Comments/Comment.js.map +1 -1
- package/dist/core/components/ui/Comments/CommentActionButtons.d.ts +5 -1
- package/dist/core/components/ui/Comments/CommentActionButtons.d.ts.map +1 -1
- package/dist/core/components/ui/Comments/CommentActionButtons.js +37 -9
- package/dist/core/components/ui/Comments/CommentActionButtons.js.map +1 -1
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.d.ts +1 -2
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.js +66 -94
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.js.map +1 -1
- package/dist/core/components/ui/Sensors/Sensor.d.ts +16 -3
- package/dist/core/components/ui/Sensors/Sensor.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/Sensor.js +76 -37
- package/dist/core/components/ui/Sensors/Sensor.js.map +1 -1
- package/dist/core/components/ui/Sensors/SensorChart.d.ts +12 -2
- package/dist/core/components/ui/Sensors/SensorChart.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/SensorChart.js +49 -12
- package/dist/core/components/ui/Sensors/SensorChart.js.map +1 -1
- package/dist/core/components/ui/Sensors/SensorDetailDialog.d.ts +9 -0
- package/dist/core/components/ui/Sensors/SensorDetailDialog.d.ts.map +1 -0
- package/dist/core/components/ui/Sensors/SensorDetailDialog.js +128 -0
- package/dist/core/components/ui/Sensors/SensorDetailDialog.js.map +1 -0
- package/dist/core/components/ui/Sensors/SensorInput.d.ts +6 -1
- package/dist/core/components/ui/Sensors/SensorInput.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/SensorInput.js +94 -8
- package/dist/core/components/ui/Sensors/SensorInput.js.map +1 -1
- package/dist/core/components/ui/Sensors/SensorTagsSection.d.ts +3 -1
- package/dist/core/components/ui/Sensors/SensorTagsSection.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/SensorTagsSection.js +4 -1
- package/dist/core/components/ui/Sensors/SensorTagsSection.js.map +1 -1
- package/dist/core/components/ui/Sensors/SensorsSection.d.ts +1 -3
- package/dist/core/components/ui/Sensors/SensorsSection.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/SensorsSection.js +33 -8
- package/dist/core/components/ui/Sensors/SensorsSection.js.map +1 -1
- package/dist/core/components/ui/Sensors/TimeZoneSelect.d.ts +7 -0
- package/dist/core/components/ui/Sensors/TimeZoneSelect.d.ts.map +1 -0
- package/dist/core/components/ui/Sensors/TimeZoneSelect.js +56 -0
- package/dist/core/components/ui/Sensors/TimeZoneSelect.js.map +1 -0
- package/dist/core/components/ui/Sensors/sensorData.d.ts +42 -0
- package/dist/core/components/ui/Sensors/sensorData.d.ts.map +1 -0
- package/dist/core/components/ui/Sensors/sensorData.js +118 -0
- package/dist/core/components/ui/Sensors/sensorData.js.map +1 -0
- package/dist/core/components/ui/Sensors/sensorRange.d.ts +24 -0
- package/dist/core/components/ui/Sensors/sensorRange.d.ts.map +1 -0
- package/dist/core/components/ui/Sensors/sensorRange.js +34 -0
- package/dist/core/components/ui/Sensors/sensorRange.js.map +1 -0
- package/dist/core/components/ui/Sensors/useSensorSeries.d.ts +15 -0
- package/dist/core/components/ui/Sensors/useSensorSeries.d.ts.map +1 -0
- package/dist/core/components/ui/Sensors/useSensorSeries.js +63 -0
- package/dist/core/components/ui/Sensors/useSensorSeries.js.map +1 -0
- package/dist/core/components/viewers/Viewer.js +1 -1
- package/dist/core/components/viewers/Viewer.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/SensorsTab/index.d.ts +1 -3
- package/dist/core/components/viewers/bim/src/BimSidebar/src/SensorsTab/index.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/SensorsTab/index.js +2 -2
- package/dist/core/components/viewers/bim/src/BimSidebar/src/SensorsTab/index.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/index.js +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/index.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/index.d.ts +1 -2
- package/dist/core/components/viewers/bim/src/tools/AddToBim/index.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/index.js +25 -2
- package/dist/core/components/viewers/bim/src/tools/AddToBim/index.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.d.ts +1 -22
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.js +2 -105
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimSensor.d.ts +8 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimSensor.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimSensor.js +15 -3
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimSensor.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.d.ts +2 -23
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.js +2 -24
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentMarkerConfig.d.ts +33 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentMarkerConfig.d.ts.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentMarkerConfig.js +90 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentMarkerConfig.js.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.d.ts +7 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.js +26 -230
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.d.ts +7 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.js +39 -11
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.js.map +1 -1
- package/dist/core/components/viewers/map/MapViewer.d.ts +1 -2
- package/dist/core/components/viewers/map/MapViewer.d.ts.map +1 -1
- package/dist/core/components/viewers/map/MapViewer.js +2 -2
- package/dist/core/components/viewers/map/MapViewer.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/index.d.ts +1 -2
- package/dist/core/components/viewers/map/src/MapLayers/index.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/index.js +2 -2
- package/dist/core/components/viewers/map/src/MapLayers/index.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.d.ts +1 -3
- package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.js +74 -39
- package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/index.js +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/index.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.d.ts +1 -3
- package/dist/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.js +2 -2
- package/dist/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.js.map +1 -1
- package/dist/core/components/viewers/shared/markers/BimMarkerCluster.d.ts +19 -0
- package/dist/core/components/viewers/shared/markers/BimMarkerCluster.d.ts.map +1 -0
- package/dist/core/components/viewers/shared/markers/BimMarkerCluster.js +108 -0
- package/dist/core/components/viewers/shared/markers/BimMarkerCluster.js.map +1 -0
- package/dist/core/components/viewers/shared/markers/computeMarkerLookAt.d.ts +24 -0
- package/dist/core/components/viewers/shared/markers/computeMarkerLookAt.d.ts.map +1 -0
- package/dist/core/components/viewers/shared/markers/computeMarkerLookAt.js +27 -0
- package/dist/core/components/viewers/shared/markers/computeMarkerLookAt.js.map +1 -0
- package/dist/core/components/viewers/shared/markers/types.d.ts +66 -0
- package/dist/core/components/viewers/shared/markers/types.d.ts.map +1 -0
- package/dist/core/components/viewers/shared/markers/types.js +1 -0
- package/dist/core/components/viewers/shared/markers/types.js.map +1 -0
- package/dist/core/components/viewers/shared/markers/useMarkerLayerBim.d.ts +22 -0
- package/dist/core/components/viewers/shared/markers/useMarkerLayerBim.d.ts.map +1 -0
- package/dist/core/components/viewers/shared/markers/useMarkerLayerBim.js +233 -0
- package/dist/core/components/viewers/shared/markers/useMarkerLayerBim.js.map +1 -0
- package/dist/core/hooks/ui/useResizableSidebarWidth.js.map +1 -1
- package/dist/core/i18n/messages/en.json +5 -1
- package/dist/core/i18n/messages/es.json +5 -1
- package/dist/core/i18n/messages/fr.json +5 -1
- package/dist/core/store/AppConfig/context.d.ts.map +1 -1
- package/dist/core/store/AppConfig/context.js +4 -1
- package/dist/core/store/AppConfig/context.js.map +1 -1
- package/dist/core/store/AppConfig/reducer.d.ts +10 -0
- package/dist/core/store/AppConfig/reducer.d.ts.map +1 -1
- package/dist/core/store/AppConfig/reducer.js +10 -0
- package/dist/core/store/AppConfig/reducer.js.map +1 -1
- package/dist/core/store/Menus/context.d.ts.map +1 -1
- package/dist/core/store/Menus/context.js +5 -2
- package/dist/core/store/Menus/context.js.map +1 -1
- package/dist/core/store/Menus/reducer.d.ts +45 -1
- package/dist/core/store/Menus/reducer.d.ts.map +1 -1
- package/dist/core/store/Menus/reducer.js +21 -3
- package/dist/core/store/Menus/reducer.js.map +1 -1
- package/dist/core/utils/timeUtils.d.ts +24 -1
- package/dist/core/utils/timeUtils.d.ts.map +1 -1
- package/dist/core/utils/timeUtils.js +52 -3
- package/dist/core/utils/timeUtils.js.map +1 -1
- package/package.json +1 -1
|
@@ -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":";AAwJY,cAIA,YAJA;AAnJZ,YAAY,QAAQ;AACpB,SAAS,kBAAkB;AAC3B,SAAS,uBAAuB;AAChC,YAAY,WAAW;AAEvB,SAAS,eAAe;AACxB,SAAS,sBAAsB;AAC/B,SAAS,yBAAyB;AAClC,SAAS,uBAAuB;AAChC,SAAS,UAAU;AACnB,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB,SAAS,kBAAkB;AAE3B,SAAS,4BAA4B;AA8B9B,SAAS,uBAAuB;AAAA,EACrC;AAAA,EACA;AAAA,EACA,UAAU,CAAC;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAgC;AAjEhC;AAkEE,QAAM,IAAI,gBAAgB,iBAAiB;AAC3C,QAAM,EAAE,QAAQ,IAAI,eAAe;AAEnC,QAAM,UAAU,QAAQ;AACxB,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,KAAK;AACxD,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,EAAE;AACnD,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,KAAK;AACtD,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,QAAQ,IAAI;AAC3D,QAAM,eAAe,MAAM,OAAuB,IAAI;AACtD,QAAM,gBAAgB,MAAM,OAAyB,IAAI;AACzD,QAAM,iBAAiB,MAAM,OAAO,SAAS;AAE7C,QAAM,QAAO,gBAAW,EAAE,SAAb,mBAAmB;AAChC,QAAM,YAAW,6BAAM,QAAO,OAAO,QAAQ,QAAQ;AACrD,QAAM,WAAW,QAAQ,IAAI,UAAU,SAAS;AAChD,QAAM,UAAU,QAAQ,IAAI,UAAU,SAAS;AAC/C,QAAM,YAAY,QAAQ,IAAI,UAAU,SAAS;AAEjD,QAAM,EAAE,MAAM,OAAO,IAAI,QAAQ,OAAO,QAAQ,QAAQ,CAAC;AACzD,QAAM,cAAqB,sCAAQ,SAAR,YAAgB;AAG3C,QAAM,UAAU,MAAM;AACpB,QAAI,eAAe,WAAW,CAAC,UAAW,eAAc,KAAK;AAC7D,mBAAe,UAAU;AAAA,EAC3B,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,kBAAkB,MAAM,YAAY,MAAM;AAC9C,gBAAY,QAAQ,IAAI;AACxB,iBAAa,IAAI;AAAA,EACnB,GAAG,CAAC,QAAQ,IAAI,CAAC;AAIjB,QAAM,YAAY,MAAM,YAAY,MAAM;AACxC,kBAAc,IAAI;AAClB,0BAAsB,MAAG;AAtG7B,UAAAA;AAsGgC,cAAAA,MAAA,cAAc,YAAd,gBAAAA,IAAuB;AAAA,KAAO;AAAA,EAC5D,GAAG,CAAC,CAAC;AAGL,QAAM,UAAU,MAAM;AA1GxB,QAAAA;AA2GI,QAAI,CAAC,cAAe;AACpB,UAAM,cAAc,cAAc,cAAc,QAAQ;AACxD,UAAM,eAAe,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,cAAc,SAAS;AACzE,QAAI,aAAa;AACf,UAAI,cAAc,WAAW,OAAQ,iBAAgB;AAAA,UAChD,WAAU;AACf,OAAAA,MAAA,aAAa,YAAb,gBAAAA,IAAsB,eAAe,EAAE,UAAU,UAAU,OAAO,UAAU;AAAA,IAC9E;AACA,QAAI,aAAc,eAAc,IAAI;AAAA,EACtC,GAAG,CAAC,+CAAe,SAAS,CAAC;AAE7B,QAAM,oBAAoB,MAAM;AAC9B,QAAI,UAAU,KAAK,KAAK,SAAS;AAC/B,cAAQ,SAAS,SAAS;AAC1B,mBAAa,EAAE;AAAA,IACjB;AAAA,EACF;AAEA,QAAM,mBAAmB,MAAM;AAC7B,UAAM,OAAO,SAAS,KAAK;AAC3B,QAAI,QAAQ,SAAS,QAAQ,QAAQ,OAAQ,QAAO,QAAQ,IAAI,IAAI;AACpE,iBAAa,KAAK;AAAA,EACpB;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAW;AAAA,QACT;AAAA,QACA,CAAC,WAAW;AAAA,QACZ,WAAW;AAAA,MACb;AAAA,MACA,OAAO,UAAU,EAAE,WAAW,kBAAkB,EAAE,SAAS,KAAK,CAAC,GAAG,aAAa,cAAc,IAAI;AAAA,MACnG;AAAA,MACA;AAAA,MACA,eAAe,CAAC,MAAM;AACpB,UAAE,gBAAgB;AAClB;AAAA,MACF;AAAA,MACA,OAAO,UAAU,EAAE,iBAAiB,IAAI;AAAA,MAExC;AAAA,6BAAC,SAAI,WAAW,GAAG,6BAA6B,CAAC,WAAW,mDAAmD,WAAW,gBAAgB,GAExI;AAAA,+BAAC,SAAI,WAAU,0BACb;AAAA,gCAAC,UAAO,WAAW,GAAG,gDAAgD,UAAU,YAAY,SAAS,GACnG,8BAAC,cAAW,aAAa,iCAAQ,aAAa,MAAM,YAAY,GAClE;AAAA,YAEA,oBAAC,SAAI,WAAU,kBACb,+BAAC,SAAI,WAAU,2BACb;AAAA,kCAAC,UAAK,WAAU,gDAAgD,sBAAW;AAAA,cAC1E,QAAQ,cAAc,QAAQ,aAC7B,oBAAC,SAAM,SAAQ,WAAU,WAAU,WAAW,YAAE,QAAQ,GAAE;AAAA,cAE5D,oBAAC,UAAK,WAAU,2DACb,0BAAgB,QAAQ,SAAS,GACpC;AAAA,eACF,GACF;AAAA,YAEC,YACC;AAAA,cAAC;AAAA;AAAA,gBAEC,SAAS,CAAC,WAAW,UAAU,YAAY;AAAA,gBAC3C,QAAQ,WAAW,kBAAkB;AAAA,gBACrC,UAAU,WAAW,MAAM,SAAS,UAAU,QAAQ,EAAE,IAAI;AAAA,gBAC5D;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA,QAAQ,EAAE,OAAO,EAAE,gBAAgB,GAAG,MAAM,EAAE,aAAa,GAAG,QAAQ,EAAE,eAAe,EAAE;AAAA;AAAA,YAC3F;AAAA,aAEJ;AAAA,UAGC,YACC,qBAAC,SAAI,WAAU,cAAa,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAClE;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,OAAO;AAAA,gBACP,UAAU,CAAC,MAAM,YAAY,EAAE,OAAO,KAAK;AAAA,gBAC3C,WAAU;AAAA,gBACV,WAAS;AAAA,gBACT,WAAW,CAAC,MAAM;AAChB,sBAAI,EAAE,QAAQ,WAAW,CAAC,EAAE,UAAU;AACpC,sBAAE,eAAe;AACjB,qCAAiB;AAAA,kBACnB;AACA,sBAAI,EAAE,QAAQ,SAAU,cAAa,KAAK;AAAA,gBAC5C;AAAA,gBACA,UAAU,CAAC;AAAA;AAAA,YACb;AAAA,YACA,oBAAC,UAAO,MAAK,QAAO,WAAU,eAAc,SAAS,kBAAkB,UAAU,CAAC,SAAS,KAAK,KAAK,CAAC,SACpG,8BAAC,GAAG,OAAH,EAAS,WAAU,WAAU,GAChC;AAAA,YACA,oBAAC,UAAO,MAAK,QAAO,SAAQ,SAAQ,WAAU,eAAc,SAAS,MAAM,aAAa,KAAK,GAC3F,8BAAC,GAAG,GAAH,EAAK,WAAU,WAAU,GAC5B;AAAA,aACF,IAEA,oBAAC,OAAE,WAAU,2DAA2D,kBAAQ,MAAK;AAAA,UAItF,CAAC,WAAW,QAAQ,SAAS,KAC5B;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,WAAU;AAAA,cACV,SAAS,MAAM,cAAc,CAAC,MAAM,CAAC,CAAC;AAAA,cAEtC;AAAA,oCAAC,GAAG,cAAH,EAAgB,WAAW,GAAG,oCAAoC,cAAc,WAAW,GAAG;AAAA,gBAC9F,QAAQ;AAAA,gBAAO;AAAA,gBAAE,QAAQ,WAAW,IAAI,EAAE,OAAO,IAAI,EAAE,SAAS;AAAA;AAAA;AAAA,UACnE;AAAA,WAEJ;AAAA,QAGC,CAAC,WAAW,cACX,qBAAC,SAAI,WAAU,8CACZ;AAAA,kBAAQ,IAAI,CAAC,UACZ;AAAA,YAAC;AAAA;AAAA,cAEC,SAAS;AAAA,cACT;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA,OAAO,QAAQ;AAAA,cACf;AAAA;AAAA,YAPK,MAAM;AAAA,UAQb,CACD;AAAA,UAEA,WACC,qBAAC,SAAI,WAAU,wBAAuB,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAC5E;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,KAAK;AAAA,gBACL,OAAO;AAAA,gBACP,UAAU,CAAC,MAAM,aAAa,EAAE,OAAO,KAAK;AAAA,gBAC5C,aAAa,EAAE,YAAY;AAAA,gBAC3B,WAAU;AAAA,gBACV,WAAW,CAAC,MAAM;AAChB,sBAAI,EAAE,QAAQ,WAAW,CAAC,EAAE,UAAU;AACpC,sBAAE,eAAe;AACjB,sCAAkB;AAAA,kBACpB;AAAA,gBACF;AAAA,gBACA,UAAU,CAAC;AAAA;AAAA,YACb;AAAA,YACA,oBAAC,UAAO,MAAK,QAAO,WAAU,eAAc,SAAS,mBAAmB,UAAU,CAAC,UAAU,KAAK,KAAK,CAAC,UACtG,8BAAC,GAAG,MAAH,EAAQ,WAAU,WAAU,GAC/B;AAAA,aACF;AAAA,WAEJ;AAAA;AAAA;AAAA,EAEJ;AAEJ;","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;
|
|
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: (
|
|
55
|
-
"aria-label": (
|
|
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: (
|
|
69
|
-
"aria-label": (
|
|
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: (
|
|
83
|
-
"aria-label": (
|
|
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: (
|
|
97
|
-
"aria-label": (
|
|
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":";;;;;;;;;;;;;;;;;;;;
|
|
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,
|
|
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":"
|
|
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 {
|
|
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
|
|
32
|
-
const
|
|
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
|
|
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 = (
|
|
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 = (
|
|
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__ */
|
|
104
|
-
/* @__PURE__ */
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
/* @__PURE__ */ jsx("
|
|
128
|
-
|
|
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:
|
|
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: (
|
|
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
|
);
|