Using the UI Kit with React

This sample application uses the Next.js framework to demonstrate how to load a model and use React with the UI kit. Follow along step-by-step, or scroll to the bottom for the completed project.

Project setup

  1. npx create-next-app@latest
../../../_images/next-setup.png
  1. Create a folder called @hoops in your main project directory and copy all folders (except beta-demo-app-src) from the <HC_INSTALL_DIR>/web_viewer/@hoops directory to this folder.
  2. npm install "./@hoops/web-viewer-components-react"
  3. Copy microengine.scs from the HOOPS Communicator package to the public folder.
  4. Copy engine.esm.wasm from ./@hoops/sc-engine to the public folder.
  5. Create a directory in your project’s app directory called components.
  6. Create a Viewer.tsx file here and create an empty basic component:
  7. At the top of the file, add the ‘use client’ directive to support the web viewer and React functionalities.
  8. Below that, add an import statement for the web viewer components:
import { WebViewerComponent } from '@hoops/web-viewer-components-react';

Note: you may be able to skip this step if your IDE can automatically add these as you use them in your code.

  1. In the Viewer function, update the return with a WebViewerComponent:
export default function Viewer() {
    return (
        <WebViewerComponent
        id='hwv'
        endpointUri='./microengine.scs'
        enginePath='./'
        />
    )
}
  1. Delete all the contents of app/page.tsx and replace with the following:
'use client'
import dynamic from "next/dynamic";

const Viewer = dynamic(()=> import('./components/Viewer'), {
  ssr: false
});

export default function Home() {
  return (
     <Viewer />
  )
}
  1. From your app’s directory, run npm run dev:
../../../_images/minimal-viewer.png

Adding a button

  1. Edit the Viewer.tsx file to return the following:
<>
    <WebViewerComponent
        id='hwv'
        endpointUri='./microengine.scs'
        enginePath='./'
    />
    <div id="hwvHomeButton" style={{position: "absolute", top: "10px", right: "10px"}}>
        <HoopsIconButton>
            <HoopsIcon icon="home"></HoopsIcon>
        </HoopsIconButton>
    </div>
</>
  1. Ensure the following import statements have been added if not already done in the last step:
import { HoopsButton, HoopsIcon, HoopsIconButton } from '@/@hoops/ui-kit-react/lib';
  1. Next, we will use a useRef hook to manage the instantiated Web Viewer since we will not need it to trigger a re-render when updated. First, import useRef and initialize the variable for the Web Viewer inside of the Viewer function:
import { useRef } from 'react';

export default function Viewer() {
    const hwvRef = useRef<WebViewer | null>(null);
    ...
}
  1. Now we can add functionality to the button. Create a new function as follows:
function modelReady(event: CustomEvent<{hwv: WebViewer}>) {
    const { hwv } = event.detail;
    hwvRef.current = hwv;
}
  1. Finally, add an onClick property to the element containing the HoopsIconButton:
<div id="hwvHomeButton" style={{position: "absolute", top: "10px", right: "10px"}} onClick={()=>hwvRef.current?.reset()} >

You should now have a working “Home” button.

Customizing appearance

  1. In app/globals.css, add the following at the end of the file to make the viewer appear full screen:
hoops-web-viewer {
  width: 100vw;
  height: 100vh;
}
  1. To change the appearance of the hoops-icon, you can customize the CSS variables that define its behavior, such as the stroke color. In the :root selector, add the following:
:root {
    --hoops-svg-stroke-color: blue;
}

Adding the model tree

  1. In this section, we will add a model tree and a button to toggle its visibility. First, we will need to create a reference for the model tree. Add this directly below the declaration of hwvRef:
const modelTreeRef = useRef<HoopsModelTreeElement | null>(null);
  1. We will also create a hook to track the visibility state of the model tree in our Viewer component. Add useState to your import statement as follows:
import { useRef, useState } from 'react';

Also, create the hook directly below the declaration of modelTreeRef:

const [modelTreeVisible, setModelTreeVisible] = useState<boolean>(false);

let modelTreeDisplay = "none";
if (modelTreeVisible) modelTreeDisplay = "block";
  1. In the modelReady function, we can set the model tree reference to the instantiated viewer’s model property:
function modelReady(event: CustomEvent<{hwv: WebViewer}>) {
    // ...

    if (modelTreeRef.current) {
        modelTreeRef.current.model = hwv.model;
    }
}
  1. Define a function to toggle the visibility of the tree (still within the Viewer function):
function toggleTree() {
    if (modelTreeVisible) {
        setModelTreeVisible(false);
    }
    else {
        setModelTreeVisible(true);
    }
}
  1. In globals.css, add the style for the <hoops-model-tree> element:
hoops-model-tree {
  width: 600px;
  overflow: auto;
  z-index: 3;
  position: absolute;
  left: 20px;
  top: 50px;
  display: none;
}
  1. Back in Viewer.tsx, add a model tree toggle button to the return statement of the Viewer function:
<div id="modelTreeDiv" style={{position: "absolute", top: "10px", left: "10px", zIndex: 2}} onClick={toggleTree}>
    <HoopsModelTreeButton id="hwvModelTreeButton"></HoopsModelTreeButton>
</div>
  1. Finally, also add the HoopsModelTree component to the return statement, passing in the model tree reference to the ref property:
<HoopsModelTree id="hwvModelTree" style={{display: modelTreeDisplay}} ref={modelTreeRef}></HoopsModelTree>

Now your app should have a model tree button that toggles the tree when clicked.

Making the model tree interactive

  1. Update your import statement to import SelectionMode as follows:
import { WebViewer, SelectionMode } from '@/@hoops/web-viewer';
  1. Create a new function to handle a model tree node click (add this within the Viewer` function just above the return statement):
function handleNodeClick(event: CustomEvent<{nodeId: number}>) {
    event.stopPropagation();
    hwvRef.current?.selectionManager.clear(false);
    hwvRef.current?.selectionManager.selectNode(event.detail.nodeId, SelectionMode.Add);
}
  1. Update the HoopsModelTree component in the return statement with modelTreeNodeClick:
<HoopsModelTree id= "hwvModelTree" style={{display: modelTreeDisplay}} ref={modelTreeRef} modelTreeNodeClick={handleNodeClick}></HoopsModelTree>

When the app runs, you should now be able to click an element in the model tree to select the respective node on the model.

  1. Now, to add functionality to the visibility icons, create a function to handle a visibility change (you can add this after your handleNodeClick function):
function handleNodeVisibilityChange(event: CustomEvent<{nodeId: number, visibility: boolean}>) {
    event.stopPropagation();
    hwvRef.current?.model.setNodesVisibility([event.detail.nodeId], event.detail.visibility);
    modelTreeRef.current?.updateNodeData(event.detail.nodeId, {
        visible: event.detail.visibility
    });
}
  1. Update the HoopsModelTree component’s modelTreeNodeVisibilityChange:
<HoopsModelTree id="hwvModelTree" style={{display: modelTreeDisplay}} ref={modelTreeRef} modelTreeNodeClick={handleNodeClick} modelTreeNodeVisibilityChange={handleNodeVisibilityChange}></HoopsModelTree>
  1. The model tree can only affect the Web Viewer at the moment (and not vice versa), so in order to fully sync the Web Viewer and model tree, we can use the Web Viewer callbacks so changes can propogate to the model tree (add this within the modelReady function):
hwvRef.current.setCallbacks({
    selectionArray: ()=> {
        modelTreeRef.current!.selected = hwvRef.current!.selectionManager.getResults().map((current)=> {
            return current.getNodeId();
        });
    },
    visibilityChanged: (shownBodyIds: number[], hiddenBodyIds: number[])=> {
        shownBodyIds.forEach((nodeId)=>{
            modelTreeRef.current?.updateNodeData(nodeId, {visible: true});
        });
        hiddenBodyIds.forEach((nodeId)=>{
            modelTreeRef.current?.updateNodeData(nodeId, {visibility: false});
        })
    }
});

Note: this is a simple example and the node IDs returned by the visibilityChanged callback will be leaf nodes - to see the effect of the Web Viewer on the model tree, you may need to expand the tree or alter the logic. For example, if you add hwv.model.setNodesVisibility([hwv.model.getAbsoluteRootNode()], false); in the modelReady function, you will still see the visible icon on the parent nodes and invisible icon for the leaf nodes.

  1. Finally, let’s update the style of the model tree. In globals.css within the :root selector, add --hoops-accent-foreground-hover: #005588; and --body-font: sans-serif;.

For more information about the components, refer to the Programming Guide and API documentation.

../../../_images/react-tutorial-1.gif

The complete sample application from this page can be found here: react-app.zip.