@collabdt/core 0.2.0 → 0.2.1
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/styles/globals.css +46 -33
- package/package.json +1 -1
|
@@ -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":"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","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":";AAEA,YAAY,WAAW;AACvB,SAAS,cAAc,YAAY,kBAAkB;AACrD,SAAS,mBAAmB;AAE5B,SAAS,aAAa,MAA6B;AACjD,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,SAAS,IAAI,WAAW;AAC9B,WAAO,SAAS,MAAM,QAAQ,OAAO,MAAgB;AACrD,WAAO,cAAc,IAAI;AAAA,EAC3B,CAAC;AACH;AAEA,SAAS,UAAU,KAAwC;AACzD,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,MAAM,IAAI,MAAM;AACtB,QAAI,SAAS,MAAM,QAAQ,GAAG;AAC9B,QAAI,UAAU;AACd,QAAI,MAAM;AAAA,EACZ,CAAC;AACH;AAEO,SAAS,uBAAqD;AACnE,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,SAAO,MAAM,YAAY,YAAoC;AAC3D,QAAI;AACF,UAAI,gBAA+B;AACnC,UAAI,eAAyC;AAC7C,UAAI,eAA+B;AAEnC,UAAI,kBAAkB,YAAY,OAAO,KAAK;AAC5C,cAAM,QAAQ,KAAK;AAAA,UACjB,IAAI,QAAc,CAAC,QAAQ;AACzB,gBAAI,KAAK,UAAU,MAAM;AACvB,6BAAe,IAAI,UAAU;AAC7B,6BAAe,aAAa,sBAAsB;AAClD,kBAAI;AAAE,gCAAgB,aAAa,UAAU,WAAW;AAAA,cAAE,SAAQ;AAAA,cAAqB;AACvF,kBAAI;AAAA,YACN,CAAC;AACD,gBAAI,eAAe;AAAA,UACrB,CAAC;AAAA,UACD,IAAI,QAAc,CAAC,MAAM,WAAW,GAAG,GAAI,CAAC;AAAA,QAC9C,CAAC;AAAA,MACH,WAAW,kBAAkB,YAAY,QAAO,+BAAO,cAAY,+BAAO,WAAS,+BAAO,SAAQ;AAChG,cAAM,SAAS,MAAM,OAAO,MAAM,MAAM,OAAO,MAAM,OAAO,KAAK;AACjE,uBAAe,SAAS,cAAiC,8BAA8B;AACvF,YAAI,cAAc;AAChB,yBAAe,aAAa,sBAAsB;AAClD,cAAI;AAAE,4BAAgB,aAAa,UAAU,WAAW;AAAA,UAAE,SAAQ;AAAA,UAAe;AAAA,QACnF;AAAA,MACF,OAAO;AACL,cAAM,MAAM,kBAAkB,YAAY,aAAa,iCAAiC;AACxF,uBAAe,MACX,SAAS,cAAiC,GAAG,GAAG,SAAS,IACzD,SAAS,cAAiC,QAAQ;AACtD,YAAI,cAAc;AAChB,yBAAe,aAAa,sBAAsB;AAClD,cAAI;AAAE,4BAAgB,aAAa,UAAU,WAAW;AAAA,UAAE,SAAQ;AAAA,UAAe;AAAA,QACnF;AAAA,MACF;AAEA,YAAM,EAAE,SAAS,YAAY,IAAI,MAAM,OAAO,aAAa;AAM3D,UAAI,eAAmC;AACvC,YAAM,WAAW,SAAS,cAAc,iBAAiB;AACzD,UAAI,UAAU;AACZ,YAAI,OAAuB;AAC3B,eAAO,QAAQ,KAAK,kBAAkB,SAAS,MAAM;AACnD,iBAAO,KAAK;AAAA,QACd;AACA,YAAI,QAAQ,SAAS,SAAS,KAAM,gBAAe;AAAA,MACrD;AAEA,UAAI,aAAc,cAAa,MAAM,UAAU;AAE/C,YAAM,YAAY,CAAC,iBAA0B,YAAY,SAAS,MAAM;AAAA,QACtE,SAAS;AAAA,QACT,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,OAAO,OAAO,oBAAoB;AAAA,QAClC,OAAO,OAAO;AAAA,QACd,QAAQ,OAAO;AAAA,QACf,GAAG,OAAO;AAAA,QACV,GAAG,OAAO;AAAA,QACV,aAAa,OAAO;AAAA,QACpB,cAAc,OAAO;AAAA,QACrB,SAAS,CAAC,OAAO;AAAA,QACjB,SAAS,CAAC,OAAO;AAAA,QACjB,gBAAgB,CAAC,OACf,OAAO,gBAAiB,gBAAgB,GAAG,YAAY;AAAA,MAC3D,CAAC;AAED,UAAI;AACJ,UAAI;AACF,YAAI;AACF,qBAAW,MAAM,UAAU,KAAK;AAAA,QAClC,SAAS,KAAK;AAOZ,kBAAQ,KAAK,6EAA6E,GAAG;AAC7F,qBAAW,MAAM,UAAU,IAAI;AAAA,QACjC;AAAA,MACF,UAAE;AACA,YAAI,aAAc,cAAa,MAAM,UAAU;AAAA,MACjD;AAGA,UAAI,iBAAiB,cAAc;AACjC,cAAM,MAAM,SAAS,WAAW,IAAI;AACpC,YAAI,KAAK;AACP,cAAI;AACF,kBAAM,MAAM,MAAM,UAAU,aAAa;AACzC,kBAAM,MAAM,OAAO,oBAAoB;AACvC,gBAAI;AAAA,cACF;AAAA,cACA,aAAa,OAAO;AAAA,cACpB,aAAa,MAAM;AAAA,cACnB,aAAa,QAAQ;AAAA,cACrB,aAAa,SAAS;AAAA,YACxB;AAAA,UACF,SAAQ;AAAA,UAAkC;AAAA,QAC5C;AAAA,MACF;AAEA,aAAO,IAAI,QAAuB,CAAC,YAAY;AAC7C,iBAAS,OAAO,CAAC,SAAS;AACxB,cAAI,CAAC,MAAM;AAAE,oBAAQ,IAAI;AAAG;AAAA,UAAO;AACnC,uBAAa,IAAI,EAAE,KAAK,OAAO;AAAA,QACjC,GAAG,aAAa,CAAG;AAAA,MACrB,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,cAAQ,MAAM,kCAAkC,GAAG;AACnD,YAAM,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAAA,IAC1D;AAAA,EACF,GAAG,CAAC,eAAe,KAAK,KAAK,CAAC;AAChC;","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":[]}
|
|
@@ -963,29 +963,6 @@ ul {
|
|
|
963
963
|
color: white;
|
|
964
964
|
}
|
|
965
965
|
|
|
966
|
-
/* smooths any reverse transitions */
|
|
967
|
-
.maplibregl-ctrl-bottom-left,
|
|
968
|
-
.maplibregl-ctrl-bottom-right {
|
|
969
|
-
transform: translateY(-75px);
|
|
970
|
-
transition: transform 400ms ease-in-out;
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
.map-ctrls-init {
|
|
974
|
-
transform: translateY(0px);
|
|
975
|
-
transition: transform 400ms ease-in-out;
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
/* sliding to match toolbar behavior */
|
|
979
|
-
.mobile-map-ctrls {
|
|
980
|
-
transform: translateY(-75px);
|
|
981
|
-
transition: transform 300ms ease-in-out;
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
.mobile-map-ctrls-expanded {
|
|
985
|
-
transform: translateY(-255px);
|
|
986
|
-
transition: transform 250ms ease-in-out;
|
|
987
|
-
}
|
|
988
|
-
|
|
989
966
|
.center-align {
|
|
990
967
|
display: flex;
|
|
991
968
|
flex-direction: column;
|
|
@@ -998,7 +975,6 @@ ul {
|
|
|
998
975
|
white-space: nowrap !important;
|
|
999
976
|
overflow: hidden !important;
|
|
1000
977
|
text-overflow: ellipsis !important;
|
|
1001
|
-
z-index: -1 !important;
|
|
1002
978
|
}
|
|
1003
979
|
|
|
1004
980
|
.maplibregl-ctrl-bottom-right details:hover {
|
|
@@ -1010,15 +986,6 @@ ul {
|
|
|
1010
986
|
background-color: white !important;
|
|
1011
987
|
}
|
|
1012
988
|
|
|
1013
|
-
@media only screen and (min-width: 475px) {
|
|
1014
|
-
/* smooths any reverse transitions */
|
|
1015
|
-
.maplibregl-ctrl-bottom-left,
|
|
1016
|
-
.maplibregl-ctrl-bottom-right {
|
|
1017
|
-
transform: translateY(0px);
|
|
1018
|
-
transition: transform 400ms ease-in-out;
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
989
|
@media only screen and (max-width: 600px) {
|
|
1023
990
|
.maplibregl-ctrl-zoom-in {
|
|
1024
991
|
opacity: 0;
|
|
@@ -1052,6 +1019,52 @@ ul {
|
|
|
1052
1019
|
}
|
|
1053
1020
|
}
|
|
1054
1021
|
|
|
1022
|
+
@media (max-width: 768px) {
|
|
1023
|
+
.maplibregl-ctrl-bottom-right {
|
|
1024
|
+
margin-bottom: 48px !important;
|
|
1025
|
+
font-size: 10px !important;
|
|
1026
|
+
/* The floating map toolbar (bottom, centered) sits at z-10 — without
|
|
1027
|
+
this, it renders on top of (hides) the attribution popup instead of
|
|
1028
|
+
the popup coming forward/in front of it when tapped open. This
|
|
1029
|
+
container is already `position: absolute` via MapLibre's own base
|
|
1030
|
+
CSS, so z-index applies directly here. */
|
|
1031
|
+
z-index: 30 !important;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
/* MapLibre auto-adds `open` + `.maplibregl-compact-show` together once on
|
|
1035
|
+
mount for narrow viewports (attribution_control.ts _updateCompact()).
|
|
1036
|
+
Trying to distinguish that from a genuine tap via those same
|
|
1037
|
+
classes/attributes is unreliable — tapping <summary> fires BOTH the
|
|
1038
|
+
browser's native <details> toggle AND MapLibre's own JS toggle at once,
|
|
1039
|
+
which can reproduce the same "both present" combination on a real tap
|
|
1040
|
+
too. Side-step MapLibre's class bookkeeping entirely: force hidden
|
|
1041
|
+
always, reveal only on real, live interaction (:hover for pointer
|
|
1042
|
+
devices, :focus-within once tapped/focused, :active as a fallback for
|
|
1043
|
+
iOS Safari's unreliable tap-to-focus on button-like elements). */
|
|
1044
|
+
.maplibregl-ctrl-bottom-right .maplibregl-ctrl-attrib-inner {
|
|
1045
|
+
display: none !important;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
.maplibregl-ctrl-bottom-right details:hover .maplibregl-ctrl-attrib-inner,
|
|
1049
|
+
.maplibregl-ctrl-bottom-right details:focus-within .maplibregl-ctrl-attrib-inner,
|
|
1050
|
+
.maplibregl-ctrl-bottom-right details:active .maplibregl-ctrl-attrib-inner {
|
|
1051
|
+
display: block !important;
|
|
1052
|
+
max-width: calc(100vw - 20px) !important;
|
|
1053
|
+
width: auto !important;
|
|
1054
|
+
white-space: normal !important;
|
|
1055
|
+
word-break: break-word !important;
|
|
1056
|
+
overflow-wrap: break-word !important;
|
|
1057
|
+
font-size: 10px !important;
|
|
1058
|
+
box-sizing: border-box !important;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
.maplibregl-ctrl-bottom-right details:hover,
|
|
1062
|
+
.maplibregl-ctrl-bottom-right details:focus-within,
|
|
1063
|
+
.maplibregl-ctrl-bottom-right details:active {
|
|
1064
|
+
overflow: visible !important;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1055
1068
|
/* Home page styles */
|
|
1056
1069
|
@layer utilities {
|
|
1057
1070
|
.gradient-button {
|