Three Javascript injection

I am using the Three Javascript library to inject into a markdown component.
Now I am using an older version of Three since I get error with the newest one:

This older version does not have any Import/Export statements, so it works fine.

Now I am planning to use the OrbitControls library on the same repository

I minified the library but I am having issues with running it since this has Import/Export statements.

Does anybody have any ideas on how to replace this Import/Export statements?

@victordcq @bmusson maybe you guys have some ideas?

You can't just minify the file, you'll need to bundle everything together into a single self-contained script.

The bundler needs to resolve and inline the code from the Three.js imports and emit a single browser-compatible file.

I like Rollup.

But, my recommendation is to write a module and not do JavaScript injection. If you can understand how JavaScript injection works using the Markdown component, then you are more than qualified to write a proper module (and people around here will be more likely to help you if you're doing something supported).

3 Likes

Thanks for the advice!
I was thinking about getting started with module development, but I had no application on mind before ... Now I have this good use case!

1 Like

If you miniefied a whole lib, you should definitly put it into either a module,
or if you are not familiar enough with JAVA you should put the mini.js script into injectables.

Check out this post, here i copied a mini lib to injectable/head.txt. and call its functions in a markdown

That said a module for three.js is probably something other people are interested in too.

1 Like

I am starting already with the module tutorials. Thank you!

1 Like

Here's a super untested component that will render user-supplied JavaScript as a React Function component. It uses Babel to transpile JSX on the client-side, no idea yet what the performance implications are.

Props are passed in like you'd expect. You also currently have access to the Bable TransformOptions through options.babel

I spent more time on the component icons than the component itself, so I'm sure this has lifecycle problems somewhere :man_shrugging:.

The attached view shows an example of loading Three.js from a CDN.

I'm not sure I want to go down the road of including Three.js in Periscope... there is simply an endless amount of JavaScript libraries that someone may want to include, and I want to be conscious of Periscope's bundle size. However, I do recognize that loading from a CDN is not feasible for most Ignition installations. I think it's time for inclusion of the web-library folder feature.

I don't currently have much time to allocate to this component, which is why I'm shadow dropping it in its current state. Any testing/feedback would be appreciated.

P.S. Still wishing for a way to interact with NodeEditor.createValueEditor.... :genie: :diya_lamp: :thought_balloon::thought_balloon:

Embr-Periscope-0.7.4-ReactComponent.modl (2.5 MB)

view.json
{
  "custom": {},
  "params": {},
  "props": {
    "defaultSize": {
      "height": 747,
      "width": 1399
    }
  },
  "root": {
    "children": [
      {
        "meta": {
          "name": "React_1"
        },
        "position": {
          "height": 694,
          "width": 754,
          "y": -0.5
        },
        "props": {
          "component": "() \u003d\u003e {\n  const containerRef \u003d React.useRef(null);\n\n  React.useEffect(() \u003d\u003e {\n    let renderer, camera, scene, cube, frameId;\n    let resizeObserver;\n\n    const loadAndInit \u003d async () \u003d\u003e {\n      const THREE \u003d await import(\u0027https://cdn.jsdelivr.net/npm/three@0.161.0/build/three.module.js\u0027);\n\n      const {\n        Scene,\n        PerspectiveCamera,\n        WebGLRenderer,\n        BoxGeometry,\n        MeshBasicMaterial,\n        Mesh,\n        Color,\n      } \u003d THREE;\n\n      scene \u003d new Scene();\n      scene.background \u003d new Color(0x202030);\n\n      const container \u003d containerRef.current;\n      const width \u003d container.clientWidth;\n      const height \u003d container.clientHeight;\n\n      camera \u003d new PerspectiveCamera(75, width / height, 0.1, 1000);\n      camera.position.z \u003d 3;\n\n      renderer \u003d new WebGLRenderer({ antialias: true });\n      renderer.setSize(width, height);\n      container.appendChild(renderer.domElement);\n\n      const geometry \u003d new BoxGeometry();\n      const material \u003d new MeshBasicMaterial({ color: 0x44aa88, wireframe: true });\n      cube \u003d new Mesh(geometry, material);\n      scene.add(cube);\n\n      const animate \u003d () \u003d\u003e {\n        cube.rotation.x +\u003d 0.01;\n        cube.rotation.y +\u003d 0.01;\n        renderer.render(scene, camera);\n        frameId \u003d requestAnimationFrame(animate);\n      };\n\n      animate();\n\n      const handleResize \u003d () \u003d\u003e {\n        if (!renderer || !camera) return;\n        const newWidth \u003d container.clientWidth;\n        const newHeight \u003d container.clientHeight;\n        camera.aspect \u003d newWidth / newHeight;\n        camera.updateProjectionMatrix();\n        renderer.setSize(newWidth, newHeight);\n      };\n\n      resizeObserver \u003d new ResizeObserver(handleResize);\n      resizeObserver.observe(container);\n    };\n\n    loadAndInit().catch(console.error);\n\n    return () \u003d\u003e {\n      cancelAnimationFrame(frameId);\n      if (resizeObserver) {\n        resizeObserver.disconnect();\n      }\n      if (renderer?.domElement \u0026\u0026 containerRef.current?.contains(renderer.domElement)) {\n        containerRef.current.removeChild(renderer.domElement);\n      }\n    };\n  }, []);\n\n  return (\n    \u003cdiv\n      ref\u003d{containerRef}\n      style\u003d{{\n        width: \u0027100%\u0027,\n        height: \u0027100%\u0027,\n        overflow: \u0027hidden\u0027,\n        margin: 0,\n        padding: 0,\n        position: \u0027relative\u0027,\n      }}\n    /\u003e\n  );\n}",
          "options": {
            "babel": {
              "presets": [
                "react"
              ]
            }
          },
          "props": {}
        },
        "type": "embr.periscope.embedding.react"
      },
      {
        "meta": {
          "name": "React"
        },
        "position": {
          "height": 32,
          "width": 300,
          "x": 750,
          "y": -0.5
        },
        "props": {
          "component": "(props) \u003d\u003e \u003cdiv\u003e{props.text}\u003c/div\u003e",
          "options": {
            "babel": {
              "presets": [
                "react"
              ]
            }
          },
          "props": {
            "text": "Hello World!"
          }
        },
        "type": "embr.periscope.embedding.react"
      },
      {
        "meta": {
          "name": "React_0"
        },
        "position": {
          "height": 503,
          "width": 300,
          "x": 770,
          "y": 188
        },
        "props": {
          "component": "(props) \u003d\u003e {\n  const styles \u003d {\n    container: {\n      minHeight: \u0027auto\u0027,\n      height: \u0027100%\u0027,\n      display: \u0027flex\u0027,\n      alignItems: \u0027center\u0027,\n      justifyContent: \u0027center\u0027,\n      backgroundColor: \u0027transparent\u0027\n    },\n    form: {\n      backgroundColor: \u0027#ffffff\u0027,\n      padding: \u00272rem\u0027,\n      borderRadius: \u00271rem\u0027,\n      boxShadow: \u00270 10px 25px rgba(0, 0, 0, 0.1)\u0027,\n      width: \u0027100%\u0027,\n      maxWidth: \u0027400px\u0027,\n    },\n    title: {\n      fontSize: \u00271.5rem\u0027,\n      fontWeight: \u0027bold\u0027,\n      marginBottom: \u00271.5rem\u0027,\n      color: \u0027#1f2937\u0027,\n      textAlign: \u0027center\u0027,\n    },\n    label: {\n      display: \u0027block\u0027,\n      color: \u0027#4b5563\u0027,\n      marginBottom: \u00270.5rem\u0027,\n    },\n    input: {\n      width: \u0027100%\u0027,\n      padding: \u00270.5rem 1rem\u0027,\n      border: \u00271px solid #d1d5db\u0027,\n      borderRadius: \u00270.5rem\u0027,\n      outline: \u0027none\u0027,\n      marginBottom: \u00271.5rem\u0027,\n    },\n    button: {\n      width: \u0027100%\u0027,\n      backgroundColor: \u0027#2563eb\u0027,\n      color: \u0027#ffffff\u0027,\n      padding: \u00270.5rem 1rem\u0027,\n      borderRadius: \u00270.5rem\u0027,\n      border: \u0027none\u0027,\n      cursor: \u0027pointer\u0027,\n      transition: \u0027background-color 0.2s ease\u0027,\n    },\n    footerText: {\n      textAlign: \u0027center\u0027,\n      fontSize: \u00270.875rem\u0027,\n      color: \u0027#6b7280\u0027,\n      marginTop: \u00271rem\u0027,\n    },\n    link: {\n      color: \u0027#2563eb\u0027,\n      textDecoration: \u0027none\u0027,\n    },\n  };\n\n  const handleClick \u003d (message) \u003d\u003e (event) \u003d\u003e {\n    event.preventDefault();\n    perspective.sendMessage(message, {}, \u0027page\u0027);\n  };\n\n  return (\n    \u003cdiv style\u003d{styles.container}\u003e\n      \u003cform style\u003d{styles.form}\u003e\n        \u003ch2 style\u003d{styles.title}\u003e{props.title}\u003c/h2\u003e\n\n        \u003clabel htmlFor\u003d\"email\" style\u003d{styles.label}\u003eEmail\u003c/label\u003e\n        \u003cinput\n          type\u003d\"email\"\n          id\u003d\"email\"\n          placeholder\u003d\"you@example.com\"\n          style\u003d{styles.input}\n        /\u003e\n\n        \u003clabel htmlFor\u003d\"password\" style\u003d{styles.label}\u003ePassword\u003c/label\u003e\n        \u003cinput\n          type\u003d\"password\"\n          id\u003d\"password\"\n          placeholder\u003d\"••••••••\"\n          style\u003d{styles.input}\n        /\u003e\n\n        \u003cbutton type\u003d\"button\" onClick\u003d{handleClick(\u0027loginClicked\u0027)} style\u003d{styles.button}\u003e\n          Log In\n        \u003c/button\u003e\n\n        \u003cp style\u003d{styles.footerText}\u003e\n          Don’t have an account?{\u0027 \u0027}\n          \u003ca href\u003d\"#\" onClick\u003d{handleClick(\u0027signupClicked\u0027)} style\u003d{styles.link}\u003eSign up\u003c/a\u003e\n        \u003c/p\u003e\n      \u003c/form\u003e\n    \u003c/div\u003e\n  );\n}",
          "options": {
            "babel": {
              "presets": [
                "react"
              ]
            }
          },
          "props": {
            "title": "New Title!!"
          },
          "style": {
            "overflow": "visible"
          }
        },
        "scripts": {
          "customMethods": [],
          "extensionFunctions": null,
          "messageHandlers": [
            {
              "messageType": "loginClicked",
              "pageScope": true,
              "script": "\tsystem.perspective.print(\u0027Login Clicked!\u0027)",
              "sessionScope": false,
              "viewScope": false
            },
            {
              "messageType": "signupClicked",
              "pageScope": true,
              "script": "\tsystem.perspective.print(\u0027Signup Clicked!\u0027)",
              "sessionScope": false,
              "viewScope": false
            }
          ]
        },
        "type": "embr.periscope.embedding.react"
      }
    ],
    "meta": {
      "name": "root"
    },
    "type": "ia.container.coord"
  }
}

Edit: Here's the branch - GitHub - mussonindustrial/embr at feat/periscope/react-component

1 Like

It's not the same, but you could register a design delegate that will show up in the space above the property editor (like the flex container buttons) and put your own RSyntaxTextArea or what have you up there, right?

2 Likes

Ooh, yeah.
I think that would be a good solution for this component since it only has the one main script property.

1 Like