@paircode/tool-art 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/client.js +9 -12
  2. package/index.js +135 -16
  3. package/package.json +1 -2
package/client.js CHANGED
@@ -1,4 +1,4 @@
1
- // tool-art — client 半:注册「画板」插件面板并注入 bundle
1
+ // tool-art — client 半:注册「画板」主内容区视图并注入 bundle
2
2
  //
3
3
  // ★ 2026-09-17 合并:本插件原本拆成 tool-art(工具面)+ ui-art(UI 面)两个独立
4
4
  // 发布包,同属一个创作域却分两处维护(版本/文档/发布各一份,且用户看不出「AI 用的
@@ -10,8 +10,12 @@
10
10
  // 区域发现对「同包同时含 dsh.ui + dsh.ui.build」的包生效,与工具面共存无冲突)。
11
11
  // external 共享核心(Vue/api 等)从 window.__PAIRCODE_CORE 取。
12
12
  //
13
- // 与其余五域(design/model/music/rig/voice)同包面板同样走通用 ui.registerPanel
14
- // (不动壳、纯加法,卸载插件即消失)。
13
+ // 2026-09-18:不再注册进「插件面板」(ui.registerPanel)—— 那是壳级逃生口的
14
+ // 管理/总览区(docs/plugin-development.md §12 分工表),六域是「编辑器式工作台」,
15
+ // 主内容区视图(ui.registerView)才是它的位置。此前两处同时注册:插件面板里多一个
16
+ // 重复入口,且两处各 mount 一份 Vue 实例(状态不同步)。现只保留 view。
17
+ //
18
+ // 与其余五域同构:只注册主内容区视图(不动壳、纯加法,卸载插件即消失)。
15
19
  (ui) => {
16
20
  const GLOBAL = 'ArtPanel'
17
21
  const JS = 'plugins-assets/tool-art/assets/art-panel.js'
@@ -26,20 +30,13 @@
26
30
  document.head.appendChild(link)
27
31
  }
28
32
 
29
- // 注册面板(bundle 就绪时立即注册;否则先注入 script,onload 后注册)
33
+ // 注册视图(bundle 就绪时立即注册;否则先注入 script,onload 后注册)
30
34
  const register = () => {
31
35
  const bundle = window[GLOBAL]
32
36
  if (!bundle || typeof bundle.mount !== 'function') return false
33
- ui.registerPanel({
34
- id: 'art-panel',
35
- title: '画板',
36
- icon: 'grid',
37
- render: (el) => bundle.mount(el),
38
- })
39
- // ★ 中间区域视图(2026-09 ui.registerView):与「对话 / 编辑器 / 市场 / 工具集」
37
+ // ★ 主内容区视图(2026-09 ui.registerView):与「对话 / 编辑器 / 市场 / 工具集」
40
38
  // 同级的主内容区 tab。open:true → 默认以「后台 tab」形式出现,不抢占对话主视图
41
39
  // (mainTab 仍是 conversation),点 tab 才激活;可与对话并排(「并排对话」按钮)。
42
- // 与上面的插件面板共用同一 bundle(render 每次调用各建独立 Vue 实例)。
43
40
  ui.registerView({
44
41
  id: 'art-view',
45
42
  title: '画板',
package/index.js CHANGED
@@ -1257,6 +1257,50 @@ function makeShape(proj, spec) {
1257
1257
  return sh;
1258
1258
  }
1259
1259
 
1260
+ // ── 批量新增图元(art_add 与 art_project create 的 shapes 共用)────
1261
+ // 与 art_edit 的 shape.add 同一套构造/校验(都走 makeShape),差别只在「N 个图元一次调用」:
1262
+ // 省掉手写 N 条 op 的样板。事务性:逐项入列(后一项才看得见前一项占用的 id/z),
1263
+ // 全部通过后由调用方落盘一次;任一项失败直接抛出 → 调用方走不到 saveProject,工程保持原样。
1264
+ var SHAPE_SPEC_FIELDS = ['id', 'name', 'type', 'layer', 'x', 'y', 'w', 'h', 'r', 'rx', 'ry',
1265
+ 'cx', 'cy', 'x1', 'y1', 'x2', 'y2', 'points', 'd', 'text', 'fontSize', 'fontWeight',
1266
+ 'anchor', 'fill', 'stroke', 'strokeWidth', 'opacity'];
1267
+
1268
+ function isPlainObj(v) { return !!v && typeof v === 'object' && !(v instanceof Array); }
1269
+
1270
+ // 单件写法:把同层参数收拢成一个图元描述(与 art_edit op=shape.add 的同层参数一致)
1271
+ function shapeSpecFromArgs(args) {
1272
+ var spec = {};
1273
+ for (var i = 0; i < SHAPE_SPEC_FIELDS.length; i++) {
1274
+ if (args[SHAPE_SPEC_FIELDS[i]] !== undefined) spec[SHAPE_SPEC_FIELDS[i]] = args[SHAPE_SPEC_FIELDS[i]];
1275
+ }
1276
+ return spec;
1277
+ }
1278
+
1279
+ // 批量入列;specs 为 undefined/null 视为「没传」(返回空数组,供 create 复用)
1280
+ function applyShapeSpecs(proj, specs, what) {
1281
+ if (specs === undefined || specs === null) return [];
1282
+ if (!(specs instanceof Array)) throw new Error((what || 'shapes') + ' 必须是数组');
1283
+ if (!specs.length) throw new Error((what || 'shapes') + ' 是空数组(要么不传,要么至少给一个图元)');
1284
+ var added = [];
1285
+ for (var i = 0; i < specs.length; i++) {
1286
+ if (!isPlainObj(specs[i])) throw new Error((what || 'shapes') + '[' + i + '] 必须是对象');
1287
+ var op = { op: 'shape.add' };
1288
+ for (var k in specs[i]) {
1289
+ if (Object.prototype.hasOwnProperty.call(specs[i], k)) op[k] = specs[i][k];
1290
+ }
1291
+ var log;
1292
+ try {
1293
+ log = applyOps(proj, [op]);
1294
+ } catch (e) {
1295
+ throw new Error('第 ' + (i + 1) + ' 个图元' + (specs[i].id ? '(' + specs[i].id + ')' : '') +
1296
+ '新增失败,整批未写入任何改动:' + ((e && e.message) || e));
1297
+ }
1298
+ var sh = proj.shapes[proj.shapes.length - 1];
1299
+ added.push({ id: sh.id, type: sh.type, layer: sh.layer, z: sh.z, detail: log[0] });
1300
+ }
1301
+ return added;
1302
+ }
1303
+
1260
1304
  // ── op 引擎(命令式编辑链,与 UI 操作同源) ────────────────
1261
1305
  function applyOps(proj, ops) {
1262
1306
  var log = [];
@@ -1892,8 +1936,18 @@ function artProject(args, exec, ctx) {
1892
1936
  }
1893
1937
  proj.layers = lys;
1894
1938
  }
1939
+ // ★ 一次调用成型:create 时可带 shapes=[…](每项与 shape.add 同构)——
1940
+ // 省掉「先 create 再逐条 art_edit」的往返;任一项校验失败整批不落盘(不留半成品工程)。
1941
+ var created = applyShapeSpecs(proj, args.shapes, 'shapes');
1895
1942
  saveProject(ctx, path, proj);
1896
- return '✅ 已创建画板工程: ' + path + '\n\n' + projectSummary(proj, path);
1943
+ var resNew = {
1944
+ ok: true, action: 'created', path: path,
1945
+ layers: proj.layers.length, shapes: proj.shapes.length,
1946
+ summary: projectSummary(proj, path),
1947
+ next: 'art_add 批量加图元 / art_edit 调整(align/distribute/set)/ art_export 出 SVG / art_verify 校验',
1948
+ };
1949
+ if (created.length) resNew.added = created;
1950
+ return JSON.stringify(resNew, null, 2);
1897
1951
  }
1898
1952
  if (mode === 'show') {
1899
1953
  var p1 = loadProject(ctx, path);
@@ -1933,10 +1987,50 @@ function artEdit(args, exec, ctx) {
1933
1987
  }];
1934
1988
  }
1935
1989
  if (!ops || !ops.length) throw new Error('需要 ops 数组(或 op + 同层参数)');
1936
- var log = applyOps(proj, ops);
1990
+ // 逐条应用 = 一次 load / 一次 save:批量语义不变(同一内存工程按序改),
1991
+ // 但能精确报出是哪一条失败,并保证「任一 op 失败整批不落盘」(saveProject 在循环之后)。
1992
+ var log = [];
1993
+ for (var i = 0; i < ops.length; i++) {
1994
+ var one;
1995
+ try {
1996
+ one = applyOps(proj, [ops[i]]);
1997
+ } catch (e) {
1998
+ throw new Error('第 ' + (i + 1) + ' 条 op(' + ((ops[i] && ops[i].op) || '?') + ')失败,整批未写入任何改动:' +
1999
+ ((e && e.message) || e));
2000
+ }
2001
+ log = log.concat(one);
2002
+ }
2003
+ saveProject(ctx, path, proj);
2004
+ // 结构化返回(与 tool-model / tool-rig 同一口径):ok/applied/log 供程序化消费,
2005
+ // summary 保留人类可读摘要 —— 信息量不比旧的纯文本少。
2006
+ return JSON.stringify({
2007
+ ok: true, action: 'edited', path: path, applied: ops.length, log: log,
2008
+ shapes: proj.shapes.length, layers: proj.layers.length,
2009
+ summary: projectSummary(proj, path),
2010
+ next: 'art_add 批量加图元 / art_edit 继续调整 / art_export 出 SVG / art_verify 校验',
2011
+ }, null, 2);
2012
+ }
2013
+
2014
+ // ── art_add:批量新增图元(一次调用 N 个,事务式)────────────────
2015
+ // 与 tool-model 的 model_add(parts=[…])同一形态:单件写法兼容 + 数组批量 + 整批不落盘。
2016
+ function artAdd(args, exec, ctx) {
2017
+ var path = argStr(args, 'path', 'art.project.json');
2018
+ var proj = loadProject(ctx, path);
2019
+ var specs;
2020
+ if (args.shapes !== undefined) {
2021
+ specs = args.shapes; // 批量写法:以 shapes 为准(忽略同层单件字段)
2022
+ } else {
2023
+ if (!args.type) throw new Error('art_add 需要 shapes 数组(批量);或给 type 等几何参数(单件)');
2024
+ specs = [shapeSpecFromArgs(args)]; // 单件写法:同层参数收拢成一个图元
2025
+ }
2026
+ var added = applyShapeSpecs(proj, specs, 'shapes');
1937
2027
  saveProject(ctx, path, proj);
1938
- return '✅ 已应用 ' + ops.length + ' 条编辑命令到 ' + path + '\n\n' + log.join('\n') +
1939
- '\n\n' + projectSummary(proj, path);
2028
+ var res = {
2029
+ ok: true, action: 'added', path: path, added: added, shapes: proj.shapes.length,
2030
+ next: 'art_edit 调整(align/distribute/set/move)/ art_export 出 SVG / art_verify 校验',
2031
+ };
2032
+ if (added.length === 1) { res.id = added[0].id; res.shape = added[0]; } // 单件写法兼容旧返回体
2033
+ return JSON.stringify(res, null, 2);
1940
2034
  }
1941
2035
 
1942
2036
  function artImport(args, exec, ctx) {
@@ -2003,13 +2097,13 @@ function artVerify(args, exec, ctx) {
2003
2097
  var TOOL_DEFS = [
2004
2098
  {
2005
2099
  name: 'art_project',
2006
- description: '矢量画板工程管理(文本真相源 art.project.json):创建/查看/更新画布尺寸、背景色、色板、图层。图元几何用绝对坐标 + 可选 2D 仿射矩阵 transform;SVG 是唯一文本产物(零依赖生成、可回读)。PNG 光栅化不在沙箱内(见 art_export)。',
2007
- usageGuide: '创作第一步:mode=create 建画板(width/height/background,可带 layers=[{id,name}])→ art_edit 加图元(shape.add)与调整(move/resize/set/align/distribute/z)→ art_export 出 SVG → art_verify 校验。mode=show 只看摘要,mode=update 改画布属性。已有 SVG 素材请用 art_import 导入。',
2100
+ description: '矢量画板工程管理(文本真相源 art.project.json):创建/查看/更新画布尺寸、背景色、色板、图层,并可在创建时一次带入整批图元(shapes=[…])。图元几何用绝对坐标 + 可选 2D 仿射矩阵 transform;SVG 是唯一文本产物(零依赖生成、可回读)。PNG 光栅化不在沙箱内(见 art_export)。',
2101
+ usageGuide: '创作第一步:mode=create 建画板(width/height/background,可带 layers=[{id,name}],也可带 shapes=[{type:"rect",…},{type:"text",…}] 一次把画面画好)→ art_add 批量加图元 / art_edit 调整(move/resize/set/align/distribute/z)→ art_export 出 SVG → art_verify 校验。mode=show 只看摘要,mode=update 改画布属性。已有 SVG 素材请用 art_import 导入。',
2008
2102
  category: '创作',
2009
2103
  parameters: {
2010
2104
  type: 'object',
2011
2105
  properties: {
2012
- path: { type: 'string', description: '工程文件路径(默认 art.project.json;相对主项目根解析)' },
2106
+ path: { type: 'string', description: '工程文件路径(默认 <主项目根>/art.project.json;相对主项目根解析,跨项目传绝对路径)' },
2013
2107
  mode: { type: 'string', description: 'create(新建)| show(默认,查看摘要)| update(改画布属性)' },
2014
2108
  title: { type: 'string', description: '可选:画板标题' },
2015
2109
  width: { type: 'integer', description: '可选:画布宽(像素,1..8192,默认 800)' },
@@ -2017,19 +2111,43 @@ var TOOL_DEFS = [
2017
2111
  background: { type: 'string', description: '可选:背景色(#RRGGBB / rgb() / 命名色 / none)' },
2018
2112
  palette: { type: 'array', description: '可选:色板(颜色字符串数组,默认 Tailwind 标准 10 色)' },
2019
2113
  layers: { type: 'array', description: '可选(仅 create):图层定义 [{id,name,visible}]' },
2114
+ shapes: { type: 'array', description: '可选(仅 create):一次带入的图元数组(每项与 art_add 的 shapes 项 / art_edit 的 shape.add 同构);任一项非法则整批不落盘' },
2020
2115
  overwrite: { type: 'boolean', description: '可选(仅 create):已存在时是否重建(默认 false)' },
2021
2116
  },
2022
2117
  },
2023
2118
  },
2119
+ {
2120
+ name: 'art_add',
2121
+ description: '批量新增图元到画板工程(一次调用任意多个,事务式)。shapes=[{type:"rect",x:…,y:…,w:…,h:…,fill:…},{type:"text",x:…,y:…,text:…}] —— 每项与 art_edit 的 shape.add 同构(rect/circle/ellipse/line/polyline/polygon/path/text;字段 x/y/w/h/rx/cx/cy/r/points/d/text/fontSize/fontWeight/anchor/fill/stroke/strokeWidth/opacity/layer/id/name)。也支持单件写法(type + 同层参数)。全部校验通过才落盘一次,任一项失败整批不写入(工程保持原样)。',
2122
+ usageGuide: '一次把画面画出来:art_add shapes=[{"type":"rect","x":40,"y":40,"w":320,"h":180,"fill":"#2563EB","rx":8},{"type":"text","x":40,"y":80,"text":"标题","fontSize":24,"fontWeight":"600","fill":"#111827"}]。id 不传自动分配(s1、s2…),z 按图层内顺序自动排。新增后要调位置/样式用 art_edit(shape.move/resize/set/align/distribute),出图用 art_export,交付前 art_verify。',
2123
+ category: '创作',
2124
+ parameters: {
2125
+ type: 'object',
2126
+ properties: {
2127
+ path: { type: 'string', description: '可选:工程路径(默认 <主项目根>/art.project.json;相对主项目根解析)' },
2128
+ shapes: { type: 'array', description: '图元描述数组(每项与 art_edit 的 shape.add 同构;批量写法)' },
2129
+ type: { type: 'string', description: '可选(单件写法):图元类型 rect/circle/ellipse/line/polyline/polygon/path/text' },
2130
+ id: { type: 'string', description: '可选(单件写法):图元 id(默认自动分配)' },
2131
+ layer: { type: 'string', description: '可选(单件写法):目标图层 id(默认第一个图层)' },
2132
+ x: { type: 'number', description: '可选(单件写法):x(rect/text 用;其余几何字段建议走 shapes)' },
2133
+ y: { type: 'number', description: '可选(单件写法):y' },
2134
+ w: { type: 'number', description: '可选(单件写法):宽' },
2135
+ h: { type: 'number', description: '可选(单件写法):高' },
2136
+ text: { type: 'string', description: '可选(单件写法):文本内容(type=text 必填)' },
2137
+ fill: { type: 'string', description: '可选(单件写法):填充色(none 表示不填充)' },
2138
+ stroke: { type: 'string', description: '可选(单件写法):描边色' },
2139
+ },
2140
+ },
2141
+ },
2024
2142
  {
2025
2143
  name: 'art_edit',
2026
- description: '向画板工程追加编辑命令(命令式 op,与面板操作同源)。支持:图元(shape.add/remove/duplicate/move/resize/set/text/z)、排版(shape.align 六向对齐 + shape.distribute 等距分布)、图层(layer.add/remove/rename/visible/reorder)、画布与色板(set.canvas/set.palette)、标题(project.rename)。图元类型:rect/circle/ellipse/line/polyline/polygon/path/text。目标选择支持 ids / id / type / layer / name / all 过滤(不指定目标直接报错,避免误改全部)。',
2027
- usageGuide: '示例:{"op":"shape.add","type":"rect","x":40,"y":40,"w":320,"h":180,"fill":"#2563EB","rx":8} / {"op":"shape.add","type":"text","x":40,"y":80,"text":"标题","fontSize":24,"fontWeight":"600","fill":"#111827"} / {"op":"shape.align","type":"rect","to":"hcenter"} / {"op":"shape.distribute","ids":["s1","s2","s3"],"axis":"h"} / move:{"op":"shape.move","ids":["s1"],"to":{"x":100,"y":60}} 或 dx/dy 相对偏移。改完用 art_verify 校验、art_export 出 SVG。',
2144
+ description: '向画板工程追加编辑命令(命令式 op,与面板操作同源)。支持:图元(shape.add/remove/duplicate/move/resize/set/text/z)、排版(shape.align 六向对齐 + shape.distribute 等距分布)、图层(layer.add/remove/rename/visible/reorder)、画布与色板(set.canvas/set.palette)、标题(project.rename)。图元类型:rect/circle/ellipse/line/polyline/polygon/path/text。目标选择支持 ids / id / type / layer / name / all 过滤(不指定目标直接报错,避免误改全部)。纯新增图元用 art_add(shapes 数组一次成型)更省。返回结构化结果(ok/applied/log/summary)。',
2145
+ usageGuide: '示例:{"op":"shape.add","type":"rect","x":40,"y":40,"w":320,"h":180,"fill":"#2563EB","rx":8} / {"op":"shape.add","type":"text","x":40,"y":80,"text":"标题","fontSize":24,"fontWeight":"600","fill":"#111827"} / {"op":"shape.align","type":"rect","to":"hcenter"} / {"op":"shape.distribute","ids":["s1","s2","s3"],"axis":"h"} / move:{"op":"shape.move","ids":["s1"],"to":{"x":100,"y":60}} 或 dx/dy 相对偏移。改完用 art_verify 校验、art_export 出 SVG。 ★ 批量:一次调用可传任意多条 op(按顺序应用;任一 op 失败整批不落盘)—— 图元多时一次调用成型(如 [{op:"shape.add",...},{op:"shape.add",...},{op:"shape.align",...}]),别一条一条调。',
2028
2146
  category: '创作',
2029
2147
  parameters: {
2030
2148
  type: 'object',
2031
2149
  properties: {
2032
- path: { type: 'string', description: '可选:工程路径(默认 art.project.json' },
2150
+ path: { type: 'string', description: '可选:工程路径(默认 <主项目根>/art.project.json;相对主项目根解析)' },
2033
2151
  ops: { type: 'array', description: '编辑命令数组(按顺序应用)' },
2034
2152
  op: { type: 'string', description: '可选:单条 op 名(与同层参数合成为一条命令)' },
2035
2153
  ids: { type: 'array', description: '可选:目标图元 id 数组' },
@@ -2048,8 +2166,8 @@ var TOOL_DEFS = [
2048
2166
  parameters: {
2049
2167
  type: 'object',
2050
2168
  properties: {
2051
- path: { type: 'string', description: '要导入的 SVG 文件路径(相对主项目根解析)' },
2052
- out: { type: 'string', description: '可选:输出的工程路径(默认 art.project.json' },
2169
+ path: { type: 'string', description: '要导入的 SVG 文件路径(相对主项目根解析,跨项目传绝对路径)' },
2170
+ out: { type: 'string', description: '可选:输出的工程路径(默认 <主项目根>/art.project.json;相对主项目根解析)' },
2053
2171
  title: { type: 'string', description: '可选:画板标题(默认取 SVG 的 <title>)' },
2054
2172
  overwrite: { type: 'boolean', description: '可选:目标工程已存在时是否覆盖(默认 false)' },
2055
2173
  },
@@ -2064,9 +2182,9 @@ var TOOL_DEFS = [
2064
2182
  parameters: {
2065
2183
  type: 'object',
2066
2184
  properties: {
2067
- path: { type: 'string', description: '可选:工程路径(默认 art.project.json' },
2185
+ path: { type: 'string', description: '可选:工程路径(默认 <主项目根>/art.project.json;相对主项目根解析)' },
2068
2186
  format: { type: 'string', description: '产物格式:svg(默认,矢量文本产物);png 会返回替代路径说明(沙箱无渲染器)' },
2069
- out: { type: 'string', description: '可选:输出路径(默认 art.svg' },
2187
+ out: { type: 'string', description: '可选:输出路径(默认 <主项目根>/art.svg;相对主项目根解析)' },
2070
2188
  },
2071
2189
  },
2072
2190
  },
@@ -2078,8 +2196,8 @@ var TOOL_DEFS = [
2078
2196
  parameters: {
2079
2197
  type: 'object',
2080
2198
  properties: {
2081
- path: { type: 'string', description: '可选:工程路径(默认 art.project.json' },
2082
- report: { type: 'string', description: '可选:旁挂校验报告路径(默认 art.verify.json)' },
2199
+ path: { type: 'string', description: '可选:工程路径(默认 <主项目根>/art.project.json;相对主项目根解析)' },
2200
+ report: { type: 'string', description: '可选:旁挂校验报告路径(默认 <主项目根>/art.verify.json)' },
2083
2201
  minContrast: { type: 'number', description: '可选:文本对比度统一阈值(默认按 WCAG:4.5,大字 3.0)' },
2084
2202
  },
2085
2203
  },
@@ -2088,6 +2206,7 @@ var TOOL_DEFS = [
2088
2206
 
2089
2207
  var IMPLS = {
2090
2208
  art_project: artProject,
2209
+ art_add: artAdd,
2091
2210
  art_edit: artEdit,
2092
2211
  art_import: artImport,
2093
2212
  art_export: artExport,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paircode/tool-art",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "scope": "global",
5
5
  "type": "plugin",
6
6
  "main": "index.js",
@@ -37,7 +37,6 @@
37
37
  "client.js",
38
38
  "assets",
39
39
  "bin",
40
- "lib",
41
40
  "package.json"
42
41
  ]
43
42
  }