Browse Source

Pass container to `UIPlugin.render` for non-Preact integration (#5437)

Merlijn Vos 8 months ago
parent
commit
bc27b4a5c8

+ 50 - 8
examples/react-example/App.tsx

@@ -1,16 +1,60 @@
 /* eslint-disable */
 import React from 'react'
-import Uppy from '@uppy/core'
+import { createRoot } from 'react-dom/client'
+import Uppy, {
+  UIPlugin,
+  type Meta,
+  type Body,
+  type UIPluginOptions,
+  type State,
+} from '@uppy/core'
 import Tus from '@uppy/tus'
 import Webcam from '@uppy/webcam'
-import RemoteSources from '@uppy/remote-sources'
 import { Dashboard, useUppyState } from '@uppy/react'
 
 import '@uppy/core/dist/style.css'
 import '@uppy/dashboard/dist/style.css'
-import '@uppy/drag-drop/dist/style.css'
-import '@uppy/file-input/dist/style.css'
-import '@uppy/progress-bar/dist/style.css'
+import '@uppy/webcam/dist/style.css'
+
+interface MyPluginOptions extends UIPluginOptions {}
+
+interface MyPluginState extends Record<string, unknown> {}
+
+// Custom plugin example inside React
+class MyPlugin<M extends Meta, B extends Body> extends UIPlugin<
+  MyPluginOptions,
+  M,
+  B,
+  MyPluginState
+> {
+  container!: HTMLElement
+
+  constructor(uppy: Uppy<M, B>, opts?: MyPluginOptions) {
+    super(uppy, opts)
+    this.type = 'acquirer'
+    this.id = this.opts.id || 'TEST'
+    this.title = 'Test'
+  }
+
+  override install() {
+    const { target } = this.opts
+    if (target) {
+      this.mount(target, this)
+    }
+  }
+
+  override uninstall() {
+    this.unmount()
+  }
+
+  override render(state: State<M, B>, container: HTMLElement) {
+    // Important: during the initial render is not defined. Safely return.
+    if (!container) return
+    createRoot(container).render(
+      <h2>React component inside Uppy's Preact UI</h2>,
+    )
+  }
+}
 
 const metaFields = [
   { id: 'license', name: 'License', placeholder: 'specify license' },
@@ -20,9 +64,7 @@ function createUppy() {
   return new Uppy({ restrictions: { requiredMetaFields: ['license'] } })
     .use(Tus, { endpoint: 'https://tusd.tusdemo.net/files/' })
     .use(Webcam)
-    .use(RemoteSources, {
-      companionUrl: 'https://companion.uppy.io',
-    })
+    .use(MyPlugin)
 }
 
 export default function App() {

+ 1 - 1
examples/react-example/main.tsx

@@ -3,4 +3,4 @@ import React from 'react'
 import { createRoot } from 'react-dom/client'
 import App from './App.tsx'
 
-createRoot(document.querySelector('#app')).render(<App />)
+createRoot(document.querySelector('#app')!).render(<App />)

+ 12 - 5
packages/@uppy/core/src/UIPlugin.ts

@@ -1,5 +1,5 @@
 /* eslint-disable class-methods-use-this */
-import { render, type ComponentChild } from 'preact'
+import { render } from 'preact'
 import findDOMElement from '@uppy/utils/lib/findDOMElement'
 import getTextDirection from '@uppy/utils/lib/getTextDirection'
 
@@ -112,7 +112,7 @@ class UIPlugin<
         // so it could still be called even after uppy.removePlugin or uppy.destroy
         // hence the check
         if (!this.uppy.getPlugin(this.id)) return
-        render(this.render(state), uppyRootElement)
+        render(this.render(state, uppyRootElement), uppyRootElement)
         this.afterUpdate()
       })
 
@@ -127,7 +127,10 @@ class UIPlugin<
         targetElement.innerHTML = ''
       }
 
-      render(this.render(this.uppy.getState()), uppyRootElement)
+      render(
+        this.render(this.uppy.getState(), uppyRootElement),
+        uppyRootElement,
+      )
       this.el = uppyRootElement
       targetElement.appendChild(uppyRootElement)
 
@@ -176,8 +179,12 @@ class UIPlugin<
    * so this.el and this.parent might not be available in `install`.
    * This is the case with @uppy/react plugins, for example.
    */
-  // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  render(state: Record<string, unknown>): ComponentChild {
+  render(
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    state: Record<string, unknown>,
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    container: HTMLElement,
+  ): any {
     throw new Error(
       'Extend the render method to add your plugin to a DOM element',
     )

+ 5 - 2
packages/@uppy/dashboard/src/components/PickerPanelContent.tsx

@@ -1,5 +1,6 @@
 import { h } from 'preact'
 import classNames from 'classnames'
+import { useRef } from 'preact/hooks'
 import ignoreEvent from '../utils/ignoreEvent.ts'
 
 type $TSFixMe = any
@@ -12,6 +13,7 @@ function PickerPanelContent({
   state,
   uppy,
 }: $TSFixMe) {
+  const ref = useRef<HTMLDivElement>(null)
   return (
     <div
       className={classNames('uppy-DashboardContent-panel', className)}
@@ -39,8 +41,9 @@ function PickerPanelContent({
           {i18n('cancel')}
         </button>
       </div>
-      <div className="uppy-DashboardContent-panelBody">
-        {uppy.getPlugin(activePickerPanel.id).render(state)}
+
+      <div ref={ref} className="uppy-DashboardContent-panelBody">
+        {uppy.getPlugin(activePickerPanel.id).render(state, ref.current)}
       </div>
     </div>
   )