Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(react-components): clicked node hook #3557

Merged
merged 27 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ba0494b
feat: useClickedNode hook
haakonflatval-cognite Aug 7, 2023
578a99c
feat(WIP): ClickedNode context
haakonflatval-cognite Aug 7, 2023
16a1f06
chore: lint fix
haakonflatval-cognite Aug 7, 2023
9583185
refactor: make data query function into a hook
haakonflatval-cognite Aug 8, 2023
6732615
feat: introduce `useClickedNode` hook instead of higher-order component
haakonflatval-cognite Aug 8, 2023
ad72a6f
fix: correct query hash keys
haakonflatval-cognite Aug 8, 2023
2592d97
chore: make sure story hook doesn't do unnecessary updates
haakonflatval-cognite Aug 8, 2023
007a7d6
Merge branch 'master' into hflatval/clicked-node-hook
haakonflatval-cognite Aug 8, 2023
d4bb156
chore: lint fix
haakonflatval-cognite Aug 8, 2023
7e4aeb2
fix: return promise as promised
haakonflatval-cognite Aug 8, 2023
765a791
refactor: refactor hook to not deal with intersection logic
haakonflatval-cognite Aug 8, 2023
e461cab
chore: lint fix
haakonflatval-cognite Aug 9, 2023
291ad0e
fix: build fix
haakonflatval-cognite Aug 9, 2023
bcbf697
chore: remove unnecessary check
haakonflatval-cognite Aug 9, 2023
0b6008d
chore: remove unused comments
haakonflatval-cognite Aug 9, 2023
53e0d05
refactor: rename to useClickedNodeData
haakonflatval-cognite Aug 14, 2023
c09d3e0
fix: avoid stringification of circular JSON struct
haakonflatval-cognite Aug 14, 2023
a17c851
refactor: combine all react queries into one
haakonflatval-cognite Aug 14, 2023
8831e63
chore: Remove new story
haakonflatval-cognite Aug 14, 2023
c6a294a
Merge branch 'master' into hflatval/clicked-node-hook
haakonflatval-cognite Aug 14, 2023
4abbbf2
fix: correct use of space names and properties
haakonflatval-cognite Aug 14, 2023
a0b9c99
chore: lint fix
haakonflatval-cognite Aug 14, 2023
576d10f
Merge branch 'master' into hflatval/clicked-node-hook
haakonflatval-cognite Aug 15, 2023
b3daabb
fix: add zoom-to-clicked in HighlightNode
haakonflatval-cognite Aug 15, 2023
8893ab6
chore: lint fix
haakonflatval-cognite Aug 15, 2023
201fbb1
chore: remove old `queryMappedData` file
haakonflatval-cognite Aug 16, 2023
bb2550f
Merge master into hflatval/clicked-node-hook
cognite-bulldozer[bot] Aug 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
* Copyright 2023 Cognite AS
*/
import { useRef, type ReactElement, useContext, useState, useEffect } from 'react';
import { type NodeAppearance, type Cognite3DViewer, type PointerEventData } from '@cognite/reveal';
import {
type NodeAppearance,
type Cognite3DViewer,
type PointCloudAppearance
} from '@cognite/reveal';
import { ModelsLoadingStateContext } from './ModelsLoadingContext';
import { CadModelContainer, type CadModelStyling } from '../CadModelContainer/CadModelContainer';
import {
Expand All @@ -22,6 +26,7 @@ import { queryMappedData } from './queryMappedData';
import { useFdmSdk, useSDK } from '../RevealContainer/SDKProvider';
import { useCalculateModelsStyling } from '../../hooks/useCalculateModelsStyling';
import { type DmsUniqueIdentifier } from '../../utilities/FdmSDK';
import { useClickedNodeData } from '../..';

export type FdmAssetStylingGroup = {
fdmAssetExternalIds: DmsUniqueIdentifier[];
Expand All @@ -43,29 +48,20 @@ export const Reveal3DResources = ({

const { setModelsAdded } = useContext(ModelsLoadingStateContext);
const viewer = useReveal();
const fdmSdk = useFdmSdk();
const client = useSDK();
const numModelsLoaded = useRef(0);

useEffect(() => {
getTypedModels(resources, viewer).then(setReveal3DModels).catch(console.error);
}, [resources, viewer]);

const reveal3DModelsStyling = useCalculateModelsStyling(reveal3DModels, instanceStyling ?? []);
const clickedNodeData = useClickedNodeData();

useEffect(() => {
const callback = (event: PointerEventData): void => {
if (onNodeClick === undefined) return;
const data = queryMappedData(viewer, client, fdmSdk, event);
onNodeClick(data);
};

viewer.on('click', callback);

return () => {
viewer.off('click', callback);
};
}, [viewer, client, fdmSdk, onNodeClick]);
if (clickedNodeData !== undefined) {
onNodeClick?.(Promise.resolve(clickedNodeData));
}
}, [clickedNodeData, onNodeClick]);

const image360CollectionAddOptions = resources.filter(
(resource): resource is AddImageCollection360Options =>
Expand Down
9 changes: 2 additions & 7 deletions react-components/src/components/Reveal3DResources/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@
* Copyright 2023 Cognite AS
*/

import {
type AddModelOptions,
type SupportedModelTypes,
type CadIntersection,
type NodeAppearance
} from '@cognite/reveal';
import { NodeAppearance, type AddModelOptions, type SupportedModelTypes } from '@cognite/reveal';

import { type Matrix4 } from 'three';
import { type Source } from '../../utilities/FdmSDK';
import { type Node3D } from '@cognite/sdk/dist/src';
Expand All @@ -29,5 +25,4 @@ export type NodeDataResult = {
nodeExternalId: string;
view: Source;
cadNode: Node3D;
intersection: CadIntersection;
};
45 changes: 45 additions & 0 deletions react-components/src/hooks/useClickedNode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*!
* Copyright 2023 Cognite AS
*/

import { type CadIntersection, type PointerEventData } from '@cognite/reveal';
import { useReveal, type NodeDataResult } from '../';
import { useEffect, useState } from 'react';
import { useNodeMappedData } from './useNodeMappedData';

export type ClickedNodeData = NodeDataResult & {
intersection: CadIntersection;
};

export const useClickedNodeData = (): ClickedNodeData | undefined => {
const viewer = useReveal();

const [cadIntersection, setCadIntersection] = useState<CadIntersection | undefined>(undefined);

useEffect(() => {
const callback = (event: PointerEventData): void => {
void (async () => {
const intersection = await viewer.getIntersectionFromPixel(event.offsetX, event.offsetY);

if (intersection === null || intersection.type !== 'cad') {
return;
}

setCadIntersection(intersection);
})();
};

viewer.on('click', callback);

return () => {
viewer.off('click', callback);
};
}, [viewer]);

const nodeData = useNodeMappedData(cadIntersection?.treeIndex, cadIntersection?.model);

if (nodeData === undefined || cadIntersection === undefined) {
return undefined;
}
Savokr marked this conversation as resolved.
Show resolved Hide resolved
return { intersection: cadIntersection, ...nodeData };
};
155 changes: 155 additions & 0 deletions react-components/src/hooks/useNodeMappedData.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*!
* Copyright 2023 Cognite AS
*/

import { useQuery } from '@tanstack/react-query';

import { type CogniteCadModel } from '@cognite/reveal';
import { CogniteClient, type CogniteInternalId, type Node3D } from '@cognite/sdk';
import { type NodeDataResult } from '../components/Reveal3DResources/types';
import { useFdmSdk, useSDK } from '../components/RevealContainer/SDKProvider';

import assert from 'assert';
import {
FdmSDK,
type DmsUniqueIdentifier,
type EdgeItem,
type InspectResultList
} from '../utilities/FdmSDK';
import { INSTANCE_SPACE_3D_DATA, SYSTEM_3D_EDGE_SOURCE, SYSTEM_SPACE_3D_SCHEMA } from '../utilities/globalDataModels';

export const useNodeMappedData = (
treeIndex: number | undefined,
model: CogniteCadModel | undefined
): NodeDataResult | undefined => {

const cogniteClient = useSDK();
const fdmClient = useFdmSdk();

const mappedDataHashKey = `${model?.modelId}-${model?.revisionId}-${treeIndex}`;

const queryResult = useQuery(
['cdf', '3d', mappedDataHashKey],
async () => {

if (model === undefined || treeIndex === undefined) {
return null;
}

const ancestors = await fetchAncestorNodesForTreeIndex(model, treeIndex, cogniteClient);

if (ancestors.length === 0) {
return null;
}

const mappings = await fetchNodeMappingEdges(model, ancestors.map((n) => n.id), fdmClient);

const selectedEdge =
mappings !== undefined && mappings.edges.length > 0 ? mappings.edges[0] : undefined;

const selectedNodeId = selectedEdge?.properties.revisionNodeId;

const dataNode = selectedEdge?.startNode;

if (dataNode === undefined) {
return null;
}

const inspectionResult = await inspectNode(dataNode, fdmClient);

const dataView =
inspectionResult?.items[0]?.inspectionResults.involvedViewsAndContainers?.views[0];

if (dataView === undefined) {
return null;
}

const selectedNode = ancestors.find((n) => n.id === selectedNodeId)!;

return {
nodeExternalId: dataNode.externalId,
view: dataView,
cadNode: selectedNode
};
});

return queryResult.data ?? undefined;
};

async function fetchAncestorNodesForTreeIndex(
model: CogniteCadModel,
treeIndex: number,
cogniteClient: CogniteClient,
): Promise<Node3D[]> {

const nodeId = await model.mapTreeIndexToNodeId(treeIndex);

const ancestorNodes = await cogniteClient.revisions3D.list3DNodeAncestors(
model.modelId,
model.revisionId,
nodeId
);

return ancestorNodes.items;
}

async function fetchNodeMappingEdges(
model: CogniteCadModel,
ancestorIds: CogniteInternalId[],
fdmClient: FdmSDK
): Promise<{ edges: Array<EdgeItem<Record<string, any>>> } | undefined> {

assert(ancestorIds.length !== 0);

const filter = {
and: [
{
equals: {
property: ['edge', 'endNode'],
value: {
space: INSTANCE_SPACE_3D_DATA,
externalId: `${model.modelId}`
}
}
},
{
equals: {
property: [
SYSTEM_SPACE_3D_SCHEMA,
`${SYSTEM_3D_EDGE_SOURCE.externalId}/${SYSTEM_3D_EDGE_SOURCE.version}`,
'revisionId'
],
value: model.revisionId
}
},
{
in: {
property: [
SYSTEM_SPACE_3D_SCHEMA,
`${SYSTEM_3D_EDGE_SOURCE.externalId}/${SYSTEM_3D_EDGE_SOURCE.version}`,
'revisionNodeId'
],
values: ancestorIds
}
}
]
};

return fdmClient.filterAllInstances(filter, 'edge', SYSTEM_3D_EDGE_SOURCE);
}

async function inspectNode(dataNode: DmsUniqueIdentifier, fdmClient: FdmSDK): Promise<InspectResultList | undefined> {

const inspectionResult = await fdmClient.inspectInstances({
inspectionOperations: { involvedViewsAndContainers: {} },
items: [
{
instanceType: 'node',
externalId: dataNode.externalId,
space: dataNode.space
}
]
});

return inspectionResult;
}
1 change: 1 addition & 0 deletions react-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export { RevealKeepAlive } from './components/RevealKeepAlive/RevealKeepAlive';
export { useReveal } from './components/RevealContainer/RevealContext';
export { use3DModelName } from './hooks/use3DModelName';
export { useFdmAssetMappings } from './hooks/useFdmAssetMappings';
export { useClickedNodeData } from './hooks/useClickedNode';

// Higher order components
export { withSuppressRevealEvents } from './higher-order-components/withSuppressRevealEvents';
Expand Down
47 changes: 18 additions & 29 deletions react-components/stories/HighlightNode.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import {
RevealContainer,
RevealToolbar,
Reveal3DResources,
type NodeDataResult,
type AddResourceOptions,
CameraController,
type FdmAssetStylingGroup
useClickedNodeData,
FdmAssetStylingGroup,
} from '../src';
import { Color } from 'three';
import { type ReactElement, useState, useCallback, useRef } from 'react';
import { DefaultNodeAppearance, TreeIndexNodeCollection } from '@cognite/reveal';
import { type ReactElement, useState, useEffect, useRef } from 'react';
import { DefaultNodeAppearance } from '@cognite/reveal';
import { createSdkByUrlToken } from './utilities/createSdkByUrlToken';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

Expand Down Expand Up @@ -51,13 +51,13 @@ export const Main: Story = {
<RevealContainer sdk={sdk} color={new Color(0x4a4a4a)}>
<StoryContent resources={resources} />
<CameraController
initialFitCamera={{
to: 'allModels'
}}
cameraControlsOptions={{
changeCameraTargetOnClick: true,
mouseWheelAction: 'zoomToCursor'
}}
initialFitCamera={{
to: 'allModels'
}}
cameraControlsOptions={{
changeCameraTargetOnClick: true,
mouseWheelAction: 'zoomToCursor'
}}
/>
<ReactQueryDevtools />
</RevealContainer>
Expand All @@ -68,21 +68,11 @@ export const Main: Story = {
const StoryContent = ({ resources }: { resources: AddResourceOptions[] }): ReactElement => {
const [highlightedId, setHighlightedId] = useState<string | undefined>(undefined);
const stylingGroupsRef = useRef<FdmAssetStylingGroup[]>([]);
const callback = async (nodeData: Promise<NodeDataResult | undefined>): Promise<void> => {
const nodeDataResult = await nodeData;
setHighlightedId(nodeDataResult?.nodeExternalId);
const nodeData = useClickedNodeData();

if (nodeDataResult === undefined) return;

nodeDataResult.intersection.model.assignStyledNodeCollection(
new TreeIndexNodeCollection([nodeDataResult.cadNode.treeIndex]),
DefaultNodeAppearance.Highlighted
);
};

const onClick = useCallback((nodeData: Promise<NodeDataResult | undefined>) => {
void callback(nodeData);
}, []);
useEffect(() => {
setHighlightedId(nodeData?.nodeExternalId);
}, [nodeData?.nodeExternalId]);

if (stylingGroupsRef.current.length === 1) {
stylingGroupsRef.current.pop();
Expand All @@ -98,11 +88,10 @@ const StoryContent = ({ resources }: { resources: AddResourceOptions[] }): React
return (
<>
<Reveal3DResources
resources={resources}
instanceStyling={stylingGroupsRef.current}
onNodeClick={onClick}
resources={resources}
instanceStyling={stylingGroupsRef.current}
/>
<RevealToolbar />
</>
</>
);
};
Loading