diff --git a/react-components/.eslintrc.cjs b/react-components/.eslintrc.cjs index 5d1d664f654..626bc37f36e 100644 --- a/react-components/.eslintrc.cjs +++ b/react-components/.eslintrc.cjs @@ -19,6 +19,7 @@ module.exports = { rules: { 'react/react-in-jsx-scope': 'off', '@typescript-eslint/consistent-type-definitions': ['error', 'type'], + '@typescript-eslint/no-misused-promises': 'off', eqeqeq: ['error', 'always'] }, settings: { diff --git a/react-components/package.json b/react-components/package.json index 2afb4b393f5..30f2b48e03d 100644 --- a/react-components/package.json +++ b/react-components/package.json @@ -1,6 +1,6 @@ { "name": "@cognite/reveal-react-components", - "version": "0.9.0", + "version": "0.10.0", "exports": "./dist/index.js", "types": "./dist/index.d.ts", "type": "module", @@ -30,12 +30,12 @@ "@cognite/cogs.js": "^9.17.0", "@cognite/reveal": "4.4.0", "@cognite/sdk": "^8.2.0", - "@storybook/addon-essentials": "7.2.2", - "@storybook/addon-interactions": "7.2.2", - "@storybook/addon-links": "7.2.2", - "@storybook/blocks": "7.2.2", - "@storybook/react": "7.2.2", - "@storybook/react-webpack5": "7.2.2", + "@storybook/addon-essentials": "7.2.3", + "@storybook/addon-interactions": "7.2.3", + "@storybook/addon-links": "7.2.3", + "@storybook/blocks": "7.2.3", + "@storybook/react": "7.2.3", + "@storybook/react-webpack5": "7.2.3", "@storybook/testing-library": "0.2.0", "@tanstack/react-query-devtools": "^4.29.19", "@types/lodash": "^4.14.190", @@ -58,7 +58,7 @@ "prop-types": "15.8.1", "react": "18.2.0", "react-dom": "18.2.0", - "storybook": "7.2.2", + "storybook": "7.2.3", "style-loader": "^3.3.3", "styled-components": "5.3.11", "three": "0.155.0", diff --git a/react-components/src/components/CadModelContainer/CadModelContainer.tsx b/react-components/src/components/CadModelContainer/CadModelContainer.tsx index 0add639357a..45e1fcc06d7 100644 --- a/react-components/src/components/CadModelContainer/CadModelContainer.tsx +++ b/react-components/src/components/CadModelContainer/CadModelContainer.tsx @@ -9,7 +9,8 @@ import { TreeIndexNodeCollection, NodeIdNodeCollection, DefaultNodeAppearance, - type NodeCollection + type NodeCollection, + type Cognite3DViewer } from '@cognite/reveal'; import { useReveal } from '../RevealContainer/RevealContext'; import { Matrix4 } from 'three'; @@ -61,16 +62,16 @@ export function CadModelContainer({ }, [modelId, revisionId, geometryFilter]); useEffect(() => { - if (model === undefined || transform === undefined) return; + if (!modelExists(model, viewer) || transform === undefined) return; model.setModelTransformation(transform); }, [transform, model]); useEffect(() => { - if (model === undefined || styleGroups === undefined) return; + if (!modelExists(model, viewer) || styleGroups === undefined) return; const stylingCollections = applyStyling(sdk, model, styleGroups); return () => { - if (model === undefined) return; + if (!modelExists(model, viewer)) return; void stylingCollections.then((nodeCollections) => { nodeCollections.forEach((nodeCollection) => { model.unassignStyledNodeCollection(nodeCollection); @@ -80,12 +81,13 @@ export function CadModelContainer({ }, [styleGroups, model]); useEffect(() => { - if (model === undefined) return; + if (!modelExists(model, viewer)) return; model.setDefaultNodeAppearance(defaultStyle); return () => { - if (model !== undefined) { - model.setDefaultNodeAppearance(DefaultNodeAppearance.Default); + if (!modelExists(model, viewer)) { + return; } + model.setDefaultNodeAppearance(DefaultNodeAppearance.Default); }; }, [defaultStyle, model]); @@ -123,7 +125,7 @@ export function CadModelContainer({ } function removeModel(): void { - if (model === undefined || !viewer.models.includes(model)) return; + if (!modelExists(model, viewer)) return; if (cachedViewerRef !== undefined && !cachedViewerRef.isRevealContainerMountedRef.current) return; @@ -153,3 +155,10 @@ async function applyStyling( } return collections; } + +function modelExists( + model: CogniteCadModel | undefined, + viewer: Cognite3DViewer +): model is CogniteCadModel { + return model !== undefined && viewer.models.includes(model); +} diff --git a/react-components/src/components/CameraController/CameraController.tsx b/react-components/src/components/CameraController/CameraController.tsx deleted file mode 100644 index 1b92df1bd4d..00000000000 --- a/react-components/src/components/CameraController/CameraController.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/*! - * Copyright 2023 Cognite AS - */ -import { type ReactElement, useEffect, useContext, useRef } from 'react'; -import { useReveal } from '../RevealContainer/RevealContext'; -import { ModelsLoadingStateContext } from '../Reveal3DResources/ModelsLoadingContext'; -import { - DefaultCameraManager, - type CameraControlsOptions, - type CameraState -} from '@cognite/reveal'; - -export type CameraControllerProps = { - initialFitCamera?: FittingStrategy; - cameraControlsOptions?: CameraControlsOptions; -}; - -type FittingStrategy = - | { to: 'cameraState'; state: CameraState } - | { to: 'allModels' } - | { to: 'none' }; - -export function CameraController({ - initialFitCamera, - cameraControlsOptions -}: CameraControllerProps): ReactElement { - const initialCameraSet = useRef(false); - const viewer = useReveal(); - const { modelsAdded } = useContext(ModelsLoadingStateContext); - - const fittingStrategy: Required = initialFitCamera ?? { to: 'allModels' }; - - useEffect(() => { - if (cameraControlsOptions === undefined) return; - - if (!(viewer.cameraManager instanceof DefaultCameraManager)) - throw new Error('CameraControlsOptions can be set only on default CameraManager'); - - viewer.cameraManager.setCameraControlsOptions(cameraControlsOptions); - }, [cameraControlsOptions]); - - useEffect(() => { - if (initialCameraSet.current) return; - if (fittingStrategy.to === 'none') { - initialCameraSet.current = true; - return; - } - if (fittingStrategy.to === 'cameraState') { - viewer.cameraManager.setCameraState(fittingStrategy.state); - initialCameraSet.current = true; - return; - } - if (fittingStrategy.to === 'allModels' && modelsAdded) { - viewer.fitCameraToModels(undefined, undefined, true); - initialCameraSet.current = true; - } - }, [modelsAdded]); - - return <>; -} diff --git a/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx b/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx index 7a10e872c37..96c9b860a7b 100644 --- a/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx +++ b/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx @@ -97,7 +97,7 @@ export function PointCloudContainer({ } function cleanStyling(): void { - if (model === undefined) return; + if (model === undefined || !viewer.models.includes(model)) return; model.setDefaultPointCloudAppearance(DefaultPointCloudAppearance); model.removeAllStyledObjectCollections(); diff --git a/react-components/src/components/Reveal3DResources/ModelsLoadingContext.ts b/react-components/src/components/Reveal3DResources/ModelsLoadingContext.ts deleted file mode 100644 index 6ee0a25325d..00000000000 --- a/react-components/src/components/Reveal3DResources/ModelsLoadingContext.ts +++ /dev/null @@ -1,13 +0,0 @@ -/*! - * Copyright 2023 Cognite AS - */ -import { createContext } from 'react'; - -export type ModelsLoadingState = { - modelsAdded: boolean; - setModelsAdded: (value: boolean) => void; -}; -export const ModelsLoadingStateContext = createContext({ - modelsAdded: false, - setModelsAdded: () => {} -}); diff --git a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx index 88eba782bde..17fe396efb0 100644 --- a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx +++ b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx @@ -1,9 +1,8 @@ /*! * Copyright 2023 Cognite AS */ -import { useRef, type ReactElement, useContext, useState, useEffect } from 'react'; +import { useRef, type ReactElement, useState, useEffect } from 'react'; import { type Cognite3DViewer, type PointerEventData } from '@cognite/reveal'; -import { ModelsLoadingStateContext } from './ModelsLoadingContext'; import { CadModelContainer, type CadModelStyling } from '../CadModelContainer/CadModelContainer'; import { PointCloudContainer, @@ -27,11 +26,11 @@ export const Reveal3DResources = ({ resources, defaultResourceStyling, instanceStyling, - onNodeClick + onNodeClick, + onResourcesAdded }: Reveal3DResourcesProps): ReactElement => { const [reveal3DModels, setReveal3DModels] = useState([]); - const { setModelsAdded } = useContext(ModelsLoadingStateContext); const viewer = useReveal(); const fdmSdk = useFdmSdk(); const client = useSDK(); @@ -73,8 +72,8 @@ export const Reveal3DResources = ({ const onModelLoaded = (): void => { numModelsLoaded.current += 1; - if (numModelsLoaded.current === resources.length) { - setModelsAdded(true); + if (numModelsLoaded.current === resources.length && onResourcesAdded !== undefined) { + onResourcesAdded(); } }; diff --git a/react-components/src/components/Reveal3DResources/types.ts b/react-components/src/components/Reveal3DResources/types.ts index d6c06ff69f0..3ca1b91216e 100644 --- a/react-components/src/components/Reveal3DResources/types.ts +++ b/react-components/src/components/Reveal3DResources/types.ts @@ -47,4 +47,5 @@ export type Reveal3DResourcesProps = { defaultResourceStyling?: DefaultResourceStyling; instanceStyling?: FdmAssetStylingGroup[]; onNodeClick?: (node: Promise) => void; + onResourcesAdded?: () => void; }; diff --git a/react-components/src/components/RevealContainer/RevealContainer.tsx b/react-components/src/components/RevealContainer/RevealContainer.tsx index bf2401ee122..825214d4308 100644 --- a/react-components/src/components/RevealContainer/RevealContainer.tsx +++ b/react-components/src/components/RevealContainer/RevealContainer.tsx @@ -7,7 +7,6 @@ import { createPortal } from 'react-dom'; import { Cognite3DViewer, type Cognite3DViewerOptions } from '@cognite/reveal'; import { RevealContext } from './RevealContext'; import { type Color } from 'three'; -import { ModelsLoadingStateContext } from '../Reveal3DResources/ModelsLoadingContext'; import { SDKProvider } from './SDKProvider'; import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; import { useRevealKeepAlive } from '../RevealKeepAlive/RevealKeepAliveContext'; @@ -78,9 +77,7 @@ export function RevealContainer({ <> - - {createPortal(children, viewerDomElement.current)} - + {createPortal(children, viewerDomElement.current)} @@ -104,13 +101,3 @@ export function RevealContainer({ return viewer; } } - -function ModelsLoadingProvider({ children }: { children?: ReactNode }): ReactElement { - const [modelsLoading, setModelsLoading] = useState(false); - return ( - - {children} - - ); -} diff --git a/react-components/src/components/RevealToolbar/FitModelsButton.tsx b/react-components/src/components/RevealToolbar/FitModelsButton.tsx index 242b4e2c662..00d3fbf78f7 100644 --- a/react-components/src/components/RevealToolbar/FitModelsButton.tsx +++ b/react-components/src/components/RevealToolbar/FitModelsButton.tsx @@ -4,21 +4,15 @@ import { type ReactElement, useCallback } from 'react'; -import { Box3 } from 'three'; - -import { useReveal } from '../RevealContainer/RevealContext'; import { Button } from '@cognite/cogs.js'; +import { useCameraNavigation } from '../../hooks/useCameraNavigation'; export const FitModelsButton = (): ReactElement => { - const viewer = useReveal(); + const cameraNavigation = useCameraNavigation(); const updateCamera = useCallback(() => { - const box = new Box3(); - - viewer.models.forEach((model) => box.union(model.getModelBoundingBox())); - - viewer.cameraManager.fitCameraToBoundingBox(box); - }, [viewer, ...viewer.models]); + cameraNavigation.fitCameraToAllModels(); + }, []); return (