@collabdt/core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -21
- package/dist/core/components/AppSidebarContent.d.ts.map +1 -1
- package/dist/core/components/AppSidebarContent.js +21 -27
- package/dist/core/components/AppSidebarContent.js.map +1 -1
- package/dist/core/components/authentication/AuthPage.d.ts.map +1 -1
- package/dist/core/components/authentication/AuthPage.js +32 -7
- package/dist/core/components/authentication/AuthPage.js.map +1 -1
- package/dist/core/components/authentication/SignInAuthHeader.d.ts +7 -0
- package/dist/core/components/authentication/SignInAuthHeader.d.ts.map +1 -0
- package/dist/core/components/authentication/SignInAuthHeader.js +32 -0
- package/dist/core/components/authentication/SignInAuthHeader.js.map +1 -0
- package/dist/core/components/authentication/Signin.d.ts.map +1 -1
- package/dist/core/components/authentication/Signin.js +308 -10
- package/dist/core/components/authentication/Signin.js.map +1 -1
- package/dist/core/components/settings/index.js +3 -3
- package/dist/core/components/settings/index.js.map +1 -1
- package/dist/core/components/settings/src/OrganizationSettingsPanel.d.ts.map +1 -1
- package/dist/core/components/settings/src/OrganizationSettingsPanel.js +5 -4
- package/dist/core/components/settings/src/OrganizationSettingsPanel.js.map +1 -1
- package/dist/core/components/settings/src/SettingsSidebar.d.ts.map +1 -1
- package/dist/core/components/settings/src/SettingsSidebar.js +12 -2
- package/dist/core/components/settings/src/SettingsSidebar.js.map +1 -1
- package/dist/core/components/support/BugReportDialog.d.ts.map +1 -1
- package/dist/core/components/support/BugReportDialog.js +94 -13
- package/dist/core/components/support/BugReportDialog.js.map +1 -1
- package/dist/core/components/support/FeatureRequestDialog.d.ts.map +1 -1
- package/dist/core/components/support/FeatureRequestDialog.js +33 -6
- package/dist/core/components/support/FeatureRequestDialog.js.map +1 -1
- package/dist/core/components/ui/AppSidebar.d.ts.map +1 -1
- package/dist/core/components/ui/AppSidebar.js +52 -9
- package/dist/core/components/ui/AppSidebar.js.map +1 -1
- package/dist/core/components/ui/DataTable.js +3 -3
- package/dist/core/components/ui/DataTable.js.map +1 -1
- package/dist/core/components/ui/Dialog.js +1 -1
- package/dist/core/components/ui/Dialog.js.map +1 -1
- package/dist/core/components/ui/InfoSidebar/Container.js +1 -1
- package/dist/core/components/ui/InfoSidebar/Container.js.map +1 -1
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.js +2 -2
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.js.map +1 -1
- package/dist/core/components/ui/Sensors/SensorsSection.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/SensorsSection.js +2 -1
- package/dist/core/components/ui/Sensors/SensorsSection.js.map +1 -1
- package/dist/core/components/ui/Sensors/sensorUtils.d.ts +9 -0
- package/dist/core/components/ui/Sensors/sensorUtils.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/sensorUtils.js +6 -0
- package/dist/core/components/ui/Sensors/sensorUtils.js.map +1 -1
- package/dist/core/components/ui/Sidebar.d.ts +4 -0
- package/dist/core/components/ui/Sidebar.d.ts.map +1 -1
- package/dist/core/components/ui/Sidebar.js +11 -5
- package/dist/core/components/ui/Sidebar.js.map +1 -1
- package/dist/core/components/ui/Table.js +2 -2
- package/dist/core/components/ui/Table.js.map +1 -1
- package/dist/core/components/viewers/Data/DataMenu.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/DataMenu.js +8 -8
- package/dist/core/components/viewers/Data/DataMenu.js.map +1 -1
- package/dist/core/components/viewers/Data/buildingDetails/BuildingDetails.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/buildingDetails/BuildingDetails.js +26 -0
- package/dist/core/components/viewers/Data/buildingDetails/BuildingDetails.js.map +1 -1
- package/dist/core/components/viewers/Data/details/DetailHeader.js +14 -14
- package/dist/core/components/viewers/Data/details/DetailHeader.js.map +1 -1
- package/dist/core/components/viewers/Data/details/TabSidebar.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/details/TabSidebar.js +22 -0
- package/dist/core/components/viewers/Data/details/TabSidebar.js.map +1 -1
- package/dist/core/components/viewers/Data/infrastructureDetails/InfrastructureDetails.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/infrastructureDetails/InfrastructureDetails.js +19 -0
- package/dist/core/components/viewers/Data/infrastructureDetails/InfrastructureDetails.js.map +1 -1
- package/dist/core/components/viewers/Data/siteDetails/SiteDetails.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/siteDetails/SiteDetails.js +19 -0
- package/dist/core/components/viewers/Data/siteDetails/SiteDetails.js.map +1 -1
- package/dist/core/components/viewers/Data/utils/Columns.js +2 -2
- package/dist/core/components/viewers/Data/utils/Columns.js.map +1 -1
- package/dist/core/components/viewers/Data/utils/positionParser.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/utils/positionParser.js +6 -5
- package/dist/core/components/viewers/Data/utils/positionParser.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.js +8 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.js +7 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/RowActions.js +6 -6
- package/dist/core/components/viewers/map/datasets/RowActions.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/data.js +1 -1
- package/dist/core/components/viewers/map/datasets/data.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/index.d.ts.map +1 -1
- package/dist/core/components/viewers/map/datasets/index.js +28 -9
- package/dist/core/components/viewers/map/datasets/index.js.map +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudLoadingState/index.d.ts.map +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudLoadingState/index.js +54 -31
- package/dist/core/components/viewers/pointcloud/src/PointCloudLoadingState/index.js.map +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/index.js +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/index.js.map +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/src/PointCloudSection.d.ts +2 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/src/PointCloudSection.d.ts.map +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/src/PointCloudSection.js +5 -4
- package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/src/PointCloudSection.js.map +1 -1
- package/dist/core/hooks/useCaptureScreenshot.d.ts.map +1 -1
- package/dist/core/hooks/useCaptureScreenshot.js +62 -31
- package/dist/core/hooks/useCaptureScreenshot.js.map +1 -1
- package/dist/core/hooks/useCurrentViewerPosition.d.ts +18 -0
- package/dist/core/hooks/useCurrentViewerPosition.d.ts.map +1 -0
- package/dist/core/hooks/useCurrentViewerPosition.js +66 -0
- package/dist/core/hooks/useCurrentViewerPosition.js.map +1 -0
- package/dist/core/hooks/useShareUrl.d.ts +2 -0
- package/dist/core/hooks/useShareUrl.d.ts.map +1 -0
- package/dist/core/hooks/useShareUrl.js +74 -0
- package/dist/core/hooks/useShareUrl.js.map +1 -0
- package/dist/core/i18n/index.d.ts +8 -0
- package/dist/core/i18n/index.d.ts.map +1 -0
- package/dist/core/i18n/index.js +8 -0
- package/dist/core/i18n/index.js.map +1 -0
- package/dist/core/i18n/messages/en.json +1873 -0
- package/dist/core/i18n/messages/es.json +1870 -0
- package/dist/core/i18n/messages/fr.json +1870 -0
- package/dist/core/styles/globals.css +46 -33
- package/package.json +9 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../../../src/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/src/PointCloudSection.tsx"],"sourcesContent":["'use client'\r\n\r\n// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport * as React from 'react'\r\nimport * as LR from 'lucide-react'\r\nimport { useTranslations } from 'next-intl'\r\nimport { useSession } from 'next-auth/react'\r\nimport { CollapsibleSection } from '../../../../../../../ui/CollapsibleSection'\r\nimport { FileItemComponent } from '../../../../../../../ui/FilesManager/src/FileItemComponent'\r\nimport { useFileActions } from '../../../../../../../ui/FilesManager/src/useFileActions'\r\nimport ConfirmDialog from '../../../../../../../ConfirmDialog'\r\nimport { Button } from '../../../../../../../ui/Button'\r\nimport { DbFile } from '../../../../../../../../types/dbTypes'\r\nimport { PointCloudContext } from '../../../../../../../../store'\r\n// import { useAppConfigContext } from '../../../../../../../../store/AppConfig/context'\r\n\r\ntype CreatePointCloudResponse = {\r\n pointCloud: {\r\n id: string\r\n name: string\r\n }\r\n upload: {\r\n uploadUrl: string\r\n }\r\n}\r\n\r\ntype StartConversionResponse = {\r\n jobId: string\r\n}\r\n\r\nconst POINT_CLOUD_OPTIONS: import('../../../../../../../../types/global').FileAction[] = ['view', 'delete', 'info']\r\n\r\ninterface PointCloudsSectionProps {\r\n files: DbFile[]\r\n pointcloudApiUrl?: string\r\n}\r\n\r\nexport function PointCloudsSection({ files, pointcloudApiUrl }: PointCloudsSectionProps) {\r\n // Translation\r\n const t = useTranslations('PointCloudManagement')\r\n const API_BASE = pointcloudApiUrl ?? 'http://localhost:5101'\r\n\r\n // Get current user session (needed for uploadedBy)\r\n const { data: session } = useSession()\r\n\r\n // Point cloud context for loading/unloading clouds in the viewer\r\n const { dispatch: pointCloudDispatch } = React.useContext(PointCloudContext)\r\n\r\n // State for point clouds — includes isVisible to track which are loaded in viewer\r\n const [pointcloudsFiles, setPointcloudsFiles] = React.useState<(DbFile & { isVisible?: boolean })[]>(\r\n () => files.map(f => ({ ...f, isVisible: false }))\r\n )\r\n const esRef = React.useRef<EventSource | null>(null)\r\n const autoLoadedRef = React.useRef(false)\r\n\r\n const fileSyncKey = files\r\n .map(\r\n (file) =>\r\n `${file.id}:${file.name}:${Boolean(file.pointCloudUploaded)}:${Boolean(file.pointCloudPotreeConverted)}:${file.uploadedAt}:${file.potreeMetadataFileKey ?? ''}`\r\n )\r\n .join('|')\r\n\r\n // Sync incoming files from props while preserving isVisible state for known files\r\n React.useEffect(() => {\r\n setPointcloudsFiles(prev => {\r\n const prevMap = new Map(prev.map(f => [String(f.id), f]))\r\n return files.map(f => ({\r\n ...f,\r\n isVisible: prevMap.get(String(f.id))?.isVisible ?? false,\r\n }))\r\n })\r\n }, [fileSyncKey])\r\n\r\n // Auto-load the most recently uploaded converted file on first access\r\n React.useEffect(() => {\r\n if (autoLoadedRef.current || files.length === 0) return\r\n\r\n const readyFiles = files.filter(f => Boolean(f.pointCloudPotreeConverted))\r\n if (readyFiles.length === 0) return\r\n\r\n const lastFile = readyFiles.reduce((latest, f) =>\r\n new Date(f.uploadedAt) > new Date(latest.uploadedAt) ? f : latest\r\n )\r\n\r\n autoLoadedRef.current = true\r\n pointCloudDispatch({ type: 'LOAD_POINT_CLOUD', payload: { id: String(lastFile.id) } })\r\n setPointcloudsFiles(prev =>\r\n prev.map(f => ({ ...f, isVisible: String(f.id) === String(lastFile.id) }))\r\n )\r\n }, [fileSyncKey, files, pointCloudDispatch])\r\n\r\n React.useEffect(() => {\r\n // Cleanup SSE on unmount\r\n return () => {\r\n if (esRef.current) {\r\n esRef.current.close()\r\n }\r\n }\r\n }, [])\r\n\r\n // Toggle a point cloud on/off in the viewer via context\r\n const handleView = React.useCallback((file: DbFile, newVisibility: boolean) => {\r\n const id = String(file.id)\r\n if (newVisibility) {\r\n pointCloudDispatch({ type: 'LOAD_POINT_CLOUD', payload: { id } })\r\n } else {\r\n pointCloudDispatch({ type: 'UNLOAD_POINT_CLOUD', payload: { id } })\r\n }\r\n }, [pointCloudDispatch])\r\n\r\n const handleDeleteFile = React.useCallback(async (file: DbFile) => {\r\n const res = await fetch(`${API_BASE}/point-cloud/${encodeURIComponent(String(file.id))}`, {\r\n method: 'DELETE',\r\n })\r\n\r\n if (!res.ok) {\r\n const msg = await res.text()\r\n throw new Error(msg || `Delete failed (${res.status})`)\r\n }\r\n }, [])\r\n\r\n const handleConvert = React.useCallback(async (file: DbFile) => {\r\n const res = await fetch(\r\n `${API_BASE}/point-cloud/${encodeURIComponent(String(file.id))}/convert-to-potree`,\r\n {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ options: { sampling_method: 'poisson' } }),\r\n }\r\n )\r\n\r\n if (!res.ok) {\r\n const msg = await res.text()\r\n throw new Error(msg || `Conversion failed (${res.status})`)\r\n }\r\n\r\n const { jobId } = await res.json() as StartConversionResponse\r\n subscribeToProgress(jobId, file.id)\r\n }, [])\r\n\r\n const handleDeletePointCloud = React.useCallback((file: DbFile) => {\r\n pointCloudDispatch({ type: 'UNLOAD_POINT_CLOUD', payload: { id: String(file.id) } })\r\n }, [pointCloudDispatch])\r\n\r\n // Use the generic file actions hook\r\n const { handleAction, deleteDialog } = useFileActions({\r\n files: pointcloudsFiles,\r\n setFiles: setPointcloudsFiles as React.Dispatch<React.SetStateAction<(DbFile & { isVisible?: boolean })[]>>,\r\n buildingId: 0,\r\n handleDeleteFile,\r\n onView: handleView,\r\n onDelete: handleDeletePointCloud,\r\n })\r\n\r\n // Sort: visible (loaded) files at the top, preserving relative order within each group\r\n const sortedFiles = React.useMemo(() => {\r\n return [...pointcloudsFiles].sort((a, b) => {\r\n const aLoaded = a.isVisible !== false ? 1 : 0\r\n const bLoaded = b.isVisible !== false ? 1 : 0\r\n return bLoaded - aLoaded\r\n })\r\n }, [pointcloudsFiles])\r\n\r\n // Create point cloud entry — routed through the CDT proxy so that\r\n // organizationId is injected server-side from the authenticated session.\r\n async function createPointCloud(name: string, uploadedBy: string | null): Promise<CreatePointCloudResponse> {\r\n const res = await fetch('/api/point-cloud', {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ name, uploadedBy }),\r\n })\r\n\r\n if (!res.ok) {\r\n const msg = await res.text()\r\n throw new Error(`Create point-cloud failed: ${msg || res.status}`)\r\n }\r\n\r\n return res.json()\r\n }\r\n\r\n // Upload to presigned URL\r\n function uploadToPresignedUrl(\r\n url: string,\r\n file: File,\r\n onProgress?: (pct: number) => void\r\n ): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n try {\r\n const xhr = new XMLHttpRequest()\r\n xhr.open('PUT', url, true)\r\n\r\n if (onProgress) {\r\n xhr.upload.onprogress = (evt) => {\r\n if (evt.lengthComputable) {\r\n const pct = Math.round((evt.loaded / evt.total) * 100)\r\n onProgress(pct)\r\n }\r\n }\r\n }\r\n\r\n xhr.onload = () => {\r\n if (xhr.status >= 200 && xhr.status < 300) resolve()\r\n else reject(new Error(`Upload failed with status ${xhr.status}`))\r\n }\r\n\r\n xhr.onerror = () => reject(new Error('Network error during upload'))\r\n xhr.send(file)\r\n } catch (err) {\r\n reject(err)\r\n }\r\n })\r\n }\r\n\r\n // Start conversion\r\n async function startConversion(pointCloudId: string): Promise<StartConversionResponse> {\r\n const res = await fetch(\r\n `${API_BASE}/point-cloud/${encodeURIComponent(pointCloudId)}/convert-to-potree`,\r\n {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ options: { sampling_method: 'poisson' } }),\r\n }\r\n )\r\n\r\n if (!res.ok) {\r\n const msg = await res.text()\r\n throw new Error(`convert-to-potree failed: ${msg || res.status}`)\r\n }\r\n\r\n return res.json()\r\n }\r\n\r\n // Subscribe to conversion progress\r\n function subscribeToProgress(jobId: string, pointCloudId: number) {\r\n if (!jobId) return\r\n\r\n if (esRef.current) {\r\n esRef.current.close()\r\n }\r\n\r\n const es = new EventSource(\r\n `${API_BASE}/events/convert-progress/${encodeURIComponent(jobId)}`\r\n )\r\n esRef.current = es\r\n\r\n es.onopen = () => console.log('SSE connection opened')\r\n\r\n es.onerror = (event) => {\r\n console.error('SSE error:', event)\r\n // Update to failed state\r\n setPointcloudsFiles((prev) =>\r\n prev.map((pc) =>\r\n Number(pc.id) === pointCloudId\r\n ? { ...pc, status: 'idle', conversionProgress: 0 }\r\n : pc\r\n )\r\n )\r\n }\r\n\r\n es.addEventListener('progress', (event) => {\r\n try {\r\n const msgEvent = event as MessageEvent\r\n const data = JSON.parse(msgEvent.data)\r\n\r\n console.log('[progress]', data.status, data.message, data.progress)\r\n\r\n if (typeof data.progress === 'number') {\r\n setPointcloudsFiles((prev) =>\r\n prev.map((pc) =>\r\n Number(pc.id) === pointCloudId\r\n ? { ...pc, conversionProgress: data.progress }\r\n : pc\r\n )\r\n )\r\n }\r\n } catch (err) {\r\n console.error('Failed to parse progress event', err)\r\n }\r\n })\r\n\r\n es.addEventListener('finished', () => {\r\n console.log('[finished] Conversion completed')\r\n setPointcloudsFiles((prev) =>\r\n prev.map((pc) =>\r\n Number(pc.id) === pointCloudId\r\n ? { ...pc, status: 'idle', conversionProgress: 100 }\r\n : pc\r\n )\r\n )\r\n es.close()\r\n esRef.current = null\r\n })\r\n\r\n es.addEventListener('failed', () => {\r\n console.log('[failed] Conversion failed')\r\n setPointcloudsFiles((prev) =>\r\n prev.map((pc) =>\r\n Number(pc.id) === pointCloudId\r\n ? { ...pc, status: 'idle', conversionProgress: 0 }\r\n : pc\r\n )\r\n )\r\n es.close()\r\n esRef.current = null\r\n })\r\n }\r\n\r\n // Handle file upload\r\n const handleFileUpload = async (file: File) => {\r\n if (!file) return\r\n\r\n const originalFileName = file.name\r\n const fileExt = originalFileName.toLowerCase()\r\n const baseFileName = originalFileName.replace(/\\.(laz|las)$/i, '')\r\n\r\n // Validate file extension\r\n if (!fileExt.endsWith('.laz') && !fileExt.endsWith('.las')) {\r\n alert('Please select a LAZ or LAS file.')\r\n return\r\n }\r\n\r\n // Check for duplicate names and append (1), (2), etc.\r\n let fileName = baseFileName\r\n let counter = 1\r\n const existingNames = pointcloudsFiles.map((pc) => pc.name)\r\n \r\n while (existingNames.includes(fileName)) {\r\n fileName = `${baseFileName} (${counter})`\r\n counter++\r\n }\r\n\r\n try {\r\n console.log('Creating point cloud entry...')\r\n const userEmail = session?.user?.email || null\r\n const { pointCloud, upload } = await createPointCloud(fileName, userEmail)\r\n console.log('Point cloud created:', pointCloud.id)\r\n\r\n console.log('Uploading file...')\r\n await uploadToPresignedUrl(upload.uploadUrl, file, (pct) => {\r\n console.log(`Upload progress: ${pct}%`)\r\n })\r\n console.log('Upload complete!')\r\n\r\n console.log('Starting conversion...')\r\n const conversion = await startConversion(pointCloud.id)\r\n console.log('Conversion started:', conversion.jobId)\r\n\r\n // Subscribe to progress\r\n subscribeToProgress(conversion.jobId, Number(pointCloud.id))\r\n } catch (error) {\r\n console.error('Error uploading point cloud:', error)\r\n \r\n alert(\r\n error instanceof Error\r\n ? `Failed to upload: ${error.message}`\r\n : 'Failed to upload point cloud'\r\n )\r\n }\r\n }\r\n\r\n // Handle upload button click\r\n const handleUpload = () => {\r\n const input = document.createElement('input')\r\n input.type = 'file'\r\n input.accept = '.laz,.las'\r\n input.onchange = (e) => {\r\n const file = (e.target as HTMLInputElement).files?.[0]\r\n if (file) {\r\n handleFileUpload(file)\r\n }\r\n }\r\n input.click()\r\n }\r\n\r\n return (\r\n <>\r\n <CollapsibleSection\r\n title={t('title')}\r\n icon={LR.Grip}\r\n className=\"h-1/2 overflow-y-auto\"\r\n itemCount={pointcloudsFiles.length}\r\n onAddItem={handleUpload}\r\n addItemTitle={t('uploadTitle')}\r\n >\r\n <div className=\"space-y-1\">\r\n {sortedFiles.length === 0 ? (\r\n <div className=\"px-2 py-3 text-sm text-muted-foreground text-center\">\r\n {t('noPointClouds')}\r\n </div>\r\n ) : (\r\n sortedFiles.map((file) => (\r\n <div key={file.id} className=\"flex items-center gap-1\">\r\n <div className=\"flex-1 min-w-0\">\r\n <FileItemComponent\r\n file={file}\r\n onAction={handleAction}\r\n options={POINT_CLOUD_OPTIONS}\r\n />\r\n </div>\r\n {Boolean(file.pointCloudUploaded) && !Boolean(file.pointCloudPotreeConverted) && (\r\n <Button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n className=\"h-6 w-6 p-0\"\r\n onClick={() => void handleConvert(file)}\r\n title={t('convertTitle')}\r\n >\r\n <LR.RefreshCw className=\"h-3 w-3\" />\r\n </Button>\r\n )}\r\n </div>\r\n ))\r\n )}\r\n </div>\r\n </CollapsibleSection>\r\n\r\n <ConfirmDialog\r\n isOpen={deleteDialog.isOpen}\r\n isDeleting={deleteDialog.isDeleting}\r\n onOpenChange={deleteDialog.onOpenChange}\r\n handleConfirm={deleteDialog.onConfirm}\r\n itemName={deleteDialog.itemName}\r\n dataType=\"file\"\r\n />\r\n </>\r\n )\r\n}\r\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../../../src/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/src/PointCloudSection.tsx"],"sourcesContent":["'use client'\r\n\r\n// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport * as React from 'react'\r\nimport * as LR from 'lucide-react'\r\nimport { useTranslations } from 'next-intl'\r\nimport { useSession } from 'next-auth/react'\r\nimport { CollapsibleSection } from '../../../../../../../ui/CollapsibleSection'\r\nimport { FileItemComponent } from '../../../../../../../ui/FilesManager/src/FileItemComponent'\r\nimport { useFileActions } from '../../../../../../../ui/FilesManager/src/useFileActions'\r\nimport ConfirmDialog from '../../../../../../../ConfirmDialog'\r\nimport { Button } from '../../../../../../../ui/Button'\r\nimport { DbFile } from '../../../../../../../../types/dbTypes'\r\nimport { PointCloudContext } from '../../../../../../../../store'\r\n// import { useAppConfigContext } from '../../../../../../../../store/AppConfig/context'\r\n\r\ntype CreatePointCloudResponse = {\r\n pointCloud: {\r\n id: string\r\n name: string\r\n }\r\n upload: {\r\n uploadUrl: string\r\n }\r\n}\r\n\r\ntype StartConversionResponse = {\r\n jobId: string\r\n}\r\n\r\nconst POINT_CLOUD_OPTIONS: import('../../../../../../../../types/global').FileAction[] = ['view', 'delete', 'info']\r\n\r\ninterface PointCloudsSectionProps {\r\n files: DbFile[]\r\n pointcloudApiUrl?: string\r\n buildingId?: number\r\n}\r\n\r\nexport function PointCloudsSection({ files, pointcloudApiUrl, buildingId }: PointCloudsSectionProps) {\r\n // Translation\r\n const t = useTranslations('PointCloudManagement')\r\n const API_BASE = pointcloudApiUrl ?? 'http://localhost:5101'\r\n\r\n // Get current user session (needed for uploadedBy)\r\n const { data: session } = useSession()\r\n\r\n // Point cloud context for loading/unloading clouds in the viewer\r\n const { dispatch: pointCloudDispatch } = React.useContext(PointCloudContext)\r\n\r\n // State for point clouds — includes isVisible to track which are loaded in viewer\r\n const [pointcloudsFiles, setPointcloudsFiles] = React.useState<(DbFile & { isVisible?: boolean })[]>(\r\n () => files.map(f => ({ ...f, isVisible: false }))\r\n )\r\n const esRef = React.useRef<EventSource | null>(null)\r\n const autoLoadedRef = React.useRef(false)\r\n\r\n const fileSyncKey = files\r\n .map(\r\n (file) =>\r\n `${file.id}:${file.name}:${Boolean(file.pointCloudUploaded)}:${Boolean(file.pointCloudPotreeConverted)}:${file.uploadedAt}:${file.potreeMetadataFileKey ?? ''}`\r\n )\r\n .join('|')\r\n\r\n // Sync incoming files from props while preserving isVisible state for known files\r\n React.useEffect(() => {\r\n setPointcloudsFiles(prev => {\r\n const prevMap = new Map(prev.map(f => [String(f.id), f]))\r\n return files.map(f => ({\r\n ...f,\r\n isVisible: prevMap.get(String(f.id))?.isVisible ?? false,\r\n }))\r\n })\r\n }, [fileSyncKey])\r\n\r\n // Auto-load the most recently uploaded converted file on first access\r\n React.useEffect(() => {\r\n if (autoLoadedRef.current || files.length === 0) return\r\n\r\n const readyFiles = files.filter(f => Boolean(f.pointCloudPotreeConverted))\r\n if (readyFiles.length === 0) return\r\n\r\n const lastFile = readyFiles.reduce((latest, f) =>\r\n new Date(f.uploadedAt) > new Date(latest.uploadedAt) ? f : latest\r\n )\r\n\r\n autoLoadedRef.current = true\r\n pointCloudDispatch({ type: 'LOAD_POINT_CLOUD', payload: { id: String(lastFile.id) } })\r\n setPointcloudsFiles(prev =>\r\n prev.map(f => ({ ...f, isVisible: String(f.id) === String(lastFile.id) }))\r\n )\r\n }, [fileSyncKey, files, pointCloudDispatch])\r\n\r\n React.useEffect(() => {\r\n // Cleanup SSE on unmount\r\n return () => {\r\n if (esRef.current) {\r\n esRef.current.close()\r\n }\r\n }\r\n }, [])\r\n\r\n // Toggle a point cloud on/off in the viewer via context\r\n const handleView = React.useCallback((file: DbFile, newVisibility: boolean) => {\r\n const id = String(file.id)\r\n if (newVisibility) {\r\n pointCloudDispatch({ type: 'LOAD_POINT_CLOUD', payload: { id } })\r\n } else {\r\n pointCloudDispatch({ type: 'UNLOAD_POINT_CLOUD', payload: { id } })\r\n }\r\n }, [pointCloudDispatch])\r\n\r\n const handleDeleteFile = React.useCallback(async (file: DbFile) => {\r\n const res = await fetch(`${API_BASE}/point-cloud/${encodeURIComponent(String(file.id))}`, {\r\n method: 'DELETE',\r\n })\r\n\r\n if (!res.ok) {\r\n const msg = await res.text()\r\n throw new Error(msg || `Delete failed (${res.status})`)\r\n }\r\n }, [])\r\n\r\n const handleConvert = React.useCallback(async (file: DbFile) => {\r\n const res = await fetch(\r\n `${API_BASE}/point-cloud/${encodeURIComponent(String(file.id))}/convert-to-potree`,\r\n {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ options: { sampling_method: 'poisson' } }),\r\n }\r\n )\r\n\r\n if (!res.ok) {\r\n const msg = await res.text()\r\n throw new Error(msg || `Conversion failed (${res.status})`)\r\n }\r\n\r\n const { jobId } = await res.json() as StartConversionResponse\r\n subscribeToProgress(jobId, file.id)\r\n }, [])\r\n\r\n const handleDeletePointCloud = React.useCallback((file: DbFile) => {\r\n pointCloudDispatch({ type: 'UNLOAD_POINT_CLOUD', payload: { id: String(file.id) } })\r\n }, [pointCloudDispatch])\r\n\r\n // Use the generic file actions hook\r\n const { handleAction, deleteDialog } = useFileActions({\r\n files: pointcloudsFiles,\r\n setFiles: setPointcloudsFiles as React.Dispatch<React.SetStateAction<(DbFile & { isVisible?: boolean })[]>>,\r\n buildingId: 0,\r\n handleDeleteFile,\r\n onView: handleView,\r\n onDelete: handleDeletePointCloud,\r\n })\r\n\r\n // Sort: visible (loaded) files at the top, preserving relative order within each group\r\n const sortedFiles = React.useMemo(() => {\r\n return [...pointcloudsFiles].sort((a, b) => {\r\n const aLoaded = a.isVisible !== false ? 1 : 0\r\n const bLoaded = b.isVisible !== false ? 1 : 0\r\n return bLoaded - aLoaded\r\n })\r\n }, [pointcloudsFiles])\r\n\r\n // Create point cloud entry — routed through the CDT proxy so that\r\n // organizationId is injected server-side from the authenticated session.\r\n async function createPointCloud(name: string, buildingId?: number): Promise<CreatePointCloudResponse> {\r\n const res = await fetch('/api/point-cloud', {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ name, buildingId }),\r\n })\r\n\r\n if (!res.ok) {\r\n const msg = await res.text()\r\n throw new Error(`Create point-cloud failed: ${msg || res.status}`)\r\n }\r\n\r\n return res.json()\r\n }\r\n\r\n // Upload to presigned URL\r\n function uploadToPresignedUrl(\r\n url: string,\r\n file: File,\r\n onProgress?: (pct: number) => void\r\n ): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n try {\r\n const xhr = new XMLHttpRequest()\r\n xhr.open('PUT', url, true)\r\n\r\n if (onProgress) {\r\n xhr.upload.onprogress = (evt) => {\r\n if (evt.lengthComputable) {\r\n const pct = Math.round((evt.loaded / evt.total) * 100)\r\n onProgress(pct)\r\n }\r\n }\r\n }\r\n\r\n xhr.onload = () => {\r\n if (xhr.status >= 200 && xhr.status < 300) resolve()\r\n else reject(new Error(`Upload failed with status ${xhr.status}`))\r\n }\r\n\r\n xhr.onerror = () => reject(new Error('Network error during upload'))\r\n xhr.send(file)\r\n } catch (err) {\r\n reject(err)\r\n }\r\n })\r\n }\r\n\r\n // Start conversion\r\n async function startConversion(pointCloudId: string): Promise<StartConversionResponse> {\r\n const res = await fetch(\r\n `${API_BASE}/point-cloud/${encodeURIComponent(pointCloudId)}/convert-to-potree`,\r\n {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ options: { sampling_method: 'poisson' } }),\r\n }\r\n )\r\n\r\n if (!res.ok) {\r\n const msg = await res.text()\r\n throw new Error(`convert-to-potree failed: ${msg || res.status}`)\r\n }\r\n\r\n return res.json()\r\n }\r\n\r\n // Subscribe to conversion progress\r\n function subscribeToProgress(jobId: string, pointCloudId: number) {\r\n if (!jobId) return\r\n\r\n if (esRef.current) {\r\n esRef.current.close()\r\n }\r\n\r\n const es = new EventSource(\r\n `${API_BASE}/events/convert-progress/${encodeURIComponent(jobId)}`\r\n )\r\n esRef.current = es\r\n\r\n es.onopen = () => console.log('SSE connection opened')\r\n\r\n es.onerror = (event) => {\r\n console.error('SSE error:', event)\r\n // Update to failed state\r\n setPointcloudsFiles((prev) =>\r\n prev.map((pc) =>\r\n Number(pc.id) === pointCloudId\r\n ? { ...pc, status: 'idle', conversionProgress: 0 }\r\n : pc\r\n )\r\n )\r\n }\r\n\r\n es.addEventListener('progress', (event) => {\r\n try {\r\n const msgEvent = event as MessageEvent\r\n const data = JSON.parse(msgEvent.data)\r\n\r\n console.log('[progress]', data.status, data.message, data.progress)\r\n\r\n if (typeof data.progress === 'number') {\r\n setPointcloudsFiles((prev) =>\r\n prev.map((pc) =>\r\n Number(pc.id) === pointCloudId\r\n ? { ...pc, conversionProgress: data.progress }\r\n : pc\r\n )\r\n )\r\n }\r\n } catch (err) {\r\n console.error('Failed to parse progress event', err)\r\n }\r\n })\r\n\r\n es.addEventListener('finished', () => {\r\n console.log('[finished] Conversion completed')\r\n setPointcloudsFiles((prev) =>\r\n prev.map((pc) =>\r\n Number(pc.id) === pointCloudId\r\n ? { ...pc, status: 'idle', conversionProgress: 100 }\r\n : pc\r\n )\r\n )\r\n es.close()\r\n esRef.current = null\r\n window.location.reload()\r\n })\r\n\r\n es.addEventListener('failed', () => {\r\n console.log('[failed] Conversion failed')\r\n setPointcloudsFiles((prev) =>\r\n prev.map((pc) =>\r\n Number(pc.id) === pointCloudId\r\n ? { ...pc, status: 'idle', conversionProgress: 0 }\r\n : pc\r\n )\r\n )\r\n es.close()\r\n esRef.current = null\r\n })\r\n }\r\n\r\n // Handle file upload\r\n const handleFileUpload = async (file: File) => {\r\n if (!file) return\r\n\r\n const originalFileName = file.name\r\n const fileExt = originalFileName.toLowerCase()\r\n const baseFileName = originalFileName.replace(/\\.(laz|las)$/i, '')\r\n\r\n // Validate file extension\r\n if (!fileExt.endsWith('.laz') && !fileExt.endsWith('.las')) {\r\n alert('Please select a LAZ or LAS file.')\r\n return\r\n }\r\n\r\n // Check for duplicate names and append (1), (2), etc.\r\n let fileName = baseFileName\r\n let counter = 1\r\n const existingNames = pointcloudsFiles.map((pc) => pc.name)\r\n \r\n while (existingNames.includes(fileName)) {\r\n fileName = `${baseFileName} (${counter})`\r\n counter++\r\n }\r\n\r\n try {\r\n console.log('Creating point cloud entry...')\r\n const userEmail = session?.user?.email || null\r\n const { pointCloud, upload } = await createPointCloud(fileName, buildingId)\r\n console.log('Point cloud created:', pointCloud.id)\r\n\r\n console.log('Uploading file...')\r\n await uploadToPresignedUrl(upload.uploadUrl, file, (pct) => {\r\n console.log(`Upload progress: ${pct}%`)\r\n })\r\n console.log('Upload complete!')\r\n\r\n console.log('Starting conversion...')\r\n const conversion = await startConversion(pointCloud.id)\r\n console.log('Conversion started:', conversion.jobId)\r\n\r\n subscribeToProgress(conversion.jobId, Number(pointCloud.id))\r\n } catch (error) {\r\n console.error('Error uploading point cloud:', error)\r\n \r\n alert(\r\n error instanceof Error\r\n ? `Failed to upload: ${error.message}`\r\n : 'Failed to upload point cloud'\r\n )\r\n }\r\n }\r\n\r\n // Handle upload button click\r\n const handleUpload = () => {\r\n const input = document.createElement('input')\r\n input.type = 'file'\r\n input.accept = '.laz,.las'\r\n input.onchange = (e) => {\r\n const file = (e.target as HTMLInputElement).files?.[0]\r\n if (file) {\r\n handleFileUpload(file)\r\n }\r\n }\r\n input.click()\r\n }\r\n\r\n return (\r\n <>\r\n <CollapsibleSection\r\n title={t('title')}\r\n icon={LR.Grip}\r\n className=\"h-1/2 overflow-y-auto\"\r\n itemCount={pointcloudsFiles.length}\r\n onAddItem={handleUpload}\r\n addItemTitle={t('uploadTitle')}\r\n >\r\n <div className=\"space-y-1\">\r\n {sortedFiles.length === 0 ? (\r\n <div className=\"px-2 py-3 text-sm text-muted-foreground text-center\">\r\n {t('noPointClouds')}\r\n </div>\r\n ) : (\r\n sortedFiles.map((file) => (\r\n <div key={file.id} className=\"flex items-center gap-1\">\r\n <div className=\"flex-1 min-w-0\">\r\n <FileItemComponent\r\n file={file}\r\n onAction={handleAction}\r\n options={POINT_CLOUD_OPTIONS}\r\n />\r\n </div>\r\n {Boolean(file.pointCloudUploaded) && !Boolean(file.pointCloudPotreeConverted) && (\r\n <Button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n className=\"h-6 w-6 p-0\"\r\n onClick={() => void handleConvert(file)}\r\n title={t('convertTitle')}\r\n >\r\n <LR.RefreshCw className=\"h-3 w-3\" />\r\n </Button>\r\n )}\r\n </div>\r\n ))\r\n )}\r\n </div>\r\n </CollapsibleSection>\r\n\r\n <ConfirmDialog\r\n isOpen={deleteDialog.isOpen}\r\n isDeleting={deleteDialog.isDeleting}\r\n onOpenChange={deleteDialog.onOpenChange}\r\n handleConfirm={deleteDialog.onConfirm}\r\n itemName={deleteDialog.itemName}\r\n dataType=\"file\"\r\n />\r\n </>\r\n )\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA0XI,mBAWQ,KAKE,YAhBV;AArXJ,YAAY,WAAW;AACvB,YAAY,QAAQ;AACpB,SAAS,uBAAuB;AAChC,SAAS,kBAAkB;AAC3B,SAAS,0BAA0B;AACnC,SAAS,yBAAyB;AAClC,SAAS,sBAAsB;AAC/B,OAAO,mBAAmB;AAC1B,SAAS,cAAc;AAEvB,SAAS,yBAAyB;AAiBlC,MAAM,sBAAmF,CAAC,QAAQ,UAAU,MAAM;AAQ3G,SAAS,mBAAmB,EAAE,OAAO,kBAAkB,WAAW,GAA4B;AAEnG,QAAM,IAAI,gBAAgB,sBAAsB;AAChD,QAAM,WAAW,8CAAoB;AAGrC,QAAM,EAAE,MAAM,QAAQ,IAAI,WAAW;AAGrC,QAAM,EAAE,UAAU,mBAAmB,IAAI,MAAM,WAAW,iBAAiB;AAG3E,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,MAAM;AAAA,IACpD,MAAM,MAAM,IAAI,OAAM,iCAAK,IAAL,EAAQ,WAAW,MAAM,EAAE;AAAA,EACnD;AACA,QAAM,QAAQ,MAAM,OAA2B,IAAI;AACnD,QAAM,gBAAgB,MAAM,OAAO,KAAK;AAExC,QAAM,cAAc,MACjB;AAAA,IACC,CAAC,SAAM;AA5Db;AA6DQ,gBAAG,KAAK,EAAE,IAAI,KAAK,IAAI,IAAI,QAAQ,KAAK,kBAAkB,CAAC,IAAI,QAAQ,KAAK,yBAAyB,CAAC,IAAI,KAAK,UAAU,KAAI,UAAK,0BAAL,YAA8B,EAAE;AAAA;AAAA,EACjK,EACC,KAAK,GAAG;AAGX,QAAM,UAAU,MAAM;AACpB,wBAAoB,UAAQ;AAC1B,YAAM,UAAU,IAAI,IAAI,KAAK,IAAI,OAAK,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;AACxD,aAAO,MAAM,IAAI,OAAE;AArEzB;AAqE6B,gDAClB,IADkB;AAAA,UAErB,YAAW,mBAAQ,IAAI,OAAO,EAAE,EAAE,CAAC,MAAxB,mBAA2B,cAA3B,YAAwC;AAAA,QACrD;AAAA,OAAE;AAAA,IACJ,CAAC;AAAA,EACH,GAAG,CAAC,WAAW,CAAC;AAGhB,QAAM,UAAU,MAAM;AACpB,QAAI,cAAc,WAAW,MAAM,WAAW,EAAG;AAEjD,UAAM,aAAa,MAAM,OAAO,OAAK,QAAQ,EAAE,yBAAyB,CAAC;AACzE,QAAI,WAAW,WAAW,EAAG;AAE7B,UAAM,WAAW,WAAW;AAAA,MAAO,CAAC,QAAQ,MAC1C,IAAI,KAAK,EAAE,UAAU,IAAI,IAAI,KAAK,OAAO,UAAU,IAAI,IAAI;AAAA,IAC7D;AAEA,kBAAc,UAAU;AACxB,uBAAmB,EAAE,MAAM,oBAAoB,SAAS,EAAE,IAAI,OAAO,SAAS,EAAE,EAAE,EAAE,CAAC;AACrF;AAAA,MAAoB,UAClB,KAAK,IAAI,OAAM,iCAAK,IAAL,EAAQ,WAAW,OAAO,EAAE,EAAE,MAAM,OAAO,SAAS,EAAE,EAAE,EAAE;AAAA,IAC3E;AAAA,EACF,GAAG,CAAC,aAAa,OAAO,kBAAkB,CAAC;AAE3C,QAAM,UAAU,MAAM;AAEpB,WAAO,MAAM;AACX,UAAI,MAAM,SAAS;AACjB,cAAM,QAAQ,MAAM;AAAA,MACtB;AAAA,IACF;AAAA,EACF,GAAG,CAAC,CAAC;AAGL,QAAM,aAAa,MAAM,YAAY,CAAC,MAAc,kBAA2B;AAC7E,UAAM,KAAK,OAAO,KAAK,EAAE;AACzB,QAAI,eAAe;AACjB,yBAAmB,EAAE,MAAM,oBAAoB,SAAS,EAAE,GAAG,EAAE,CAAC;AAAA,IAClE,OAAO;AACL,yBAAmB,EAAE,MAAM,sBAAsB,SAAS,EAAE,GAAG,EAAE,CAAC;AAAA,IACpE;AAAA,EACF,GAAG,CAAC,kBAAkB,CAAC;AAEvB,QAAM,mBAAmB,MAAM,YAAY,OAAO,SAAiB;AACjE,UAAM,MAAM,MAAM,MAAM,GAAG,QAAQ,gBAAgB,mBAAmB,OAAO,KAAK,EAAE,CAAC,CAAC,IAAI;AAAA,MACxF,QAAQ;AAAA,IACV,CAAC;AAED,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,MAAM,MAAM,IAAI,KAAK;AAC3B,YAAM,IAAI,MAAM,OAAO,kBAAkB,IAAI,MAAM,GAAG;AAAA,IACxD;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,gBAAgB,MAAM,YAAY,OAAO,SAAiB;AAC9D,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,QAAQ,gBAAgB,mBAAmB,OAAO,KAAK,EAAE,CAAC,CAAC;AAAA,MAC9D;AAAA,QACE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,SAAS,EAAE,iBAAiB,UAAU,EAAE,CAAC;AAAA,MAClE;AAAA,IACF;AAEA,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,MAAM,MAAM,IAAI,KAAK;AAC3B,YAAM,IAAI,MAAM,OAAO,sBAAsB,IAAI,MAAM,GAAG;AAAA,IAC5D;AAEA,UAAM,EAAE,MAAM,IAAI,MAAM,IAAI,KAAK;AACjC,wBAAoB,OAAO,KAAK,EAAE;AAAA,EACpC,GAAG,CAAC,CAAC;AAEL,QAAM,yBAAyB,MAAM,YAAY,CAAC,SAAiB;AACjE,uBAAmB,EAAE,MAAM,sBAAsB,SAAS,EAAE,IAAI,OAAO,KAAK,EAAE,EAAE,EAAE,CAAC;AAAA,EACrF,GAAG,CAAC,kBAAkB,CAAC;AAGvB,QAAM,EAAE,cAAc,aAAa,IAAI,eAAe;AAAA,IACpD,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ;AAAA,IACA,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ,CAAC;AAGD,QAAM,cAAc,MAAM,QAAQ,MAAM;AACtC,WAAO,CAAC,GAAG,gBAAgB,EAAE,KAAK,CAAC,GAAG,MAAM;AAC1C,YAAM,UAAU,EAAE,cAAc,QAAQ,IAAI;AAC5C,YAAM,UAAU,EAAE,cAAc,QAAQ,IAAI;AAC5C,aAAO,UAAU;AAAA,IACnB,CAAC;AAAA,EACH,GAAG,CAAC,gBAAgB,CAAC;AAIrB,iBAAe,iBAAiB,MAAcA,aAAwD;AACpG,UAAM,MAAM,MAAM,MAAM,oBAAoB;AAAA,MAC1C,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,MAAM,YAAAA,YAAW,CAAC;AAAA,IAC3C,CAAC;AAED,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,MAAM,MAAM,IAAI,KAAK;AAC3B,YAAM,IAAI,MAAM,8BAA8B,OAAO,IAAI,MAAM,EAAE;AAAA,IACnE;AAEA,WAAO,IAAI,KAAK;AAAA,EAClB;AAGA,WAAS,qBACP,KACA,MACA,YACe;AACf,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI;AACF,cAAM,MAAM,IAAI,eAAe;AAC/B,YAAI,KAAK,OAAO,KAAK,IAAI;AAEzB,YAAI,YAAY;AACd,cAAI,OAAO,aAAa,CAAC,QAAQ;AAC/B,gBAAI,IAAI,kBAAkB;AACxB,oBAAM,MAAM,KAAK,MAAO,IAAI,SAAS,IAAI,QAAS,GAAG;AACrD,yBAAW,GAAG;AAAA,YAChB;AAAA,UACF;AAAA,QACF;AAEA,YAAI,SAAS,MAAM;AACjB,cAAI,IAAI,UAAU,OAAO,IAAI,SAAS,IAAK,SAAQ;AAAA,cAC9C,QAAO,IAAI,MAAM,6BAA6B,IAAI,MAAM,EAAE,CAAC;AAAA,QAClE;AAEA,YAAI,UAAU,MAAM,OAAO,IAAI,MAAM,6BAA6B,CAAC;AACnE,YAAI,KAAK,IAAI;AAAA,MACf,SAAS,KAAK;AACZ,eAAO,GAAG;AAAA,MACZ;AAAA,IACF,CAAC;AAAA,EACH;AAGA,iBAAe,gBAAgB,cAAwD;AACrF,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,QAAQ,gBAAgB,mBAAmB,YAAY,CAAC;AAAA,MAC3D;AAAA,QACE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,SAAS,EAAE,iBAAiB,UAAU,EAAE,CAAC;AAAA,MAClE;AAAA,IACF;AAEA,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,MAAM,MAAM,IAAI,KAAK;AAC3B,YAAM,IAAI,MAAM,6BAA6B,OAAO,IAAI,MAAM,EAAE;AAAA,IAClE;AAEA,WAAO,IAAI,KAAK;AAAA,EAClB;AAGA,WAAS,oBAAoB,OAAe,cAAsB;AAChE,QAAI,CAAC,MAAO;AAEZ,QAAI,MAAM,SAAS;AACjB,YAAM,QAAQ,MAAM;AAAA,IACtB;AAEA,UAAM,KAAK,IAAI;AAAA,MACb,GAAG,QAAQ,4BAA4B,mBAAmB,KAAK,CAAC;AAAA,IAClE;AACA,UAAM,UAAU;AAEhB,OAAG,SAAS,MAAM,QAAQ,IAAI,uBAAuB;AAErD,OAAG,UAAU,CAAC,UAAU;AACtB,cAAQ,MAAM,cAAc,KAAK;AAEjC;AAAA,QAAoB,CAAC,SACnB,KAAK;AAAA,UAAI,CAAC,OACR,OAAO,GAAG,EAAE,MAAM,eACd,iCAAK,KAAL,EAAS,QAAQ,QAAQ,oBAAoB,EAAE,KAC/C;AAAA,QACN;AAAA,MACF;AAAA,IACF;AAEA,OAAG,iBAAiB,YAAY,CAAC,UAAU;AACzC,UAAI;AACF,cAAM,WAAW;AACjB,cAAM,OAAO,KAAK,MAAM,SAAS,IAAI;AAErC,gBAAQ,IAAI,cAAc,KAAK,QAAQ,KAAK,SAAS,KAAK,QAAQ;AAElE,YAAI,OAAO,KAAK,aAAa,UAAU;AACrC;AAAA,YAAoB,CAAC,SACnB,KAAK;AAAA,cAAI,CAAC,OACR,OAAO,GAAG,EAAE,MAAM,eACd,iCAAK,KAAL,EAAS,oBAAoB,KAAK,SAAS,KAC3C;AAAA,YACN;AAAA,UACF;AAAA,QACF;AAAA,MACF,SAAS,KAAK;AACZ,gBAAQ,MAAM,kCAAkC,GAAG;AAAA,MACrD;AAAA,IACF,CAAC;AAED,OAAG,iBAAiB,YAAY,MAAM;AACpC,cAAQ,IAAI,iCAAiC;AAC7C;AAAA,QAAoB,CAAC,SACnB,KAAK;AAAA,UAAI,CAAC,OACR,OAAO,GAAG,EAAE,MAAM,eACd,iCAAK,KAAL,EAAS,QAAQ,QAAQ,oBAAoB,IAAI,KACjD;AAAA,QACN;AAAA,MACF;AACA,SAAG,MAAM;AACT,YAAM,UAAU;AAChB,aAAO,SAAS,OAAO;AAAA,IACzB,CAAC;AAED,OAAG,iBAAiB,UAAU,MAAM;AAClC,cAAQ,IAAI,4BAA4B;AACxC;AAAA,QAAoB,CAAC,SACnB,KAAK;AAAA,UAAI,CAAC,OACR,OAAO,GAAG,EAAE,MAAM,eACd,iCAAK,KAAL,EAAS,QAAQ,QAAQ,oBAAoB,EAAE,KAC/C;AAAA,QACN;AAAA,MACF;AACA,SAAG,MAAM;AACT,YAAM,UAAU;AAAA,IAClB,CAAC;AAAA,EACH;AAGA,QAAM,mBAAmB,OAAO,SAAe;AAxTjD;AAyTI,QAAI,CAAC,KAAM;AAEX,UAAM,mBAAmB,KAAK;AAC9B,UAAM,UAAU,iBAAiB,YAAY;AAC7C,UAAM,eAAe,iBAAiB,QAAQ,iBAAiB,EAAE;AAGjE,QAAI,CAAC,QAAQ,SAAS,MAAM,KAAK,CAAC,QAAQ,SAAS,MAAM,GAAG;AAC1D,YAAM,kCAAkC;AACxC;AAAA,IACF;AAGA,QAAI,WAAW;AACf,QAAI,UAAU;AACd,UAAM,gBAAgB,iBAAiB,IAAI,CAAC,OAAO,GAAG,IAAI;AAE1D,WAAO,cAAc,SAAS,QAAQ,GAAG;AACvC,iBAAW,GAAG,YAAY,KAAK,OAAO;AACtC;AAAA,IACF;AAEA,QAAI;AACF,cAAQ,IAAI,+BAA+B;AAC3C,YAAM,cAAY,wCAAS,SAAT,mBAAe,UAAS;AAC1C,YAAM,EAAE,YAAY,OAAO,IAAI,MAAM,iBAAiB,UAAU,UAAU;AAC1E,cAAQ,IAAI,wBAAwB,WAAW,EAAE;AAEjD,cAAQ,IAAI,mBAAmB;AAC/B,YAAM,qBAAqB,OAAO,WAAW,MAAM,CAAC,QAAQ;AAC1D,gBAAQ,IAAI,oBAAoB,GAAG,GAAG;AAAA,MACxC,CAAC;AACD,cAAQ,IAAI,kBAAkB;AAE9B,cAAQ,IAAI,wBAAwB;AACpC,YAAM,aAAa,MAAM,gBAAgB,WAAW,EAAE;AACtD,cAAQ,IAAI,uBAAuB,WAAW,KAAK;AAEnD,0BAAoB,WAAW,OAAO,OAAO,WAAW,EAAE,CAAC;AAAA,IAC7D,SAAS,OAAO;AACd,cAAQ,MAAM,gCAAgC,KAAK;AAEnD;AAAA,QACE,iBAAiB,QACb,qBAAqB,MAAM,OAAO,KAClC;AAAA,MACN;AAAA,IACF;AAAA,EACF;AAGA,QAAM,eAAe,MAAM;AACzB,UAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,UAAM,OAAO;AACb,UAAM,SAAS;AACf,UAAM,WAAW,CAAC,MAAM;AAhX5B;AAiXM,YAAM,QAAQ,OAAE,OAA4B,UAA9B,mBAAsC;AACpD,UAAI,MAAM;AACR,yBAAiB,IAAI;AAAA,MACvB;AAAA,IACF;AACA,UAAM,MAAM;AAAA,EACd;AAEA,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,OAAO;AAAA,QAChB,MAAM,GAAG;AAAA,QACT,WAAU;AAAA,QACV,WAAW,iBAAiB;AAAA,QAC5B,WAAW;AAAA,QACX,cAAc,EAAE,aAAa;AAAA,QAE7B,8BAAC,SAAI,WAAU,aACZ,sBAAY,WAAW,IACtB,oBAAC,SAAI,WAAU,uDACZ,YAAE,eAAe,GACpB,IAEA,YAAY,IAAI,CAAC,SACf,qBAAC,SAAkB,WAAU,2BAC3B;AAAA,8BAAC,SAAI,WAAU,kBACb;AAAA,YAAC;AAAA;AAAA,cACC;AAAA,cACA,UAAU;AAAA,cACV,SAAS;AAAA;AAAA,UACX,GACF;AAAA,UACC,QAAQ,KAAK,kBAAkB,KAAK,CAAC,QAAQ,KAAK,yBAAyB,KAC1E;AAAA,YAAC;AAAA;AAAA,cACC,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,WAAU;AAAA,cACV,SAAS,MAAM,KAAK,cAAc,IAAI;AAAA,cACtC,OAAO,EAAE,cAAc;AAAA,cAEvB,8BAAC,GAAG,WAAH,EAAa,WAAU,WAAU;AAAA;AAAA,UACpC;AAAA,aAjBM,KAAK,EAmBf,CACD,GAEL;AAAA;AAAA,IACF;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,QAAQ,aAAa;AAAA,QACrB,YAAY,aAAa;AAAA,QACzB,cAAc,aAAa;AAAA,QAC3B,eAAe,aAAa;AAAA,QAC5B,UAAU,aAAa;AAAA,QACvB,UAAS;AAAA;AAAA,IACX;AAAA,KACF;AAEJ;","names":["buildingId"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCaptureScreenshot.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/useCaptureScreenshot.ts"],"names":[],"mappings":"AAuBA,wBAAgB,oBAAoB,IAAI,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"useCaptureScreenshot.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/useCaptureScreenshot.ts"],"names":[],"mappings":"AAuBA,wBAAgB,oBAAoB,IAAI,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiInE"}
|
|
@@ -24,56 +24,85 @@ function useCaptureScreenshot() {
|
|
|
24
24
|
const { map } = mapState.map;
|
|
25
25
|
const { state: bimState } = React.useContext(BimContext);
|
|
26
26
|
const { world } = bimState.bim;
|
|
27
|
-
return React.useCallback(() => {
|
|
28
|
-
|
|
27
|
+
return React.useCallback(async () => {
|
|
28
|
+
try {
|
|
29
29
|
let viewerDataUrl = null;
|
|
30
|
+
let viewerCanvas = null;
|
|
30
31
|
let viewerBounds = null;
|
|
31
|
-
let viewerCanvasElement = null;
|
|
32
32
|
if (currentViewer === ViewerNames.map && map) {
|
|
33
|
-
await
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
33
|
+
await Promise.race([
|
|
34
|
+
new Promise((res) => {
|
|
35
|
+
map.once("render", () => {
|
|
36
|
+
viewerCanvas = map.getCanvas();
|
|
37
|
+
viewerBounds = viewerCanvas.getBoundingClientRect();
|
|
38
|
+
try {
|
|
39
|
+
viewerDataUrl = viewerCanvas.toDataURL("image/png");
|
|
40
|
+
} catch (e) {
|
|
41
|
+
}
|
|
42
|
+
res();
|
|
43
|
+
});
|
|
44
|
+
map.triggerRepaint();
|
|
45
|
+
}),
|
|
46
|
+
new Promise((r) => setTimeout(r, 3e3))
|
|
47
|
+
]);
|
|
46
48
|
} else if (currentViewer === ViewerNames.bim && (world == null ? void 0 : world.renderer) && (world == null ? void 0 : world.scene) && (world == null ? void 0 : world.camera)) {
|
|
47
49
|
world.renderer.three.render(world.scene.three, world.camera.three);
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
|
|
51
|
-
viewerBounds = canvas.getBoundingClientRect();
|
|
50
|
+
viewerCanvas = document.querySelector("#bim-viewer-container canvas");
|
|
51
|
+
if (viewerCanvas) {
|
|
52
|
+
viewerBounds = viewerCanvas.getBoundingClientRect();
|
|
52
53
|
try {
|
|
53
|
-
viewerDataUrl =
|
|
54
|
+
viewerDataUrl = viewerCanvas.toDataURL("image/png");
|
|
54
55
|
} catch (e) {
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
} else {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
viewerBounds =
|
|
59
|
+
const sel = currentViewer === ViewerNames.pointcloud ? "#pointcloud-viewer-container" : null;
|
|
60
|
+
viewerCanvas = sel ? document.querySelector(`${sel} canvas`) : document.querySelector("canvas");
|
|
61
|
+
if (viewerCanvas) {
|
|
62
|
+
viewerBounds = viewerCanvas.getBoundingClientRect();
|
|
62
63
|
try {
|
|
63
|
-
viewerDataUrl =
|
|
64
|
+
viewerDataUrl = viewerCanvas.toDataURL("image/png");
|
|
64
65
|
} catch (e) {
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
69
|
const { default: html2canvas } = await import("html2canvas");
|
|
69
|
-
|
|
70
|
+
let dialogPortal = null;
|
|
71
|
+
const dialogEl = document.querySelector('[role="dialog"]');
|
|
72
|
+
if (dialogEl) {
|
|
73
|
+
let node = dialogEl;
|
|
74
|
+
while (node && node.parentElement !== document.body) {
|
|
75
|
+
node = node.parentElement;
|
|
76
|
+
}
|
|
77
|
+
if (node && node !== document.body) dialogPortal = node;
|
|
78
|
+
}
|
|
79
|
+
if (dialogPortal) dialogPortal.style.display = "none";
|
|
80
|
+
const captureUi = (ignoreImages) => html2canvas(document.body, {
|
|
70
81
|
useCORS: true,
|
|
71
82
|
allowTaint: false,
|
|
72
83
|
logging: false,
|
|
73
|
-
removeContainer: true,
|
|
74
84
|
scale: window.devicePixelRatio || 1,
|
|
75
|
-
|
|
85
|
+
width: window.innerWidth,
|
|
86
|
+
height: window.innerHeight,
|
|
87
|
+
x: window.scrollX,
|
|
88
|
+
y: window.scrollY,
|
|
89
|
+
windowWidth: window.innerWidth,
|
|
90
|
+
windowHeight: window.innerHeight,
|
|
91
|
+
scrollX: -window.scrollX,
|
|
92
|
+
scrollY: -window.scrollY,
|
|
93
|
+
ignoreElements: (el) => el === viewerCanvas || ignoreImages && el.tagName === "IMG"
|
|
76
94
|
});
|
|
95
|
+
let uiCanvas;
|
|
96
|
+
try {
|
|
97
|
+
try {
|
|
98
|
+
uiCanvas = await captureUi(false);
|
|
99
|
+
} catch (err) {
|
|
100
|
+
console.warn("[useCaptureScreenshot] retrying without <img> elements after taint error:", err);
|
|
101
|
+
uiCanvas = await captureUi(true);
|
|
102
|
+
}
|
|
103
|
+
} finally {
|
|
104
|
+
if (dialogPortal) dialogPortal.style.display = "";
|
|
105
|
+
}
|
|
77
106
|
if (viewerDataUrl && viewerBounds) {
|
|
78
107
|
const ctx = uiCanvas.getContext("2d");
|
|
79
108
|
if (ctx) {
|
|
@@ -100,8 +129,10 @@ function useCaptureScreenshot() {
|
|
|
100
129
|
blobToBase64(blob).then(resolve);
|
|
101
130
|
}, "image/png", 1);
|
|
102
131
|
});
|
|
103
|
-
}
|
|
104
|
-
|
|
132
|
+
} catch (err) {
|
|
133
|
+
console.error("[useCaptureScreenshot] failed:", err);
|
|
134
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
135
|
+
}
|
|
105
136
|
}, [currentViewer, map, world]);
|
|
106
137
|
}
|
|
107
138
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/hooks/useCaptureScreenshot.ts"],"sourcesContent":["'use client'\r\n\r\nimport * as React from 'react'\r\nimport { MenusContext, MapContext, BimContext } from '../store'\r\nimport { ViewerNames } from '../types'\r\n\r\nfunction blobToBase64(blob: Blob): Promise<string> {\r\n return new Promise((resolve) => {\r\n const reader = new FileReader()\r\n reader.onload = () => resolve(reader.result as string)\r\n reader.readAsDataURL(blob)\r\n })\r\n}\r\n\r\nfunction loadImage(src: string): Promise<HTMLImageElement> {\r\n return new Promise((resolve, reject) => {\r\n const img = new Image()\r\n img.onload = () => resolve(img)\r\n img.onerror = reject\r\n img.src = src\r\n })\r\n}\r\n\r\nexport function useCaptureScreenshot(): () => Promise<string | null> {\r\n const { state: menusState } = React.useContext(MenusContext)\r\n const { currentViewer } = menusState.menus\r\n\r\n const { state: mapState } = React.useContext(MapContext)\r\n const { map } = mapState.map\r\n\r\n const { state: bimState } = React.useContext(BimContext)\r\n const { world } = bimState.bim\r\n\r\n return React.useCallback((): Promise<string | null> => {\r\n
|
|
1
|
+
{"version":3,"sources":["../../../src/core/hooks/useCaptureScreenshot.ts"],"sourcesContent":["'use client'\r\n\r\nimport * as React from 'react'\r\nimport { MenusContext, MapContext, BimContext } from '../store'\r\nimport { ViewerNames } from '../types'\r\n\r\nfunction blobToBase64(blob: Blob): Promise<string> {\r\n return new Promise((resolve) => {\r\n const reader = new FileReader()\r\n reader.onload = () => resolve(reader.result as string)\r\n reader.readAsDataURL(blob)\r\n })\r\n}\r\n\r\nfunction loadImage(src: string): Promise<HTMLImageElement> {\r\n return new Promise((resolve, reject) => {\r\n const img = new Image()\r\n img.onload = () => resolve(img)\r\n img.onerror = reject\r\n img.src = src\r\n })\r\n}\r\n\r\nexport function useCaptureScreenshot(): () => Promise<string | null> {\r\n const { state: menusState } = React.useContext(MenusContext)\r\n const { currentViewer } = menusState.menus\r\n\r\n const { state: mapState } = React.useContext(MapContext)\r\n const { map } = mapState.map\r\n\r\n const { state: bimState } = React.useContext(BimContext)\r\n const { world } = bimState.bim\r\n\r\n return React.useCallback(async (): Promise<string | null> => {\r\n try {\r\n let viewerDataUrl: string | null = null\r\n let viewerCanvas: HTMLCanvasElement | null = null\r\n let viewerBounds: DOMRect | null = null\r\n\r\n if (currentViewer === ViewerNames.map && map) {\r\n await Promise.race([\r\n new Promise<void>((res) => {\r\n map.once('render', () => {\r\n viewerCanvas = map.getCanvas()\r\n viewerBounds = viewerCanvas.getBoundingClientRect()\r\n try { viewerDataUrl = viewerCanvas.toDataURL('image/png') } catch { /* cross-origin */ }\r\n res()\r\n })\r\n map.triggerRepaint()\r\n }),\r\n new Promise<void>((r) => setTimeout(r, 3000)),\r\n ])\r\n } else if (currentViewer === ViewerNames.bim && world?.renderer && world?.scene && world?.camera) {\r\n world.renderer.three.render(world.scene.three, world.camera.three)\r\n viewerCanvas = document.querySelector<HTMLCanvasElement>('#bim-viewer-container canvas')\r\n if (viewerCanvas) {\r\n viewerBounds = viewerCanvas.getBoundingClientRect()\r\n try { viewerDataUrl = viewerCanvas.toDataURL('image/png') } catch { /* ignore */ }\r\n }\r\n } else {\r\n const sel = currentViewer === ViewerNames.pointcloud ? '#pointcloud-viewer-container' : null\r\n viewerCanvas = sel\r\n ? document.querySelector<HTMLCanvasElement>(`${sel} canvas`)\r\n : document.querySelector<HTMLCanvasElement>('canvas')\r\n if (viewerCanvas) {\r\n viewerBounds = viewerCanvas.getBoundingClientRect()\r\n try { viewerDataUrl = viewerCanvas.toDataURL('image/png') } catch { /* ignore */ }\r\n }\r\n }\r\n\r\n const { default: html2canvas } = await import('html2canvas')\r\n\r\n // Find the dialog portal: walk up from [role=\"dialog\"] to its direct\r\n // child-of-body ancestor. We hide it with display:none before capture\r\n // and restore it after. This avoids ignoreElements on a body child,\r\n // which causes \"Unable to find element in cloned iframe\".\r\n let dialogPortal: HTMLElement | null = null\r\n const dialogEl = document.querySelector('[role=\"dialog\"]')\r\n if (dialogEl) {\r\n let node: Element | null = dialogEl\r\n while (node && node.parentElement !== document.body) {\r\n node = node.parentElement\r\n }\r\n if (node && node !== document.body) dialogPortal = node as HTMLElement\r\n }\r\n\r\n if (dialogPortal) dialogPortal.style.display = 'none'\r\n\r\n const captureUi = (ignoreImages: boolean) => html2canvas(document.body, {\r\n useCORS: true,\r\n allowTaint: false,\r\n logging: false,\r\n scale: window.devicePixelRatio || 1,\r\n width: window.innerWidth,\r\n height: window.innerHeight,\r\n x: window.scrollX,\r\n y: window.scrollY,\r\n windowWidth: window.innerWidth,\r\n windowHeight: window.innerHeight,\r\n scrollX: -window.scrollX,\r\n scrollY: -window.scrollY,\r\n ignoreElements: (el: Element) =>\r\n el === viewerCanvas || (ignoreImages && el.tagName === 'IMG'),\r\n })\r\n\r\n let uiCanvas: HTMLCanvasElement\r\n try {\r\n try {\r\n uiCanvas = await captureUi(false)\r\n } catch (err) {\r\n // Safari throws \"The operation is insecure\" when a cross-origin\r\n // image (org logo, user avatar, ...) taints html2canvas's internal\r\n // canvas even with allowTaint:false — retry once with all <img>\r\n // elements excluded so the capture still succeeds, just without\r\n // those images. Chrome is more lenient here, which is why this\r\n // only shows up on mobile Safari.\r\n console.warn('[useCaptureScreenshot] retrying without <img> elements after taint error:', err)\r\n uiCanvas = await captureUi(true)\r\n }\r\n } finally {\r\n if (dialogPortal) dialogPortal.style.display = ''\r\n }\r\n\r\n // Composite the pre-captured viewer image at the exact canvas bounds.\r\n if (viewerDataUrl && viewerBounds) {\r\n const ctx = uiCanvas.getContext('2d')\r\n if (ctx) {\r\n try {\r\n const img = await loadImage(viewerDataUrl)\r\n const dpr = window.devicePixelRatio || 1\r\n ctx.drawImage(\r\n img,\r\n viewerBounds.left * dpr,\r\n viewerBounds.top * dpr,\r\n viewerBounds.width * dpr,\r\n viewerBounds.height * dpr,\r\n )\r\n } catch { /* skip compositing on error */ }\r\n }\r\n }\r\n\r\n return new Promise<string | null>((resolve) => {\r\n uiCanvas.toBlob((blob) => {\r\n if (!blob) { resolve(null); return }\r\n blobToBase64(blob).then(resolve)\r\n }, 'image/png', 1.0)\r\n })\r\n } catch (err) {\r\n console.error('[useCaptureScreenshot] failed:', err)\r\n throw err instanceof Error ? err : new Error(String(err))\r\n }\r\n }, [currentViewer, map, world])\r\n}\r\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type ViewerPosition = {
|
|
2
|
+
type: 'map';
|
|
3
|
+
lat: number;
|
|
4
|
+
lng: number;
|
|
5
|
+
zoom: number;
|
|
6
|
+
bearing: number;
|
|
7
|
+
pitch: number;
|
|
8
|
+
} | {
|
|
9
|
+
type: 'bim' | 'pointcloud';
|
|
10
|
+
camX: number;
|
|
11
|
+
camY: number;
|
|
12
|
+
camZ: number;
|
|
13
|
+
tarX: number;
|
|
14
|
+
tarY: number;
|
|
15
|
+
tarZ: number;
|
|
16
|
+
};
|
|
17
|
+
export declare function useCurrentViewerPosition(): () => ViewerPosition | null;
|
|
18
|
+
//# sourceMappingURL=useCurrentViewerPosition.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCurrentViewerPosition.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/useCurrentViewerPosition.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACvF;IAAE,IAAI,EAAE,KAAK,GAAG,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtH,wBAAgB,wBAAwB,IAAI,MAAM,cAAc,GAAG,IAAI,CA4DtE"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import * as THREE from "three";
|
|
4
|
+
import { MenusContext, MapContext, BimContext, PointCloudContext } from "../store";
|
|
5
|
+
import { ViewerNames } from "../types";
|
|
6
|
+
import { getCameraPosition } from "../components/viewers/pointcloud/src/tools/SharePointCloudTool/src/getCameraPosition";
|
|
7
|
+
function useCurrentViewerPosition() {
|
|
8
|
+
const { state: menusState } = React.useContext(MenusContext);
|
|
9
|
+
const { currentViewer } = menusState.menus;
|
|
10
|
+
const { state: mapState } = React.useContext(MapContext);
|
|
11
|
+
const { map } = mapState.map;
|
|
12
|
+
const { state: bimState } = React.useContext(BimContext);
|
|
13
|
+
const { world } = bimState.bim;
|
|
14
|
+
const { state: pcState } = React.useContext(PointCloudContext);
|
|
15
|
+
const { viewer } = pcState.pointcloud;
|
|
16
|
+
return React.useCallback(() => {
|
|
17
|
+
if (currentViewer === ViewerNames.map && map) {
|
|
18
|
+
const center = map.getCenter();
|
|
19
|
+
return {
|
|
20
|
+
type: "map",
|
|
21
|
+
lat: parseFloat(center.lat.toFixed(7)),
|
|
22
|
+
lng: parseFloat(center.lng.toFixed(7)),
|
|
23
|
+
zoom: parseFloat(map.getZoom().toFixed(3)),
|
|
24
|
+
bearing: parseFloat(map.getBearing().toFixed(1)),
|
|
25
|
+
pitch: parseFloat(map.getPitch().toFixed(1))
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (currentViewer === ViewerNames.bim && (world == null ? void 0 : world.camera)) {
|
|
29
|
+
try {
|
|
30
|
+
const pos = new THREE.Vector3();
|
|
31
|
+
const tar = new THREE.Vector3();
|
|
32
|
+
world.camera.controls.getPosition(pos);
|
|
33
|
+
world.camera.controls.getTarget(tar);
|
|
34
|
+
return {
|
|
35
|
+
type: "bim",
|
|
36
|
+
camX: parseFloat(pos.x.toFixed(3)),
|
|
37
|
+
camY: parseFloat(pos.y.toFixed(3)),
|
|
38
|
+
camZ: parseFloat(pos.z.toFixed(3)),
|
|
39
|
+
tarX: parseFloat(tar.x.toFixed(3)),
|
|
40
|
+
tarY: parseFloat(tar.y.toFixed(3)),
|
|
41
|
+
tarZ: parseFloat(tar.z.toFixed(3))
|
|
42
|
+
};
|
|
43
|
+
} catch (e) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (currentViewer === ViewerNames.pointcloud && viewer) {
|
|
48
|
+
const cam = getCameraPosition(viewer);
|
|
49
|
+
if (!cam) return null;
|
|
50
|
+
return {
|
|
51
|
+
type: "pointcloud",
|
|
52
|
+
camX: parseFloat(cam.position.x.toFixed(3)),
|
|
53
|
+
camY: parseFloat(cam.position.y.toFixed(3)),
|
|
54
|
+
camZ: parseFloat(cam.position.z.toFixed(3)),
|
|
55
|
+
tarX: parseFloat(cam.target.x.toFixed(3)),
|
|
56
|
+
tarY: parseFloat(cam.target.y.toFixed(3)),
|
|
57
|
+
tarZ: parseFloat(cam.target.z.toFixed(3))
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}, [currentViewer, map, world, viewer]);
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
useCurrentViewerPosition
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=useCurrentViewerPosition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/core/hooks/useCurrentViewerPosition.ts"],"sourcesContent":["'use client'\r\n\r\nimport * as React from 'react'\r\nimport * as THREE from 'three'\r\nimport { MenusContext, MapContext, BimContext, PointCloudContext } from '../store'\r\nimport { ViewerNames } from '../types'\r\nimport { getCameraPosition } from '../components/viewers/pointcloud/src/tools/SharePointCloudTool/src/getCameraPosition'\r\n\r\nexport type ViewerPosition =\r\n | { type: 'map'; lat: number; lng: number; zoom: number; bearing: number; pitch: number }\r\n | { type: 'bim' | 'pointcloud'; camX: number; camY: number; camZ: number; tarX: number; tarY: number; tarZ: number }\r\n\r\nexport function useCurrentViewerPosition(): () => ViewerPosition | null {\r\n const { state: menusState } = React.useContext(MenusContext)\r\n const { currentViewer } = menusState.menus\r\n\r\n const { state: mapState } = React.useContext(MapContext)\r\n const { map } = mapState.map\r\n\r\n const { state: bimState } = React.useContext(BimContext)\r\n const { world } = bimState.bim\r\n\r\n const { state: pcState } = React.useContext(PointCloudContext)\r\n const { viewer } = pcState.pointcloud\r\n\r\n return React.useCallback((): ViewerPosition | null => {\r\n if (currentViewer === ViewerNames.map && map) {\r\n const center = map.getCenter()\r\n return {\r\n type: 'map',\r\n lat: parseFloat(center.lat.toFixed(7)),\r\n lng: parseFloat(center.lng.toFixed(7)),\r\n zoom: parseFloat(map.getZoom().toFixed(3)),\r\n bearing: parseFloat(map.getBearing().toFixed(1)),\r\n pitch: parseFloat(map.getPitch().toFixed(1)),\r\n }\r\n }\r\n\r\n if (currentViewer === ViewerNames.bim && world?.camera) {\r\n try {\r\n const pos = new THREE.Vector3()\r\n const tar = new THREE.Vector3()\r\n world.camera.controls.getPosition(pos)\r\n world.camera.controls.getTarget(tar)\r\n return {\r\n type: 'bim',\r\n camX: parseFloat(pos.x.toFixed(3)),\r\n camY: parseFloat(pos.y.toFixed(3)),\r\n camZ: parseFloat(pos.z.toFixed(3)),\r\n tarX: parseFloat(tar.x.toFixed(3)),\r\n tarY: parseFloat(tar.y.toFixed(3)),\r\n tarZ: parseFloat(tar.z.toFixed(3)),\r\n }\r\n } catch { return null }\r\n }\r\n\r\n if (currentViewer === ViewerNames.pointcloud && viewer) {\r\n const cam = getCameraPosition(viewer)\r\n if (!cam) return null\r\n return {\r\n type: 'pointcloud',\r\n camX: parseFloat(cam.position.x.toFixed(3)),\r\n camY: parseFloat(cam.position.y.toFixed(3)),\r\n camZ: parseFloat(cam.position.z.toFixed(3)),\r\n tarX: parseFloat(cam.target.x.toFixed(3)),\r\n tarY: parseFloat(cam.target.y.toFixed(3)),\r\n tarZ: parseFloat(cam.target.z.toFixed(3)),\r\n }\r\n }\r\n\r\n return null\r\n }, [currentViewer, map, world, viewer])\r\n}\r\n"],"mappings":";AAEA,YAAY,WAAW;AACvB,YAAY,WAAW;AACvB,SAAS,cAAc,YAAY,YAAY,yBAAyB;AACxE,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAM3B,SAAS,2BAAwD;AACtE,QAAM,EAAE,OAAO,WAAW,IAAI,MAAM,WAAW,YAAY;AAC3D,QAAM,EAAE,cAAc,IAAI,WAAW;AAErC,QAAM,EAAE,OAAO,SAAS,IAAI,MAAM,WAAW,UAAU;AACvD,QAAM,EAAE,IAAI,IAAI,SAAS;AAEzB,QAAM,EAAE,OAAO,SAAS,IAAI,MAAM,WAAW,UAAU;AACvD,QAAM,EAAE,MAAM,IAAI,SAAS;AAE3B,QAAM,EAAE,OAAO,QAAQ,IAAI,MAAM,WAAW,iBAAiB;AAC7D,QAAM,EAAE,OAAO,IAAI,QAAQ;AAE3B,SAAO,MAAM,YAAY,MAA6B;AACpD,QAAI,kBAAkB,YAAY,OAAO,KAAK;AAC5C,YAAM,SAAS,IAAI,UAAU;AAC7B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,KAAK,WAAW,OAAO,IAAI,QAAQ,CAAC,CAAC;AAAA,QACrC,KAAK,WAAW,OAAO,IAAI,QAAQ,CAAC,CAAC;AAAA,QACrC,MAAM,WAAW,IAAI,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAAA,QACzC,SAAS,WAAW,IAAI,WAAW,EAAE,QAAQ,CAAC,CAAC;AAAA,QAC/C,OAAO,WAAW,IAAI,SAAS,EAAE,QAAQ,CAAC,CAAC;AAAA,MAC7C;AAAA,IACF;AAEA,QAAI,kBAAkB,YAAY,QAAO,+BAAO,SAAQ;AACtD,UAAI;AACF,cAAM,MAAM,IAAI,MAAM,QAAQ;AAC9B,cAAM,MAAM,IAAI,MAAM,QAAQ;AAC9B,cAAM,OAAO,SAAS,YAAY,GAAG;AACrC,cAAM,OAAO,SAAS,UAAU,GAAG;AACnC,eAAO;AAAA,UACL,MAAM;AAAA,UACN,MAAM,WAAW,IAAI,EAAE,QAAQ,CAAC,CAAC;AAAA,UACjC,MAAM,WAAW,IAAI,EAAE,QAAQ,CAAC,CAAC;AAAA,UACjC,MAAM,WAAW,IAAI,EAAE,QAAQ,CAAC,CAAC;AAAA,UACjC,MAAM,WAAW,IAAI,EAAE,QAAQ,CAAC,CAAC;AAAA,UACjC,MAAM,WAAW,IAAI,EAAE,QAAQ,CAAC,CAAC;AAAA,UACjC,MAAM,WAAW,IAAI,EAAE,QAAQ,CAAC,CAAC;AAAA,QACnC;AAAA,MACF,SAAQ;AAAE,eAAO;AAAA,MAAK;AAAA,IACxB;AAEA,QAAI,kBAAkB,YAAY,cAAc,QAAQ;AACtD,YAAM,MAAM,kBAAkB,MAAM;AACpC,UAAI,CAAC,IAAK,QAAO;AACjB,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM,WAAW,IAAI,SAAS,EAAE,QAAQ,CAAC,CAAC;AAAA,QAC1C,MAAM,WAAW,IAAI,SAAS,EAAE,QAAQ,CAAC,CAAC;AAAA,QAC1C,MAAM,WAAW,IAAI,SAAS,EAAE,QAAQ,CAAC,CAAC;AAAA,QAC1C,MAAM,WAAW,IAAI,OAAO,EAAE,QAAQ,CAAC,CAAC;AAAA,QACxC,MAAM,WAAW,IAAI,OAAO,EAAE,QAAQ,CAAC,CAAC;AAAA,QACxC,MAAM,WAAW,IAAI,OAAO,EAAE,QAAQ,CAAC,CAAC;AAAA,MAC1C;AAAA,IACF;AAEA,WAAO;AAAA,EACT,GAAG,CAAC,eAAe,KAAK,OAAO,MAAM,CAAC;AACxC;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useShareUrl.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/useShareUrl.ts"],"names":[],"mappings":"AAQA,wBAAgB,WAAW,IAAI,MAAM,OAAO,CAAC,MAAM,CAAC,CAwEnD"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import * as THREE from "three";
|
|
4
|
+
import { MenusContext, MapContext, BimContext, PointCloudContext, BuildingsContext } from "../store";
|
|
5
|
+
import { ViewerNames } from "../types";
|
|
6
|
+
import { getCameraPosition } from "../components/viewers/pointcloud/src/tools/SharePointCloudTool/src/getCameraPosition";
|
|
7
|
+
function useShareUrl() {
|
|
8
|
+
const { state: menusState } = React.useContext(MenusContext);
|
|
9
|
+
const { currentViewer } = menusState.menus;
|
|
10
|
+
const { state: mapState } = React.useContext(MapContext);
|
|
11
|
+
const { map } = mapState.map;
|
|
12
|
+
const { state: bimState } = React.useContext(BimContext);
|
|
13
|
+
const { world } = bimState.bim;
|
|
14
|
+
const { state: pcState } = React.useContext(PointCloudContext);
|
|
15
|
+
const { viewer } = pcState.pointcloud;
|
|
16
|
+
const { state: buildingState } = React.useContext(BuildingsContext);
|
|
17
|
+
const { building } = buildingState.buildings;
|
|
18
|
+
return React.useCallback(async () => {
|
|
19
|
+
const origin = window.location.origin;
|
|
20
|
+
const pathname = window.location.pathname;
|
|
21
|
+
const fallback = window.location.href;
|
|
22
|
+
if (currentViewer === ViewerNames.map && map) {
|
|
23
|
+
const params = new URLSearchParams({
|
|
24
|
+
viewer: "map",
|
|
25
|
+
lat: map.getCenter().lat.toFixed(7),
|
|
26
|
+
lng: map.getCenter().lng.toFixed(7),
|
|
27
|
+
zoom: map.getZoom().toFixed(3),
|
|
28
|
+
bearing: map.getBearing().toFixed(1),
|
|
29
|
+
pitch: map.getPitch().toFixed(1)
|
|
30
|
+
});
|
|
31
|
+
return `${origin}${pathname}?${params.toString()}`;
|
|
32
|
+
}
|
|
33
|
+
if (currentViewer === ViewerNames.bim && (world == null ? void 0 : world.camera)) {
|
|
34
|
+
try {
|
|
35
|
+
const pos = new THREE.Vector3();
|
|
36
|
+
const tar = new THREE.Vector3();
|
|
37
|
+
world.camera.controls.getPosition(pos);
|
|
38
|
+
world.camera.controls.getTarget(tar);
|
|
39
|
+
const params = new URLSearchParams(new URL(window.location.href).search);
|
|
40
|
+
params.set("viewer", "bim");
|
|
41
|
+
params.set("camX", pos.x.toFixed(3));
|
|
42
|
+
params.set("camY", pos.y.toFixed(3));
|
|
43
|
+
params.set("camZ", pos.z.toFixed(3));
|
|
44
|
+
params.set("tarX", tar.x.toFixed(3));
|
|
45
|
+
params.set("tarY", tar.y.toFixed(3));
|
|
46
|
+
params.set("tarZ", tar.z.toFixed(3));
|
|
47
|
+
if (building) params.set("buildingId", String(building.id));
|
|
48
|
+
return `${origin}${pathname}?${params.toString()}`;
|
|
49
|
+
} catch (e) {
|
|
50
|
+
return fallback;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (currentViewer === ViewerNames.pointcloud && viewer) {
|
|
54
|
+
const cam = getCameraPosition(viewer);
|
|
55
|
+
if (cam) {
|
|
56
|
+
const params = new URLSearchParams(new URL(window.location.href).search);
|
|
57
|
+
params.set("viewer", "pointcloud");
|
|
58
|
+
params.set("camX", cam.position.x.toFixed(3));
|
|
59
|
+
params.set("camY", cam.position.y.toFixed(3));
|
|
60
|
+
params.set("camZ", cam.position.z.toFixed(3));
|
|
61
|
+
params.set("tarX", cam.target.x.toFixed(3));
|
|
62
|
+
params.set("tarY", cam.target.y.toFixed(3));
|
|
63
|
+
params.set("tarZ", cam.target.z.toFixed(3));
|
|
64
|
+
if (building) params.set("buildingId", String(building.id));
|
|
65
|
+
return `${origin}${pathname}?${params.toString()}`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return fallback;
|
|
69
|
+
}, [currentViewer, map, world, viewer, building]);
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
useShareUrl
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=useShareUrl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/core/hooks/useShareUrl.ts"],"sourcesContent":["'use client'\r\n\r\nimport * as React from 'react'\r\nimport * as THREE from 'three'\r\nimport { MenusContext, MapContext, BimContext, PointCloudContext, BuildingsContext } from '../store'\r\nimport { ViewerNames } from '../types'\r\nimport { getCameraPosition } from '../components/viewers/pointcloud/src/tools/SharePointCloudTool/src/getCameraPosition'\r\n\r\nexport function useShareUrl(): () => Promise<string> {\r\n const { state: menusState } = React.useContext(MenusContext)\r\n const { currentViewer } = menusState.menus\r\n\r\n const { state: mapState } = React.useContext(MapContext)\r\n const { map } = mapState.map\r\n\r\n const { state: bimState } = React.useContext(BimContext)\r\n const { world } = bimState.bim\r\n\r\n const { state: pcState } = React.useContext(PointCloudContext)\r\n const { viewer } = pcState.pointcloud\r\n\r\n const { state: buildingState } = React.useContext(BuildingsContext)\r\n const { building } = buildingState.buildings\r\n\r\n return React.useCallback(async (): Promise<string> => {\r\n const origin = window.location.origin\r\n const pathname = window.location.pathname\r\n const fallback = window.location.href\r\n\r\n if (currentViewer === ViewerNames.map && map) {\r\n const params = new URLSearchParams({\r\n viewer: 'map',\r\n lat: map.getCenter().lat.toFixed(7),\r\n lng: map.getCenter().lng.toFixed(7),\r\n zoom: map.getZoom().toFixed(3),\r\n bearing: map.getBearing().toFixed(1),\r\n pitch: map.getPitch().toFixed(1),\r\n })\r\n return `${origin}${pathname}?${params.toString()}`\r\n }\r\n\r\n if (currentViewer === ViewerNames.bim && world?.camera) {\r\n try {\r\n const pos = new THREE.Vector3()\r\n const tar = new THREE.Vector3()\r\n world.camera.controls.getPosition(pos)\r\n world.camera.controls.getTarget(tar)\r\n const params = new URLSearchParams(new URL(window.location.href).search)\r\n params.set('viewer', 'bim')\r\n params.set('camX', pos.x.toFixed(3))\r\n params.set('camY', pos.y.toFixed(3))\r\n params.set('camZ', pos.z.toFixed(3))\r\n params.set('tarX', tar.x.toFixed(3))\r\n params.set('tarY', tar.y.toFixed(3))\r\n params.set('tarZ', tar.z.toFixed(3))\r\n if (building) params.set('buildingId', String(building.id))\r\n return `${origin}${pathname}?${params.toString()}`\r\n } catch {\r\n return fallback\r\n }\r\n }\r\n\r\n if (currentViewer === ViewerNames.pointcloud && viewer) {\r\n const cam = getCameraPosition(viewer)\r\n if (cam) {\r\n const params = new URLSearchParams(new URL(window.location.href).search)\r\n params.set('viewer', 'pointcloud')\r\n params.set('camX', cam.position.x.toFixed(3))\r\n params.set('camY', cam.position.y.toFixed(3))\r\n params.set('camZ', cam.position.z.toFixed(3))\r\n params.set('tarX', cam.target.x.toFixed(3))\r\n params.set('tarY', cam.target.y.toFixed(3))\r\n params.set('tarZ', cam.target.z.toFixed(3))\r\n if (building) params.set('buildingId', String(building.id))\r\n return `${origin}${pathname}?${params.toString()}`\r\n }\r\n }\r\n\r\n return fallback\r\n }, [currentViewer, map, world, viewer, building])\r\n}\r\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core message catalogs keyed by lowercase locale code.
|
|
3
|
+
* The consuming app merges these under its own messages so that:
|
|
4
|
+
* 1. App-level overrides win (same key in app beats core).
|
|
5
|
+
* 2. Core's EN catalog backfills namespaces a locale hasn't translated yet.
|
|
6
|
+
*/
|
|
7
|
+
export declare const coreMessages: Record<string, Record<string, unknown>>;
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/i18n/index.ts"],"names":[],"mappings":"AAOA;;;;;GAKG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAkB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/core/i18n/index.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\n// Copyright (C) 2025 Collab Digital Twins\n\nimport en from './messages/en.json'\nimport fr from './messages/fr.json'\nimport es from './messages/es.json'\n\n/**\n * Core message catalogs keyed by lowercase locale code.\n * The consuming app merges these under its own messages so that:\n * 1. App-level overrides win (same key in app beats core).\n * 2. Core's EN catalog backfills namespaces a locale hasn't translated yet.\n */\nexport const coreMessages: Record<string, Record<string, unknown>> = { en, fr, es }\n"],"mappings":"AAGA,OAAO,QAAQ;AACf,OAAO,QAAQ;AACf,OAAO,QAAQ;AAQR,MAAM,eAAwD,EAAE,IAAI,IAAI,GAAG;","names":[]}
|