Uppy File Panel
This example allows users to upload files using Uppy by replacing the default File Panel with an Uppy Dashboard.
Uppy is highly extensible and has an extensive ecosystem of plugins. For example, you can:
- Record audio, screen or webcam
- Import files from Box / Dropbox / Facebook / Google Drive / Google Photos / Instagram / OneDrive / Zoom
- Select files from Unsplash
- Show an image editor (crop, rotate, etc)
In this example, we've enabled the Webcam, ScreenCapture and Image Editor plugins.
Try it out: Click the "Add Image" button and you can either drop files or click "browse files" to upload them.
Relevant Docs:
import "@blocknote/core/fonts/inter.css";import { BlockNoteView } from "@blocknote/mantine";import "@blocknote/mantine/style.css";import { FilePanelController, FormattingToolbar, FormattingToolbarController, getFormattingToolbarItems, useCreateBlockNote,} from "@blocknote/react";import { FileReplaceButton } from "./FileReplaceButton";import { uploadFile, UppyFilePanel } from "./UppyFilePanel";export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ initialContent: [ { type: "paragraph", content: "Welcome to this demo!", }, { type: "paragraph", content: "Upload an image using the button below", }, { type: "image", }, { type: "paragraph", }, ], uploadFile, }); // Renders the editor instance using a React component. return ( <BlockNoteView editor={editor} formattingToolbar={false} filePanel={false}> <FormattingToolbarController formattingToolbar={(props) => { // Replaces default file replace button with one that opens Uppy. const items = getFormattingToolbarItems(); items.splice( items.findIndex((c) => c.key === "replaceFileButton"), 1, <FileReplaceButton key={"fileReplaceButton"} />, ); return <FormattingToolbar {...props}>{items}</FormattingToolbar>; }} /> {/* Replaces default file panel with Uppy one. */} <FilePanelController filePanel={UppyFilePanel} /> </BlockNoteView> );}import { BlockSchema, blockHasType, InlineContentSchema, StyleSchema,} from "@blocknote/core";import { useBlockNoteEditor, useComponentsContext, useDictionary, useSelectedBlocks,} from "@blocknote/react";import { useEffect, useState } from "react";import { RiImageEditFill } from "react-icons/ri";import { UppyFilePanel } from "./UppyFilePanel";// Copied with minor changes from:// https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/FormattingToolbar/DefaultButtons/FileReplaceButton.tsx// Opens Uppy file panel instead of the default one.export const FileReplaceButton = () => { const dict = useDictionary(); const Components = useComponentsContext()!; const editor = useBlockNoteEditor< BlockSchema, InlineContentSchema, StyleSchema >(); const selectedBlocks = useSelectedBlocks(editor); const [isOpen, setIsOpen] = useState<boolean>(false); useEffect(() => { setIsOpen(false); }, [selectedBlocks]); const block = selectedBlocks.length === 1 ? selectedBlocks[0] : undefined; if ( block === undefined || !blockHasType(block, editor, "file", { url: "string" }) || !editor.isEditable ) { return null; } return ( <Components.Generic.Popover.Root open={isOpen} position={"bottom"}> <Components.Generic.Popover.Trigger> <Components.FormattingToolbar.Button className={"bn-button"} onClick={() => setIsOpen(!isOpen)} isSelected={isOpen} mainTooltip={ dict.formatting_toolbar.file_replace.tooltip[block.type] || dict.formatting_toolbar.file_replace.tooltip["file"] } label={ dict.formatting_toolbar.file_replace.tooltip[block.type] || dict.formatting_toolbar.file_replace.tooltip["file"] } icon={<RiImageEditFill />} /> </Components.Generic.Popover.Trigger> <Components.Generic.Popover.Content className={"bn-popover-content bn-panel-popover"} variant={"panel-popover"} > {/* Replaces default file panel with our Uppy one. */} <UppyFilePanel blockId={block.id} /> </Components.Generic.Popover.Content> </Components.Generic.Popover.Root> );};import { FilePanelProps, useBlockNoteEditor } from "@blocknote/react";import Uppy, { UploadSuccessCallback } from "@uppy/core";import "@uppy/core/dist/style.min.css";import "@uppy/dashboard/dist/style.min.css";import { Dashboard } from "@uppy/react";import XHR from "@uppy/xhr-upload";import { useEffect } from "react";// Image editor pluginimport ImageEditor from "@uppy/image-editor";import "@uppy/image-editor/dist/style.min.css";// Screen capture pluginimport ScreenCapture from "@uppy/screen-capture";import "@uppy/screen-capture/dist/style.min.css";// Webcam pluginimport Webcam from "@uppy/webcam";import "@uppy/webcam/dist/style.min.css";// Configure your Uppy instance here.const uppy = new Uppy() // Enabled plugins - you probably want to customize this // See https://uppy.io/examples/ for all the integrations like Google Drive, // Instagram Dropbox etc. .use(Webcam) .use(ScreenCapture) .use(ImageEditor) // Uses an XHR upload plugin to upload files to tmpfiles.org. // You want to replace this with your own upload endpoint or Uppy Companion // server. .use(XHR, { endpoint: "https://tmpfiles.org/api/v1/upload", getResponseData(text, resp) { return { url: JSON.parse(text).data.url.replace( "tmpfiles.org/", "tmpfiles.org/dl/", ), }; }, });export function UppyFilePanel(props: FilePanelProps) { const { blockId } = props; const editor = useBlockNoteEditor(); useEffect(() => { // Listen for successful tippy uploads, and then update the Block with the // uploaded URL. const handler: UploadSuccessCallback<Record<string, unknown>> = ( file, response, ) => { if (!file) { return; } if (file.source === "uploadFile") { // Didn't originate from Dashboard, should be handled by `uploadFile` return; } if (response.status === 200) { const updateData = { props: { name: file?.name, url: response.uploadURL, }, }; editor.updateBlock(blockId, updateData); // File should be removed from the Uppy instance after upload. uppy.removeFile(file.id); } }; uppy.on("upload-success", handler); return () => { uppy.off("upload-success", handler); }; }, [blockId, editor]); // set up dashboard as in https://uppy.io/examples/ return <Dashboard uppy={uppy} width={400} height={500} />;}// Implementation for the BlockNote `uploadFile` function.// This function is used when for example, files are dropped into the editor.export async function uploadFile(file: File) { const id = uppy.addFile({ id: file.name, name: file.name, type: file.type, data: file, source: "uploadFile", }); try { const result = await uppy.upload(); return result.successful[0].response!.uploadURL!; } finally { uppy.removeFile(id); }}