Fabric.js Document Engine

Search documentation

Find a page or section

中文

Keep chosen states of a document, and restore any of them later.

Undo covers the last few minutes. Canvas version history keeps chosen states for days or months, such as "Sent to client". Versions are full copies of the document, kept in your storage adapter.

Loading the live demo. Its code is below.

versions-demo.tsx
"use client";
 
import type { VersionSummary } from "fabricjs-document-engine";
import { useDocumentEngine } from "fabricjs-document-engine/react";
import { createMemoryStorage } from "fabricjs-document-engine/storage";
import { BookmarkPlusIcon, CircleIcon, SquareIcon } from "lucide-react";
import { useFormatter, useTranslations } from "next-intl";
import { useEffect, useState } from "react";
 
import {
  DemoCanvas,
  DemoFrame,
  newShape,
  seedShapes,
  Toolbar,
  ToolButton,
} from "./demo-ui";
import { useFabricCanvas } from "./use-fabric-canvas";
 
const storage = createMemoryStorage();
 
export default function VersionsDemo() {
  const t = useTranslations("demos");
  const format = useFormatter();
  const { elementRef, canvas } = useFabricCanvas();
  const engine = useDocumentEngine(canvas, { storage });
  const [versions, setVersions] = useState<VersionSummary[]>([]);
 
  useEffect(() => {
    if (!engine) return;
    engine.canvas.add(...seedShapes());
    engine.clearHistory();
  }, [engine]);
 
  async function keepVersion() {
    if (!engine) return;
    await engine.save();
    await engine.createVersion(
      t("versions.name", { number: versions.length + 1 })
    );
    setVersions(await namedVersions());
  }
 
  async function restore(id: string) {
    if (!engine) return;
    await engine.restoreVersion(id);
    await engine.save();
    setVersions(await namedVersions());
  }
 
  // The engine also keeps automatic versions; this list shows the named ones.
  async function namedVersions() {
    const all = (await engine?.listVersions()) ?? [];
    return all.filter((version) => version.kind === "named");
  }
 
  return (
    <DemoFrame>
      <DemoCanvas elementRef={elementRef} label={t("canvas")} />
 
      <Toolbar>
        <ToolButton
          icon={SquareIcon}
          label={t("square")}
          onClick={() => canvas?.add(newShape("square"))}
        />
        <ToolButton
          icon={CircleIcon}
          label={t("circle")}
          onClick={() => canvas?.add(newShape("circle"))}
        />
        <ToolButton
          icon={BookmarkPlusIcon}
          label={t("versions.keep")}
          primary
          disabled={!engine}
          className="ml-auto"
          onClick={() => void keepVersion()}
        />
      </Toolbar>
 
      {/* A fixed height: the list scrolls instead of growing the card. */}
      <ul className="h-[92px] overflow-y-auto rounded-lg border text-[13px]">
        {versions.length === 0 && (
          <li className="px-3 py-2 text-muted-foreground">
            {t("versions.empty")}
          </li>
        )}
        {versions.map((version) => (
          <li
            key={version.id}
            className="flex h-[30px] items-center gap-2 border-b px-3 last:border-b-0"
          >
            <span className="truncate font-medium">{version.name}</span>
            <span className="text-muted-foreground">
              {format.dateTime(new Date(version.createdAt), {
                timeStyle: "medium",
              })}
            </span>
            <button
              type="button"
              className="ml-auto shrink-0 font-medium text-[#6f00ff] hover:underline"
              onClick={() => void restore(version.id)}
            >
              {t("versions.restore")}
            </button>
          </li>
        ))}
      </ul>
    </DemoFrame>
  );
}

Keep and list versions

const version = await engine.createVersion("Sent to client");
const versions = await engine.listVersions();

listVersions returns summaries, newest first: { id, documentId, name, kind, createdAt, revision }. kind is named for a named version and auto for an automatic one.

Restore a version

await engine.restoreVersion(version.id);

When you restore a version, nothing is lost:

  1. The engine first keeps an automatic version named Before restoring "...".
  2. It then loads the old content as a new, unsaved revision of the same document.
  3. The next save stores it as the newest revision, so the canvas version history stays in order.

To undo a restore, restore the automatic version.

Automatic versions

createDocumentEngine({
  canvas,
  storage,
  versions: { autoEvery: 10, keepAuto: 20 },
});

This keeps an automatic version after every 10 successful saves, and only the newest 20 automatic ones. A named version is never pruned.

Delete a version

await engine.deleteVersion(version.id);

Storage support

The built-in adapters support versions. A custom adapter adds four methods: saveVersion, listVersions, loadVersion and deleteVersion. Without them, version calls fail with VERSIONS_UNSUPPORTED. See custom backend to add canvas version history to your own API.