Whiteboard images

An image shape shows an image from a URL. Users paste or drop image files on the board, your app uploads them, and the board stores the URL that the upload returns. So the board stays free of any storage.

Upload the files

Tell the board where image files go with the images option of WhiteboardKit. Without it, users can't add images, but the images on the board still show. This example sends each file to an endpoint of your app, which answers with the file's URL:

import { Editor } from '@tiptap/core'
import { WhiteboardKit } from '@tiptap-pro/extension-whiteboard'

const upload = async ({ file, signal }) => {
  const body = new FormData()
  body.append('file', file)
  const response = await fetch('/api/uploads', { method: 'POST', body, signal })

  if (!response.ok) {
    throw new Error(`The upload failed with status ${response.status}`)
  }

  const { url } = await response.json()
  return url
}

const editor = new Editor({
  element: viewport,
  extensions: [
    WhiteboardKit.configure({
      images: {
        upload,
        onError: ({ file, reason }) => showMessage(`${file.name}: ${reason}`),
      },
    }),
  ],
})

In React, create the configured kit once, outside the component or with useMemo. See Set up a board in React.

The images object has these members:

  • upload({ file, signal }) uploads an image file and returns the URL that its image shows from. signal aborts when the board's editor is destroyed before the upload ends, so pass it on to fetch.
  • accept, which is optional, lists the image types that users can add. Default: PNG, JPEG, GIF, WebP, AVIF and SVG.
  • maxSize, which is optional, is the size of the largest file that users can add, in bytes. Default: 10 MB.
  • onError({ file, reason, error }), which is optional, tells your app about a file that the board left out, like to show a message. reason is type when accept doesn't list the file's type or the browser can't show the file, size when the file is larger than maxSize, and upload when upload throws or returns a URL that can't show. error holds what upload threw or returned.

The URLs of images

The board stores the URL in the image shape's src. Board files and the board's Y.Doc hold the URL too, and every user's browser loads the image from it. So return a URL that keeps working, not one that expires soon, like a short-lived signed URL.

Images show from http and https URLs, from URLs relative to the page, and from data:image/ URLs. Other URLs don't show, like blob: URLs, which stop working when the page that made them closes. A data: URL puts the whole file into the board's document, so keep it for small images, or for a demo without a server.

Add images

Once the stage is attached, see Stage and camera, image files that users paste land in the middle of the stage, and files that they drop land where they drop them. While text is edited, a paste goes into the text, and its image files stay out.

For a button of your own, pass the files of a file input to addImageFiles. It adds the images in the middle of the stage, or around a point in board units:

import { addImageFiles, focusBoard } from '@tiptap-pro/extension-whiteboard'

input.addEventListener('change', async () => {
  await addImageFiles({ stage, editor, files: input.files ?? [] })
  // Lets users pick the same file again, and gives the board the keyboard
  input.value = ''
  focusBoard(editor.view)
})

Give the file input the same types as accept, like accept="image/png,image/jpeg", so the file picker offers only those.

The board uploads the files first. Once all uploads are done, it adds their images in one step of the board's history, and selects them. Until then, the board shows nothing, so show your own progress in upload when uploads take long. Each image has its natural size, but no more than half of the stage, and images added together stand side by side. On a read-only board, nothing is added.

In code, add an image like any other shape, with a URL and a size of your own. alt holds the text that screen readers read for the image:

editor.commands.insertShape({
  type: 'boardImage',
  src: '/plan.png',
  width: 400,
  height: 300,
  alt: 'The floor plan',
})

Images that users paste or drop have an empty alt. Set it later with updateShape, like any other attribute.

Resize images

An image keeps its proportions while a corner handle resizes it. The side that grows more decides the size, so the pointer stays on an edge of the image. Images are at least 16 by 16 board units.

Next steps