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.
"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:
- The engine first keeps an automatic version named
Before restoring "...". - It then loads the old content as a new, unsaved revision of the same document.
- 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.