9.4 KiB
OpenNodes V1 — Vector、Icon 和 Path
本文件是 DWS OpenNodes V1 协议的拆分章节。按需读取入口见 协议索引。
7.9 Vector(已上传 SVG/矢量资源)
vector 表示已经通过 dws doc media upload 获得稳定引用的 SVG/矢量图片。
OpenNodes 只接收上传结果中的资源引用,不接收本地路径或原始 SVG/XML 内容。
上传时必须使用与后续白板更新相同的文档 nodeId:
dws doc media upload \
--node <DOC_NODE_ID> \
--file ./icon.svg \
--mime-type image/svg+xml \
--format json
将上传结果的 resourceId 和 resourceUrl 分别写入
resource.resourceId 和 resource.url。
Update 必须提供完整的托管资源信息:
interface OpenManagedVectorResourceWrite {
kind: "managed";
resourceId: string;
url: string;
}
完整节点结构见第 6 节 OpenVectorNodeWrite。
resource 字段规则:
| 字段 | 必填 | 规则 |
|---|---|---|
kind |
是 | 当前只允许固定值 managed,表示资源已经通过 DWS 上传。 |
resourceId |
是 | 资源稳定 ID,长度 1~256,只允许字母、数字、.、_、:、-。 |
url |
是 | 已上传资源地址,最长 4096;必须包含且只能包含一个同值的 resourceId 查询参数。 |
url 必须直接使用 dws doc media upload 返回的 resourceUrl,不得自行拼装
或修改。
以下内容会被拒绝:
- 原始 SVG/XML、
data:、blob:、http:URL。 //host/path协议相对地址,以及自行构造或修改的其他相对地址。- 含空白、控制字符、反斜杠、fragment 或用户凭证的 URL。
- URL 缺少
resourceId、重复出现resourceId,或者 URL 中 ID 与显式resource.resourceId不一致。 resource缺失、字段不完整、kind不是managed,或包含未知字段。
完整 Append 示例:
{
"source": {
"schemaVersion": "1.0",
"catalogVersion": "dml-v1",
"nodes": [
{
"id": "uploaded-svg-1",
"type": "vector",
"x": 100,
"y": 80,
"width": 240,
"height": 180,
"angle": 0,
"resource": {
"kind": "managed",
"resourceId": "0c1c94e1-f9af-4228-b32f-42bbd1555253",
"url": "https://resources.example.com/assets/opaque-path?resourceId=0c1c94e1-f9af-4228-b32f-42bbd1555253"
}
}
]
},
"overwrite": false
}
示例中的 resources.example.com 是占位域名,实际调用必须使用
dws doc media upload 返回的 resourceUrl。
resourceId 同时显式出现并包含在 URL 中,用于校验资源身份与地址是否一致。
两者必须来自同一次 dws doc media upload 结果。
Query 的 resource 可能是:
type OpenVectorResource =
| { kind: "managed"; resourceId: string; url: string }
| { kind: "external" | "embedded" | "unresolved" };
- 能识别为托管资源的引用返回完整
managed信息。 - HTTP(S) 外链但不满足托管资源契约时返回
external。 data:/blob:返回embedded。- 其他缺失或无法识别的地址返回
unresolved。 - 非
managed资源会令节点只读,并分别产生vector.resource.external、vector.resource.embedded或vector.resource.unresolved。
V1 vector update 不接受 style。既有节点包含 V1 无法表达的 fill、stroke、
opacity、effect 或 adjustments 时,query 仍可读取资源和几何,但节点会标为只读。
不影响资源内容的兼容性装饰不会单独令节点变为只读。
DWS 会校验资源引用。上传与 whiteboard update 必须使用同一个文档
nodeId;不要跨文档复用资源,也不要使用临时 uploadUrl。
7.10 Icon(内置图标)
icon 表示内置图标。OpenNodes 使用版本化的 catalogId 作为稳定标识,
允许值见下列类型定义和附录 B。
Update 结构:
type OpenIconCatalogId =
| `emoji/${
| "happy"
| "smile"
| "laugh"
| "fighting"
| "like"
| "ok"
| "please"
| "face-plam"
| "tears-of-joy"
| "cry"
| "question"
| "face-with-sweat"
| "bloody-nose"
| "doggy"}`
| `tools/${
| "pad"
| "blue-note"
| "yellow-notes"
| "chart"
| "chart-2"
| "pencil"
| "pen"
| "bag"
| "rocket"
| "fire"
| "gold"
| "light"
| "pin"
| "red-flag"
| "tea"
| "island"
| "ball"
| "lucky-fish"
| "coffee"
| "milky-tea"
| "pan"}`
| `priority/priority-${1 | 2 | 3 | 4 | 5 | 6 | 7}`
| `task/${
| "task-start"
| "task-oct"
| "task-3oct"
| "task-half"
| "task-5oct"
| "task-7oct"
| "task-done"}`;
完整节点结构见第 6 节 OpenIconNodeWrite。
完整 Append 示例:
{
"source": {
"schemaVersion": "1.0",
"catalogVersion": "dml-v1",
"nodes": [
{
"id": "pencil-icon",
"type": "icon",
"x": 100,
"y": 80,
"width": 48,
"height": 48,
"catalogId": "tools/pencil"
}
]
},
"overwrite": false
}
规则:
catalogId必填,格式为<group>/<name>,并且必须精确命中附录 B 的dml-v1allowlist;大小写、连字符和历史拼写都不能自行修正。- 当前目录有
emoji、tools、priority、task四组,共 49 项。 - update 只接受
catalogId,不接受group、name、style或resource。 - 内置 icon 不需要调用方上传资源,也不需要
resourceId。 - 任意已上传 SVG 或自定义图标应使用
vector,并按 7.9 节提供完整resource信息,不能伪造一个 iconcatalogId。 - icon 可以作为 group/frame 子节点;V1 connector 的 node 端点当前仍不接受 icon 作为目标。
Query 返回相同的 catalogId。既有 icon 无法映射到当前目录时,节点仍会以
type: "icon" 返回以便诊断,但 writeSupport 为 readOnly,原因包含
icon.catalogId。非默认 opacity、filter/effect、text 或 adjustments 同样会令节点
只读。
7.11 Path(自由画笔)
path 表示自由画笔轨迹。OpenNodes 保留两组互相独立的尺寸:
- 节点公共
width/height是画布上的实际渲染尺寸,缩放节点时会变化。 path.intrinsicWidth/path.intrinsicHeight是 SVG path 自身的坐标空间尺寸, 表示path.data使用的内部坐标空间。
interface OpenPathData {
data: string;
intrinsicWidth: number;
intrinsicHeight: number;
}
type OpenPathDataWrite = OpenPathData;
完整 update 节点结构见第 6 节 OpenPathNodeWrite。query 和 update 的 path
字段结构相同,但 update 仍必须满足下方命令子集和大小限制。
{
"id": "freehand-stroke",
"type": "path",
"x": 120,
"y": 100,
"width": 500,
"height": 150,
"path": {
"data": "M0,75 Q50,0 100,75 Q150,150 200,75 Q250,0 300,75 Q350,150 400,75 Q450,0 500,75",
"intrinsicWidth": 500,
"intrinsicHeight": 150
},
"style": {
"fill": { "type": "none" },
"stroke": {
"paint": { "type": "solid", "color": "#7C3AED" },
"width": 10,
"lineCap": "round",
"lineJoin": "round"
}
}
}
下面是一个仍然只使用 V1 命令子集、但包含 12 段二次贝塞尔曲线的蝴蝶轮廓。
它显式回到起点,因此不需要使用尚未支持的 Z:
{
"id": "complex-butterfly-path",
"type": "path",
"x": 1500,
"y": 1215,
"width": 500,
"height": 420,
"path": {
"data": "M250,180 Q210,105 145,70 Q55,25 35,110 Q10,195 125,220 Q35,275 80,355 Q125,415 210,315 Q235,285 250,250 Q265,285 290,315 Q375,415 420,355 Q465,275 375,220 Q490,195 465,110 Q445,25 355,70 Q290,105 250,180",
"intrinsicWidth": 500,
"intrinsicHeight": 420
},
"style": {
"opacity": 0.96,
"fill": {
"type": "solid",
"color": "#EDE9FE",
"opacity": 0.72
},
"stroke": {
"paint": {
"type": "solid",
"color": "#6D28D9"
},
"width": 8,
"lineCap": "round",
"lineJoin": "round"
}
}
}
V1 path 写入约束如下:
data必须是一个绝对M,后跟至少一个显式写出的绝对Q;不接受相对命令, 也不接受L、C、A、Z等通用 SVG 命令。- 所有命令参数必须完整且为有限数值;单节点
data最长 1 MiB,最多 50,000 个 命令。 intrinsicWidth和intrinsicHeight必须为有限正数;它们不要求等于节点的width和height。- 未传
style时,默认使用透明填充、#222222描边、宽度 5,以及 round line cap/join。需要稳定视觉结果时仍应显式传入style。 - path 可以作为 group/frame 的子节点,也可以作为同一 update 请求中 connector 的 node 端点。
query 会把其他能够解析的 SVG path 保留为 type: "path",但标记为
readOnly,原因包含 path.commands;超出上述 V1 限制时使用
path.data.size 或 path.commands.limit。既有 path 带 text、adjustments、非
nonzero fillRule 或 V1 无法表达的样式时也会只读。仅包含不影响画笔几何的
兼容性信息时,不会因此变为只读。theme stroke 遵循通用 style 契约:query 会
保留 token 和明暗参数;只要 token 能在当前白板主题中解析,就可以按 theme
paint 回写。