From bfc25db7eb7813bf4dd0b56454702244619285d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Flatval?= <70905152+haakonflatval-cognite@users.noreply.github.com> Date: Fri, 4 Aug 2023 10:42:02 +0200 Subject: [PATCH 1/2] fix: viewer anchor adjustments (#3546) * chore: add suppression events targeting ViewerAnchor in example * fix: make sure ViewerAnchor appears when first mounted * fix: don't suppress up-events --- .../components/ViewerAnchor/ViewerAnchor.tsx | 29 ++++++++++++------- .../withSuppressRevealEvents.tsx | 2 -- .../stories/ViewerAnchor.stories.tsx | 14 +++++---- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/react-components/src/components/ViewerAnchor/ViewerAnchor.tsx b/react-components/src/components/ViewerAnchor/ViewerAnchor.tsx index d9f10f3167c..f254a681c9a 100644 --- a/react-components/src/components/ViewerAnchor/ViewerAnchor.tsx +++ b/react-components/src/components/ViewerAnchor/ViewerAnchor.tsx @@ -2,7 +2,7 @@ * Copyright 2023 Cognite AS */ -import { useEffect, useRef, type ReactElement, type RefObject, useState } from 'react'; +import { useEffect, useRef, type ReactElement, type RefObject, useState, useCallback } from 'react'; import { Vector2, type Vector3 } from 'three'; import { useReveal } from '../RevealContainer/RevealContext'; @@ -22,20 +22,29 @@ export const ViewerAnchor = ({ position, children }: ViewerAnchorProps): ReactEl const [divTranslation, setDivTranslation] = useState(new Vector2()); const [visible, setVisible] = useState(false); - const cameraChanged = (cameraPosition: Vector3, cameraTarget: Vector3): void => { - const cameraDirection = cameraTarget.clone().sub(cameraPosition).normalize(); - const elementDirection = position.clone().sub(cameraPosition).normalize(); + const cameraChanged = useCallback( + (cameraPosition: Vector3, cameraTarget: Vector3): void => { + const cameraDirection = cameraTarget.clone().sub(cameraPosition).normalize(); + const elementDirection = position.clone().sub(cameraPosition).normalize(); - setVisible(elementDirection.dot(cameraDirection) > 0); + setVisible(elementDirection.dot(cameraDirection) > 0); - const screenSpacePosition = viewer.worldToScreen(position.clone()); - if (screenSpacePosition !== null) { - setDivTranslation(screenSpacePosition); - } - }; + const screenSpacePosition = viewer.worldToScreen(position.clone()); + if (screenSpacePosition !== null) { + setDivTranslation(screenSpacePosition); + } + }, + [viewer, position] + ); useEffect(() => { viewer.cameraManager.on('cameraChange', cameraChanged); + + cameraChanged( + viewer.cameraManager.getCameraState().position, + viewer.cameraManager.getCameraState().target + ); + return () => { viewer.cameraManager.off('cameraChange', cameraChanged); }; diff --git a/react-components/src/higher-order-components/withSuppressRevealEvents.tsx b/react-components/src/higher-order-components/withSuppressRevealEvents.tsx index 48f44dfde6c..0e3d943b06b 100644 --- a/react-components/src/higher-order-components/withSuppressRevealEvents.tsx +++ b/react-components/src/higher-order-components/withSuppressRevealEvents.tsx @@ -22,13 +22,11 @@ export function withSuppressRevealEvents( } div.addEventListener('pointerdown', stopPropagation); - div.addEventListener('pointerup', stopPropagation); div.addEventListener('pointermove', stopPropagation); div.addEventListener('wheel', stopPropagation); return () => { div.removeEventListener('pointerdown', stopPropagation); - div.removeEventListener('pointerup', stopPropagation); div.removeEventListener('pointermove', stopPropagation); div.removeEventListener('wheel', stopPropagation); }; diff --git a/react-components/stories/ViewerAnchor.stories.tsx b/react-components/stories/ViewerAnchor.stories.tsx index aff81002cb2..1f94e52549a 100644 --- a/react-components/stories/ViewerAnchor.stories.tsx +++ b/react-components/stories/ViewerAnchor.stories.tsx @@ -2,10 +2,11 @@ * Copyright 2023 Cognite AS */ import type { Meta, StoryObj } from '@storybook/react'; -import { Reveal3DResources, RevealContainer } from '../src'; +import { Reveal3DResources, RevealContainer, withSuppressRevealEvents } from '../src'; import { Color, Vector3 } from 'three'; import { CameraController, ViewerAnchor } from '../src/'; import { createSdkByUrlToken } from './utilities/createSdkByUrlToken'; +import styled from 'styled-components'; const meta = { title: 'Example/ViewerAnchor', @@ -31,8 +32,9 @@ export const Main: Story = { ] }, render: ({ resources, styling, fdmAssetMappingConfig }) => { - const position = new Vector3(50, 30, 50); + const position = new Vector3(25, 0, -25); const position2 = new Vector3(); + const SuppressedDiv = withSuppressRevealEvents(styled.div``); return ( -

- This label is stuck at position {position.toArray().join(',')} -

+ +

This label is stuck at position {position.toArray().join(',')}

+
+

Date: Fri, 4 Aug 2023 11:04:11 +0200 Subject: [PATCH 2/2] feat(react-components): add pagination support for styling hook allowing styling more than 1000 nodes (#3544) * Working pagination for styling hook * Refactored styling hook * Refactored styling and assetMappings hooks --- .../PointCloudContainer.tsx | 2 - react-components/src/hooks/types.ts | 8 +- .../src/hooks/useCalculateModelsStyling.tsx | 119 +++++++---- .../src/hooks/useFdmAssetMappings.tsx | 45 ++-- .../useMappedEquipmentBy3DModelsList.tsx | 37 ++++ .../stories/HighlightNode.stories.tsx | 12 +- .../stories/ModelsStyling.stories.tsx | 193 ++++++++++++++++++ .../stories/Reveal3DResources.stories.tsx | 12 +- .../stories/utilities/fdmConfig.ts | 15 ++ .../stories/utilities/is3DModelOptions.ts | 13 ++ 10 files changed, 372 insertions(+), 84 deletions(-) create mode 100644 react-components/src/hooks/useMappedEquipmentBy3DModelsList.tsx create mode 100644 react-components/stories/ModelsStyling.stories.tsx create mode 100644 react-components/stories/utilities/fdmConfig.ts create mode 100644 react-components/stories/utilities/is3DModelOptions.ts diff --git a/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx b/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx index f5feb0d0efa..989b937f5e3 100644 --- a/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx +++ b/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx @@ -63,8 +63,6 @@ export function PointCloudContainer({ async function addModel(modelId: number, revisionId: number, transform?: Matrix4): Promise { const pointCloudModel = await viewer.addPointCloudModel({ modelId, revisionId }); - viewer.fitCameraToModel(pointCloudModel); - if (transform !== undefined) { pointCloudModel.setModelTransformation(transform); } diff --git a/react-components/src/hooks/types.ts b/react-components/src/hooks/types.ts index 95253774f16..fb7a8ad1d0c 100644 --- a/react-components/src/hooks/types.ts +++ b/react-components/src/hooks/types.ts @@ -1,6 +1,7 @@ /*! * Copyright 2023 Cognite AS */ +import { type CogniteExternalId, type CogniteInternalId } from '@cognite/sdk'; import { type Source } from '../utilities/FdmSDK'; export type FdmAssetMappingsConfig = { @@ -22,5 +23,10 @@ export type FdmAssetMappingsConfig = { export type ThreeDModelMappings = { modelId: number; revisionId: number; - mappings: Array<{ nodeId: number; externalId: string }>; + mappings: Map; +}; + +export type Model3DEdgeProperties = { + revisionId: number; + revisionNodeId: number; }; diff --git a/react-components/src/hooks/useCalculateModelsStyling.tsx b/react-components/src/hooks/useCalculateModelsStyling.tsx index 586fb05f35d..dba8e7849a5 100644 --- a/react-components/src/hooks/useCalculateModelsStyling.tsx +++ b/react-components/src/hooks/useCalculateModelsStyling.tsx @@ -1,16 +1,14 @@ /*! * Copyright 2023 Cognite AS */ -import { useMemo } from 'react'; -import { type FdmAssetMappingsConfig } from './types'; +import { useEffect, useMemo } from 'react'; +import { type ThreeDModelMappings, type FdmAssetMappingsConfig } from './types'; import { type Reveal3DResourcesStyling } from '../components/Reveal3DResources/Reveal3DResources'; import { type TypedReveal3DModel } from '../components/Reveal3DResources/types'; import { useFdmAssetMappings } from './useFdmAssetMappings'; import { type PointCloudModelStyling } from '../components/PointCloudContainer/PointCloudContainer'; -import { - type CadModelStyling, - type NodeStylingGroup -} from '../components/CadModelContainer/CadModelContainer'; +import { type CadModelStyling } from '../components/CadModelContainer/CadModelContainer'; +import { type CogniteExternalId, type CogniteInternalId } from '@cognite/sdk'; /** * Calculates the styling for the models based on the styling configuration and the mappings. @@ -29,49 +27,30 @@ export const useCalculateModelsStyling = ( [styling] ); - const { data: mappings } = useFdmAssetMappings(stylingExternalIds, fdmAssetMappingConfig); + const { + data: mappings, + hasNextPage, + fetchNextPage, + isFetchingNextPage + } = useFdmAssetMappings(stylingExternalIds, fdmAssetMappingConfig); + + useEffect(() => { + if (hasNextPage !== undefined && !isFetchingNextPage) { + void fetchNextPage(); + } + }, [hasNextPage, fetchNextPage]); const modelsStyling = useMemo(() => { if (styling === undefined || models === undefined) return []; + const allPagesMappings = mappings?.pages.flatMap((page) => page.items); + const internalModelsStyling = models.map((model) => { let modelStyling: PointCloudModelStyling | CadModelStyling; switch (model.type) { case 'cad': { - const modelNodeMappings = mappings?.find( - (mapping) => - mapping.modelId === model.modelId && mapping.revisionId === model.revisionId - ); - - const newStylingGroups: NodeStylingGroup[] | undefined = - styling.groups !== null ? [] : undefined; - - styling.groups?.forEach((group) => { - const connectedExternalIds = group.fdmAssetExternalIds.filter( - (externalId) => - modelNodeMappings?.mappings.some( - (modelNodeMapping) => modelNodeMapping.externalId === externalId - ) - ); - - const newGroup: NodeStylingGroup = { - style: group.style.cad, - nodeIds: connectedExternalIds.map((externalId) => { - const mapping = modelNodeMappings?.mappings.find( - (mapping) => mapping.externalId === externalId - ); - return mapping?.nodeId ?? -1; - }) - }; - - if (connectedExternalIds.length > 0) newStylingGroups?.push(newGroup); - }); - - modelStyling = { - defaultStyle: styling.defaultStyle?.cad, - groups: newStylingGroups - }; + modelStyling = calculateCadModelStyling(styling, allPagesMappings, model); break; } case 'pointcloud': { @@ -94,3 +73,63 @@ export const useCalculateModelsStyling = ( return modelsStyling; }; + +function getModelMappings( + mappings: ThreeDModelMappings[] | undefined, + model: TypedReveal3DModel +): Map | undefined { + return mappings + ?.filter( + (mapping) => mapping.modelId === model.modelId && mapping.revisionId === model.revisionId + ) + .reduce( + (acc, mapping) => { + // reduce is added to avoid duplicate models from several pages. + mergeMaps(acc.mappings, mapping.mappings); + return acc; + }, + { modelId: model.modelId, revisionId: model.revisionId, mappings: new Map() } + ).mappings; +} + +function calculateCadModelStyling( + styling: Reveal3DResourcesStyling, + mappings: ThreeDModelMappings[] | undefined, + model: TypedReveal3DModel +): CadModelStyling { + const modelMappings = getModelMappings(mappings, model); + + const resourcesStylingGroups = styling?.groups; + + if (resourcesStylingGroups === undefined || modelMappings === undefined) + return { + defaultStyle: styling.defaultStyle?.cad + }; + + const modelStylingGroups = resourcesStylingGroups + .map((resourcesGroup) => { + const modelMappedNodeIds = resourcesGroup.fdmAssetExternalIds + .map((externalId) => modelMappings.get(externalId)) + .filter((nodeId): nodeId is number => nodeId !== undefined); + + return { + style: resourcesGroup.style.cad, + nodeIds: modelMappedNodeIds + }; + }) + .filter((group) => group.nodeIds.length > 0); + + return { + defaultStyle: styling.defaultStyle?.cad, + groups: modelStylingGroups + }; +} + +function mergeMaps( + targetMap: Map, + addedMap: Map +): Map { + addedMap.forEach((value, key) => targetMap.set(key, value)); + + return targetMap; +} diff --git a/react-components/src/hooks/useFdmAssetMappings.tsx b/react-components/src/hooks/useFdmAssetMappings.tsx index 2caec424f02..d255b465b12 100644 --- a/react-components/src/hooks/useFdmAssetMappings.tsx +++ b/react-components/src/hooks/useFdmAssetMappings.tsx @@ -3,8 +3,12 @@ */ import { type CogniteExternalId } from '@cognite/sdk'; import { useFdmSdk } from '../components/RevealContainer/SDKProvider'; -import { type UseQueryResult, useQuery } from '@tanstack/react-query'; -import { type FdmAssetMappingsConfig, type ThreeDModelMappings } from './types'; +import { useInfiniteQuery, type UseInfiniteQueryResult } from '@tanstack/react-query'; +import { + type Model3DEdgeProperties, + type FdmAssetMappingsConfig, + type ThreeDModelMappings +} from './types'; import { DEFAULT_QUERY_STALE_TIME } from '../utilities/constants'; /** @@ -13,13 +17,13 @@ import { DEFAULT_QUERY_STALE_TIME } from '../utilities/constants'; export const useFdmAssetMappings = ( fdmAssetExternalIds: CogniteExternalId[], fdmConfig?: FdmAssetMappingsConfig -): UseQueryResult => { +): UseInfiniteQueryResult<{ items: ThreeDModelMappings[]; nextCursor: string }> => { const fdmSdk = useFdmSdk(); - return useQuery( + return useInfiniteQuery( ['reveal', 'react-components', fdmAssetExternalIds], - async () => { - if (fdmAssetExternalIds?.length === 0) return []; + async ({ pageParam }) => { + if (fdmAssetExternalIds?.length === 0) return { items: [], nextCursor: undefined }; if (fdmConfig === undefined) throw Error('FDM config must be defined when using FDM asset mappings'); @@ -36,16 +40,16 @@ export const useFdmAssetMappings = ( const instances = await fdmSdk.filterInstances( fdmAssetMappingFilter, 'edge', - fdmConfig.source + fdmConfig.source, + pageParam ); const modelMappingsTemp: ThreeDModelMappings[] = []; instances.edges.forEach((instance) => { - const mappingProperty = - instance.properties[fdmConfig.source.space][ - `${fdmConfig.source.externalId}/${fdmConfig.source.version}` - ]; + const mappingProperty = instance.properties[fdmConfig.source.space][ + `${fdmConfig.source.externalId}/${fdmConfig.source.version}` + ] as Model3DEdgeProperties; const modelId = Number.parseInt(instance.endNode.externalId.slice(9)); const revisionId = mappingProperty.revisionId; @@ -55,27 +59,28 @@ export const useFdmAssetMappings = ( ); if (!isAdded) { + const mappingsMap = new Map(); + mappingsMap.set(instance.startNode.externalId, mappingProperty.revisionNodeId); + modelMappingsTemp.push({ modelId, revisionId, - mappings: [ - { nodeId: mappingProperty.revisionNodeId, externalId: instance.startNode.externalId } - ] + mappings: mappingsMap }); } else { const modelMapping = modelMappingsTemp.find( (mapping) => mapping.modelId === modelId && mapping.revisionId === revisionId ); - modelMapping?.mappings.push({ - nodeId: mappingProperty.revisionNodeId, - externalId: instance.startNode.externalId - }); + modelMapping?.mappings.set(instance.startNode.externalId, mappingProperty.revisionNodeId); } }); - return modelMappingsTemp; + return { items: modelMappingsTemp, nextCursor: instances.nextCursor }; }, - { staleTime: DEFAULT_QUERY_STALE_TIME } + { + staleTime: DEFAULT_QUERY_STALE_TIME, + getNextPageParam: (lastPage) => lastPage.nextCursor + } ); }; diff --git a/react-components/src/hooks/useMappedEquipmentBy3DModelsList.tsx b/react-components/src/hooks/useMappedEquipmentBy3DModelsList.tsx new file mode 100644 index 00000000000..fd85cc39dad --- /dev/null +++ b/react-components/src/hooks/useMappedEquipmentBy3DModelsList.tsx @@ -0,0 +1,37 @@ +/*! + * Copyright 2023 Cognite AS + */ +import { type UseQueryResult, useQuery } from '@tanstack/react-query'; +import { type FdmAssetMappingsConfig } from '..'; +import { useFdmSdk } from '../components/RevealContainer/SDKProvider'; + +export const useMappedEquipmentBy3DModelsList = ( + Default3DFdmConfig: FdmAssetMappingsConfig, + modelsList: Array<{ modelId: number; revisionId: number }> = [] +): UseQueryResult => { + const fdmClient = useFdmSdk(); + + return useQuery( + ['reveal', 'react-components', ...modelsList.map(({ modelId }) => modelId.toString()).sort()], + async () => { + const filter = { + in: { + property: ['edge', 'endNode'], + values: modelsList.map(({ modelId }) => ({ + space: Default3DFdmConfig.global3dSpace, + externalId: `model_3d_${modelId}` + })) + } + }; + + const mappings = await fdmClient.filterAllInstances( + filter, + 'edge', + Default3DFdmConfig.source + ); + + return mappings.edges.map((edge) => edge.startNode.externalId); + }, + { staleTime: Infinity } + ); +}; diff --git a/react-components/stories/HighlightNode.stories.tsx b/react-components/stories/HighlightNode.stories.tsx index 4b538d94a7a..cbfa8b459a4 100644 --- a/react-components/stories/HighlightNode.stories.tsx +++ b/react-components/stories/HighlightNode.stories.tsx @@ -15,17 +15,7 @@ import { Color, Matrix4 } from 'three'; import { type ReactElement, useState } from 'react'; import { DefaultNodeAppearance, TreeIndexNodeCollection } from '@cognite/reveal'; import { createSdkByUrlToken } from './utilities/createSdkByUrlToken'; - -const DefaultFdmConfig: FdmAssetMappingsConfig = { - source: { - space: 'hf_3d_schema', - version: '1', - type: 'view', - externalId: 'cdf_3d_connection_data' - }, - global3dSpace: 'hf_3d_global_data', - assetFdmSpace: 'hf_customer_a' -}; +import { DefaultFdmConfig } from './utilities/fdmConfig'; const meta = { title: 'Example/HighlightNode', diff --git a/react-components/stories/ModelsStyling.stories.tsx b/react-components/stories/ModelsStyling.stories.tsx new file mode 100644 index 00000000000..3ded0012230 --- /dev/null +++ b/react-components/stories/ModelsStyling.stories.tsx @@ -0,0 +1,193 @@ +/*! + * Copyright 2023 Cognite AS + */ +import type { Meta, StoryObj } from '@storybook/react'; +import { + type AddReveal3DModelOptions, + Reveal3DResources, + type Reveal3DResourcesProps, + type Reveal3DResourcesStyling, + RevealContainer +} from '../src'; +import { Color, Matrix4 } from 'three'; +import { CameraController } from '../src/'; +import { createSdkByUrlToken } from './utilities/createSdkByUrlToken'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { DefaultFdmConfig } from './utilities/fdmConfig'; +import { type ReactElement, useMemo } from 'react'; +import { useMappedEquipmentBy3DModelsList } from '../src/hooks/useMappedEquipmentBy3DModelsList'; +import { is3DModelOptions } from './utilities/is3DModelOptions'; + +const meta = { + title: 'Example/ModelsStyling', + component: Reveal3DResources, + tags: ['autodocs'], + argTypes: { + styling: { + description: 'Styling of all models', + options: ['RedCad', 'BlueMapped', 'GreenRedAssetMapped', 'None'], + control: { + type: 'radio' + }, + label: 'Styling of models', + mapping: { + RedCad: { + defaultStyle: { + cad: { color: new Color('red') } + } + }, + BlueMapped: { + groups: [ + { + fdmAssetExternalIds: ['allMappings'], + style: { + cad: { + color: new Color('blue') + } + } + } + ] + }, + GreenRedAssetMapped: { + defaultStyle: { + cad: { color: new Color('white') } + }, + groups: [ + { + fdmAssetExternalIds: ['halfMappings'], + style: { + cad: { + color: new Color('green') + } + } + } + ] + }, + None: {} + } + } + } +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const sdk = createSdkByUrlToken(); + +export const Main: Story = { + args: { + resources: [ + { + modelId: 2551525377383868, + revisionId: 2143672450453400, + transform: new Matrix4().makeTranslation(-340, -480, 80) + } + ], + styling: {}, + fdmAssetMappingConfig: DefaultFdmConfig + }, + render: ({ resources, styling, fdmAssetMappingConfig }) => { + return ( + } + viewerOptions={{ + loadingIndicatorStyle: { + opacity: 1, + placement: 'topRight' + } + }}> + + + + ); + } +}; + +const StyledReveal3DResources = (props: Reveal3DResourcesProps): ReactElement => { + const filtered = props.resources?.filter( + (resource): resource is AddReveal3DModelOptions => is3DModelOptions(resource) + ); + + const { data } = useMappedEquipmentBy3DModelsList(props.fdmAssetMappingConfig, filtered); + + const styling = useMemo(() => { + const stylingMode = props?.styling?.groups?.[0].fdmAssetExternalIds[0]; + + let newStyling: Reveal3DResourcesStyling = {}; + + switch (stylingMode) { + case 'allMappings': + newStyling = { + defaultStyle: { + cad: { + color: new Color('white') + } + }, + groups: [ + { + style: { + cad: { + color: new Color('blue') + } + }, + fdmAssetExternalIds: data ?? [] + } + ] + }; + break; + case 'halfMappings': + newStyling = { + defaultStyle: { + cad: { + color: new Color('white') + } + }, + groups: [ + { + style: { + cad: { + color: new Color('red') + } + }, + fdmAssetExternalIds: data?.slice(0, Math.floor(data.length / 2)) ?? [] + }, + { + style: { + cad: { + color: new Color('green') + } + }, + fdmAssetExternalIds: data?.slice(Math.floor(data.length / 2)) ?? [] + } + ] + }; + break; + default: + newStyling = props.styling ?? {}; + } + + return newStyling; + }, [props.styling, data]); + + return ( + + ); +}; diff --git a/react-components/stories/Reveal3DResources.stories.tsx b/react-components/stories/Reveal3DResources.stories.tsx index d4a45dfed52..793d1246974 100644 --- a/react-components/stories/Reveal3DResources.stories.tsx +++ b/react-components/stories/Reveal3DResources.stories.tsx @@ -7,6 +7,7 @@ import { Color, Matrix4 } from 'three'; import { CameraController } from '../src/'; import { createSdkByUrlToken } from './utilities/createSdkByUrlToken'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { DefaultFdmConfig } from './utilities/fdmConfig'; const meta = { title: 'Example/Reveal3DResources', @@ -108,16 +109,7 @@ export const Main: Story = { } ], styling: {}, - fdmAssetMappingConfig: { - source: { - space: 'fdm-3d-test-savelii', - version: '1', - type: 'view', - externalId: 'CDF_3D_Connection_Data' - }, - assetFdmSpace: 'bark-corporation', - global3dSpace: 'hf_3d_global_data' - } + fdmAssetMappingConfig: DefaultFdmConfig }, render: ({ resources, styling, fdmAssetMappingConfig }) => { return ( diff --git a/react-components/stories/utilities/fdmConfig.ts b/react-components/stories/utilities/fdmConfig.ts new file mode 100644 index 00000000000..99bd8eb189e --- /dev/null +++ b/react-components/stories/utilities/fdmConfig.ts @@ -0,0 +1,15 @@ +/*! + * Copyright 2023 Cognite AS + */ +import { type FdmAssetMappingsConfig } from '../../src'; + +export const DefaultFdmConfig: FdmAssetMappingsConfig = { + source: { + space: 'hf_3d_schema', + version: '1', + type: 'view', + externalId: 'Cdf3dConnectionData' + }, + global3dSpace: 'hf_3d_global_data', + assetFdmSpace: 'hf_customer_a' +}; diff --git a/react-components/stories/utilities/is3DModelOptions.ts b/react-components/stories/utilities/is3DModelOptions.ts new file mode 100644 index 00000000000..d332a0857da --- /dev/null +++ b/react-components/stories/utilities/is3DModelOptions.ts @@ -0,0 +1,13 @@ +/*! + * Copyright 2023 Cognite AS + */ +import { type AddResourceOptions, type AddReveal3DModelOptions } from '../../src'; + +export function is3DModelOptions( + threeDResource: AddResourceOptions +): threeDResource is AddReveal3DModelOptions { + return ( + (threeDResource as AddReveal3DModelOptions).modelId !== undefined && + (threeDResource as AddReveal3DModelOptions).revisionId !== undefined + ); +}