Developer Manual

Preview

The Preview component is the most complex one.

The app lets users modify the code of the edited asset. This introduces challenges with the preview: it must not crash the app if the code contains errors, and should avoid interacting with the app (click events, CSS, code that sets certain properties on the body… can all disrupt the app’s function in non-predictable ways) . For this reason, instead of making the preview part of the app as a canvas based component, we use an iframe to render it, like online code editors such as CodePen do. This also helps us avoid all the issues that would arise if we tried to modify the asset code on a live canvas – instead of worrying about them, we just need to reload the iframe. In order to avoid dealing with CORS issues, the iframe is created at the same URL as the edit app.

Instantiating

The preview is instantiated if the page’s URL query string contains:

  • compId – the ID of the asset to preview
  • preview – value doesn’t need to be set

The check is performed in the app’s entry point, src/index.js. The preview is then instantiated from src/initPreview.js.

We register asset loaders (OBJ, GLTF…), then load the required asset’s document. If it’s a VisComp, we launch the app as usual. If it’s a Shader.ProceduralTexture, we create a proxy document for a preview composition and launch the app using this composition. The proxy composition updates the previewed shader when its inputs are set in the Inspector.

Once ready, the Edit app is notified.

The preview app is instantiated with the property isIframePreview set to true. This lets the VisComp and ProceduralTexturePreview classes know that they shouldn’t create their own animation graphs.

Communicating with the Edit app

The preview iframe communicates with the app using the window.postMessage method.

The preview responds to the following events:

  • reload: reloads the page
  • stop: stops the app’s update loop
  • resume: resumes the app’s update loop
  • screenshot: takes a screenshot (using SceneSingle). The screenshot data is then transformed into a Blob and posted back to the Edit app in a screenshot message.
  • change: anytime a property is changed in the Inspector, the preview receives this message and updates its inputs accordingly
  • updateGraph: the preview app does not create its own graph. Instead, the graph is part of the Edit app and posts its current state to the preview each frame.
  • eval: runs eval() on commands typed into the Console component
  • log: logs the selected element to the console (triggered by the Monitor component)
  • stats: sends CloudClient, ExtAssetCache and TypeManager stats to the Edit app window

Additionally, it sends the following events:

  • ready: sent once ready
  • inputStats: sent to the parent window each frame, contains InputManager data
  • shaderError: when editing a shader, the shader preview VisComp checks it for errors. If the shader doesn’t compile, the error message is analysed to determine whether the error happened in the shader’s declarations or in its main() function, as well as the line number where it occurred (this line number is relative to the code document’s lines, not to the compiled shader’s lines, which includes other code created by THREE.js

Animation graph

The preview app does not create its own graph. Instead, the graph is part of the Edit app and posts its current state to the preview each frame. A ViscompUpdaterFromData class is created, and serves as a proxy to update the previewed asset’s inputs when graph updates are received by the preview iframe.

React Integration

The preview iframe is rendered at the top level of the app, in EditorApp, which stores a React ref to it.

The Preview component receives that ref as a prop, then creates an empty div that is styled so that it fills the screen where the preview iframe should go. When that div is resized, or the component is mounted, we style the preview iframe so that it appears over that div, as if it were its child.
We proceed like this because we only want to create the iframe on page load, and only refresh it when necessary. If we had the iframe rendered as a child of the Preview component, each time the layout changes, the Preview’s parent might change, meaning the iframe would be re-rendered and would have to reload at every layout change.