Skip to content

Commit

Permalink
feat(react-components): add pagination support for styling hook allow…
Browse files Browse the repository at this point in the history
…ing styling more than 1000 nodes (#3544)

* Working pagination for styling hook

* Refactored styling hook

* Refactored styling and assetMappings hooks
  • Loading branch information
Savokr authored Aug 4, 2023
1 parent bfc25db commit 81e6f6c
Show file tree
Hide file tree
Showing 10 changed files with 372 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ export function PointCloudContainer({
async function addModel(modelId: number, revisionId: number, transform?: Matrix4): Promise<void> {
const pointCloudModel = await viewer.addPointCloudModel({ modelId, revisionId });

viewer.fitCameraToModel(pointCloudModel);

if (transform !== undefined) {
pointCloudModel.setModelTransformation(transform);
}
Expand Down
8 changes: 7 additions & 1 deletion react-components/src/hooks/types.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -22,5 +23,10 @@ export type FdmAssetMappingsConfig = {
export type ThreeDModelMappings = {
modelId: number;
revisionId: number;
mappings: Array<{ nodeId: number; externalId: string }>;
mappings: Map<CogniteExternalId, CogniteInternalId>;
};

export type Model3DEdgeProperties = {
revisionId: number;
revisionNodeId: number;
};
119 changes: 79 additions & 40 deletions react-components/src/hooks/useCalculateModelsStyling.tsx
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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': {
Expand All @@ -94,3 +73,63 @@ export const useCalculateModelsStyling = (

return modelsStyling;
};

function getModelMappings(
mappings: ThreeDModelMappings[] | undefined,
model: TypedReveal3DModel
): Map<CogniteExternalId, CogniteInternalId> | 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<string, number>() }
).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<string, number>,
addedMap: Map<string, number>
): Map<string, number> {
addedMap.forEach((value, key) => targetMap.set(key, value));

return targetMap;
}
45 changes: 25 additions & 20 deletions react-components/src/hooks/useFdmAssetMappings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand All @@ -13,13 +17,13 @@ import { DEFAULT_QUERY_STALE_TIME } from '../utilities/constants';
export const useFdmAssetMappings = (
fdmAssetExternalIds: CogniteExternalId[],
fdmConfig?: FdmAssetMappingsConfig
): UseQueryResult<ThreeDModelMappings[]> => {
): 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');

Expand All @@ -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;
Expand All @@ -55,27 +59,28 @@ export const useFdmAssetMappings = (
);

if (!isAdded) {
const mappingsMap = new Map<string, number>();
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
}
);
};
37 changes: 37 additions & 0 deletions react-components/src/hooks/useMappedEquipmentBy3DModelsList.tsx
Original file line number Diff line number Diff line change
@@ -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<string[]> => {
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 }
);
};
12 changes: 1 addition & 11 deletions react-components/stories/HighlightNode.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading

0 comments on commit 81e6f6c

Please sign in to comment.