Fabric.js Document Engine

搜索文档

查找页面或章节

EN

保留文档的某些状态,之后随时恢复。

撤销只覆盖最近几分钟。画布版本历史能把你选定的状态保留几天甚至几个月,比如「已发给客户」。版本是文档的完整副本,保存在你的存储适配器里。

正在加载在线示例,代码在下方。

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>
  );
}

保存和列出版本

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

listVersions 返回版本摘要,最新的在前:{ id, documentId, name, kind, createdAt, revision }。kind 为 named 表示命名版本,为 auto 表示自动版本。

恢复版本

await engine.restoreVersion(version.id);

恢复版本时不会丢任何内容:

  1. 引擎先保存一个名为 Before restoring "..." 的自动版本。
  2. 然后把旧内容作为同一份文档的一个新的、未保存的修订加载进来。
  3. 下一次保存会把它存为最新修订,所以画布版本历史始终是线性的。

想撤销这次恢复,恢复那个自动版本就行。

自动版本

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

这样每成功保存 10 次就会留一个自动版本,并且只保留最新的 20 个自动版本。命名版本永远不会被清理。

删除版本

await engine.deleteVersion(version.id);

存储支持

内置适配器都支持版本。自定义适配器需要多实现四个方法:saveVersion、listVersions、loadVersion 和 deleteVersion。缺少它们时,版本相关的调用会以 VERSIONS_UNSUPPORTED 失败。想给你自己的 API 加上画布版本历史,请看自定义后端。