Files
Endoleap/docs/CORE_CANVAS_API.md
T

117 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Canvas 数据持久化:交给 Core 的接口需求
## 已核对的现状
核对 Core main `e8ed5e4f9ffc526f4a42b001850ffc240420a1e0`:
- `internal/platform/materials.go` 的 material GET / PATCH 保存名称、描述、content,并以带引号的 `If-Match` 控制版本。
- `internal/platform/content.go` 的 `Content` 只有 schema_version/text/article/image/parts,解码器使用 `DisallowUnknownFields`;board 只允许空内容 `{ "schema_version": 1 }`。直接提交 nodes、edges 或样式会被拒绝,不能用现有接口冒充已存库。
- 未发现 Canvas 快照读写接口。当前前端节点、文本样式、图标、角度、尺寸和连线仍保存在本地草稿及导出的 JSON,尚未存入数据库。
## 必需接口
建议为 board 增加独立 Canvas 快照,使用独立的版本号,避免改名与编辑画布互相覆盖。
后端路径:`/projects/{project}/workspaces/{workspace}/materials/{board}/canvas`
前端同源代理路径:`/api/foundry/workspaces/{workspace}/materials/{board}/canvas`。Core 完成后前端需扩展当前代理白名单,并接入下述读写接口;本次没有向不存在的接口发送请求。
### GET
响应 `200`,`ETag: "7"`:
```json
{
"version": 7,
"document": {
"schema_version": 1,
"nodes": [
{
"id": "text-node-1",
"type": "material",
"position": { "x": 100, "y": 200 },
"width": 320,
"height": 220,
"data": {
"kind": "text",
"title": "灵感笔记",
"text": "正文",
"rotation": 30,
"fontSize": 24,
"textStyle": {
"color": "#37352f",
"background": "transparent",
"bold": true,
"italic": false,
"underline": false,
"align": "left",
"fontFamily": "sans",
"lineHeight": 1.5
}
}
},
{
"id": "icon-node-1",
"type": "icon",
"position": { "x": 500, "y": 200 },
"width": 80,
"height": 80,
"data": {
"kind": "icon",
"title": "火箭",
"text": "",
"icon": "Rocket",
"rotation": 0,
"textStyle": { "color": "#78523f" }
}
}
],
"edges": [],
"viewport": { "x": 0, "y": 0, "zoom": 1 }
}
}
```
没有快照的旧 board 应返回 version=1 的空图谱(nodes/edges 为空、viewport 为 0/0/1),可自动初始化。非 board 或不可访问的材料按已有资源错误约定返回。
### PATCH
请求 `If-Match: "7"`,body 为 `{ "document": { ...完整快照... } }`,原子替换整张图谱,成功响应 `200`,body `{ "version": 8 }`,`ETag: "8"`。
- 必须校验 project/workspace/board 归属及现有成员/材料授权;GET 允许 viewer,PATCH 只允许 admin/editor。
- 缺版本返回 428,版本冲突返回 412 / `version_conflict`,冲突不得覆盖数据库内容。
- 无效快照返回 400 / `invalid_canvas`,超限返回 413;拒绝时不能部分写入。
- 返回持久化版本,刷新、重新登录或另一台设备 GET 时必须能还原全部内容。
- Canvas 与材料名称/描述分别更新,不能在保存快照时覆盖名称,也不能在改名时清空快照。
## 数据字段与校验
以本仓库 `src/lib/canvas.ts` 的 `decodeCanvas` 和 `src/lib/canvas-elements.ts` 为当前前端契约:
| 数据 | 必须保留或校验的内容 |
| --- | --- |
| 节点类型 | text/image/script 使用 material;plain-text 使用 plainText;icon 使用 icon |
| 几何 | position.x/y 为有限数、绝对值不超过 1,000,000;width/height 成对、20–3000;rotation 为 [0,360) |
| 文本 | title 最长 300 字符,text 最长 100,000 字符;fontSize 8–160 |
| 样式 | color 为六位 hex;background 为六位 hex 或 transparent;bold/italic/underline 为布尔;align=left/center/right;fontFamily=sans/serif/mono;lineHeight=1–3 |
| 图标 | icon 为 Lucide PascalCase 标识,当前例如 Star/Rocket/Heart;颜色及尺寸由相同样式/几何字段确定 |
| 图片 | src 为 HTTPS 图片 URL 或当前前端支持的 PNG/JPEG/WebP/GIF data URL;单次本地选图最大 2 MB |
| 连线 | id/source/target/sourceHandle/targetHandle;四侧 handle=top/right/bottom/left;保留旧无名连接点兼容;引用必须存在,不允许连接纯文本或图标 |
| 视图 | viewport.x/y 为有限数,zoom=0.1–4 |
最多 500 个节点、2000 条连线;节点与连线 ID 各自唯一。selected/dragging/measured 等 UI 临时状态不入库。节点 style.width/height 是前端尺寸镜像,可不持久化,恢复时用 width/height 生成。连线按贝塞尔曲线渲染。
快照请求体建议支持至少 5 MiB(目前 Core 和前端代理都是 1 MiB,需要同步调整该路由的限制),否则一张 2 MB 图片转为 data URL 后就无法保存。限制应按 UTF-8 字节计算。若 Core 选择使用独立图片上传接口代替 data URL,请提供上传和持久 URL 返回契约,前端再替换图片保存方式。
## 图标是否需要专门的后端服务
本次图标由已有 npm 包 `lucide-react@1.69.0` 本地渲染,前端提供 45 个图标和搜索。添加、搜索与显示不需要图标下载或目录 API;数据库只需通过 Canvas 快照保存 icon 标识、颜色、位置、尺寸和旋转角度,不保存 SVG 源码。后续如支持多个图标库,再为节点增加图标库及版本标识。
## Core 交付验收
1. 写入含中文文本、全部 textStyle 字段、旋转尺寸、图标和四向连线的快照,重新 GET 完全还原。
2. 两个客户端持相同版本写入,只允许一个成功,另一个收到 412。
3. viewer、其他 workspace 成员及未登录用户不能越权写入;非法引用、无效样式、超限请求不能留下部分数据。
4. 改名不清空快照,保存快照不改变名称;旧 board 可读取空快照;包含一张 2 MB 图片的合法快照能够保存和还原。
5. 接口上线后前端接入:优先读取服务端快照、提供本地旧草稿迁移、串行自动保存与冲突处理,并验证跨设备恢复;在此之前不能将前端本地持久化称为数据库保存。