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: Info card overlay #3521

Merged
merged 8 commits into from
Jul 31, 2023
Merged
60 changes: 60 additions & 0 deletions react-components/src/components/InfoCard/InfoCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*!
* Copyright 2023 Cognite AS
*/

import React, { type JSX, useEffect, useRef } from 'react';
import { type Vector3 } from 'three';

import { useReveal } from '../RevealContainer/RevealContext';

import { HtmlOverlayTool } from '@cognite/reveal/tools';
import { useAuxillaryDivContext } from '../RevealContainer/AuxillaryDivProvider';

export type InfoCardElementMapping = {
ref: React.RefObject<HTMLElement>;
position: Vector3;
};

export type InfoCardProps = {
position: Vector3;
children: React.ReactNode;
haakonflatval-cognite marked this conversation as resolved.
Show resolved Hide resolved
uniqueKey: string;
};

export const InfoCard = ({ position, children, uniqueKey }: InfoCardProps): JSX.Element => {
haakonflatval-cognite marked this conversation as resolved.
Show resolved Hide resolved
const viewer = useReveal();

const htmlTool = useRef<HtmlOverlayTool>(new HtmlOverlayTool(viewer));

const auxContext = useAuxillaryDivContext();

const htmlRef = useRef<HTMLDivElement>(null);
const element = (
<div className="infocard" key={uniqueKey} ref={htmlRef} style={{ position: 'absolute' }}>
{children}
</div>
);

useEffect(() => {
auxContext.addElement(element);
return () => {
auxContext.removeElement(element);
};
}, []);

useEffect(() => {
if (htmlRef.current === null) {
return;
}

const elementRef = htmlRef.current;

htmlTool.current.add(elementRef, position);

return () => {
htmlTool.current.remove(elementRef);
};
}, [auxContext, children, htmlRef.current]);

return <></>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*!
haakonflatval-cognite marked this conversation as resolved.
Show resolved Hide resolved
* Copyright 2023 Cognite AS
*/

import React, { createContext, useContext, useState, useCallback, type JSX } from 'react';

export const AuxillaryDivProvider = ({ children }: { children: React.ReactNode }): JSX.Element => {
haakonflatval-cognite marked this conversation as resolved.
Show resolved Hide resolved
const [elements, setElements] = useState<JSX.Element[]>([]);

// Maintain a local copy of the elements, needed for properly supporting multiple
// `addElement` calls between rerenders
let cachedElements = elements;

const addElement = useCallback(
(element: JSX.Element) => {
const newElementList = [...cachedElements, element];

setElements(newElementList);
cachedElements = newElementList;
},
[elements, setElements]
);

const removeElement = useCallback(
(element: JSX.Element) => {
const newElementList = cachedElements.filter((e) => e !== element);

setElements(newElementList);
cachedElements = newElementList;
},
[elements, setElements]
);

return (
<>
<AuxillaryDivContext.Provider value={{ addElement, removeElement }}>
<div>{elements}</div>
{children}
</AuxillaryDivContext.Provider>
</>
);
};

type AuxillaryContextData = {
addElement: (element: JSX.Element) => void;
removeElement: (element: JSX.Element) => void;
};

const AuxillaryDivContext = createContext<AuxillaryContextData | null>(null);

export const useAuxillaryDivContext = (): AuxillaryContextData => {
const auxContext = useContext(AuxillaryDivContext);
if (auxContext === null) {
throw new Error('useAuxillaryDivContext must be used inside AuxillaryDivContext');
}

return auxContext;
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { type Color } from 'three';
import { ModelsLoadingStateContext } from '../Reveal3DResources/ModelsLoadingContext';
import { SDKProvider } from './SDKProvider';
import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
import { AuxillaryDivProvider } from './AuxillaryDivProvider';

type RevealContainerProps = {
color?: Color;
Expand Down Expand Up @@ -47,9 +48,11 @@ export function RevealContainer({
return (
<SDKProvider sdk={sdk}>
<QueryClientProvider client={queryClient}>
<div style={{ width: '100%', height: '100%' }} ref={revealDomElementRef}>
{mountChildren()}
</div>
<AuxillaryDivProvider>
<div style={{ width: '100%', height: '100%' }} ref={revealDomElementRef}>
{mountChildren()}
</div>
</AuxillaryDivProvider>
{uiElements}
</QueryClientProvider>
</SDKProvider>
Expand Down
88 changes: 60 additions & 28 deletions react-components/stories/Reveal3DResources.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
*/
import type { Meta, StoryObj } from '@storybook/react';
import { Reveal3DResources, RevealContainer } from '../src';
import { Color, Matrix4 } from 'three';
import { Color, Matrix4, Vector3 } from 'three';
import { CameraController } from '../src/';
import { createSdkByUrlToken } from './utilities/createSdkByUrlToken';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { InfoCard } from '../src/components/InfoCard/InfoCard';

const meta = {
title: 'Example/Reveal3DResources',
Expand Down Expand Up @@ -123,31 +124,62 @@ export const Main: Story = {
assetFdmSpace: 'bark-corporation'
}
},
render: ({ resources, styling, fdmAssetMappingConfig }) => (
<RevealContainer
sdk={sdk}
color={new Color(0x4a4a4a)}
uiElements={<ReactQueryDevtools initialIsOpen={false} />}
viewerOptions={{
loadingIndicatorStyle: {
opacity: 1,
placement: 'topRight'
}
}}>
<Reveal3DResources
resources={resources}
styling={styling}
fdmAssetMappingConfig={fdmAssetMappingConfig}
/>
<CameraController
initialFitCamera={{
to: 'allModels'
}}
cameraControlsOptions={{
changeCameraTargetOnClick: true,
mouseWheelAction: 'zoomToCursor'
}}
/>
</RevealContainer>
)
render: ({ resources, styling, fdmAssetMappingConfig }) => {
const position = new Vector3(50, 30, 50);
const position2 = new Vector3(0, 0, 0);

return (
<RevealContainer
sdk={sdk}
color={new Color(0x4a4a4a)}
uiElements={<ReactQueryDevtools initialIsOpen={false} />}
viewerOptions={{
loadingIndicatorStyle: {
opacity: 1,
placement: 'topRight'
}
}}>
<Reveal3DResources
resources={resources}
styling={styling}
fdmAssetMappingConfig={fdmAssetMappingConfig}
/>
<InfoCard position={position} uniqueKey="key2">
<p
style={{
backgroundColor: 'turquoise',
borderColor: 'black',
borderWidth: '10px',
borderStyle: 'solid',
maxWidth: '300px',
transform: 'translate(-50%, calc(-100% - 50px))'
}}>
This label is stuck at position {position.toArray().join(',')}
</p>
</InfoCard>
<InfoCard position={position2} uniqueKey="key1">
<p
style={{
backgroundColor: 'red',
borderColor: 'black',
borderWidth: '10px',
borderStyle: 'solid',
maxWidth: '300px',
transform: 'translate(0px, 0px)'
}}>
This label is stuck at position {position2.toArray().join(',')}
</p>
</InfoCard>
<CameraController
initialFitCamera={{
to: 'allModels'
}}
cameraControlsOptions={{
changeCameraTargetOnClick: true,
mouseWheelAction: 'zoomToCursor'
}}
/>
</RevealContainer>
);
}
};
Loading