From bc27b4a5c8710b9936f49b728d39db0954f94313 Mon Sep 17 00:00:00 2001 From: Merlijn Vos Date: Thu, 29 Aug 2024 16:12:27 +0200 Subject: [PATCH] Pass container to `UIPlugin.render` for non-Preact integration (#5437) --- examples/react-example/App.tsx | 58 ++++++++++++++++--- examples/react-example/main.tsx | 2 +- packages/@uppy/core/src/UIPlugin.ts | 17 ++++-- .../src/components/PickerPanelContent.tsx | 7 ++- 4 files changed, 68 insertions(+), 16 deletions(-) diff --git a/examples/react-example/App.tsx b/examples/react-example/App.tsx index e7636f1dbe..3387e8045c 100644 --- a/examples/react-example/App.tsx +++ b/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 {} + +// Custom plugin example inside React +class MyPlugin extends UIPlugin< + MyPluginOptions, + M, + B, + MyPluginState +> { + container!: HTMLElement + + constructor(uppy: Uppy, 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, container: HTMLElement) { + // Important: during the initial render is not defined. Safely return. + if (!container) return + createRoot(container).render( +

React component inside Uppy's Preact UI

, + ) + } +} 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() { diff --git a/examples/react-example/main.tsx b/examples/react-example/main.tsx index bf7e150561..5969f2d933 100644 --- a/examples/react-example/main.tsx +++ b/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() +createRoot(document.querySelector('#app')!).render() diff --git a/packages/@uppy/core/src/UIPlugin.ts b/packages/@uppy/core/src/UIPlugin.ts index 8ea1a490ca..49f6772711 100644 --- a/packages/@uppy/core/src/UIPlugin.ts +++ b/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): ComponentChild { + render( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + state: Record, + // 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', ) diff --git a/packages/@uppy/dashboard/src/components/PickerPanelContent.tsx b/packages/@uppy/dashboard/src/components/PickerPanelContent.tsx index ad3677f815..7a795ec9f1 100644 --- a/packages/@uppy/dashboard/src/components/PickerPanelContent.tsx +++ b/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(null) return (
-
- {uppy.getPlugin(activePickerPanel.id).render(state)} + +
+ {uppy.getPlugin(activePickerPanel.id).render(state, ref.current)}
)