撤销只覆盖最近几分钟。画布版本历史能把你选定的状态保留几天甚至几个月,比如「已发给客户」。版本是文档的完整副本,保存在你的存储适配器里。
正在加载在线示例,代码在下方。
"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);恢复版本时不会丢任何内容:
- 引擎先保存一个名为
Before restoring "..."的自动版本。 - 然后把旧内容作为同一份文档的一个新的、未保存的修订加载进来。
- 下一次保存会把它存为最新修订,所以画布版本历史始终是线性的。
想撤销这次恢复,恢复那个自动版本就行。
自动版本
createDocumentEngine({
canvas,
storage,
versions: { autoEvery: 10, keepAuto: 20 },
});这样每成功保存 10 次就会留一个自动版本,并且只保留最新的 20 个自动版本。命名版本永远不会被清理。
删除版本
await engine.deleteVersion(version.id);存储支持
内置适配器都支持版本。自定义适配器需要多实现四个方法:saveVersion、listVersions、loadVersion 和 deleteVersion。缺少它们时,版本相关的调用会以 VERSIONS_UNSUPPORTED 失败。想给你自己的 API 加上画布版本历史,请看自定义后端。