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).

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!

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.

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

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

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?

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

Thanks for sharing! I did not understand the module in depth but this worked for me. I was able to use some three libraries. I also used a CDN. I wanted to use a local repository but I have issues loading them and I got a bunch of issues with cors policy no 'access-control-allow-origin'
I will update any progress or issues I encounter.

Yeah, before I will support merging this component into mainline Embr there will have to be a way to include local web dependencies.