@collabdt/core 0.0.55 → 0.0.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/components/AppSidebarContent.d.ts.map +1 -1
- package/dist/core/components/AppSidebarContent.js +3 -3
- 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 +3 -2
- package/dist/core/components/authentication/AuthPage.js.map +1 -1
- package/dist/core/components/authentication/Signin.d.ts +5 -1
- package/dist/core/components/authentication/Signin.d.ts.map +1 -1
- package/dist/core/components/authentication/Signin.js +17 -6
- package/dist/core/components/authentication/Signin.js.map +1 -1
- package/dist/core/components/settings/src/OrganizationSettingsPanel.d.ts.map +1 -1
- package/dist/core/components/settings/src/OrganizationSettingsPanel.js +4 -2
- package/dist/core/components/settings/src/OrganizationSettingsPanel.js.map +1 -1
- package/dist/core/components/ui/AnimatedBackground.d.ts.map +1 -1
- package/dist/core/components/ui/AnimatedBackground.js +3 -1
- package/dist/core/components/ui/AnimatedBackground.js.map +1 -1
- package/dist/core/components/ui/AppSidebar.d.ts.map +1 -1
- package/dist/core/components/ui/AppSidebar.js +11 -11
- package/dist/core/components/ui/AppSidebar.js.map +1 -1
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.js +3 -1
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.js +4 -2
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/RowActions.d.ts.map +1 -1
- package/dist/core/components/viewers/map/datasets/RowActions.js +3 -1
- package/dist/core/components/viewers/map/datasets/RowActions.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/index.js +2 -2
- package/dist/core/components/viewers/map/datasets/index.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/minioDatasets.d.ts +1 -1
- package/dist/core/components/viewers/map/datasets/src/minioDatasets.d.ts.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/minioDatasets.js +3 -5
- package/dist/core/components/viewers/map/datasets/src/minioDatasets.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.js +5 -3
- package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/LayersTab/index.js +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/LayersTab/index.js.map +1 -1
- package/dist/core/components/viewers/pointcloud/PointCloudViewer.d.ts.map +1 -1
- package/dist/core/components/viewers/pointcloud/PointCloudViewer.js +4 -2
- package/dist/core/components/viewers/pointcloud/PointCloudViewer.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 +4 -2
- package/dist/core/components/viewers/pointcloud/src/PointCloudLoadingState/index.js.map +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudManagement/PointCloudTable.d.ts.map +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudManagement/PointCloudTable.js +7 -5
- package/dist/core/components/viewers/pointcloud/src/PointCloudManagement/PointCloudTable.js.map +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudManagement/PointCloudUploadPanel.d.ts.map +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudManagement/PointCloudUploadPanel.js +15 -8
- package/dist/core/components/viewers/pointcloud/src/PointCloudManagement/PointCloudUploadPanel.js.map +1 -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 +13 -12
- package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/src/PointCloudSection.js.map +1 -1
- package/dist/core/store/AppConfig/context.d.ts +10 -1
- package/dist/core/store/AppConfig/context.d.ts.map +1 -1
- package/dist/core/store/AppConfig/context.js +26 -5
- package/dist/core/store/AppConfig/context.js.map +1 -1
- package/package.json +1 -1
|
@@ -120,7 +120,7 @@ function LayersTab() {
|
|
|
120
120
|
const loadOrganizationalDatasets = async () => {
|
|
121
121
|
var _a2;
|
|
122
122
|
try {
|
|
123
|
-
const martinBaseUrl = (_a2 =
|
|
123
|
+
const martinBaseUrl = (_a2 = appConfigState.runtimeConfig.martinUrl) == null ? void 0 : _a2.replace(/\/+$/, "");
|
|
124
124
|
if (!martinBaseUrl) return;
|
|
125
125
|
const datasetsUrl = martinBaseUrl.includes("/tiles/index.json") ? martinBaseUrl : `${martinBaseUrl}/tiles/index.json`;
|
|
126
126
|
const localPortal = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../../src/core/components/viewers/map/src/MapSidebar/src/LayersTab/index.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 { usePathname } from 'next/navigation'\r\n\r\nimport { DatasetsContext, MenusContext, useMapContext } from '../../../../../../../store'\r\nimport { useAppConfigContext } from '../../../../../../../store/AppConfig/context'\r\nimport {\r\n useOpenDataPortalsByCountrySubdivision,\r\n useOpenDataPortalsByGroup,\r\n useOpenDataPortalsByMunicipality,\r\n} from '../../../../../../../hooks/openDataPortals/openDataPortals'\r\nimport { DatasetGroup } from '../../../../../../../types/dbTypes'\r\nimport type { Dataset } from '../../../../../../../types/datasetTypes'\r\nimport { useDatasetsForPortals } from '../../../../datasets/src/useDatasetsForPortals'\r\nimport { fetchLocalDatasets } from '../../../../datasets/src/localDatasets'\r\nimport { SearchInput } from '../../../../../../ui'\r\nimport { AppliedDatasetsSection } from './src/AppliedDatasetsSection'\r\nimport { AvailableDatasetsSection } from './src/AvailableDatasetsSection'\r\n\r\ntype OrgVisibility = {\r\n isAdmin: boolean\r\n currentOrgId: number\r\n allowedOrgIds: number[]\r\n}\r\n\r\nconst ADMIN_ALLOWED_ORGS = [1, 2, 3, 4, 5, 6]\r\nconst ORG_BY_PATH_PREFIX: Record<string, number> = {\r\n '/envirocentre': 1,\r\n '/dnd': 3,\r\n '/canada': 4,\r\n '/gac': 5,\r\n '/carleton': 6,\r\n}\r\n\r\nfunction normalizeOrgId(org?: number | string | null) {\r\n if (org === null || org === undefined) return undefined\r\n const parsed = typeof org === 'number' ? org : Number.parseInt(String(org), 10)\r\n return Number.isFinite(parsed) ? parsed : undefined\r\n}\r\n\r\nfunction getOrgVisibilityFromPath(pathname?: string | null): OrgVisibility {\r\n const normalizedPath = (pathname || '').toLowerCase()\r\n const firstSegment = normalizedPath.split('/').filter(Boolean)[0]\r\n const prefix = firstSegment ? `/${firstSegment}` : ''\r\n\r\n if (prefix === '/cdt') {\r\n return { isAdmin: true, currentOrgId: 1, allowedOrgIds: ADMIN_ALLOWED_ORGS }\r\n }\r\n\r\n const currentOrgId = ORG_BY_PATH_PREFIX[prefix] ?? 2\r\n return { isAdmin: false, currentOrgId, allowedOrgIds: Array.from(new Set([2, currentOrgId])) }\r\n}\r\n\r\nfunction datasetVisibleForOrg(dataset: Dataset, visibility: OrgVisibility) {\r\n if (visibility.isAdmin) return true\r\n if (dataset.group !== DatasetGroup.Organizational) return true\r\n const orgId = normalizeOrgId(dataset.organization)\r\n if (orgId === undefined) return false\r\n return visibility.allowedOrgIds.includes(orgId)\r\n}\r\n\r\nconst BLOCKLIST_URLS = [\r\n '/2021_statistics_canada_boundaries/featureserver',\r\n '/limites_du_recensement_de_statistique_canada_(2021)/featureserver',\r\n]\r\nconst BLOCKLIST_NAMES = [\r\n '2021 statistics canada boundaries',\r\n 'limites du recensement de statistique canada (2021)',\r\n]\r\n\r\nfunction isBlocklisted(ds: Dataset) {\r\n const url = (ds.url || '').toLowerCase()\r\n const name = (ds.name || '').toLowerCase()\r\n return BLOCKLIST_URLS.some(u => url.includes(u)) || BLOCKLIST_NAMES.some(n => name.includes(n))\r\n}\r\n\r\nfunction sanitizeDatasets(list: Dataset[]) {\r\n const uniqueByName = new Map<string, Dataset>()\r\n for (const ds of list) {\r\n const name = ds.name?.toLowerCase() || ''\r\n if (!uniqueByName.has(name)) uniqueByName.set(name, ds)\r\n }\r\n const filtered = [...uniqueByName.values()].filter((ds) => {\r\n const name = ds.name?.toLowerCase() || ''\r\n const type = ds.type?.toLowerCase() || ''\r\n return !name.includes('basemap') && !type.includes('geocoding') && !isBlocklisted(ds)\r\n })\r\n return filtered.sort((a, b) => (a.name || '').localeCompare(b.name || ''))\r\n}\r\n\r\nexport function LayersTab() {\r\n const [searchQuery, setSearchQuery] = React.useState('')\r\n const [favourites, setFavourites] = React.useState<Dataset[]>([])\r\n\r\n const { state: mapState } = useMapContext()\r\n const { currentLocation } = mapState.map\r\n const { state: appConfigState } = useAppConfigContext()\r\n const org = appConfigState.appConfig.organization\r\n const orgCountry = (org?.country || '').toUpperCase()\r\n\r\n const municipality = currentLocation?.municipality ?? org?.municipality ?? null\r\n const countrySubdivision = currentLocation?.countrySubdivision ?? org?.countrySubdivision ?? null\r\n\r\n const { state: datasetState, dispatch: datasetDispatch } = React.useContext(DatasetsContext)\r\n const { state: menusState } = React.useContext(MenusContext)\r\n const { rowsPerPage } = menusState.menus\r\n\r\n const pathname = usePathname()\r\n const orgVisibility = React.useMemo(() => getOrgVisibilityFromPath(pathname), [pathname])\r\n\r\n const { openDataPortals: municipalPortals } = useOpenDataPortalsByMunicipality(municipality)\r\n const { openDataPortals: subdivisionPortals } = useOpenDataPortalsByCountrySubdivision(countrySubdivision)\r\n const { openDataPortals: nationalPortals } = useOpenDataPortalsByGroup(DatasetGroup.National)\r\n\r\n const matchesOrgCountry = React.useCallback(\r\n (portalCountry: string | null | undefined) => {\r\n if (!orgCountry) return true\r\n if (!portalCountry) return true\r\n return portalCountry.toUpperCase() === orgCountry\r\n },\r\n [orgCountry],\r\n )\r\n\r\n const filteredMunicipalPortals = React.useMemo(\r\n () => (Array.isArray(municipalPortals) ? municipalPortals.filter(p => matchesOrgCountry(p.country)) : []),\r\n [municipalPortals, matchesOrgCountry],\r\n )\r\n const filteredSubdivisionPortals = React.useMemo(\r\n () => (Array.isArray(subdivisionPortals)\r\n ? subdivisionPortals.filter(p => p.municipality == null && matchesOrgCountry(p.country))\r\n : []),\r\n [subdivisionPortals, matchesOrgCountry],\r\n )\r\n const filteredNationalPortals = React.useMemo(\r\n () => (Array.isArray(nationalPortals) ? nationalPortals.filter(p => matchesOrgCountry(p.country)) : []),\r\n [nationalPortals, matchesOrgCountry],\r\n )\r\n\r\n const municipal = useDatasetsForPortals(filteredMunicipalPortals, { rowsPerPage })\r\n const subdivision = useDatasetsForPortals(filteredSubdivisionPortals, { rowsPerPage })\r\n const national = useDatasetsForPortals(filteredNationalPortals, { rowsPerPage })\r\n\r\n React.useEffect(() => {\r\n const loadOrganizationalDatasets = async () => {\r\n try {\r\n const martinBaseUrl = process.env.NEXT_PUBLIC_MARTIN_SERVER_URL?.replace(/\\/+$/, '')\r\n if (!martinBaseUrl) return\r\n\r\n const datasetsUrl = martinBaseUrl.includes('/tiles/index.json')\r\n ? martinBaseUrl\r\n : `${martinBaseUrl}/tiles/index.json`\r\n\r\n const localPortal = {\r\n id: -1,\r\n name: 'Organizational Datasets',\r\n apiUrl: datasetsUrl,\r\n dataManagementSystem: 'Other' as const,\r\n countrySubdivision: null,\r\n municipality: null,\r\n group: 'Organizational' as const,\r\n }\r\n\r\n const organizationalDatasets = await fetchLocalDatasets(localPortal as any)\r\n const visible = organizationalDatasets.filter(ds => datasetVisibleForOrg(ds, orgVisibility))\r\n if (visible.length > 0) {\r\n datasetDispatch({ type: 'SET_DATASETS', payload: { datasets: visible } })\r\n }\r\n }\r\n catch (error) {\r\n console.error('Failed to load organizational datasets:', error)\r\n }\r\n }\r\n loadOrganizationalDatasets()\r\n }, [datasetDispatch, orgVisibility])\r\n\r\n const nationalDatasets = React.useMemo(\r\n () => sanitizeDatasets(national.datasets.filter(ds => datasetVisibleForOrg(ds, orgVisibility))),\r\n [national.datasets, orgVisibility],\r\n )\r\n const subdivisionDatasets = React.useMemo(\r\n () => sanitizeDatasets(subdivision.datasets.filter(ds => datasetVisibleForOrg(ds, orgVisibility))),\r\n [subdivision.datasets, orgVisibility],\r\n )\r\n const municipalDatasets = React.useMemo(\r\n () => sanitizeDatasets(municipal.datasets.filter(ds => datasetVisibleForOrg(ds, orgVisibility))),\r\n [municipal.datasets, orgVisibility],\r\n )\r\n const orgDatasets = React.useMemo(\r\n () => sanitizeDatasets((datasetState.datasets.datasets || []).filter(ds => datasetVisibleForOrg(ds, orgVisibility))),\r\n [datasetState.datasets.datasets, orgVisibility],\r\n )\r\n\r\n const allDatasets = React.useMemo(() => {\r\n const combined = [...nationalDatasets, ...subdivisionDatasets, ...municipalDatasets, ...orgDatasets]\r\n return sanitizeDatasets(combined)\r\n }, [nationalDatasets, subdivisionDatasets, municipalDatasets, orgDatasets])\r\n\r\n const liveDatasets = React.useMemo(\r\n () => allDatasets.filter(ds => ds.portal?.live === true),\r\n [allDatasets],\r\n )\r\n\r\n const toggleFavourite = React.useCallback((dataset: Dataset) => {\r\n setFavourites(prev => (\r\n prev.some(f => f.name === dataset.name)\r\n ? prev.filter(f => f.name !== dataset.name)\r\n : [...prev, dataset]\r\n ))\r\n }, [])\r\n\r\n const showMunicipal = Boolean(municipality)\r\n\r\n return (\r\n <div className=\"flex-1 flex flex-col space-y-4 py-4 overflow-hidden\">\r\n <div className=\"px-4\">\r\n <SearchInput\r\n placeholder=\"Search...\"\r\n value={searchQuery}\r\n onChange={e => setSearchQuery(e.target.value)}\r\n />\r\n </div>\r\n <div className=\"flex-1 min-h-0 overflow-y-auto space-y-2 divide-y\">\r\n <div className=\"pb-2\">\r\n <AppliedDatasetsSection />\r\n </div>\r\n <div className=\"pt-2\">\r\n <AvailableDatasetsSection\r\n query={searchQuery}\r\n allDatasets={allDatasets}\r\n nationalDatasets={nationalDatasets}\r\n subdivisionDatasets={subdivisionDatasets}\r\n municipalDatasets={municipalDatasets}\r\n organizationalDatasets={orgDatasets}\r\n liveDatasets={liveDatasets}\r\n showMunicipal={showMunicipal}\r\n favourites={favourites}\r\n onFavouriteToggle={toggleFavourite}\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n )\r\n}\r\n"],"mappings":"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","names":["_a"]}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../../src/core/components/viewers/map/src/MapSidebar/src/LayersTab/index.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 { usePathname } from 'next/navigation'\r\n\r\nimport { DatasetsContext, MenusContext, useMapContext } from '../../../../../../../store'\r\nimport { useAppConfigContext } from '../../../../../../../store/AppConfig/context'\r\nimport {\r\n useOpenDataPortalsByCountrySubdivision,\r\n useOpenDataPortalsByGroup,\r\n useOpenDataPortalsByMunicipality,\r\n} from '../../../../../../../hooks/openDataPortals/openDataPortals'\r\nimport { DatasetGroup } from '../../../../../../../types/dbTypes'\r\nimport type { Dataset } from '../../../../../../../types/datasetTypes'\r\nimport { useDatasetsForPortals } from '../../../../datasets/src/useDatasetsForPortals'\r\nimport { fetchLocalDatasets } from '../../../../datasets/src/localDatasets'\r\nimport { SearchInput } from '../../../../../../ui'\r\nimport { AppliedDatasetsSection } from './src/AppliedDatasetsSection'\r\nimport { AvailableDatasetsSection } from './src/AvailableDatasetsSection'\r\n\r\ntype OrgVisibility = {\r\n isAdmin: boolean\r\n currentOrgId: number\r\n allowedOrgIds: number[]\r\n}\r\n\r\nconst ADMIN_ALLOWED_ORGS = [1, 2, 3, 4, 5, 6]\r\nconst ORG_BY_PATH_PREFIX: Record<string, number> = {\r\n '/envirocentre': 1,\r\n '/dnd': 3,\r\n '/canada': 4,\r\n '/gac': 5,\r\n '/carleton': 6,\r\n}\r\n\r\nfunction normalizeOrgId(org?: number | string | null) {\r\n if (org === null || org === undefined) return undefined\r\n const parsed = typeof org === 'number' ? org : Number.parseInt(String(org), 10)\r\n return Number.isFinite(parsed) ? parsed : undefined\r\n}\r\n\r\nfunction getOrgVisibilityFromPath(pathname?: string | null): OrgVisibility {\r\n const normalizedPath = (pathname || '').toLowerCase()\r\n const firstSegment = normalizedPath.split('/').filter(Boolean)[0]\r\n const prefix = firstSegment ? `/${firstSegment}` : ''\r\n\r\n if (prefix === '/cdt') {\r\n return { isAdmin: true, currentOrgId: 1, allowedOrgIds: ADMIN_ALLOWED_ORGS }\r\n }\r\n\r\n const currentOrgId = ORG_BY_PATH_PREFIX[prefix] ?? 2\r\n return { isAdmin: false, currentOrgId, allowedOrgIds: Array.from(new Set([2, currentOrgId])) }\r\n}\r\n\r\nfunction datasetVisibleForOrg(dataset: Dataset, visibility: OrgVisibility) {\r\n if (visibility.isAdmin) return true\r\n if (dataset.group !== DatasetGroup.Organizational) return true\r\n const orgId = normalizeOrgId(dataset.organization)\r\n if (orgId === undefined) return false\r\n return visibility.allowedOrgIds.includes(orgId)\r\n}\r\n\r\nconst BLOCKLIST_URLS = [\r\n '/2021_statistics_canada_boundaries/featureserver',\r\n '/limites_du_recensement_de_statistique_canada_(2021)/featureserver',\r\n]\r\nconst BLOCKLIST_NAMES = [\r\n '2021 statistics canada boundaries',\r\n 'limites du recensement de statistique canada (2021)',\r\n]\r\n\r\nfunction isBlocklisted(ds: Dataset) {\r\n const url = (ds.url || '').toLowerCase()\r\n const name = (ds.name || '').toLowerCase()\r\n return BLOCKLIST_URLS.some(u => url.includes(u)) || BLOCKLIST_NAMES.some(n => name.includes(n))\r\n}\r\n\r\nfunction sanitizeDatasets(list: Dataset[]) {\r\n const uniqueByName = new Map<string, Dataset>()\r\n for (const ds of list) {\r\n const name = ds.name?.toLowerCase() || ''\r\n if (!uniqueByName.has(name)) uniqueByName.set(name, ds)\r\n }\r\n const filtered = [...uniqueByName.values()].filter((ds) => {\r\n const name = ds.name?.toLowerCase() || ''\r\n const type = ds.type?.toLowerCase() || ''\r\n return !name.includes('basemap') && !type.includes('geocoding') && !isBlocklisted(ds)\r\n })\r\n return filtered.sort((a, b) => (a.name || '').localeCompare(b.name || ''))\r\n}\r\n\r\nexport function LayersTab() {\r\n const [searchQuery, setSearchQuery] = React.useState('')\r\n const [favourites, setFavourites] = React.useState<Dataset[]>([])\r\n\r\n const { state: mapState } = useMapContext()\r\n const { currentLocation } = mapState.map\r\n const { state: appConfigState } = useAppConfigContext()\r\n const org = appConfigState.appConfig.organization\r\n const orgCountry = (org?.country || '').toUpperCase()\r\n\r\n const municipality = currentLocation?.municipality ?? org?.municipality ?? null\r\n const countrySubdivision = currentLocation?.countrySubdivision ?? org?.countrySubdivision ?? null\r\n\r\n const { state: datasetState, dispatch: datasetDispatch } = React.useContext(DatasetsContext)\r\n const { state: menusState } = React.useContext(MenusContext)\r\n const { rowsPerPage } = menusState.menus\r\n\r\n const pathname = usePathname()\r\n const orgVisibility = React.useMemo(() => getOrgVisibilityFromPath(pathname), [pathname])\r\n\r\n const { openDataPortals: municipalPortals } = useOpenDataPortalsByMunicipality(municipality)\r\n const { openDataPortals: subdivisionPortals } = useOpenDataPortalsByCountrySubdivision(countrySubdivision)\r\n const { openDataPortals: nationalPortals } = useOpenDataPortalsByGroup(DatasetGroup.National)\r\n\r\n const matchesOrgCountry = React.useCallback(\r\n (portalCountry: string | null | undefined) => {\r\n if (!orgCountry) return true\r\n if (!portalCountry) return true\r\n return portalCountry.toUpperCase() === orgCountry\r\n },\r\n [orgCountry],\r\n )\r\n\r\n const filteredMunicipalPortals = React.useMemo(\r\n () => (Array.isArray(municipalPortals) ? municipalPortals.filter(p => matchesOrgCountry(p.country)) : []),\r\n [municipalPortals, matchesOrgCountry],\r\n )\r\n const filteredSubdivisionPortals = React.useMemo(\r\n () => (Array.isArray(subdivisionPortals)\r\n ? subdivisionPortals.filter(p => p.municipality == null && matchesOrgCountry(p.country))\r\n : []),\r\n [subdivisionPortals, matchesOrgCountry],\r\n )\r\n const filteredNationalPortals = React.useMemo(\r\n () => (Array.isArray(nationalPortals) ? nationalPortals.filter(p => matchesOrgCountry(p.country)) : []),\r\n [nationalPortals, matchesOrgCountry],\r\n )\r\n\r\n const municipal = useDatasetsForPortals(filteredMunicipalPortals, { rowsPerPage })\r\n const subdivision = useDatasetsForPortals(filteredSubdivisionPortals, { rowsPerPage })\r\n const national = useDatasetsForPortals(filteredNationalPortals, { rowsPerPage })\r\n\r\n React.useEffect(() => {\r\n const loadOrganizationalDatasets = async () => {\r\n try {\r\n const martinBaseUrl = appConfigState.runtimeConfig.martinUrl?.replace(/\\/+$/, '')\r\n if (!martinBaseUrl) return\r\n\r\n const datasetsUrl = martinBaseUrl.includes('/tiles/index.json')\r\n ? martinBaseUrl\r\n : `${martinBaseUrl}/tiles/index.json`\r\n\r\n const localPortal = {\r\n id: -1,\r\n name: 'Organizational Datasets',\r\n apiUrl: datasetsUrl,\r\n dataManagementSystem: 'Other' as const,\r\n countrySubdivision: null,\r\n municipality: null,\r\n group: 'Organizational' as const,\r\n }\r\n\r\n const organizationalDatasets = await fetchLocalDatasets(localPortal as any)\r\n const visible = organizationalDatasets.filter(ds => datasetVisibleForOrg(ds, orgVisibility))\r\n if (visible.length > 0) {\r\n datasetDispatch({ type: 'SET_DATASETS', payload: { datasets: visible } })\r\n }\r\n }\r\n catch (error) {\r\n console.error('Failed to load organizational datasets:', error)\r\n }\r\n }\r\n loadOrganizationalDatasets()\r\n }, [datasetDispatch, orgVisibility])\r\n\r\n const nationalDatasets = React.useMemo(\r\n () => sanitizeDatasets(national.datasets.filter(ds => datasetVisibleForOrg(ds, orgVisibility))),\r\n [national.datasets, orgVisibility],\r\n )\r\n const subdivisionDatasets = React.useMemo(\r\n () => sanitizeDatasets(subdivision.datasets.filter(ds => datasetVisibleForOrg(ds, orgVisibility))),\r\n [subdivision.datasets, orgVisibility],\r\n )\r\n const municipalDatasets = React.useMemo(\r\n () => sanitizeDatasets(municipal.datasets.filter(ds => datasetVisibleForOrg(ds, orgVisibility))),\r\n [municipal.datasets, orgVisibility],\r\n )\r\n const orgDatasets = React.useMemo(\r\n () => sanitizeDatasets((datasetState.datasets.datasets || []).filter(ds => datasetVisibleForOrg(ds, orgVisibility))),\r\n [datasetState.datasets.datasets, orgVisibility],\r\n )\r\n\r\n const allDatasets = React.useMemo(() => {\r\n const combined = [...nationalDatasets, ...subdivisionDatasets, ...municipalDatasets, ...orgDatasets]\r\n return sanitizeDatasets(combined)\r\n }, [nationalDatasets, subdivisionDatasets, municipalDatasets, orgDatasets])\r\n\r\n const liveDatasets = React.useMemo(\r\n () => allDatasets.filter(ds => ds.portal?.live === true),\r\n [allDatasets],\r\n )\r\n\r\n const toggleFavourite = React.useCallback((dataset: Dataset) => {\r\n setFavourites(prev => (\r\n prev.some(f => f.name === dataset.name)\r\n ? prev.filter(f => f.name !== dataset.name)\r\n : [...prev, dataset]\r\n ))\r\n }, [])\r\n\r\n const showMunicipal = Boolean(municipality)\r\n\r\n return (\r\n <div className=\"flex-1 flex flex-col space-y-4 py-4 overflow-hidden\">\r\n <div className=\"px-4\">\r\n <SearchInput\r\n placeholder=\"Search...\"\r\n value={searchQuery}\r\n onChange={e => setSearchQuery(e.target.value)}\r\n />\r\n </div>\r\n <div className=\"flex-1 min-h-0 overflow-y-auto space-y-2 divide-y\">\r\n <div className=\"pb-2\">\r\n <AppliedDatasetsSection />\r\n </div>\r\n <div className=\"pt-2\">\r\n <AvailableDatasetsSection\r\n query={searchQuery}\r\n allDatasets={allDatasets}\r\n nationalDatasets={nationalDatasets}\r\n subdivisionDatasets={subdivisionDatasets}\r\n municipalDatasets={municipalDatasets}\r\n organizationalDatasets={orgDatasets}\r\n liveDatasets={liveDatasets}\r\n showMunicipal={showMunicipal}\r\n favourites={favourites}\r\n onFavouriteToggle={toggleFavourite}\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n )\r\n}\r\n"],"mappings":"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","names":["_a"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PointCloudViewer.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/viewers/pointcloud/PointCloudViewer.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"PointCloudViewer.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/viewers/pointcloud/PointCloudViewer.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAa/B,wBAAgB,gBAAgB,sBAwO/B"}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
var _a;
|
|
4
3
|
import * as React from "react";
|
|
5
4
|
import { useSearchParams } from "next/navigation";
|
|
6
5
|
import { loadAllAssets } from "./utils/potreeLoader";
|
|
7
6
|
import { BuildingsContext, PointCloudContext } from "../../../store";
|
|
7
|
+
import { useAppConfigContext } from "../../../store/AppConfig/context";
|
|
8
8
|
import { restoreCameraFromUrl } from "./utils/restoreCameraFromUrl";
|
|
9
9
|
import { PointCloudLoadingState } from "./src/PointCloudLoadingState";
|
|
10
10
|
import { useFilesByBuildingId } from "../../../hooks/files/files";
|
|
11
11
|
import { ViewportGizmo } from "./src/ViewportGizmo";
|
|
12
12
|
import { usePointCloudCoordinateSystem } from "../useCoordinateSystem";
|
|
13
|
-
const API_BASE = (_a = process.env.NEXT_PUBLIC_POINTCLOUD_API_URL) != null ? _a : "http://localhost:5101";
|
|
14
13
|
function PointCloudViewer() {
|
|
14
|
+
var _a;
|
|
15
15
|
usePointCloudCoordinateSystem();
|
|
16
|
+
const { state: appConfigState } = useAppConfigContext();
|
|
17
|
+
const API_BASE = (_a = appConfigState.runtimeConfig.pointcloudApiUrl) != null ? _a : "http://localhost:5101";
|
|
16
18
|
const containerRef = React.useRef(null);
|
|
17
19
|
const viewerRef = React.useRef(null);
|
|
18
20
|
const gizmoRef = React.useRef(null);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/core/components/viewers/pointcloud/PointCloudViewer.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 THREE from \"three\";\r\nimport { useSearchParams } from \"next/navigation\";\r\nimport { loadAllAssets } from \"./utils/potreeLoader\";\r\nimport { BuildingsContext, PointCloudContext } from '../../../store'\r\nimport { restoreCameraFromUrl } from \"./utils/restoreCameraFromUrl\";\r\nimport { PointCloudLoadingState } from \"./src/PointCloudLoadingState\";\r\nimport { useFilesByBuildingId } from '../../../hooks/files/files'\r\nimport { ViewportGizmo } from './src/ViewportGizmo';\r\nimport { usePointCloudCoordinateSystem } from '../useCoordinateSystem';\r\n\r\n\r\nconst API_BASE =\r\n process.env.NEXT_PUBLIC_POINTCLOUD_API_URL ?? \"http://localhost:5101\";\r\n\r\nexport function PointCloudViewer() {\r\n // Enforce Z-up coordinate system for Potree. Resets to Y-up on unmount\r\n // so the BIM viewer doesn't inherit Z-up when switching viewers.\r\n usePointCloudCoordinateSystem();\r\n\r\n const containerRef = React.useRef<HTMLDivElement>(null);\r\n const viewerRef = React.useRef<any | null>(null);\r\n const gizmoRef = React.useRef<ViewportGizmo | null>(null);\r\n // Tracks Potree point cloud objects keyed by point cloud ID\r\n const loadedCloudsRef = React.useRef<Map<string, any>>(new Map());\r\n // IDs currently being fetched/loaded — prevents duplicate loads while async is in-flight\r\n const pendingIdsRef = React.useRef<Set<string>>(new Set());\r\n const cameraRestoredRef = React.useRef(false);\r\n\r\n const { state: pointCloudState, dispatch: pointCloudDispatch } = React.useContext(PointCloudContext)\r\n const PointCloudGlobalState = pointCloudState.pointcloud\r\n\r\n const { state: buildingState } = React.useContext(BuildingsContext)\r\n const { building } = buildingState.buildings\r\n\r\n const searchParams = useSearchParams()\r\n\r\n // Get files for the building to check if there are LAZ/LAS files\r\n const { files, isError: filesError } = useFilesByBuildingId(building?.id)\r\n const hasPointCloudFiles = React.useMemo(() =>\r\n !filesError && files.some(file => {\r\n const ext = file.extension?.toLowerCase()\r\n if (!ext) return false\r\n return ext === \"laz\" || ext === \"las\"\r\n }),\r\n [files, filesError])\r\n\r\n // --- Load CSS/JS assets once\r\n React.useEffect(() => {\r\n (async () => {\r\n try {\r\n await loadAllAssets()\r\n pointCloudDispatch({ type: \"SET_READY\", payload: { ready: true } })\r\n }\r\n catch (err) {\r\n console.error(\"PointCloudViewer - Failed to load assets\", err)\r\n }\r\n })();\r\n }, []);\r\n\r\n // --- Init viewer once when ready. Separate from cloud loading so the viewer\r\n // persists across toggle on/off of individual point clouds.\r\n React.useEffect(() => {\r\n if (!PointCloudGlobalState.ready || !containerRef.current || !(window as any)?.Potree) return;\r\n\r\n const Potree = (window as any).Potree;\r\n const el = containerRef.current;\r\n\r\n // Clear tracking state since we're creating a fresh viewer\r\n loadedCloudsRef.current.clear();\r\n pendingIdsRef.current.clear();\r\n cameraRestoredRef.current = false;\r\n\r\n const viewer = new Potree.Viewer(el);\r\n viewerRef.current = viewer;\r\n\r\n viewer.setEDLEnabled(true);\r\n viewer.setFOV(60);\r\n viewer.setPointBudget(10_000_000);\r\n viewer.loadSettingsFromURL();\r\n viewer.setBackground(\"white\");\r\n\r\n const gizmo = new ViewportGizmo(viewer);\r\n gizmoRef.current = gizmo;\r\n gizmo.enabled = true;\r\n\r\n pointCloudDispatch({\r\n type: \"INIT\",\r\n payload: {\r\n initialState: {\r\n ready: true,\r\n viewer: viewerRef.current,\r\n }\r\n }\r\n })\r\n\r\n return () => {\r\n try {\r\n if (gizmoRef.current) {\r\n gizmoRef.current.dispose();\r\n gizmoRef.current = null;\r\n }\r\n const v = viewerRef.current;\r\n // Potree's render loop re-schedules itself via requestAnimationFrame with no stop API;\r\n // shadow it with a no-op so it halts on unmount instead of running forever against a\r\n // disposed renderer (otherwise a new loop accumulates on every visit).\r\n if (v) {\r\n v.loop = () => { };\r\n v.update = () => { };\r\n }\r\n if (v?.scene?.pointclouds) {\r\n v.scene.pointclouds.forEach((pc: any) => {\r\n v.scene.removePointCloud?.(pc) ?? v.scene.scene?.remove?.(pc);\r\n });\r\n }\r\n v?.renderer?.dispose?.();\r\n } catch { }\r\n loadedCloudsRef.current.clear();\r\n pendingIdsRef.current.clear();\r\n viewerRef.current = null;\r\n el.innerHTML = \"\";\r\n };\r\n }, [PointCloudGlobalState.ready]);\r\n\r\n // --- Add/remove point clouds whenever loadedPointCloudIds changes or the\r\n // viewer is (re)initialized (PointCloudGlobalState.viewer updates on INIT).\r\n React.useEffect(() => {\r\n const viewer = viewerRef.current;\r\n if (!PointCloudGlobalState.ready || !viewer) return;\r\n\r\n const Potree = (window as any).Potree;\r\n if (!Potree) return;\r\n\r\n const currentIds = new Set(PointCloudGlobalState.loadedPointCloudIds);\r\n const alreadyLoadedIds = new Set(loadedCloudsRef.current.keys());\r\n\r\n // Remove point clouds no longer in the list.\r\n // Potree classic has no removePointCloud() — splice from the internal\r\n // pointclouds array and remove from the Three.js scene separately.\r\n for (const id of alreadyLoadedIds) {\r\n if (!currentIds.has(id)) {\r\n const pc = loadedCloudsRef.current.get(id);\r\n if (pc) {\r\n try {\r\n const pcs: any[] = viewer.scene.pointclouds ?? [];\r\n const idx = pcs.indexOf(pc);\r\n if (idx !== -1) pcs.splice(idx, 1);\r\n viewer.scene.scene?.remove(pc);\r\n } catch { }\r\n loadedCloudsRef.current.delete(id);\r\n pendingIdsRef.current.delete(id);\r\n try { viewer.fitToScreen(); } catch { }\r\n }\r\n }\r\n }\r\n\r\n // Add newly requested point clouds.\r\n // Skip if already tracked in loadedCloudsRef OR still in-flight in pendingIdsRef\r\n // — prevents duplicate loads when Effect 2 fires again before the callback runs.\r\n for (const id of currentIds) {\r\n if (alreadyLoadedIds.has(id) || pendingIdsRef.current.has(id)) continue;\r\n\r\n pendingIdsRef.current.add(id);\r\n ; (async () => {\r\n try {\r\n const res = await fetch(`${API_BASE}/point-cloud/${encodeURIComponent(id)}`);\r\n const data = await res.json();\r\n const url = `${API_BASE}/private/${data.bucket}/${data.potreeMetadataFileKey}`;\r\n\r\n Potree.loadPointCloud(url, id, (e: any) => {\r\n pendingIdsRef.current.delete(id);\r\n // Bail if the viewer was torn down (or re-init'd) while this load was in flight —\r\n // the init-effect cleanup nulls viewerRef, so this guard avoids attaching to a dead viewer.\r\n if (viewerRef.current !== viewer) return;\r\n try {\r\n viewer.scene.addPointCloud(e.pointcloud);\r\n loadedCloudsRef.current.set(id, e.pointcloud);\r\n\r\n const mat = e.pointcloud.material;\r\n mat.size = 1;\r\n mat.pointSizeType = Potree.PointSizeType.ADAPTIVE;\r\n\r\n const restored = !cameraRestoredRef.current && restoreCameraFromUrl(viewer, searchParams)\r\n cameraRestoredRef.current = true;\r\n if (!restored) viewer.fitToScreen();\r\n\r\n if (gizmoRef.current) {\r\n gizmoRef.current.add();\r\n }\r\n } catch (err) {\r\n console.error(\"[PointCloudViewer] Error adding point cloud\", err);\r\n }\r\n });\r\n } catch (err) {\r\n pendingIdsRef.current.delete(id);\r\n console.error(\"[PointCloudViewer] Error fetching point cloud metadata\", err);\r\n }\r\n })();\r\n }\r\n }, [PointCloudGlobalState.loadedPointCloudIds, PointCloudGlobalState.viewer, PointCloudGlobalState.ready]);\r\n\r\n const { loadedPointCloudIds } = PointCloudGlobalState;\r\n const hasLoadedClouds = loadedPointCloudIds.length > 0;\r\n\r\n return (\r\n <div\r\n style={{\r\n position: \"absolute\",\r\n left: 0,\r\n top: 0,\r\n width: \"100%\",\r\n height: \"100%\",\r\n }}\r\n >\r\n {/* Potree container — always rendered so the viewer can measure its dimensions on init.\r\n PointCloudLoadingState sits above it via z-index when no clouds are loaded. */}\r\n <div>\r\n <div\r\n className={`pointer-events-none absolute inset-0 `}\r\n style={{\r\n zIndex: 9,\r\n background: 'radial-gradient(circle at center, rgba(220,220,220,0) 0%, rgba(220,220,220,0) 50%, rgba(220,220,220,0.9) 100%)',\r\n mixBlendMode: 'multiply'\r\n }}\r\n />\r\n <div\r\n ref={containerRef}\r\n className=\"potree_container\"\r\n style={{\r\n position: \"absolute\",\r\n inset: 0,\r\n backgroundSize: \"cover\",\r\n }}\r\n />\r\n </div>\r\n\r\n {/* Show PointCloudLoadingState only when there are no point cloud files uploaded at all.\r\n Rendered after the container so it layers on top (higher DOM order = higher paint order). */}\r\n {!hasPointCloudFiles && !hasLoadedClouds && (\r\n <div className=\"absolute inset-0\" style={{ zIndex: 10 }}>\r\n <PointCloudLoadingState />\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":["_a","_b","_c"]}
|
|
1
|
+
{"version":3,"sources":["../../../../../src/core/components/viewers/pointcloud/PointCloudViewer.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 THREE from \"three\";\r\nimport { useSearchParams } from \"next/navigation\";\r\nimport { loadAllAssets } from \"./utils/potreeLoader\";\r\nimport { BuildingsContext, PointCloudContext } from '../../../store'\r\nimport { useAppConfigContext } from '../../../store/AppConfig/context'\r\nimport { restoreCameraFromUrl } from \"./utils/restoreCameraFromUrl\";\r\nimport { PointCloudLoadingState } from \"./src/PointCloudLoadingState\";\r\nimport { useFilesByBuildingId } from '../../../hooks/files/files'\r\nimport { ViewportGizmo } from './src/ViewportGizmo';\r\nimport { usePointCloudCoordinateSystem } from '../useCoordinateSystem';\r\n\r\n\r\nexport function PointCloudViewer() {\r\n // Enforce Z-up coordinate system for Potree. Resets to Y-up on unmount\r\n // so the BIM viewer doesn't inherit Z-up when switching viewers.\r\n usePointCloudCoordinateSystem();\r\n const { state: appConfigState } = useAppConfigContext()\r\n const API_BASE = appConfigState.runtimeConfig.pointcloudApiUrl ?? 'http://localhost:5101'\r\n\r\n const containerRef = React.useRef<HTMLDivElement>(null);\r\n const viewerRef = React.useRef<any | null>(null);\r\n const gizmoRef = React.useRef<ViewportGizmo | null>(null);\r\n // Tracks Potree point cloud objects keyed by point cloud ID\r\n const loadedCloudsRef = React.useRef<Map<string, any>>(new Map());\r\n // IDs currently being fetched/loaded — prevents duplicate loads while async is in-flight\r\n const pendingIdsRef = React.useRef<Set<string>>(new Set());\r\n const cameraRestoredRef = React.useRef(false);\r\n\r\n const { state: pointCloudState, dispatch: pointCloudDispatch } = React.useContext(PointCloudContext)\r\n const PointCloudGlobalState = pointCloudState.pointcloud\r\n\r\n const { state: buildingState } = React.useContext(BuildingsContext)\r\n const { building } = buildingState.buildings\r\n\r\n const searchParams = useSearchParams()\r\n\r\n // Get files for the building to check if there are LAZ/LAS files\r\n const { files, isError: filesError } = useFilesByBuildingId(building?.id)\r\n const hasPointCloudFiles = React.useMemo(() =>\r\n !filesError && files.some(file => {\r\n const ext = file.extension?.toLowerCase()\r\n if (!ext) return false\r\n return ext === \"laz\" || ext === \"las\"\r\n }),\r\n [files, filesError])\r\n\r\n // --- Load CSS/JS assets once\r\n React.useEffect(() => {\r\n (async () => {\r\n try {\r\n await loadAllAssets()\r\n pointCloudDispatch({ type: \"SET_READY\", payload: { ready: true } })\r\n }\r\n catch (err) {\r\n console.error(\"PointCloudViewer - Failed to load assets\", err)\r\n }\r\n })();\r\n }, []);\r\n\r\n // --- Init viewer once when ready. Separate from cloud loading so the viewer\r\n // persists across toggle on/off of individual point clouds.\r\n React.useEffect(() => {\r\n if (!PointCloudGlobalState.ready || !containerRef.current || !(window as any)?.Potree) return;\r\n\r\n const Potree = (window as any).Potree;\r\n const el = containerRef.current;\r\n\r\n // Clear tracking state since we're creating a fresh viewer\r\n loadedCloudsRef.current.clear();\r\n pendingIdsRef.current.clear();\r\n cameraRestoredRef.current = false;\r\n\r\n const viewer = new Potree.Viewer(el);\r\n viewerRef.current = viewer;\r\n\r\n viewer.setEDLEnabled(true);\r\n viewer.setFOV(60);\r\n viewer.setPointBudget(10_000_000);\r\n viewer.loadSettingsFromURL();\r\n viewer.setBackground(\"white\");\r\n\r\n const gizmo = new ViewportGizmo(viewer);\r\n gizmoRef.current = gizmo;\r\n gizmo.enabled = true;\r\n\r\n pointCloudDispatch({\r\n type: \"INIT\",\r\n payload: {\r\n initialState: {\r\n ready: true,\r\n viewer: viewerRef.current,\r\n }\r\n }\r\n })\r\n\r\n return () => {\r\n try {\r\n if (gizmoRef.current) {\r\n gizmoRef.current.dispose();\r\n gizmoRef.current = null;\r\n }\r\n const v = viewerRef.current;\r\n // Potree's render loop re-schedules itself via requestAnimationFrame with no stop API;\r\n // shadow it with a no-op so it halts on unmount instead of running forever against a\r\n // disposed renderer (otherwise a new loop accumulates on every visit).\r\n if (v) {\r\n v.loop = () => { };\r\n v.update = () => { };\r\n }\r\n if (v?.scene?.pointclouds) {\r\n v.scene.pointclouds.forEach((pc: any) => {\r\n v.scene.removePointCloud?.(pc) ?? v.scene.scene?.remove?.(pc);\r\n });\r\n }\r\n v?.renderer?.dispose?.();\r\n } catch { }\r\n loadedCloudsRef.current.clear();\r\n pendingIdsRef.current.clear();\r\n viewerRef.current = null;\r\n el.innerHTML = \"\";\r\n };\r\n }, [PointCloudGlobalState.ready]);\r\n\r\n // --- Add/remove point clouds whenever loadedPointCloudIds changes or the\r\n // viewer is (re)initialized (PointCloudGlobalState.viewer updates on INIT).\r\n React.useEffect(() => {\r\n const viewer = viewerRef.current;\r\n if (!PointCloudGlobalState.ready || !viewer) return;\r\n\r\n const Potree = (window as any).Potree;\r\n if (!Potree) return;\r\n\r\n const currentIds = new Set(PointCloudGlobalState.loadedPointCloudIds);\r\n const alreadyLoadedIds = new Set(loadedCloudsRef.current.keys());\r\n\r\n // Remove point clouds no longer in the list.\r\n // Potree classic has no removePointCloud() — splice from the internal\r\n // pointclouds array and remove from the Three.js scene separately.\r\n for (const id of alreadyLoadedIds) {\r\n if (!currentIds.has(id)) {\r\n const pc = loadedCloudsRef.current.get(id);\r\n if (pc) {\r\n try {\r\n const pcs: any[] = viewer.scene.pointclouds ?? [];\r\n const idx = pcs.indexOf(pc);\r\n if (idx !== -1) pcs.splice(idx, 1);\r\n viewer.scene.scene?.remove(pc);\r\n } catch { }\r\n loadedCloudsRef.current.delete(id);\r\n pendingIdsRef.current.delete(id);\r\n try { viewer.fitToScreen(); } catch { }\r\n }\r\n }\r\n }\r\n\r\n // Add newly requested point clouds.\r\n // Skip if already tracked in loadedCloudsRef OR still in-flight in pendingIdsRef\r\n // — prevents duplicate loads when Effect 2 fires again before the callback runs.\r\n for (const id of currentIds) {\r\n if (alreadyLoadedIds.has(id) || pendingIdsRef.current.has(id)) continue;\r\n\r\n pendingIdsRef.current.add(id);\r\n ; (async () => {\r\n try {\r\n const res = await fetch(`${API_BASE}/point-cloud/${encodeURIComponent(id)}`);\r\n const data = await res.json();\r\n const url = `${API_BASE}/private/${data.bucket}/${data.potreeMetadataFileKey}`;\r\n\r\n Potree.loadPointCloud(url, id, (e: any) => {\r\n pendingIdsRef.current.delete(id);\r\n // Bail if the viewer was torn down (or re-init'd) while this load was in flight —\r\n // the init-effect cleanup nulls viewerRef, so this guard avoids attaching to a dead viewer.\r\n if (viewerRef.current !== viewer) return;\r\n try {\r\n viewer.scene.addPointCloud(e.pointcloud);\r\n loadedCloudsRef.current.set(id, e.pointcloud);\r\n\r\n const mat = e.pointcloud.material;\r\n mat.size = 1;\r\n mat.pointSizeType = Potree.PointSizeType.ADAPTIVE;\r\n\r\n const restored = !cameraRestoredRef.current && restoreCameraFromUrl(viewer, searchParams)\r\n cameraRestoredRef.current = true;\r\n if (!restored) viewer.fitToScreen();\r\n\r\n if (gizmoRef.current) {\r\n gizmoRef.current.add();\r\n }\r\n } catch (err) {\r\n console.error(\"[PointCloudViewer] Error adding point cloud\", err);\r\n }\r\n });\r\n } catch (err) {\r\n pendingIdsRef.current.delete(id);\r\n console.error(\"[PointCloudViewer] Error fetching point cloud metadata\", err);\r\n }\r\n })();\r\n }\r\n }, [PointCloudGlobalState.loadedPointCloudIds, PointCloudGlobalState.viewer, PointCloudGlobalState.ready]);\r\n\r\n const { loadedPointCloudIds } = PointCloudGlobalState;\r\n const hasLoadedClouds = loadedPointCloudIds.length > 0;\r\n\r\n return (\r\n <div\r\n style={{\r\n position: \"absolute\",\r\n left: 0,\r\n top: 0,\r\n width: \"100%\",\r\n height: \"100%\",\r\n }}\r\n >\r\n {/* Potree container — always rendered so the viewer can measure its dimensions on init.\r\n PointCloudLoadingState sits above it via z-index when no clouds are loaded. */}\r\n <div>\r\n <div\r\n className={`pointer-events-none absolute inset-0 `}\r\n style={{\r\n zIndex: 9,\r\n background: 'radial-gradient(circle at center, rgba(220,220,220,0) 0%, rgba(220,220,220,0) 50%, rgba(220,220,220,0.9) 100%)',\r\n mixBlendMode: 'multiply'\r\n }}\r\n />\r\n <div\r\n ref={containerRef}\r\n className=\"potree_container\"\r\n style={{\r\n position: \"absolute\",\r\n inset: 0,\r\n backgroundSize: \"cover\",\r\n }}\r\n />\r\n </div>\r\n\r\n {/* Show PointCloudLoadingState only when there are no point cloud files uploaded at all.\r\n Rendered after the container so it layers on top (higher DOM order = higher paint order). */}\r\n {!hasPointCloudFiles && !hasLoadedClouds && (\r\n <div className=\"absolute inset-0\" style={{ zIndex: 10 }}>\r\n <PointCloudLoadingState />\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":["_a","_b","_c"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/components/viewers/pointcloud/src/PointCloudLoadingState/index.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/components/viewers/pointcloud/src/PointCloudLoadingState/index.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,OAAO,CAAA;AA0BzB,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,mBAAmB,GAAG,YAAY,CAAA;AAElH,wBAAgB,sBAAsB,sBA4WrC"}
|
|
@@ -19,7 +19,6 @@ var __spreadValues = (a, b) => {
|
|
|
19
19
|
};
|
|
20
20
|
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
21
21
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
22
|
-
var _a;
|
|
23
22
|
import React from "react";
|
|
24
23
|
import { Button } from "../../../../ui/Button";
|
|
25
24
|
import { Card, CardContent, CardHeader } from "../../../../ui/Card";
|
|
@@ -27,14 +26,17 @@ import { Input } from "../../../../ui/Input";
|
|
|
27
26
|
import { LoadingSpinner } from "../../../../ui/LoadingSpinner";
|
|
28
27
|
import * as LR from "lucide-react";
|
|
29
28
|
import { BuildingsContext, usePermissions } from "../../../../../store";
|
|
29
|
+
import { useAppConfigContext } from "../../../../../store/AppConfig/context";
|
|
30
30
|
import { useTranslations } from "next-intl";
|
|
31
31
|
import { useFilesByBuildingId } from "../../../../../hooks/files/files";
|
|
32
32
|
import { cn } from "../../../../../utils/utils";
|
|
33
33
|
import { useSearchParams, useRouter } from "next/navigation";
|
|
34
34
|
import { useBuilding, useBuildings } from "../../../../../hooks/buildings/buildings";
|
|
35
|
-
const API_BASE = (_a = process.env.NEXT_PUBLIC_POINTCLOUD_API_URL) != null ? _a : "http://localhost:5101";
|
|
36
35
|
function PointCloudLoadingState() {
|
|
36
|
+
var _a;
|
|
37
37
|
const t = useTranslations("PointCloudLoadingState");
|
|
38
|
+
const { state: appConfigState } = useAppConfigContext();
|
|
39
|
+
const API_BASE = (_a = appConfigState.runtimeConfig.pointcloudApiUrl) != null ? _a : "http://localhost:5101";
|
|
38
40
|
const { ability } = usePermissions();
|
|
39
41
|
const { state: buildingState, dispatch: buildingDispatch } = React.useContext(BuildingsContext);
|
|
40
42
|
const { building: storeBuilding } = buildingState.buildings;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../src/core/components/viewers/pointcloud/src/PointCloudLoadingState/index.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 React from 'react'\r\nimport { Button } from '../../../../ui/Button'\r\nimport { Card, CardContent, CardHeader } from '../../../../ui/Card'\r\nimport { Input } from '../../../../ui/Input'\r\nimport { LoadingSpinner } from '../../../../ui/LoadingSpinner'\r\nimport * as LR from 'lucide-react'\r\nimport { BuildingsContext, usePermissions } from '../../../../../store'\r\nimport { useTranslations } from 'next-intl'\r\nimport { useFilesByBuildingId } from '../../../../../hooks/files/files'\r\nimport type { DbFile } from '../../../../../types/dbTypes'\r\nimport { cn } from '../../../../../utils/utils'\r\nimport { useSearchParams, useRouter } from 'next/navigation'\r\nimport { useBuilding, useBuildings } from '../../../../../hooks/buildings/buildings'\r\nimport { SquareArrowOutUpRight } from 'lucide-react'\r\n\r\nconst API_BASE = process.env.NEXT_PUBLIC_POINTCLOUD_API_URL ?? 'http://localhost:5101'\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\nexport type pointCloudViewerState = 'opening' | 'loading' | 'ready' | 'error' | 'noPointCloudFiles' | 'noBuilding'\r\n\r\nexport function PointCloudLoadingState() {\r\n const t = useTranslations('PointCloudLoadingState')\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n const { state: buildingState, dispatch: buildingDispatch } = React.useContext(BuildingsContext)\r\n const { building: storeBuilding } = buildingState.buildings\r\n\r\n const searchParams = useSearchParams()\r\n const buildingId = searchParams.get(\"buildingId\")\r\n const { building: urlBuilding, isLoading: buildingLoading, isError: buildingError } = useBuilding(buildingId ? Number(buildingId) : 0)\r\n const { buildings, isLoading: buildingsLoading } = useBuildings()\r\n\r\n // Determine the current building to use\r\n const building = storeBuilding || urlBuilding\r\n\r\n React.useEffect(() => {\r\n if (!storeBuilding && urlBuilding) {\r\n buildingDispatch({ type: \"SET-CURRENT-BUILDING\", payload: { building: urlBuilding } })\r\n }\r\n }, [urlBuilding, storeBuilding, buildingDispatch])\r\n\r\n const router = useRouter()\r\n const fileInputRef = React.useRef<HTMLInputElement>(null)\r\n\r\n const [currentState, setCurrentState] = React.useState<pointCloudViewerState>('opening')\r\n const [uploading, setUploading] = React.useState(false)\r\n const [searchTerm, setSearchTerm] = React.useState('')\r\n const [searchResults, setSearchResults] = React.useState<any[]>([])\r\n const [selectedBuilding, setSelectedBuilding] = React.useState<any>(null)\r\n \r\n const { files, isLoading: filesLoading } = useFilesByBuildingId(building?.id)\r\n const pointCloudFiles: DbFile[] = React.useMemo(() => (\r\n files.filter(file => {\r\n const ext = file.extension?.toLowerCase()\r\n if (!ext) return false\r\n return ext === \"laz\" || ext === \"las\"\r\n })\r\n ), [files])\r\n\r\n // Fuzzy search function for buildings\r\n const fuzzySearchBuildings = React.useCallback((buildings: any[], searchTerm: string) => {\r\n if (!searchTerm.trim()) return buildings.slice(0, 10)\r\n \r\n const fuzzySearchScore = (search: string, text: string): number => {\r\n if (!search || !text) return 0\r\n const searchLower = search.toLowerCase()\r\n const textLower = text.toLowerCase()\r\n \r\n if (textLower === searchLower) return 1000\r\n if (textLower.startsWith(searchLower)) return 800\r\n if (textLower.includes(searchLower)) return 600\r\n \r\n return 0\r\n }\r\n\r\n return buildings\r\n .map((building) => {\r\n const address = building.buildingAddress || ''\r\n const name = building.buildingName || ''\r\n const id = building.id.toString()\r\n \r\n const addressScore = fuzzySearchScore(searchTerm, address)\r\n const nameScore = fuzzySearchScore(searchTerm, name)\r\n const idScore = fuzzySearchScore(searchTerm, id)\r\n \r\n const maxScore = Math.max(addressScore, nameScore, idScore)\r\n return { ...building, searchScore: maxScore }\r\n })\r\n .filter(building => building.searchScore > 0)\r\n .sort((a, b) => b.searchScore - a.searchScore)\r\n .slice(0, 10)\r\n }, [])\r\n\r\n // Handle search\r\n const handleSearch = React.useCallback((term: string) => {\r\n setSearchTerm(term)\r\n if (buildings) {\r\n setSearchResults(fuzzySearchBuildings(buildings, term))\r\n }\r\n }, [buildings, fuzzySearchBuildings])\r\n\r\n const shouldShow = (currentState === 'opening' || currentState === 'noPointCloudFiles' || currentState === 'noBuilding')\r\n\r\n // Handle initial state transitions\r\n React.useEffect(() => {\r\n // If building is loading, stay in opening state\r\n if (buildingLoading) {\r\n setCurrentState('opening')\r\n return\r\n }\r\n\r\n // If building failed to load or no buildingId provided, show noBuilding state\r\n if (buildingError || (!buildingId && !building)) {\r\n setCurrentState('noBuilding')\r\n return\r\n }\r\n\r\n // If we have a building but files are still loading, stay in opening state\r\n if (building && filesLoading) {\r\n setCurrentState('opening')\r\n return\r\n }\r\n\r\n // If we have a building but no point cloud files, show noPointCloudFiles state\r\n if (building && pointCloudFiles.length === 0) {\r\n setCurrentState('noPointCloudFiles')\r\n return\r\n }\r\n\r\n // We have point cloud files, hide the loading state\r\n if (building && pointCloudFiles.length > 0) {\r\n setCurrentState('ready')\r\n }\r\n }, [building, buildingLoading, buildingError, buildingId, filesLoading, pointCloudFiles.length])\r\n\r\n // Don't render if not visible\r\n if (!shouldShow) {\r\n return null\r\n }\r\n\r\n const handleBuildingSelect = (building: any) => {\r\n const url = new URL(window.location.href)\r\n url.searchParams.set('buildingId', building.id.toString())\r\n buildingDispatch({ type: 'SET-CURRENT-BUILDING', payload: { building } })\r\n setSelectedBuilding(building)\r\n setSearchTerm('')\r\n setSearchResults([])\r\n router.push(url.toString())\r\n }\r\n\r\n // Create point cloud entry\r\n async function createPointCloud(name: string): Promise<CreatePointCloudResponse> {\r\n const res = await fetch(`${API_BASE}/point-cloud`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ name }),\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 const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {\r\n const files = event.target.files\r\n if (!files || files.length === 0) return\r\n\r\n const file = files[0]\r\n const fileName = file.name\r\n const fileExt = fileName.toLowerCase()\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 setUploading(true)\r\n\r\n try {\r\n console.log('Creating point cloud entry...')\r\n const { pointCloud, upload } = await createPointCloud(fileName)\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 // Trigger a refresh of the files - the state will update automatically\r\n window.location.reload()\r\n }\r\n catch (error) {\r\n console.error('Error uploading point cloud:', error)\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 setUploading(false)\r\n }\r\n }\r\n\r\n const handleUploadClick = () => {\r\n fileInputRef.current?.click()\r\n }\r\n\r\n return (\r\n <>\r\n <div className=\"absolute z-10 inset-0 bg-background/50 backdrop-blur-[1px] flex items-center justify-center pointer-events-none\">\r\n <Card className={cn(\"w-[536px] pointer-events-auto flex flex-col justify-start items-center gap-2\")}>\r\n <CardHeader className=\"flex justify-center items-center\">\r\n <div className=\"w-12 h-12 p-2 bg-card rounded-md shadow-sm border border-border flex justify-center items-center\">\r\n {currentState === 'noBuilding' ? (\r\n <LR.Building2 className={cn(\"w-4 h-5 text-foreground\", buildingsLoading && \"animate-pulse\")} strokeWidth={2} />\r\n ) : (\r\n <LR.Grip className={cn(\"w-4 h-5 text-foreground\", currentState === 'opening' && \"animate-pulse\")} strokeWidth={2} />\r\n )}\r\n </div>\r\n </CardHeader>\r\n\r\n <CardContent className=\"self-stretch flex flex-col justify-start items-center gap-6 pt-0\">\r\n <div className=\"self-stretch flex flex-col justify-start items-center gap-2\">\r\n <div className=\"self-stretch text-center text-foreground text-xl font-semibold font-['Inter'] leading-7 flex items-center justify-center gap-2\">\r\n {currentState === 'opening' ? (\r\n <>\r\n {t('openingText')}\r\n <LoadingSpinner size={20} />\r\n </>\r\n ) : currentState === 'noBuilding' ? (\r\n <>\r\n {t('noBuilding')}\r\n </>\r\n ) : uploading ? (\r\n <>\r\n {t('uploading')}\r\n <LoadingSpinner size={20} />\r\n </>\r\n ) : (\r\n t('noPointCloudLoaded')\r\n )}\r\n </div>\r\n {(currentState === 'noPointCloudFiles' || currentState === 'noBuilding') && !uploading && (\r\n <div className=\"self-stretch text-center text-muted-foreground text-sm font-normal font-['Inter'] leading-tight\">\r\n {currentState === 'noBuilding' ? t('selectBuildingHelpText') : t('helpText')}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {currentState === 'noBuilding' && (\r\n <div className=\"self-stretch flex flex-col gap-3 justify-centre items-center\">\r\n {buildingsLoading ? (\r\n <div className=\"flex items-center justify-center gap-2 text-muted-foreground\">\r\n <LoadingSpinner size={16} />\r\n <span>{t('loadingBuildings') || 'Loading buildings...'}</span>\r\n </div>\r\n ) : buildings && buildings.length > 0 ? (\r\n <div className=\"w-full relative\">\r\n {selectedBuilding ? (\r\n <div className=\"relative flex justify-end items-center\">\r\n <Input\r\n type=\"text\"\r\n value={\r\n 'Building: '\r\n + (selectedBuilding.buildingName && selectedBuilding.buildingAddress\r\n ? `${selectedBuilding.buildingName}, ${selectedBuilding.buildingAddress}`\r\n : selectedBuilding.buildingName || selectedBuilding.buildingAddress || `Building ${selectedBuilding.id}`)\r\n }\r\n readOnly\r\n className=\"w-full\"\r\n />\r\n <LR.X \r\n className=\"absolute right-2 cursor-pointer w-4 h-4 text-muted-foreground hover:text-foreground\" \r\n onClick={() => {\r\n setSelectedBuilding(null)\r\n setSearchTerm('')\r\n setSearchResults([])\r\n }} \r\n />\r\n </div>\r\n ) : (\r\n <div className=\"relative\">\r\n <LR.Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4\" />\r\n <Input\r\n placeholder={t('searchBuildings') || 'Search buildings...'}\r\n value={searchTerm}\r\n onChange={(e) => handleSearch(e.target.value)}\r\n className=\"w-full pl-10\"\r\n disabled={!ability.can('read', 'File')}\r\n />\r\n </div>\r\n )}\r\n \r\n {/* Search results dropdown */}\r\n {searchResults.length > 0 && searchTerm !== '' && !selectedBuilding && (\r\n <div className=\"absolute top-full left-0 right-0 mt-1 bg-background border border-border rounded-md shadow-lg max-h-60 overflow-y-auto z-20\">\r\n {searchResults.map(building => (\r\n <div\r\n key={building.id}\r\n className=\"px-3 py-2 text-sm cursor-pointer hover:bg-muted transition-colors flex gap-2 items-center\"\r\n onClick={() => handleBuildingSelect(building)}\r\n >\r\n <LR.SquareArrowOutUpRight className=\"w-3 h-3\" />\r\n {building.buildingName\r\n ? `${building.buildingName}, ${building.buildingAddress}`\r\n : building.buildingAddress || `Building ${building.id}`}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n ) : (\r\n <div className=\"text-muted-foreground text-center\">\r\n {t('noBuildings') || 'No buildings available'}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {currentState === 'noPointCloudFiles' && (\r\n <div className=\"self-stretch flex justify-center items-center gap-3\">\r\n <Input\r\n ref={fileInputRef}\r\n type=\"file\"\r\n accept=\".laz,.las\"\r\n onChange={handleFileChange}\r\n className=\"hidden\"\r\n />\r\n <Button\r\n onClick={handleUploadClick}\r\n size=\"sm\"\r\n className=\"gap-2\"\r\n disabled={uploading || !ability.can('create', 'File')}\r\n >\r\n <LR.Upload className=\"w-4 h-4\" />\r\n {uploading ? t('uploading') : t('uploadButton')}\r\n </Button>\r\n </div>\r\n )}\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </>\r\n )\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA0QgB,SAWE,UAXF,KAWE,YAXF;AA1QhB;AAKA,OAAO,WAAW;AAClB,SAAS,cAAc;AACvB,SAAS,MAAM,aAAa,kBAAkB;AAC9C,SAAS,aAAa;AACtB,SAAS,sBAAsB;AAC/B,YAAY,QAAQ;AACpB,SAAS,kBAAkB,sBAAsB;AACjD,SAAS,uBAAuB;AAChC,SAAS,4BAA4B;AAErC,SAAS,UAAU;AACnB,SAAS,iBAAiB,iBAAiB;AAC3C,SAAS,aAAa,oBAAoB;AAG1C,MAAM,YAAW,aAAQ,IAAI,mCAAZ,YAA8C;AAcxD,SAAS,yBAAyB;AACvC,QAAM,IAAI,gBAAgB,wBAAwB;AAElD,QAAM,EAAE,QAAQ,IAAI,eAAe;AAEnC,QAAM,EAAE,OAAO,eAAe,UAAU,iBAAiB,IAAI,MAAM,WAAW,gBAAgB;AAC9F,QAAM,EAAE,UAAU,cAAc,IAAI,cAAc;AAElD,QAAM,eAAe,gBAAgB;AACrC,QAAM,aAAa,aAAa,IAAI,YAAY;AAChD,QAAM,EAAE,UAAU,aAAa,WAAW,iBAAiB,SAAS,cAAc,IAAI,YAAY,aAAa,OAAO,UAAU,IAAI,CAAC;AACrI,QAAM,EAAE,WAAW,WAAW,iBAAiB,IAAI,aAAa;AAGhE,QAAM,WAAW,iBAAiB;AAElC,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,iBAAiB,aAAa;AACjC,uBAAiB,EAAE,MAAM,wBAAwB,SAAS,EAAE,UAAU,YAAY,EAAE,CAAC;AAAA,IACvF;AAAA,EACF,GAAG,CAAC,aAAa,eAAe,gBAAgB,CAAC;AAEjD,QAAM,SAAS,UAAU;AACzB,QAAM,eAAe,MAAM,OAAyB,IAAI;AAExD,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAgC,SAAS;AACvF,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,KAAK;AACtD,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,EAAE;AACrD,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAgB,CAAC,CAAC;AAClE,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,MAAM,SAAc,IAAI;AAExE,QAAM,EAAE,OAAO,WAAW,aAAa,IAAI,qBAAqB,qCAAU,EAAE;AAC5E,QAAM,kBAA4B,MAAM,QAAQ,MAC9C,MAAM,OAAO,UAAQ;AAnEzB,QAAAA;AAoEM,UAAM,OAAMA,MAAA,KAAK,cAAL,gBAAAA,IAAgB;AAC5B,QAAI,CAAC,IAAK,QAAO;AACjB,WAAO,QAAQ,SAAS,QAAQ;AAAA,EAClC,CAAC,GACA,CAAC,KAAK,CAAC;AAGV,QAAM,uBAAuB,MAAM,YAAY,CAACC,YAAkBC,gBAAuB;AACvF,QAAI,CAACA,YAAW,KAAK,EAAG,QAAOD,WAAU,MAAM,GAAG,EAAE;AAEpD,UAAM,mBAAmB,CAAC,QAAgB,SAAyB;AACjE,UAAI,CAAC,UAAU,CAAC,KAAM,QAAO;AAC7B,YAAM,cAAc,OAAO,YAAY;AACvC,YAAM,YAAY,KAAK,YAAY;AAEnC,UAAI,cAAc,YAAa,QAAO;AACtC,UAAI,UAAU,WAAW,WAAW,EAAG,QAAO;AAC9C,UAAI,UAAU,SAAS,WAAW,EAAG,QAAO;AAE5C,aAAO;AAAA,IACT;AAEA,WAAOA,WACJ,IAAI,CAACE,cAAa;AACjB,YAAM,UAAUA,UAAS,mBAAmB;AAC5C,YAAM,OAAOA,UAAS,gBAAgB;AACtC,YAAM,KAAKA,UAAS,GAAG,SAAS;AAEhC,YAAM,eAAe,iBAAiBD,aAAY,OAAO;AACzD,YAAM,YAAY,iBAAiBA,aAAY,IAAI;AACnD,YAAM,UAAU,iBAAiBA,aAAY,EAAE;AAE/C,YAAM,WAAW,KAAK,IAAI,cAAc,WAAW,OAAO;AAC1D,aAAO,iCAAKC,YAAL,EAAe,aAAa,SAAS;AAAA,IAC9C,CAAC,EACA,OAAO,CAAAA,cAAYA,UAAS,cAAc,CAAC,EAC3C,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,EAAE,WAAW,EAC5C,MAAM,GAAG,EAAE;AAAA,EAChB,GAAG,CAAC,CAAC;AAGL,QAAM,eAAe,MAAM,YAAY,CAAC,SAAiB;AACvD,kBAAc,IAAI;AAClB,QAAI,WAAW;AACb,uBAAiB,qBAAqB,WAAW,IAAI,CAAC;AAAA,IACxD;AAAA,EACF,GAAG,CAAC,WAAW,oBAAoB,CAAC;AAEpC,QAAM,aAAc,iBAAiB,aAAa,iBAAiB,uBAAuB,iBAAiB;AAG3G,QAAM,UAAU,MAAM;AAEpB,QAAI,iBAAiB;AACnB,sBAAgB,SAAS;AACzB;AAAA,IACF;AAGA,QAAI,iBAAkB,CAAC,cAAc,CAAC,UAAW;AAC/C,sBAAgB,YAAY;AAC5B;AAAA,IACF;AAGA,QAAI,YAAY,cAAc;AAC5B,sBAAgB,SAAS;AACzB;AAAA,IACF;AAGA,QAAI,YAAY,gBAAgB,WAAW,GAAG;AAC5C,sBAAgB,mBAAmB;AACnC;AAAA,IACF;AAGA,QAAI,YAAY,gBAAgB,SAAS,GAAG;AAC1C,sBAAgB,OAAO;AAAA,IACzB;AAAA,EACF,GAAG,CAAC,UAAU,iBAAiB,eAAe,YAAY,cAAc,gBAAgB,MAAM,CAAC;AAG/F,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AAEA,QAAM,uBAAuB,CAACA,cAAkB;AAC9C,UAAM,MAAM,IAAI,IAAI,OAAO,SAAS,IAAI;AACxC,QAAI,aAAa,IAAI,cAAcA,UAAS,GAAG,SAAS,CAAC;AACzD,qBAAiB,EAAE,MAAM,wBAAwB,SAAS,EAAE,UAAAA,UAAS,EAAE,CAAC;AACxE,wBAAoBA,SAAQ;AAC5B,kBAAc,EAAE;AAChB,qBAAiB,CAAC,CAAC;AACnB,WAAO,KAAK,IAAI,SAAS,CAAC;AAAA,EAC5B;AAGA,iBAAe,iBAAiB,MAAiD;AAC/E,UAAM,MAAM,MAAM,MAAM,GAAG,QAAQ,gBAAgB;AAAA,MACjD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,KAAK,CAAC;AAAA,IAC/B,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;AAEA,QAAM,mBAAmB,OAAO,UAA+C;AAC7E,UAAMC,SAAQ,MAAM,OAAO;AAC3B,QAAI,CAACA,UAASA,OAAM,WAAW,EAAG;AAElC,UAAM,OAAOA,OAAM,CAAC;AACpB,UAAM,WAAW,KAAK;AACtB,UAAM,UAAU,SAAS,YAAY;AAGrC,QAAI,CAAC,QAAQ,SAAS,MAAM,KAAK,CAAC,QAAQ,SAAS,MAAM,GAAG;AAC1D,YAAM,kCAAkC;AACxC;AAAA,IACF;AAEA,iBAAa,IAAI;AAEjB,QAAI;AACF,cAAQ,IAAI,+BAA+B;AAC3C,YAAM,EAAE,YAAY,OAAO,IAAI,MAAM,iBAAiB,QAAQ;AAC9D,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;AAG9B,aAAO,SAAS,OAAO;AAAA,IACzB,SACO,OAAO;AACZ,cAAQ,MAAM,gCAAgC,KAAK;AACnD;AAAA,QACE,iBAAiB,QACb,qBAAqB,MAAM,OAAO,KAClC;AAAA,MACN;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF;AAEA,QAAM,oBAAoB,MAAM;AA/PlC,QAAAJ;AAgQI,KAAAA,MAAA,aAAa,YAAb,gBAAAA,IAAsB;AAAA,EACxB;AAEA,SACE,gCACE,8BAAC,SAAI,WAAU,mHACb,+BAAC,QAAK,WAAW,GAAG,8EAA8E,GAChG;AAAA,wBAAC,cAAW,WAAU,oCACpB,8BAAC,SAAI,WAAU,oGACZ,2BAAiB,eAChB,oBAAC,GAAG,WAAH,EAAa,WAAW,GAAG,2BAA2B,oBAAoB,eAAe,GAAG,aAAa,GAAG,IAE7G,oBAAC,GAAG,MAAH,EAAQ,WAAW,GAAG,2BAA2B,iBAAiB,aAAa,eAAe,GAAG,aAAa,GAAG,GAEtH,GACF;AAAA,IAEA,qBAAC,eAAY,WAAU,oEACrB;AAAA,2BAAC,SAAI,WAAU,+DACb;AAAA,4BAAC,SAAI,WAAU,kIACZ,2BAAiB,YAChB,iCACG;AAAA,YAAE,aAAa;AAAA,UAChB,oBAAC,kBAAe,MAAM,IAAI;AAAA,WAC5B,IACE,iBAAiB,eACnB,gCACG,YAAE,YAAY,GACjB,IACE,YACF,iCACG;AAAA,YAAE,WAAW;AAAA,UACd,oBAAC,kBAAe,MAAM,IAAI;AAAA,WAC5B,IAEA,EAAE,oBAAoB,GAE1B;AAAA,SACE,iBAAiB,uBAAuB,iBAAiB,iBAAiB,CAAC,aAC3E,oBAAC,SAAI,WAAU,mGACZ,2BAAiB,eAAe,EAAE,wBAAwB,IAAI,EAAE,UAAU,GAC7E;AAAA,SAEJ;AAAA,MAEC,iBAAiB,gBAChB,oBAAC,SAAI,WAAU,gEACZ,6BACC,qBAAC,SAAI,WAAU,gEACb;AAAA,4BAAC,kBAAe,MAAM,IAAI;AAAA,QAC1B,oBAAC,UAAM,YAAE,kBAAkB,KAAK,wBAAuB;AAAA,SACzD,IACE,aAAa,UAAU,SAAS,IAClC,qBAAC,SAAI,WAAU,mBACZ;AAAA,2BACC,qBAAC,SAAI,WAAU,0CACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,OACE,gBACG,iBAAiB,gBAAgB,iBAAiB,kBACjD,GAAG,iBAAiB,YAAY,KAAK,iBAAiB,eAAe,KACrE,iBAAiB,gBAAgB,iBAAiB,mBAAmB,YAAY,iBAAiB,EAAE;AAAA,cAE1G,UAAQ;AAAA,cACR,WAAU;AAAA;AAAA,UACZ;AAAA,UACA;AAAA,YAAC,GAAG;AAAA,YAAH;AAAA,cACC,WAAU;AAAA,cACV,SAAS,MAAM;AACb,oCAAoB,IAAI;AACxB,8BAAc,EAAE;AAChB,iCAAiB,CAAC,CAAC;AAAA,cACrB;AAAA;AAAA,UACF;AAAA,WACF,IAEA,qBAAC,SAAI,WAAU,YACb;AAAA,8BAAC,GAAG,QAAH,EAAU,WAAU,oFAAmF;AAAA,UACxG;AAAA,YAAC;AAAA;AAAA,cACC,aAAa,EAAE,iBAAiB,KAAK;AAAA,cACrC,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,aAAa,EAAE,OAAO,KAAK;AAAA,cAC5C,WAAU;AAAA,cACV,UAAU,CAAC,QAAQ,IAAI,QAAQ,MAAM;AAAA;AAAA,UACvC;AAAA,WACF;AAAA,QAID,cAAc,SAAS,KAAK,eAAe,MAAM,CAAC,oBACjD,oBAAC,SAAI,WAAU,+HACZ,wBAAc,IAAI,CAAAG,cACjB;AAAA,UAAC;AAAA;AAAA,YAEC,WAAU;AAAA,YACV,SAAS,MAAM,qBAAqBA,SAAQ;AAAA,YAE5C;AAAA,kCAAC,GAAG,uBAAH,EAAyB,WAAU,WAAU;AAAA,cAC7CA,UAAS,eACN,GAAGA,UAAS,YAAY,KAAKA,UAAS,eAAe,KACrDA,UAAS,mBAAmB,YAAYA,UAAS,EAAE;AAAA;AAAA;AAAA,UAPlDA,UAAS;AAAA,QAQhB,CACD,GACH;AAAA,SAEJ,IAEA,oBAAC,SAAI,WAAU,qCACZ,YAAE,aAAa,KAAK,0BACvB,GAEJ;AAAA,MAGD,iBAAiB,uBAChB,qBAAC,SAAI,WAAU,uDACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,MAAK;AAAA,YACL,QAAO;AAAA,YACP,UAAU;AAAA,YACV,WAAU;AAAA;AAAA,QACZ;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,SAAS;AAAA,YACT,MAAK;AAAA,YACL,WAAU;AAAA,YACV,UAAU,aAAa,CAAC,QAAQ,IAAI,UAAU,MAAM;AAAA,YAEpD;AAAA,kCAAC,GAAG,QAAH,EAAU,WAAU,WAAU;AAAA,cAC9B,YAAY,EAAE,WAAW,IAAI,EAAE,cAAc;AAAA;AAAA;AAAA,QAChD;AAAA,SACF;AAAA,OAEJ;AAAA,KACF,GACF,GACF;AAEJ;","names":["_a","buildings","searchTerm","building","files"]}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../src/core/components/viewers/pointcloud/src/PointCloudLoadingState/index.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 React from 'react'\r\nimport { Button } from '../../../../ui/Button'\r\nimport { Card, CardContent, CardHeader } from '../../../../ui/Card'\r\nimport { Input } from '../../../../ui/Input'\r\nimport { LoadingSpinner } from '../../../../ui/LoadingSpinner'\r\nimport * as LR from 'lucide-react'\r\nimport { BuildingsContext, usePermissions } from '../../../../../store'\r\nimport { useAppConfigContext } from '../../../../../store/AppConfig/context'\r\nimport { useTranslations } from 'next-intl'\r\nimport { useFilesByBuildingId } from '../../../../../hooks/files/files'\r\nimport type { DbFile } from '../../../../../types/dbTypes'\r\nimport { cn } from '../../../../../utils/utils'\r\nimport { useSearchParams, useRouter } from 'next/navigation'\r\nimport { useBuilding, useBuildings } from '../../../../../hooks/buildings/buildings'\r\nimport { SquareArrowOutUpRight } from 'lucide-react'\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\nexport type pointCloudViewerState = 'opening' | 'loading' | 'ready' | 'error' | 'noPointCloudFiles' | 'noBuilding'\r\n\r\nexport function PointCloudLoadingState() {\r\n const t = useTranslations('PointCloudLoadingState')\r\n const { state: appConfigState } = useAppConfigContext()\r\n const API_BASE = appConfigState.runtimeConfig.pointcloudApiUrl ?? 'http://localhost:5101'\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n const { state: buildingState, dispatch: buildingDispatch } = React.useContext(BuildingsContext)\r\n const { building: storeBuilding } = buildingState.buildings\r\n\r\n const searchParams = useSearchParams()\r\n const buildingId = searchParams.get(\"buildingId\")\r\n const { building: urlBuilding, isLoading: buildingLoading, isError: buildingError } = useBuilding(buildingId ? Number(buildingId) : 0)\r\n const { buildings, isLoading: buildingsLoading } = useBuildings()\r\n\r\n // Determine the current building to use\r\n const building = storeBuilding || urlBuilding\r\n\r\n React.useEffect(() => {\r\n if (!storeBuilding && urlBuilding) {\r\n buildingDispatch({ type: \"SET-CURRENT-BUILDING\", payload: { building: urlBuilding } })\r\n }\r\n }, [urlBuilding, storeBuilding, buildingDispatch])\r\n\r\n const router = useRouter()\r\n const fileInputRef = React.useRef<HTMLInputElement>(null)\r\n\r\n const [currentState, setCurrentState] = React.useState<pointCloudViewerState>('opening')\r\n const [uploading, setUploading] = React.useState(false)\r\n const [searchTerm, setSearchTerm] = React.useState('')\r\n const [searchResults, setSearchResults] = React.useState<any[]>([])\r\n const [selectedBuilding, setSelectedBuilding] = React.useState<any>(null)\r\n \r\n const { files, isLoading: filesLoading } = useFilesByBuildingId(building?.id)\r\n const pointCloudFiles: DbFile[] = React.useMemo(() => (\r\n files.filter(file => {\r\n const ext = file.extension?.toLowerCase()\r\n if (!ext) return false\r\n return ext === \"laz\" || ext === \"las\"\r\n })\r\n ), [files])\r\n\r\n // Fuzzy search function for buildings\r\n const fuzzySearchBuildings = React.useCallback((buildings: any[], searchTerm: string) => {\r\n if (!searchTerm.trim()) return buildings.slice(0, 10)\r\n \r\n const fuzzySearchScore = (search: string, text: string): number => {\r\n if (!search || !text) return 0\r\n const searchLower = search.toLowerCase()\r\n const textLower = text.toLowerCase()\r\n \r\n if (textLower === searchLower) return 1000\r\n if (textLower.startsWith(searchLower)) return 800\r\n if (textLower.includes(searchLower)) return 600\r\n \r\n return 0\r\n }\r\n\r\n return buildings\r\n .map((building) => {\r\n const address = building.buildingAddress || ''\r\n const name = building.buildingName || ''\r\n const id = building.id.toString()\r\n \r\n const addressScore = fuzzySearchScore(searchTerm, address)\r\n const nameScore = fuzzySearchScore(searchTerm, name)\r\n const idScore = fuzzySearchScore(searchTerm, id)\r\n \r\n const maxScore = Math.max(addressScore, nameScore, idScore)\r\n return { ...building, searchScore: maxScore }\r\n })\r\n .filter(building => building.searchScore > 0)\r\n .sort((a, b) => b.searchScore - a.searchScore)\r\n .slice(0, 10)\r\n }, [])\r\n\r\n // Handle search\r\n const handleSearch = React.useCallback((term: string) => {\r\n setSearchTerm(term)\r\n if (buildings) {\r\n setSearchResults(fuzzySearchBuildings(buildings, term))\r\n }\r\n }, [buildings, fuzzySearchBuildings])\r\n\r\n const shouldShow = (currentState === 'opening' || currentState === 'noPointCloudFiles' || currentState === 'noBuilding')\r\n\r\n // Handle initial state transitions\r\n React.useEffect(() => {\r\n // If building is loading, stay in opening state\r\n if (buildingLoading) {\r\n setCurrentState('opening')\r\n return\r\n }\r\n\r\n // If building failed to load or no buildingId provided, show noBuilding state\r\n if (buildingError || (!buildingId && !building)) {\r\n setCurrentState('noBuilding')\r\n return\r\n }\r\n\r\n // If we have a building but files are still loading, stay in opening state\r\n if (building && filesLoading) {\r\n setCurrentState('opening')\r\n return\r\n }\r\n\r\n // If we have a building but no point cloud files, show noPointCloudFiles state\r\n if (building && pointCloudFiles.length === 0) {\r\n setCurrentState('noPointCloudFiles')\r\n return\r\n }\r\n\r\n // We have point cloud files, hide the loading state\r\n if (building && pointCloudFiles.length > 0) {\r\n setCurrentState('ready')\r\n }\r\n }, [building, buildingLoading, buildingError, buildingId, filesLoading, pointCloudFiles.length])\r\n\r\n // Don't render if not visible\r\n if (!shouldShow) {\r\n return null\r\n }\r\n\r\n const handleBuildingSelect = (building: any) => {\r\n const url = new URL(window.location.href)\r\n url.searchParams.set('buildingId', building.id.toString())\r\n buildingDispatch({ type: 'SET-CURRENT-BUILDING', payload: { building } })\r\n setSelectedBuilding(building)\r\n setSearchTerm('')\r\n setSearchResults([])\r\n router.push(url.toString())\r\n }\r\n\r\n // Create point cloud entry\r\n async function createPointCloud(name: string): Promise<CreatePointCloudResponse> {\r\n const res = await fetch(`${API_BASE}/point-cloud`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ name }),\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 const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {\r\n const files = event.target.files\r\n if (!files || files.length === 0) return\r\n\r\n const file = files[0]\r\n const fileName = file.name\r\n const fileExt = fileName.toLowerCase()\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 setUploading(true)\r\n\r\n try {\r\n console.log('Creating point cloud entry...')\r\n const { pointCloud, upload } = await createPointCloud(fileName)\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 // Trigger a refresh of the files - the state will update automatically\r\n window.location.reload()\r\n }\r\n catch (error) {\r\n console.error('Error uploading point cloud:', error)\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 setUploading(false)\r\n }\r\n }\r\n\r\n const handleUploadClick = () => {\r\n fileInputRef.current?.click()\r\n }\r\n\r\n return (\r\n <>\r\n <div className=\"absolute z-10 inset-0 bg-background/50 backdrop-blur-[1px] flex items-center justify-center pointer-events-none\">\r\n <Card className={cn(\"w-[536px] pointer-events-auto flex flex-col justify-start items-center gap-2\")}>\r\n <CardHeader className=\"flex justify-center items-center\">\r\n <div className=\"w-12 h-12 p-2 bg-card rounded-md shadow-sm border border-border flex justify-center items-center\">\r\n {currentState === 'noBuilding' ? (\r\n <LR.Building2 className={cn(\"w-4 h-5 text-foreground\", buildingsLoading && \"animate-pulse\")} strokeWidth={2} />\r\n ) : (\r\n <LR.Grip className={cn(\"w-4 h-5 text-foreground\", currentState === 'opening' && \"animate-pulse\")} strokeWidth={2} />\r\n )}\r\n </div>\r\n </CardHeader>\r\n\r\n <CardContent className=\"self-stretch flex flex-col justify-start items-center gap-6 pt-0\">\r\n <div className=\"self-stretch flex flex-col justify-start items-center gap-2\">\r\n <div className=\"self-stretch text-center text-foreground text-xl font-semibold font-['Inter'] leading-7 flex items-center justify-center gap-2\">\r\n {currentState === 'opening' ? (\r\n <>\r\n {t('openingText')}\r\n <LoadingSpinner size={20} />\r\n </>\r\n ) : currentState === 'noBuilding' ? (\r\n <>\r\n {t('noBuilding')}\r\n </>\r\n ) : uploading ? (\r\n <>\r\n {t('uploading')}\r\n <LoadingSpinner size={20} />\r\n </>\r\n ) : (\r\n t('noPointCloudLoaded')\r\n )}\r\n </div>\r\n {(currentState === 'noPointCloudFiles' || currentState === 'noBuilding') && !uploading && (\r\n <div className=\"self-stretch text-center text-muted-foreground text-sm font-normal font-['Inter'] leading-tight\">\r\n {currentState === 'noBuilding' ? t('selectBuildingHelpText') : t('helpText')}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {currentState === 'noBuilding' && (\r\n <div className=\"self-stretch flex flex-col gap-3 justify-centre items-center\">\r\n {buildingsLoading ? (\r\n <div className=\"flex items-center justify-center gap-2 text-muted-foreground\">\r\n <LoadingSpinner size={16} />\r\n <span>{t('loadingBuildings') || 'Loading buildings...'}</span>\r\n </div>\r\n ) : buildings && buildings.length > 0 ? (\r\n <div className=\"w-full relative\">\r\n {selectedBuilding ? (\r\n <div className=\"relative flex justify-end items-center\">\r\n <Input\r\n type=\"text\"\r\n value={\r\n 'Building: '\r\n + (selectedBuilding.buildingName && selectedBuilding.buildingAddress\r\n ? `${selectedBuilding.buildingName}, ${selectedBuilding.buildingAddress}`\r\n : selectedBuilding.buildingName || selectedBuilding.buildingAddress || `Building ${selectedBuilding.id}`)\r\n }\r\n readOnly\r\n className=\"w-full\"\r\n />\r\n <LR.X \r\n className=\"absolute right-2 cursor-pointer w-4 h-4 text-muted-foreground hover:text-foreground\" \r\n onClick={() => {\r\n setSelectedBuilding(null)\r\n setSearchTerm('')\r\n setSearchResults([])\r\n }} \r\n />\r\n </div>\r\n ) : (\r\n <div className=\"relative\">\r\n <LR.Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4\" />\r\n <Input\r\n placeholder={t('searchBuildings') || 'Search buildings...'}\r\n value={searchTerm}\r\n onChange={(e) => handleSearch(e.target.value)}\r\n className=\"w-full pl-10\"\r\n disabled={!ability.can('read', 'File')}\r\n />\r\n </div>\r\n )}\r\n \r\n {/* Search results dropdown */}\r\n {searchResults.length > 0 && searchTerm !== '' && !selectedBuilding && (\r\n <div className=\"absolute top-full left-0 right-0 mt-1 bg-background border border-border rounded-md shadow-lg max-h-60 overflow-y-auto z-20\">\r\n {searchResults.map(building => (\r\n <div\r\n key={building.id}\r\n className=\"px-3 py-2 text-sm cursor-pointer hover:bg-muted transition-colors flex gap-2 items-center\"\r\n onClick={() => handleBuildingSelect(building)}\r\n >\r\n <LR.SquareArrowOutUpRight className=\"w-3 h-3\" />\r\n {building.buildingName\r\n ? `${building.buildingName}, ${building.buildingAddress}`\r\n : building.buildingAddress || `Building ${building.id}`}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n ) : (\r\n <div className=\"text-muted-foreground text-center\">\r\n {t('noBuildings') || 'No buildings available'}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {currentState === 'noPointCloudFiles' && (\r\n <div className=\"self-stretch flex justify-center items-center gap-3\">\r\n <Input\r\n ref={fileInputRef}\r\n type=\"file\"\r\n accept=\".laz,.las\"\r\n onChange={handleFileChange}\r\n className=\"hidden\"\r\n />\r\n <Button\r\n onClick={handleUploadClick}\r\n size=\"sm\"\r\n className=\"gap-2\"\r\n disabled={uploading || !ability.can('create', 'File')}\r\n >\r\n <LR.Upload className=\"w-4 h-4\" />\r\n {uploading ? t('uploading') : t('uploadButton')}\r\n </Button>\r\n </div>\r\n )}\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </>\r\n )\r\n}\r\n"],"mappings":"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","names":["_a","buildings","searchTerm","building","files"]}
|
package/dist/core/components/viewers/pointcloud/src/PointCloudManagement/PointCloudTable.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PointCloudTable.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/components/viewers/pointcloud/src/PointCloudManagement/PointCloudTable.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"PointCloudTable.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/components/viewers/pointcloud/src/PointCloudManagement/PointCloudTable.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAgD/B,KAAK,oBAAoB,GAAG,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,GAAG;IAC/D,mBAAmB,EAAE,MAAM,IAAI,CAAC;CACnC,CAAC;AAEF,wBAAgB,eAAe,CAAC,EAC5B,mBAAmB,EACnB,GAAG,QAAQ,EACd,EAAE,oBAAoB,qBAoZtB"}
|
|
@@ -15,7 +15,6 @@ var __objRest = (source, exclude) => {
|
|
|
15
15
|
return target;
|
|
16
16
|
};
|
|
17
17
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
18
|
-
var _a;
|
|
19
18
|
import * as React from "react";
|
|
20
19
|
import {
|
|
21
20
|
Table,
|
|
@@ -31,6 +30,7 @@ import { useRouter, usePathname } from "next/navigation";
|
|
|
31
30
|
import { useTranslations } from "next-intl";
|
|
32
31
|
import { toast } from "sonner";
|
|
33
32
|
import ConfirmDialog from "../../../../ConfirmDialog";
|
|
33
|
+
import { useAppConfigContext } from "../../../../../store/AppConfig/context";
|
|
34
34
|
import {
|
|
35
35
|
Star,
|
|
36
36
|
StarOff,
|
|
@@ -41,18 +41,20 @@ import {
|
|
|
41
41
|
ChevronLeft,
|
|
42
42
|
ChevronRight
|
|
43
43
|
} from "lucide-react";
|
|
44
|
-
const API_BASE = (_a = process.env.NEXT_PUBLIC_POINTCLOUD_API_URL) != null ? _a : "http://localhost:5101";
|
|
45
44
|
var PAGE = /* @__PURE__ */ ((PAGE2) => {
|
|
46
45
|
PAGE2["TABLE"] = "table";
|
|
47
46
|
PAGE2["UPLOAD"] = "upload";
|
|
48
47
|
return PAGE2;
|
|
49
48
|
})(PAGE || {});
|
|
50
|
-
function PointCloudTable(
|
|
51
|
-
var
|
|
49
|
+
function PointCloudTable(_a) {
|
|
50
|
+
var _b = _a, {
|
|
52
51
|
onUploadButtonClick
|
|
53
|
-
} =
|
|
52
|
+
} = _b, divProps = __objRest(_b, [
|
|
54
53
|
"onUploadButtonClick"
|
|
55
54
|
]);
|
|
55
|
+
var _a2;
|
|
56
|
+
const { state: appConfigState } = useAppConfigContext();
|
|
57
|
+
const API_BASE = (_a2 = appConfigState.runtimeConfig.pointcloudApiUrl) != null ? _a2 : "http://localhost:5101";
|
|
56
58
|
const [pointClouds, setPointClouds] = React.useState([]);
|
|
57
59
|
const [loading, setLoading] = React.useState(false);
|
|
58
60
|
const [error, setError] = React.useState(null);
|
package/dist/core/components/viewers/pointcloud/src/PointCloudManagement/PointCloudTable.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../src/core/components/viewers/pointcloud/src/PointCloudManagement/PointCloudTable.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 {\r\n Table,\r\n TableBody,\r\n TableCell,\r\n TableHead,\r\n TableHeader,\r\n TableRow,\r\n} from \"../../../../ui/Table\";\r\n\r\nimport { Badge, Button, Checkbox, Input, ScrollArea } from '../../../../ui';\r\n\r\nimport { Tabs, TabsList, TabsTrigger } from \"../../../../ui/Tabs\";\r\n\r\nimport { useRouter, usePathname } from 'next/navigation'\r\nimport { useTranslations } from 'next-intl'\r\nimport { toast } from 'sonner'\r\nimport ConfirmDialog from '../../../../ConfirmDialog'\r\n\r\nimport {\r\n Star,\r\n StarOff,\r\n Eye,\r\n Trash2,\r\n Filter,\r\n RotateCcw,\r\n ChevronLeft,\r\n ChevronRight,\r\n} from \"lucide-react\";\r\n\r\nconst API_BASE =\r\n process.env.NEXT_PUBLIC_POINTCLOUD_API_URL ?? \"http://localhost:5101\";\r\n\r\ntype PointCloud = {\r\n id: string;\r\n name: string;\r\n point_cloud_uploaded: boolean;\r\n point_cloud_potree_converted: boolean;\r\n createdAt: string;\r\n potree_metadata_presigned_url?: string | null;\r\n potree_metadata_file_key?: string | null;\r\n};\r\n\r\ntype StatusFilter = \"all\" | \"uploaded\" | \"converted\" | \"not-converted\";\r\n\r\nenum PAGE {\r\n TABLE = \"table\",\r\n UPLOAD = \"upload\",\r\n}\r\n\r\ntype PointCloudTableProps = React.HTMLAttributes<HTMLDivElement> & {\r\n onUploadButtonClick: () => void;\r\n};\r\n\r\nexport function PointCloudTable({\r\n onUploadButtonClick, \r\n ...divProps\r\n}: PointCloudTableProps) {\r\n const [pointClouds, setPointClouds] = React.useState<PointCloud[]>([]);\r\n const [loading, setLoading] = React.useState(false);\r\n const [error, setError] = React.useState<string | null>(null);\r\n const [search, setSearch] = React.useState(\"\");\r\n const [statusFilter, setStatusFilter] = React.useState<StatusFilter>(\"all\");\r\n const [favorites, setFavorites] = React.useState<Set<string>>(new Set());\r\n const [currentUrl, setCurrentUrl] = React.useState<URL | null>(null);\r\n const [deleteTarget, setDeleteTarget] = React.useState<PointCloud | null>(null)\r\n const [isDeleting, setIsDeleting] = React.useState(false)\r\n const t = useTranslations('PointCloudManagement')\r\n\r\n React.useEffect(() => {\r\n // This only runs in the browser\r\n const url = new URL(window.location.href);\r\n setCurrentUrl(url);\r\n }, []);\r\n\r\n const [currentPage, setCurrentPage] = React.useState<PAGE>(PAGE.TABLE)\r\n\r\n // route and navigation\r\n const router = useRouter()\r\n const pathname = usePathname()\r\n\r\n const loadPointClouds = React.useCallback(async () => {\r\n setLoading(true);\r\n setError(null);\r\n\r\n try {\r\n const res = await fetch(`${API_BASE}/point-cloud`, {\r\n headers: { \"Accept\": \"application/json\" },\r\n });\r\n\r\n if (!res.ok) {\r\n throw new Error(`Failed to fetch point clouds (${res.status})`);\r\n }\r\n\r\n const data: PointCloud[] = await res.json();\r\n setPointClouds(Array.isArray(data) ? data : []);\r\n } catch (err) {\r\n console.error(err);\r\n setError(\r\n err instanceof Error ? err.message : \"Unknown error while loading data\",\r\n );\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, []);\r\n\r\n React.useEffect(() => {\r\n loadPointClouds();\r\n }, [loadPointClouds]);\r\n\r\n const openViewer = (pointCloudId) => {\r\n if (!pointCloudId) return;\r\n const origin = window.location.origin\r\n const pathname = window.location.pathname\r\n const params = new URLSearchParams(currentUrl.search)\r\n params.set('viewer', 'pointcloud')\r\n params.set('pointCloudId', pointCloudId)\r\n const newurl = `${origin}${pathname}?${params.toString()}`\r\n router.push(newurl, { scroll: false }) // push but do not refresh\r\n };\r\n\r\n const handleDelete = async () => {\r\n if (!deleteTarget) return\r\n\r\n setIsDeleting(true)\r\n\r\n try {\r\n const res = await fetch(`${API_BASE}/point-cloud/${deleteTarget.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 setPointClouds((prev) => prev.filter((pc) => pc.id !== deleteTarget.id));\r\n setFavorites((prev) => {\r\n const next = new Set(prev);\r\n next.delete(deleteTarget.id);\r\n return next;\r\n });\r\n toast.success(t('deleteSuccess', { name: deleteTarget.name }))\r\n } catch (err) {\r\n console.error(err);\r\n toast.error(\r\n err instanceof Error\r\n ? t('deleteFailedWithReason', { error: err.message })\r\n : t('deleteFailed'),\r\n );\r\n } finally {\r\n setIsDeleting(false)\r\n setDeleteTarget(null)\r\n }\r\n };\r\n\r\n const toggleFavorite = (id: string) => {\r\n setFavorites((prev) => {\r\n const next = new Set(prev);\r\n if (next.has(id)) next.delete(id);\r\n else next.add(id);\r\n return next;\r\n });\r\n };\r\n\r\n const filtered = React.useMemo(() => {\r\n return pointClouds.filter((pc) => {\r\n const matchesSearch =\r\n !search ||\r\n pc.name.toLowerCase().includes(search.toLowerCase()) ||\r\n pc.id.toLowerCase().includes(search.toLowerCase());\r\n\r\n let matchesStatus = true;\r\n if (statusFilter === \"uploaded\") {\r\n matchesStatus = pc.point_cloud_uploaded;\r\n } else if (statusFilter === \"converted\") {\r\n matchesStatus = pc.point_cloud_potree_converted;\r\n } else if (statusFilter === \"not-converted\") {\r\n matchesStatus =\r\n pc.point_cloud_uploaded && !pc.point_cloud_potree_converted;\r\n }\r\n\r\n return matchesSearch && matchesStatus;\r\n });\r\n }, [pointClouds, search, statusFilter]);\r\n\r\n return (\r\n <div className={`p-2 mt-20 ${divProps}`}>\r\n {/* Header */}\r\n <div className=\"flex items-start justify-between gap-4\">\r\n <div className=\"space-y-1\">\r\n <h1 className=\"text-2xl font-semibold tracking-tight\">\r\n Point Clouds\r\n </h1>\r\n </div>\r\n\r\n <div className=\"flex items-center gap-2\">\r\n <Input\r\n placeholder=\"Search point clouds...\"\r\n className=\"w-[240px]\"\r\n value={search}\r\n onChange={(e) => setSearch(e.target.value)}\r\n />\r\n <Button variant=\"outline\" size=\"icon\" title=\"Filters (not wired)\">\r\n <Filter className=\"h-4 w-4\" />\r\n </Button>\r\n <Button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n onClick={loadPointClouds}\r\n disabled={loading}\r\n title=\"Refresh\"\r\n >\r\n <RotateCcw className=\"h-4 w-4\" />\r\n </Button>\r\n </div>\r\n </div>\r\n\r\n {/* Tabs row */}\r\n <Tabs\r\n value={statusFilter}\r\n onValueChange={(val) => setStatusFilter(val as StatusFilter)}\r\n className=\"w-full mb-2\"\r\n >\r\n <TabsList className=\"h-8\">\r\n <TabsTrigger value=\"all\" className=\"px-3\">\r\n All\r\n </TabsTrigger>\r\n <TabsTrigger value=\"uploaded\" className=\"px-3\">\r\n Uploaded\r\n </TabsTrigger>\r\n <TabsTrigger value=\"converted\" className=\"px-3\">\r\n Converted\r\n </TabsTrigger>\r\n <TabsTrigger value=\"not-converted\" className=\"px-3\">\r\n Needs conversion\r\n </TabsTrigger>\r\n </TabsList>\r\n </Tabs>\r\n\r\n {/* Table card */}\r\n <div className=\"rounded-md border bg-background\">\r\n <ScrollArea className=\"max-h-[520px]\">\r\n <Table>\r\n <TableHeader className=\"sticky top-0 z-10 bg-background/95 backdrop-blur\">\r\n <TableRow>\r\n <TableHead className=\"w-[40px] pl-4\">\r\n <Checkbox aria-label=\"Select all\" />\r\n </TableHead>\r\n <TableHead className=\"w-[48px]\"></TableHead>\r\n <TableHead className=\"min-w-[220px]\">Name</TableHead>\r\n <TableHead className=\"w-[200px]\">Status</TableHead>\r\n <TableHead className=\"w-[220px]\">Created</TableHead>\r\n <TableHead className=\"w-[160px]\">Metadata</TableHead>\r\n <TableHead className=\"w-[140px] text-right pr-4\">\r\n Actions\r\n </TableHead>\r\n </TableRow>\r\n </TableHeader>\r\n\r\n <TableBody>\r\n {loading && pointClouds.length === 0 && (\r\n <TableRow>\r\n <TableCell colSpan={7} className=\"h-24 text-center\">\r\n Loading point clouds…\r\n </TableCell>\r\n </TableRow>\r\n )}\r\n\r\n {!loading && error && (\r\n <TableRow>\r\n <TableCell colSpan={7} className=\"h-24 text-center\">\r\n <span className=\"text-sm text-destructive\">\r\n {error}\r\n </span>\r\n </TableCell>\r\n </TableRow>\r\n )}\r\n\r\n {!loading && !error && filtered.length === 0 && (\r\n <TableRow>\r\n <TableCell colSpan={7} className=\"h-24 text-center\">\r\n <span className=\"text-sm text-muted-foreground\">\r\n No point clouds found.\r\n </span>\r\n </TableCell>\r\n </TableRow>\r\n )}\r\n\r\n {filtered.map((pc) => {\r\n const isFavorite = favorites.has(pc.id);\r\n return (\r\n <TableRow\r\n key={pc.id}\r\n className=\"hover:bg-muted/50 transition-colors\"\r\n >\r\n <TableCell className=\"pl-4\">\r\n <Checkbox aria-label={`Select ${pc.name}`} />\r\n </TableCell>\r\n\r\n {/* Favorite star */}\r\n <TableCell className=\"text-center\">\r\n <button\r\n type=\"button\"\r\n onClick={() => toggleFavorite(pc.id)}\r\n className=\"inline-flex h-6 w-6 items-center justify-center rounded-full text-muted-foreground hover:text-yellow-500\"\r\n aria-label={\r\n isFavorite\r\n ? `Remove ${pc.name} from favourites`\r\n : `Add ${pc.name} to favourites`\r\n }\r\n >\r\n {isFavorite ? (\r\n <Star className=\"h-4 w-4 fill-yellow-400\" />\r\n ) : (\r\n <StarOff className=\"h-4 w-4\" />\r\n )}\r\n </button>\r\n </TableCell>\r\n\r\n {/* Name & ID */}\r\n <TableCell>\r\n <div className=\"font-medium\">{pc.name}</div>\r\n <div className=\"text-xs text-muted-foreground\">\r\n {pc.id}\r\n </div>\r\n </TableCell>\r\n\r\n {/* Status badges */}\r\n <TableCell>\r\n <div className=\"flex flex-wrap gap-1\">\r\n <Badge\r\n className={`text-xs border ${\r\n pc.point_cloud_uploaded\r\n ? \"bg-emerald-50 text-emerald-700 border-emerald-200\"\r\n : \"bg-rose-50 text-rose-700 border-rose-200\"\r\n }`}\r\n >\r\n {pc.point_cloud_uploaded ? \"Uploaded\" : \"Not uploaded\"}\r\n </Badge>\r\n\r\n <Badge\r\n className={`text-xs border ${\r\n pc.point_cloud_potree_converted\r\n ? \"bg-sky-50 text-sky-700 border-sky-200\"\r\n : \"bg-amber-50 text-amber-700 border-amber-200\"\r\n }`}\r\n >\r\n {pc.point_cloud_potree_converted\r\n ? \"Ready to view\"\r\n : \"Needs conversion\"}\r\n </Badge>\r\n </div>\r\n </TableCell>\r\n\r\n {/* Created date */}\r\n <TableCell className=\"whitespace-nowrap text-sm text-muted-foreground\">\r\n {new Date(pc.createdAt).toLocaleString()}\r\n </TableCell>\r\n\r\n {/* Metadata link */}\r\n <TableCell>\r\n {pc.potree_metadata_presigned_url ? (\r\n <Button\r\n asChild\r\n variant=\"link\"\r\n size=\"sm\"\r\n className=\"px-0\"\r\n >\r\n <a\r\n href={pc.potree_metadata_presigned_url}\r\n target=\"_blank\"\r\n rel=\"noreferrer\"\r\n >\r\n metadata.json\r\n </a>\r\n </Button>\r\n ) : (\r\n <span className=\"text-xs text-muted-foreground\">—</span>\r\n )}\r\n </TableCell>\r\n\r\n {/* Actions */}\r\n <TableCell className=\"pr-4 text-right\">\r\n <div className=\"flex justify-end gap-2\">\r\n <Button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n disabled={!pc.potree_metadata_file_key}\r\n onClick={() =>\r\n openViewer(pc.id)\r\n }\r\n title=\"Open Potree viewer\"\r\n >\r\n <Eye className=\"h-4 w-4\" />\r\n </Button>\r\n <Button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n className=\"text-destructive hover:text-destructive\"\r\n onClick={() => setDeleteTarget(pc)}\r\n title={t('deleteTitle')}\r\n >\r\n <Trash2 className=\"h-4 w-4\" />\r\n </Button>\r\n </div>\r\n </TableCell>\r\n </TableRow>\r\n );\r\n })}\r\n </TableBody>\r\n </Table>\r\n </ScrollArea>\r\n\r\n {/* Faux pagination / footer */}\r\n <div className=\"flex items-center justify-between border-t px-4 py-2 text-xs text-muted-foreground\">\r\n <div>\r\n Showing <span className=\"font-medium\">{filtered.length}</span> of{\" \"}\r\n <span className=\"font-medium\">{pointClouds.length}</span> point\r\n clouds\r\n </div>\r\n <div className=\"flex items-center gap-1\">\r\n <Button variant=\"ghost\" size=\"icon\" disabled>\r\n <ChevronLeft className=\"h-4 w-4\" />\r\n </Button>\r\n <Button variant=\"ghost\" size=\"icon\" disabled>\r\n <ChevronRight className=\"h-4 w-4\" />\r\n </Button>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n\r\n <Button\r\n onClick={() => {\r\n if (onUploadButtonClick != undefined){\r\n onUploadButtonClick()\r\n }\r\n }}\r\n className=\"mt-1 flex gap-2\"\r\n >\r\n Upload point cloud →\r\n </Button>\r\n\r\n <ConfirmDialog\r\n isOpen={deleteTarget != null}\r\n isDeleting={isDeleting}\r\n onOpenChange={(open) => {\r\n if (!open) setDeleteTarget(null)\r\n }}\r\n handleConfirm={async (e) => {\r\n e.preventDefault()\r\n await handleDelete()\r\n }}\r\n itemName={deleteTarget?.name}\r\n dataType={t('pointCloudItem')}\r\n />\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":["PAGE","pathname"]}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../src/core/components/viewers/pointcloud/src/PointCloudManagement/PointCloudTable.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 {\r\n Table,\r\n TableBody,\r\n TableCell,\r\n TableHead,\r\n TableHeader,\r\n TableRow,\r\n} from \"../../../../ui/Table\";\r\n\r\nimport { Badge, Button, Checkbox, Input, ScrollArea } from '../../../../ui';\r\n\r\nimport { Tabs, TabsList, TabsTrigger } from \"../../../../ui/Tabs\";\r\n\r\nimport { useRouter, usePathname } from 'next/navigation'\r\nimport { useTranslations } from 'next-intl'\r\nimport { toast } from 'sonner'\r\nimport ConfirmDialog from '../../../../ConfirmDialog'\r\nimport { useAppConfigContext } from '../../../../../store/AppConfig/context'\r\n\r\nimport {\r\n Star,\r\n StarOff,\r\n Eye,\r\n Trash2,\r\n Filter,\r\n RotateCcw,\r\n ChevronLeft,\r\n ChevronRight,\r\n} from \"lucide-react\";\r\n\r\ntype PointCloud = {\r\n id: string;\r\n name: string;\r\n point_cloud_uploaded: boolean;\r\n point_cloud_potree_converted: boolean;\r\n createdAt: string;\r\n potree_metadata_presigned_url?: string | null;\r\n potree_metadata_file_key?: string | null;\r\n};\r\n\r\ntype StatusFilter = \"all\" | \"uploaded\" | \"converted\" | \"not-converted\";\r\n\r\nenum PAGE {\r\n TABLE = \"table\",\r\n UPLOAD = \"upload\",\r\n}\r\n\r\ntype PointCloudTableProps = React.HTMLAttributes<HTMLDivElement> & {\r\n onUploadButtonClick: () => void;\r\n};\r\n\r\nexport function PointCloudTable({\r\n onUploadButtonClick,\r\n ...divProps\r\n}: PointCloudTableProps) {\r\n const { state: appConfigState } = useAppConfigContext()\r\n const API_BASE = appConfigState.runtimeConfig.pointcloudApiUrl ?? 'http://localhost:5101'\r\n const [pointClouds, setPointClouds] = React.useState<PointCloud[]>([]);\r\n const [loading, setLoading] = React.useState(false);\r\n const [error, setError] = React.useState<string | null>(null);\r\n const [search, setSearch] = React.useState(\"\");\r\n const [statusFilter, setStatusFilter] = React.useState<StatusFilter>(\"all\");\r\n const [favorites, setFavorites] = React.useState<Set<string>>(new Set());\r\n const [currentUrl, setCurrentUrl] = React.useState<URL | null>(null);\r\n const [deleteTarget, setDeleteTarget] = React.useState<PointCloud | null>(null)\r\n const [isDeleting, setIsDeleting] = React.useState(false)\r\n const t = useTranslations('PointCloudManagement')\r\n\r\n React.useEffect(() => {\r\n // This only runs in the browser\r\n const url = new URL(window.location.href);\r\n setCurrentUrl(url);\r\n }, []);\r\n\r\n const [currentPage, setCurrentPage] = React.useState<PAGE>(PAGE.TABLE)\r\n\r\n // route and navigation\r\n const router = useRouter()\r\n const pathname = usePathname()\r\n\r\n const loadPointClouds = React.useCallback(async () => {\r\n setLoading(true);\r\n setError(null);\r\n\r\n try {\r\n const res = await fetch(`${API_BASE}/point-cloud`, {\r\n headers: { \"Accept\": \"application/json\" },\r\n });\r\n\r\n if (!res.ok) {\r\n throw new Error(`Failed to fetch point clouds (${res.status})`);\r\n }\r\n\r\n const data: PointCloud[] = await res.json();\r\n setPointClouds(Array.isArray(data) ? data : []);\r\n } catch (err) {\r\n console.error(err);\r\n setError(\r\n err instanceof Error ? err.message : \"Unknown error while loading data\",\r\n );\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, []);\r\n\r\n React.useEffect(() => {\r\n loadPointClouds();\r\n }, [loadPointClouds]);\r\n\r\n const openViewer = (pointCloudId) => {\r\n if (!pointCloudId) return;\r\n const origin = window.location.origin\r\n const pathname = window.location.pathname\r\n const params = new URLSearchParams(currentUrl.search)\r\n params.set('viewer', 'pointcloud')\r\n params.set('pointCloudId', pointCloudId)\r\n const newurl = `${origin}${pathname}?${params.toString()}`\r\n router.push(newurl, { scroll: false }) // push but do not refresh\r\n };\r\n\r\n const handleDelete = async () => {\r\n if (!deleteTarget) return\r\n\r\n setIsDeleting(true)\r\n\r\n try {\r\n const res = await fetch(`${API_BASE}/point-cloud/${deleteTarget.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 setPointClouds((prev) => prev.filter((pc) => pc.id !== deleteTarget.id));\r\n setFavorites((prev) => {\r\n const next = new Set(prev);\r\n next.delete(deleteTarget.id);\r\n return next;\r\n });\r\n toast.success(t('deleteSuccess', { name: deleteTarget.name }))\r\n } catch (err) {\r\n console.error(err);\r\n toast.error(\r\n err instanceof Error\r\n ? t('deleteFailedWithReason', { error: err.message })\r\n : t('deleteFailed'),\r\n );\r\n } finally {\r\n setIsDeleting(false)\r\n setDeleteTarget(null)\r\n }\r\n };\r\n\r\n const toggleFavorite = (id: string) => {\r\n setFavorites((prev) => {\r\n const next = new Set(prev);\r\n if (next.has(id)) next.delete(id);\r\n else next.add(id);\r\n return next;\r\n });\r\n };\r\n\r\n const filtered = React.useMemo(() => {\r\n return pointClouds.filter((pc) => {\r\n const matchesSearch =\r\n !search ||\r\n pc.name.toLowerCase().includes(search.toLowerCase()) ||\r\n pc.id.toLowerCase().includes(search.toLowerCase());\r\n\r\n let matchesStatus = true;\r\n if (statusFilter === \"uploaded\") {\r\n matchesStatus = pc.point_cloud_uploaded;\r\n } else if (statusFilter === \"converted\") {\r\n matchesStatus = pc.point_cloud_potree_converted;\r\n } else if (statusFilter === \"not-converted\") {\r\n matchesStatus =\r\n pc.point_cloud_uploaded && !pc.point_cloud_potree_converted;\r\n }\r\n\r\n return matchesSearch && matchesStatus;\r\n });\r\n }, [pointClouds, search, statusFilter]);\r\n\r\n return (\r\n <div className={`p-2 mt-20 ${divProps}`}>\r\n {/* Header */}\r\n <div className=\"flex items-start justify-between gap-4\">\r\n <div className=\"space-y-1\">\r\n <h1 className=\"text-2xl font-semibold tracking-tight\">\r\n Point Clouds\r\n </h1>\r\n </div>\r\n\r\n <div className=\"flex items-center gap-2\">\r\n <Input\r\n placeholder=\"Search point clouds...\"\r\n className=\"w-[240px]\"\r\n value={search}\r\n onChange={(e) => setSearch(e.target.value)}\r\n />\r\n <Button variant=\"outline\" size=\"icon\" title=\"Filters (not wired)\">\r\n <Filter className=\"h-4 w-4\" />\r\n </Button>\r\n <Button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n onClick={loadPointClouds}\r\n disabled={loading}\r\n title=\"Refresh\"\r\n >\r\n <RotateCcw className=\"h-4 w-4\" />\r\n </Button>\r\n </div>\r\n </div>\r\n\r\n {/* Tabs row */}\r\n <Tabs\r\n value={statusFilter}\r\n onValueChange={(val) => setStatusFilter(val as StatusFilter)}\r\n className=\"w-full mb-2\"\r\n >\r\n <TabsList className=\"h-8\">\r\n <TabsTrigger value=\"all\" className=\"px-3\">\r\n All\r\n </TabsTrigger>\r\n <TabsTrigger value=\"uploaded\" className=\"px-3\">\r\n Uploaded\r\n </TabsTrigger>\r\n <TabsTrigger value=\"converted\" className=\"px-3\">\r\n Converted\r\n </TabsTrigger>\r\n <TabsTrigger value=\"not-converted\" className=\"px-3\">\r\n Needs conversion\r\n </TabsTrigger>\r\n </TabsList>\r\n </Tabs>\r\n\r\n {/* Table card */}\r\n <div className=\"rounded-md border bg-background\">\r\n <ScrollArea className=\"max-h-[520px]\">\r\n <Table>\r\n <TableHeader className=\"sticky top-0 z-10 bg-background/95 backdrop-blur\">\r\n <TableRow>\r\n <TableHead className=\"w-[40px] pl-4\">\r\n <Checkbox aria-label=\"Select all\" />\r\n </TableHead>\r\n <TableHead className=\"w-[48px]\"></TableHead>\r\n <TableHead className=\"min-w-[220px]\">Name</TableHead>\r\n <TableHead className=\"w-[200px]\">Status</TableHead>\r\n <TableHead className=\"w-[220px]\">Created</TableHead>\r\n <TableHead className=\"w-[160px]\">Metadata</TableHead>\r\n <TableHead className=\"w-[140px] text-right pr-4\">\r\n Actions\r\n </TableHead>\r\n </TableRow>\r\n </TableHeader>\r\n\r\n <TableBody>\r\n {loading && pointClouds.length === 0 && (\r\n <TableRow>\r\n <TableCell colSpan={7} className=\"h-24 text-center\">\r\n Loading point clouds…\r\n </TableCell>\r\n </TableRow>\r\n )}\r\n\r\n {!loading && error && (\r\n <TableRow>\r\n <TableCell colSpan={7} className=\"h-24 text-center\">\r\n <span className=\"text-sm text-destructive\">\r\n {error}\r\n </span>\r\n </TableCell>\r\n </TableRow>\r\n )}\r\n\r\n {!loading && !error && filtered.length === 0 && (\r\n <TableRow>\r\n <TableCell colSpan={7} className=\"h-24 text-center\">\r\n <span className=\"text-sm text-muted-foreground\">\r\n No point clouds found.\r\n </span>\r\n </TableCell>\r\n </TableRow>\r\n )}\r\n\r\n {filtered.map((pc) => {\r\n const isFavorite = favorites.has(pc.id);\r\n return (\r\n <TableRow\r\n key={pc.id}\r\n className=\"hover:bg-muted/50 transition-colors\"\r\n >\r\n <TableCell className=\"pl-4\">\r\n <Checkbox aria-label={`Select ${pc.name}`} />\r\n </TableCell>\r\n\r\n {/* Favorite star */}\r\n <TableCell className=\"text-center\">\r\n <button\r\n type=\"button\"\r\n onClick={() => toggleFavorite(pc.id)}\r\n className=\"inline-flex h-6 w-6 items-center justify-center rounded-full text-muted-foreground hover:text-yellow-500\"\r\n aria-label={\r\n isFavorite\r\n ? `Remove ${pc.name} from favourites`\r\n : `Add ${pc.name} to favourites`\r\n }\r\n >\r\n {isFavorite ? (\r\n <Star className=\"h-4 w-4 fill-yellow-400\" />\r\n ) : (\r\n <StarOff className=\"h-4 w-4\" />\r\n )}\r\n </button>\r\n </TableCell>\r\n\r\n {/* Name & ID */}\r\n <TableCell>\r\n <div className=\"font-medium\">{pc.name}</div>\r\n <div className=\"text-xs text-muted-foreground\">\r\n {pc.id}\r\n </div>\r\n </TableCell>\r\n\r\n {/* Status badges */}\r\n <TableCell>\r\n <div className=\"flex flex-wrap gap-1\">\r\n <Badge\r\n className={`text-xs border ${\r\n pc.point_cloud_uploaded\r\n ? \"bg-emerald-50 text-emerald-700 border-emerald-200\"\r\n : \"bg-rose-50 text-rose-700 border-rose-200\"\r\n }`}\r\n >\r\n {pc.point_cloud_uploaded ? \"Uploaded\" : \"Not uploaded\"}\r\n </Badge>\r\n\r\n <Badge\r\n className={`text-xs border ${\r\n pc.point_cloud_potree_converted\r\n ? \"bg-sky-50 text-sky-700 border-sky-200\"\r\n : \"bg-amber-50 text-amber-700 border-amber-200\"\r\n }`}\r\n >\r\n {pc.point_cloud_potree_converted\r\n ? \"Ready to view\"\r\n : \"Needs conversion\"}\r\n </Badge>\r\n </div>\r\n </TableCell>\r\n\r\n {/* Created date */}\r\n <TableCell className=\"whitespace-nowrap text-sm text-muted-foreground\">\r\n {new Date(pc.createdAt).toLocaleString()}\r\n </TableCell>\r\n\r\n {/* Metadata link */}\r\n <TableCell>\r\n {pc.potree_metadata_presigned_url ? (\r\n <Button\r\n asChild\r\n variant=\"link\"\r\n size=\"sm\"\r\n className=\"px-0\"\r\n >\r\n <a\r\n href={pc.potree_metadata_presigned_url}\r\n target=\"_blank\"\r\n rel=\"noreferrer\"\r\n >\r\n metadata.json\r\n </a>\r\n </Button>\r\n ) : (\r\n <span className=\"text-xs text-muted-foreground\">—</span>\r\n )}\r\n </TableCell>\r\n\r\n {/* Actions */}\r\n <TableCell className=\"pr-4 text-right\">\r\n <div className=\"flex justify-end gap-2\">\r\n <Button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n disabled={!pc.potree_metadata_file_key}\r\n onClick={() =>\r\n openViewer(pc.id)\r\n }\r\n title=\"Open Potree viewer\"\r\n >\r\n <Eye className=\"h-4 w-4\" />\r\n </Button>\r\n <Button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n className=\"text-destructive hover:text-destructive\"\r\n onClick={() => setDeleteTarget(pc)}\r\n title={t('deleteTitle')}\r\n >\r\n <Trash2 className=\"h-4 w-4\" />\r\n </Button>\r\n </div>\r\n </TableCell>\r\n </TableRow>\r\n );\r\n })}\r\n </TableBody>\r\n </Table>\r\n </ScrollArea>\r\n\r\n {/* Faux pagination / footer */}\r\n <div className=\"flex items-center justify-between border-t px-4 py-2 text-xs text-muted-foreground\">\r\n <div>\r\n Showing <span className=\"font-medium\">{filtered.length}</span> of{\" \"}\r\n <span className=\"font-medium\">{pointClouds.length}</span> point\r\n clouds\r\n </div>\r\n <div className=\"flex items-center gap-1\">\r\n <Button variant=\"ghost\" size=\"icon\" disabled>\r\n <ChevronLeft className=\"h-4 w-4\" />\r\n </Button>\r\n <Button variant=\"ghost\" size=\"icon\" disabled>\r\n <ChevronRight className=\"h-4 w-4\" />\r\n </Button>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n\r\n <Button\r\n onClick={() => {\r\n if (onUploadButtonClick != undefined){\r\n onUploadButtonClick()\r\n }\r\n }}\r\n className=\"mt-1 flex gap-2\"\r\n >\r\n Upload point cloud →\r\n </Button>\r\n\r\n <ConfirmDialog\r\n isOpen={deleteTarget != null}\r\n isDeleting={isDeleting}\r\n onOpenChange={(open) => {\r\n if (!open) setDeleteTarget(null)\r\n }}\r\n handleConfirm={async (e) => {\r\n e.preventDefault()\r\n await handleDelete()\r\n }}\r\n itemName={deleteTarget?.name}\r\n dataType={t('pointCloudItem')}\r\n />\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":["PAGE","_a","pathname"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PointCloudUploadPanel.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/components/viewers/pointcloud/src/PointCloudManagement/PointCloudUploadPanel.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAyC/B,KAAK,yBAAyB,GAAG;IAC/B,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;CAClC,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAAC,EAC3C,mBAAmB,GACpB,EAAE,yBAAyB,
|
|
1
|
+
{"version":3,"file":"PointCloudUploadPanel.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/components/viewers/pointcloud/src/PointCloudManagement/PointCloudUploadPanel.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAyC/B,KAAK,yBAAyB,GAAG;IAC/B,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;CAClC,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAAC,EAC3C,mBAAmB,GACpB,EAAE,yBAAyB,qBAiX3B"}
|