dsh-design-studio 0.11.0 → 0.13.0
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.
- package/README.md +2 -2
- package/assets/demo.gif +0 -0
- package/assets/hero-sm.jpg +0 -0
- package/lib/client.js +1937 -542
- package/lib/core.js +210 -6
- package/lib/draft-page.js +763 -0
- package/lib/draft.js +151 -0
- package/lib/index.js +17 -3
- package/lib/picker.js +227 -1
- package/lib/serve.js +177 -1
- package/lib/tweaks.js +58 -24
- package/package.json +1 -1
- package/skills/ui-design/SKILL.md +35 -5
package/lib/client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-design-studio 的客户端部分
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 右侧栏标签页「设计台」(sidebarRightTabs + sidebar.right.pane.tab)
|
|
5
5
|
* - 工具栏:地址(点一下能改)、选择、画布 / 单页、类型(网页 / App / 平板 / 海报 / 幻灯片 / 邮件)、刷新、在浏览器打开
|
|
6
6
|
* - 画布:同一个目录里的几屏按生成顺序排成网格,套上对应的外框(手机、浏览器窗口 …),能拖、能缩放;
|
|
7
7
|
* 模型还在出新的一屏时,末尾留一个「生成中…」的占位
|
|
@@ -40,13 +40,47 @@ window.__ModuleLoader__.load({
|
|
|
40
40
|
const API = '/dsh-design-studio/api/'
|
|
41
41
|
|
|
42
42
|
const ZH = {
|
|
43
|
-
title: '
|
|
44
|
-
|
|
43
|
+
title: '设计台',
|
|
44
|
+
tabTitle: '设计台',
|
|
45
|
+
guideDesc: '做网页、App、海报,点哪改哪,还能转成代码',
|
|
45
46
|
blockedLink: '已拦下外链:{href}(设计库来的设计不跳到别的网站)',
|
|
46
47
|
blockedDownload: '已拦下下载(设计库来的设计不能下载文件)',
|
|
47
48
|
empty: '还没有要预览的页面',
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
emptyHead: '想做点什么?',
|
|
50
|
+
emptySub: '说一句,设计会一屏一屏出现在画布上;点哪改哪,做完直接转成代码。',
|
|
51
|
+
homeTab: '开始',
|
|
52
|
+
askPh: '比如:{ex}',
|
|
53
|
+
askTab: '用这句',
|
|
54
|
+
exAuto: '巷口面馆的点单小程序,三屏,暖色手作风',
|
|
55
|
+
exWeb: '独立咖啡馆的官网首页:招牌豆子、门店地图、在线预订',
|
|
56
|
+
exApp: '社区健身房的会员 App:约课、打卡、会员卡',
|
|
57
|
+
exMini: '巷口面馆的点单小程序:菜单、购物车、取餐号',
|
|
58
|
+
exTablet: '幼儿园老师用的平板 App:点名、课表、家长消息',
|
|
59
|
+
exDesktop: '小团队记账的桌面软件:流水、报表、设置',
|
|
60
|
+
exPoster: '周末城市夜跑活动的海报,清爽运动风',
|
|
61
|
+
exSlides: '新品发布会的五页幻灯片:问题、方案、数字、价格、下一步',
|
|
62
|
+
exEmail: '会员生日月的优惠邮件:一张大图、一段祝福、一个按钮',
|
|
63
|
+
askKindLine: '类型:{k}',
|
|
64
|
+
askMore: '更多开始方式',
|
|
65
|
+
askMoreD: '打开已有的 HTML、用自己的品牌、从设计库打开',
|
|
66
|
+
askOpenHtml: '打开已有的 HTML…',
|
|
67
|
+
askBrand: '用自己的品牌',
|
|
68
|
+
askLib: '从设计库打开',
|
|
69
|
+
askRef: '参考图',
|
|
70
|
+
askRefD: '照一张截图或设计稿做:选一张,也能直接拖进来、粘贴',
|
|
71
|
+
askStyleAuto: '风格:自动',
|
|
72
|
+
askStyle: '风格:{name}',
|
|
73
|
+
askStyleD: '先定个风格;不选就按题材自动挑一个',
|
|
74
|
+
askStylePick: '挑个风格',
|
|
75
|
+
askStyleAutoBtn: '自动(按题材挑)',
|
|
76
|
+
askSend: '开始设计',
|
|
77
|
+
askSent: '发出去了,第一屏马上出来',
|
|
78
|
+
homeRecent: '接着做',
|
|
79
|
+
homeRecentAll: '全部作品 →',
|
|
80
|
+
waysHtml: '打开已有的 HTML',
|
|
81
|
+
moreWays: '其他开始方式',
|
|
82
|
+
emptyHint: '输入文件路径,或本地端口(比如 5173)',
|
|
83
|
+
emptyHintPick: '点「浏览…」选一个、直接拖进来,或输入文件路径 / 本地端口(比如 5173)',
|
|
50
84
|
browse: '浏览…',
|
|
51
85
|
browseTip: '选一个本地 HTML 文件打开',
|
|
52
86
|
pathNone: '拿不到这个文件的路径,请直接输入它的路径打开',
|
|
@@ -96,7 +130,7 @@ window.__ModuleLoader__.load({
|
|
|
96
130
|
refPick: '照参考图改风格…',
|
|
97
131
|
refChoose: '选一张',
|
|
98
132
|
refPickTip: '选一张参考图,照它的风格改(也可以把图片拖进预览区,或在这里粘贴)',
|
|
99
|
-
refEmpty: '
|
|
133
|
+
refEmpty: '照参考图做:拖进来、粘贴,或',
|
|
100
134
|
refDrop: '松开,作为参考图',
|
|
101
135
|
refDropSub: '存进设计目录的 refs/,提取配色放进输入框',
|
|
102
136
|
refNotImage: '只认图片(PNG / JPG / WebP / GIF)',
|
|
@@ -142,9 +176,10 @@ window.__ModuleLoader__.load({
|
|
|
142
176
|
gShadow: '阴影',
|
|
143
177
|
gOther: '其他',
|
|
144
178
|
exportT: '导出',
|
|
145
|
-
exFrame: '
|
|
146
|
-
exRaw: '
|
|
147
|
-
exLong: '
|
|
179
|
+
exFrame: '{name} · 带外框',
|
|
180
|
+
exRaw: '{name} · 纯画面',
|
|
181
|
+
exLong: '{name} · 整页长图',
|
|
182
|
+
exThisName: '这一屏',
|
|
148
183
|
exShow: '整套展示图',
|
|
149
184
|
exPdf: 'PDF · 每屏一页',
|
|
150
185
|
exPptx: 'PPT · 每屏一页(整页图片)',
|
|
@@ -155,7 +190,8 @@ window.__ModuleLoader__.load({
|
|
|
155
190
|
checking: '正在体检…',
|
|
156
191
|
checkNone: '没发现问题',
|
|
157
192
|
checkSum: '{s} 屏 · {n} 个问题',
|
|
158
|
-
checkFix: '
|
|
193
|
+
checkFix: '发到对话',
|
|
194
|
+
checkFixTip: '把这些问题发到对话,让模型修',
|
|
159
195
|
checkAgain: '再查一次',
|
|
160
196
|
checkFail: '没查成:{msg}',
|
|
161
197
|
checkNote: '只量得出排版上的硬伤,好不好看还得你看',
|
|
@@ -164,7 +200,7 @@ window.__ModuleLoader__.load({
|
|
|
164
200
|
checkTail: '请逐个修好:改源文件,只动有问题的地方;颜色、字号能改设计变量就改设计变量。',
|
|
165
201
|
ckToolHead: '量了 {s} 屏,发现 {n} 个问题。逐个改源文件,只动有问题的地方;颜色、字号能改设计变量就改设计变量:',
|
|
166
202
|
ckToolOk: '量了 {s} 屏({labels}),没发现问题。',
|
|
167
|
-
ckToolOff: '
|
|
203
|
+
ckToolOff: '用户正在看旧版本、试用或多尺寸,右侧栏现在量不了;过一会儿再调,或照「自查」清单自己过一遍。',
|
|
168
204
|
ckToolErr: '{label}:没量成({msg})',
|
|
169
205
|
ck_hscroll: '超出屏幕',
|
|
170
206
|
ck_clipped: '文字被截断',
|
|
@@ -232,7 +268,7 @@ window.__ModuleLoader__.load({
|
|
|
232
268
|
exportedCopied: '已导出 {name},也复制到剪贴板了',
|
|
233
269
|
exportFail: '导出失败:{msg}',
|
|
234
270
|
reveal: '打开文件夹',
|
|
235
|
-
madeWith: '用 DSH
|
|
271
|
+
madeWith: '用 DSH 设计台做的',
|
|
236
272
|
showSub: '{n} 屏 · {kind} · {date}',
|
|
237
273
|
history: '版本',
|
|
238
274
|
histTip: '每轮对话前后各存一版。点一版看当时的样子,还能和现在左右对比',
|
|
@@ -254,19 +290,21 @@ window.__ModuleLoader__.load({
|
|
|
254
290
|
cmpNow: '现在',
|
|
255
291
|
dark: '深色模式',
|
|
256
292
|
variants: '再出两版',
|
|
257
|
-
pickThis: '
|
|
258
|
-
pickShort: '
|
|
293
|
+
pickThis: '用这版:其他版本挪进 drafts/,接着做全套',
|
|
294
|
+
pickShort: '用这版',
|
|
295
|
+
takeN: '第 {l} 版',
|
|
259
296
|
pickText: '〔选定方向〕就用「{name}」({where})。先把同组的 {others} 挪进 drafts/ 子目录(别删),再把它改名成 {base}.html;它的设计变量搬进 tokens.css(没有就新建)、改用 tokens.css。然后接着做全套。\n',
|
|
260
297
|
pickKeep: '〔选定方向〕留原版「{name}」({where})。同组的 {others} 挪进 drafts/ 子目录(别删),其他不动。\n',
|
|
261
298
|
variantText: '给「{name}」再出两版不同方向的设计(布局或视觉风格明显不同),存成同目录下的 {b}-b.html 和 {b}-c.html(原版:{where}),会紧挨着原版排在画布上。原版和 tokens.css 别动;新版要换配色就在自己文件里覆盖变量。\n',
|
|
262
299
|
exThis: '导出这一屏',
|
|
263
|
-
demo: '
|
|
264
|
-
demoOn: '
|
|
300
|
+
demo: '试用:点页面里的按钮跳到别的屏',
|
|
301
|
+
demoOn: '试用中',
|
|
302
|
+
demoHow: '点按钮跳屏',
|
|
265
303
|
demoBack: '返回上一屏',
|
|
266
304
|
demoRestart: '从头开始',
|
|
267
|
-
demoExit: '
|
|
305
|
+
demoExit: '退出试用',
|
|
268
306
|
textEdit: '改字:点页面里的文字直接改,回车保存(Esc 退出)',
|
|
269
|
-
mark: '
|
|
307
|
+
mark: '画笔标注:在页面上画圈、箭头、写字,拼成一张图发到对话',
|
|
270
308
|
mkPen: '画笔',
|
|
271
309
|
mkHl: '荧光笔',
|
|
272
310
|
mkArrow: '箭头',
|
|
@@ -279,7 +317,7 @@ window.__ModuleLoader__.load({
|
|
|
279
317
|
mkUndo: '撤销',
|
|
280
318
|
mkRedo: '重做',
|
|
281
319
|
mkClear: '全部清掉',
|
|
282
|
-
mkHint: '
|
|
320
|
+
mkHint: '在页面上画,画完点「发到对话」',
|
|
283
321
|
mkHow_pen: '按住拖动,随手画',
|
|
284
322
|
mkHow_hl: '按住拖过去,把要改的地方涂亮',
|
|
285
323
|
mkHow_arrow: '从起点按住,拖到要指的地方',
|
|
@@ -290,13 +328,13 @@ window.__ModuleLoader__.load({
|
|
|
290
328
|
mkCoach: '直接在页面上画',
|
|
291
329
|
mkCoach1: '上面挑个工具:画笔、箭头、圆圈、文字…',
|
|
292
330
|
mkCoach2: '在页面上按住拖动,画出要改的地方',
|
|
293
|
-
mkCoach3: '
|
|
331
|
+
mkCoach3: '画完点「发到对话」,再补一句要怎么改',
|
|
294
332
|
mkCancel: '取消',
|
|
295
|
-
mkSend: '
|
|
333
|
+
mkSend: '发到对话',
|
|
296
334
|
mkCopy: '复制',
|
|
297
335
|
mkCopyTip: '把标注图复制到剪贴板,贴到别的地方用',
|
|
298
336
|
mkCopied: '标注图复制好了,贴到别处就行',
|
|
299
|
-
mkCopyFail: '
|
|
337
|
+
mkCopyFail: '没能复制到剪贴板,再点一次「复制」,或者「发到对话」',
|
|
300
338
|
mkSending: '正在拼图…',
|
|
301
339
|
mkTextPh: '写点什么,回车完成',
|
|
302
340
|
mkEmpty: '还没画东西',
|
|
@@ -307,14 +345,14 @@ window.__ModuleLoader__.load({
|
|
|
307
345
|
mkIn: '标注图放进输入框了,补一句要怎么改再发',
|
|
308
346
|
mkMany: '画到的屏太多,只拼了前 {n} 屏,其余留成灰块',
|
|
309
347
|
mkMoved: '设计换了,这次标注没交出去',
|
|
310
|
-
mkNoInput: '
|
|
311
|
-
textSaved: '
|
|
348
|
+
mkNoInput: '没能放进输入框,点一下聊天输入框再点「发到对话」',
|
|
349
|
+
textSaved: '已改好,保存到 {file}',
|
|
312
350
|
textAsk: '把页面上的「{before}」改成「{after}」({where};只改这一处文字,别的不动)。\n',
|
|
313
351
|
styleEdit: '调样式:点页面里的元素,拖滑块改字号、颜色、圆角、间距(Esc 退出)',
|
|
314
352
|
styleAny: '点任意一屏里的元素调样式 · Esc 退出',
|
|
315
353
|
styleOne: '点页面里的元素调样式 · Esc 退出',
|
|
316
354
|
style: '调样式',
|
|
317
|
-
styleNone: '
|
|
355
|
+
styleNone: '在页面上点一个元素。改的时候页面上先看效果,满意了再保存。',
|
|
318
356
|
styleFont: '字号',
|
|
319
357
|
styleWeight: '字重',
|
|
320
358
|
styleColor: '文字',
|
|
@@ -331,14 +369,14 @@ window.__ModuleLoader__.load({
|
|
|
331
369
|
styleVar: '设计变量 {name}',
|
|
332
370
|
styleGradient: '现在是渐变,选一个颜色会换成纯色',
|
|
333
371
|
styleReset: '还原',
|
|
334
|
-
styleAsk: '
|
|
335
|
-
styleSave: '
|
|
372
|
+
styleAsk: '发到对话',
|
|
373
|
+
styleSave: '直接保存',
|
|
336
374
|
styleSaveTip: '写进这个元素在源码里的 style 属性(改前改后各存一版)',
|
|
337
375
|
styleAskTip: '让模型改进样式表(能用设计变量就用)',
|
|
338
|
-
styleNoWrite: '
|
|
339
|
-
styleBusyTip: '
|
|
340
|
-
styleSaved: '
|
|
341
|
-
styleMiss: '
|
|
376
|
+
styleNoWrite: '开发服务器的页面不能直接保存,发到对话让模型改',
|
|
377
|
+
styleBusyTip: '模型在干活,等它停了再保存',
|
|
378
|
+
styleSaved: '样式已保存到 {file}',
|
|
379
|
+
styleMiss: '源码里没找到这个元素(可能是脚本生成的),改成发到对话',
|
|
342
380
|
styleN: '改了 {n} 项',
|
|
343
381
|
styleAskText: '〔调样式〕{name}({sel}):{list}。{rules}把这些写进它的样式规则({where};能用设计变量就用设计变量),别写成行内 style。\n',
|
|
344
382
|
ruleFrom: '- {props}:现在由 {sel} 决定({at})',
|
|
@@ -349,11 +387,13 @@ window.__ModuleLoader__.load({
|
|
|
349
387
|
styleArrow: '{prop} {from} → {to}',
|
|
350
388
|
styleJoin: ';',
|
|
351
389
|
lang: 'zh',
|
|
352
|
-
stylesHead: '
|
|
353
|
-
stylesTip: '
|
|
390
|
+
stylesHead: '或者先挑个风格',
|
|
391
|
+
stylesTip: '点开看样板,满意了点「用这个风格」,再说要做什么',
|
|
392
|
+
stylesMore: '更多风格({n})',
|
|
393
|
+
stylesLess: '收起',
|
|
354
394
|
stylesOpen: '换个风格…',
|
|
355
395
|
stylesTitle: '换个风格',
|
|
356
|
-
stylesSwap: '
|
|
396
|
+
stylesSwap: '点一个看样板;「用这个风格」让模型把现在的设计换成它(只换配色、字体、圆角,布局和文字不动)',
|
|
357
397
|
stylesAll: '全部风格',
|
|
358
398
|
stylePrev: '上一个(←)',
|
|
359
399
|
styleNext: '下一个(→)',
|
|
@@ -379,6 +419,7 @@ window.__ModuleLoader__.load({
|
|
|
379
419
|
styleFonts: '标题 {head};正文 {body}',
|
|
380
420
|
styleShape: '圆角 {r}px;阴影 {s}',
|
|
381
421
|
styleStart: '照这个风格来做(先把这些定进 tokens.css;不用再问想要什么感觉):',
|
|
422
|
+
styleAuto: '这张风格卡是首页按题材自动挑的,不是我选的:直接做全套就照它的配色和字体来(定进 tokens.css);要先出三个方向,就让方向 A 用它、B 和 C 从风格卡里另挑两种不同的感觉。我在话里另说了风格、或者有默认的品牌设计系统,就以那个为准:',
|
|
382
423
|
styleRestyle: '把现在的设计换成这个风格:先改 tokens.css(配色、字体、圆角、阴影),各屏跟着变;页面里写死的颜色也换成变量;布局和文案先不动。\n',
|
|
383
424
|
demoNone: '这一屏还没有跳到别的屏的按钮;可以让模型用 <a href="detail.html"> 把几屏串起来',
|
|
384
425
|
phone: '在手机上看',
|
|
@@ -396,29 +437,111 @@ window.__ModuleLoader__.load({
|
|
|
396
437
|
phoneAuto: '只有扫了这个码的手机能打开,30 分钟没人看自动关。',
|
|
397
438
|
phoneNoLan: '电脑没连上 Wi-Fi 或有线网,手机连不过来。',
|
|
398
439
|
phoneFail: '没打开:{msg}',
|
|
399
|
-
phoneHelp: '
|
|
440
|
+
phoneHelp: '确认在同一个 Wi-Fi;手机开着代理 / VPN 的先关掉;电脑问「是否允许传入连接」时点允许。',
|
|
441
|
+
phoneTrouble: '打不开?',
|
|
400
442
|
zoomIn: '放大',
|
|
401
443
|
zoomOut: '缩小',
|
|
402
444
|
fit: '适合画布',
|
|
403
445
|
openScreen: '双击只看这一屏',
|
|
404
|
-
generating: '
|
|
446
|
+
generating: '设计中…',
|
|
405
447
|
waiting: '模型正在工作,页面写出来就显示在这里',
|
|
448
|
+
waitingLive: '页面正在一段段写出来,写完就能点着改',
|
|
406
449
|
refresh: '刷新',
|
|
407
450
|
external: '在浏览器里打开',
|
|
408
451
|
more: '更多',
|
|
409
|
-
demoShort: '
|
|
452
|
+
demoShort: '试用',
|
|
453
|
+
// 浮块按钮悬停说明:名字下面这一句
|
|
454
|
+
dPick: '点一个元素或拖出一块区域,发给模型改',
|
|
455
|
+
dText: '点页面里的字直接改,回车保存',
|
|
456
|
+
dStyle: '点一个元素,拖滑块调字号、颜色、圆角和间距',
|
|
457
|
+
dMark: '画圈、画箭头、写字,拼成一张图发给模型',
|
|
458
|
+
dPlay: '像真的 App 一样点,按钮会跳到对应的屏',
|
|
459
|
+
dLib: '存过的设计、风格和模板,也能逛设计市场',
|
|
460
|
+
dLook: '改配色、字体,或整套换个风格',
|
|
461
|
+
dCheck: '找出文字溢出、重叠、看不清、按钮太小',
|
|
462
|
+
dCheckN: '上次找到 {n} 个问题,点开看',
|
|
463
|
+
dExport: '转成代码、存图片 / PDF / PPT、打包分享',
|
|
464
|
+
dMore: '版本、在手机上看、刷新、在浏览器里打开',
|
|
465
|
+
dZoom: '按住 {mod} 滚动也能缩放',
|
|
466
|
+
dScreen: '换一屏、看全部、多尺寸、换类型',
|
|
467
|
+
toolsT: '工具',
|
|
410
468
|
editAddr: '换一个地址',
|
|
469
|
+
textShort: '改字',
|
|
470
|
+
styleShort: '样式',
|
|
471
|
+
markShort: '画笔',
|
|
472
|
+
viewT: '视图',
|
|
473
|
+
canvasShort: '画布',
|
|
474
|
+
singleShort: '单页',
|
|
475
|
+
sizesShort: '多尺寸',
|
|
476
|
+
look: '外观',
|
|
477
|
+
lookTip: '外观:配色、字体、整套风格',
|
|
478
|
+
deliver: '导出',
|
|
479
|
+
deliverTip: '导出:转成代码、存成图片 / PDF / PPT、打包分享',
|
|
480
|
+
exFiles: '存成文件',
|
|
481
|
+
checkShort: '体检',
|
|
482
|
+
zoomT: '缩放',
|
|
483
|
+
zoomReal: '实际大小',
|
|
484
|
+
actX: '导出',
|
|
485
|
+
actV: '再出两版',
|
|
486
|
+
actO: '单看',
|
|
487
|
+
deliverCode: '代码',
|
|
488
|
+
deliverShare: '分享',
|
|
489
|
+
screensT: '换一屏 / 打开别的页面',
|
|
490
|
+
screensHd: '屏幕',
|
|
491
|
+
screensAll: '全部 {n} 屏',
|
|
492
|
+
screensN: '{name} · {n} 屏',
|
|
493
|
+
openOther: '打开别的页面…',
|
|
494
|
+
phoneOnTip: '手机预览开着',
|
|
495
|
+
lookSet: '整套',
|
|
496
|
+
lookStyle: '换风格',
|
|
497
|
+
lookBrand: '品牌',
|
|
498
|
+
lookColorTip: '点色块直接改,所有屏一起变',
|
|
499
|
+
lookTokTip: '点一下放进输入框,跟模型说怎么改',
|
|
500
|
+
lookTw: '调节滑块',
|
|
501
|
+
lookTwNone: '还没有专属滑块(留白、圆角、排法……)。',
|
|
502
|
+
lookN: '改了 {n} 处,还没保存',
|
|
503
|
+
lookSave: '直接保存',
|
|
504
|
+
lookSaveTip: '写进设计文件(不经过模型;改前改后各存一版)',
|
|
505
|
+
lookAskHead: '〔外观〕我在右侧外观面板里把这几个设计变量改成了:',
|
|
506
|
+
lookAskTail: '照这个改:改 tokens.css(或样式里 :root 上)的默认值,页面里写死的同一种颜色也换成变量。',
|
|
507
|
+
lookInspire: '灵感库',
|
|
508
|
+
lookRef: '照参考图做…',
|
|
509
|
+
el_button: '按钮', el_a: '链接', el_img: '图片', el_h: '标题', el_p: '段落', el_input: '输入框', el_li: '列表项', el_ul: '列表', el_nav: '导航', el_header: '顶栏', el_footer: '底栏', el_text: '文字', el_icon: '图标', el_card: '卡片', el_tag: '标签', el_avatar: '头像', el_box: '区块',
|
|
510
|
+
tkn_bg: '背景', tkn_surface: '卡片底', tkn_text: '正文字', tkn_muted: '次要字', tkn_line: '分隔线', tkn_primary: '主色', tkn_accent: '强调色', tkn_onPrimary: '主色上的字', tkn_link: '链接', tkn_danger: '警示色', tkn_success: '成功色', tkn_warning: '提醒色',
|
|
511
|
+
tkn_xs: '注释', tkn_sm: '小字', tkn_md: '中号字', tkn_body: '正文', tkn_lg: '大字', tkn_title: '标题', tkn_display: '大标题',
|
|
512
|
+
tkn_rSm: '小圆角', tkn_rMd: '中圆角', tkn_rLg: '大圆角', tkn_rFull: '全圆', tkn_r: '圆角',
|
|
513
|
+
tkn_space: '间距', tkn_gutter: '页边距',
|
|
514
|
+
tkn_fHead: '标题字体', tkn_fBody: '正文字体', tkn_fMono: '等宽字体',
|
|
515
|
+
tkn_shadow: '阴影', tkn_light: '浅', tkn_dark: '深', tkn_hover: '悬停',
|
|
516
|
+
phoneOnShort: '开着',
|
|
517
|
+
sizesNoShort: '固定画幅',
|
|
411
518
|
working: '模型正在工作',
|
|
412
519
|
asking: '模型在等你回答(在对话里)',
|
|
413
520
|
writing: '正在写 {file}',
|
|
521
|
+
curLayout: '排版',
|
|
522
|
+
curColor: '配色',
|
|
523
|
+
curCopy: '文案',
|
|
524
|
+
stThink: '在想…',
|
|
525
|
+
stNew: '正在做第 {n} 屏',
|
|
526
|
+
stEdit: '正在改「{name}」',
|
|
527
|
+
stWriteAny: '正在改文件',
|
|
528
|
+
stCheck: '正在体检',
|
|
529
|
+
stLook: '正在看效果',
|
|
530
|
+
stPreview: '正在打开预览',
|
|
531
|
+
stBrand: '正在提取品牌',
|
|
532
|
+
stCode: '正在转成代码',
|
|
533
|
+
stCompare: '正在逐像素对比',
|
|
534
|
+
stShell: '正在跑命令',
|
|
535
|
+
stRead: '正在看文件',
|
|
536
|
+
stWeb: '正在查资料',
|
|
537
|
+
stSkill: '正在看设计规范',
|
|
538
|
+
stTime: '已用时 {time}',
|
|
539
|
+
stLines: '已写 {n} 行',
|
|
414
540
|
born: '已生成 {name}',
|
|
415
541
|
updated: '已更新{file}',
|
|
416
542
|
updatedN: '已更新{file} · {n} 处改动',
|
|
417
543
|
updatedWhole: '已更新{file} · 整页重排',
|
|
418
|
-
|
|
419
|
-
step2: '点「选择」,点元素或拖出一块区域',
|
|
420
|
-
step3: '在输入框补一句要怎么改,发出去',
|
|
421
|
-
tryHead: '不知道怎么开口?点一句试试',
|
|
544
|
+
tryHead: '试试这样说',
|
|
422
545
|
try1: '帮我设计一个社区健身房的会员 App',
|
|
423
546
|
try2: '做一个精品咖啡店的落地页',
|
|
424
547
|
try3: '做一张周末市集的活动海报',
|
|
@@ -427,7 +550,7 @@ window.__ModuleLoader__.load({
|
|
|
427
550
|
copied: '已复制,粘贴到输入框里',
|
|
428
551
|
failed: '没放进去:{msg}',
|
|
429
552
|
openFailed: '打不开:{msg}',
|
|
430
|
-
headerTip: '
|
|
553
|
+
headerTip: '设计台',
|
|
431
554
|
tweaks: '调节滑块',
|
|
432
555
|
twTip: '模型给这套设计做的几个维度:拖的时候所有屏一起变,还没写进文件。',
|
|
433
556
|
twEmpty: '这套设计还没有专属滑块。让模型挑几个最值得试的维度(留白、圆角、主色、排法……),做成滑块放在这里。',
|
|
@@ -439,15 +562,16 @@ window.__ModuleLoader__.load({
|
|
|
439
562
|
twAskTail: '照这个改设计(改样式源文件,tweaks.json 里对应的 value 也改成新值)。',
|
|
440
563
|
twAskTip: '让模型照这几个值改设计(可以顺手调整相关的地方)',
|
|
441
564
|
twSaveTip: 'CSS 变量写进 tokens.css 的默认值,属性写到每屏的 <html> 上(改前改后各存一版)',
|
|
442
|
-
twNoWrite: '
|
|
565
|
+
twNoWrite: '开发服务器的页面不能直接保存,发到对话让模型改',
|
|
566
|
+
twVer: '正在看旧版本:回到当前版本再保存',
|
|
443
567
|
twMoved: '设计已经换了,没写回;再拖一次',
|
|
444
|
-
twSaved: '
|
|
445
|
-
twMissed: '{names}
|
|
568
|
+
twSaved: '已保存 {n} 项',
|
|
569
|
+
twMissed: '{names} 在样式里没找到默认值,没保存:发到对话让模型改',
|
|
446
570
|
systems: '品牌设计系统',
|
|
447
571
|
sysOpen: '品牌设计系统…',
|
|
448
|
-
sysEmptyLine: '
|
|
572
|
+
sysEmptyLine: '用自己的品牌:',
|
|
449
573
|
sysTip: '从你的网站或项目里提取的颜色、字体、圆角和组件。设为默认后,做新设计都照它来。',
|
|
450
|
-
sysEmpty: '
|
|
574
|
+
sysEmpty: '还没有。在上面填你的网站地址或项目文件夹,让模型提一套。',
|
|
451
575
|
sysDefault: '默认',
|
|
452
576
|
sysSet: '设为默认',
|
|
453
577
|
sysUnset: '不再默认',
|
|
@@ -461,7 +585,9 @@ window.__ModuleLoader__.load({
|
|
|
461
585
|
handoff: '转成代码',
|
|
462
586
|
hoTip: '把设计做成真项目的代码:先整理交接说明和截图,再一屏一屏写,每屏写完和设计稿逐像素比,对上了再做下一屏。',
|
|
463
587
|
hoFw: '用什么做',
|
|
464
|
-
hoSame: '
|
|
588
|
+
hoSame: '跟现有项目一样:{label}',
|
|
589
|
+
hoIn: '在 {dir} 里',
|
|
590
|
+
hoEdit: '点一下改路径',
|
|
465
591
|
hoHtml: '纯 HTML / CSS',
|
|
466
592
|
hoUni: 'uni-app(小程序 + 网页版)',
|
|
467
593
|
hoWeapp: '微信原生小程序',
|
|
@@ -480,10 +606,16 @@ window.__ModuleLoader__.load({
|
|
|
480
606
|
// 设计库
|
|
481
607
|
library: '设计库',
|
|
482
608
|
libBack: '回到画布',
|
|
609
|
+
homeBtn: '首页',
|
|
610
|
+
dHome: '回首页:新做一套、接着做别的、逛设计库',
|
|
611
|
+
homeBack: '回到「{name}」',
|
|
612
|
+
askNewLine: '(另起一套新设计,放新文件夹里,别改现在这套)',
|
|
613
|
+
askNewNote: '新做一套,现在这套不会被改',
|
|
614
|
+
wkHere: '正在看',
|
|
483
615
|
libMarket: '市场',
|
|
484
616
|
libWorks: '我的作品',
|
|
485
617
|
libInspire: '灵感',
|
|
486
|
-
libLine: '
|
|
618
|
+
libLine: '以前做的设计、存下的灵感、朋友给的设计包:',
|
|
487
619
|
libOpen: '打开设计库',
|
|
488
620
|
libClose: '返回',
|
|
489
621
|
libFail: '没办成:{msg}',
|
|
@@ -497,7 +629,10 @@ window.__ModuleLoader__.load({
|
|
|
497
629
|
mkOffline: '连不上市场,下面是上次看到的。',
|
|
498
630
|
mkOff: '「设计库不联网」开着:不访问任何网址,只显示下载过的。要联网,去「插件」页关掉「设计库不联网」。',
|
|
499
631
|
mkNeedUpdate: '市场里有新东西要新版设计台才能看:去「插件」页点「更新」。',
|
|
500
|
-
mkAt: '
|
|
632
|
+
mkAt: '目录更新于 {ago}',
|
|
633
|
+
mkUse0: '可商用',
|
|
634
|
+
mkUseBY: '可商用 · 要署名',
|
|
635
|
+
mkSortT: '排序',
|
|
501
636
|
mkSeedNote: '这是设计台自带的一批',
|
|
502
637
|
mkRefresh: '刷新',
|
|
503
638
|
mkTypeTpl: '模板',
|
|
@@ -554,6 +689,9 @@ window.__ModuleLoader__.load({
|
|
|
554
689
|
wkNow: '正在看',
|
|
555
690
|
wkOpen: '打开',
|
|
556
691
|
wkReveal: '在文件夹里显示',
|
|
692
|
+
wkRevealShort: '文件夹',
|
|
693
|
+
wkZipShort: 'zip',
|
|
694
|
+
wkShareShort: '分享',
|
|
557
695
|
wkPin: '置顶',
|
|
558
696
|
wkUnpin: '取消置顶',
|
|
559
697
|
wkZip: '打成 zip',
|
|
@@ -572,7 +710,7 @@ window.__ModuleLoader__.load({
|
|
|
572
710
|
agoHour: '{n} 小时前',
|
|
573
711
|
agoDay: '{n} 天前',
|
|
574
712
|
inMyStyles: '我的风格',
|
|
575
|
-
inSaveStyle: '
|
|
713
|
+
inSaveStyle: '存当前设计',
|
|
576
714
|
inSaveStyleTip: '存下现在这套的配色、字体、圆角,以后做新设计一键用',
|
|
577
715
|
inStyleSaved: '已存成风格「{name}」',
|
|
578
716
|
inStyleNone: '这套设计没用设计变量(tokens.css),存不了风格',
|
|
@@ -644,7 +782,8 @@ window.__ModuleLoader__.load({
|
|
|
644
782
|
exZip: '打成 zip(发给朋友)',
|
|
645
783
|
exShare: '分享到设计市场',
|
|
646
784
|
shTitle: '分享到设计市场',
|
|
647
|
-
|
|
785
|
+
shGh: '投稿要有 GitHub 账号',
|
|
786
|
+
shTip: '交上去的东西是公开的:别人能看、能拿去用。先查一遍,过了打成 zip,再到 GitHub 上交。',
|
|
648
787
|
shName: '名字',
|
|
649
788
|
shDesc: '一句话介绍',
|
|
650
789
|
shDescPh: '比如:街角茶饮店的三屏点单 App',
|
|
@@ -653,7 +792,7 @@ window.__ModuleLoader__.load({
|
|
|
653
792
|
shAgreePrivate: '里面没有真实的个人信息(手机号、邮箱、证件号、地址、真人照片)',
|
|
654
793
|
shAgreeRules: '同意按选的授权公开;上架后随时能撤,但别人已经复制走的收不回',
|
|
655
794
|
shBYNote: 'CC BY 的署名就是你的 GitHub 用户名。',
|
|
656
|
-
shGo: '
|
|
795
|
+
shGo: '检查并投稿',
|
|
657
796
|
shChecking: '在查…',
|
|
658
797
|
shOk: '查过了,打好了:{name}(在「下载」里)',
|
|
659
798
|
shSteps: '还差三步:',
|
|
@@ -673,10 +812,44 @@ window.__ModuleLoader__.load({
|
|
|
673
812
|
title: 'Design Studio',
|
|
674
813
|
blockedLink: 'Blocked an outside link: {href} (designs from the gallery cannot leave the page)',
|
|
675
814
|
blockedDownload: 'Blocked a download (designs from the gallery cannot download files)',
|
|
676
|
-
|
|
815
|
+
tabTitle: 'Design',
|
|
816
|
+
guideDesc: 'Design pages, apps and posters; click to change, then turn into code',
|
|
677
817
|
empty: 'Nothing to preview yet',
|
|
678
|
-
|
|
679
|
-
|
|
818
|
+
emptyHead: 'What do you want to make?',
|
|
819
|
+
emptySub: 'Describe it in a sentence. Screens appear on the canvas one by one; click anything to change it, then turn it into code.',
|
|
820
|
+
homeTab: 'Start',
|
|
821
|
+
askPh: 'e.g. {ex}',
|
|
822
|
+
askTab: 'use this',
|
|
823
|
+
exAuto: 'An ordering mini program for a corner noodle shop, 3 screens, warm hand-made style',
|
|
824
|
+
exWeb: 'Home page for an independent coffee roaster: signature beans, store map, booking',
|
|
825
|
+
exApp: 'Member app for a neighborhood gym: classes, check-ins, membership card',
|
|
826
|
+
exMini: 'Ordering mini program for a noodle shop: menu, cart, pickup number',
|
|
827
|
+
exTablet: 'Tablet app for kindergarten teachers: roll call, timetable, parent messages',
|
|
828
|
+
exDesktop: 'Desktop bookkeeping app for a small team: transactions, reports, settings',
|
|
829
|
+
exPoster: 'Poster for a weekend night run in the city, fresh sporty style',
|
|
830
|
+
exSlides: 'Five-slide launch deck: problem, solution, numbers, pricing, next steps',
|
|
831
|
+
exEmail: 'Birthday-month offer email for members: one big image, a short note, one button',
|
|
832
|
+
askKindLine: 'Type: {k}',
|
|
833
|
+
askMore: 'More ways to start',
|
|
834
|
+
askMoreD: 'Open existing HTML, use your own brand, open from the library',
|
|
835
|
+
askOpenHtml: 'Open existing HTML…',
|
|
836
|
+
askBrand: 'Use your own brand',
|
|
837
|
+
askLib: 'Open from the library',
|
|
838
|
+
askRef: 'Reference',
|
|
839
|
+
askRefD: 'Design from a screenshot or mockup: pick one, or drop / paste it',
|
|
840
|
+
askStyleAuto: 'Style: auto',
|
|
841
|
+
askStyle: 'Style: {name}',
|
|
842
|
+
askStyleD: 'Pick a style first, or one is picked for the subject',
|
|
843
|
+
askStylePick: 'Pick a style',
|
|
844
|
+
askStyleAutoBtn: 'Auto (picked for the subject)',
|
|
845
|
+
askSend: 'Start designing',
|
|
846
|
+
askSent: 'Sent. The first screen is on its way',
|
|
847
|
+
homeRecent: 'Continue',
|
|
848
|
+
homeRecentAll: 'All designs →',
|
|
849
|
+
waysHtml: 'Open existing HTML',
|
|
850
|
+
moreWays: 'Other ways to start',
|
|
851
|
+
emptyHint: 'Enter a file path or a local port (like 5173)',
|
|
852
|
+
emptyHintPick: 'Click "Browse…", drop it here, or enter a file path / local port (like 5173)',
|
|
680
853
|
browse: 'Browse…',
|
|
681
854
|
browseTip: 'Pick a local HTML file to open',
|
|
682
855
|
pathNone: "Couldn't get this file's path. Type its path to open it.",
|
|
@@ -726,7 +899,7 @@ window.__ModuleLoader__.load({
|
|
|
726
899
|
refPick: 'Restyle from a reference…',
|
|
727
900
|
refChoose: 'Choose one',
|
|
728
901
|
refPickTip: 'Pick a reference image to restyle after (or drop / paste an image into the preview)',
|
|
729
|
-
refEmpty: '
|
|
902
|
+
refEmpty: 'Design from a reference image: drop it, paste it, or',
|
|
730
903
|
refDrop: 'Drop to use as a reference',
|
|
731
904
|
refDropSub: 'Saved to refs/ in the design folder; its colors go into the input',
|
|
732
905
|
refNotImage: 'Images only (PNG / JPG / WebP / GIF)',
|
|
@@ -772,9 +945,10 @@ window.__ModuleLoader__.load({
|
|
|
772
945
|
gShadow: 'Shadows',
|
|
773
946
|
gOther: 'Other',
|
|
774
947
|
exportT: 'Export',
|
|
775
|
-
exFrame: '
|
|
776
|
-
exRaw: '
|
|
777
|
-
exLong: '
|
|
948
|
+
exFrame: '{name} · in frame',
|
|
949
|
+
exRaw: '{name} · plain',
|
|
950
|
+
exLong: '{name} · full page',
|
|
951
|
+
exThisName: 'This screen',
|
|
778
952
|
exShow: 'Showcase of all screens',
|
|
779
953
|
exPdf: 'PDF · one page per screen',
|
|
780
954
|
exPptx: 'PowerPoint · one slide per screen (as images)',
|
|
@@ -785,7 +959,8 @@ window.__ModuleLoader__.load({
|
|
|
785
959
|
checking: 'Checking…',
|
|
786
960
|
checkNone: 'No problems found',
|
|
787
961
|
checkSum: '{s} screens · {n} problems',
|
|
788
|
-
checkFix: '
|
|
962
|
+
checkFix: 'Send to chat',
|
|
963
|
+
checkFixTip: 'Send these issues to the chat for the model to fix',
|
|
789
964
|
checkAgain: 'Check again',
|
|
790
965
|
checkFail: 'Check failed: {msg}',
|
|
791
966
|
checkNote: 'Only measurable layout problems; whether it looks good is still up to you',
|
|
@@ -884,17 +1059,19 @@ window.__ModuleLoader__.load({
|
|
|
884
1059
|
cmpNow: 'Now',
|
|
885
1060
|
dark: 'Dark mode',
|
|
886
1061
|
variants: 'Two more takes',
|
|
887
|
-
pickThis: 'Use this
|
|
1062
|
+
pickThis: 'Use this take: move the other takes to drafts/ and build the rest',
|
|
888
1063
|
pickShort: 'Use this',
|
|
1064
|
+
takeN: 'Take {l}',
|
|
889
1065
|
pickText: '[Direction picked] Use "{name}" ({where}). First move the other takes in its group ({others}) into a drafts/ subfolder (don\'t delete them), then rename it to {base}.html; move its design tokens into tokens.css (create it if missing) and switch it to tokens.css. Then build the rest.\n',
|
|
890
1066
|
pickKeep: '[Direction picked] Keep the original "{name}" ({where}). Move the other takes in its group ({others}) into a drafts/ subfolder (don\'t delete them) and leave everything else as is.\n',
|
|
891
1067
|
variantText: 'Make two alternative designs of "{name}" in clearly different directions (layout or visual style). Save them next to it as {b}-b.html and {b}-c.html (original: {where}); they will sit right after the original on the canvas. Leave the original and tokens.css unchanged; override variables inside the new files if they need other colors.\n',
|
|
892
1068
|
exThis: 'Export this screen',
|
|
893
1069
|
demo: 'Play: tap buttons in the page to move between screens',
|
|
894
|
-
demoOn: 'Playing
|
|
1070
|
+
demoOn: 'Playing',
|
|
1071
|
+
demoHow: 'tap buttons to move',
|
|
895
1072
|
demoBack: 'Back',
|
|
896
1073
|
demoRestart: 'Start over',
|
|
897
|
-
demoExit: 'Stop playing
|
|
1074
|
+
demoExit: 'Stop playing',
|
|
898
1075
|
textEdit: 'Edit text: click any text in the page, Enter to save (Esc to stop)',
|
|
899
1076
|
mark: 'Mark up: draw circles, arrows and notes on the page, then hand it to the model as one image',
|
|
900
1077
|
mkPen: 'Pen',
|
|
@@ -909,7 +1086,7 @@ window.__ModuleLoader__.load({
|
|
|
909
1086
|
mkUndo: 'Undo',
|
|
910
1087
|
mkRedo: 'Redo',
|
|
911
1088
|
mkClear: 'Clear all',
|
|
912
|
-
mkHint: 'Draw on the page, then
|
|
1089
|
+
mkHint: 'Draw on the page, then click Send to chat',
|
|
913
1090
|
mkHow_pen: 'Press and drag to draw freehand',
|
|
914
1091
|
mkHow_hl: 'Drag over what needs changing to highlight it',
|
|
915
1092
|
mkHow_arrow: 'Press at the start and drag to what you mean',
|
|
@@ -920,13 +1097,13 @@ window.__ModuleLoader__.load({
|
|
|
920
1097
|
mkCoach: 'Draw right on the page',
|
|
921
1098
|
mkCoach1: 'Pick a tool above: pen, arrow, circle, text…',
|
|
922
1099
|
mkCoach2: 'Press and drag on the page to mark what to change',
|
|
923
|
-
mkCoach3: 'Then click "Send to
|
|
1100
|
+
mkCoach3: 'Then click "Send to chat" and add a line on what to do',
|
|
924
1101
|
mkCancel: 'Cancel',
|
|
925
|
-
mkSend: 'Send to
|
|
1102
|
+
mkSend: 'Send to chat',
|
|
926
1103
|
mkCopy: 'Copy',
|
|
927
1104
|
mkCopyTip: 'Copy the marked-up image to paste somewhere else',
|
|
928
1105
|
mkCopied: 'Markup image copied. Paste it anywhere',
|
|
929
|
-
mkCopyFail: 'Could not copy to the clipboard. Click Copy again, or Send to
|
|
1106
|
+
mkCopyFail: 'Could not copy to the clipboard. Click Copy again, or Send to chat',
|
|
930
1107
|
mkSending: 'Building the image…',
|
|
931
1108
|
mkTextPh: 'Type a note, Enter to finish',
|
|
932
1109
|
mkEmpty: 'Nothing drawn yet',
|
|
@@ -937,7 +1114,7 @@ window.__ModuleLoader__.load({
|
|
|
937
1114
|
mkIn: 'Markup added to the input. Say what to change, then send',
|
|
938
1115
|
mkMany: 'Too many screens marked: captured the first {n}, the rest are gray',
|
|
939
1116
|
mkMoved: 'The design changed, so the markup was not sent',
|
|
940
|
-
mkNoInput: 'Could not add it to the input. Click the chat input, then Send to
|
|
1117
|
+
mkNoInput: 'Could not add it to the input. Click the chat input, then Send to chat again',
|
|
941
1118
|
textSaved: 'Saved to {file}',
|
|
942
1119
|
textAsk: 'Change the text "{before}" to "{after}" on the page ({where}; only this text, nothing else).\n',
|
|
943
1120
|
styleEdit: 'Style: click an element, then drag sliders for size, colors, radius and spacing (Esc to stop)',
|
|
@@ -961,14 +1138,14 @@ window.__ModuleLoader__.load({
|
|
|
961
1138
|
styleVar: 'Token {name}',
|
|
962
1139
|
styleGradient: 'It is a gradient now; picking a color makes it solid',
|
|
963
1140
|
styleReset: 'Reset',
|
|
964
|
-
styleAsk: '
|
|
965
|
-
styleSave: 'Save
|
|
1141
|
+
styleAsk: 'Send to chat',
|
|
1142
|
+
styleSave: 'Save',
|
|
966
1143
|
styleSaveTip: 'Writes into this element\'s style attribute in the source (a version is kept before and after)',
|
|
967
1144
|
styleAskTip: 'Let the model move it into the stylesheet (using tokens where it can)',
|
|
968
|
-
styleNoWrite: 'Dev-server pages
|
|
1145
|
+
styleNoWrite: 'Dev-server pages cannot be saved here; send it to the chat',
|
|
969
1146
|
styleBusyTip: 'The model is working; save when it stops',
|
|
970
1147
|
styleSaved: 'Style saved to {file}',
|
|
971
|
-
styleMiss: 'Could not find this element in the source (maybe a script made it);
|
|
1148
|
+
styleMiss: 'Could not find this element in the source (maybe a script made it); sending it to the chat instead',
|
|
972
1149
|
styleN: '{n} changed',
|
|
973
1150
|
styleAskText: '[Style] {name} ({sel}): {list}. {rules}Put it in its stylesheet rule ({where}), using design tokens where you can, not an inline style.\n',
|
|
974
1151
|
ruleFrom: '- {props}: currently set by {sel} ({at})',
|
|
@@ -979,11 +1156,13 @@ window.__ModuleLoader__.load({
|
|
|
979
1156
|
styleArrow: '{prop} {from} → {to}',
|
|
980
1157
|
styleJoin: '; ',
|
|
981
1158
|
lang: 'en',
|
|
982
|
-
stylesHead: '
|
|
983
|
-
stylesTip: '
|
|
1159
|
+
stylesHead: 'Or pick a style first',
|
|
1160
|
+
stylesTip: 'Open one to preview, click "Use this style", then say what to make',
|
|
1161
|
+
stylesMore: 'More styles ({n})',
|
|
1162
|
+
stylesLess: 'Show less',
|
|
984
1163
|
stylesOpen: 'Try another style…',
|
|
985
1164
|
stylesTitle: 'Change the style',
|
|
986
|
-
stylesSwap: 'Pick one to see a sample; "Use this style" has the model restyle the current design (
|
|
1165
|
+
stylesSwap: 'Pick one to see a sample; "Use this style" has the model restyle the current design (colors, fonts and corners only; layout and copy stay)',
|
|
987
1166
|
stylesAll: 'All styles',
|
|
988
1167
|
stylePrev: 'Previous (←)',
|
|
989
1168
|
styleNext: 'Next (→)',
|
|
@@ -1009,6 +1188,7 @@ window.__ModuleLoader__.load({
|
|
|
1009
1188
|
styleFonts: 'headings {head}; body {body}',
|
|
1010
1189
|
styleShape: 'radius {r}px; shadow {s}',
|
|
1011
1190
|
styleStart: 'Design in this style (put these into tokens.css first; no need to ask about the feel again):',
|
|
1191
|
+
styleAuto: 'Home picked this style card for the subject; I did not choose it. If you build the full set right away, use its colors and fonts (put them into tokens.css); if you start with three directions, make direction A in this style and pick two different feels from the style cards for B and C. If my message names another style, or there is a default brand design system, follow that instead:',
|
|
1012
1192
|
styleRestyle: 'Restyle the current design to this: update tokens.css first (colors, fonts, radius, shadows) so every screen follows; replace hard-coded colors in the pages with tokens; keep layout and copy for now.\n',
|
|
1013
1193
|
demoNone: 'This screen has no links to other screens yet. Ask the model to connect screens with <a href="detail.html">',
|
|
1014
1194
|
phone: 'View on phone',
|
|
@@ -1026,29 +1206,110 @@ window.__ModuleLoader__.load({
|
|
|
1026
1206
|
phoneAuto: 'Only a phone that scanned this code can open it. Closes after 30 minutes idle.',
|
|
1027
1207
|
phoneNoLan: 'This computer is not on Wi-Fi or a wired network, so a phone cannot reach it.',
|
|
1028
1208
|
phoneFail: 'Could not start: {msg}',
|
|
1029
|
-
phoneHelp:
|
|
1209
|
+
phoneHelp: 'Check both are on the same Wi-Fi, turn off any VPN or proxy on the phone, and allow incoming connections if your computer asks.',
|
|
1210
|
+
phoneTrouble: "Won't open?",
|
|
1030
1211
|
zoomIn: 'Zoom in',
|
|
1031
1212
|
zoomOut: 'Zoom out',
|
|
1032
1213
|
fit: 'Fit',
|
|
1033
1214
|
openScreen: 'Double-click to view this screen alone',
|
|
1034
|
-
generating: '
|
|
1215
|
+
generating: 'Designing…',
|
|
1035
1216
|
waiting: 'The model is working. The page shows up here once it is written',
|
|
1217
|
+
waitingLive: 'The page is being written piece by piece—click to edit once it’s done',
|
|
1036
1218
|
refresh: 'Reload',
|
|
1037
1219
|
external: 'Open in browser',
|
|
1038
1220
|
more: 'More',
|
|
1039
1221
|
demoShort: 'Play',
|
|
1222
|
+
dPick: 'Click an element or drag a region to send it to the model',
|
|
1223
|
+
dText: 'Click any text in the page and type, Enter to save',
|
|
1224
|
+
dStyle: 'Click an element, then drag sliders for size, colors, radius and spacing',
|
|
1225
|
+
dMark: 'Draw circles, arrows and notes, sent to the model as one image',
|
|
1226
|
+
dPlay: 'Click through it like a real app; buttons jump to their screens',
|
|
1227
|
+
dLib: 'Saved designs, styles and templates, plus the design market',
|
|
1228
|
+
dLook: 'Change colors and fonts, or switch the whole style',
|
|
1229
|
+
dCheck: 'Find overflowing or overlapping text, low contrast and tiny tap targets',
|
|
1230
|
+
dCheckN: '{n} problems last time. Click to see them',
|
|
1231
|
+
dExport: 'Build as code, save images / PDF / PPT, or pack and share',
|
|
1232
|
+
dMore: 'Versions, view on phone, refresh, open in browser',
|
|
1233
|
+
dZoom: 'Hold {mod} and scroll to zoom too',
|
|
1234
|
+
dScreen: 'Switch screens, see all, sizes or type',
|
|
1235
|
+
toolsT: 'Tools',
|
|
1040
1236
|
editAddr: 'Change address',
|
|
1237
|
+
textShort: 'Text',
|
|
1238
|
+
styleShort: 'Style',
|
|
1239
|
+
markShort: 'Draw',
|
|
1240
|
+
viewT: 'View',
|
|
1241
|
+
canvasShort: 'Canvas',
|
|
1242
|
+
singleShort: 'Single',
|
|
1243
|
+
sizesShort: 'Sizes',
|
|
1244
|
+
look: 'Look',
|
|
1245
|
+
lookTip: 'Look: colors, fonts and overall style',
|
|
1246
|
+
deliver: 'Export',
|
|
1247
|
+
deliverTip: 'Export: build as code, save images / PDF / PPT, pack and share',
|
|
1248
|
+
exFiles: 'Save as file',
|
|
1249
|
+
checkShort: 'Check',
|
|
1250
|
+
zoomT: 'Zoom',
|
|
1251
|
+
zoomReal: 'Actual size',
|
|
1252
|
+
actX: 'Export',
|
|
1253
|
+
actV: 'More takes',
|
|
1254
|
+
actO: 'Open',
|
|
1255
|
+
deliverCode: 'Code',
|
|
1256
|
+
deliverShare: 'Share',
|
|
1257
|
+
screensT: 'Switch screen / open another page',
|
|
1258
|
+
screensHd: 'Screens',
|
|
1259
|
+
screensAll: 'All {n}',
|
|
1260
|
+
screensN: '{name} · {n} screens',
|
|
1261
|
+
openOther: 'Open another page…',
|
|
1262
|
+
phoneOnTip: 'Phone preview is on',
|
|
1263
|
+
lookSet: 'Overall',
|
|
1264
|
+
lookStyle: 'Restyle',
|
|
1265
|
+
lookBrand: 'Brand',
|
|
1266
|
+
lookColorTip: 'Click a swatch to change it; every screen follows',
|
|
1267
|
+
lookTokTip: 'Click to put it in the input, then tell the model how to change it',
|
|
1268
|
+
lookTw: 'Tweaks',
|
|
1269
|
+
lookTwNone: 'No tweaks for this design yet (spacing, corners, layout…).',
|
|
1270
|
+
lookN: '{n} change(s), not saved',
|
|
1271
|
+
lookSave: 'Save',
|
|
1272
|
+
lookSaveTip: 'Write into the design files (no model; a version is kept before and after)',
|
|
1273
|
+
lookAskHead: '[Look] I changed these design tokens in the Look panel:',
|
|
1274
|
+
lookAskTail: 'Make it so: change the defaults in tokens.css (or :root in the styles), and replace hard-coded copies of the same colors with the tokens.',
|
|
1275
|
+
lookInspire: 'Inspiration',
|
|
1276
|
+
lookRef: 'From a reference image…',
|
|
1277
|
+
el_button: 'Button', el_a: 'Link', el_img: 'Image', el_h: 'Heading', el_p: 'Paragraph', el_input: 'Input', el_li: 'List item', el_ul: 'List', el_nav: 'Nav', el_header: 'Header', el_footer: 'Footer', el_text: 'Text', el_icon: 'Icon', el_card: 'Card', el_tag: 'Tag', el_avatar: 'Avatar', el_box: 'Block',
|
|
1278
|
+
tkn_bg: 'Background', tkn_surface: 'Card', tkn_text: 'Text', tkn_muted: 'Muted text', tkn_line: 'Divider', tkn_primary: 'Primary', tkn_accent: 'Accent', tkn_onPrimary: 'Text on primary', tkn_link: 'Link', tkn_danger: 'Danger', tkn_success: 'Success', tkn_warning: 'Warning',
|
|
1279
|
+
tkn_xs: 'Caption', tkn_sm: 'Small', tkn_md: 'Medium', tkn_body: 'Body', tkn_lg: 'Large', tkn_title: 'Title', tkn_display: 'Display',
|
|
1280
|
+
tkn_rSm: 'Small corners', tkn_rMd: 'Medium corners', tkn_rLg: 'Large corners', tkn_rFull: 'Pill', tkn_r: 'Corners',
|
|
1281
|
+
tkn_space: 'Spacing', tkn_gutter: 'Page margin',
|
|
1282
|
+
tkn_fHead: 'Heading font', tkn_fBody: 'Body font', tkn_fMono: 'Mono font',
|
|
1283
|
+
tkn_shadow: 'Shadow', tkn_light: 'light', tkn_dark: 'dark', tkn_hover: 'hover',
|
|
1284
|
+
phoneOnShort: 'On',
|
|
1285
|
+
sizesNoShort: 'Fixed size',
|
|
1041
1286
|
working: 'The model is working',
|
|
1042
1287
|
asking: 'The model is waiting for your answer (in the chat)',
|
|
1043
1288
|
writing: 'Writing {file}',
|
|
1289
|
+
curLayout: 'Layout',
|
|
1290
|
+
curColor: 'Color',
|
|
1291
|
+
curCopy: 'Copy',
|
|
1292
|
+
stThink: 'Thinking…',
|
|
1293
|
+
stNew: 'Designing screen {n}',
|
|
1294
|
+
stEdit: 'Editing "{name}"',
|
|
1295
|
+
stWriteAny: 'Editing files',
|
|
1296
|
+
stCheck: 'Checking the design',
|
|
1297
|
+
stLook: 'Looking at the result',
|
|
1298
|
+
stPreview: 'Opening the preview',
|
|
1299
|
+
stBrand: 'Extracting the brand',
|
|
1300
|
+
stCode: 'Turning it into code',
|
|
1301
|
+
stCompare: 'Comparing pixel by pixel',
|
|
1302
|
+
stShell: 'Running a command',
|
|
1303
|
+
stRead: 'Reading files',
|
|
1304
|
+
stWeb: 'Looking things up',
|
|
1305
|
+
stSkill: 'Reading the design guide',
|
|
1306
|
+
stTime: '{time} elapsed',
|
|
1307
|
+
stLines: '{n} lines so far',
|
|
1044
1308
|
born: 'Generated {name}',
|
|
1045
1309
|
updated: 'Updated{file}',
|
|
1046
1310
|
updatedN: 'Updated{file} · {n} changes',
|
|
1047
1311
|
updatedWhole: 'Updated{file} · whole page',
|
|
1048
|
-
|
|
1049
|
-
step2: 'Click Select, then an element or drag a region',
|
|
1050
|
-
step3: 'Say what to change in the input and send',
|
|
1051
|
-
tryHead: 'Not sure where to start? Try one',
|
|
1312
|
+
tryHead: 'Try saying',
|
|
1052
1313
|
try1: 'Design a member app for a neighborhood gym',
|
|
1053
1314
|
try2: 'Make a landing page for a specialty coffee shop',
|
|
1054
1315
|
try3: 'Make a poster for a weekend street market',
|
|
@@ -1069,15 +1330,16 @@ window.__ModuleLoader__.load({
|
|
|
1069
1330
|
twAskTail: 'Change the design to match (edit the style sources and update the matching values in tweaks.json).',
|
|
1070
1331
|
twAskTip: 'Let the model apply these values (and adjust related details)',
|
|
1071
1332
|
twSaveTip: 'CSS variables go into the defaults in tokens.css, attributes onto each screen\'s <html> (a version is kept before and after)',
|
|
1072
|
-
twNoWrite: 'Dev-server pages cannot be
|
|
1333
|
+
twNoWrite: 'Dev-server pages cannot be saved here. Send it to the chat instead',
|
|
1334
|
+
twVer: 'Viewing an old version. Go back to the current one to save',
|
|
1073
1335
|
twMoved: 'The design changed, so nothing was written. Try again',
|
|
1074
|
-
twSaved: '
|
|
1075
|
-
twMissed: 'No default found in the styles for {names}, not
|
|
1336
|
+
twSaved: 'Saved {n}',
|
|
1337
|
+
twMissed: 'No default found in the styles for {names}, not saved. Send it to the chat instead',
|
|
1076
1338
|
systems: 'Brand design systems',
|
|
1077
1339
|
sysOpen: 'Brand design systems…',
|
|
1078
|
-
sysEmptyLine: '
|
|
1340
|
+
sysEmptyLine: 'Use your own brand:',
|
|
1079
1341
|
sysTip: 'Colors, fonts, radii and components pulled from your site or project. The default one is used for every new design.',
|
|
1080
|
-
sysEmpty: 'None yet. Enter your website or project folder
|
|
1342
|
+
sysEmpty: 'None yet. Enter your website or project folder above and let the model extract one.',
|
|
1081
1343
|
sysDefault: 'Default',
|
|
1082
1344
|
sysSet: 'Make default',
|
|
1083
1345
|
sysUnset: 'Stop using',
|
|
@@ -1091,7 +1353,9 @@ window.__ModuleLoader__.load({
|
|
|
1091
1353
|
handoff: 'Build as code',
|
|
1092
1354
|
hoTip: 'Turn the design into a real project: a handoff note and screenshots first, then one screen at a time, each pixel-compared with the design before moving on.',
|
|
1093
1355
|
hoFw: 'Build with',
|
|
1094
|
-
hoSame: 'Same as the project
|
|
1356
|
+
hoSame: 'Same as the project: {label}',
|
|
1357
|
+
hoIn: 'in {dir}',
|
|
1358
|
+
hoEdit: 'Click to edit the path',
|
|
1095
1359
|
hoHtml: 'Plain HTML / CSS',
|
|
1096
1360
|
hoUni: 'uni-app (mini program + H5)',
|
|
1097
1361
|
hoWeapp: 'WeChat native mini program',
|
|
@@ -1109,10 +1373,16 @@ window.__ModuleLoader__.load({
|
|
|
1109
1373
|
hoCheckAny: 'After each screen, check it the way HANDOFF.md says (compare_design until the difference is under 3% when it runs in a browser; for a native mini program, ask the user to check it in WeChat DevTools), then move on. ',
|
|
1110
1374
|
library: 'Library',
|
|
1111
1375
|
libBack: 'Back to canvas',
|
|
1376
|
+
homeBtn: 'Home',
|
|
1377
|
+
dHome: 'Go home: start a new design, pick up another one, browse the library',
|
|
1378
|
+
homeBack: 'Back to "{name}"',
|
|
1379
|
+
askNewLine: '(Start a new design in a new folder; leave the current one as is.)',
|
|
1380
|
+
askNewNote: 'This starts a new design; the current one stays as is',
|
|
1381
|
+
wkHere: 'Open now',
|
|
1112
1382
|
libMarket: 'Gallery',
|
|
1113
1383
|
libWorks: 'My designs',
|
|
1114
1384
|
libInspire: 'Inspiration',
|
|
1115
|
-
libLine: 'Past designs, saved inspiration, design packs from friends',
|
|
1385
|
+
libLine: 'Past designs, saved inspiration, design packs from friends:',
|
|
1116
1386
|
libOpen: 'Open the library',
|
|
1117
1387
|
libClose: 'Back',
|
|
1118
1388
|
libFail: 'That did not work: {msg}',
|
|
@@ -1126,7 +1396,10 @@ window.__ModuleLoader__.load({
|
|
|
1126
1396
|
mkOffline: 'Cannot reach the gallery; showing what you saw last time.',
|
|
1127
1397
|
mkOff: '“Design library offline” is on: no network is used and only downloaded designs are shown. Turn it off in the Plugins page to go online.',
|
|
1128
1398
|
mkNeedUpdate: 'Some gallery items need a newer Design Studio: click Update on the Plugins page.',
|
|
1129
|
-
mkAt: 'Catalog
|
|
1399
|
+
mkAt: 'Catalog updated {ago}',
|
|
1400
|
+
mkUse0: 'Free to use',
|
|
1401
|
+
mkUseBY: 'Free to use · credit',
|
|
1402
|
+
mkSortT: 'Sort',
|
|
1130
1403
|
mkSeedNote: 'This is the set bundled with the studio',
|
|
1131
1404
|
mkRefresh: 'Refresh',
|
|
1132
1405
|
mkTypeTpl: 'Templates',
|
|
@@ -1183,6 +1456,9 @@ window.__ModuleLoader__.load({
|
|
|
1183
1456
|
wkNow: 'Open now',
|
|
1184
1457
|
wkOpen: 'Open',
|
|
1185
1458
|
wkReveal: 'Show in folder',
|
|
1459
|
+
wkRevealShort: 'Folder',
|
|
1460
|
+
wkZipShort: 'Zip',
|
|
1461
|
+
wkShareShort: 'Share',
|
|
1186
1462
|
wkPin: 'Pin',
|
|
1187
1463
|
wkUnpin: 'Unpin',
|
|
1188
1464
|
wkZip: 'Save as zip',
|
|
@@ -1201,7 +1477,7 @@ window.__ModuleLoader__.load({
|
|
|
1201
1477
|
agoHour: '{n} h ago',
|
|
1202
1478
|
agoDay: '{n} d ago',
|
|
1203
1479
|
inMyStyles: 'My styles',
|
|
1204
|
-
inSaveStyle: 'Save
|
|
1480
|
+
inSaveStyle: 'Save current',
|
|
1205
1481
|
inSaveStyleTip: 'Keep its colors, fonts and corners to reuse in one click',
|
|
1206
1482
|
inStyleSaved: 'Saved as style "{name}"',
|
|
1207
1483
|
inStyleNone: 'This design has no design tokens (tokens.css), so there is no style to save',
|
|
@@ -1273,7 +1549,8 @@ window.__ModuleLoader__.load({
|
|
|
1273
1549
|
exZip: 'Save as zip (to send)',
|
|
1274
1550
|
exShare: 'Share to the gallery',
|
|
1275
1551
|
shTitle: 'Share to the gallery',
|
|
1276
|
-
|
|
1552
|
+
shGh: 'You need a GitHub account to submit',
|
|
1553
|
+
shTip: 'What you share is public: anyone can see and reuse it. It is checked first, saved as a zip, then you submit it on GitHub.',
|
|
1277
1554
|
shName: 'Name',
|
|
1278
1555
|
shDesc: 'One-line description',
|
|
1279
1556
|
shDescPh: 'e.g. A three-screen ordering app for a corner tea shop',
|
|
@@ -1282,7 +1559,7 @@ window.__ModuleLoader__.load({
|
|
|
1282
1559
|
shAgreePrivate: 'It contains no real personal data (phone, email, ID numbers, address, photos of real people)',
|
|
1283
1560
|
shAgreeRules: 'I agree to publish it under the chosen license; it can be taken down any time, but copies others already made stay',
|
|
1284
1561
|
shBYNote: 'For CC BY, the credit is your GitHub username.',
|
|
1285
|
-
shGo: 'Check and
|
|
1562
|
+
shGo: 'Check and submit',
|
|
1286
1563
|
shChecking: 'Checking…',
|
|
1287
1564
|
shOk: 'Checked and saved: {name} (in Downloads)',
|
|
1288
1565
|
shSteps: 'Three more steps:',
|
|
@@ -1315,6 +1592,8 @@ window.__ModuleLoader__.load({
|
|
|
1315
1592
|
// ── 样式(DSH 设计变量,深浅主题跟着走)──────────────────────────────────
|
|
1316
1593
|
|
|
1317
1594
|
const ACCENT = '#4d6bfe'
|
|
1595
|
+
const TOP_R = 52 // 画布上面一排浮块占的高度:单页、演示、对比让开它,画布排的时候也让开
|
|
1596
|
+
const BOT_R = 64 // 底下一排(工具坞、视图、缩放)占的高度
|
|
1318
1597
|
const PICK_SRC = 'dsh-design-studio' // 输入框里点选标签的来源名(样式里也用)
|
|
1319
1598
|
// 输入框里的各种标签:图标按来源名分(DSH 的标签上只带来源名),消息里和悬停卡片按 data-i
|
|
1320
1599
|
const CHIP_ICON = {
|
|
@@ -1335,11 +1614,73 @@ window.__ModuleLoader__.load({
|
|
|
1335
1614
|
const EASE = 'cubic-bezier(.2,.8,.2,1)'
|
|
1336
1615
|
const CSS = `
|
|
1337
1616
|
.ddp{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--dsw-alias-bg-base,var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family,inherit);font-size:12px;container-type:inline-size}
|
|
1338
|
-
|
|
1339
|
-
.ddp-
|
|
1340
|
-
|
|
1341
|
-
.ddp-
|
|
1342
|
-
.ddp-
|
|
1617
|
+
/* 浮在画布上的几块:上面一排(左上 设计库 + 屏名,右上 外观 体检 交付 更多)、底部中间工具坞、左下视图和类型、右下缩放 */
|
|
1618
|
+
.ddp-isl{position:relative;z-index:9;display:flex;align-items:center;gap:2px;padding:4px;border-radius:12px;background:var(--dsw-alias-bg-layer-1);
|
|
1619
|
+
box-shadow:0 6px 20px rgba(0,0,0,.12),0 0 0 .5px var(--dsw-alias-border-l3)}
|
|
1620
|
+
.ddp-isl .ddp-btn{height:30px;min-width:30px;border-radius:8px}
|
|
1621
|
+
.ddp-isl .ddp-btn svg{width:16px;height:16px}
|
|
1622
|
+
.ddp-isl .ddp-btn .cr{width:10px;height:10px}
|
|
1623
|
+
.ddp-isl .ddp-sep{height:18px;margin:0 3px}
|
|
1624
|
+
.ddp-isl .ddp-menu{top:calc(100% + 8px)}
|
|
1625
|
+
.ddp-top{position:absolute;left:10px;right:10px;top:10px;z-index:9;display:flex;align-items:flex-start;gap:8px;pointer-events:none}
|
|
1626
|
+
.ddp-top>.ddp-isl{pointer-events:auto}
|
|
1627
|
+
.ddp-top>.tl{flex:0 1 auto;min-width:0}
|
|
1628
|
+
.ddp-top>.tl.wide{flex:1 1 auto;max-width:560px}
|
|
1629
|
+
.ddp-top>.tl.wide .ddp-input{height:30px}
|
|
1630
|
+
.ddp-top>.tr{margin-left:auto;flex:none}
|
|
1631
|
+
.ddp-isl .ddp-btn.pri{padding:0 8px 0 9px;background:${ACCENT};color:#fff;font-weight:500}
|
|
1632
|
+
.ddp-isl .ddp-btn.pri:hover,.ddp-isl .ddp-btn.pri.sel{background:#3d5bf0;color:#fff}
|
|
1633
|
+
.ddp-isl .ddp-btn.pri .cr{opacity:.8}
|
|
1634
|
+
.ddp-dock{position:absolute;left:50%;bottom:10px;transform:translateX(-50%);padding:4px;gap:2px}
|
|
1635
|
+
.ddp-dock .ddp-sep{height:26px}
|
|
1636
|
+
/* 悬停说明:深底浅字(跟提示消息一个配色,深色模式自己反过来);第一行名字 + 快捷键键帽,第二行一句用途
|
|
1637
|
+
说明控制在两行内,text-wrap:pretty 不让一个字单独掉到下一行 */
|
|
1638
|
+
.ddp-tipbox{position:absolute;left:0;top:0;z-index:30;display:none;flex-direction:column;gap:3px;width:max-content;max-width:min(228px,calc(100% - 16px));padding:7px 10px 8px;border-radius:9px;
|
|
1639
|
+
background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-1);font-size:12px;line-height:17px;letter-spacing:.01em;pointer-events:none;box-shadow:0 6px 20px rgba(0,0,0,.2)}
|
|
1640
|
+
.ddp-tipbox.on{display:flex;animation:ddp-tip .12s ease}
|
|
1641
|
+
.ddp-tipbox .nm{display:flex;align-items:center;gap:10px}
|
|
1642
|
+
.ddp-tipbox b{font-weight:600}
|
|
1643
|
+
.ddp-tipbox kbd{margin-left:auto;padding:0 5px;border-radius:4px;box-shadow:inset 0 0 0 1px currentColor;font:500 10.5px/16px ui-monospace,SFMono-Regular,Menlo,monospace;opacity:.6;white-space:nowrap}
|
|
1644
|
+
.ddp-tipbox span{opacity:.72;text-wrap:pretty}
|
|
1645
|
+
@keyframes ddp-tip{from{opacity:0}}
|
|
1646
|
+
.ddp-tool{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;width:48px;height:42px;padding:0;border:0;border-radius:8px;background:transparent;
|
|
1647
|
+
color:var(--dsw-alias-label-secondary);font:inherit;font-size:11px;line-height:13px;cursor:pointer;flex:none;white-space:nowrap}
|
|
1648
|
+
.ddp-tool svg{width:18px;height:18px;flex:none}
|
|
1649
|
+
.ddp-tool:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary)}
|
|
1650
|
+
.ddp-tool.on,.ddp-tool.on:hover:not(:disabled){background:${ACCENT};color:#fff}
|
|
1651
|
+
.ddp-tool:disabled{opacity:.4;cursor:default}
|
|
1652
|
+
.ddp-isl .ddp-btn.lab{padding:0 9px 0 7px}
|
|
1653
|
+
.ddp-isl .ddp-btn .lb{font-size:12px}
|
|
1654
|
+
/* 左上菜单里的「类型」:四列小格,一点就换 */
|
|
1655
|
+
.ddp-menu.l{min-width:268px}
|
|
1656
|
+
.ddp-kinds{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:2px;padding:0 2px 4px}
|
|
1657
|
+
.ddp-menu .ddp-kinds button{flex-direction:column;justify-content:center;gap:4px;height:50px;padding:0 2px;font-size:11px;text-align:center}
|
|
1658
|
+
.ddp-menu .ddp-kinds button span{max-width:100%}
|
|
1659
|
+
.ddp-menu .ddp-kinds button.sel{background:rgba(77,107,254,.12)}
|
|
1660
|
+
/* 标注时它有自己的上下两条:浮块都收起来 */
|
|
1661
|
+
.ddp-stage:has(>.ddp-mark)>.ddp-top,.ddp-stage:has(>.ddp-mark)>.ddp-isl{display:none}
|
|
1662
|
+
/* 上下两排浮块让出来:提示、消息、横幅、生成中、右边的面板都挪到两排中间 */
|
|
1663
|
+
.ddp-stage.isl>.ddp-hint{top:58px}
|
|
1664
|
+
.ddp-stage.isl>.ddp-toast:not(.mk){top:58px}
|
|
1665
|
+
.ddp-stage.isl>.ddp-toast.low{top:104px}
|
|
1666
|
+
.ddp-stage.isl>.ddp-banner{top:58px}
|
|
1667
|
+
.ddp-stage.isl>.ddp-chip{bottom:76px}
|
|
1668
|
+
.ddp-stage.isl>.ddp-chip.side{left:calc((100% - 296px) / 2)}
|
|
1669
|
+
.ddp-stage.isl>.ddp-tokens{top:58px;max-height:calc(100% - 58px - 74px)}
|
|
1670
|
+
.ddp-kind.ddp-where{flex:0 1 auto;min-width:80px;max-width:320px}
|
|
1671
|
+
.ddp-addr{width:100%;min-width:0;display:flex;align-items:center;gap:7px;height:30px;padding:0 8px 0 9px;border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-weight:500;cursor:pointer;border:0;overflow:hidden}
|
|
1672
|
+
.ddp-addr:hover,.ddp-addr.sel{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}
|
|
1673
|
+
.ddp-addr span.lb2 em{color:var(--dsw-alias-label-tertiary);font-weight:400}
|
|
1674
|
+
.ddp-addr span{flex:1;min-width:0;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
1675
|
+
/* 窄了先截名字,「· 3 屏」「· B」留着 */
|
|
1676
|
+
.ddp-addr span.lb2{display:flex;overflow:visible}
|
|
1677
|
+
.ddp-addr span.lb2 b{min-width:0;font-weight:inherit;overflow:hidden;text-overflow:ellipsis}
|
|
1678
|
+
.ddp-addr span.lb2 em{flex:none;font-style:normal;white-space:pre}
|
|
1679
|
+
.ddp-addr .cr{width:10px;height:10px;flex:none;opacity:.55}
|
|
1680
|
+
.ddp-menu.l{left:0;right:auto;max-width:min(320px,calc(100cqw - 16px))}
|
|
1681
|
+
.ddp-menu .ls{max-height:min(360px,55vh);overflow-y:auto}
|
|
1682
|
+
.ddp-menu button>span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
1683
|
+
.ddp-menu .sp{height:.5px;margin:4px 6px;background:var(--dsw-alias-border-l3)}
|
|
1343
1684
|
.ddp-dot{width:6px;height:6px;border-radius:50%;background:#22c55e;flex:none;box-shadow:0 0 0 3px rgba(34,197,94,.15)}
|
|
1344
1685
|
.ddp-dot.off{background:var(--dsw-alias-label-tertiary);box-shadow:none}
|
|
1345
1686
|
.ddp-dot.wait{background:#f5a524;box-shadow:0 0 0 3px rgba(245,165,36,.15);animation:ddp-blink 1s ease-in-out infinite}
|
|
@@ -1371,7 +1712,6 @@ window.__ModuleLoader__.load({
|
|
|
1371
1712
|
.ddp-menu button.sel,.ddp-menu button.sel svg{color:${ACCENT}}
|
|
1372
1713
|
.ddp-menu em{margin-left:auto;font-style:normal;color:var(--dsw-alias-label-tertiary);font-size:11px;font-variant-numeric:tabular-nums}
|
|
1373
1714
|
.ddp-stage{position:relative;flex:1;min-height:0;overflow:hidden;background:var(--dsw-alias-bg-layer-2)}
|
|
1374
|
-
.ddp-stage.full{background:#fff}
|
|
1375
1715
|
.ddp-scr{position:absolute;inset:0;overflow:hidden}
|
|
1376
1716
|
.ddp-away{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;padding:24px;text-align:center;font-size:13px;line-height:1.6;color:var(--dsw-alias-text-secondary,#888);background:var(--dsw-alias-bg-layer-1)}
|
|
1377
1717
|
.ddp-frame{position:absolute;left:0;top:0;width:100%;height:100%;border:0;background:#fff;opacity:0;z-index:0;pointer-events:none}
|
|
@@ -1403,7 +1743,12 @@ window.__ModuleLoader__.load({
|
|
|
1403
1743
|
.ddp-cap.w .sp{stroke:rgba(255,255,255,.25)}
|
|
1404
1744
|
.ddp-cap.w .rg{stroke:#fff}
|
|
1405
1745
|
.ddp-body{position:absolute;inset:0}
|
|
1406
|
-
.ddp-body.
|
|
1746
|
+
.ddp-body.pad{top:${TOP_R}px;bottom:${BOT_R}px}
|
|
1747
|
+
.ddp-body.pad.vb{top:${TOP_R + 46}px}
|
|
1748
|
+
.ddp-body.pad.ease{transition:top .42s ${EASE}}
|
|
1749
|
+
.ddp-body.pad.ease .ddp-fit{transition:transform .42s ${EASE}}
|
|
1750
|
+
/* 网页单页夹在上下两排浮块中间:描一条细边,和画布底色分开 */
|
|
1751
|
+
.ddp-body.pad .ddp-dev.fill{box-shadow:0 0 0 .5px var(--dsw-alias-border-l3)}
|
|
1407
1752
|
.ddp-single{position:absolute;inset:0;overflow:hidden}
|
|
1408
1753
|
.ddp-fit{position:absolute;left:50%;top:50%;transform-origin:center center}
|
|
1409
1754
|
.ddp-fit.fill{left:0;top:0;width:100%;height:100%;transform:none}
|
|
@@ -1421,15 +1766,64 @@ window.__ModuleLoader__.load({
|
|
|
1421
1766
|
.ddp-lab span{overflow:hidden;text-overflow:ellipsis}
|
|
1422
1767
|
.ddp-cell.sel .ddp-lab{color:${ACCENT};font-weight:500}
|
|
1423
1768
|
.ddp-lab .sp{width:10px;height:10px;flex:none;border-radius:50%;border:1.5px solid rgba(77,107,254,.25);border-top-color:${ACCENT};animation:ddp-rot .75s linear infinite}
|
|
1424
|
-
.ddp-cell.ghost>.ddp-dev{animation:ddp-ghost 1.6s ease-in-out infinite}
|
|
1425
1769
|
.ddp-cell.ghost .ddp-vp::after{content:"";position:absolute;inset:0;z-index:3;border:calc(2px / var(--z)) dashed rgba(77,107,254,.55);border-radius:inherit}
|
|
1426
1770
|
.ddp-cell.ghost .ddp-lab{color:${ACCENT}}
|
|
1427
1771
|
@keyframes ddp-ghost{50%{opacity:.62}}
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
.ddp-
|
|
1431
|
-
|
|
1432
|
-
.ddp-
|
|
1772
|
+
/* 生成中的占位:鼠标框版面 */
|
|
1773
|
+
.ddp-gd{position:absolute;inset:0;z-index:2;overflow:hidden;background:var(--dsw-alias-bg-layer-1);pointer-events:none}
|
|
1774
|
+
.ddp-gd .b{position:absolute;box-sizing:border-box;border:calc(1.5px / var(--z, 1)) dashed var(--c);border-radius:calc(8px / var(--z, 1));
|
|
1775
|
+
background:color-mix(in srgb,var(--c) 9%,transparent);overflow:hidden}
|
|
1776
|
+
.ddp-gd .b::after{content:"";position:absolute;inset:0;transform:translateX(-100%);background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--c) 22%,transparent),transparent);animation:ddp-shine 1.3s ease-in-out infinite}
|
|
1777
|
+
.ddp-gd .c{position:absolute;z-index:2;display:block;width:0;height:0}
|
|
1778
|
+
.ddp-gd .c svg{display:block;width:calc(16px / var(--z, 1));height:calc(20px / var(--z, 1));overflow:visible;filter:drop-shadow(0 1px 2px rgba(0,0,0,.28))}
|
|
1779
|
+
.ddp-gd .c path{fill:var(--c);stroke:#fff;stroke-width:1.5;stroke-linejoin:round}
|
|
1780
|
+
.ddp-gd .c em{position:absolute;left:calc(12px / var(--z, 1));top:calc(16px / var(--z, 1));padding:calc(2px / var(--z, 1)) calc(7px / var(--z, 1));
|
|
1781
|
+
border-radius:calc(4px / var(--z, 1)) calc(8px / var(--z, 1)) calc(8px / var(--z, 1)) calc(8px / var(--z, 1));background:var(--c);color:#fff;
|
|
1782
|
+
font-style:normal;font-weight:600;font-size:calc(11px / var(--z, 1));line-height:1.35;white-space:nowrap;box-shadow:0 2px 6px rgba(0,0,0,.18)}
|
|
1783
|
+
.ddp-gd .c.flip em{left:auto;right:calc(-4px / var(--z, 1))}
|
|
1784
|
+
/* 边写边看:正在写的那页盖在占位上,页面报到了才淡入 */
|
|
1785
|
+
.ddp-draft{position:absolute;inset:0;z-index:3;width:100%;height:100%;border:0;display:block;background:transparent;opacity:0;transition:opacity .5s ${EASE};color-scheme:normal;pointer-events:none}
|
|
1786
|
+
.ddp-draft.on{opacity:1}
|
|
1787
|
+
.ddp-fitd{position:absolute;inset:0;z-index:3;overflow:hidden}
|
|
1788
|
+
.ddp-fitd>div{position:absolute;left:50%;top:50%;transform-origin:center center}
|
|
1789
|
+
/* 顶上的状态条:在做什么 · 用了多久;三个点是三只鼠标的颜色 */
|
|
1790
|
+
.ddp-busy{position:absolute;left:50%;top:12px;transform:translateX(-50%);z-index:7;display:flex;align-items:center;gap:8px;max-width:calc(100% - 24px);height:30px;box-sizing:border-box;
|
|
1791
|
+
padding:0 12px 0 11px;border-radius:999px;overflow:hidden;background:rgba(18,18,24,.84);color:#f1f1f4;font-size:12px;line-height:16px;
|
|
1792
|
+
backdrop-filter:blur(10px) saturate(1.4);box-shadow:0 8px 24px rgba(0,0,0,.2),inset 0 0 0 .5px rgba(255,255,255,.14);pointer-events:none;animation:ddp-drop .34s ${EASE}}
|
|
1793
|
+
.ddp-busy.side{left:calc((100% - 296px) / 2);max-width:calc(100% - 320px)}
|
|
1794
|
+
.ddp-stage.isl>.ddp-busy{top:58px}
|
|
1795
|
+
/* 右侧栏够宽:状态条放进上排两块浮块中间,不挡画布 */
|
|
1796
|
+
@container (min-width:860px){.ddp-stage.isl>.ddp-busy:not(.side){top:13px;max-width:calc(100% - 600px)}.ddp-stage.isl.bz:has(>.ddp-busy:not(.side))>.ddp-hint,.ddp-stage.isl.bz:has(>.ddp-busy:not(.side))>.ddp-toast:not(.mk),.ddp-stage.isl.bz:has(>.ddp-busy:not(.side))>.ddp-banner,.ddp-stage.isl.bz:has(>.ddp-busy:not(.side))>.ddp-tokens{top:58px}}
|
|
1797
|
+
.ddp-busy .dots{display:flex;gap:3px;flex:none}
|
|
1798
|
+
.ddp-busy .dots i{width:5px;height:5px;border-radius:50%;background:#6d87ff;animation:ddp-hop 1.2s ease-in-out infinite}
|
|
1799
|
+
.ddp-busy .dots i:nth-child(2){background:#ff8a4c;animation-delay:.15s}
|
|
1800
|
+
.ddp-busy .dots i:nth-child(3){background:#2fd3a0;animation-delay:.3s}
|
|
1801
|
+
.ddp-busy .q{width:8px;height:8px;flex:none;border-radius:50%;background:#f59e0b;box-shadow:0 0 0 3px rgba(245,158,11,.25)}
|
|
1802
|
+
.ddp-busy .tx{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
|
|
1803
|
+
background:linear-gradient(90deg,#f1f1f4 0 38%,#a9b8ff 50%,#f1f1f4 62% 100%);background-size:260% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;animation:ddp-txt 2.2s linear infinite}
|
|
1804
|
+
.ddp-busy.ask .tx{background:none;color:#f1f1f4;animation:none}
|
|
1805
|
+
.ddp-busy .sub{min-width:0;flex:0 1000 auto;color:rgba(241,241,244,.5);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
1806
|
+
.ddp-busy .tm{flex:none;padding-left:8px;border-left:1px solid rgba(255,255,255,.16);color:rgba(241,241,244,.66);font-variant-numeric:tabular-nums}
|
|
1807
|
+
.ddp-busy .bar{position:absolute;left:0;right:0;bottom:0;height:2px;background:linear-gradient(90deg,transparent,#6d87ff,#a78bfa,#5eead4,transparent) no-repeat;background-size:38% 100%;animation:ddp-sweep 1.9s ease-in-out infinite}
|
|
1808
|
+
@keyframes ddp-hop{0%,60%,100%{transform:translateY(0)}30%{transform:translateY(-3px)}}
|
|
1809
|
+
@keyframes ddp-sweep{from{background-position:-60% 0}to{background-position:160% 0}}
|
|
1810
|
+
@keyframes ddp-drop{from{opacity:0;transform:translate(-50%,-8px)}}
|
|
1811
|
+
/* 状态条占了顶上那格:提示、消息、横幅往下挪一格 */
|
|
1812
|
+
.ddp-stage.bz>.ddp-hint,.ddp-stage.bz>.ddp-toast:not(.mk),.ddp-stage.bz~.ddp-toast:not(.mk),.ddp-stage.bz>.ddp-banner{top:54px}
|
|
1813
|
+
.ddp-stage.isl.bz>.ddp-hint,.ddp-stage.isl.bz>.ddp-toast:not(.mk),.ddp-stage.isl.bz>.ddp-banner,.ddp-stage.isl.bz>.ddp-tokens{top:100px}
|
|
1814
|
+
.ddp-stage.isl.bz>.ddp-toast.low{top:146px}
|
|
1815
|
+
.ddp-stage.isl.bz>.ddp-tokens{max-height:calc(100% - 100px - 74px)}
|
|
1816
|
+
@media (prefers-reduced-motion:reduce){
|
|
1817
|
+
.ddp-busy,.ddp-busy *,.ddp-gd *,.ddp-gd .b::after{animation:none!important}
|
|
1818
|
+
.ddp-busy .bar{display:none}
|
|
1819
|
+
.ddp-body.pad.ease,.ddp-body.pad.ease .ddp-fit{transition:none}
|
|
1820
|
+
}
|
|
1821
|
+
.ddp-zoom{position:absolute;right:10px;bottom:10px;z-index:6;display:flex;align-items:center;gap:2px;padding:4px;border-radius:12px;background:var(--dsw-alias-bg-layer-1);
|
|
1822
|
+
box-shadow:0 6px 20px rgba(0,0,0,.12),0 0 0 .5px var(--dsw-alias-border-l3)}
|
|
1823
|
+
.ddp-zoom .ddp-btn{height:30px;min-width:30px;padding:0 4px;border-radius:8px}
|
|
1824
|
+
.ddp-zoom .ddp-btn svg{width:15px;height:15px}
|
|
1825
|
+
.ddp-zoom .ddp-btn .lb{min-width:30px;text-align:right;font-variant-numeric:tabular-nums}
|
|
1826
|
+
.ddp-zoom .ddp-menu{top:auto;bottom:calc(100% + 8px);min-width:168px}
|
|
1433
1827
|
.ddp-hint{position:absolute;left:50%;top:12px;transform:translateX(-50%);z-index:7;max-width:calc(100% - 24px);padding:6px 13px;border-radius:999px;
|
|
1434
1828
|
background:rgba(18,18,24,.84);color:#ececf0;font-size:12px;line-height:18px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
|
|
1435
1829
|
box-shadow:0 6px 18px rgba(0,0,0,.22);backdrop-filter:blur(8px);pointer-events:none;animation:ddp-in .2s ease}
|
|
@@ -1487,7 +1881,7 @@ window.__ModuleLoader__.load({
|
|
|
1487
1881
|
.ddp-mk-bar button.go{background:${ACCENT};color:#fff;font-weight:600}
|
|
1488
1882
|
.ddp-mk-bar button.go:hover:not(:disabled){background:#3d5bf0}
|
|
1489
1883
|
.ddp-mk-bar button.go.idle{background:rgba(77,107,254,.45);color:rgba(255,255,255,.75)}
|
|
1490
|
-
.ddp-mk-coach{position:absolute;left:50%;
|
|
1884
|
+
.ddp-mk-coach{position:absolute;left:50%;bottom:72px;z-index:2;transform:translateX(-50%);width:max-content;max-width:calc(100% - 32px);padding:14px 18px 12px;border-radius:14px;pointer-events:none;background:rgba(24,24,27,.86);color:#f4f4f5;font-size:13px;line-height:1.55;box-shadow:0 10px 30px rgba(0,0,0,.25);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);animation:ddp-in .2s ease}
|
|
1491
1885
|
.ddp-mk-coach b{display:block;margin-bottom:6px;font-size:14px;font-weight:600;color:#fff}
|
|
1492
1886
|
.ddp-mk-coach ol{margin:0;padding:0 0 0 1.3em;color:rgba(244,244,245,.86)}
|
|
1493
1887
|
.ddp-mk-coach li+li{margin-top:2px}
|
|
@@ -1499,38 +1893,30 @@ window.__ModuleLoader__.load({
|
|
|
1499
1893
|
@keyframes ddp-shine{100%{transform:translateX(100%)}}
|
|
1500
1894
|
.ddp-wait{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;padding:24px}
|
|
1501
1895
|
.ddp-wait .card{position:relative;width:min(78%,440px);aspect-ratio:16/11;border-radius:12px;overflow:hidden;box-shadow:0 0 0 .5px var(--dsw-alias-border-l3),0 14px 40px rgba(0,0,0,.14);animation:ddp-ghost 1.6s ease-in-out infinite}
|
|
1896
|
+
.ddp-wait .card.k-app,.ddp-wait .card.k-miniprogram{width:auto;height:min(62%,560px);aspect-ratio:390/844;border-radius:22px}
|
|
1897
|
+
.ddp-wait .card.k-poster{width:auto;height:min(62%,560px);aspect-ratio:3/4}
|
|
1898
|
+
.ddp-wait .card.k-slides{aspect-ratio:16/9}
|
|
1899
|
+
.ddp-wait .card.k-email{width:min(60%,380px);aspect-ratio:3/4}
|
|
1900
|
+
.ddp-wait .card:has(.ddp-gd){animation:none}
|
|
1502
1901
|
.ddp-wait p{margin:0;color:var(--dsw-alias-label-tertiary);text-align:center;line-height:1.6}
|
|
1503
1902
|
.ddp-toast{position:absolute;left:12px;right:12px;top:14px;width:fit-content;max-width:calc(100% - 24px);margin-inline:auto;z-index:7;padding:7px 14px;border-radius:14px;background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-1);font-size:12px;line-height:1.5;text-align:center;overflow-wrap:anywhere;box-shadow:0 6px 20px rgba(0,0,0,.2);animation:ddp-in .2s ease}
|
|
1504
|
-
.ddp-
|
|
1505
|
-
.ddp-empty b{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500}
|
|
1506
|
-
.ddp-empty p{margin:6px 0 0;max-width:300px;line-height:1.6}
|
|
1507
|
-
.ddp-steps{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px;text-align:left;counter-reset:s}
|
|
1508
|
-
.ddp-steps li{display:flex;align-items:center;gap:9px;color:var(--dsw-alias-label-secondary);line-height:18px}
|
|
1509
|
-
.ddp-steps li::before{counter-increment:s;content:counter(s);flex:none;width:18px;height:18px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-size:10.5px;font-weight:600;color:${ACCENT};background:rgba(77,107,254,.12)}
|
|
1510
|
-
.ddp-art{width:176px;height:120px;overflow:visible;margin-bottom:2px}
|
|
1511
|
-
.ddp-art .pg{fill:var(--dsw-alias-bg-layer-1);stroke:var(--dsw-alias-border-l3);stroke-width:1}
|
|
1512
|
-
.ddp-art .ln{stroke:var(--dsw-alias-border-l3);stroke-width:1}
|
|
1513
|
-
.ddp-art .dt{fill:var(--dsw-alias-border-l3)}
|
|
1514
|
-
.ddp-art .sk{fill:var(--dsw-alias-bg-layer-2)}
|
|
1515
|
-
.ddp-art .bt{fill:${ACCENT};opacity:.9}
|
|
1516
|
-
.ddp-art .sel{fill:rgba(77,107,254,.08);stroke:${ACCENT};stroke-width:1.3;stroke-dasharray:4 3;animation:ddp-ants .9s linear infinite,ddp-sel 3.6s ease-in-out infinite}
|
|
1517
|
-
.ddp-art .cur{fill:var(--dsw-alias-label-primary);stroke:var(--dsw-alias-bg-layer-1);stroke-width:1;animation:ddp-cur 3.6s ease-in-out infinite}
|
|
1518
|
-
@keyframes ddp-ants{to{stroke-dashoffset:-14}}
|
|
1519
|
-
@keyframes ddp-sel{0%,30%{opacity:0}42%,88%{opacity:1}100%{opacity:0}}
|
|
1520
|
-
@keyframes ddp-cur{0%,100%{transform:translate(132px,98px)}36%,90%{transform:translate(58px,76px)}}
|
|
1521
|
-
.ddp-empty form{display:flex;flex-wrap:wrap;gap:6px;width:100%;max-width:320px;margin-top:4px}
|
|
1903
|
+
.ddp-home form{display:flex;flex-wrap:wrap;gap:6px;width:100%;margin:0}
|
|
1522
1904
|
.ddp-browse{height:26px;padding:0 12px;flex:none;border:.5px solid var(--dsw-alias-border-l3,rgba(127,127,127,.4));border-radius:999px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer}
|
|
1523
1905
|
.ddp-browse:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}
|
|
1524
1906
|
.ddp-drop .in.no{border-color:var(--dsw-alias-label-tertiary)}
|
|
1525
1907
|
.ddp-drop .in.no svg{color:var(--dsw-alias-label-tertiary)}
|
|
1526
|
-
.ddp-go{height:26px;padding:0 12px;border:0;border-radius:999px;background
|
|
1908
|
+
.ddp-go{height:26px;padding:0 12px;border:0;border-radius:999px;background:${ACCENT};color:#fff;font:inherit;cursor:pointer}
|
|
1909
|
+
.ddp-go:hover{filter:brightness(.94)}
|
|
1910
|
+
.ddp-go.sec{background:transparent;color:var(--dsw-alias-label-primary);box-shadow:inset 0 0 0 1px var(--dsw-alias-separator-primary,rgba(127,127,127,.35))}
|
|
1911
|
+
.ddp-go.sec:hover{filter:none;background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}
|
|
1912
|
+
.ddp-go:disabled{filter:none;background:transparent;color:var(--dsw-alias-label-tertiary);box-shadow:inset 0 0 0 1px var(--dsw-alias-separator-primary,rgba(127,127,127,.3));cursor:default}
|
|
1527
1913
|
.ddp-err{color:var(--dsw-alias-state-error-primary,#e5484d);font-size:11.5px}
|
|
1528
1914
|
.ddp-head{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:0;border-radius:var(--dsw-radius-md,8px);background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;position:relative}
|
|
1529
1915
|
.ddp-head:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary)}
|
|
1530
1916
|
.ddp-head svg{width:16px;height:16px}
|
|
1531
1917
|
.ddp-head.hint::after{content:"";position:absolute;right:5px;top:5px;width:5px;height:5px;border-radius:50%;background:var(--dsw-alias-accent-primary,#4f6ef7)}
|
|
1532
1918
|
.ddp-head.live::after{content:"";position:absolute;right:5px;top:5px;width:5px;height:5px;border-radius:50%;background:#22c55e}
|
|
1533
|
-
.ddp-tokens{position:absolute;top:10px;right:10px;
|
|
1919
|
+
.ddp-tokens{position:absolute;top:10px;right:10px;max-height:calc(100% - 20px);width:min(276px,calc(100% - 20px));z-index:7;display:flex;flex-direction:column;border-radius:12px;
|
|
1534
1920
|
background:var(--dsw-alias-bg-layer-1);box-shadow:0 12px 36px rgba(0,0,0,.22),0 0 0 .5px var(--dsw-alias-border-l3);overflow:hidden;animation:ddp-in .2s ${EASE}}
|
|
1535
1921
|
.ddp-tokens header{display:flex;align-items:center;gap:8px;padding:10px 8px 2px 14px;font-size:13px;font-weight:500}
|
|
1536
1922
|
.ddp-tokens header b{flex:1;font-weight:500}
|
|
@@ -1539,7 +1925,9 @@ window.__ModuleLoader__.load({
|
|
|
1539
1925
|
.ddp-tokens .tip{margin:0;padding:0 14px 8px;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}
|
|
1540
1926
|
.ddp-tokens .bd{flex:1;min-height:0;overflow:auto;padding:0 8px 10px}
|
|
1541
1927
|
.ddp-tokens h4{margin:10px 6px 4px;font-size:11px;font-weight:500;color:var(--dsw-alias-label-tertiary)}
|
|
1542
|
-
.ddp-phone{
|
|
1928
|
+
.ddp-phone details{margin-top:4px;text-align:left}
|
|
1929
|
+
.ddp-phone details summary{cursor:pointer;text-align:center}
|
|
1930
|
+
.ddp-phone details p{margin-top:4px}
|
|
1543
1931
|
.ddp-phone .bd{display:flex;flex-direction:column;align-items:center;text-align:center;padding:4px 16px 14px;gap:6px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
|
|
1544
1932
|
.ddp-phone .bd>*{flex:none}
|
|
1545
1933
|
.ddp-phone .qr{width:176px;height:176px;margin:6px 0 4px;padding:0;border-radius:12px;background:#fff;box-shadow:0 0 0 .5px rgba(0,0,0,.12)}
|
|
@@ -1560,6 +1948,9 @@ window.__ModuleLoader__.load({
|
|
|
1560
1948
|
.ddp-tokens footer button{display:flex;align-items:center;gap:7px;width:100%;height:30px;padding:0 10px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;text-align:left}
|
|
1561
1949
|
.ddp-tokens footer button:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary)}
|
|
1562
1950
|
.ddp-tokens footer svg{width:15px;height:15px;flex:none}
|
|
1951
|
+
/* 所有面板一样的禁用样子:描边、浅字,不像能点;主按钮都是 DSH 的蓝 */
|
|
1952
|
+
.ddp-tokens footer button:disabled{background:transparent;color:var(--dsw-alias-label-tertiary);box-shadow:none;cursor:default;opacity:1;filter:none}
|
|
1953
|
+
.ddp-tokens footer .go:disabled{box-shadow:inset 0 0 0 1px var(--dsw-alias-separator-primary,rgba(127,127,127,.3))}
|
|
1563
1954
|
.ddp{position:relative;outline:none}
|
|
1564
1955
|
.ddp-drop{position:absolute;inset:0;z-index:30;display:flex;align-items:center;justify-content:center;padding:16px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-1) 72%,transparent);backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);animation:ddp-in .15s ease}
|
|
1565
1956
|
.ddp-drop *{pointer-events:none}
|
|
@@ -1567,9 +1958,8 @@ window.__ModuleLoader__.load({
|
|
|
1567
1958
|
.ddp-drop .in{display:flex;flex-direction:column;align-items:center;gap:6px;width:100%;max-width:340px;padding:28px 20px;border:1.5px dashed ${ACCENT};border-radius:16px;background:var(--dsw-alias-bg-layer-1);box-shadow:0 10px 30px rgba(0,0,0,.18);text-align:center;color:var(--dsw-alias-label-tertiary);line-height:1.5}
|
|
1568
1959
|
.ddp-drop .in svg{width:28px;height:28px;color:${ACCENT};margin-bottom:4px}
|
|
1569
1960
|
.ddp-drop .in b{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500}
|
|
1570
|
-
.ddp-
|
|
1571
|
-
.ddp-
|
|
1572
|
-
.ddp-empty .lk:hover{text-decoration:underline;text-underline-offset:2px}
|
|
1961
|
+
.ddp-home .lk{border:0;padding:0;background:transparent;color:${ACCENT};font:inherit;cursor:pointer}
|
|
1962
|
+
.ddp-home .lk:hover{text-decoration:underline;text-underline-offset:2px}
|
|
1573
1963
|
.ddp-ck{align-items:flex-start}
|
|
1574
1964
|
.ddp-ck .no{width:18px;height:18px;flex:none;margin-top:1px;border-radius:9px;background:#f59e0b;color:#fff;font:700 10px/18px system-ui,sans-serif;text-align:center}
|
|
1575
1965
|
.ddp-ck .no.hi{background:#e5484d}
|
|
@@ -1577,10 +1967,10 @@ window.__ModuleLoader__.load({
|
|
|
1577
1967
|
.ddp-tok.ddp-ck .vl{white-space:normal;overflow-wrap:anywhere}
|
|
1578
1968
|
.ddp-check .ok{display:flex;align-items:center;gap:8px;padding:14px 6px;color:var(--dsw-alias-label-secondary)}
|
|
1579
1969
|
.ddp-check .ok svg{width:16px;height:16px;flex:none;color:#30a46c}
|
|
1580
|
-
.ddp-check footer.row,.ddp-style footer.row{display:flex;gap:6px}
|
|
1581
|
-
.ddp-check footer.row button,.ddp-style footer.row button{flex:1;justify-content:center;padding:0 6px}
|
|
1582
|
-
.ddp-check footer .go,.ddp-style footer .go{background
|
|
1583
|
-
.ddp-check footer .go:hover,.ddp-style footer .go:hover{background
|
|
1970
|
+
.ddp-check footer.row,.ddp-style footer.row,.ddp-look footer.row{display:flex;gap:6px}
|
|
1971
|
+
.ddp-check footer.row button,.ddp-style footer.row button,.ddp-look footer.row button{flex:1;justify-content:center;padding:0 6px}
|
|
1972
|
+
.ddp-check footer .go,.ddp-style footer .go,.ddp-look footer .go{background:${ACCENT};color:#fff}
|
|
1973
|
+
.ddp-check footer .go:hover,.ddp-style footer .go:hover,.ddp-look footer .go:hover{background:${ACCENT};color:#fff;filter:brightness(.94)}
|
|
1584
1974
|
.ddp-check footer button:disabled,.ddp-style footer button:disabled{opacity:.45;cursor:default}
|
|
1585
1975
|
.ddp-style .bd{padding:0 14px 10px}
|
|
1586
1976
|
.ddp-style .who{margin:0;padding:0 14px 6px;font-size:12px;line-height:17px;color:var(--dsw-alias-label-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
@@ -1648,14 +2038,114 @@ window.__ModuleLoader__.load({
|
|
|
1648
2038
|
.ddp-samview .row em{min-width:40px;font-style:normal;font-size:11px;text-align:center;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}
|
|
1649
2039
|
.ddp-samview .row .ddp-go{margin-left:auto;height:28px;font-size:12px}
|
|
1650
2040
|
.ddp-sc .nm{font-size:12px;line-height:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
1651
|
-
|
|
1652
|
-
.ddp-
|
|
1653
|
-
.ddp-
|
|
1654
|
-
|
|
1655
|
-
.ddp-
|
|
1656
|
-
.ddp-
|
|
1657
|
-
.ddp-
|
|
1658
|
-
.ddp-
|
|
2041
|
+
/* ── 首页:顶上页签、一句问话、大输入框、接着做、模板 ── */
|
|
2042
|
+
.ddp-home{position:relative;flex:1;min-height:0;display:flex;flex-direction:column;color:var(--dsw-alias-label-secondary);background:radial-gradient(120% 320px at 50% -80px,color-mix(in srgb,${ACCENT} 14%,transparent),transparent 72%)}
|
|
2043
|
+
.ddp-homebar{flex:none;display:flex;align-items:center;gap:8px;height:48px;padding:0 12px 0 16px}
|
|
2044
|
+
.ddp-homebar .nm{display:flex;align-items:center;gap:8px;min-width:0}
|
|
2045
|
+
.ddp-homebar .nm b{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);white-space:nowrap}
|
|
2046
|
+
.ddp-homebar .mk{flex:none;width:18px;height:18px;border-radius:6px;background:linear-gradient(135deg,#8ea0ff,${ACCENT} 55%,#3a55e0);box-shadow:0 2px 6px rgba(77,107,254,.35)}
|
|
2047
|
+
.ddp-homebar .seg{margin-left:auto}
|
|
2048
|
+
.ddp-homebar>.ddp-btn{margin-left:-6px}
|
|
2049
|
+
.ddp-homeback{display:flex;align-items:center;gap:3px;min-width:0;max-width:46%;height:30px;margin-left:-8px;padding:0 10px 0 5px;border:0;border-radius:8px;background:transparent;
|
|
2050
|
+
color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;cursor:pointer;flex:0 1 auto}
|
|
2051
|
+
.ddp-homeback svg{width:15px;height:15px;flex:none}
|
|
2052
|
+
.ddp-homeback span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
2053
|
+
.ddp-homeback:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary)}
|
|
2054
|
+
.ddp-homebar.led .nm{display:none}
|
|
2055
|
+
/* 开着设计时叠上来的首页:淡入、轻轻放大;几块依次浮上来;收起反过来 */
|
|
2056
|
+
.ddp-home.over~.ddp-libwrap{z-index:11}
|
|
2057
|
+
.ddp-home.over{position:absolute;inset:0;z-index:10;background-color:var(--dsw-alias-bg-base,var(--dsw-alias-bg-layer-1));animation:ddp-homein .28s ${EASE} both}
|
|
2058
|
+
.ddp-home.over.out{animation:ddp-homeout .19s ease both;pointer-events:none}
|
|
2059
|
+
.ddp-home .ddp-start>*{animation:ddp-rise .5s ${EASE} both}
|
|
2060
|
+
.ddp-home .ddp-start>:nth-child(2){animation-delay:.05s}
|
|
2061
|
+
.ddp-home .ddp-start>:nth-child(3){animation-delay:.1s}
|
|
2062
|
+
.ddp-home .ddp-start>:nth-child(4){animation-delay:.15s}
|
|
2063
|
+
.ddp-home .ddp-start>:nth-child(n+5){animation-delay:.2s}
|
|
2064
|
+
@keyframes ddp-homein{from{opacity:0;transform:scale(.985)}}
|
|
2065
|
+
@keyframes ddp-homeout{to{opacity:0;transform:scale(.985)}}
|
|
2066
|
+
@keyframes ddp-rise{from{opacity:0;transform:translateY(10px)}}
|
|
2067
|
+
.ddp-asknote{display:flex;align-items:center;justify-content:center;gap:5px;margin:-4px 0 0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
|
2068
|
+
.ddp-asknote svg{width:13px;height:13px;flex:none}
|
|
2069
|
+
.ddp-recent .wk{position:relative}
|
|
2070
|
+
.ddp-recent .wk.here .im{box-shadow:0 0 0 2px ${ACCENT}}
|
|
2071
|
+
.ddp-recent .wk .now{position:absolute;left:8px;top:8px;z-index:2;padding:1px 7px;border-radius:999px;background:${ACCENT};color:#fff;font-style:normal;font-size:11px;font-weight:600;line-height:18px;box-shadow:0 2px 6px rgba(0,0,0,.18)}
|
|
2072
|
+
@media (prefers-reduced-motion:reduce){.ddp-home.over,.ddp-home.over.out,.ddp-home .ddp-start>*{animation:none}}
|
|
2073
|
+
.ddp-lib>.ddp-homebar{border-bottom:1px solid var(--dsw-alias-separator-primary,rgba(127,127,127,.16))}
|
|
2074
|
+
.ddp-homebd{flex:1;min-height:0;overflow-y:auto;padding:0 20px 36px}
|
|
2075
|
+
.ddp-start{width:100%;max-width:600px;margin:0 auto;display:flex;flex-direction:column;gap:32px;text-align:left}
|
|
2076
|
+
.ddp-hero{margin:clamp(12px,7vh,52px) 0 -14px;text-align:center}
|
|
2077
|
+
.ddp-hero h1{margin:0;font-size:26px;line-height:34px;font-weight:600;letter-spacing:-.01em;color:var(--dsw-alias-label-primary);text-wrap:balance;word-break:keep-all}
|
|
2078
|
+
.ddp-hero p{margin:8px auto 0;max-width:440px;font-size:13px;line-height:20px;text-wrap:balance;word-break:keep-all}
|
|
2079
|
+
.ddp-start>.ddp-go{align-self:center;margin:-16px 0}
|
|
2080
|
+
.ddp-askwrap{display:flex;flex-direction:column;gap:14px}
|
|
2081
|
+
.ddp-ask{position:relative;border:1px solid var(--dsw-alias-separator-primary,rgba(127,127,127,.25));border-radius:16px;background:var(--dsw-alias-bg-layer-1);box-shadow:0 10px 30px rgba(0,0,0,.07),0 0 0 4px color-mix(in srgb,${ACCENT} 6%,transparent);transition:border-color .15s,box-shadow .15s}
|
|
2082
|
+
.ddp-ask:focus-within{border-color:color-mix(in srgb,${ACCENT} 50%,transparent);box-shadow:0 10px 30px rgba(0,0,0,.09),0 0 0 4px color-mix(in srgb,${ACCENT} 14%,transparent)}
|
|
2083
|
+
.ddp-askta{display:block;box-sizing:border-box;width:100%;min-height:78px;max-height:200px;field-sizing:content;padding:14px 16px 2px;border:0;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;resize:none;outline:none}
|
|
2084
|
+
.ddp-askta::placeholder{color:var(--dsw-alias-label-tertiary)}
|
|
2085
|
+
.ddp-ask>.row{display:flex;align-items:center;gap:6px;padding:6px 10px 10px}
|
|
2086
|
+
.ddp-ask .chip{display:inline-flex;align-items:center;gap:5px;min-width:0;height:30px;padding:0 11px;border:0;border-radius:999px;background:color-mix(in srgb,var(--dsw-alias-label-primary) 5%,transparent);color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;white-space:nowrap;cursor:pointer;transition:background .15s,color .15s}
|
|
2087
|
+
.ddp-ask .chip:hover,.ddp-ask .chip[aria-expanded=true]{background:color-mix(in srgb,var(--dsw-alias-label-primary) 9%,transparent);color:var(--dsw-alias-label-primary)}
|
|
2088
|
+
.ddp-ask .chip>span{min-width:0;overflow:hidden;text-overflow:ellipsis}
|
|
2089
|
+
.ddp-ask .chip svg{flex:none;width:14px;height:14px}
|
|
2090
|
+
.ddp-ask .chip .cr{width:10px;height:10px;opacity:.6}
|
|
2091
|
+
.ddp-ask .chip.on{background:color-mix(in srgb,${ACCENT} 13%,transparent);color:${ACCENT}}
|
|
2092
|
+
.ddp-ask .chip .sw{flex:none;width:12px;height:12px;border-radius:50%;box-shadow:inset 0 0 0 1px rgba(0,0,0,.15)}
|
|
2093
|
+
.ddp-askplus>.ddp-btn{width:30px;min-width:30px;height:30px;padding:0;border-radius:999px;background:color-mix(in srgb,var(--dsw-alias-label-primary) 5%,transparent)}
|
|
2094
|
+
.ddp-askplus>.ddp-btn:hover,.ddp-askplus>.ddp-btn.sel{background:color-mix(in srgb,var(--dsw-alias-label-primary) 9%,transparent)}
|
|
2095
|
+
.ddp-askplus>.ddp-btn svg{width:15px;height:15px}
|
|
2096
|
+
.ddp-askplus .ddp-menu{left:0;right:auto;min-width:200px}
|
|
2097
|
+
.ddp-ask .tabhint{margin-left:auto;display:flex;align-items:center;gap:5px;font-size:11px;color:var(--dsw-alias-label-tertiary);white-space:nowrap;cursor:pointer;border:0;padding:0 2px;background:transparent;font-family:inherit}
|
|
2098
|
+
.ddp-ask .tabhint:hover{color:var(--dsw-alias-label-secondary)}
|
|
2099
|
+
.ddp-ask kbd{padding:0 5px;border-radius:4px;box-shadow:inset 0 0 0 1px currentColor;font:500 10.5px/16px ui-monospace,SFMono-Regular,Menlo,monospace;opacity:.8}
|
|
2100
|
+
.ddp-ask .send{flex:none;display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;margin-left:auto;padding:0;border:0;border-radius:50%;background:${ACCENT};color:#fff;cursor:pointer;transition:filter .15s,opacity .15s}
|
|
2101
|
+
.ddp-ask .tabhint+.send{margin-left:8px}
|
|
2102
|
+
.ddp-ask .send:disabled{opacity:.3;cursor:default}
|
|
2103
|
+
.ddp-ask .send:hover:not(:disabled){filter:brightness(1.08)}
|
|
2104
|
+
.ddp-ask .send svg{width:17px;height:17px}
|
|
2105
|
+
.ddp-askpop{position:absolute;left:-1px;right:-1px;top:calc(100% + 8px);z-index:20;max-height:min(460px,62vh);overflow-y:auto;overscroll-behavior:contain;padding:14px;border:1px solid var(--dsw-alias-separator-primary,rgba(127,127,127,.25));border-radius:14px;background:var(--dsw-alias-bg-layer-1);box-shadow:0 16px 40px rgba(0,0,0,.18);text-align:left}
|
|
2106
|
+
.ddp-askpop .hd{display:flex;align-items:baseline;gap:8px;margin-bottom:10px}
|
|
2107
|
+
.ddp-askpop .hd b{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary)}
|
|
2108
|
+
.ddp-askpop .hd .lk{margin-left:auto;font-size:12px}
|
|
2109
|
+
.ddp-askpop .ddp-styles{grid-template-columns:repeat(auto-fill,minmax(92px,1fr));gap:10px}
|
|
2110
|
+
.ddp-askkinds{display:flex;flex-wrap:wrap;justify-content:center;gap:5px}
|
|
2111
|
+
.ddp-askkinds button{display:inline-flex;align-items:center;gap:4px;height:28px;padding:0 9px 0 8px;border:1px solid var(--dsw-alias-separator-primary,rgba(127,127,127,.28));border-radius:999px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;white-space:nowrap;cursor:pointer;transition:border-color .15s,color .15s,background .15s}
|
|
2112
|
+
.ddp-askkinds button:hover{border-color:var(--dsw-alias-label-tertiary);color:var(--dsw-alias-label-primary)}
|
|
2113
|
+
.ddp-askkinds button.on{border-color:${ACCENT};background:color-mix(in srgb,${ACCENT} 10%,transparent);color:${ACCENT}}
|
|
2114
|
+
.ddp-askkinds svg{width:13px;height:13px}
|
|
2115
|
+
.ddp-homesec>.hd{display:flex;align-items:baseline;gap:8px;margin-bottom:12px}
|
|
2116
|
+
.ddp-homesec>.hd b{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary)}
|
|
2117
|
+
.ddp-homesec>.hd .lk{margin-left:auto;font-size:12px}
|
|
2118
|
+
.ddp-recent,.ddp-tplstart .row{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px 12px}
|
|
2119
|
+
.ddp-recent .wk,.ddp-tplstart .tp{display:flex;flex-direction:column;gap:8px;min-width:0;padding:0;border:0;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}
|
|
2120
|
+
.ddp-recent .im,.ddp-tplstart .im{position:relative;display:flex;align-items:center;justify-content:center;border-radius:12px;overflow:hidden;background:var(--dsw-alias-bg-layer-2);box-shadow:0 0 0 .5px rgba(127,127,127,.35);color:var(--dsw-alias-label-tertiary);transition:transform .18s ${EASE},box-shadow .18s}
|
|
2121
|
+
.ddp-recent .im{aspect-ratio:16/10}
|
|
2122
|
+
.ddp-tplstart .im{aspect-ratio:4/5}
|
|
2123
|
+
.ddp-recent .im img,.ddp-tplstart .im img{position:absolute;inset:0;z-index:1;width:100%;height:100%;object-fit:cover;object-position:top}
|
|
2124
|
+
.ddp-recent .im svg,.ddp-tplstart .im svg{width:22px;height:22px}
|
|
2125
|
+
.ddp-recent .wk:hover .im,.ddp-recent .wk:focus-visible .im,.ddp-tplstart .tp:hover .im,.ddp-tplstart .tp:focus-visible .im{transform:translateY(-2px);box-shadow:0 0 0 1.5px ${ACCENT},0 8px 20px rgba(0,0,0,.12)}
|
|
2126
|
+
.ddp-recent .wk:focus-visible,.ddp-tplstart .tp:focus-visible{outline:none}
|
|
2127
|
+
.ddp-recent .tx,.ddp-tplstart .tx{display:flex;flex-direction:column;gap:1px;min-width:0;padding:0 2px}
|
|
2128
|
+
.ddp-recent .tx b,.ddp-tplstart .tx b{font-size:12.5px;line-height:18px;font-weight:500;color:var(--dsw-alias-label-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
2129
|
+
.ddp-recent .tx span,.ddp-tplstart .tx span{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
2130
|
+
@container (max-width:470px){
|
|
2131
|
+
.ddp-homebar .nm b{display:none}
|
|
2132
|
+
.ddp-homebd{padding:0 14px 28px}
|
|
2133
|
+
.ddp-hero h1{font-size:22px;line-height:30px}
|
|
2134
|
+
.ddp-recent,.ddp-tplstart .row{grid-template-columns:repeat(2,minmax(0,1fr))}
|
|
2135
|
+
.ddp-recent .wk:nth-child(3){display:none}
|
|
2136
|
+
.ddp-ask .tabhint{display:none}
|
|
2137
|
+
.ddp-ask .tabhint+.send{margin-left:auto}
|
|
2138
|
+
}
|
|
2139
|
+
@container (max-width:340px){.ddp-ask .chip.ref>span{display:none}.ddp-ask .chip.ref{padding:0 8px}}
|
|
2140
|
+
.ddp-more-st{display:block;margin-top:10px;font-size:12px}
|
|
2141
|
+
.ddp-ways{padding-top:16px;border-top:.5px solid var(--dsw-alias-separator-primary,rgba(127,127,127,.3))}
|
|
2142
|
+
.ddp-ways summary{display:inline-flex;align-items:center;gap:6px;list-style:none;cursor:pointer;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
|
|
2143
|
+
.ddp-ways summary::-webkit-details-marker{display:none}
|
|
2144
|
+
.ddp-ways summary:hover{color:var(--dsw-alias-label-primary)}
|
|
2145
|
+
.ddp-ways summary svg{width:12px;height:12px;transform:rotate(-90deg);transition:transform .15s ${EASE}}
|
|
2146
|
+
.ddp-ways[open] summary svg{transform:none}
|
|
2147
|
+
.ddp-ways .in{display:flex;flex-direction:column;gap:10px;margin-top:12px}
|
|
2148
|
+
.ddp-ways .in p{margin:0;line-height:1.55}
|
|
1659
2149
|
.ddp-tok{display:flex;align-items:center;gap:9px;width:100%;min-height:32px;padding:4px 6px;border:0;border-radius:7px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;cursor:pointer}
|
|
1660
2150
|
.ddp-tok:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}
|
|
1661
2151
|
.ddp-tok .pv{width:24px;height:24px;flex:none;display:flex;align-items:center;justify-content:center;overflow:hidden}
|
|
@@ -1699,13 +2189,18 @@ window.__ModuleLoader__.load({
|
|
|
1699
2189
|
background:var(--dsw-alias-bg-layer-1);box-shadow:0 8px 24px rgba(0,0,0,.18),0 0 0 .5px var(--dsw-alias-border-l3);font-size:12px;white-space:nowrap;animation:ddp-in .2s ${EASE}}
|
|
1700
2190
|
.ddp-banner>svg{width:14px;height:14px;flex:none;color:${ACCENT}}
|
|
1701
2191
|
.ddp-banner>span{overflow:hidden;text-overflow:ellipsis;min-width:0;margin-right:2px}
|
|
1702
|
-
|
|
1703
|
-
.ddp-banner
|
|
1704
|
-
.ddp-banner
|
|
2192
|
+
/* 试用横幅:「试用中」蓝字、屏名正文、提示淡;窄了先收提示 */
|
|
2193
|
+
.ddp-banner>b{flex:none;font-weight:600;color:${ACCENT}}
|
|
2194
|
+
.ddp-banner>.nm{color:var(--dsw-alias-label-primary)}
|
|
2195
|
+
.ddp-banner>em{flex:none;margin-right:4px;font-style:normal;color:var(--dsw-alias-label-tertiary)}
|
|
2196
|
+
@container (max-width:420px){.ddp-banner>em{display:none}}
|
|
2197
|
+
.ddp-banner .ddp-go{height:24px;padding:0 10px;font-size:12px;flex:none}
|
|
2198
|
+
.ddp-banner .ddp-go.sec.on{background:rgba(77,107,254,.12);color:${ACCENT};box-shadow:inset 0 0 0 1px ${ACCENT}}
|
|
1705
2199
|
.ddp-banner .ddp-btn{height:24px;min-width:24px;border-radius:999px}
|
|
1706
2200
|
.ddp-toast{display:flex;align-items:center;gap:10px}
|
|
1707
2201
|
.ddp-toast.low{top:56px}
|
|
1708
2202
|
.ddp-toast.mk{top:auto;bottom:62px}
|
|
2203
|
+
.ddp-toast.side{right:308px}
|
|
1709
2204
|
.ddp-toast button{border:0;padding:0;background:transparent;color:inherit;font:inherit;font-weight:600;text-decoration:underline;text-underline-offset:2px;cursor:pointer}
|
|
1710
2205
|
.ddp-cmp{position:absolute;inset:0;overflow:hidden}
|
|
1711
2206
|
.ddp-cmp>.ly{position:absolute;inset:0}
|
|
@@ -1722,20 +2217,60 @@ window.__ModuleLoader__.load({
|
|
|
1722
2217
|
.ddp-cmp .tag{position:absolute;top:10px;z-index:4;padding:3px 9px;border-radius:999px;background:rgba(0,0,0,.6);color:#fff;font:500 11px/16px -apple-system,BlinkMacSystemFont,"PingFang SC",sans-serif;pointer-events:none}
|
|
1723
2218
|
.ddp-cmp .tag.l{left:10px}
|
|
1724
2219
|
.ddp-cmp .tag.r{right:10px;background:${ACCENT}}
|
|
1725
|
-
|
|
2220
|
+
/* 屏头小按钮:悬停或选中那屏才出来(平时不占地方,给屏名和「用这版」留位置) */
|
|
2221
|
+
.ddp-act{margin-left:auto;display:none;gap:1px;flex:none}
|
|
2222
|
+
.ddp-cell:hover .ddp-act,.ddp-cell.sel .ddp-act{display:flex}
|
|
2223
|
+
.ddp-vt{flex:none;display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 4px;border-radius:5px;background:rgba(127,127,127,.14);color:var(--dsw-alias-label-secondary);font-size:11px;font-weight:600}
|
|
2224
|
+
.ddp-cell.sel .ddp-vt{background:rgba(77,107,254,.14);color:${ACCENT}}
|
|
1726
2225
|
.ddp-take{flex:none;display:inline-flex;align-items:center;gap:3px;height:20px;padding:0 8px 0 6px;border:0;border-radius:10px;
|
|
1727
2226
|
background:rgba(77,107,254,.12);color:${ACCENT};font:inherit;font-size:11px;font-weight:500;cursor:pointer}
|
|
1728
2227
|
.ddp-take:hover{background:rgba(77,107,254,.2)}
|
|
1729
2228
|
.ddp-take svg{width:12px;height:12px;flex:none}
|
|
1730
2229
|
/* 画布缩得很小时只留 ✓,屏名先保住 */
|
|
1731
2230
|
.ddp-lab{container-type:inline-size}
|
|
1732
|
-
.ddp-lab>span:first-
|
|
1733
|
-
@container (max-width:
|
|
2231
|
+
.ddp-lab>span:first-of-type{min-width:3em}
|
|
2232
|
+
@container (max-width:130px){.ddp-take{padding:0 4px}.ddp-take span{display:none}}
|
|
2233
|
+
@container (max-width:230px){.ddp-cell:hover .ddp-take,.ddp-cell.sel .ddp-take{padding:0 4px}.ddp-cell:hover .ddp-take span,.ddp-cell.sel .ddp-take span{display:none}}
|
|
1734
2234
|
.ddp-act .ddp-btn{height:22px;min-width:22px;padding:0 4px;color:${ACCENT}}
|
|
1735
2235
|
.ddp-act .ddp-btn svg{width:13px;height:13px}
|
|
1736
|
-
|
|
2236
|
+
/* 屏头的小按钮:屏够宽就带字(导出 / 再出两版 / 单看),窄了只剩图标,悬停看说明 */
|
|
2237
|
+
.ddp-act .lb{display:none;font-size:11px}
|
|
2238
|
+
@container (min-width:300px){.ddp-act .lb{display:inline}.ddp-act{gap:4px}}
|
|
1737
2239
|
.ddp-tw .row:first-child{border-top:0}
|
|
1738
2240
|
.ddp-sys .bd{display:flex;flex-direction:column;gap:8px;padding:2px 10px 10px}
|
|
2241
|
+
.ddp-sys.bare{flex:1;min-height:0;display:flex;flex-direction:column}
|
|
2242
|
+
.ddp-sys>form{display:flex;gap:6px;flex:none;padding:0 10px 10px}
|
|
2243
|
+
/* 外观面板:三页切换;整套里颜色两列、点色块直接改 */
|
|
2244
|
+
.ddp-lktabs{display:flex;gap:2px;flex:none;margin:6px 12px 8px;padding:2px;border-radius:8px;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.1))}
|
|
2245
|
+
.ddp-lktabs button{flex:1;height:26px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);font:12px/26px inherit;cursor:pointer}
|
|
2246
|
+
.ddp-lktabs button:hover{color:var(--dsw-alias-label-primary)}
|
|
2247
|
+
.ddp-lktabs button.on{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);box-shadow:0 1px 2px rgba(0,0,0,.14)}
|
|
2248
|
+
.ddp-look h4{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 6px;margin-top:12px}
|
|
2249
|
+
.ddp-look .bd>h4:first-child{margin-top:4px}
|
|
2250
|
+
.ddp-look h4 span{font-weight:400}
|
|
2251
|
+
.ddp-lkc{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:2px}
|
|
2252
|
+
.ddp-lkc .it{display:flex;align-items:center;gap:8px;min-width:0;padding:4px 6px;border-radius:7px;cursor:pointer}
|
|
2253
|
+
.ddp-lkc .it:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}
|
|
2254
|
+
.ddp-lkc .sw{position:relative;width:24px;height:24px;flex:none;border-radius:7px;box-shadow:inset 0 0 0 .5px rgba(127,127,127,.35);overflow:hidden}
|
|
2255
|
+
.ddp-lkc .sw input{position:absolute;inset:0;width:100%;height:100%;margin:0;padding:0;border:0;opacity:0;cursor:pointer}
|
|
2256
|
+
.ddp-lkc .it.on .sw{box-shadow:0 0 0 1.5px ${ACCENT}}
|
|
2257
|
+
.ddp-lkc .tx{flex:1;min-width:0;display:flex;flex-direction:column}
|
|
2258
|
+
.ddp-lkc .nm{font-size:12px;line-height:16px;color:var(--dsw-alias-label-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
2259
|
+
.ddp-lkc .vl{font:10.5px/14px ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--dsw-alias-label-tertiary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
2260
|
+
.ddp-lkc .it.on .vl{color:${ACCENT}}
|
|
2261
|
+
.ddp-look .ddp-tok .nm{font:12px/16px inherit}
|
|
2262
|
+
.ddp-lktw{padding:0 6px}
|
|
2263
|
+
.ddp-lktw .row:first-child{border-top:0;padding-top:2px}
|
|
2264
|
+
.ddp-lk-none{margin:0 6px;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.6}
|
|
2265
|
+
.ddp-tokens .lk{border:0;padding:0;background:transparent;color:${ACCENT};font:inherit;cursor:pointer}
|
|
2266
|
+
.ddp-tokens .lk:hover{text-decoration:underline;text-underline-offset:2px}
|
|
2267
|
+
.ddp-lk-more{display:flex;flex-direction:column;gap:2px;margin-top:12px;padding-top:8px;border-top:1px solid var(--dsw-alias-separator-primary,rgba(127,127,127,.16))}
|
|
2268
|
+
.ddp-lk-more button{display:flex;align-items:center;gap:7px;height:30px;padding:0 8px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;text-align:left;cursor:pointer}
|
|
2269
|
+
.ddp-lk-more button:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary)}
|
|
2270
|
+
.ddp-lk-more svg{width:15px;height:15px;flex:none}
|
|
2271
|
+
.ddp-look footer.row{flex-wrap:wrap;align-items:center}
|
|
2272
|
+
.ddp-look footer.row .cnt{flex-basis:100%;padding:0 2px 2px;font-size:11px;color:var(--dsw-alias-label-tertiary)}
|
|
2273
|
+
.ddp-style .who b{margin-right:2px;font-weight:500;color:var(--dsw-alias-label-primary)}
|
|
1739
2274
|
.ddp-sysc{display:flex;flex-direction:column;gap:5px;padding:8px;border-radius:10px;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.08))}
|
|
1740
2275
|
.ddp-sysc.def{box-shadow:inset 0 0 0 1.5px ${ACCENT}}
|
|
1741
2276
|
.ddp-sysc img{display:block;width:100%;aspect-ratio:16/10;object-fit:cover;object-position:top;border-radius:6px;background:var(--dsw-alias-bg-layer-1)}
|
|
@@ -1752,38 +2287,42 @@ window.__ModuleLoader__.load({
|
|
|
1752
2287
|
.ddp-sys form{display:flex;gap:6px}
|
|
1753
2288
|
.ddp-sys form input,.ddp-ho input[type=text]{flex:1;min-width:0;box-sizing:border-box;height:28px;padding:0 9px;border:1px solid var(--dsw-alias-separator-primary,rgba(127,127,127,.3));border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:12px inherit;outline:none}
|
|
1754
2289
|
.ddp-sys form input:focus,.ddp-ho input[type=text]:focus{border-color:${ACCENT}}
|
|
1755
|
-
.ddp-
|
|
1756
|
-
.ddp-
|
|
2290
|
+
.ddp-sys>form button[type=submit]{height:28px;flex:none;padding:0 12px;border:0;border-radius:8px;background:${ACCENT};color:#fff;font:12px inherit;cursor:pointer}
|
|
2291
|
+
.ddp-sys>form button[type=submit]:disabled{background:transparent;color:var(--dsw-alias-label-tertiary);box-shadow:inset 0 0 0 1px var(--dsw-alias-separator-primary,rgba(127,127,127,.3));cursor:default}
|
|
1757
2292
|
.ddp-ho .bd{padding:0 14px 10px}
|
|
1758
2293
|
.ddp-ho .row{padding:9px 0 10px;border-top:1px solid var(--dsw-alias-separator-primary,rgba(127,127,127,.16))}
|
|
1759
2294
|
.ddp-ho .row:first-child{border-top:0}
|
|
1760
2295
|
.ddp-ho label{display:block;margin-bottom:7px;font-size:12px;color:var(--dsw-alias-label-secondary)}
|
|
1761
2296
|
.ddp-ho .pathrow{display:flex;gap:6px;align-items:center}
|
|
2297
|
+
.ddp-ho .pathv{flex:1;min-width:0;display:flex;align-items:center;gap:6px;height:28px;padding:0 9px;border:1px solid var(--dsw-alias-separator-primary,rgba(127,127,127,.3));border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:12px inherit;text-align:left;cursor:text}
|
|
2298
|
+
.ddp-ho .pathv:hover{border-color:var(--dsw-alias-label-tertiary)}
|
|
2299
|
+
.ddp-ho .pathv svg{width:14px;height:14px;flex:none;color:var(--dsw-alias-label-secondary)}
|
|
2300
|
+
.ddp-ho .pathv b{font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:none;max-width:60%}
|
|
2301
|
+
.ddp-ho .pathv em{font-style:normal;color:var(--dsw-alias-label-tertiary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
|
|
1762
2302
|
.ddp-ho .moved{margin:6px 0 0;font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary)}
|
|
1763
2303
|
.ddp-ho .moved.bad{color:var(--dsw-alias-state-error-primary,#d54941)}
|
|
1764
2304
|
.ddp-dirbtn{display:flex;align-items:center;justify-content:center;width:28px;height:28px;flex:none;padding:0;border:1px solid var(--dsw-alias-separator-primary,rgba(127,127,127,.3));border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);cursor:pointer}
|
|
1765
2305
|
.ddp-dirbtn:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3,rgba(127,127,127,.5))}
|
|
1766
2306
|
.ddp-dirbtn:disabled{opacity:.5;cursor:default}
|
|
1767
2307
|
.ddp-dirbtn svg{width:15px;height:15px}
|
|
1768
|
-
.ddp-
|
|
1769
|
-
.ddp-tokens.ddp-sys footer form button.ddp-dirbtn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3,rgba(127,127,127,.5))}
|
|
2308
|
+
.ddp-sys>form button.ddp-dirbtn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3,rgba(127,127,127,.5))}
|
|
1770
2309
|
.ddp-ho input[type=text]{width:100%;box-sizing:border-box;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}
|
|
1771
2310
|
.ddp-ho .opt{display:flex;align-items:center;gap:8px;width:100%;min-height:28px;padding:4px 8px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary);font:12px/18px inherit;text-align:left;cursor:pointer}
|
|
1772
2311
|
.ddp-ho .opt:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}
|
|
1773
2312
|
.ddp-ho .opt i{width:12px;height:12px;flex:none;border-radius:50%;box-shadow:inset 0 0 0 1.5px var(--dsw-alias-label-tertiary)}
|
|
1774
2313
|
.ddp-ho .opt.on{color:var(--dsw-alias-label-primary)}
|
|
1775
2314
|
.ddp-ho .opt.on i{box-shadow:inset 0 0 0 4px ${ACCENT}}
|
|
1776
|
-
.ddp-ho footer .go{justify-content:center;background
|
|
1777
|
-
.ddp-ho footer .go:hover{background
|
|
1778
|
-
|
|
1779
|
-
.ddp-libwrap{position:absolute;inset:0;z-index:
|
|
2315
|
+
.ddp-ho footer .go{justify-content:center;background:${ACCENT};color:#fff}
|
|
2316
|
+
.ddp-ho footer .go:hover{background:${ACCENT};color:#fff;filter:brightness(.94)}
|
|
2317
|
+
|
|
2318
|
+
.ddp-libwrap{position:absolute;inset:0;z-index:10;display:flex;flex-direction:column;background:var(--dsw-alias-bg-base,var(--dsw-alias-bg-layer-1))}
|
|
1780
2319
|
.ddp-lib{flex:1;min-height:0;display:flex;flex-direction:column;animation:ddp-in .2s ${EASE}}
|
|
1781
|
-
.ddp-lib>header{flex:none;display:flex;align-items:center;gap:8px;height:44px;padding:0 10px 0 6px;border-bottom:1px solid var(--dsw-alias-separator-primary,rgba(127,127,127,.16))}
|
|
1782
|
-
.ddp-lib>header b{font-size:13px;font-weight:600;white-space:nowrap}
|
|
1783
|
-
.ddp-lib .seg{margin-left:auto;display:inline-flex;padding:2px;border-radius:8px;background:var(--dsw-alias-bg-layer-2)}
|
|
1784
|
-
.ddp-lib .seg button{height:26px;padding:0 12px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;white-space:nowrap}
|
|
1785
|
-
.ddp-lib .seg button:hover{color:var(--dsw-alias-label-primary)}
|
|
1786
|
-
.ddp-lib .seg button.on{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);box-shadow:0 1px 2px rgba(0,0,0,.12)}
|
|
2320
|
+
.ddp-lib>header:not(.ddp-homebar){flex:none;display:flex;align-items:center;gap:8px;height:44px;padding:0 10px 0 6px;border-bottom:1px solid var(--dsw-alias-separator-primary,rgba(127,127,127,.16))}
|
|
2321
|
+
.ddp-lib>header:not(.ddp-homebar) b{font-size:13px;font-weight:600;white-space:nowrap}
|
|
2322
|
+
.ddp-lib .seg,.ddp-homebar .seg{margin-left:auto;display:inline-flex;padding:2px;border-radius:8px;background:var(--dsw-alias-bg-layer-2)}
|
|
2323
|
+
.ddp-lib .seg button,.ddp-homebar .seg button{height:26px;padding:0 12px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;white-space:nowrap}
|
|
2324
|
+
.ddp-lib .seg button:hover,.ddp-homebar .seg button:hover{color:var(--dsw-alias-label-primary)}
|
|
2325
|
+
.ddp-lib .seg button.on,.ddp-homebar .seg button.on{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);box-shadow:0 1px 2px rgba(0,0,0,.12)}
|
|
1787
2326
|
.ddp-lib>.bd{flex:1;min-height:0;overflow:auto;padding:14px}
|
|
1788
2327
|
.ddp-pack .lk{border:0;padding:0;background:transparent;color:${ACCENT};font:inherit;cursor:pointer}
|
|
1789
2328
|
.ddp-pack .lk:hover{text-decoration:underline;text-underline-offset:2px}
|
|
@@ -1796,11 +2335,11 @@ window.__ModuleLoader__.load({
|
|
|
1796
2335
|
.ddp-libnone p{margin:0;max-width:320px}
|
|
1797
2336
|
.ddp-libfoot{margin:18px 0 0;font-size:11px;line-height:1.5;color:var(--dsw-alias-label-tertiary);text-align:center}
|
|
1798
2337
|
.ddp-works{display:grid;grid-template-columns:repeat(auto-fill,minmax(168px,1fr));gap:16px 14px}
|
|
1799
|
-
.ddp-work{display:flex;flex-direction:column;gap:4px;min-width:0}
|
|
2338
|
+
.ddp-work{display:flex;flex-direction:column;gap:4px;min-width:0;container-type:inline-size}
|
|
1800
2339
|
.ddp-work .th{position:relative;display:block;aspect-ratio:4/3;padding:0;border:0;border-radius:10px;overflow:hidden;background:var(--dsw-alias-bg-layer-2);cursor:pointer;box-shadow:0 0 0 .5px rgba(127,127,127,.35);transition:transform .15s ${EASE},box-shadow .15s}
|
|
1801
2340
|
.ddp-work .th:hover:not(:disabled),.ddp-work .th:focus-visible{outline:none;transform:translateY(-1px);box-shadow:0 0 0 1.5px ${ACCENT},0 6px 16px rgba(0,0,0,.12)}
|
|
1802
2341
|
.ddp-work .th:disabled{cursor:default}
|
|
1803
|
-
.ddp-work .th img{position:absolute;inset:0;z-index:1;width:100%;height:100%;object-fit:cover;object-position:top}
|
|
2342
|
+
.ddp-work .th img{position:absolute;inset:0;z-index:1;width:100%;height:100%;object-fit:cover;object-position:left top}
|
|
1804
2343
|
.ddp-work .th .ic{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:var(--dsw-alias-label-tertiary)}
|
|
1805
2344
|
.ddp-work .th .ic svg{width:26px;height:26px}
|
|
1806
2345
|
.ddp-work .th em{position:absolute;left:8px;top:8px;z-index:2;padding:1px 7px;border-radius:9px;background:${ACCENT};color:#fff;font-size:11px;font-style:normal}
|
|
@@ -1811,13 +2350,17 @@ window.__ModuleLoader__.load({
|
|
|
1811
2350
|
.ddp-work .bdg{flex:none;height:17px;padding:0 6px;border-radius:9px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font-size:10.5px;line-height:17px}
|
|
1812
2351
|
.ddp-work .pin{flex:none;color:#f5a524;font-size:11px}
|
|
1813
2352
|
.ddp-work .mt{font-size:11px;color:var(--dsw-alias-label-tertiary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
1814
|
-
.ddp-work .acts{display:flex;align-items:center;gap:2px;min-width:0;margin-left:-4px}
|
|
2353
|
+
.ddp-work .acts{display:flex;flex-wrap:wrap;align-items:center;gap:2px;min-width:0;margin-left:-4px}
|
|
2354
|
+
/* 卡片窄(侧栏窄、多列):操作只留图标(悬停有说明),别挤进旁边那件 */
|
|
2355
|
+
@container (max-width:259px){.ddp-work .acts button svg+span{display:none}}
|
|
1815
2356
|
.ddp-work .acts button,.ddp-insp .rm,.ddp-market .rm{display:inline-flex;align-items:center;justify-content:center;flex:none;height:24px;min-width:24px;padding:0 6px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;white-space:nowrap}
|
|
1816
2357
|
.ddp-work .acts button:hover:not(:disabled),.ddp-insp .rm:hover,.ddp-market button.rm:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary)}
|
|
1817
2358
|
.ddp-work .acts button:disabled{opacity:.45;cursor:default}
|
|
1818
2359
|
.ddp-work .acts button.on{color:#f5a524}
|
|
1819
2360
|
.ddp-work .acts svg,.ddp-insp .rm svg,.ddp-market .rm svg{width:14px;height:14px}
|
|
1820
2361
|
.ddp-work .acts .rm{margin-left:auto}
|
|
2362
|
+
.ddp-work .acts button span{font-size:11px}
|
|
2363
|
+
.ddp-work .acts button svg+span{margin-left:3px}
|
|
1821
2364
|
.ddp-work .acts .rm.sure,.ddp-insp .rm.sure{color:var(--dsw-alias-state-error-primary,#d54941);background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#d54941) 12%,transparent);font-size:11px}
|
|
1822
2365
|
.ddp-insp h4{margin:18px 0 8px;font-size:12px;font-weight:500;color:var(--dsw-alias-label-secondary)}
|
|
1823
2366
|
.ddp-insp h4:first-child{margin-top:0}
|
|
@@ -1835,11 +2378,23 @@ window.__ModuleLoader__.load({
|
|
|
1835
2378
|
.ddp-imgs{display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:8px}
|
|
1836
2379
|
.ddp-imgs:empty{display:none}
|
|
1837
2380
|
.ddp-market .ddp-mkbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:10px}
|
|
1838
|
-
.ddp-market .ddp-mkbar .
|
|
1839
|
-
.ddp-market .ddp-mkbar .
|
|
2381
|
+
.ddp-market .ddp-mkbar .tabs{display:flex;gap:14px;flex:none}
|
|
2382
|
+
.ddp-market .ddp-mkbar .tabs button{display:flex;align-items:baseline;gap:4px;height:28px;padding:0;border:0;border-bottom:2px solid transparent;background:transparent;color:var(--dsw-alias-label-secondary);font:13px inherit;cursor:pointer;white-space:nowrap}
|
|
2383
|
+
.ddp-market .ddp-mkbar .tabs button em{font-style:normal;font-size:11px;color:var(--dsw-alias-label-tertiary)}
|
|
2384
|
+
.ddp-market .ddp-mkbar .tabs button:hover{color:var(--dsw-alias-label-primary)}
|
|
2385
|
+
.ddp-market .ddp-mkbar .tabs button.on{color:var(--dsw-alias-label-primary);font-weight:500;border-bottom-color:${ACCENT}}
|
|
2386
|
+
.ddp-market .ddp-mkbar .q{flex:1 1 140px;min-width:0;height:28px;padding:0 10px 0 28px;background-image:${iconUrl('M11 4a7 7 0 1 0 0 14a7 7 0 1 0 0-14 M20 20l-4.2-4.2').replace('black', '%23999')};background-repeat:no-repeat;background-size:14px;background-position:8px center;border:1px solid var(--dsw-alias-separator-primary,rgba(127,127,127,.3));border-radius:8px;background-color:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:12px inherit;outline:none}
|
|
1840
2387
|
.ddp-market .ddp-mkbar .q:focus{border-color:${ACCENT}}
|
|
1841
|
-
|
|
1842
|
-
.ddp-
|
|
2388
|
+
/* 窄了:搜索框缩成一个放大镜,点进去再展开 */
|
|
2389
|
+
@container (max-width:480px){.ddp-market .ddp-mkbar .q{flex:0 0 32px;padding-right:0;cursor:pointer;transition:flex-basis .15s ${EASE}}.ddp-market .ddp-mkbar .q::placeholder{color:transparent}.ddp-market .ddp-mkbar .q:focus,.ddp-market .ddp-mkbar .q:not(:placeholder-shown){flex:1 1 140px;cursor:text}.ddp-market .ddp-mkbar .q:focus::placeholder{color:var(--dsw-alias-label-tertiary)}}
|
|
2390
|
+
.ddp-mkkinds{display:flex;align-items:center;gap:6px;margin:0 0 12px;overflow-x:auto;scrollbar-width:none}
|
|
2391
|
+
.ddp-mkkinds::-webkit-scrollbar{display:none}
|
|
2392
|
+
.ddp-mkkinds>button{flex:none}
|
|
2393
|
+
.ddp-mkkinds button:disabled{opacity:.4;cursor:default}
|
|
2394
|
+
.ddp-mkbar .sort{display:flex;gap:2px;flex:none}
|
|
2395
|
+
.ddp-mkbar .sort button{height:24px;padding:0 8px;border:0;border-radius:12px;background:transparent;color:var(--dsw-alias-label-secondary);font:12px inherit;cursor:pointer;white-space:nowrap}
|
|
2396
|
+
.ddp-mkbar .sort button:hover{color:var(--dsw-alias-label-primary)}
|
|
2397
|
+
.ddp-mkbar .sort button.on{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
|
|
1843
2398
|
.ddp-mkkinds button,.ddp-mkdots button{height:24px;padding:0 10px;border:0;border-radius:12px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font:12px inherit;cursor:pointer;white-space:nowrap}
|
|
1844
2399
|
.ddp-mkkinds button:hover,.ddp-mkdots button:hover{color:var(--dsw-alias-label-primary)}
|
|
1845
2400
|
.ddp-mkkinds button.on,.ddp-mkdots button.on{background:${ACCENT};color:#fff}
|
|
@@ -1859,25 +2414,14 @@ window.__ModuleLoader__.load({
|
|
|
1859
2414
|
.ddp-mkdots{display:flex;flex-wrap:wrap;gap:6px}
|
|
1860
2415
|
.ddp-mkd .acts{display:flex;gap:8px;flex-wrap:wrap;margin-top:4px}
|
|
1861
2416
|
.ddp-pbtn,.ddp-sbtn{height:32px;padding:0 16px;border:0;border-radius:8px;font:13px inherit;cursor:pointer;white-space:nowrap}
|
|
1862
|
-
.ddp-pbtn{background
|
|
2417
|
+
.ddp-pbtn{background:${ACCENT};color:#fff;font-weight:600}
|
|
2418
|
+
.ddp-pbtn:hover:not(:disabled){filter:brightness(.94)}
|
|
1863
2419
|
.ddp-sbtn{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
|
|
1864
2420
|
.ddp-pbtn:disabled,.ddp-sbtn:disabled{opacity:.5;cursor:default}
|
|
1865
2421
|
.ddp-mkd .facts{margin:6px 0 0;padding-left:18px;font-size:12px;line-height:1.6;color:var(--dsw-alias-label-secondary)}
|
|
1866
2422
|
.ddp-mkd .ddp-libfoot a{color:${ACCENT};text-decoration:none}
|
|
1867
2423
|
.ddp-mkd .ddp-libfoot a:hover{text-decoration:underline}
|
|
1868
2424
|
.ddp-mkd code{font-size:11px;color:var(--dsw-alias-label-tertiary)}
|
|
1869
|
-
.ddp-tplstart{width:100%;max-width:560px;margin:18px auto 0;text-align:left}
|
|
1870
|
-
.ddp-tplstart .hd{display:flex;align-items:baseline;gap:8px;margin-bottom:8px}
|
|
1871
|
-
.ddp-tplstart .hd b{font-size:13px;font-weight:600}
|
|
1872
|
-
.ddp-tplstart .hd .lk{margin-left:auto;border:0;padding:0;background:transparent;color:${ACCENT};font:12px inherit;cursor:pointer}
|
|
1873
|
-
.ddp-tplstart .row{display:grid;grid-template-columns:repeat(auto-fill,minmax(84px,1fr));gap:10px}
|
|
1874
|
-
.ddp-tplstart .tp{display:flex;flex-direction:column;gap:4px;padding:0;border:0;background:transparent;color:var(--dsw-alias-label-primary);font:11px inherit;text-align:left;cursor:pointer;min-width:0}
|
|
1875
|
-
.ddp-tplstart .im{position:relative;display:flex;align-items:center;justify-content:center;aspect-ratio:3/4;border-radius:8px;overflow:hidden;background:var(--dsw-alias-bg-layer-2);box-shadow:0 0 0 .5px rgba(127,127,127,.35);color:var(--dsw-alias-label-tertiary);transition:transform .15s ${EASE},box-shadow .15s}
|
|
1876
|
-
.ddp-tplstart .tp:hover .im,.ddp-tplstart .tp:focus-visible .im{transform:translateY(-1px);box-shadow:0 0 0 1.5px ${ACCENT}}
|
|
1877
|
-
.ddp-tplstart .tp:focus-visible{outline:none}
|
|
1878
|
-
.ddp-tplstart .im img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:top}
|
|
1879
|
-
.ddp-tplstart .im svg{width:20px;height:20px}
|
|
1880
|
-
.ddp-tplstart .nm{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
1881
2425
|
.ddp-img{position:relative;aspect-ratio:1;border-radius:8px;overflow:hidden;background:var(--dsw-alias-bg-layer-2)}
|
|
1882
2426
|
.ddp-img img{display:block;width:100%;height:100%;object-fit:cover}
|
|
1883
2427
|
.ddp-img .acts{position:absolute;left:0;right:0;bottom:0;display:flex;align-items:center;gap:4px;padding:6px;background:linear-gradient(transparent,rgba(0,0,0,.55));opacity:0;transition:opacity .15s}
|
|
@@ -1896,8 +2440,8 @@ window.__ModuleLoader__.load({
|
|
|
1896
2440
|
.ddp-urlform{display:flex;gap:6px}
|
|
1897
2441
|
.ddp-urlform input{flex:1;min-width:0;box-sizing:border-box;height:28px;padding:0 9px;border:1px solid var(--dsw-alias-separator-primary,rgba(127,127,127,.3));border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:12px inherit;outline:none}
|
|
1898
2442
|
.ddp-urlform input:focus{border-color:${ACCENT}}
|
|
1899
|
-
.ddp-urlform button{flex:none;height:28px;padding:0 12px;border:0;border-radius:8px;background
|
|
1900
|
-
.ddp-urlform button:disabled{
|
|
2443
|
+
.ddp-urlform button{flex:none;height:28px;padding:0 12px;border:0;border-radius:8px;background:${ACCENT};color:#fff;font:12px inherit;cursor:pointer}
|
|
2444
|
+
.ddp-urlform button:disabled{background:transparent;color:var(--dsw-alias-label-tertiary);box-shadow:inset 0 0 0 1px var(--dsw-alias-separator-primary,rgba(127,127,127,.3));cursor:default}
|
|
1901
2445
|
.ddp-pack .which{display:flex;align-items:center;gap:6px;margin:4px 6px 2px;font-size:12px;color:var(--dsw-alias-label-primary);min-width:0}
|
|
1902
2446
|
.ddp-pack .which svg{width:14px;height:14px;flex:none;color:var(--dsw-alias-label-tertiary)}
|
|
1903
2447
|
.ddp-pack .which span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
@@ -1905,6 +2449,8 @@ window.__ModuleLoader__.load({
|
|
|
1905
2449
|
.ddp-pack .lic label{display:flex;align-items:flex-start;gap:8px;padding:6px 8px;border-radius:8px;color:var(--dsw-alias-label-secondary);line-height:1.5;cursor:pointer}
|
|
1906
2450
|
.ddp-pack .lic label.on{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
|
|
1907
2451
|
.ddp-pack .lic input{flex:none;margin:3px 0 0;accent-color:${ACCENT}}
|
|
2452
|
+
.ddp-share .tip.gh{display:flex;align-items:center;gap:6px;margin-bottom:0;color:var(--dsw-alias-label-primary);font-weight:500}
|
|
2453
|
+
.ddp-share .tip.gh svg{width:14px;height:14px;flex:none;color:${ACCENT}}
|
|
1908
2454
|
.ddp-share .agree{display:flex;flex-direction:column;gap:2px;margin-top:10px}
|
|
1909
2455
|
.ddp-share .agree label{display:flex;align-items:flex-start;gap:8px;padding:5px 8px;border-radius:8px;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5;cursor:pointer}
|
|
1910
2456
|
.ddp-share .agree label:hover{color:var(--dsw-alias-label-primary)}
|
|
@@ -1925,14 +2471,13 @@ window.__ModuleLoader__.load({
|
|
|
1925
2471
|
.ddp-pack .note ul{margin:2px 0 0;padding-left:16px}
|
|
1926
2472
|
.ddp-pack footer.row{display:flex;gap:6px}
|
|
1927
2473
|
.ddp-pack footer.row button{flex:1;justify-content:center;padding:0 6px}
|
|
1928
|
-
.ddp-pack footer .go{background
|
|
1929
|
-
.ddp-pack footer .go:hover{background
|
|
1930
|
-
|
|
2474
|
+
.ddp-pack footer .go{background:${ACCENT};color:#fff}
|
|
2475
|
+
.ddp-pack footer .go:hover{background:${ACCENT};color:#fff;filter:brightness(.94)}
|
|
2476
|
+
|
|
1931
2477
|
.ddp-libwrap>.ddp-toast{z-index:3}
|
|
1932
|
-
/*
|
|
1933
|
-
.ddp-
|
|
1934
|
-
@container (max-width:
|
|
1935
|
-
@container (max-width:580px){.ddp-bar>.o2{display:none}.ddp-menu .o2{display:flex}}
|
|
2478
|
+
/* 浮块窄了:先截屏名,到 440 再收按钮上的字,400 收工具坞里的演示(演示时横幅上能退出) */
|
|
2479
|
+
@container (max-width:440px){.ddp-isl .lab .lb,.ddp-isl .pri .lb{display:none}.ddp-isl .ddp-btn.lab{padding:0 6px}.ddp-tool{width:36px;height:36px}.ddp-tool span{display:none}.ddp-dock .ddp-sep{height:20px}}
|
|
2480
|
+
@container (max-width:380px){.ddp-tool{width:32px}.ddp-kind.ddp-where{min-width:64px}}
|
|
1936
2481
|
/* 标注底栏:右侧栏窄时「这支笔怎么用」折成两行,不被三个按钮挤成省略号 */
|
|
1937
2482
|
@container (max-width:640px){.ddp-mk-bar{border-radius:18px}.ddp-mk-bar span{white-space:normal;line-height:1.35;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}}
|
|
1938
2483
|
/* 画标注时预览不动,画布右下角的缩放按钮被标注层盖着也点不到:先收起来,免得和底栏叠在一起 */
|
|
@@ -2006,9 +2551,11 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
2006
2551
|
more: () => svg([h('circle', { key: 1, cx: 5.5, cy: 12, r: 1.3 }), h('circle', { key: 2, cx: 12, cy: 12, r: 1.3 }), h('circle', { key: 3, cx: 18.5, cy: 12, r: 1.3 })]),
|
|
2007
2552
|
play: () => svg([h('path', { key: 1, d: 'M8 5.5v13l10.5-6.5z', strokeLinejoin: 'round' })]),
|
|
2008
2553
|
back: () => svg([h('path', { key: 1, d: 'M14.5 6l-6 6 6 6', strokeWidth: 2.2 })]),
|
|
2554
|
+
home: () => svg([h('path', { key: 1, d: 'M4.5 10.2 12 4.2l7.5 6V19a1 1 0 0 1-1 1h-4v-5.5h-5V20h-4a1 1 0 0 1-1-1z' })]),
|
|
2009
2555
|
moon: () => svg([h('path', { key: 1, d: 'M20 14.5A8 8 0 0 1 9.5 4a8 8 0 1 0 10.5 10.5z' })]),
|
|
2010
2556
|
sparkle: () => svg([h('path', { key: 1, d: 'M12 3.5l1.8 5.2 5.2 1.8-5.2 1.8L12 17.5l-1.8-5.2L5 10.5l5.2-1.8z' }), h('path', { key: 2, d: 'M18.5 16v4M16.5 18h4' })]),
|
|
2011
2557
|
plus: () => svg([h('path', { key: 1, d: 'M12 5v14M5 12h14' })]),
|
|
2558
|
+
send: () => svg([h('path', { key: 1, d: 'M12 19V5.5M6 11.5l6-6 6 6', strokeWidth: 2.2 })]),
|
|
2012
2559
|
minus: () => svg([h('path', { key: 1, d: 'M5 12h14' })]),
|
|
2013
2560
|
fit: () => svg([h('path', { key: 1, d: 'M4 9V5.5A1.5 1.5 0 0 1 5.5 4H9M15 4h3.5A1.5 1.5 0 0 1 20 5.5V9M20 15v3.5a1.5 1.5 0 0 1-1.5 1.5H15M9 20H5.5A1.5 1.5 0 0 1 4 18.5V15' })]),
|
|
2014
2561
|
refresh: () => svg([h('path', { key: 1, d: 'M20 11a8 8 0 1 0-2.3 5.7' }), h('path', { key: 2, d: 'M20 5v6h-6' })]),
|
|
@@ -2030,6 +2577,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
2030
2577
|
redo: () => svg([h('path', { key: 1, d: 'M15 14l4.5-4.5L15 5' }), h('path', { key: 2, d: 'M19.5 9.5H9a5 5 0 0 0 0 10h3' })]),
|
|
2031
2578
|
trash: () => svg([h('path', { key: 1, d: 'M4.5 7h15M9.5 7V4.5h5V7' }), h('path', { key: 2, d: 'M6.5 7l.9 12a1.5 1.5 0 0 0 1.5 1.4h6.2a1.5 1.5 0 0 0 1.5-1.4l.9-12' }), h('path', { key: 3, d: 'M10.5 11v5.5M13.5 11v5.5' })]),
|
|
2032
2579
|
folder: () => svg([h('path', { key: 1, d: 'M3.5 7.5a2 2 0 0 1 2-2h3.8l2 2.2h7.2a2 2 0 0 1 2 2v7.8a2 2 0 0 1-2 2h-13a2 2 0 0 1-2-2z' })]),
|
|
2580
|
+
info: () => svg([h('circle', { key: 1, cx: 12, cy: 12, r: 8.5 }), h('path', { key: 2, d: 'M12 11v5.5M12 7.8v.2', strokeWidth: 2 })]),
|
|
2033
2581
|
box: () => svg([h('path', { key: 1, d: 'M3.5 7.5L12 3.5l8.5 4v9L12 20.5l-8.5-4z' }), h('path', { key: 2, d: 'M3.5 7.5L12 11.5l8.5-4M12 11.5v9' })]),
|
|
2034
2582
|
share: () => svg([h('path', { key: 1, d: 'M12 15V4' }), h('path', { key: 2, d: 'M7.5 8.5L12 4l4.5 4.5' }), h('path', { key: 3, d: 'M5 13v5.5A1.5 1.5 0 0 0 6.5 20h11a1.5 1.5 0 0 0 1.5-1.5V13' })]),
|
|
2035
2583
|
page: (p) => svg([h('rect', { key: 1, x: 3, y: 4, width: 18, height: 16, rx: 2.5 }), h('path', { key: 2, d: 'M3 9h18' }), h('rect', { key: 3, x: 7, y: 12.5, width: 5, height: 4, rx: 1, strokeDasharray: '2 1.6' })], p),
|
|
@@ -2371,7 +2919,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
2371
2919
|
{ i: 'style', label: (v, t) => t('chipTwFix'), alts: both('twFixText'), card: (v, t) => [['b', t('chipTwFix')], ['p', v.errs]] },
|
|
2372
2920
|
{ i: 'look', label: (v, t) => t('chipSysEx'), alts: both('sysExtractText'), card: (v, t) => [['b', t('chipSysEx')], ['code', v.src]] },
|
|
2373
2921
|
{ i: 'look', label: (v, t) => t('chipSysAp'), alts: both('sysApplyText'), card: (v, t) => [['b', t('chipSysAp')], ['code', v.name], ['span', v.dir]] },
|
|
2374
|
-
{ i: 'look', label: (v, t) => t('chipLook', { name: v.name }), alts: L.map((x) => tplSrc(x.styleText, { name: '[^\\n]*?', ask: lit(['styleStart', 'styleRestyle']) })),
|
|
2922
|
+
{ i: 'look', label: (v, t) => t('chipLook', { name: v.name }), alts: L.map((x) => tplSrc(x.styleText, { name: '[^\\n]*?', ask: lit(['styleStart', 'styleRestyle', 'styleAuto']) })),
|
|
2375
2923
|
card: (v, t) => [['b', t('chipLook', { name: v.name })], ['p', v.desc], ['sw', hexes(v.colors)], ['span', v.fonts && v.fonts.split(/[;;]/)[0]]] },
|
|
2376
2924
|
{ i: 'look', label: (v) => v.name, alts: [{ src: `〔设计变量〕([^\\n]*?): ([^\\n]*)((${WHERE_RE}))`, keys: ['name', 'value', 'where'] }],
|
|
2377
2925
|
card: (v) => [['b', v.name], ['code', v.value], ['sw', hexes(v.value)], ['span', fileOf(v)]] },
|
|
@@ -2612,6 +3160,106 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
2612
3160
|
}
|
|
2613
3161
|
|
|
2614
3162
|
/** 点了一个变量 → 输入框里的一段字 */
|
|
3163
|
+
/** 设计变量的人话名字:--bg → 背景,--fs-micro → 注释,--radius-md → 中圆角;认不出的把名字去掉 -- 和连字符 */
|
|
3164
|
+
function tokenLabel(t, tok) {
|
|
3165
|
+
const raw = String(tok.name).replace(/^--/, '')
|
|
3166
|
+
const words = raw.toLowerCase().split(/[-_]+/).filter(Boolean)
|
|
3167
|
+
const has = (re) => words.some((w) => re.test(w))
|
|
3168
|
+
const kind = tokenKind(tok)
|
|
3169
|
+
const rest = (re) => words.filter((w) => !re.test(w) && !/^(color|clr|c|token|tk|font|fs|text|size|radius|r|rounded|space|spacing|sp|gap|shadow|elevation|family|ff)$/.test(w))
|
|
3170
|
+
const mod = (left) => {
|
|
3171
|
+
const out = []
|
|
3172
|
+
for (const w of left) {
|
|
3173
|
+
if (/^(soft|light|lighter|pale|tint|weak|subtle|faint|bg|50|100|200)$/.test(w)) out.push(t('tkn_light'))
|
|
3174
|
+
else if (/^(dark|darker|deep|strong|bold|700|800|900)$/.test(w)) out.push(t('tkn_dark'))
|
|
3175
|
+
else if (/^(hover|active|pressed|focus)$/.test(w)) out.push(t('tkn_hover'))
|
|
3176
|
+
else out.push(w)
|
|
3177
|
+
}
|
|
3178
|
+
return [...new Set(out)].join(' ')
|
|
3179
|
+
}
|
|
3180
|
+
const name = (key, re) => { const m = mod(rest(re)); return m ? `${t('tkn_' + key)} · ${m}` : t('tkn_' + key) }
|
|
3181
|
+
if (kind === 'color') {
|
|
3182
|
+
if (words.includes('on') && words.length > 1) return t('tkn_onPrimary')
|
|
3183
|
+
const roles = [
|
|
3184
|
+
[/^(muted|subtle|dim|faint|placeholder|secondary-text|text2|hint)$/, 'muted'],
|
|
3185
|
+
[/^(line|border|divider|stroke|outline|hairline|sep|separator|rule)$/, 'line'],
|
|
3186
|
+
[/^(surface|card|panel|elev|elevated|sheet|raised|layer|paper)$/, 'surface'],
|
|
3187
|
+
[/^(text|fg|ink|foreground|body|copy)$/, 'text'],
|
|
3188
|
+
[/^(bg|background|page|canvas|base|backdrop)$/, 'bg'],
|
|
3189
|
+
[/^(primary|brand|main|theme)$/, 'primary'],
|
|
3190
|
+
[/^(accent|highlight|secondary)$/, 'accent'],
|
|
3191
|
+
[/^(link|links)$/, 'link'],
|
|
3192
|
+
[/^(danger|error|destructive|alert)$/, 'danger'],
|
|
3193
|
+
[/^(success|positive|ok)$/, 'success'],
|
|
3194
|
+
[/^(warning|warn|caution)$/, 'warning'],
|
|
3195
|
+
]
|
|
3196
|
+
for (const [re, key] of roles) if (has(re)) return name(key, re)
|
|
3197
|
+
} else if (kind === 'size') {
|
|
3198
|
+
const sizes = [[/^(xs|xxs|micro|tiny|caption|note|footnote|mini)$/, 'xs'], [/^(sm|small)$/, 'sm'], [/^(md|medium)$/, 'md'], [/^(base|body|normal|p|regular)$/, 'body'], [/^(lg|large|lead|sub|subtitle)$/, 'lg'], [/^(xl|title|h2|h3|h4|heading|head)$/, 'title'], [/^(2xl|3xl|4xl|xxl|display|hero|h1|huge|jumbo)$/, 'display']]
|
|
3199
|
+
for (const [re, key] of sizes) if (has(re)) return name(key, re)
|
|
3200
|
+
} else if (kind === 'radius') {
|
|
3201
|
+
const rs = [[/^(xs|sm|small)$/, 'rSm'], [/^(md|base|medium|default)$/, 'rMd'], [/^(lg|xl|large|2xl)$/, 'rLg'], [/^(full|pill|round|circle|999)$/, 'rFull']]
|
|
3202
|
+
for (const [re, key] of rs) if (has(re)) return name(key, re)
|
|
3203
|
+
const m = mod(rest(/^$/))
|
|
3204
|
+
return m ? `${t('tkn_r')} ${m}` : t('tkn_r')
|
|
3205
|
+
} else if (kind === 'space') {
|
|
3206
|
+
if (has(/^(gutter|page|edge|inset)$/)) return t('tkn_gutter')
|
|
3207
|
+
const m = rest(/^$/).join(' ')
|
|
3208
|
+
return m ? `${t('tkn_space')} ${m}` : t('tkn_space')
|
|
3209
|
+
} else if (kind === 'font') {
|
|
3210
|
+
if (has(/^(head|heading|display|title|serif|brand)$/)) return t('tkn_fHead')
|
|
3211
|
+
if (has(/^(mono|code)$/)) return t('tkn_fMono')
|
|
3212
|
+
if (has(/^(body|text|sans|base|ui)$/)) return t('tkn_fBody')
|
|
3213
|
+
} else if (kind === 'shadow') {
|
|
3214
|
+
const m = rest(/^$/).join(' ')
|
|
3215
|
+
return m ? `${t('tkn_shadow')} ${m}` : t('tkn_shadow')
|
|
3216
|
+
}
|
|
3217
|
+
return raw.replace(/[-_]+/g, ' ')
|
|
3218
|
+
}
|
|
3219
|
+
|
|
3220
|
+
/** 一套变量的人话名字(名字 → 名字):撞名的后面带上原名的尾巴,比如「卡片底(paper)」 */
|
|
3221
|
+
function tokenLabels(t, tokens) {
|
|
3222
|
+
const out = new Map()
|
|
3223
|
+
const seen = new Map()
|
|
3224
|
+
for (const tok of tokens || []) {
|
|
3225
|
+
const l = tokenLabel(t, tok)
|
|
3226
|
+
out.set(tok.name, l)
|
|
3227
|
+
seen.set(l, (seen.get(l) || 0) + 1)
|
|
3228
|
+
}
|
|
3229
|
+
for (const tok of tokens || []) {
|
|
3230
|
+
const l = out.get(tok.name)
|
|
3231
|
+
if (seen.get(l) > 1) out.set(tok.name, `${l}(${String(tok.name).replace(/^--/, '').replace(/^(color|clr|c|font|fs|text|size|radius|space|spacing|shadow)-/i, '')})`)
|
|
3232
|
+
}
|
|
3233
|
+
return out
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
/** 调样式面板头上:这是个什么(按钮「去预约」),不是一串选择器 */
|
|
3237
|
+
function elemLabel(t, name, text) {
|
|
3238
|
+
const n = String(name || '').toLowerCase()
|
|
3239
|
+
const tag = (/^[a-z][a-z0-9]*/.exec(n) || [''])[0]
|
|
3240
|
+
const cls = n.slice(tag.length)
|
|
3241
|
+
let k = 'box'
|
|
3242
|
+
if (/btn|button/.test(cls) || tag === 'button') k = 'button'
|
|
3243
|
+
else if (/card/.test(cls)) k = 'card'
|
|
3244
|
+
else if (/tag|chip|badge|pill/.test(cls)) k = 'tag'
|
|
3245
|
+
else if (/avatar/.test(cls)) k = 'avatar'
|
|
3246
|
+
else if (/icon|ico\b/.test(cls) || tag === 'svg' || tag === 'i') k = 'icon'
|
|
3247
|
+
else if (tag === 'a') k = 'a'
|
|
3248
|
+
else if (tag === 'img' || tag === 'picture' || tag === 'video') k = 'img'
|
|
3249
|
+
else if (/^h[1-6]$/.test(tag)) k = 'h'
|
|
3250
|
+
else if (tag === 'p') k = 'p'
|
|
3251
|
+
else if (tag === 'input' || tag === 'textarea' || tag === 'select') k = 'input'
|
|
3252
|
+
else if (tag === 'li') k = 'li'
|
|
3253
|
+
else if (tag === 'ul' || tag === 'ol') k = 'ul'
|
|
3254
|
+
else if (tag === 'nav' || /nav|tabbar|tab-bar/.test(cls)) k = 'nav'
|
|
3255
|
+
else if (tag === 'header') k = 'header'
|
|
3256
|
+
else if (tag === 'footer') k = 'footer'
|
|
3257
|
+
else if (/^(span|b|strong|em|small|label|time)$/.test(tag)) k = 'text'
|
|
3258
|
+
const s = String(text || '').replace(/\s+/g, ' ').trim()
|
|
3259
|
+
const short = [...s].length > 14 ? [...s].slice(0, 13).join('') + '…' : s
|
|
3260
|
+
return { role: t('el_' + k), text: short }
|
|
3261
|
+
}
|
|
3262
|
+
|
|
2615
3263
|
function formatToken(tok, st) {
|
|
2616
3264
|
return `〔设计变量〕${tok.name}: ${String(tok.value).replace(/\s*\n\s*/g, ' ')}(${whereOf(tok.href, st)})\n`
|
|
2617
3265
|
}
|
|
@@ -2783,7 +3431,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
2783
3431
|
}
|
|
2784
3432
|
|
|
2785
3433
|
/** 选了一个风格 → 输入框里的一段字(restyle:给已经打开的设计换风格) */
|
|
2786
|
-
function formatStyle(t, pre, restyle) {
|
|
3434
|
+
function formatStyle(t, pre, restyle, auto) {
|
|
2787
3435
|
const en = t('lang') === 'en'
|
|
2788
3436
|
const c = pre.c
|
|
2789
3437
|
const fonts = t('styleFonts', { head: `${pre.head} · ${pre.w}`, body: SANS })
|
|
@@ -2794,10 +3442,47 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
2794
3442
|
fonts,
|
|
2795
3443
|
shape: t('styleShape', { r: pre.radius, s: pre.shadow }),
|
|
2796
3444
|
notes: en ? pre.nen : pre.nzh,
|
|
2797
|
-
ask: t(restyle ? 'styleRestyle' : 'styleStart'),
|
|
3445
|
+
ask: t(restyle ? 'styleRestyle' : auto ? 'styleAuto' : 'styleStart'),
|
|
2798
3446
|
})
|
|
2799
3447
|
}
|
|
2800
3448
|
|
|
3449
|
+
/** 首页「风格:自动」时按题材替模型挑一张风格卡(模型自己定时,消费类 App 总落回米色暖调)。
|
|
3450
|
+
* 用户话里已经说了风格 → 不挑(null),照他的话来;没认出题材 → 按类型给一张不是米色的 */
|
|
3451
|
+
const AUTO_STYLE = [
|
|
3452
|
+
['sport', /跑步|健身|运动|赛事|马拉松|骑行|瑜伽|球馆|\b(runners?|running|fitness|gym|workouts?|sports?|marathons?|cycling|athletes?)\b/i],
|
|
3453
|
+
['cyber', /电音|夜店|电竞|游戏|音乐节|livehouse|酒吧|\b(game|gaming|esports?|nightclub|club|rave|festival)\b/i],
|
|
3454
|
+
['sale', /促销|大促|双十一|双11|618|秒杀|优惠|团购|外卖|\b(sale|deals?|coupons?|promo|black friday)\b/i],
|
|
3455
|
+
['tech', /后台|管理系统|仪表盘|看板|数据|金融|理财|银行|证券|\b(saas|crm|erp|dashboard|admin|analytics|finance|fintech|bank|banking|crypto|b2b|devops|api)\b/i],
|
|
3456
|
+
['dark', /奢|高端|珠宝|腕表|名表|红酒|威士忌|私人|黑卡|\b(luxury|premium|jewel|jewelry|jewellery|wristwatch(es)?|timepieces?|wine|whisky|concierge)\b/i],
|
|
3457
|
+
['cute', /儿童|幼儿|宠物|猫|狗|萌|甜品|蛋糕|玩具|\b(kids?|children|pets?|cats?|dogs?|puppy|toys?|dessert|candy)\b/i],
|
|
3458
|
+
['fresh', /健康|医疗|医院|诊所|户外|露营|母婴|植物|花店|有机|环保|民宿|旅行|旅游|度假|\b(health|medical|clinic|hospital|outdoor|camping|baby|plants?|florist|organic|eco|travel|trip|stays?|vacation|hotel|bnb)\b/i],
|
|
3459
|
+
['playful', /奶茶|果茶|饮品|饮料|社交|交友|学生|校园|潮牌|\b(bubble tea|boba|drinks?|smoothie|social|dating|students?|campus|streetwear)\b/i],
|
|
3460
|
+
['guochao', /春节|新年|中秋|端午|国潮|老字号|文创|庙会/],
|
|
3461
|
+
['retro', /咖啡|黑胶|唱片|市集|古着|理发|面包|\b(vinyl|records?|vintage|barber|bakery|coffee)\b/i],
|
|
3462
|
+
['glass', /音乐|播放器|天气|智能家居|\b(music|player|weather|smart home|podcast)\b/i],
|
|
3463
|
+
['editorial', /新闻|杂志|博客|阅读|读书|书店|媒体|播客|\b(news|magazine|blog|reading|books?|media|newsletter)\b/i],
|
|
3464
|
+
['swiss', /设计工作室|建筑|展览|美术馆|画廊|作品集|\b(agency|studio|architecture|exhibition|gallery|museum|portfolio)\b/i],
|
|
3465
|
+
]
|
|
3466
|
+
const AUTO_BY_KIND = { slides: 'swiss', web: 'minimal', desktop: 'tech', email: 'minimal', poster: 'playful', tablet: 'minimal' }
|
|
3467
|
+
const AUTO_SPREAD = ['minimal', 'editorial', 'playful', 'fresh', 'tech', 'glass', 'dark', 'sport']
|
|
3468
|
+
// 「北欧风」「清新风:首页…」「中国风的界面」算说了风格;先去掉「通风」「台风」「新风系统」「风控」「风景」这类不是风格的词
|
|
3469
|
+
const NOT_STYLE_FENG = /通风|台风|新风(系统|机|量)|风险|风控|风景|风扇|风味|风暴|风光|风速|风向|风筝|风铃|风车|吹风|防风|挡风|避风|屏风|旋风|狂风|微风|寒风|疾风|顺风|逆风|作风|中风|风湿|风水/g
|
|
3470
|
+
const SAID_STYLE = /风格|配色|色调|主色|色系|[^\s。,,、::;;((]风|\b(style|styled|aesthetic|vibe|palette|colou?r scheme|look and feel)\b/i
|
|
3471
|
+
// 「风格你定」「配色:随意」「any style」:等于没说(「配色定为蓝白」是说了)
|
|
3472
|
+
const STYLE_FREE = /(风格|配色|色调|主色)[::]?\s?(你定|您定|你来定|您来定|你决定|你看着办|随意|随便|不限|都行|无所谓|自由发挥)|\b(any|whatever)\s(style|colou?rs?|palette)\b|\b(style|colou?rs?|palette)\s(is\s)?up\sto\syou\b/gi
|
|
3473
|
+
function autoStyle(text, kind) {
|
|
3474
|
+
const s = String(text || '')
|
|
3475
|
+
if (!s.trim()) return null
|
|
3476
|
+
const said = s.replace(STYLE_FREE, ' ').replace(NOT_STYLE_FENG, ' ')
|
|
3477
|
+
if (SAID_STYLE.test(said) || STYLES.some((x) => said.includes(x.zh) || new RegExp(`\\b${x.en}\\s+(style|look|theme|aesthetic|vibe)\\b`, 'i').test(said))) return null
|
|
3478
|
+
const by = (id) => STYLES.find((x) => x.id === id) || null
|
|
3479
|
+
for (const [id, re] of AUTO_STYLE) if (re.test(s)) return by(id)
|
|
3480
|
+
if (AUTO_BY_KIND[kind]) return by(AUTO_BY_KIND[kind])
|
|
3481
|
+
let n = 0
|
|
3482
|
+
for (const ch of s) n = (n * 31 + ch.codePointAt(0)) >>> 0
|
|
3483
|
+
return by(AUTO_SPREAD[n % AUTO_SPREAD.length])
|
|
3484
|
+
}
|
|
3485
|
+
|
|
2801
3486
|
// ── 设计库:〔模板〕〔准备分享〕标签、我的风格 ──────────────────────────────
|
|
2802
3487
|
|
|
2803
3488
|
const HEAD_STACK = { SANS, SERIF, ROUND, MONO, GROTESK }
|
|
@@ -3569,6 +4254,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
3569
4254
|
const KIND_LIST = ['web', 'app', 'miniprogram', 'tablet', 'desktop', 'poster', 'slides', 'email']
|
|
3570
4255
|
const KIND_KEY = { web: 'kindWeb', app: 'kindApp', miniprogram: 'kindMini', tablet: 'kindTablet', desktop: 'kindDesktop', poster: 'kindPoster', slides: 'kindSlides', email: 'kindEmail' }
|
|
3571
4256
|
const GHOST_MS = 90000 // 这么久没出新屏就撤掉「生成中…」占位
|
|
4257
|
+
const HOLD_MAX = 15000 // 草稿放到一半真文件就落地了:最多再放这么久(一般几秒放完自己说一声)
|
|
3572
4258
|
const EDIT_GRACE_MS = 3000 // 退出改字后这么久里,页面交上来的最后一处改动还收
|
|
3573
4259
|
const KIND_SIZE = { web: [1280, 800], app: [390, 844], miniprogram: [375, 812], tablet: [820, 1180], desktop: [1280, 800], poster: [1080, 1350], slides: [1280, 720], email: [640, 960] }
|
|
3574
4260
|
// 外框:pad 边框厚度、top 顶上一条(浏览器栏)、r 外圆角、ir 屏幕圆角、island 灵动岛离顶的距离、capsule 小程序右上角的胶囊、win 桌面软件的窗口按钮
|
|
@@ -3662,7 +4348,8 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
3662
4348
|
|
|
3663
4349
|
/** 整块居中放进舞台(最大 100%) */
|
|
3664
4350
|
function fitCam(L, sw, sh) {
|
|
3665
|
-
|
|
4351
|
+
// 排的时候按整块舞台算的缩放;右边被面板挡着时 sw 变窄,再缩到放得下
|
|
4352
|
+
const z = clampZ(Math.min(1, L.z, (sw - PAD * 2) / Math.max(1, L.bw), (sh - PAD_TOP - PAD) / Math.max(1, L.bh)))
|
|
3666
4353
|
return { z, x: (sw - L.bw * z) / 2, y: PAD_TOP + Math.max(0, (sh - PAD_TOP - PAD - L.bh * z) / 2) }
|
|
3667
4354
|
}
|
|
3668
4355
|
|
|
@@ -4003,6 +4690,226 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
4003
4690
|
|
|
4004
4691
|
// ── 小部件 ───────────────────────────────────────────────────────────────
|
|
4005
4692
|
|
|
4693
|
+
// 模型在做什么:按它调的工具说人话(写文件另算,见 busyPhase)
|
|
4694
|
+
const TOOL_PHASE = [
|
|
4695
|
+
[/^check_design$/, 'stCheck'], [/^look_design$/, 'stLook'], [/^preview_page$/, 'stPreview'], [/^extract_design_system$/, 'stBrand'],
|
|
4696
|
+
[/^handoff_design$/, 'stCode'], [/^compare_design$/, 'stCompare'], [/^ask_user_question$/, 'asking'],
|
|
4697
|
+
[/^(bash|shell|sh|zsh|powershell|pwsh|exec|run_command|terminal)$/i, 'stShell'],
|
|
4698
|
+
[/^skill$/i, 'stSkill'],
|
|
4699
|
+
[/web|fetch|browse|http/i, 'stWeb'],
|
|
4700
|
+
[/^(write|edit|multi_?edit|create_file|str_replace\w*|apply_patch|patch)$/i, 'stWriteAny'],
|
|
4701
|
+
[/read|grep|glob|list|^ls$|find|search|view/i, 'stRead'],
|
|
4702
|
+
]
|
|
4703
|
+
const clock = (ms) => {
|
|
4704
|
+
const s = Math.max(0, Math.floor(ms / 1000))
|
|
4705
|
+
const p2 = (n) => String(n).padStart(2, '0')
|
|
4706
|
+
return s >= 3600 ? `${Math.floor(s / 3600)}:${p2(Math.floor(s / 60) % 60)}:${p2(s % 60)}` : `${Math.floor(s / 60)}:${p2(s % 60)}`
|
|
4707
|
+
}
|
|
4708
|
+
|
|
4709
|
+
/** 顶上的状态条:在做什么 · 第几屏 · 用了多久(自己每秒走一下,不带着整个右侧栏重画) */
|
|
4710
|
+
function BusyBar({ t, phase, since, side }) {
|
|
4711
|
+
const [, tick] = useState(0)
|
|
4712
|
+
useEffect(() => {
|
|
4713
|
+
const id = setInterval(() => tick((n) => n + 1), 1000)
|
|
4714
|
+
return () => clearInterval(id)
|
|
4715
|
+
}, [])
|
|
4716
|
+
const tm = clock(Date.now() - since)
|
|
4717
|
+
return h('div', { className: 'ddp-busy' + (phase.asking ? ' ask' : '') + (side ? ' side' : '') },
|
|
4718
|
+
phase.asking ? h('i', { className: 'q', 'aria-hidden': true }) : h('span', { className: 'dots', 'aria-hidden': true }, h('i'), h('i'), h('i')),
|
|
4719
|
+
h('span', { className: 'tx', role: 'status' }, phase.text),
|
|
4720
|
+
phase.sub ? h('span', { className: 'sub' }, phase.sub) : null,
|
|
4721
|
+
h('span', { className: 'tm', title: t('stTime', { time: tm }) }, tm),
|
|
4722
|
+
phase.asking ? null : h('i', { className: 'bar', 'aria-hidden': true }))
|
|
4723
|
+
}
|
|
4724
|
+
|
|
4725
|
+
// 生成中的占位:三只带名字的鼠标轮流框出版面(按类型给个大概的样子),框里闪骨架,一遍遍画。位置都是百分比,放哪都行
|
|
4726
|
+
const GD_PLAN = {
|
|
4727
|
+
app: [[0, 0, 100, 11], [5, 14, 90, 24], [5, 42, 44, 4], [5, 49, 90, 11], [5, 63, 90, 11], [5, 77, 90, 9], [0, 90, 100, 10]],
|
|
4728
|
+
web: [[0, 0, 100, 9], [6, 15, 52, 8], [6, 26, 38, 4], [6, 35, 88, 29], [6, 70, 27, 23], [36.5, 70, 27, 23], [67, 70, 27, 23]],
|
|
4729
|
+
poster: [[8, 6, 84, 50], [8, 61, 70, 8], [8, 72, 50, 4], [8, 84, 36, 7]],
|
|
4730
|
+
slides: [[6, 10, 60, 13], [6, 32, 40, 52], [52, 32, 42, 52]],
|
|
4731
|
+
email: [[10, 4, 80, 7], [10, 14, 80, 30], [10, 48, 60, 5], [10, 56, 80, 14], [10, 75, 30, 6]],
|
|
4732
|
+
}
|
|
4733
|
+
const GD_C = ['#4d6bfe', '#e8590c', '#0ca678']
|
|
4734
|
+
/** 占位里的鼠标什么时候动:换了在做的事、或者又写了 15 行就加一;不在生成就不动 */
|
|
4735
|
+
function pulseStep(pr, busy, key, ln) {
|
|
4736
|
+
if (!busy) {
|
|
4737
|
+
pr.key = ''
|
|
4738
|
+
pr.lines = 0
|
|
4739
|
+
return pr.n
|
|
4740
|
+
}
|
|
4741
|
+
if (ln < pr.lines) pr.lines = ln
|
|
4742
|
+
if (key !== pr.key) {
|
|
4743
|
+
pr.key = key
|
|
4744
|
+
pr.lines = ln
|
|
4745
|
+
pr.n++
|
|
4746
|
+
} else if (ln - pr.lines >= 15) {
|
|
4747
|
+
pr.lines = ln
|
|
4748
|
+
pr.n++
|
|
4749
|
+
}
|
|
4750
|
+
return pr.n
|
|
4751
|
+
}
|
|
4752
|
+
const planOf = (kind) => GD_PLAN[kind === 'miniprogram' ? 'app' : kind === 'tablet' || kind === 'desktop' ? 'web' : kind] || GD_PLAN.web
|
|
4753
|
+
const reduced = () => { try { return matchMedia('(prefers-reduced-motion: reduce)').matches } catch { return false } }
|
|
4754
|
+
|
|
4755
|
+
/**
|
|
4756
|
+
* 生成中的占位。三只带名字的鼠标先把版面框一遍,框完就停住——不循环;之后模型每真有一点动静
|
|
4757
|
+
* (换了在做的事、又写了一截),才有一只去重新框一块。模型停着,它们也停着。
|
|
4758
|
+
*/
|
|
4759
|
+
function GhostDraw({ t, kind, pulse = 0 }) {
|
|
4760
|
+
const ref = useRef(null)
|
|
4761
|
+
const plan = planOf(kind)
|
|
4762
|
+
const S = useRef(null)
|
|
4763
|
+
const EASE_K = 'cubic-bezier(.3,.7,.2,1)'
|
|
4764
|
+
useEffect(() => {
|
|
4765
|
+
const el = ref.current
|
|
4766
|
+
if (!el) return
|
|
4767
|
+
const boxes = [...el.querySelectorAll('.b')]
|
|
4768
|
+
const curs = [...el.querySelectorAll('.c')]
|
|
4769
|
+
const s = { boxes, curs, pos: curs.map((_, i) => ({ x: 30 + i * 20, y: 112 })), cur: [], box: [], last: -1, seen: pulse, till: 0, still: typeof el.animate !== 'function' || reduced() }
|
|
4770
|
+
S.current = s
|
|
4771
|
+
if (s.still) return
|
|
4772
|
+
const STEP = 520
|
|
4773
|
+
const MOVE = 320
|
|
4774
|
+
const DRAG = 420
|
|
4775
|
+
const paths = curs.map((_, i) => [{ ms: 0, x: s.pos[i].x, y: s.pos[i].y, o: 0 }])
|
|
4776
|
+
plan.forEach(([x, y, w, hh], i) => {
|
|
4777
|
+
const s0 = 250 + i * STEP
|
|
4778
|
+
const path = paths[i % curs.length]
|
|
4779
|
+
const last = path[path.length - 1]
|
|
4780
|
+
path.push({ ms: s0, x: last.x, y: last.y, o: 1 }, { ms: s0 + MOVE, x, y, o: 1 }, { ms: s0 + MOVE + DRAG, x: Math.min(97, x + w), y: Math.min(97, y + hh), o: 1 })
|
|
4781
|
+
const box = boxes[i]
|
|
4782
|
+
if (box) {
|
|
4783
|
+
s.box[i] = box.animate([
|
|
4784
|
+
{ opacity: 0, clipPath: 'inset(0 100% 100% 0)' },
|
|
4785
|
+
{ offset: 0.001, opacity: 1, clipPath: 'inset(0 100% 100% 0)', easing: EASE_K },
|
|
4786
|
+
{ opacity: 1, clipPath: 'inset(0 0 0 0)' },
|
|
4787
|
+
], { duration: DRAG, delay: s0 + MOVE, fill: 'both' })
|
|
4788
|
+
}
|
|
4789
|
+
})
|
|
4790
|
+
let end = 0
|
|
4791
|
+
curs.forEach((c, i) => {
|
|
4792
|
+
const path = paths[i]
|
|
4793
|
+
const D = path[path.length - 1].ms || 1
|
|
4794
|
+
end = Math.max(end, D)
|
|
4795
|
+
const kf = []
|
|
4796
|
+
for (const p of path) {
|
|
4797
|
+
const o = p.ms / D
|
|
4798
|
+
if (kf.length && o <= kf[kf.length - 1].offset) continue
|
|
4799
|
+
kf.push({ offset: o, left: p.x + '%', top: p.y + '%', opacity: p.o, easing: EASE_K })
|
|
4800
|
+
}
|
|
4801
|
+
if (kf[kf.length - 1].offset < 1) kf.push({ ...kf[kf.length - 1], offset: 1 })
|
|
4802
|
+
const last = path[path.length - 1]
|
|
4803
|
+
s.pos[i] = { x: last.x, y: last.y }
|
|
4804
|
+
c.classList.toggle('flip', last.x > 78)
|
|
4805
|
+
s.cur[i] = c.animate(kf, { duration: D, fill: 'forwards' })
|
|
4806
|
+
})
|
|
4807
|
+
s.till = performance.now() + end
|
|
4808
|
+
return () => { s.cur.forEach((a) => a && a.cancel()); s.box.forEach((a) => a && a.cancel()) }
|
|
4809
|
+
}, [plan])
|
|
4810
|
+
// 模型真有动静:轮到的那只去重新框一块(框过的那块先收起再跟着鼠标拉开)
|
|
4811
|
+
useEffect(() => {
|
|
4812
|
+
const s = S.current
|
|
4813
|
+
if (!s || s.still || pulse === s.seen) return
|
|
4814
|
+
s.seen = pulse
|
|
4815
|
+
const now = performance.now()
|
|
4816
|
+
if (now < s.till) return // 头一遍还没框完 / 上一下还没做完:这一下不插队
|
|
4817
|
+
const k = pulse % s.curs.length
|
|
4818
|
+
let j = Math.floor(Math.random() * plan.length)
|
|
4819
|
+
if (j === s.last) j = (j + 1) % plan.length
|
|
4820
|
+
s.last = j
|
|
4821
|
+
const [x, y, w, hh] = plan[j]
|
|
4822
|
+
const MOVE = 420
|
|
4823
|
+
const DRAG = 480
|
|
4824
|
+
const from = s.pos[k]
|
|
4825
|
+
const to = { x: Math.min(97, x + w), y: Math.min(97, y + hh) }
|
|
4826
|
+
const c = s.curs[k]
|
|
4827
|
+
const prev = s.cur[k]
|
|
4828
|
+
s.cur[k] = c.animate([
|
|
4829
|
+
{ left: from.x + '%', top: from.y + '%', opacity: 1, easing: EASE_K },
|
|
4830
|
+
{ offset: MOVE / (MOVE + DRAG), left: x + '%', top: y + '%', opacity: 1, easing: EASE_K },
|
|
4831
|
+
{ left: to.x + '%', top: to.y + '%', opacity: 1 },
|
|
4832
|
+
], { duration: MOVE + DRAG, fill: 'forwards' })
|
|
4833
|
+
if (prev) prev.cancel()
|
|
4834
|
+
s.pos[k] = to
|
|
4835
|
+
c.classList.toggle('flip', to.x > 78)
|
|
4836
|
+
const box = s.boxes[j]
|
|
4837
|
+
if (box) {
|
|
4838
|
+
const pb = s.box[j]
|
|
4839
|
+
s.box[j] = box.animate([
|
|
4840
|
+
{ opacity: 1, clipPath: 'inset(0 100% 100% 0)', easing: EASE_K },
|
|
4841
|
+
{ opacity: 1, clipPath: 'inset(0 0 0 0)' },
|
|
4842
|
+
], { duration: DRAG, delay: MOVE, fill: 'both' })
|
|
4843
|
+
if (pb) pb.cancel()
|
|
4844
|
+
}
|
|
4845
|
+
s.till = now + MOVE + DRAG
|
|
4846
|
+
}, [pulse])
|
|
4847
|
+
const names = [t('curLayout'), t('curColor'), t('curCopy')]
|
|
4848
|
+
return h('div', { className: 'ddp-gd', ref, 'aria-hidden': true },
|
|
4849
|
+
...plan.map(([x, y, w, hh], i) => h('i', { key: 'b' + i, className: 'b', style: { left: x + '%', top: y + '%', width: w + '%', height: hh + '%', '--c': GD_C[i % 3] } })),
|
|
4850
|
+
...GD_C.map((c, i) => h('span', { key: 'c' + i, className: 'c', style: { '--c': c, left: (30 + i * 20) + '%', top: '112%' } },
|
|
4851
|
+
h('svg', { viewBox: '0 0 17 21' }, h('path', { d: 'M1.2 1.2v15.6l4.3-4 3 6.6 2.9-1.3-3-6.5h6z' })),
|
|
4852
|
+
h('em', null, names[i]))))
|
|
4853
|
+
}
|
|
4854
|
+
|
|
4855
|
+
/**
|
|
4856
|
+
* 边写边看:预览服务器上正在写的那一页(/某屏.html?__ddp_draft=…),浏览器边收边画。
|
|
4857
|
+
* 页面里的小脚本报到后才显出来(之前看到的是底下的占位),画布缩放告诉它,鼠标和字看起来一样大。
|
|
4858
|
+
*/
|
|
4859
|
+
function DraftFrame({ url, z = 1, lang, onEnd }) {
|
|
4860
|
+
const ref = useRef(null)
|
|
4861
|
+
const [live, setLive] = useState(false)
|
|
4862
|
+
const zr = useRef(z)
|
|
4863
|
+
zr.current = z
|
|
4864
|
+
const endR = useRef(onEnd)
|
|
4865
|
+
endR.current = onEnd
|
|
4866
|
+
const src = url + (url.includes('?') ? '&' : '?') + 'lang=' + (lang === 'en' ? 'en' : 'zh')
|
|
4867
|
+
let origin = '*'
|
|
4868
|
+
try { origin = new URL(url).origin } catch { /* 地址不对就不发 */ }
|
|
4869
|
+
const tell = (zz) => {
|
|
4870
|
+
const f = ref.current
|
|
4871
|
+
try { if (f && f.contentWindow) f.contentWindow.postMessage({ dshDraft: 'zoom', z: zz }, origin) } catch { /* 页面已经换了 */ }
|
|
4872
|
+
}
|
|
4873
|
+
useEffect(() => {
|
|
4874
|
+
setLive(false)
|
|
4875
|
+
const end = () => { if (endR.current) endR.current(url) }
|
|
4876
|
+
// 一直没报到(草稿已经收走、给的是真文件):别干等,算放完了
|
|
4877
|
+
let quiet = setTimeout(end, 6000)
|
|
4878
|
+
const on = (e) => {
|
|
4879
|
+
const f = ref.current
|
|
4880
|
+
if (!f || e.source !== f.contentWindow || !e.data) return
|
|
4881
|
+
if (e.data.dshDraft === 'end') return end() // 写完、鼠标也退场了:可以换真文件了
|
|
4882
|
+
if (e.data.dshDraft !== 'hello') return
|
|
4883
|
+
clearTimeout(quiet)
|
|
4884
|
+
quiet = null
|
|
4885
|
+
setLive(true)
|
|
4886
|
+
tell(zr.current)
|
|
4887
|
+
}
|
|
4888
|
+
addEventListener('message', on)
|
|
4889
|
+
return () => { clearTimeout(quiet); removeEventListener('message', on) }
|
|
4890
|
+
}, [src])
|
|
4891
|
+
useEffect(() => { if (live) tell(z) }, [z, live])
|
|
4892
|
+
return h('iframe', { ref, key: src, src, className: 'ddp-draft' + (live ? ' on' : ''), title: 'draft', sandbox: 'allow-scripts allow-same-origin', tabIndex: -1, 'aria-hidden': true })
|
|
4893
|
+
}
|
|
4894
|
+
|
|
4895
|
+
/** 没有外框的地方(第一屏的等待卡片):按设备大小排好,再整体缩进容器里 */
|
|
4896
|
+
function FitDraft({ url, kind, lang, onEnd }) {
|
|
4897
|
+
const ref = useRef(null)
|
|
4898
|
+
const [sc, setSc] = useState(0)
|
|
4899
|
+
const [w, hh] = sizeOf('', kind)
|
|
4900
|
+
useEffect(() => {
|
|
4901
|
+
const el = ref.current
|
|
4902
|
+
if (!el) return
|
|
4903
|
+
const m = () => setSc(Math.min(el.clientWidth / w, el.clientHeight / hh) || 0)
|
|
4904
|
+
m()
|
|
4905
|
+
const ro = new ResizeObserver(m)
|
|
4906
|
+
ro.observe(el)
|
|
4907
|
+
return () => ro.disconnect()
|
|
4908
|
+
}, [w, hh])
|
|
4909
|
+
return h('div', { ref, className: 'ddp-fitd' },
|
|
4910
|
+
sc ? h('div', { style: { width: w, height: hh, transform: `translate(-50%, -50%) scale(${sc})` } }, h(DraftFrame, { url, z: sc, lang, onEnd })) : null)
|
|
4911
|
+
}
|
|
4912
|
+
|
|
4006
4913
|
function Skeleton({ gone }) {
|
|
4007
4914
|
return h('div', { className: 'ddp-skel' + (gone ? ' gone' : '') },
|
|
4008
4915
|
h('b', { style: { height: 22, width: '38%' } }),
|
|
@@ -4057,23 +4964,6 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
4057
4964
|
err ? h('div', { className: 'ddp-err', style: className ? { width: '100%' } : { position: 'absolute', top: 40, left: 12 } }, err) : null)
|
|
4058
4965
|
}
|
|
4059
4966
|
|
|
4060
|
-
/** 空状态的小插画:页面线框,光标移过去,框出一块 */
|
|
4061
|
-
function EmptyArt() {
|
|
4062
|
-
return h('svg', { className: 'ddp-art', viewBox: '0 0 176 120', 'aria-hidden': true },
|
|
4063
|
-
h('rect', { className: 'pg', x: 8, y: 6, width: 160, height: 108, rx: 11 }),
|
|
4064
|
-
h('path', { className: 'ln', d: 'M8 23h160' }),
|
|
4065
|
-
h('circle', { className: 'dt', cx: 19, cy: 14.5, r: 2.4 }),
|
|
4066
|
-
h('circle', { className: 'dt', cx: 27, cy: 14.5, r: 2.4 }),
|
|
4067
|
-
h('circle', { className: 'dt', cx: 35, cy: 14.5, r: 2.4 }),
|
|
4068
|
-
h('rect', { className: 'sk', x: 22, y: 35, width: 78, height: 9, rx: 4.5 }),
|
|
4069
|
-
h('rect', { className: 'sk', x: 22, y: 50, width: 58, height: 6, rx: 3 }),
|
|
4070
|
-
h('rect', { className: 'bt', x: 22, y: 66, width: 44, height: 15, rx: 7.5 }),
|
|
4071
|
-
h('rect', { className: 'sk', x: 106, y: 35, width: 48, height: 48, rx: 9 }),
|
|
4072
|
-
h('rect', { className: 'sk', x: 22, y: 92, width: 132, height: 10, rx: 5 }),
|
|
4073
|
-
h('rect', { className: 'sel', x: 16, y: 60, width: 56, height: 27, rx: 10 }),
|
|
4074
|
-
h('path', { className: 'cur', d: 'M0 0l6.5 16 2.4-6.6 6.6-2.4z' }))
|
|
4075
|
-
}
|
|
4076
|
-
|
|
4077
4967
|
/**
|
|
4078
4968
|
* 外框:网页是浏览器窗口,App / 平板是带圆角边框的设备(App 还有灵动岛,小程序再加右上角的胶囊),海报 / 幻灯片 / 邮件是一张卡片。
|
|
4079
4969
|
* fill = 铺满舞台(单页看网页)。不管哪种,里面那屏都在同一个位置(换类型不重载页面)。top:页面顶栏右边是深色(胶囊画白的)
|
|
@@ -4252,9 +5142,9 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
4252
5142
|
setCur(id)
|
|
4253
5143
|
const p = P.current
|
|
4254
5144
|
const build = first ? !!p.born : !!(changes && changes.whole && p.busy)
|
|
4255
|
-
postTo(id, { type: '
|
|
5145
|
+
postTo(id, { type: 'zoom', z: p.zoom || 1 }) // 先定缩放:生成动画里的鼠标、名字按它定大小
|
|
5146
|
+
postTo(id, { type: 'reveal', build, lang: p.t && p.t('lang') === 'en' ? 'en' : 'zh' })
|
|
4256
5147
|
postTo(id, { type: 'busy', on: !!p.busy })
|
|
4257
|
-
postTo(id, { type: 'zoom', z: p.zoom || 1 })
|
|
4258
5148
|
if (p.picking) postTo(id, pickMsg())
|
|
4259
5149
|
if (p.demo) postTo(id, { type: 'demo', on: true })
|
|
4260
5150
|
if (p.textEdit) postTo(id, { type: 'edit', on: true, hint: false })
|
|
@@ -4506,27 +5396,6 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
4506
5396
|
}
|
|
4507
5397
|
|
|
4508
5398
|
/** 类型菜单:网页 / App / 平板 / 海报 / 幻灯片 / 邮件 */
|
|
4509
|
-
function KindMenu({ t, kind, open, setOpen, onPick, disabled }) {
|
|
4510
|
-
const ref = useRef(null)
|
|
4511
|
-
useEffect(() => {
|
|
4512
|
-
if (!open) return undefined
|
|
4513
|
-
const off = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false) }
|
|
4514
|
-
const esc = (e) => { if (e.key === 'Escape') setOpen(false) }
|
|
4515
|
-
document.addEventListener('pointerdown', off, true)
|
|
4516
|
-
window.addEventListener('keydown', esc)
|
|
4517
|
-
return () => {
|
|
4518
|
-
document.removeEventListener('pointerdown', off, true)
|
|
4519
|
-
window.removeEventListener('keydown', esc)
|
|
4520
|
-
}
|
|
4521
|
-
}, [open])
|
|
4522
|
-
return h('div', { className: 'ddp-kind', ref },
|
|
4523
|
-
h('button', { className: 'ddp-btn' + (open ? ' sel' : ''), title: t('kind'), disabled: !!disabled, onClick: () => setOpen(!open) },
|
|
4524
|
-
h(I[kind]), h('span', null, t(KIND_KEY[kind])), h(I.caret)),
|
|
4525
|
-
open ? h('div', { className: 'ddp-menu', role: 'menu' },
|
|
4526
|
-
h('div', { className: 'hd' }, t('kind')),
|
|
4527
|
-
...KIND_LIST.map((k) => h('button', { key: k, role: 'menuitemradio', 'aria-checked': k === kind, className: k === kind ? 'sel' : '', onClick: () => onPick(k) },
|
|
4528
|
-
h(I[k]), h('span', null, t(KIND_KEY[k])), h('em', null, KIND_SIZE[k].join('×'))))) : null)
|
|
4529
|
-
}
|
|
4530
5399
|
|
|
4531
5400
|
// ── 画笔标注:一层透明画布盖在预览上,画的东西按台面上的坐标(CSS 像素)一笔一笔记着,能撤销、能擦 ──
|
|
4532
5401
|
// 一笔:pen / hl { p: [[x, y]…] } · arrow / rect / oval { a: [x, y], b: [x, y] } · text { x, y, f, v };都带颜色 c,线宽 w
|
|
@@ -4793,9 +5662,9 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
4793
5662
|
const [pal, setPal] = useState(false) // 颜色板开着
|
|
4794
5663
|
const [hist, setHist] = useState({ list: [[]], i: 0 }) // 每画一笔存一份整张的,撤销 / 重做就是前后挪
|
|
4795
5664
|
const [txt, setTxt] = useState(null) // 正在写的字:{ x, y, v }
|
|
4796
|
-
//
|
|
5665
|
+
// 第一次打开时在底栏上面教一下怎么用(画了第一笔就收起;以后底栏那句够用了)
|
|
4797
5666
|
const [coach] = useState(() => {
|
|
4798
|
-
try { const n = Number(localStorage.getItem('ddp-mk-coach')) || 0; if (n >=
|
|
5667
|
+
try { const n = Number(localStorage.getItem('ddp-mk-coach')) || 0; if (n >= 1) return false; localStorage.setItem('ddp-mk-coach', String(n + 1)) } catch { /* 存不了就每次都教 */ }
|
|
4799
5668
|
return true
|
|
4800
5669
|
})
|
|
4801
5670
|
const shapes = hist.list[hist.i]
|
|
@@ -5062,39 +5931,76 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5062
5931
|
h('button', { type: 'button', className: 'go' + (shapes.length || (txt && txt.v.trim()) ? '' : ' idle'), disabled: sending, onClick: send }, t('mkSend')))))
|
|
5063
5932
|
}
|
|
5064
5933
|
|
|
5065
|
-
/**
|
|
5066
|
-
function
|
|
5934
|
+
/** 设计变量的小预览(字号、圆角、间距、阴影、字体) */
|
|
5935
|
+
function tokenPreview(kind, v) {
|
|
5936
|
+
const px = Math.max(0, parseFloat(v) * (/rem|em$/.test(v) ? 16 : 1)) || 0
|
|
5937
|
+
switch (kind) {
|
|
5938
|
+
case 'font': return h('span', { style: { fontFamily: v, fontSize: 15, fontWeight: 600 } }, 'Aa')
|
|
5939
|
+
case 'size': return h('span', { style: { fontSize: Math.min(22, px), fontWeight: 600, lineHeight: 1 } }, '字')
|
|
5940
|
+
case 'space': return h('i', { className: 'bar', style: { width: Math.max(2, Math.min(24, px / 2)) } })
|
|
5941
|
+
case 'radius': return h('i', { className: 'rd', style: { borderTopLeftRadius: Math.min(14, px) } })
|
|
5942
|
+
case 'shadow': return h('i', { className: 'sh', style: { boxShadow: v } })
|
|
5943
|
+
default: return null
|
|
5944
|
+
}
|
|
5945
|
+
}
|
|
5946
|
+
|
|
5947
|
+
/**
|
|
5948
|
+
* 外观面板:整套(配色直接改、别的变量交给模型、调节滑块)|换风格(风格卡、参考图、我的风格)|品牌(设计系统)。
|
|
5949
|
+
* 整套里改的颜色和拖的滑块先在页面上看效果,底下「直接保存」写进文件,或「交给模型」。
|
|
5950
|
+
*/
|
|
5951
|
+
function LookPanel({ t, tab, setTab, tokens, tv, onTv, onTokAsk, onTokMake, tw, live, onTw, onTwMake, write, onReset, onAsk, onSave, onPickStyle, onRef, onSaveStyle, onInspire, brand, onClose }) {
|
|
5067
5952
|
const groups = {}
|
|
5068
5953
|
for (const tok of tokens) (groups[tokenKind(tok)] = groups[tokenKind(tok)] || []).push(tok)
|
|
5069
|
-
const
|
|
5070
|
-
const
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5954
|
+
const names = tokenLabels(t, tokens)
|
|
5955
|
+
const list = (tw && tw.list) || []
|
|
5956
|
+
const errs = (tw && tw.errors) || []
|
|
5957
|
+
const n = Object.keys(tv).length + list.filter((k) => hasOwn(live, k.id)).length
|
|
5958
|
+
const tabs = [['set', t('lookSet')], ['style', t('lookStyle')], ['brand', t('lookBrand')]]
|
|
5959
|
+
let body
|
|
5960
|
+
if (tab === 'style') {
|
|
5961
|
+
body = [
|
|
5962
|
+
h('p', { className: 'tip', key: 'tip' }, t('stylesSwap')),
|
|
5963
|
+
h('div', { className: 'bd', key: 'bd' }, h(StyleCards, { t, onPick: onPickStyle }),
|
|
5964
|
+
h('div', { className: 'ddp-lk-more' },
|
|
5965
|
+
onRef ? h('button', { title: t('refPickTip'), onClick: onRef }, h(I.image), h('span', null, t('lookRef'))) : null,
|
|
5966
|
+
onSaveStyle && tokens.length ? h('button', { title: t('inSaveStyleTip'), onClick: onSaveStyle }, h(I.plus), h('span', null, t('tkSaveStyle'))) : null,
|
|
5967
|
+
onInspire ? h('button', { onClick: onInspire }, h(I.box), h('span', null, t('lookInspire'))) : null)),
|
|
5968
|
+
]
|
|
5969
|
+
} else if (tab === 'brand') {
|
|
5970
|
+
body = [brand]
|
|
5971
|
+
} else {
|
|
5972
|
+
const colors = groups.color || []
|
|
5973
|
+
body = [
|
|
5974
|
+
h('div', { className: 'bd', key: 'bd' },
|
|
5975
|
+
tokens.length ? null : h('div', { className: 'none' }, t('tokensEmpty'), h('div', null, h('button', { className: 'ddp-go', onClick: onTokMake }, t('tokensAsk')))),
|
|
5976
|
+
colors.length ? h('h4', null, t('gColor'), h('span', null, t('lookColorTip'))) : null,
|
|
5977
|
+
colors.length ? h('div', { className: 'ddp-lkc' }, ...colors.map((tok) => {
|
|
5978
|
+
const v = hasOwn(tv, tok.name) ? tv[tok.name] : tok.value
|
|
5979
|
+
return h('label', { key: tok.name, className: 'it' + (hasOwn(tv, tok.name) ? ' on' : ''), title: `${tok.name}: ${v}` },
|
|
5980
|
+
h('span', { className: 'sw', style: { background: v } },
|
|
5981
|
+
h('input', { type: 'color', value: colorHex(v, tokens), 'aria-label': names.get(tok.name), onChange: (e) => onTv(tok.name, e.target.value) })),
|
|
5982
|
+
h('span', { className: 'tx' }, h('span', { className: 'nm' }, names.get(tok.name)), h('span', { className: 'vl' }, v)))
|
|
5983
|
+
})) : null,
|
|
5984
|
+
...TOKEN_GROUPS.filter((g) => g !== 'color' && groups[g]).map((g) => h('div', { key: g },
|
|
5087
5985
|
h('h4', null, t(TOKEN_KEY[g])),
|
|
5088
|
-
...groups[g].map((tok) => h('button', { key: tok.name, className: 'ddp-tok', title: `${tok.name}: ${tok.value}`, onClick: () =>
|
|
5089
|
-
h('span', { className: g === 'shadow' ? 'pv lt' : 'pv' },
|
|
5090
|
-
h('span', { className: 'tx' }, h('span', { className: 'nm' }, tok.name), h('span', { className: 'vl' }, tok.value))))))
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5986
|
+
...groups[g].map((tok) => h('button', { key: tok.name, className: 'ddp-tok', title: `${tok.name}: ${tok.value}\n${t('lookTokTip')}`, onClick: () => onTokAsk(tok) },
|
|
5987
|
+
h('span', { className: g === 'shadow' ? 'pv lt' : 'pv' }, tokenPreview(g, tok.value)),
|
|
5988
|
+
h('span', { className: 'tx' }, h('span', { className: 'nm' }, names.get(tok.name)), h('span', { className: 'vl' }, tok.value)))))),
|
|
5989
|
+
h('h4', null, t('lookTw')),
|
|
5990
|
+
list.length
|
|
5991
|
+
? h('div', { className: 'ddp-style ddp-lktw' }, ...list.map((k) => tweakRow(t, k, live, onTw)), errs.length ? h('div', { className: 'note', key: 'err' }, errs.join(';')) : null)
|
|
5992
|
+
: h('p', { className: 'ddp-lk-none' }, errs.length ? errs.join(';') : t('lookTwNone'), ' ', h('button', { className: 'lk', onClick: () => onTwMake(errs) }, t(errs.length ? 'twFix' : 'twMake')))),
|
|
5993
|
+
n ? h('footer', { className: 'row', key: 'ft' },
|
|
5994
|
+
h('span', { className: 'cnt' }, t('lookN', { n })),
|
|
5995
|
+
h('button', { onClick: onReset }, h('span', null, t('styleReset'))),
|
|
5996
|
+
h('button', { onClick: onAsk, title: t('twAskTip') }, h('span', null, t('styleAsk'))),
|
|
5997
|
+
h('button', { className: 'go', onClick: onSave, disabled: !write.ok, title: write.ok ? t('lookSaveTip') : write.tip }, h('span', null, t('lookSave')))) : null,
|
|
5998
|
+
]
|
|
5999
|
+
}
|
|
6000
|
+
return h('div', { className: 'ddp-tokens ddp-look', onPointerDown: (e) => e.stopPropagation() },
|
|
6001
|
+
h('header', null, h(I.palette), h('b', null, t('look')), h('button', { className: 'ddp-btn', title: '×', onClick: onClose }, h(I.close))),
|
|
6002
|
+
h('div', { className: 'ddp-lktabs', role: 'tablist' }, ...tabs.map(([k, label]) => h('button', { key: k, role: 'tab', 'aria-selected': tab === k, className: tab === k ? 'on' : '', onClick: () => setTab(k) }, label))),
|
|
6003
|
+
...body)
|
|
5098
6004
|
}
|
|
5099
6005
|
|
|
5100
6006
|
/** 设计体检的结果:按屏列出问题(和页面上的编号一样),点一条在页面上滚过去、那个框闪一下 */
|
|
@@ -5118,7 +6024,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5118
6024
|
n ? h('p', { className: 'tip' }, t('checkNote')) : null,
|
|
5119
6025
|
h('footer', { className: 'row' },
|
|
5120
6026
|
h('button', { onClick: onAgain, disabled: ck.running }, h(I.refresh), h('span', null, t('checkAgain'))),
|
|
5121
|
-
h('button', { className: 'go', onClick: onFix, disabled: ck.running
|
|
6027
|
+
n ? h('button', { className: 'go', onClick: onFix, disabled: ck.running, title: t('checkFixTip') }, h(I.sparkle), h('span', null, t('checkFix'))) : null))
|
|
5122
6028
|
}
|
|
5123
6029
|
|
|
5124
6030
|
/**
|
|
@@ -5136,13 +6042,15 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5136
6042
|
// 现在用的是哪个变量:改过的看改成了什么,没改的看样式表里怎么写的
|
|
5137
6043
|
const varOf = (k) => { const m = VAR_ONLY.exec(String(cur(k) || '').trim()); return m ? m[1] : k in sty.live ? '' : (sty.vars || {})[k] || '' }
|
|
5138
6044
|
const set = (props, v) => onSet(Object.fromEntries(props.map((p) => [p, v])))
|
|
6045
|
+
const names = tokenLabels(t, tokens)
|
|
5139
6046
|
const chips = (kind, props) => (groups[kind] || []).slice(0, 10).map((tok) => h('button', {
|
|
5140
6047
|
key: tok.name,
|
|
5141
6048
|
className: 'chip' + (kind === 'color' ? ' c' : '') + (varOf(props[0]) === tok.name ? ' on' : ''),
|
|
5142
6049
|
title: `${tok.name}: ${tok.value}`,
|
|
5143
6050
|
onClick: () => set(props, `var(${tok.name})`),
|
|
5144
|
-
}, kind === 'color' ? h('i', { style: { background: tok.value } }) : tok.name
|
|
5145
|
-
const
|
|
6051
|
+
}, kind === 'color' ? h('i', { style: { background: tok.value } }) : names.get(tok.name)))
|
|
6052
|
+
const nice = (v) => names.get(v) || v.replace(/^--/, '')
|
|
6053
|
+
const shown = (k, text) => (varOf(k) ? h('em', { className: 'v', title: t('styleVar', { name: varOf(k) }) }, nice(varOf(k))) : h('em', null, text))
|
|
5146
6054
|
const slider = (key, props, max, kind) => {
|
|
5147
6055
|
const v = Math.round(pxOf(cur(props[0]), tokens) ?? 0)
|
|
5148
6056
|
const c = kind ? chips(kind, props) : []
|
|
@@ -5160,7 +6068,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5160
6068
|
const w = Math.min(700, Math.max(400, Math.round(parseInt(cur('font-weight'), 10) / 100) * 100 || 400))
|
|
5161
6069
|
const n = Object.keys(sty.live).length
|
|
5162
6070
|
return box(
|
|
5163
|
-
h('p', { className: 'who', title: sty.selector }, h('
|
|
6071
|
+
h('p', { className: 'who', title: `${sty.name} · ${sty.selector}` }, ...(() => { const e = elemLabel(t, sty.name, sty.text); return [h('b', null, e.role), e.text ? h('span', null, `「${e.text}」`) : null] })()),
|
|
5164
6072
|
h('div', { className: 'bd' },
|
|
5165
6073
|
slider('styleFont', ['font-size'], 72, 'size'),
|
|
5166
6074
|
h('div', { className: 'row', key: 'w' },
|
|
@@ -5173,10 +6081,10 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5173
6081
|
slider('stylePadX', ['padding-left', 'padding-right'], 48, 'space'),
|
|
5174
6082
|
sty.layout ? slider('styleGap', ['gap'], 48, 'space') : null),
|
|
5175
6083
|
n ? h('div', { className: 'cnt' }, t('styleN', { n: styleChanges(t, sty).length })) : null,
|
|
5176
|
-
h('footer', { className: 'row' },
|
|
5177
|
-
h('button', { onClick: onReset
|
|
5178
|
-
h('button', { onClick: onAsk,
|
|
5179
|
-
h('button', { className: 'go', onClick: onSave, disabled: !
|
|
6084
|
+
n ? h('footer', { className: 'row' },
|
|
6085
|
+
h('button', { onClick: onReset }, h('span', null, t('styleReset'))),
|
|
6086
|
+
h('button', { onClick: onAsk, title: t('styleAskTip') }, h('span', null, t('styleAsk'))),
|
|
6087
|
+
h('button', { className: 'go', onClick: onSave, disabled: !write.ok, title: write.ok ? t('lookSaveTip') : write.tip }, h('span', null, t('styleSave')))) : null)
|
|
5180
6088
|
}
|
|
5181
6089
|
|
|
5182
6090
|
/** 风格样板:用这个风格画的一小屏(顶栏、标题、按钮、商品卡、数据卡);卡片上缩小着放,点开是原大 */
|
|
@@ -5207,12 +6115,13 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5207
6115
|
}
|
|
5208
6116
|
|
|
5209
6117
|
/** 一排风格卡:每张是缩小的样板;点一张看原大样板(左右翻),「用这个风格」才交给 onPick */
|
|
5210
|
-
function StyleCards({ t, onPick }) {
|
|
6118
|
+
function StyleCards({ t, onPick, limit }) {
|
|
5211
6119
|
const en = t('lang') === 'en'
|
|
5212
6120
|
// 内置的 20 张 + 设计库 › 灵感里存的「我的风格」
|
|
5213
6121
|
const mine = useInspire().filter((x) => x.type === 'style' && x.style && x.style.c).map(myStyleOf)
|
|
5214
6122
|
const LIST = mine.length ? [...STYLES, ...mine] : STYLES
|
|
5215
6123
|
const [on, setOn] = useState(-1) // 正在看样板的那个;-1 是看全部
|
|
6124
|
+
const [all, setAll] = useState(false) // 空白页只放前 limit 张,点「更多风格」全展开
|
|
5216
6125
|
const was = useRef(-1)
|
|
5217
6126
|
const box = useRef(null)
|
|
5218
6127
|
useEffect(() => {
|
|
@@ -5224,6 +6133,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5224
6133
|
// 列表和样板高矮不一样,外面滚动的位置会变:把样板 / 刚才那张卡滚进来
|
|
5225
6134
|
if (was.current >= 0 || on >= 0) (on >= 0 ? el : f || el).scrollIntoView({ block: 'nearest' })
|
|
5226
6135
|
if (on >= 0) was.current = on
|
|
6136
|
+
if (on >= 0 && limit && on >= limit) setAll(true) // 样板里翻到了后面的卡:回全部时要看得到它
|
|
5227
6137
|
}, [on])
|
|
5228
6138
|
const n = LIST.length
|
|
5229
6139
|
if (on >= 0) {
|
|
@@ -5245,17 +6155,12 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5245
6155
|
h('button', { className: 'ddp-btn nx', title: t('styleNext'), onClick: () => go(1) }, h(I.back)),
|
|
5246
6156
|
h('button', { className: 'ddp-go', onClick: () => onPick(x) }, t('styleUse'))))
|
|
5247
6157
|
}
|
|
5248
|
-
|
|
6158
|
+
const cut = limit && !all && n > limit
|
|
6159
|
+
const grid = h('div', { className: 'ddp-styles' + (cut ? ' one' : ''), ref: box }, ...(cut ? LIST.slice(0, limit) : LIST).map((x, i) => h('button', { key: x.id, className: 'ddp-sc' + (x.mine ? ' mine' : ''), title: en ? x.den : x.dzh, onClick: () => setOn(i) },
|
|
5249
6160
|
h('span', { className: 'pv', 'aria-hidden': 'true' }, h(StyleSample, { t, x })),
|
|
5250
6161
|
h('span', { className: 'nm' }, en ? x.en : x.zh))))
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
/** 换风格面板(设计已经打开时):点一张卡让模型整套换成那个风格 */
|
|
5254
|
-
function StylesPanel({ t, onPick, onClose }) {
|
|
5255
|
-
return h('div', { className: 'ddp-tokens', onPointerDown: (e) => e.stopPropagation() },
|
|
5256
|
-
h('header', null, h(I.palette), h('b', null, t('stylesTitle')), h('button', { className: 'ddp-btn', title: '×', onClick: onClose }, h(I.close))),
|
|
5257
|
-
h('p', { className: 'tip' }, t('stylesSwap')),
|
|
5258
|
-
h('div', { className: 'bd' }, h(StyleCards, { t, onPick })))
|
|
6162
|
+
if (!limit || n <= limit) return grid
|
|
6163
|
+
return h('div', { className: 'ddp-stylesw' }, grid, h('button', { className: 'lk ddp-more-st', onClick: () => setAll(!all) }, all ? t('stylesLess') : t('stylesMore', { n: n - limit })))
|
|
5259
6164
|
}
|
|
5260
6165
|
|
|
5261
6166
|
// ── 调节滑块、品牌设计系统、转成代码 ─────────────────────────────────────
|
|
@@ -5283,41 +6188,32 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5283
6188
|
return t('twAskHead') + '\n' + rows.join('\n') + '\n' + t('twAskTail') + '\n'
|
|
5284
6189
|
}
|
|
5285
6190
|
|
|
5286
|
-
/**
|
|
5287
|
-
function
|
|
5288
|
-
const
|
|
5289
|
-
const
|
|
5290
|
-
const
|
|
5291
|
-
|
|
5292
|
-
if (!list.length) {
|
|
5293
|
-
return box(h('div', { className: 'bd' }, h('div', { className: 'none' },
|
|
5294
|
-
errs.length ? errs.map((e, i) => h('div', { key: i }, e)) : t('twEmpty'),
|
|
5295
|
-
h('div', null, h('button', { className: 'ddp-go', onClick: () => onMake(errs) }, t(errs.length ? 'twFix' : 'twMake'))))))
|
|
5296
|
-
}
|
|
5297
|
-
const cur = (k) => (hasOwn(live, k.id) ? live[k.id] : k.value)
|
|
5298
|
-
const row = (k) => {
|
|
5299
|
-
const v = cur(k)
|
|
5300
|
-
const lab = h('label', null, h('span', { title: k.var || k.attr }, k.label), h('em', { className: hasOwn(live, k.id) ? 'v' : '' }, tweakShown(k, v)))
|
|
5301
|
-
const note = k.note ? h('div', { className: 'note' }, k.note) : null
|
|
5302
|
-
if (k.kind === 'range') {
|
|
5303
|
-
return h('div', { className: 'row', key: k.id }, lab,
|
|
5304
|
-
h('input', { type: 'range', min: k.min, max: k.max, step: k.step, value: v, 'aria-label': k.label, onChange: (e) => onSet(k, Number(e.target.value)) }), note)
|
|
5305
|
-
}
|
|
5306
|
-
if (k.kind === 'color') {
|
|
5307
|
-
return h('div', { className: 'row', key: k.id }, lab,
|
|
5308
|
-
h('div', { className: 'chips' }, h('input', { type: 'color', value: /^#[0-9a-f]{6}$/i.test(v) ? v : '#000000', 'aria-label': k.label, onChange: (e) => onSet(k, e.target.value) })), note)
|
|
5309
|
-
}
|
|
6191
|
+
/** 调节滑块的一行:模型给这套设计做的维度(tweaks.json),拖的时候所有屏一起变 */
|
|
6192
|
+
function tweakRow(t, k, live, onSet) {
|
|
6193
|
+
const v = hasOwn(live, k.id) ? live[k.id] : k.value
|
|
6194
|
+
const lab = h('label', null, h('span', { title: k.var || k.attr }, k.label), h('em', { className: hasOwn(live, k.id) ? 'v' : '' }, tweakShown(k, v)))
|
|
6195
|
+
const note = k.note ? h('div', { className: 'note' }, k.note) : null
|
|
6196
|
+
if (k.kind === 'range') {
|
|
5310
6197
|
return h('div', { className: 'row', key: k.id }, lab,
|
|
5311
|
-
h('
|
|
6198
|
+
h('input', { type: 'range', min: k.min, max: k.max, step: k.step, value: v, 'aria-label': k.label, onChange: (e) => onSet(k, Number(e.target.value)) }), note)
|
|
5312
6199
|
}
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
6200
|
+
if (k.kind === 'color') {
|
|
6201
|
+
return h('div', { className: 'row', key: k.id }, lab,
|
|
6202
|
+
h('div', { className: 'chips' }, h('input', { type: 'color', value: /^#[0-9a-f]{6}$/i.test(v) ? v : '#000000', 'aria-label': k.label, onChange: (e) => onSet(k, e.target.value) })), note)
|
|
6203
|
+
}
|
|
6204
|
+
return h('div', { className: 'row', key: k.id }, lab,
|
|
6205
|
+
h('div', { className: 'seg' }, ...(k.options || []).map((o) => h('button', { key: o.value, className: String(v) === o.value ? 'on' : '', onClick: () => onSet(k, o.value) }, o.label))), note)
|
|
6206
|
+
}
|
|
6207
|
+
|
|
6208
|
+
/** 外观面板里改的:滑块 + 直接改的设计变量 → 交给模型的一段字 */
|
|
6209
|
+
function formatLook(t, list, live, tokens, tv) {
|
|
6210
|
+
let out = Object.keys(live).length ? formatTweaks(t, list, live) : ''
|
|
6211
|
+
const rows = Object.entries(tv).map(([name, v]) => {
|
|
6212
|
+
const tok = tokens.find((x) => x.name === name)
|
|
6213
|
+
return `- ${name}(${tok ? tokenLabel(t, tok) : name}):${tok ? tok.value : '?'} → ${v}`
|
|
6214
|
+
})
|
|
6215
|
+
if (rows.length) out += t('lookAskHead') + '\n' + rows.join('\n') + '\n' + t('lookAskTail') + '\n'
|
|
6216
|
+
return out
|
|
5321
6217
|
}
|
|
5322
6218
|
|
|
5323
6219
|
async function getJson(path) {
|
|
@@ -5348,7 +6244,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5348
6244
|
return h('button', { type: 'button', className: 'ddp-dirbtn', title: t('pickDir'), 'aria-label': t('pickDir'), disabled: busy, onClick: pick }, h(I.folder))
|
|
5349
6245
|
}
|
|
5350
6246
|
|
|
5351
|
-
function SystemsPanel({ t, rev, hasDesign, onExtract, onApply, onSay, onClose }) {
|
|
6247
|
+
function SystemsPanel({ t, rev, hasDesign, onExtract, onApply, onSay, onClose, bare }) {
|
|
5352
6248
|
const [data, setData] = useState(null)
|
|
5353
6249
|
const [err, setErr] = useState('')
|
|
5354
6250
|
const [src, setSrc] = useState('')
|
|
@@ -5366,9 +6262,13 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5366
6262
|
onExtract(v)
|
|
5367
6263
|
setSrc('')
|
|
5368
6264
|
}
|
|
5369
|
-
return h('div', { className: 'ddp-tokens ddp-sys', onPointerDown: (e) => e.stopPropagation() },
|
|
5370
|
-
h('header', null, h(I.library), h('b', null, t('systems')), h('button', { className: 'ddp-btn', title: '×', onClick: onClose }, h(I.close))),
|
|
6265
|
+
return h('div', { className: bare ? 'ddp-sys bare' : 'ddp-tokens ddp-sys', onPointerDown: (e) => e.stopPropagation() },
|
|
6266
|
+
bare ? null : h('header', null, h(I.library), h('b', null, t('systems')), h('button', { className: 'ddp-btn', title: '×', onClick: onClose }, h(I.close))),
|
|
5371
6267
|
h('p', { className: 'tip' }, t('sysTip')),
|
|
6268
|
+
h('form', { onSubmit: go },
|
|
6269
|
+
h('input', { value: src, placeholder: t('sysPh'), 'aria-label': t('sysPh'), onChange: (e) => setSrc(e.target.value) }),
|
|
6270
|
+
h(DirButton, { t, onPicked: setSrc, onSay }),
|
|
6271
|
+
h('button', { type: 'submit', disabled: !src.trim() }, t('sysExtract'))),
|
|
5372
6272
|
h('div', { className: 'bd' },
|
|
5373
6273
|
err ? h('div', { className: 'none' }, err) : null,
|
|
5374
6274
|
data && !list.length ? h('div', { className: 'none' }, t('sysEmpty')) : null,
|
|
@@ -5379,12 +6279,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5379
6279
|
x.sources && x.sources.length ? h('small', { title: x.sources.join('\n') }, x.sources[0]) : null,
|
|
5380
6280
|
h('div', { className: 'acts' },
|
|
5381
6281
|
h('button', { onClick: () => setDef(x, !x.default) }, x.default ? t('sysUnset') : t('sysSet')),
|
|
5382
|
-
hasDesign ? h('button', { onClick: () => onApply(x) }, t('sysApply')) : null))))
|
|
5383
|
-
h('footer', null,
|
|
5384
|
-
h('form', { onSubmit: go },
|
|
5385
|
-
h('input', { value: src, placeholder: t('sysPh'), 'aria-label': t('sysPh'), onChange: (e) => setSrc(e.target.value) }),
|
|
5386
|
-
h(DirButton, { t, onPicked: setSrc, onSay }),
|
|
5387
|
-
h('button', { type: 'submit', disabled: !src.trim() }, t('sysExtract')))))
|
|
6282
|
+
hasDesign ? h('button', { onClick: () => onApply(x) }, t('sysApply')) : null)))))
|
|
5388
6283
|
}
|
|
5389
6284
|
|
|
5390
6285
|
const FW_LIST = ['react', 'vue', 'next', 'html']
|
|
@@ -5392,6 +6287,12 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5392
6287
|
// 小程序的设计:先列 uni-app、原生小程序
|
|
5393
6288
|
const FW_MINI = ['uniapp', 'weapp']
|
|
5394
6289
|
|
|
6290
|
+
/** 「uni-app(HBuilderX,Vue 3)」→「uni-app · HBuilderX · Vue 3」:放进「跟现有项目一样:…」里别括号套括号 */
|
|
6291
|
+
const dirParts = (x) => String(x || '').split(/[\\/]+/).filter(Boolean)
|
|
6292
|
+
const dirName = (x) => dirParts(x).pop() || String(x || '')
|
|
6293
|
+
const dirParent = (x) => { const q = dirParts(x); return q.length > 1 ? q[q.length - 2] : '' }
|
|
6294
|
+
const flatLabel = (x) => String(x || '').replace(/\s*[((]\s*/g, ' · ').replace(/\s*[,,、]\s*/g, ' · ').replace(/\s*[))]\s*/g, ' ').replace(/\s+/g, ' ').trim()
|
|
6295
|
+
|
|
5395
6296
|
/** 转成代码:用什么做、放哪、转几屏;点了把一段话放进输入框,模型调 handoff_design 照交接包做,每屏做完 compare_design */
|
|
5396
6297
|
function HandoffPanel({ t, sid, root, fileTarget, count, cur, onGo, onSay, onClose }) {
|
|
5397
6298
|
const [info, setInfo] = useState(null)
|
|
@@ -5400,6 +6301,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5400
6301
|
const [one, setOne] = useState(false)
|
|
5401
6302
|
const [moved, setMoved] = useState(null) // 选的文件夹不能直接放:{ dest, why }('nested' 在里面新建、'beside' 放设计目录旁边)
|
|
5402
6303
|
const [checking, setChecking] = useState('') // 'busy' 正在问服务器这个文件夹;别的字是没问成的原因
|
|
6304
|
+
const [typing, setTyping] = useState(false) // 路径平时只显示文件夹名,点了才变输入框
|
|
5403
6305
|
// 现在是哪套设计、第几次选:选文件夹的窗口开着时换了设计、或者又选了一次 / 手改了路径,旧的结果作废
|
|
5404
6306
|
// hand:用户自己点过框架(换设计前一直听用户的,不再按选的文件夹自动换)
|
|
5405
6307
|
const live = useRef({ root, seq: 0, hand: false })
|
|
@@ -5450,7 +6352,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5450
6352
|
const p = info.project || {}
|
|
5451
6353
|
const same = !!p.framework
|
|
5452
6354
|
// 「跟现有项目一样」认得出 HBuilderX 的 uni-app、微信原生小程序(没有 package.json 也行)
|
|
5453
|
-
const sameLabel = p.framework === 'weapp' ? t('hoWeapp') : p.label
|
|
6355
|
+
const sameLabel = flatLabel(p.framework === 'weapp' ? t('hoWeapp') : p.label)
|
|
5454
6356
|
const list = info.kind === 'miniprogram' ? [...FW_MINI, ...FW_LIST] : FW_LIST
|
|
5455
6357
|
const name = (k) => (k === 'html' ? t('hoHtml') : k === 'uniapp' ? t('hoUni') : k === 'weapp' ? t('hoWeapp') : FW_LABEL[k])
|
|
5456
6358
|
const opts = [...(same ? [['same', t('hoSame', { label: sameLabel })]] : []), ...list.map((k) => [k, name(k)])]
|
|
@@ -5462,7 +6364,9 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5462
6364
|
...opts.map(([k, l]) => h('button', { key: k, className: 'opt' + (fw === k ? ' on' : ''), onClick: () => { live.current.hand = true; setFw(k) } }, h('i'), h('span', null, l)))),
|
|
5463
6365
|
h('div', { className: 'row' }, h('label', null, t('hoDest')),
|
|
5464
6366
|
h('div', { className: 'pathrow' },
|
|
5465
|
-
|
|
6367
|
+
typing || !dest.trim()
|
|
6368
|
+
? h('input', { type: 'text', value: dest, spellCheck: false, autoFocus: typing, 'aria-label': t('hoDest'), onChange: (e) => typeDest(e.target.value), onBlur: () => setTyping(false), onKeyDown: (e) => { if (e.key === 'Enter' || e.key === 'Escape') e.currentTarget.blur() } })
|
|
6369
|
+
: h('button', { type: 'button', className: 'pathv', title: dest + '\n' + t('hoEdit'), onClick: () => setTyping(true) }, h(I.folder), h('b', null, dirName(dest)), dirParent(dest) ? h('em', null, t('hoIn', { dir: dirParent(dest) })) : null),
|
|
5466
6370
|
h(DirButton, { t, onPicked: pickDest, onSay })),
|
|
5467
6371
|
checking ? h('p', { className: 'moved' + (checking === 'busy' ? '' : ' bad') }, checking === 'busy' ? t('hoChecking') : checking)
|
|
5468
6372
|
: moved && moved.dest === dest ? h('p', { className: 'moved' }, t(moved.why === 'beside' ? 'hoBeside' : 'hoMoved', { name: moved.dest.split(/[\\/]/).pop() })) : null),
|
|
@@ -5487,40 +6391,155 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5487
6391
|
window.removeEventListener('keydown', esc)
|
|
5488
6392
|
}
|
|
5489
6393
|
}, [open])
|
|
6394
|
+
// 菜单比剩下的地方高(屏多、侧栏矮):限高、整个菜单能滚,底下的入口也点得到
|
|
6395
|
+
react.useLayoutEffect(() => {
|
|
6396
|
+
const wrap = ref.current
|
|
6397
|
+
const m = open && wrap ? wrap.querySelector(':scope > .ddp-menu') : null
|
|
6398
|
+
const box = wrap ? wrap.closest('.ddp-stage, .ddp') : null
|
|
6399
|
+
if (!m || !box) return
|
|
6400
|
+
const b = box.getBoundingClientRect()
|
|
6401
|
+
const r = m.getBoundingClientRect()
|
|
6402
|
+
const up = r.bottom <= wrap.getBoundingClientRect().top + 1
|
|
6403
|
+
const room = Math.floor(up ? r.bottom - b.top - 8 : b.bottom - r.top - 8)
|
|
6404
|
+
if (r.height > room) { m.style.maxHeight = Math.max(120, room) + 'px'; m.style.overflowY = 'auto' }
|
|
6405
|
+
}, [open])
|
|
5490
6406
|
}
|
|
5491
6407
|
|
|
5492
6408
|
/** 导出菜单:这一屏(带外框 / 纯画面 / 整页长图)、整套展示图 */
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
useDismiss(open, setOpen, ref)
|
|
6409
|
+
/** 导出的几种:这一屏(带外框 / 纯画面 / 整页长图)、整套展示图、PDF、PPT */
|
|
6410
|
+
function exportItems(t, kind, count, name) {
|
|
5496
6411
|
const items = []
|
|
5497
|
-
|
|
5498
|
-
items.push(['
|
|
5499
|
-
|
|
6412
|
+
const n = { name: name || t('exThisName') } // 写出是哪一屏,别只说「这一屏」
|
|
6413
|
+
if (FRAMED[kind]) items.push(['frame', I.single, t('exFrame', n)])
|
|
6414
|
+
items.push(['raw', I.image, t('exRaw', n)])
|
|
6415
|
+
if (kind !== 'poster' && kind !== 'slides') items.push(['long', I.long, t('exLong', n)]) // 海报、幻灯片画幅是定的,没有「整页」
|
|
5500
6416
|
if (count > 1) items.push(['show', I.canvas, t('exShow')])
|
|
5501
6417
|
items.push(['pdf', I.pdf, t('exPdf')], ['pptx', I.deck, t('exPptx')])
|
|
5502
|
-
|
|
5503
|
-
return h('div', { className: 'ddp-kind', ref },
|
|
5504
|
-
h('button', { className: 'ddp-btn' + (open ? ' sel' : ''), title: t('exportT'), disabled, onClick: () => setOpen(!open) }, h(I.download)),
|
|
5505
|
-
open ? h('div', { className: 'ddp-menu', role: 'menu' },
|
|
5506
|
-
h('div', { className: 'hd' }, t('exportT')),
|
|
5507
|
-
...items.map(([mode, Icon, label]) => h('button', { key: mode, role: 'menuitem', onClick: () => onPick(mode) }, h(Icon), h('span', null, label))),
|
|
5508
|
-
h('div', { className: 'ft' }, t('exWhere'))) : null)
|
|
6418
|
+
return items
|
|
5509
6419
|
}
|
|
5510
6420
|
|
|
5511
6421
|
/**
|
|
5512
|
-
*
|
|
5513
|
-
* items: [
|
|
6422
|
+
* 工具栏上的下拉菜单(视图、交付、更多都用它)。
|
|
6423
|
+
* items: [类名, 图标, 名字, 选中, 不可用, 点了做什么(字符串就是在浏览器里打开的地址), 右边小字]
|
|
6424
|
+
* 字符串 'hd:名字' 是小标题,'sep' 是分隔线
|
|
5514
6425
|
*/
|
|
5515
|
-
|
|
6426
|
+
// 浮块按钮的悬停说明:系统 title 要等一秒多、只有一行灰字;这里停一下就出,上面名字、下面一句用途
|
|
6427
|
+
// 用法:按钮带上 tipA(名字, 说明);HoverTip 放进浮块所在的那层,自己盯着里面带 data-tip 的东西
|
|
6428
|
+
const tipA = (name, desc, key) => ({ 'data-tip': name, 'data-tip-d': desc || undefined, 'data-tip-k': key || undefined, 'aria-label': desc ? name + ' — ' + desc : name, 'aria-keyshortcuts': key && !/\s/.test(key) ? key : undefined })
|
|
6429
|
+
function HoverTip() {
|
|
6430
|
+
const ref = useRef(null)
|
|
6431
|
+
const [tip, setTip] = useState(null) // { n, d, k, cx, y, up }
|
|
6432
|
+
useEffect(() => {
|
|
6433
|
+
const el = ref.current
|
|
6434
|
+
const box = el && el.parentElement
|
|
6435
|
+
if (!box) return
|
|
6436
|
+
let cur = null // 鼠标停在哪个按钮上
|
|
6437
|
+
let timer = 0
|
|
6438
|
+
let showing = false
|
|
6439
|
+
let warmTill = 0 // 刚看过一个:挪到旁边的按钮立刻出,不再等
|
|
6440
|
+
const show = (b) => {
|
|
6441
|
+
if (!b.isConnected || b.getAttribute('aria-expanded') === 'true') return
|
|
6442
|
+
const r = b.getBoundingClientRect()
|
|
6443
|
+
const s = box.getBoundingClientRect()
|
|
6444
|
+
const up = r.top + r.height / 2 > s.top + s.height / 2 // 下半截的按钮往上弹
|
|
6445
|
+
showing = true
|
|
6446
|
+
setTip({ n: b.dataset.tip, d: b.dataset.tipD || '', k: b.dataset.tipK || '', cx: r.left + r.width / 2 - s.left, y: up ? r.top - s.top - 8 : r.bottom - s.top + 8, up })
|
|
6447
|
+
}
|
|
6448
|
+
const hide = () => {
|
|
6449
|
+
clearTimeout(timer)
|
|
6450
|
+
if (showing) warmTill = Date.now() + 600
|
|
6451
|
+
showing = false
|
|
6452
|
+
setTip(null)
|
|
6453
|
+
}
|
|
6454
|
+
const enter = (b) => {
|
|
6455
|
+
if (b === cur) return
|
|
6456
|
+
cur = b
|
|
6457
|
+
clearTimeout(timer)
|
|
6458
|
+
if (!b) return hide()
|
|
6459
|
+
if (showing || Date.now() < warmTill) show(b)
|
|
6460
|
+
else timer = setTimeout(() => show(b), 350)
|
|
6461
|
+
}
|
|
6462
|
+
const over = (e) => { const b = e.target.closest ? e.target.closest('[data-tip]') : null; enter(b && box.contains(b) ? b : null) }
|
|
6463
|
+
const leave = () => enter(null)
|
|
6464
|
+
const down = () => { clearTimeout(timer); showing = false; warmTill = 0; setTip(null) } // 点了就收,等鼠标挪开再说
|
|
6465
|
+
const focus = (e) => { const b = e.target.closest ? e.target.closest('[data-tip]') : null; if (b && b.matches(':focus-visible')) { cur = b; clearTimeout(timer); show(b) } }
|
|
6466
|
+
const blur = () => { cur = null; hide() }
|
|
6467
|
+
const key = (e) => { if (e.key === 'Escape') down() }
|
|
6468
|
+
box.addEventListener('pointerover', over)
|
|
6469
|
+
box.addEventListener('pointerleave', leave)
|
|
6470
|
+
box.addEventListener('pointerdown', down, true)
|
|
6471
|
+
box.addEventListener('focusin', focus)
|
|
6472
|
+
box.addEventListener('focusout', blur)
|
|
6473
|
+
box.addEventListener('keydown', key, true)
|
|
6474
|
+
return () => {
|
|
6475
|
+
clearTimeout(timer)
|
|
6476
|
+
box.removeEventListener('pointerover', over)
|
|
6477
|
+
box.removeEventListener('pointerleave', leave)
|
|
6478
|
+
box.removeEventListener('pointerdown', down, true)
|
|
6479
|
+
box.removeEventListener('focusin', focus)
|
|
6480
|
+
box.removeEventListener('focusout', blur)
|
|
6481
|
+
box.removeEventListener('keydown', key, true)
|
|
6482
|
+
}
|
|
6483
|
+
}, [])
|
|
6484
|
+
// 量出自己多宽再摆(画出来之前):横着别出边,上弹的贴着按钮上沿
|
|
6485
|
+
react.useLayoutEffect(() => {
|
|
6486
|
+
const el = ref.current
|
|
6487
|
+
if (!el || !tip) return
|
|
6488
|
+
const W = el.parentElement ? el.parentElement.clientWidth : 0
|
|
6489
|
+
const w = el.offsetWidth
|
|
6490
|
+
el.style.left = Math.max(8, Math.min(W - w - 8, tip.cx - w / 2)) + 'px'
|
|
6491
|
+
el.style.top = (tip.up ? tip.y - el.offsetHeight : tip.y) + 'px'
|
|
6492
|
+
}, [tip])
|
|
6493
|
+
// 说明已经在按钮的 aria-label 里,读屏不用再念一遍
|
|
6494
|
+
return h('div', { ref, className: 'ddp-tipbox' + (tip ? ' on' : ''), 'aria-hidden': 'true' },
|
|
6495
|
+
tip ? h('div', { className: 'nm' }, h('b', null, tip.n), tip.k ? h('kbd', null, tip.k) : null) : null,
|
|
6496
|
+
tip && tip.d ? h('span', null, tip.d) : null)
|
|
6497
|
+
}
|
|
6498
|
+
|
|
6499
|
+
function DropMenu({ t, cls, btnCls, title, tip, icon, label, caret, has, open, setOpen, disabled, items, foot }) {
|
|
5516
6500
|
const ref = useRef(null)
|
|
5517
6501
|
useDismiss(open, setOpen, ref)
|
|
5518
|
-
return h('div', { className: 'ddp-kind
|
|
5519
|
-
h('button', { className: 'ddp-btn' + (open ? ' sel' : '') + (has ? ' has' : ''), title:
|
|
6502
|
+
return h('div', { className: 'ddp-kind' + (cls ? ' ' + cls : ''), ref },
|
|
6503
|
+
h('button', { className: 'ddp-btn' + (btnCls ? ' ' + btnCls : '') + (open ? ' sel' : '') + (has ? ' has' : ''), ...(tip ? tipA(tip[0], tip[1], tip[2]) : { title }), 'aria-haspopup': 'menu', 'aria-expanded': open, disabled: !!disabled, onClick: () => setOpen(!open) },
|
|
6504
|
+
icon ? h(icon) : null, label ? h('span', { className: 'lb' }, label) : null, caret ? h(I.caret) : null),
|
|
5520
6505
|
open ? h('div', { className: 'ddp-menu', role: 'menu' },
|
|
5521
|
-
...items.filter(Boolean).map((
|
|
5522
|
-
|
|
5523
|
-
|
|
6506
|
+
...items.filter(Boolean).map((x, i) => {
|
|
6507
|
+
if (typeof x === 'string') {
|
|
6508
|
+
if (x.startsWith('hd:')) return h('div', { key: 'h' + i, className: 'hd' }, x.slice(3))
|
|
6509
|
+
return h('div', { key: 's' + i, className: 'sp ' + x.slice(4), role: 'separator' })
|
|
6510
|
+
}
|
|
6511
|
+
const [tier, Icon, name, on, off, run, em] = x
|
|
6512
|
+
const kids = [h(Icon), h('span', null, name), em != null && em !== '' ? h('em', null, em) : null]
|
|
6513
|
+
return typeof run === 'string'
|
|
6514
|
+
? h('a', { key: name, className: tier, role: 'menuitem', href: run, target: '_blank', rel: 'noopener noreferrer', onClick: () => setOpen(false) }, ...kids)
|
|
6515
|
+
: h('button', { key: name, className: tier + (on ? ' sel' : ''), role: 'menuitem', disabled: off, onClick: () => { setOpen(false); run() } }, ...kids)
|
|
6516
|
+
}),
|
|
6517
|
+
foot ? h('div', { className: 'ft' }, foot) : null) : null)
|
|
6518
|
+
}
|
|
6519
|
+
|
|
6520
|
+
// 左上屏名 ▾:换屏(画布 / 某一屏)、多尺寸、类型、打开别的页面——「在看什么、怎么看」都在这一个菜单里
|
|
6521
|
+
function ScreenMenu({ t, label, title, tip, src, busy, list, cur, canvas, open, setOpen, onPick, onAll, onOther, sizes, kind, onKind }) {
|
|
6522
|
+
const ref = useRef(null)
|
|
6523
|
+
useDismiss(open, setOpen, ref)
|
|
6524
|
+
const n = list.length
|
|
6525
|
+
const close = (run) => () => { setOpen(false); run() }
|
|
6526
|
+
return h('div', { className: 'ddp-kind ddp-where', ref },
|
|
6527
|
+
h('button', { className: 'ddp-addr' + (open ? ' sel' : ''), ...(tip ? tipA(tip[0], tip[1]) : { title: title || t('screensT') }), 'data-src': src || undefined, 'aria-haspopup': 'menu', 'aria-expanded': open, onClick: () => setOpen(!open) },
|
|
6528
|
+
h('i', { className: 'ddp-dot' + (busy ? ' wait' : '') }),
|
|
6529
|
+
(() => { const m = /^(.+?)( · [^·]+)$/.exec(label || ''); return m ? h('span', { className: 'lb2' }, h('b', null, m[1]), h('em', null, m[2])) : h('span', null, label) })(),
|
|
6530
|
+
h(I.caret)),
|
|
6531
|
+
open ? h('div', { className: 'ddp-menu l', role: 'menu' },
|
|
6532
|
+
h('div', { className: 'hd' }, n > 1 ? t('screensHd') : t('screensT')),
|
|
6533
|
+
h('button', { className: canvas && !(sizes && sizes.on) ? 'sel' : '', role: 'menuitemradio', 'aria-checked': canvas, onClick: close(onAll) }, h(I.canvas), h('span', null, t('canvasShort')), n > 1 ? h('em', null, t('screensAll', { n })) : null),
|
|
6534
|
+
h('div', { className: 'ls' }, ...list.map((x) => h('button', { key: x.key, className: !canvas && !(sizes && sizes.on) && x.key === cur ? 'sel' : '', role: 'menuitemradio', 'aria-checked': !canvas && x.key === cur, title: x.key, onClick: close(() => onPick(x.key)) },
|
|
6535
|
+
h(I.single), h('span', null, x.label)))),
|
|
6536
|
+
sizes ? h('div', { className: 'sp', role: 'separator' }) : null,
|
|
6537
|
+
sizes ? h('button', { className: sizes.on ? 'sel' : '', role: 'menuitemcheckbox', 'aria-checked': !!sizes.on, disabled: !!sizes.off, title: sizes.tip, onClick: close(sizes.run) }, h(I.sizes), h('span', null, t('sizesShort')), sizes.note ? h('em', null, sizes.note) : null) : null,
|
|
6538
|
+
onKind ? h('div', { className: 'hd' }, t('kind')) : null,
|
|
6539
|
+
onKind ? h('div', { className: 'ddp-kinds', role: 'group', 'aria-label': t('kind') }, ...KIND_LIST.map((k) => h('button', { key: k, role: 'menuitemradio', 'aria-checked': k === kind, className: k === kind ? 'sel' : '', title: KIND_SIZE[k].join('×'), onClick: close(() => onKind(k)) },
|
|
6540
|
+
h(I[k]), h('span', null, t(KIND_KEY[k]))))) : null,
|
|
6541
|
+
h('div', { className: 'sp', role: 'separator' }),
|
|
6542
|
+
h('button', { role: 'menuitem', onClick: close(onOther) }, h(I.folder), h('span', null, t('openOther')))) : null)
|
|
5524
6543
|
}
|
|
5525
6544
|
|
|
5526
6545
|
/** 二维码矩阵(一行一串 0 / 1)→ SVG(四周留 4 格白边) */
|
|
@@ -5553,10 +6572,12 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5553
6572
|
seen ? h('span', { key: 'st', className: 'st ok' }, h(I.check), t('phoneSeen')) : h('span', { key: 'st', className: 'st' }, h('i'), t('phoneWait')),
|
|
5554
6573
|
h('p', { key: 'fl', className: 'fine' }, t('phoneFollow')),
|
|
5555
6574
|
h('div', { key: 'row', className: 'row' },
|
|
5556
|
-
h('button', { className: 'ddp-go', onClick:
|
|
5557
|
-
h('button', { className: 'ddp-go', onClick:
|
|
5558
|
-
h('
|
|
5559
|
-
|
|
6575
|
+
h('button', { className: 'ddp-go sec', onClick: onStop }, t('phoneStop')),
|
|
6576
|
+
h('button', { className: 'ddp-go', onClick: () => onCopy(res.url) }, t('phoneCopy'))),
|
|
6577
|
+
h('details', { key: 'more', className: 'fine', open: false },
|
|
6578
|
+
h('summary', null, t('phoneTrouble')),
|
|
6579
|
+
h('p', null, t('phoneHelp')),
|
|
6580
|
+
h('p', null, t('phoneAuto'))),
|
|
5560
6581
|
]
|
|
5561
6582
|
}
|
|
5562
6583
|
return h('div', { className: 'ddp-tokens ddp-phone', onPointerDown: (e) => e.stopPropagation() },
|
|
@@ -5652,9 +6673,9 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5652
6673
|
w.exists
|
|
5653
6674
|
? [
|
|
5654
6675
|
h('button', { key: 'o', disabled: cur, onClick: () => onOpen(w) }, t('wkOpen')),
|
|
5655
|
-
h('button', { key: 'r', title: t('wkReveal'), onClick: () => op(w, 'reveal') }, h(I.folder)),
|
|
5656
|
-
h('button', { key: 'z', title: t('wkZip'), onClick: () => onPack(w.dir) }, h(I.download)),
|
|
5657
|
-
h('button', { key: 's', title: t('wkShare'), onClick: () => onShare(w.dir) }, h(I.share)),
|
|
6676
|
+
h('button', { key: 'r', title: t('wkReveal'), onClick: () => op(w, 'reveal') }, h(I.folder), h('span', null, t('wkRevealShort'))),
|
|
6677
|
+
h('button', { key: 'z', title: t('wkZip'), onClick: () => onPack(w.dir) }, h(I.download), h('span', null, t('wkZipShort'))),
|
|
6678
|
+
h('button', { key: 's', title: t('wkShare'), onClick: () => onShare(w.dir) }, h(I.share), h('span', null, t('wkShareShort'))),
|
|
5658
6679
|
h('button', { key: 'p', title: t(w.pinned ? 'wkUnpin' : 'wkPin'), className: w.pinned ? 'on' : '', onClick: () => op(w, w.pinned ? 'unpin' : 'pin') }, '★'),
|
|
5659
6680
|
]
|
|
5660
6681
|
: dirPicker() ? h('button', { key: 'l', onClick: () => relocate(w) }, t('wkRelocate')) : null,
|
|
@@ -5797,28 +6818,108 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5797
6818
|
}
|
|
5798
6819
|
const mkRevoked = (v, id, rev) => !!v && (v.revoked || []).some((r) => r.id === id && (!r.rev || !rev || r.rev === rev))
|
|
5799
6820
|
|
|
6821
|
+
// 首页顶上一条:左边名字,右边「开始 / 市场 / 我的作品 / 灵感」——设计库并进首页的页签
|
|
6822
|
+
const HOME_TABS = [['', 'homeTab'], ['market', 'libMarket'], ['works', 'libWorks'], ['inspire', 'libInspire']]
|
|
6823
|
+
function HomeBar({ t, tab, onTab, lead }) {
|
|
6824
|
+
// 左右方向键在几栏之间挪焦点,回车 / 空格再打开(按到哪就开哪会一路弹设计库)
|
|
6825
|
+
const onKey = (e) => {
|
|
6826
|
+
if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(e.key)) return
|
|
6827
|
+
const bs = [...e.currentTarget.querySelectorAll('[role=tab]')]
|
|
6828
|
+
const i = Math.max(0, bs.indexOf(document.activeElement))
|
|
6829
|
+
const n = e.key === 'Home' ? 0 : e.key === 'End' ? bs.length - 1 : (i + (e.key === 'ArrowRight' ? 1 : -1) + bs.length) % bs.length
|
|
6830
|
+
e.preventDefault()
|
|
6831
|
+
if (bs[n]) bs[n].focus()
|
|
6832
|
+
}
|
|
6833
|
+
return h('header', { className: 'ddp-homebar' + (lead ? ' led' : '') },
|
|
6834
|
+
lead || null,
|
|
6835
|
+
h('div', { className: 'nm' }, h('i', { className: 'mk', 'aria-hidden': true }), h('b', null, t('title'))),
|
|
6836
|
+
h('div', { className: 'seg', role: 'tablist', onKeyDown: onKey }, ...HOME_TABS.map(([k, key]) => h('button', { key: k || 'home', role: 'tab', 'aria-selected': tab === k, tabIndex: tab === k ? 0 : -1, className: tab === k ? 'on' : '', onClick: () => onTab(k) }, t(key)))))
|
|
6837
|
+
}
|
|
6838
|
+
|
|
6839
|
+
// 首页大输入框:说一句 + 类型 + 风格 → onSend(发给模型)。没写字时 Tab 用占位里的例子
|
|
6840
|
+
const ASK_KINDS = ['app', 'miniprogram', 'web', 'poster', 'slides', 'tablet', 'desktop', 'email']
|
|
6841
|
+
const ASK_EX = { web: 'exWeb', app: 'exApp', miniprogram: 'exMini', tablet: 'exTablet', desktop: 'exDesktop', poster: 'exPoster', slides: 'exSlides', email: 'exEmail' }
|
|
6842
|
+
function HomeAsk({ t, onSend, onRef, plus, draft, note, autoFocus }) {
|
|
6843
|
+
const d0 = (draft && draft.current) || {}
|
|
6844
|
+
const [text, setText] = useState(d0.text || '')
|
|
6845
|
+
const [kind, setKind] = useState(d0.kind || '')
|
|
6846
|
+
const [sty, setSty] = useState(d0.sty || null)
|
|
6847
|
+
useEffect(() => { if (draft) draft.current = { text, kind, sty } }, [text, kind, sty])
|
|
6848
|
+
useEffect(() => { if (autoFocus && ta.current) ta.current.focus({ preventScroll: true }) }, [])
|
|
6849
|
+
const [pop, setPop] = useState('') // 'plus' | 'style'
|
|
6850
|
+
const ta = useRef(null)
|
|
6851
|
+
const card = useRef(null)
|
|
6852
|
+
useDismiss(pop === 'style', () => setPop(''), card)
|
|
6853
|
+
const en = t('lang') === 'en'
|
|
6854
|
+
const ex = t(ASK_EX[kind] || 'exAuto')
|
|
6855
|
+
const back = () => { if (ta.current) ta.current.focus() }
|
|
6856
|
+
const go = () => {
|
|
6857
|
+
const s = text.trim()
|
|
6858
|
+
if (!s) return back()
|
|
6859
|
+
if (onSend({ text: s, kind, style: sty }) !== false) setText('')
|
|
6860
|
+
}
|
|
6861
|
+
const onKey = (e) => {
|
|
6862
|
+
if (e.nativeEvent && e.nativeEvent.isComposing) return
|
|
6863
|
+
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); go() } else if (e.key === 'Tab' && !e.shiftKey && !text) { e.preventDefault(); setText(ex) }
|
|
6864
|
+
}
|
|
6865
|
+
return h('div', { className: 'ddp-askwrap' },
|
|
6866
|
+
h('div', { className: 'ddp-ask', ref: card },
|
|
6867
|
+
h('textarea', { ref: ta, className: 'ddp-askta', rows: 2, value: text, placeholder: t('askPh', { ex }), 'aria-label': t('emptyHead'), onChange: (e) => setText(e.target.value), onKeyDown: onKey }),
|
|
6868
|
+
h('div', { className: 'row' },
|
|
6869
|
+
h(DropMenu, { t, cls: 'ddp-askplus', tip: [t('askMore'), t('askMoreD')], icon: I.plus, open: pop === 'plus', setOpen: (v) => setPop(v ? 'plus' : ''), items: plus }),
|
|
6870
|
+
h('button', { className: 'chip ref', ...tipA(t('askRef'), t('askRefD')), onClick: onRef }, h(I.image), h('span', null, t('askRef'))),
|
|
6871
|
+
h('button', { className: 'chip' + (sty ? ' on' : ''), ...tipA(sty ? t('askStyle', { name: en ? sty.en : sty.zh }) : t('askStyleAuto'), t('askStyleD')), 'aria-haspopup': 'dialog', 'aria-expanded': pop === 'style', onClick: () => setPop(pop === 'style' ? '' : 'style') },
|
|
6872
|
+
sty ? h('i', { className: 'sw', style: { background: `linear-gradient(135deg,${sty.c.primary} 0 50%,${sty.c.accent} 50%)` } }) : h(I.palette),
|
|
6873
|
+
h('span', null, sty ? t('askStyle', { name: en ? sty.en : sty.zh }) : t('askStyleAuto')), h(I.caret)),
|
|
6874
|
+
!text ? h('button', { className: 'tabhint', tabIndex: -1, onClick: () => { setText(ex); back() } }, h('kbd', null, 'Tab'), t('askTab')) : null,
|
|
6875
|
+
h('button', { className: 'send', disabled: !text.trim(), ...tipA(t('askSend'), '', '↵'), onClick: go }, h(I.send))),
|
|
6876
|
+
pop === 'style' ? h('div', { className: 'ddp-askpop', role: 'dialog', 'aria-label': t('askStylePick') },
|
|
6877
|
+
h('div', { className: 'hd' }, h('b', null, t('askStylePick')), h('button', { className: 'lk', onClick: () => { setSty(null); setPop(''); back() } }, t('askStyleAutoBtn'))),
|
|
6878
|
+
h(StyleCards, { t, onPick: (x) => { setSty(x); setPop(''); back() } })) : null),
|
|
6879
|
+
note ? h('p', { className: 'ddp-asknote' }, h(I.info), h('span', null, note)) : null,
|
|
6880
|
+
h('div', { className: 'ddp-askkinds', role: 'radiogroup', 'aria-label': t('kind') }, ...ASK_KINDS.map((k) => h('button', { key: k, role: 'radio', 'aria-checked': kind === k, className: kind === k ? 'on' : '', onClick: () => setKind(kind === k ? '' : k) },
|
|
6881
|
+
h(I[k]), h('span', null, t(KIND_KEY[k]))))))
|
|
6882
|
+
}
|
|
6883
|
+
|
|
6884
|
+
// 首页「接着做」:最近的三套(找不到文件夹的不放),点了直接打开
|
|
6885
|
+
function HomeWorks({ t, onOpen, onAll, cur, onBack }) {
|
|
6886
|
+
const norm = (x) => String(x || '').replace(/[\\/]+$/, '')
|
|
6887
|
+
const [list, setList] = useState(null)
|
|
6888
|
+
useEffect(() => {
|
|
6889
|
+
let dead = false
|
|
6890
|
+
getJson('works').then((d) => { if (!dead) setList(d.works || []) }, () => { if (!dead) setList([]) })
|
|
6891
|
+
return () => { dead = true }
|
|
6892
|
+
}, [])
|
|
6893
|
+
const ws = (list || []).filter((w) => w.exists).slice(0, 3)
|
|
6894
|
+
if (!ws.length) return null
|
|
6895
|
+
return h('section', { className: 'ddp-homesec' },
|
|
6896
|
+
h('div', { className: 'hd' }, h('b', null, t('homeRecent')), h('button', { className: 'lk', onClick: onAll }, t('homeRecentAll'))),
|
|
6897
|
+
h('div', { className: 'ddp-recent' }, ...ws.map((w) => { const here = !!cur && norm(w.dir) === norm(cur); return h('button', { key: w.id, className: 'wk' + (here ? ' here' : ''), title: w.dir, 'aria-current': here ? 'true' : undefined, onClick: () => (here && onBack ? onBack() : onOpen(w)) },
|
|
6898
|
+
here ? h('em', { className: 'now' }, t('wkHere')) : null,
|
|
6899
|
+
h('span', { className: 'im' }, w.thumb ? h('img', { src: `${API}workthumb?id=${w.id}&v=${w.thumb}`, alt: '', loading: 'lazy', onError: (e) => { e.currentTarget.style.visibility = 'hidden' } }) : null, h(I[w.kind] || I.web)),
|
|
6900
|
+
h('span', { className: 'tx' }, h('b', null, w.title), h('span', null, [t(KIND_KEY[w.kind] || 'kindWeb'), t('wkScreens', { n: w.screens }), agoText(t, w.updated)].join(' · ')))) })))
|
|
6901
|
+
}
|
|
6902
|
+
|
|
5800
6903
|
function TplStart({ t, onOpen, onMore }) {
|
|
5801
6904
|
const v = useMarketView()
|
|
5802
|
-
const list = v && v.designs ? [...v.designs].sort((a, b) => (b.featured - a.featured) || (
|
|
6905
|
+
const list = v && v.designs ? [...v.designs].sort((a, b) => (b.featured - a.featured) || String(a.id).localeCompare(String(b.id))).slice(0, 6) : [] // 按名字排:离线目录换成线上目录时不跳
|
|
5803
6906
|
if (!list.length) return null
|
|
5804
|
-
return h('
|
|
6907
|
+
return h('section', { className: 'ddp-homesec ddp-tplstart' },
|
|
5805
6908
|
h('div', { className: 'hd' }, h('b', null, t('tplStartHead')), h('button', { className: 'lk', onClick: onMore }, t('tplStartMore'))),
|
|
5806
6909
|
h('div', { className: 'row' }, ...list.map((e) => h('button', { key: e.id, className: 'tp', title: mkPick(t, e.description) || mkPick(t, e.title), onClick: () => onOpen(e.id) },
|
|
5807
6910
|
h('span', { className: 'im' }, e.thumb ? h('img', { src: `${API}mthumb?id=${encodeURIComponent(e.id)}&r=${encodeURIComponent(e.rev || '')}`, alt: '', loading: 'lazy' }) : h(I[e.kind] || I.web)),
|
|
5808
|
-
h('span', { className: '
|
|
6911
|
+
h('span', { className: 'tx' }, h('b', null, mkPick(t, e.title)), h('span', null, [t(KIND_KEY[e.kind] || 'kindWeb'), e.screens && e.screens.length ? t('wkScreens', { n: e.screens.length }) : ''].filter(Boolean).join(' · ')))))))
|
|
5809
6912
|
}
|
|
5810
6913
|
|
|
5811
|
-
function MarketTab({ t,
|
|
6914
|
+
function MarketTab({ t, cur, setCur, onUseTpl, onUseStyle, onSay }) {
|
|
5812
6915
|
const [d, setD] = useState(null)
|
|
5813
6916
|
const [err, setErr] = useState('')
|
|
5814
6917
|
const [typePick, setType] = useState('template')
|
|
5815
6918
|
const [kind, setKind] = useState('')
|
|
5816
6919
|
const [q, setQ] = useState('')
|
|
5817
6920
|
const [sort, setSort] = useState('featured')
|
|
5818
|
-
const [cur, setCur] = useState(focus || '')
|
|
5819
6921
|
const [busy, setBusy] = useState(false)
|
|
5820
6922
|
const mine = useInspire() // 收藏过的风格打勾(按市场 id 认)
|
|
5821
|
-
useEffect(() => { if (focus) setCur(focus) }, [focus])
|
|
5822
6923
|
const load = (refresh) => {
|
|
5823
6924
|
if (refresh) setBusy(true)
|
|
5824
6925
|
return loadMarket(refresh).then((x) => { setD(x); setErr('') }, (e) => setErr(e.message)).finally(() => setBusy(false))
|
|
@@ -5858,27 +6959,25 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5858
6959
|
.sort((a, b) => (sort === 'featured' ? (b.featured - a.featured) || time(b.updated) - time(a.updated) : sort === 'new' ? time(b.added) - time(a.added) : time(b.updated) - time(a.updated)))
|
|
5859
6960
|
const kinds = MK_KINDS.filter((k) => d.designs.some((e) => e.kind === k))
|
|
5860
6961
|
const card = (e) => {
|
|
5861
|
-
const by = e.author && (e.author.name || e.author.login
|
|
5862
|
-
const meta = [e.type === 'template' ? t(KIND_KEY[e.kind] || 'kindWeb') : '', e.screens && e.screens.length ? t('wkScreens', { n: e.screens.length }) : '', by ? '@' + by : ''].filter(Boolean).join(' · ')
|
|
6962
|
+
const by = e.author && !/^orderg-x$/i.test(e.author.login || '') ? e.author.name || e.author.login : '' // 官方做的不写作者(每张都一样)
|
|
6963
|
+
const meta = [e.type === 'template' ? t(KIND_KEY[e.kind] || 'kindWeb') : '', e.screens && e.screens.length ? t('wkScreens', { n: e.screens.length }) : '', t(e.license === 'CC-BY-4.0' ? 'mkUseBY' : 'mkUse0'), by ? '@' + by : ''].filter(Boolean).join(' · ')
|
|
5863
6964
|
return h('div', { key: e.id, className: 'ddp-work ddp-mk' },
|
|
5864
6965
|
h('button', { className: 'th', title: mkPick(t, e.description) || mkPick(t, e.title), onClick: () => setCur(e.id) },
|
|
5865
6966
|
e.thumb ? h('img', { src: `${API}mthumb?id=${encodeURIComponent(e.id)}&r=${encodeURIComponent(e.rev || '')}`, alt: '', loading: 'lazy', onError: (ev) => { ev.currentTarget.style.visibility = 'hidden' } }) : null,
|
|
5866
6967
|
h('span', { className: 'ic' }, e.palette && Object.keys(e.palette).length ? h('span', { className: 'pal' }, ...Object.values(e.palette).slice(0, 4).map((c, i) => h('i', { key: i, style: { background: c } }))) : h(I[e.kind] || I.web)),
|
|
5867
|
-
e.featured ? h('em', null, t('mkSortFeatured')) : null),
|
|
5868
|
-
h('div', { className: 'nm' }, h('b', { title: mkPick(t, e.title) }, mkPick(t, e.title)),
|
|
5869
|
-
|
|
5870
|
-
h('span', { className: 'bdg' }, e.license === 'CC-BY-4.0' ? 'CC BY' : 'CC0')),
|
|
5871
|
-
h('div', { className: 'mt' }, meta))
|
|
6968
|
+
e.featured && sort !== 'featured' ? h('em', null, t('mkSortFeatured')) : null), // 按精选排时精选的本来就在前面,不再每张标
|
|
6969
|
+
h('div', { className: 'nm' }, h('b', { title: mkPick(t, e.title) }, mkPick(t, e.title))),
|
|
6970
|
+
h('div', { className: 'mt', title: meta }, meta))
|
|
5872
6971
|
}
|
|
5873
6972
|
return h('div', { className: 'ddp-market' },
|
|
5874
6973
|
h('div', { className: 'ddp-mkbar' },
|
|
5875
|
-
types.length > 1 ? h('div', { className: '
|
|
6974
|
+
types.length > 1 ? h('div', { className: 'tabs', role: 'tablist' }, ...types.map(([k, label, n]) => h('button', { key: k, role: 'tab', 'aria-selected': type === k, className: type === k ? 'on' : '', onClick: () => { setType(k); setKind('') } }, label, h('em', null, n)))) : null,
|
|
5876
6975
|
h('input', { className: 'q', type: 'search', value: q, placeholder: t('mkSearch'), 'aria-label': t('mkSearch'), onChange: (e) => setQ(e.target.value) }),
|
|
5877
|
-
h('
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
h('button', { className: kind === '' ? 'on' : '', onClick: () => setKind('') }, t('mkAllKinds')),
|
|
5881
|
-
|
|
6976
|
+
type !== 'style' ? h('div', { className: 'sort', role: 'group', 'aria-label': t('mkSortT') }, ...[['featured', 'mkSortFeatured'], ['new', 'mkSortNew'], ['updated', 'mkSortUpdated']].map(([k, label]) => h('button', { key: k, className: sort === k ? 'on' : '', 'aria-pressed': sort === k, onClick: () => setSort(k) }, t(label)))) : null),
|
|
6977
|
+
// 类型一排横着滑;设计系统、风格不分类型:灰着,不收起来(下面的卡片不跳)
|
|
6978
|
+
kinds.length > 1 ? h('div', { className: 'ddp-mkkinds' },
|
|
6979
|
+
kinds.length > 1 ? [h('button', { key: '', className: kind === '' && type === 'template' ? 'on' : '', disabled: type !== 'template', onClick: () => setKind('') }, t('mkAllKinds')),
|
|
6980
|
+
...kinds.map((k) => h('button', { key: k, className: kind === k && type === 'template' ? 'on' : '', disabled: type !== 'template', onClick: () => setKind(k) }, t(KIND_KEY[k])))] : null) : null,
|
|
5882
6981
|
d.off || d.error || d.needUpdate ? h('p', { className: 'ddp-libtip warn' }, d.needUpdate ? t('mkNeedUpdate') : d.off ? t('mkOff') : t('mkOffline')) : null,
|
|
5883
6982
|
!list.length ? h('div', { className: 'ddp-libnone' }, t('mkNoResult'))
|
|
5884
6983
|
: type === 'style'
|
|
@@ -5947,7 +7046,6 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5947
7046
|
: shots && shots.length ? h('img', { key: shots[i], src: `${API}mshot?id=${encodeURIComponent(e.id)}&f=${encodeURIComponent(shots[i])}&r=${encodeURIComponent(e.rev || '')}`, alt: (screens[i] && mkPick(t, screens[i].title)) || '' })
|
|
5948
7047
|
: e.thumb ? h('img', { src: `${API}mthumb?id=${encodeURIComponent(e.id)}&r=${encodeURIComponent(e.rev || '')}`, alt: '' }) : null
|
|
5949
7048
|
return h('div', { className: 'ddp-mkd' },
|
|
5950
|
-
h('button', { className: 'lk back', onClick: onBack }, '← ', t('mkBack')),
|
|
5951
7049
|
h('h3', null, mkPick(t, e.title)),
|
|
5952
7050
|
h('p', { className: 'mt' }, [isTpl ? t(KIND_KEY[e.kind] || 'kindWeb') : t('mkTypeSys'), screens.length ? t('wkScreens', { n: screens.length }) : '', by ? t('mkBy', { who: by }) : '', e.ai && e.ai.generated ? t('mkAi') : ''].filter(Boolean).join(' · ')),
|
|
5953
7051
|
mkPick(t, e.description) ? h('p', null, mkPick(t, e.description)) : null,
|
|
@@ -5967,18 +7065,26 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5967
7065
|
e.derivedFrom ? h('li', null, t('mkDerived', { x: String(e.derivedFrom) })) : null),
|
|
5968
7066
|
h('p', { className: 'ddp-libfoot' },
|
|
5969
7067
|
h('a', { href: mkUrl(e), target: '_blank', rel: 'noopener noreferrer' }, t('mkSource')), ' · ',
|
|
5970
|
-
h('a', { href: report, target: '_blank', rel: 'noopener noreferrer' }, t('mkReport'))
|
|
7068
|
+
h('a', { href: report, target: '_blank', rel: 'noopener noreferrer', title: e.id }, t('mkReport'))))
|
|
5971
7069
|
}
|
|
5972
7070
|
|
|
5973
|
-
function LibraryView({ t, tab, setTab, focus, hasDesign, curRoot, onClose, onOpen, onPack, onShare, onSaveStyle, onUseStyle, onUseTpl, onRef, onExtract, onMarket, onSay }) {
|
|
7071
|
+
function LibraryView({ t, tab, setTab, focus, hasDesign, homey, curRoot, onClose, onOpen, onPack, onShare, onSaveStyle, onUseStyle, onUseTpl, onRef, onExtract, onMarket, onSay }) {
|
|
5974
7072
|
const tabs = [['market', t('libMarket')], ['works', t('libWorks')], ['inspire', t('libInspire')]]
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
7073
|
+
const [mkCur, setMkCur] = useState(focus || '') // 市场里点开的那一套(详情页)
|
|
7074
|
+
useEffect(() => { if (focus) setMkCur(focus) }, [focus])
|
|
7075
|
+
const inDetail = tab === 'market' && !!mkCur
|
|
7076
|
+
// 只有一个返回:在详情页回列表,不然回画布 / 空白页
|
|
7077
|
+
const back = () => (inDetail ? setMkCur('') : onClose())
|
|
7078
|
+
const backTip = inDetail ? t('mkBack') : t(hasDesign ? 'libBack' : 'libClose')
|
|
7079
|
+
return h('div', { className: 'ddp-lib', onPointerDown: (e) => e.stopPropagation(), onKeyDown: (e) => { if (e.key === 'Escape') { e.stopPropagation(); back() } } },
|
|
7080
|
+
homey
|
|
7081
|
+
? h(HomeBar, { t, tab, onTab: (k) => { if (!k) onClose(); else { setTab(k); if (k === 'market' && tab === 'market') setMkCur('') } }, lead: inDetail ? h('button', { className: 'ddp-btn', title: backTip, 'aria-label': backTip, onClick: back }, h(I.back)) : null })
|
|
7082
|
+
: h('header', null,
|
|
7083
|
+
h('button', { className: 'ddp-btn', title: backTip, 'aria-label': backTip, onClick: back }, h(I.back)),
|
|
7084
|
+
h('b', null, t('library')),
|
|
7085
|
+
h('div', { className: 'seg', role: 'tablist' }, ...tabs.map(([k, label]) => h('button', { key: k, role: 'tab', 'aria-selected': tab === k, className: tab === k ? 'on' : '', onClick: () => { setTab(k); if (k === 'market' && tab === 'market') setMkCur('') } }, label)))),
|
|
5980
7086
|
h('div', { className: 'bd' },
|
|
5981
|
-
tab === 'market' ? h(MarketTab, { t,
|
|
7087
|
+
tab === 'market' ? h(MarketTab, { t, cur: mkCur, setCur: setMkCur, onUseTpl, onUseStyle, onSay })
|
|
5982
7088
|
: tab === 'works' ? h(WorksTab, { t, curRoot, onOpen, onPack, onShare, onMarket, onSay })
|
|
5983
7089
|
: h(InspireTab, { t, hasDesign, onSaveStyle, onUseStyle, onRef, onExtract, onMarket, onSay })))
|
|
5984
7090
|
}
|
|
@@ -6031,7 +7137,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6031
7137
|
const SHARE_AGREE = [['own', 'shAgreeOwn'], ['brand', 'shAgreeBrand'], ['private', 'shAgreePrivate'], ['rules', 'shAgreeRules']]
|
|
6032
7138
|
|
|
6033
7139
|
/** 分享到设计市场:填名字、介绍、授权,勾四个确认 → 按分享的规矩查(个人信息硬拦)→ 打 zip → 打开预填好的 GitHub 投稿页 */
|
|
6034
|
-
function SharePanel({ t, sid, root, onFix, onSay, onClose }) {
|
|
7140
|
+
function SharePanel({ t, sid, root, brand, onFix, onSay, onClose }) {
|
|
6035
7141
|
const L = t('lang') === 'en' ? 'en' : 'zh'
|
|
6036
7142
|
const [info, setInfo] = useState(null)
|
|
6037
7143
|
const [title, setTitle] = useState('')
|
|
@@ -6048,7 +7154,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6048
7154
|
post('pack', { session: sid, dir: root, info: true, lang: L }).then((d) => {
|
|
6049
7155
|
if (!on) return
|
|
6050
7156
|
setInfo(d)
|
|
6051
|
-
setTitle(d.title || '')
|
|
7157
|
+
setTitle(d.title && d.title !== baseName(root) ? d.title : brand || d.title || '')
|
|
6052
7158
|
setDesc(d.description || '')
|
|
6053
7159
|
setLic(d.license === 'CC-BY-4.0' ? 'CC-BY-4.0' : 'CC0-1.0')
|
|
6054
7160
|
}, (e) => { if (on) setInfo({ error: e.message }) })
|
|
@@ -6069,6 +7175,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6069
7175
|
const done = res && res.ok && res.share
|
|
6070
7176
|
return h('div', { className: 'ddp-tokens ddp-pack ddp-share', onPointerDown: (e) => e.stopPropagation() },
|
|
6071
7177
|
h('header', null, h(I.share), h('b', null, t('shTitle')), h('button', { className: 'ddp-btn', title: '×', onClick: onClose }, h(I.close))),
|
|
7178
|
+
h('p', { className: 'tip gh' }, h(I.info), h('span', null, t('shGh'))),
|
|
6072
7179
|
h('p', { className: 'tip' }, t('shTip')),
|
|
6073
7180
|
h('div', { className: 'bd' },
|
|
6074
7181
|
root ? h('p', { className: 'which', title: root }, h(I.folder), h('span', null, baseName(root))) : h('div', { className: 'none' }, t('pkNoDesign')),
|
|
@@ -6125,6 +7232,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6125
7232
|
const [cam, setCam] = useState(null)
|
|
6126
7233
|
const [anim, setAnim] = useState(false)
|
|
6127
7234
|
const [drag, setDrag] = useState(false)
|
|
7235
|
+
const [zoomMenu, setZoomMenu] = useState(false)
|
|
6128
7236
|
const auto = useRef(true)
|
|
6129
7237
|
const camRef = useRef(cam)
|
|
6130
7238
|
camRef.current = cam
|
|
@@ -6135,8 +7243,15 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6135
7243
|
// 多尺寸:每块自己的类型和大小(items[i] = { kind, size })
|
|
6136
7244
|
const items = props.items || null
|
|
6137
7245
|
const boxes = items ? items.map((it) => deviceBox(it.kind, it.size)) : null
|
|
6138
|
-
|
|
6139
|
-
const
|
|
7246
|
+
// 上下各有一排浮块:排的时候只用中间那块高度,镜头整体往下挪 rt
|
|
7247
|
+
const rt = props.top || 0
|
|
7248
|
+
const bh = Math.max(0, box.h - rt - (props.bottom || 0))
|
|
7249
|
+
const L = box.w > 0 && bh > 0 ? (boxes ? layoutRow(boxes, box.w, bh) : layoutGrid(n, dw, dh, box.w, bh)) : null
|
|
7250
|
+
// 右边开着面板(inset 像素被挡着):排法不变,镜头只用左边看得见的那块(太窄就不让,照旧整块)
|
|
7251
|
+
const ins = props.inset && box.w - props.inset >= 240 ? props.inset : 0
|
|
7252
|
+
const vw = box.w - ins
|
|
7253
|
+
const layoutKey = L ? [n, L.cols, dw, dh, box.w, box.h, rt, bh, ins, boxes ? boxes.join(';') : ''].join('|') : ''
|
|
7254
|
+
const fitAt = () => { const c = fitCam(L, vw, bh); return { ...c, y: c.y + rt } }
|
|
6140
7255
|
|
|
6141
7256
|
useEffect(() => {
|
|
6142
7257
|
const el = ref.current
|
|
@@ -6154,16 +7269,16 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6154
7269
|
if (!L) return
|
|
6155
7270
|
if (auto.current || !camRef.current) {
|
|
6156
7271
|
setAnim(!!camRef.current)
|
|
6157
|
-
setCam(
|
|
6158
|
-
}
|
|
7272
|
+
setCam(fitAt())
|
|
7273
|
+
} else if (ins && props.sel) showKey(props.sel) // 自己挪过镜头:只把选中的那屏挪出来,别被面板挡着
|
|
6159
7274
|
}, [layoutKey])
|
|
6160
7275
|
|
|
6161
7276
|
// 让某一屏露出来(体检里点了一条):整屏不在看得见的地方就把镜头挪过去;右边 inset 像素被面板挡着
|
|
6162
7277
|
const rv = props.reveal
|
|
6163
|
-
|
|
7278
|
+
function showKey(key) {
|
|
6164
7279
|
const c = camRef.current
|
|
6165
|
-
if (!
|
|
6166
|
-
const i = screens.findIndex((x) => x.key ===
|
|
7280
|
+
if (!key || !L || !c) return
|
|
7281
|
+
const i = screens.findIndex((x) => x.key === key)
|
|
6167
7282
|
if (i < 0) return
|
|
6168
7283
|
const [cw, ch] = boxes ? boxes[i] : [dw, dh]
|
|
6169
7284
|
const vw = Math.max(120, box.w - (props.inset || 0))
|
|
@@ -6174,12 +7289,13 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6174
7289
|
let x = c.x
|
|
6175
7290
|
let y = c.y
|
|
6176
7291
|
if (x0 < 8 || x0 + w > vw - 8) x += (w > vw - 16 ? 8 : (vw - w) / 2) - x0
|
|
6177
|
-
if (y0 < LAB || y0 + hh >
|
|
7292
|
+
if (y0 < rt + LAB || y0 + hh > rt + bh - 8) y += (hh > bh - LAB - 8 ? rt + LAB : rt + (bh - hh) / 2) - y0
|
|
6178
7293
|
if (x === c.x && y === c.y) return
|
|
6179
7294
|
auto.current = false
|
|
6180
7295
|
setAnim(true)
|
|
6181
7296
|
setCam({ ...c, x, y })
|
|
6182
|
-
}
|
|
7297
|
+
}
|
|
7298
|
+
useEffect(() => { if (rv) showKey(rv.key) }, [rv && rv.n])
|
|
6183
7299
|
|
|
6184
7300
|
const zoomAt = (factor, px, py, smooth) => {
|
|
6185
7301
|
const c = camRef.current
|
|
@@ -6199,7 +7315,9 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6199
7315
|
if (!c) return
|
|
6200
7316
|
if (e.ctrlKey || e.metaKey) {
|
|
6201
7317
|
const r = el.getBoundingClientRect()
|
|
6202
|
-
|
|
7318
|
+
// 触控板捏合每次只给几个像素;鼠标滚轮(Windows 上 Ctrl + 滚轮)一格就是 100 上下,不收住一格放大近 3 倍
|
|
7319
|
+
const dy = e.deltaMode === 1 ? e.deltaY * 16 : e.deltaY
|
|
7320
|
+
zoomAt(Math.exp(-Math.max(-25, Math.min(25, dy)) * 0.01), e.clientX - r.left, e.clientY - r.top, false)
|
|
6203
7321
|
} else {
|
|
6204
7322
|
auto.current = false
|
|
6205
7323
|
setAnim(false)
|
|
@@ -6247,7 +7365,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6247
7365
|
if (!L) return
|
|
6248
7366
|
auto.current = true
|
|
6249
7367
|
setAnim(true)
|
|
6250
|
-
setCam(
|
|
7368
|
+
setCam(fitAt())
|
|
6251
7369
|
}
|
|
6252
7370
|
|
|
6253
7371
|
const z = cam ? cam.z : 1
|
|
@@ -6266,7 +7384,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6266
7384
|
className: 'ddp-cell' + (on ? ' sel' : ''),
|
|
6267
7385
|
style: { left: L.pos[i].x, top: L.pos[i].y, width: cw, height: ch },
|
|
6268
7386
|
},
|
|
6269
|
-
lab(sc.key, [h('span', { key: 's' }, labelOf(sc)), !picking && props.badge ? props.badge(sc) : null,
|
|
7387
|
+
lab(sc.key, [props.tag ? props.tag(sc) : null, h('span', { key: 's' }, labelOf(sc)), !picking && props.badge ? props.badge(sc) : null, !picking && props.actions ? h('span', { key: 'a', className: 'ddp-act' }, ...props.actions(sc)) : null], cw),
|
|
6270
7388
|
props.render(sc, z, i),
|
|
6271
7389
|
picking ? null : h('div', { className: 'ddp-shield', 'data-key': sc.key, title: items ? '' : t('openScreen'), style: { borderRadius: fr.r } })))
|
|
6272
7390
|
})
|
|
@@ -6274,7 +7392,9 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6274
7392
|
const p = L.pos[n - 1]
|
|
6275
7393
|
cells.push(h('div', { key: '__ghost', className: 'ddp-cell ghost', style: { left: p.x, top: p.y, width: dw, height: dh } },
|
|
6276
7394
|
lab('', [h('i', { key: 'i', className: 'sp' }), h('span', { key: 's' }, t('generating'))]),
|
|
6277
|
-
h(Device, { kind, w: size[0], h: size[1], label: '' },
|
|
7395
|
+
h(Device, { kind, w: size[0], h: size[1], label: '' },
|
|
7396
|
+
h(GhostDraw, { key: 'gd', t, kind, pulse: props.pulse || 0 }),
|
|
7397
|
+
props.draft ? h(DraftFrame, { key: 'df', url: props.draft, z, lang: t('lang'), onEnd: props.onDraftEnd }) : null)))
|
|
6278
7398
|
}
|
|
6279
7399
|
}
|
|
6280
7400
|
let flowSvg = null
|
|
@@ -6296,11 +7416,15 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6296
7416
|
className: 'ddp-world' + (anim ? ' anim' : ''),
|
|
6297
7417
|
style: { transform: `translate(${cam.x}px, ${cam.y}px) scale(${z})`, '--z': z },
|
|
6298
7418
|
}, ...cells, flowSvg) : null,
|
|
7419
|
+
// 右下缩放:「43% ▾」,点开是放大 / 缩小 / 适合画布 / 实际大小
|
|
6299
7420
|
h('div', { className: 'ddp-zoom' },
|
|
6300
|
-
h(
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
7421
|
+
h(DropMenu, { t, tip: [t('zoomT'), t('dZoom', { mod: MOD.replace(/\+$/, '') })], label: Math.round(z * 100) + '%', caret: true, open: zoomMenu, setOpen: setZoomMenu, items: [
|
|
7422
|
+
['', I.plus, t('zoomIn'), false, z >= ZMAX, () => center(1.25)],
|
|
7423
|
+
['', I.minus, t('zoomOut'), false, z <= ZMIN, () => center(1 / 1.25)],
|
|
7424
|
+
'sep',
|
|
7425
|
+
['', I.fit, t('fit'), false, false, fit],
|
|
7426
|
+
['', I.single, t('zoomReal'), Math.abs(z - 1) < 0.005, false, () => center(1 / z)],
|
|
7427
|
+
] })))
|
|
6304
7428
|
}
|
|
6305
7429
|
|
|
6306
7430
|
// ── 右侧栏标签页 ─────────────────────────────────────────────────────────
|
|
@@ -6317,7 +7441,6 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6317
7441
|
const [styleEdit, setStyleEdit] = useState(false) // 调样式:点元素、拖滑块,写回它的 style 属性
|
|
6318
7442
|
const [sty, setSty] = useState(null) // 调样式选中的元素:{ key, url, name, selector, text, loc, base, vars, gradient, layout, live }
|
|
6319
7443
|
const [editing, setEditing] = useState(false)
|
|
6320
|
-
const [menu, setMenu] = useState(false)
|
|
6321
7444
|
const [toast, setToast] = useState('')
|
|
6322
7445
|
const [flash, setFlash] = useState('')
|
|
6323
7446
|
const [reloadSeq, setReloadSeq] = useState(0)
|
|
@@ -6326,14 +7449,19 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6326
7449
|
const setTop = (k, dk) => setTops((cur) => (cur[k] === dk ? cur : { ...cur, [k]: dk }))
|
|
6327
7450
|
const [sel, setSel] = useState('')
|
|
6328
7451
|
const [panel, setPanel] = useState(false)
|
|
6329
|
-
const [
|
|
7452
|
+
const [lookTab, setLookTab] = useState('set') // 外观面板开在哪一页:set 整套 / style 换风格 / brand 品牌
|
|
7453
|
+
const [tv, setTv] = useState({}) // 外观面板里直接改的设计变量:名字 → 值(还没写进文件)
|
|
6330
7454
|
const [dropping, setDropping] = useState('') // 正拖着文件进来:dragKind 给的种类
|
|
6331
7455
|
const [dropSoft, setDropSoft] = useState(false) // 拖动在预览页面里:蒙层只是提示、不挡事件(松手由页面转告)
|
|
6332
7456
|
const softTimer = useRef(0)
|
|
6333
7457
|
const refInput = useRef(null)
|
|
6334
7458
|
const pageInput = useRef(null)
|
|
7459
|
+
const homeRef = useRef(null)
|
|
7460
|
+
const homeKind = useRef('') // 首页发出去时选的类型:第一屏出来前的占位按它画
|
|
6335
7461
|
const [exMenu, setExMenu] = useState(false)
|
|
6336
|
-
const [more, setMore] = useState(false) //
|
|
7462
|
+
const [more, setMore] = useState(false) // ⋯ 菜单(窄的时候收起来的几个按钮也在这里)
|
|
7463
|
+
const [scrMenu, setScrMenu] = useState(false)
|
|
7464
|
+
const brandMem = useRef({}) // 目录 → 产品名(画布上认出来的),单页时去掉标题里的它
|
|
6337
7465
|
const [hist, setHist] = useState(false) // 版本面板
|
|
6338
7466
|
const [versions, setVersions] = useState([])
|
|
6339
7467
|
const [ver, setVer] = useState(null) // 正在看的旧版本
|
|
@@ -6349,6 +7477,27 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6349
7477
|
const [demo, setDemo] = useState(null) // 演示:{ stack: [屏…], layers: [{ id, key, dir, shown }] }
|
|
6350
7478
|
const [pane, setPane] = useState('') // 右边的面板:tweaks 调节滑块、systems 品牌设计系统、handoff 转成代码、pack 打成 zip、zip 设计包没过检查
|
|
6351
7479
|
const [lib, setLib] = useState('') // 设计库:'' 关着;开着时是哪一栏(market / works / inspire)
|
|
7480
|
+
// 开着设计时叠上来的首页:'' 没开 / 'on' 开着 / 'off' 正在收起(放完收起动画再拿掉)
|
|
7481
|
+
const [homeAt, setHomeAt] = useState('')
|
|
7482
|
+
const homeDraft = useRef({ text: '', kind: '', sty: null }) // 首页输入框里写了一半的:收起再打开还在
|
|
7483
|
+
const homeOpen = () => { setLib(''); setLibFocus(''); setHomeAt('on') }
|
|
7484
|
+
const homeClose = () => {
|
|
7485
|
+
setHomeAt((c) => (c === 'on' ? 'off' : c))
|
|
7486
|
+
setTimeout(() => setHomeAt((c) => (c === 'off' ? '' : c)), reduced() ? 0 : 190)
|
|
7487
|
+
}
|
|
7488
|
+
const homeAway = () => { if (homeAt === 'on') homeClose() } // 从首页打开了设计、进了打包面板:首页收起,别挡着
|
|
7489
|
+
// 首页 / 设计库叠在上面时,Esc 只归它们:底下的调样式、改字、点选、试用别跟着退(没存的改动会丢)
|
|
7490
|
+
const overlayUp = () => { try { return !!document.querySelector('.ddp-home.over, .ddp-libwrap') } catch { return false } }
|
|
7491
|
+
useEffect(() => {
|
|
7492
|
+
if (homeAt !== 'on') return undefined
|
|
7493
|
+
const esc = (e) => {
|
|
7494
|
+
if (e.key !== 'Escape' || e.defaultPrevented) return
|
|
7495
|
+
if (document.querySelector('.ddp-home.over [aria-expanded="true"], .ddp-libwrap')) return // 先收菜单、设计库
|
|
7496
|
+
homeClose()
|
|
7497
|
+
}
|
|
7498
|
+
window.addEventListener('keydown', esc)
|
|
7499
|
+
return () => window.removeEventListener('keydown', esc)
|
|
7500
|
+
}, [homeAt])
|
|
6352
7501
|
const [libFocus, setLibFocus] = useState('') // 从空白页「从模板开始」点进来的那一套
|
|
6353
7502
|
const libDefault = () => (marketStore.has ? 'market' : 'works')
|
|
6354
7503
|
// 市场还没读过(刚开页面):等读完(本地缓存,很快)再定开哪一页
|
|
@@ -6387,6 +7536,10 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6387
7536
|
// 这一轮开始时已有的屏记下来:这轮里出了新的一屏,就在画布末尾留个「生成中…」等下一屏;
|
|
6388
7537
|
// 一阵子(GHOST_MS)没再出新屏多半是在改了,占位撤掉;正在写一个还没有的 HTML 时一定留着
|
|
6389
7538
|
const marks = useRef({ site: '', born: new Map(), built: new Set(), turnKeys: null, lastNew: 0 })
|
|
7539
|
+
const draftedR = useRef(new Map()) // 边写边看过的屏 → 什么时候:真文件出来时不再播一遍生成动画
|
|
7540
|
+
const pulseR = useRef({ key: '', lines: 0, n: 0 }) // 模型真有动静就加一(占位里的鼠标按它动)
|
|
7541
|
+
const singleDraftR = useRef('') // 单屏里正在看着写的那屏:写完换过去看它
|
|
7542
|
+
const playR = useRef({ url: '', key: '', hold: 0, end: false, meta: '', lump: false }) // 在放的草稿(见下面 dr)
|
|
6390
7543
|
const M = marks.current
|
|
6391
7544
|
if (M.site !== site) {
|
|
6392
7545
|
M.site = site
|
|
@@ -6402,13 +7555,27 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6402
7555
|
M.born.set(sc.key, busy)
|
|
6403
7556
|
if (busy) M.lastNew = Date.now()
|
|
6404
7557
|
}
|
|
6405
|
-
const isBorn = (key) => !!M.born.get(key) && !M.built.has(key)
|
|
7558
|
+
const isBorn = (key) => !!M.born.get(key) && !M.built.has(key) && !(Date.now() - (draftedR.current.get(key) || 0) < 180000)
|
|
7559
|
+
// 状态条从哪一刻开始计时:服务端说这一轮开始多久了(右侧栏中途才打开也对得上)
|
|
7560
|
+
const turnT = useRef({ on: false, at: 0 })
|
|
7561
|
+
if (busy && !turnT.current.on) turnT.current = { on: true, at: Date.now() - Math.max(0, Number(st && st.turnFor) || 0) }
|
|
7562
|
+
else if (!busy && turnT.current.on) turnT.current = { on: false, at: 0 }
|
|
7563
|
+
// 开始/结束生成时单屏的设备要让位(给状态条腾一条):这一下缓过去,别一跳
|
|
7564
|
+
const [ease, setEase] = useState(false)
|
|
7565
|
+
const busySeen = useRef(busy)
|
|
7566
|
+
useEffect(() => {
|
|
7567
|
+
if (busySeen.current === busy) return
|
|
7568
|
+
busySeen.current = busy
|
|
7569
|
+
setEase(true)
|
|
7570
|
+
const id = setTimeout(() => setEase(false), 520)
|
|
7571
|
+
return () => clearTimeout(id)
|
|
7572
|
+
}, [busy])
|
|
6406
7573
|
|
|
6407
7574
|
const infoOf = (key) => info[site + '|' + key] || null
|
|
6408
7575
|
const ik = (sc) => (sc.v ? 'v' + sc.v + '|' : '') + sc.key // 旧版本的屏单独记标题
|
|
6409
7576
|
const focus = (st && st.focus) || ''
|
|
6410
7577
|
const main = infoOf(focus) || (screens[0] && infoOf(screens[0].key)) || {}
|
|
6411
|
-
const meta = parseMeta(main.meta || '')
|
|
7578
|
+
const meta = parseMeta(main.meta || (st && st.draft && st.draft.meta) || (playR.current.end ? '' : playR.current.meta) || '') // 第一屏还在放草稿(真的没摆出来):按草稿开头写的类型
|
|
6412
7579
|
const tokens = main.tokens || []
|
|
6413
7580
|
const kind = (st && st.kind) || meta.kind || 'web'
|
|
6414
7581
|
const size = sizeOf((st && st.size) || meta.size, kind)
|
|
@@ -6416,14 +7583,68 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6416
7583
|
const canvas = view === 'canvas'
|
|
6417
7584
|
const writingNew = !!(st && st.writing) && /\.html?$/i.test(st.writing) && !screens.some((s) => s.key === st.writing)
|
|
6418
7585
|
const fresh = !!M.turnKeys && screens.some((s) => !M.turnKeys.has(s.key)) && Date.now() - M.lastNew < GHOST_MS
|
|
6419
|
-
|
|
7586
|
+
// 边写边看:模型正在写一屏新的,预览服务器上已经有它的草稿(远程看的时候本机地址打不开,还是放占位)。
|
|
7587
|
+
// 正文先到的草稿 key 是空的(还不知道是哪屏),名字到了才填上;写完落地后草稿还留一会儿,靠它认出新屏
|
|
7588
|
+
const sd = st && st.draft && st.draft.url && (busy || st.draft.live) && !away(st.draft.url) ? st.draft : null // 说完了还报的:刚写完的那份
|
|
7589
|
+
// 看着放的那份草稿:整页一下子到的(lump,预览服务器几秒里一截截放出来),真文件落地了也先放完再换。
|
|
7590
|
+
// 一路流着写的早看过了,落地就换真文件(不等放完:页面引的外部字体卡着的话 load 要等很久)。
|
|
7591
|
+
// 右侧栏头一回看到它时它得是还在写 / 刚写完的(live),中途才打开右侧栏的不补放旧的
|
|
7592
|
+
const PL = playR.current
|
|
7593
|
+
if (sd && sd.url !== PL.url && sd.live) playR.current = { url: sd.url, key: sd.key || '', hold: 0, end: false, meta: sd.meta || '', lump: !!sd.lump }
|
|
7594
|
+
else if (sd && sd.url === PL.url) {
|
|
7595
|
+
if (sd.key) PL.key = sd.key
|
|
7596
|
+
if (sd.meta) PL.meta = sd.meta
|
|
7597
|
+
if (sd.lump) PL.lump = true
|
|
7598
|
+
}
|
|
7599
|
+
const play = playR.current
|
|
7600
|
+
const landed = (key) => !!key && screens.some((x) => x.key === key)
|
|
7601
|
+
const playing = !!play.url && play.lump && !play.end && !(sd && sd.url !== play.url) && (!play.hold || Date.now() - play.hold < HOLD_MAX)
|
|
7602
|
+
const dr = sd && !landed(sd.key) ? sd : playing ? { key: play.key, url: play.url } : null
|
|
7603
|
+
if (dr && dr.url === play.url && !play.hold && (!sd || landed(dr.key))) play.hold = Date.now() // 从这时起是「放完再换」:最多等 HOLD_MAX
|
|
7604
|
+
const hideKey = dr && landed(dr.key) ? dr.key : '' // 放着的这屏已经落地:画布上先不摆真的那块(草稿就在占位那格里放)
|
|
7605
|
+
if (sd && sd.key) draftedR.current.set(sd.key, Date.now())
|
|
7606
|
+
const ghost = canvas && !ver && ((busy && (writingNew || fresh)) || !!dr)
|
|
6420
7607
|
const [, setTick] = useState(0)
|
|
7608
|
+
const draftEnd = (u) => {
|
|
7609
|
+
const p = playR.current
|
|
7610
|
+
if (p.url !== u || p.end) return
|
|
7611
|
+
p.end = true
|
|
7612
|
+
setTick((n) => n + 1)
|
|
7613
|
+
}
|
|
7614
|
+
useEffect(() => {
|
|
7615
|
+
if (!play.hold || play.end) return
|
|
7616
|
+
const id = setTimeout(() => setTick((n) => n + 1), Math.max(0, play.hold + HOLD_MAX - Date.now()) + 50)
|
|
7617
|
+
return () => clearTimeout(id)
|
|
7618
|
+
}, [play.url, play.hold, play.end])
|
|
6421
7619
|
useEffect(() => {
|
|
6422
7620
|
if (!fresh || writingNew) return
|
|
6423
7621
|
const id = setTimeout(() => setTick((n) => n + 1), Math.max(0, M.lastNew + GHOST_MS - Date.now()) + 50)
|
|
6424
7622
|
return () => clearTimeout(id)
|
|
6425
7623
|
}, [fresh, writingNew, M.lastNew])
|
|
6426
7624
|
const labelOf = (sc) => (infoOf(ik(sc)) || {}).title || sc.label
|
|
7625
|
+
// 状态条上那句:在等你回答 / 正在做第几屏 / 正在改哪屏 / 按调的工具说 / 没调工具就是在想
|
|
7626
|
+
const busyPhase = () => {
|
|
7627
|
+
if (!st) return { text: t('working') }
|
|
7628
|
+
if (st.asking) return { text: t('asking'), asking: true }
|
|
7629
|
+
const newN = screens.length + 1 // 按画布上排第几屏说(已有 3 屏、再出一屏 = 第 4 屏),和新屏出来的位置对得上
|
|
7630
|
+
const w = st.writing || ''
|
|
7631
|
+
const ln = Number(st.lines) || 0
|
|
7632
|
+
const more = ln >= 10 ? t('stLines', { n: ln }) : '' // 写了多少行:写大文件的几分钟里看得到在动
|
|
7633
|
+
if (w) {
|
|
7634
|
+
if (!/\.html?$/i.test(w)) return { text: t('writing', { file: w }), sub: more }
|
|
7635
|
+
const sc = screens.find((x) => x.key === w)
|
|
7636
|
+
return sc ? { text: t('stEdit', { name: labelOf(sc) }), sub: more } : { text: t('stNew', { n: newN }), sub: more ? `${w} · ${more}` : w }
|
|
7637
|
+
}
|
|
7638
|
+
if (st.draft && !st.draft.key && /^(write|create_file)$/i.test(String(st.tool || ''))) return { text: t('stNew', { n: newN }), sub: more } // 正文先到:看得出是新的一屏,名字还没到
|
|
7639
|
+
const tool = String(st.tool || '')
|
|
7640
|
+
if (!tool) return { text: t('stThink') }
|
|
7641
|
+
const m = TOOL_PHASE.find(([re]) => re.test(tool))
|
|
7642
|
+
if (m && m[1] === 'stWriteAny' && !url) return { text: t('stNew', { n: 1 }), sub: more } // 第一屏还没出来:写的就是它
|
|
7643
|
+
return { text: m ? t(m[1]) : t('working'), sub: m && m[1] === 'stWriteAny' ? more : '' }
|
|
7644
|
+
}
|
|
7645
|
+
const busyBar = (side) => h(BusyBar, { key: 'busy', t, phase: busyPhase(), since: turnT.current.at || Date.now(), side })
|
|
7646
|
+
// 占位里的鼠标什么时候动:换了在做的事、或者又写了 15 行——模型停着它们就停着
|
|
7647
|
+
const pulse = pulseStep(pulseR.current, busy, busy ? busyPhase().text + '|' + ((st && st.tool) || '') : '', Number(st && st.lines) || 0)
|
|
6427
7648
|
// 画布上的标题去掉几屏都带的产品名(「首页 · 梅林练」→「首页」);产品名留给导出的展示图做大标题
|
|
6428
7649
|
const shortOf = (sc) => {
|
|
6429
7650
|
const sp = splitTitles(shown.map(labelOf))
|
|
@@ -6448,8 +7669,10 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6448
7669
|
return Object.keys(out).length === Object.keys(c).length ? c : out
|
|
6449
7670
|
})
|
|
6450
7671
|
}, [tsig])
|
|
6451
|
-
|
|
6452
|
-
|
|
7672
|
+
const twGen = useRef(0) // 换一次设计加一:晚到的保存回包认得出是上一套的
|
|
7673
|
+
useEffect(() => { twGen.current++; setTw({}); setTv({}) }, [site])
|
|
7674
|
+
// 拖的滑块 + 外观面板里直接改的颜色:一起发给页面先看效果
|
|
7675
|
+
const tweak = Object.keys(tw).length || Object.keys(tv).length ? (() => { const m = tweakMsg(tlist, tw); return { vars: { ...m.vars, ...tv }, attrs: m.attrs } })() : null
|
|
6453
7676
|
|
|
6454
7677
|
// 看旧版本:画布上换成那一版的几屏(按现在的顺序,那一版独有的接在后面)
|
|
6455
7678
|
const vbase = ver ? `${origin}/__dsh_design__/v/${ver.n}/` : ''
|
|
@@ -6490,6 +7713,8 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6490
7713
|
try {
|
|
6491
7714
|
await openTarget(sid, p)
|
|
6492
7715
|
setEditing(false)
|
|
7716
|
+
homeAway() // 从叠着的首页 / 设计库里打开的:收起来,看新打开的
|
|
7717
|
+
setLib('')
|
|
6493
7718
|
} catch (e) {
|
|
6494
7719
|
say(t('openFailed', { msg: e.message }))
|
|
6495
7720
|
}
|
|
@@ -6561,6 +7786,16 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6561
7786
|
if (!patch.focus && poll && poll.state) poll.set({ ...poll.state, ...patch })
|
|
6562
7787
|
post('view', { session: sid, ...patch }).then((s) => poll && poll.set(s), (e) => say(t('openFailed', { msg: e.message })))
|
|
6563
7788
|
}
|
|
7789
|
+
// 单屏里看着写完的那屏:真文件出来了就换过去看它(不然写完一闪又回到原来那屏)
|
|
7790
|
+
useEffect(() => {
|
|
7791
|
+
let k = singleDraftR.current
|
|
7792
|
+
if (k === '*' && sd && sd.key) k = singleDraftR.current = sd.key
|
|
7793
|
+
if (!k || dr) return
|
|
7794
|
+
if (screens.some((x) => x.key === k)) {
|
|
7795
|
+
singleDraftR.current = ''
|
|
7796
|
+
if (!canvas && focus !== k) change({ view: 'single', focus: k })
|
|
7797
|
+
} else if (!busy) singleDraftR.current = ''
|
|
7798
|
+
})
|
|
6564
7799
|
|
|
6565
7800
|
// 量舞台大小,单页时按比例缩放
|
|
6566
7801
|
useEffect(() => {
|
|
@@ -6674,7 +7909,6 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6674
7909
|
setPicking(false)
|
|
6675
7910
|
setTextEdit(false)
|
|
6676
7911
|
setPanel(false)
|
|
6677
|
-
setStyles(false)
|
|
6678
7912
|
setHist(false)
|
|
6679
7913
|
setPhone(false)
|
|
6680
7914
|
closeCheck()
|
|
@@ -6718,7 +7952,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6718
7952
|
}
|
|
6719
7953
|
useEffect(() => {
|
|
6720
7954
|
if (!styleEdit) return undefined
|
|
6721
|
-
const onKey = (e) => { if (e.key === 'Escape') setStyleEdit(false) }
|
|
7955
|
+
const onKey = (e) => { if (e.key === 'Escape' && !overlayUp()) setStyleEdit(false) }
|
|
6722
7956
|
window.addEventListener('keydown', onKey)
|
|
6723
7957
|
return () => window.removeEventListener('keydown', onKey)
|
|
6724
7958
|
}, [styleEdit])
|
|
@@ -6734,14 +7968,14 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6734
7968
|
|
|
6735
7969
|
useEffect(() => {
|
|
6736
7970
|
if (!textEdit) return undefined
|
|
6737
|
-
const onKey = (e) => { if (e.key === 'Escape') setTextEdit(false) }
|
|
7971
|
+
const onKey = (e) => { if (e.key === 'Escape' && !overlayUp()) setTextEdit(false) }
|
|
6738
7972
|
window.addEventListener('keydown', onKey)
|
|
6739
7973
|
return () => window.removeEventListener('keydown', onKey)
|
|
6740
7974
|
}, [textEdit])
|
|
6741
7975
|
|
|
6742
7976
|
useEffect(() => {
|
|
6743
7977
|
if (!picking) return undefined
|
|
6744
|
-
const onKey = (e) => { if (e.key === 'Escape') setPicking(false) }
|
|
7978
|
+
const onKey = (e) => { if (e.key === 'Escape' && !overlayUp()) setPicking(false) }
|
|
6745
7979
|
window.addEventListener('keydown', onKey)
|
|
6746
7980
|
return () => window.removeEventListener('keydown', onKey)
|
|
6747
7981
|
}, [picking])
|
|
@@ -6798,7 +8032,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6798
8032
|
}
|
|
6799
8033
|
useEffect(() => {
|
|
6800
8034
|
if (!demo) return undefined
|
|
6801
|
-
const onKey = (e) => { if (e.key === 'Escape') setDemo(null) }
|
|
8035
|
+
const onKey = (e) => { if (e.key === 'Escape' && !overlayUp()) setDemo(null) }
|
|
6802
8036
|
window.addEventListener('keydown', onKey)
|
|
6803
8037
|
return () => window.removeEventListener('keydown', onKey)
|
|
6804
8038
|
}, [!!demo])
|
|
@@ -6964,15 +8198,21 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6964
8198
|
insert(g.letter ? t('pickText', { name: labelOf(sc), where, others, base: g.stem }) : t('pickKeep', { name: labelOf(sc), where, others }), 'insertedSend')
|
|
6965
8199
|
}
|
|
6966
8200
|
|
|
6967
|
-
//
|
|
8201
|
+
// 同一屏的几个版本:标 A / B / C(原版或 -a 是 A)
|
|
8202
|
+
const takeTag = (sc) => {
|
|
8203
|
+
const g = !sc.v && takesOf(shown.map((x) => x.key), sc.key)
|
|
8204
|
+
return g ? (g.letter || 'a').toUpperCase() : ''
|
|
8205
|
+
}
|
|
8206
|
+
const tagOf = (sc) => { const L = takeTag(sc); return L ? h('b', { key: 'g', className: 'ddp-vt', title: t('takeN', { l: L }) }, L) : null }
|
|
8207
|
+
// 几个方向并排时每屏标题旁都挂着「用这版」,不用先点中那一屏才看得到
|
|
6968
8208
|
const badge = (sc) => !sc.v && takesOf(shown.map((x) => x.key), sc.key)
|
|
6969
8209
|
? h('button', { key: 'p', className: 'ddp-take', title: t('pickThis'), onClick: () => pickDirection(sc) }, h(I.check), h('span', null, t('pickShort')))
|
|
6970
8210
|
: null
|
|
6971
8211
|
|
|
6972
8212
|
const actions = (sc) => [
|
|
6973
|
-
h('button', { key: 'x', className: 'ddp-btn', title: t('exThis'), onClick: () => doExport(FRAMED[kind] ? 'frame' : 'raw', sc.key) }, h(I.download)),
|
|
6974
|
-
sc.v ? null : h('button', { key: 'v', className: 'ddp-btn', title: t('variants'), onClick: () => askVariants(sc) }, h(I.sparkle)),
|
|
6975
|
-
h('button', { key: 'o', className: 'ddp-btn', title: t('openScreen'), onClick: () => change({ view: 'single', focus: sc.key }) }, h(I.single)),
|
|
8213
|
+
h('button', { key: 'x', className: 'ddp-btn', title: t('exThis'), onClick: () => doExport(FRAMED[kind] ? 'frame' : 'raw', sc.key) }, h(I.download), h('span', { className: 'lb' }, t('actX'))),
|
|
8214
|
+
sc.v ? null : h('button', { key: 'v', className: 'ddp-btn', title: t('variants'), onClick: () => askVariants(sc) }, h(I.sparkle), h('span', { className: 'lb' }, t('actV'))),
|
|
8215
|
+
h('button', { key: 'o', className: 'ddp-btn', title: t('openScreen'), onClick: () => change({ view: 'single', focus: sc.key }) }, h(I.single), h('span', { className: 'lb' }, t('actO'))),
|
|
6976
8216
|
]
|
|
6977
8217
|
|
|
6978
8218
|
// 对比:拖中间的把手
|
|
@@ -7097,14 +8337,13 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7097
8337
|
setTextEdit(false)
|
|
7098
8338
|
setStyleEdit(false)
|
|
7099
8339
|
setPanel(false)
|
|
7100
|
-
setStyles(false)
|
|
7101
8340
|
setHist(false)
|
|
7102
8341
|
setPhone(false)
|
|
7103
8342
|
closeCheck()
|
|
7104
8343
|
setPane('')
|
|
7105
8344
|
// 之前点选 / 调样式留下的「已选」框别夹在标注里
|
|
7106
8345
|
for (const a of apis.current.values()) if (a && a.unpin) a.unpin()
|
|
7107
|
-
setMarkSize(box.w && box.h ? { w: box.w, h: box.h } : null)
|
|
8346
|
+
setMarkSize(box.w && box.h > TOP_R + BOT_R ? { w: box.w, h: box.h - TOP_R - BOT_R } : null)
|
|
7108
8347
|
setMark(true)
|
|
7109
8348
|
}
|
|
7110
8349
|
// 换了设计、看旧版本、演示:画的东西对不上了,收掉
|
|
@@ -7294,13 +8533,14 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7294
8533
|
useEffect(() => { if (checkOff && ck) closeCheck() }, [checkOff])
|
|
7295
8534
|
|
|
7296
8535
|
const toggleDemo = () => (demo ? setDemo(null) : startDemo())
|
|
7297
|
-
const
|
|
8536
|
+
const openLook = (tab) => { if (tab) setLookTab(tab); setPanel(true); setHist(false); setPhone(false); closeCheck(); setStyleEdit(false) }
|
|
8537
|
+
const toggleHist = () => { setHist(!hist); setPanel(false); setPhone(false); closeCheck(); setStyleEdit(false) }
|
|
7298
8538
|
// 右边同一时间只开一个面板:别的开了,这几个关掉;开这几个时别的关掉
|
|
7299
|
-
const oldOpen = !!(panel ||
|
|
8539
|
+
const oldOpen = !!(panel || hist || phone || ck || styleEdit)
|
|
7300
8540
|
useEffect(() => { if (oldOpen) setPane('') }, [oldOpen])
|
|
7301
8541
|
const openPane = (p) => {
|
|
7302
8542
|
const next = pane === p ? '' : p
|
|
7303
|
-
if (next) { setPanel(false);
|
|
8543
|
+
if (next) { setPanel(false); setHist(false); setPhone(false); closeCheck(); setStyleEdit(false) }
|
|
7304
8544
|
setPane(next)
|
|
7305
8545
|
}
|
|
7306
8546
|
const setTweak = (k, v) => setTw((c) => {
|
|
@@ -7308,12 +8548,24 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7308
8548
|
return { ...c, [k.id]: v }
|
|
7309
8549
|
})
|
|
7310
8550
|
const saveTweaks = async () => {
|
|
8551
|
+
if (ver) return say(t('twVer')) // 旧版本的屏不收调节:存了改的是当前设计
|
|
7311
8552
|
try {
|
|
7312
8553
|
// 带上拖的是哪套设计:请求路上会话换了设计,服务端就不写
|
|
7313
|
-
const
|
|
8554
|
+
const sentTw = tw
|
|
8555
|
+
const sentTv = tv
|
|
8556
|
+
const gen = twGen.current
|
|
8557
|
+
const r = await post('tweak', { session: sid, root: st && st.target ? st.target.root : '', values: sentTw, vars: sentTv })
|
|
7314
8558
|
if (!r.ok) return say(r.reason === 'busy' ? t('styleBusyTip') : r.reason === 'moved' ? t('twMoved') : t('twNoWrite'))
|
|
7315
|
-
if (r.missed && r.missed.length) say(t('twMissed', { names: r.missed.map((x) => x.label).join(t('sep')) }))
|
|
8559
|
+
if (r.missed && r.missed.length) say(t('twMissed', { names: r.missed.map((x) => { const tok = String(x.id).startsWith('--') && tokens.find((k) => k.name === x.id); return tok ? tokenLabel(t, tok) : x.label }).join(t('sep')) }))
|
|
7316
8560
|
else showFlash(t('twSaved', { n: r.changed.length }))
|
|
8561
|
+
// 写进去的(设计变量、滑块):放掉,页面重新读文件就是新值了(滑块和配色管同一个颜色时以配色为准,滑块的旧值也得放掉);
|
|
8562
|
+
// 没写进去的留着,还能交给模型;请求路上又改过的也留着
|
|
8563
|
+
if (r.changed && r.changed.length && gen === twGen.current) {
|
|
8564
|
+
const ids = new Set(r.changed.map((x) => x.id))
|
|
8565
|
+
const drop = (c, sent) => { let out = c; for (const id of Object.keys(c)) if (ids.has(id) && c[id] === sent[id]) { if (out === c) out = { ...c }; delete out[id] } return out }
|
|
8566
|
+
setTv((c) => drop(c, sentTv))
|
|
8567
|
+
setTw((c) => drop(c, sentTw))
|
|
8568
|
+
}
|
|
7317
8569
|
} catch (e) {
|
|
7318
8570
|
say(t('failed', { msg: e.message }))
|
|
7319
8571
|
}
|
|
@@ -7330,58 +8582,88 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7330
8582
|
}), 'insertedSend')
|
|
7331
8583
|
setPane('')
|
|
7332
8584
|
}
|
|
7333
|
-
const togglePhone = () => { setPhone(!phone); setPanel(false);
|
|
8585
|
+
const togglePhone = () => { setPhone(!phone); setPanel(false); setHist(false); closeCheck(); setStyleEdit(false) }
|
|
7334
8586
|
const reload = () => setReloadSeq((x) => x + 1)
|
|
7335
8587
|
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
8588
|
+
// 浮在画布上的几块,各管一件事,按钮都带字:左上 设计库 | 屏名 ▾(换屏、多尺寸、类型都在这个菜单里);
|
|
8589
|
+
// 右上 外观 体检 | 导出 ▾ ⋯;底部中间 选择 改字 样式 画笔 | 试用;右下缩放(画布自己带)
|
|
8590
|
+
// 鼠标停一下出说明卡(HoverTip):名字 + 一句用途
|
|
8591
|
+
// 窄了(样式表里):先截屏名,再收字
|
|
8592
|
+
const curKey = canvas ? '' : (focus || (screens[0] && screens[0].key) || '')
|
|
8593
|
+
const curSc = screens.find((x) => x.key === curKey)
|
|
8594
|
+
// 产品名:画布上几屏标题都带它时认得出来;单页只拿得到一屏的标题,用记下的那个把它去掉(「订单 · 巷口面馆」→「订单」)
|
|
8595
|
+
const brand0 = splitTitles(shown.map(labelOf)).brand
|
|
8596
|
+
if (brand0) brandMem.current[site] = brand0
|
|
8597
|
+
const brand = brand0 || brandMem.current[site] || ''
|
|
8598
|
+
const dropBrand = (x) => {
|
|
8599
|
+
if (!brand || x === brand) return x
|
|
8600
|
+
const p = x.split(/\s*[·||—]\s*|\s+[-–]\s+/).map((y) => y.trim()).filter(Boolean)
|
|
8601
|
+
return p.length >= 2 && (p[0] === brand || p[p.length - 1] === brand) ? p.filter((y) => y !== brand).join(' · ') : x
|
|
8602
|
+
}
|
|
8603
|
+
const stLabel = (st && st.label) || ''
|
|
8604
|
+
const scrLabel = canvas && screens.length > 1
|
|
8605
|
+
? t('screensN', { name: brand || stLabel, n: screens.length })
|
|
8606
|
+
: curSc ? dropBrand(shortOf(curSc)) + (takeTag(curSc) ? ' · ' + takeTag(curSc) : '') : stLabel
|
|
8607
|
+
// 菜单里点某一屏:单看这一屏(「全部 n 屏」回画布)
|
|
8608
|
+
const pickScreen = (key) => {
|
|
8609
|
+
setMulti(false)
|
|
8610
|
+
if (demo) setDemo(null)
|
|
8611
|
+
setSel(key)
|
|
8612
|
+
if (canvas || key !== focus) change({ view: 'single', focus: key })
|
|
8613
|
+
}
|
|
8614
|
+
// 导出菜单写出是哪一屏(和 doExport 取同一屏)
|
|
8615
|
+
const exSc = shown.find((x) => x.key === (canvas ? sel || focus : focus)) || shown[0]
|
|
8616
|
+
const exName = exSc ? dropBrand(shortOf(exSc)) + (takeTag(exSc) ? ' · ' + takeTag(exSc) : '') : ''
|
|
8617
|
+
const libOpen = () => { setLibFocus(''); if (lib) setLib(''); else libDefaultThen((k) => setLib((c) => c || k)) }
|
|
8618
|
+
const dockTight = box.w > 0 && box.w <= 400 // 侧栏很窄:底部工具坞只放得下四个,「试用」进「更多」
|
|
8619
|
+
const tool = (cls, on, desc, off, run, Icon, label) => h('button', { className: 'ddp-tool' + (cls ? ' ' + cls : '') + (on ? ' on' : ''), ...tipA(label, desc, on ? 'Esc' : ''), 'aria-pressed': !!on, disabled: !!off, onClick: run }, h(Icon), h('span', null, label))
|
|
8620
|
+
const islands = editing
|
|
8621
|
+
? [h('div', { key: 'top', className: 'ddp-top' },
|
|
8622
|
+
h('div', { className: 'ddp-isl tl wide' },
|
|
8623
|
+
h(AddressForm, { t, sid, initial: st && st.target && st.target.kind === 'file' ? st.target.file : st && st.label ? st.label : '', autoFocus: true, onDone: () => setEditing(false), onBrowse: browse })))]
|
|
8624
|
+
: [
|
|
8625
|
+
h('div', { key: 'top', className: 'ddp-top' },
|
|
8626
|
+
h('div', { className: 'ddp-isl tl' },
|
|
8627
|
+
h('button', { className: 'ddp-btn lab' + (homeAt === 'on' ? ' sel' : ''), ...tipA(t('homeBtn'), t('dHome'), homeAt === 'on' ? 'Esc' : ''), 'aria-pressed': homeAt === 'on', onClick: () => (homeAt === 'on' ? homeClose() : homeOpen()) }, h(I.home), h('span', { className: 'lb' }, t('homeBtn'))),
|
|
8628
|
+
h('span', { className: 'ddp-sep' }),
|
|
8629
|
+
h(ScreenMenu, { t, label: scrLabel, tip: [scrLabel || stLabel, t('dScreen')], src: stLabel, busy, list: screens.map((x) => { const L = takeTag(x); return { key: x.key, label: dropBrand(shortOf(x)) + (L ? ' · ' + L : '') } }), cur: curKey, canvas: canvas && !inMulti && !demo, open: scrMenu, setOpen: setScrMenu,
|
|
8630
|
+
onPick: pickScreen, onAll: () => { setMulti(false); if (demo) setDemo(null); change({ view: 'canvas' }) }, onOther: () => setEditing(true),
|
|
8631
|
+
sizes: mark ? null : { on: inMulti, off: !sizeSet || !!ver, tip: sizeSet ? t('sizes') : t('sizesNo'), note: sizeSet ? '' : t('sizesNoShort'), run: () => setMulti(!inMulti) },
|
|
8632
|
+
kind, onKind: mark ? null : (k) => change({ kind: k }) })),
|
|
8633
|
+
h('div', { className: 'ddp-isl tr' },
|
|
8634
|
+
h('button', { className: 'ddp-btn lab' + (panel ? ' sel' : ''), ...tipA(t('look'), t('dLook')), onClick: () => (panel ? setPanel(false) : openLook()) }, h(I.palette), h('span', { className: 'lb' }, t('look'))),
|
|
8635
|
+
h('button', { className: 'ddp-btn lab' + (ck ? ' sel' : ''), ...tipA(t('checkShort'), ckCount && !ck ? t('dCheckN', { n: ckCount }) : t('dCheck')), disabled: checkOff, onClick: () => (ck ? closeCheck() : runCheck()) },
|
|
8636
|
+
h(I.stetho), h('span', { className: 'lb' }, t('checkShort')), ckCount && !ck && !checkOff ? h('span', { className: 'bdg' }, ckCount > 99 ? '99+' : String(ckCount)) : null),
|
|
8637
|
+
h('span', { className: 'ddp-sep' }),
|
|
8638
|
+
h(DropMenu, { t, tip: [t('deliver'), t('dExport')], btnCls: 'pri', icon: I.download, label: t('deliver'), caret: true, open: exMenu, setOpen: setExMenu, disabled: !url, items: [
|
|
8639
|
+
'hd:' + t('deliverCode'),
|
|
8640
|
+
['', I.code, t('handoff'), pane === 'handoff', !!ver, () => openPane('handoff')],
|
|
8641
|
+
'hd:' + t('exFiles'),
|
|
8642
|
+
...exportItems(t, kind, shown.length, exName).map(([mode, Icon, label]) => ['', Icon, label, false, false, () => doExport(mode)]),
|
|
8643
|
+
fileTarget ? 'hd:' + t('deliverShare') : null, // 只有本地的设计才能打包、分享
|
|
8644
|
+
fileTarget ? ['', I.box, t('exZip'), false, false, () => doExport('zip')] : null,
|
|
8645
|
+
fileTarget ? ['', I.share, t('exShare'), false, false, () => doExport('share')] : null,
|
|
8646
|
+
], foot: t('exWhere') }),
|
|
8647
|
+
h(DropMenu, { t, cls: 'ddp-more', tip: [t('more'), phoneOn && !phone ? t('phoneOnTip') + ' · ' + t('dMore') : t('dMore')], icon: I.more, has: phoneOn && !phone, open: more, setOpen: setMore, items: [
|
|
8648
|
+
dockTight ? ['', I.play, t('demoShort'), !!demo, !url || !!ver, toggleDemo] : null, // 侧栏太窄,底部工具坞放不下「试用」:挪到这里
|
|
8649
|
+
dockTight ? 'sep' : null,
|
|
8650
|
+
['', I.history, t('history'), hist || !!ver, !fileTarget, toggleHist],
|
|
8651
|
+
['', I.scan, t('phone'), phone, !url, togglePhone, phoneOn ? t('phoneOnShort') : ''],
|
|
8652
|
+
anyDark || dark ? ['', I.moon, t('dark'), dark, false, () => setDark(!dark)] : null,
|
|
8653
|
+
'sep',
|
|
8654
|
+
['', I.refresh, t('refresh'), false, false, reload],
|
|
8655
|
+
['', I.external, t('external'), false, !url, url],
|
|
8656
|
+
] }))),
|
|
8657
|
+
h('div', { key: 'dock', className: 'ddp-isl ddp-dock', role: 'toolbar', 'aria-label': t('toolsT') },
|
|
8658
|
+
tool('pick', picking, t('dPick'), !url || !!ver || !!demo || mark, togglePick, I.pick, t('pick')),
|
|
8659
|
+
tool('', textEdit, t('dText'), !url || !!ver || !!demo || busy || mark, toggleText, I.text, t('textShort')),
|
|
8660
|
+
tool('', styleEdit, t('dStyle'), styleOff || mark, toggleStyle, I.sliders, t('styleShort')),
|
|
8661
|
+
tool('', mark, t('dMark'), markOff, toggleMark, I.pen, t('markShort')),
|
|
8662
|
+
dockTight ? null : h('span', { className: 'ddp-sep dm' }),
|
|
8663
|
+
dockTight ? null : tool('dm', !!demo, t('dPlay'), !url || !!ver, toggleDemo, I.play, t('demoShort'))),
|
|
8664
|
+
]
|
|
7378
8665
|
|
|
7379
8666
|
// 模型用提问卡问你、在等回答:不转圈,换成一个橙点
|
|
7380
|
-
const busyChip = (cls) => st && st.asking
|
|
7381
|
-
? h('div', { className: 'ddp-chip' + cls, key: 'busy' }, h('i', { className: 'q' }), h('span', null, t('asking')))
|
|
7382
|
-
: h('div', { className: 'ddp-chip' + cls, key: 'busy' }, h('i', { className: 'sp' }),
|
|
7383
|
-
h('span', { className: 'tx' }, st && st.writing ? t('writing', { file: st.writing }) : t('working')))
|
|
7384
|
-
|
|
7385
8667
|
// ── 参考图:拖进来 / 粘贴 / 从设计规范面板选;存进设计目录的 refs/,尺寸和主要颜色放进输入框 ──
|
|
7386
8668
|
const addRef = async (file) => {
|
|
7387
8669
|
if (!file || !/^image\//.test(file.type)) return say(t('refNotImage'))
|
|
@@ -7477,9 +8759,10 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7477
8759
|
setTab: (k) => { setLibFocus(''); setLib(k) },
|
|
7478
8760
|
focus: libFocus,
|
|
7479
8761
|
hasDesign: !!url,
|
|
8762
|
+
homey: !url || homeAt === 'on',
|
|
7480
8763
|
curRoot: fileTarget ? st.target.root : '',
|
|
7481
8764
|
onClose: () => { setLib(''); setLibFocus('') },
|
|
7482
|
-
onOpen: (w) => openTarget(sid, w.dir).then(() => { setLib(''); setEditing(false) }, (e) => say(t('wkOpenFail', { msg: e.message }))),
|
|
8765
|
+
onOpen: (w) => openTarget(sid, w.dir).then(() => { setLib(''); setEditing(false); homeAway() }, (e) => say(t('wkOpenFail', { msg: e.message }))),
|
|
7483
8766
|
onPack: openPack,
|
|
7484
8767
|
onShare: openShare,
|
|
7485
8768
|
onSaveStyle: saveMyStyle,
|
|
@@ -7491,17 +8774,20 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7491
8774
|
onSay: say,
|
|
7492
8775
|
}),
|
|
7493
8776
|
toast ? h('div', { className: 'ddp-toast low' }, h('span', null, toast.text)) : null) : null
|
|
7494
|
-
|
|
8777
|
+
// 开着设计时点「首页」:首页叠在画布上(设计库再叠在它上面)
|
|
8778
|
+
const root = (...kids) => h('div', rootProps, ...kids, url && homeAt ? homeView(true) : null, libLayer, dropLayer, fileInput, pageFileInput)
|
|
7495
8779
|
|
|
7496
8780
|
// ── 设计库:打成 zip、拖进来的设计包、存成我的风格 ──
|
|
7497
8781
|
function openPack(dir) {
|
|
7498
8782
|
setPackDir(dir || '')
|
|
7499
8783
|
setLib('')
|
|
8784
|
+
homeAway()
|
|
7500
8785
|
if (pane !== 'pack') openPane('pack')
|
|
7501
8786
|
}
|
|
7502
8787
|
function openShare(dir) {
|
|
7503
8788
|
setPackDir(dir || '')
|
|
7504
8789
|
setLib('')
|
|
8790
|
+
homeAway()
|
|
7505
8791
|
if (pane !== 'share') openPane('share')
|
|
7506
8792
|
}
|
|
7507
8793
|
const sameDir = (a, b) => String(a || '').replace(/[\\/]+$/, '') === String(b || '').replace(/[\\/]+$/, '')
|
|
@@ -7532,6 +8818,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7532
8818
|
if (d.error === 'check') {
|
|
7533
8819
|
setZipIssues({ errors: d.errors || [], more: d.more || 0 })
|
|
7534
8820
|
setLib('')
|
|
8821
|
+
homeAway()
|
|
7535
8822
|
if (pane !== 'zip') openPane('zip')
|
|
7536
8823
|
return undefined
|
|
7537
8824
|
}
|
|
@@ -7542,6 +8829,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7542
8829
|
}
|
|
7543
8830
|
setLib('')
|
|
7544
8831
|
setEditing(false)
|
|
8832
|
+
homeAway()
|
|
7545
8833
|
if (pane === 'zip' || pane === 'pack' || pane === 'share') { setPane(''); setZipIssues(null) }
|
|
7546
8834
|
if (poll && d.snap) poll.set(d.snap)
|
|
7547
8835
|
insert(formatTpl(t, d))
|
|
@@ -7560,6 +8848,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7560
8848
|
}
|
|
7561
8849
|
setLib('')
|
|
7562
8850
|
setEditing(false)
|
|
8851
|
+
homeAway()
|
|
7563
8852
|
if (pane === 'zip' || pane === 'pack' || pane === 'share') { setPane(''); setZipIssues(null) }
|
|
7564
8853
|
if (poll && d.snap) poll.set(d.snap)
|
|
7565
8854
|
insert(formatTpl(t, d))
|
|
@@ -7585,20 +8874,6 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7585
8874
|
|
|
7586
8875
|
// 调节滑块 / 品牌设计系统 / 转成代码(右边的面板)
|
|
7587
8876
|
const sidePane = () => {
|
|
7588
|
-
if (pane === 'tweaks') {
|
|
7589
|
-
return h(TweaksPanel, {
|
|
7590
|
-
t,
|
|
7591
|
-
tw: tweaks,
|
|
7592
|
-
live: tw,
|
|
7593
|
-
write: busy ? { ok: false, tip: t('styleBusyTip') } : { ok: true, tip: t('twSaveTip') },
|
|
7594
|
-
onSet: setTweak,
|
|
7595
|
-
onReset: () => setTw({}),
|
|
7596
|
-
onAsk: () => insert(formatTweaks(t, tlist, tw), 'insertedSend'),
|
|
7597
|
-
onSave: saveTweaks,
|
|
7598
|
-
onMake: (errs) => insert(errs && errs.length ? t('twFixText', { errs: errs.join(';') }) + '\n' : t('twMakeText') + '\n', 'insertedSend'),
|
|
7599
|
-
onClose: () => setPane(''),
|
|
7600
|
-
})
|
|
7601
|
-
}
|
|
7602
8877
|
if (pane === 'systems') {
|
|
7603
8878
|
return h(SystemsPanel, {
|
|
7604
8879
|
t,
|
|
@@ -7614,7 +8889,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7614
8889
|
return h(PackPanel, { t, sid, root: packDir || (fileTarget ? st.target.root : ''), onFix: fixPack, onSay: say, onClose: () => setPane('') })
|
|
7615
8890
|
}
|
|
7616
8891
|
if (pane === 'share') {
|
|
7617
|
-
return h(SharePanel, { t, sid, root: packDir || (fileTarget ? st.target.root : ''), onFix: fixPack, onSay: say, onClose: () => setPane('') })
|
|
8892
|
+
return h(SharePanel, { t, sid, root: packDir || (fileTarget ? st.target.root : ''), brand: !packDir || (fileTarget && packDir === st.target.root) ? brand : '', onFix: fixPack, onSay: say, onClose: () => setPane('') })
|
|
7618
8893
|
}
|
|
7619
8894
|
if (pane === 'zip' && zipIssues) {
|
|
7620
8895
|
return h(ZipIssues, { t, issues: zipIssues, onClose: () => { setPane(''); setZipIssues(null) } })
|
|
@@ -7625,46 +8900,114 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7625
8900
|
return null
|
|
7626
8901
|
}
|
|
7627
8902
|
|
|
8903
|
+
// 首页大输入框发出去:放进 DSH 的输入框直接发;输入框里本来有字、或旧版 DSH 没有 submit:放进去等用户自己发
|
|
8904
|
+
const homeSend = ({ text, kind, style }) => {
|
|
8905
|
+
const ia = props.inputActions
|
|
8906
|
+
const cel = composerEl()
|
|
8907
|
+
const was = cel ? composerText(cel) : null
|
|
8908
|
+
const had = was === null || !!was.trim() // 读不出来(有引用标签、分屏)当成有字:只放进去,不替用户发
|
|
8909
|
+
const msg = text + (kind ? '\n' + t('askKindLine', { k: t(KIND_KEY[kind]) }) : '') + (url ? '\n' + t('askNewLine') : '')
|
|
8910
|
+
homeKind.current = kind || ''
|
|
8911
|
+
const pick = style || (had ? null : autoStyle(text, kind)) // 没选风格:按题材替模型挑一张(不然总是米色暖调);输入框里本来有东西(风格卡、参考图…)就不挑
|
|
8912
|
+
const sty = pick ? formatStyle(t, pick, false, !style) : ''
|
|
8913
|
+
if (!insert(msg, 'inserted', false)) {
|
|
8914
|
+
insert([msg, sty].filter(Boolean).join('\n\n')) // 插不进:整段复制
|
|
8915
|
+
return false
|
|
8916
|
+
}
|
|
8917
|
+
if (sty) insert(sty, 'inserted', false)
|
|
8918
|
+
if (url) homeClose() // 开着设计时从首页发的:收起首页,看着新的一套长出来
|
|
8919
|
+
if (had || !ia || typeof ia.submit !== 'function') { say(t('insertedSend')); return true }
|
|
8920
|
+
setTimeout(() => {
|
|
8921
|
+
try { ia.submit(); say(t('askSent')) } catch (e) { console.warn('[dsh-design-studio] submit 失败', e); say(t('insertedSend')) }
|
|
8922
|
+
}, 80)
|
|
8923
|
+
return true
|
|
8924
|
+
}
|
|
8925
|
+
// 「+」里的「打开已有的 HTML」:桌面版直接选文件;网页版展开下面的地址框
|
|
8926
|
+
const openHtml = () => {
|
|
8927
|
+
if (browse) return browse()
|
|
8928
|
+
const d = homeRef.current && homeRef.current.querySelector('.ddp-ways')
|
|
8929
|
+
if (!d) { if (url) { homeClose(); setEditing(true) } return } // 叠着的首页没有地址框:用左上角的
|
|
8930
|
+
d.open = true
|
|
8931
|
+
d.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
|
|
8932
|
+
const i = d.querySelector('input')
|
|
8933
|
+
if (i) i.focus({ preventScroll: true })
|
|
8934
|
+
}
|
|
8935
|
+
const toLib = (k, id) => { setLibFocus(id || ''); setLib(k) }
|
|
8936
|
+
const homeView = (over) => h('div', { key: 'home', className: 'ddp-home' + (over ? ' over' + (homeAt === 'off' ? ' out' : '') : ''), ref: homeRef, 'aria-label': over ? t('homeBtn') : undefined, role: over ? 'dialog' : undefined },
|
|
8937
|
+
h(HomeBar, {
|
|
8938
|
+
t,
|
|
8939
|
+
tab: '',
|
|
8940
|
+
onTab: (k) => { if (k) toLib(k) },
|
|
8941
|
+
lead: over ? h('button', { className: 'ddp-homeback', ...tipA(t('homeBack', { name: scrLabel || stLabel }), '', 'Esc'), onClick: homeClose }, h(I.back), h('span', null, t('homeBack', { name: scrLabel || stLabel }))) : null,
|
|
8942
|
+
}),
|
|
8943
|
+
h('div', { className: 'ddp-homebd' },
|
|
8944
|
+
h('div', { className: 'ddp-start' },
|
|
8945
|
+
h('div', { className: 'ddp-hero' }, h('h1', null, t('emptyHead')), h('p', null, t('emptySub'))),
|
|
8946
|
+
!over && st && st.hint ? h('button', {
|
|
8947
|
+
className: 'ddp-go',
|
|
8948
|
+
onClick: () => post('open', { session: sid, target: st.hint }).then((x) => { if (poll) poll.set(x) }, (e) => say(t('openFailed', { msg: e.message }))),
|
|
8949
|
+
}, t('hintOpen', { name: st.hint.split(/[\\/]/).pop() })) : null,
|
|
8950
|
+
h(HomeAsk, {
|
|
8951
|
+
t,
|
|
8952
|
+
draft: homeDraft,
|
|
8953
|
+
autoFocus: over,
|
|
8954
|
+
note: over ? t('askNewNote') : '',
|
|
8955
|
+
onSend: homeSend,
|
|
8956
|
+
onRef: pickRef,
|
|
8957
|
+
plus: [
|
|
8958
|
+
['', I.page, t('askOpenHtml'), false, false, openHtml],
|
|
8959
|
+
['', I.palette, t('askBrand'), false, false, () => { if (over) homeClose(); openPane('systems') }],
|
|
8960
|
+
['', I.library, t('askLib'), false, false, () => { setLibFocus(''); libDefaultThen((k) => setLib((c) => c || k)) }],
|
|
8961
|
+
],
|
|
8962
|
+
}),
|
|
8963
|
+
h(HomeWorks, {
|
|
8964
|
+
t,
|
|
8965
|
+
cur: over && fileTarget ? st.target.root : '',
|
|
8966
|
+
onBack: homeClose,
|
|
8967
|
+
onOpen: (w) => openTarget(sid, w.dir).then(() => { setEditing(false); if (over) homeClose() }, (e) => say(t('wkOpenFail', { msg: e.message }))),
|
|
8968
|
+
onAll: () => toLib('works'),
|
|
8969
|
+
}),
|
|
8970
|
+
h(TplStart, { t, onOpen: (id) => toLib('market', id), onMore: () => toLib('market') }),
|
|
8971
|
+
over ? null : h('details', { className: 'ddp-ways' },
|
|
8972
|
+
h('summary', null, h(I.caret), t('waysHtml')),
|
|
8973
|
+
h('div', { className: 'in' },
|
|
8974
|
+
h('p', null, t(browse ? 'emptyHintPick' : 'emptyHint')),
|
|
8975
|
+
h(AddressForm, { t, sid, className: 'ddp-form', onDone: () => {}, onBrowse: browse }))))),
|
|
8976
|
+
h(HoverTip, { key: 'tip' }))
|
|
8977
|
+
|
|
7628
8978
|
if (!url) {
|
|
7629
8979
|
const toastEl = toast ? h('div', { className: 'ddp-toast' }, h('span', null, toast.text)) : null
|
|
7630
8980
|
if (st ? st.loading : !waited) return root(h('div', { className: 'ddp-stage', ref: stageRef }), toastEl)
|
|
7631
8981
|
if (busy) {
|
|
7632
|
-
//
|
|
8982
|
+
// 模型在做第一屏:先放一个生成中的占位(按选的类型画个大概的样子)
|
|
8983
|
+
const waitKind = homeKind.current || (st && st.kind) || 'web'
|
|
7633
8984
|
return root(
|
|
7634
|
-
h('div', { className: 'ddp-stage', ref: stageRef },
|
|
7635
|
-
h('div', { className: 'ddp-wait' },
|
|
8985
|
+
h('div', { className: 'ddp-stage bz', ref: stageRef },
|
|
8986
|
+
h('div', { className: 'ddp-wait' },
|
|
8987
|
+
h('div', { className: 'card k-' + waitKind },
|
|
8988
|
+
h(GhostDraw, { key: 'gd', t, kind: waitKind, pulse }),
|
|
8989
|
+
dr ? h(FitDraft, { key: 'df', url: dr.url, kind: waitKind, lang: t('lang'), onEnd: draftEnd }) : null),
|
|
8990
|
+
h('p', null, t(dr ? 'waitingLive' : 'waiting'))),
|
|
7636
8991
|
h('div', { className: 'ddp-glow' }),
|
|
7637
8992
|
h('div', { className: 'ddp-ring' }),
|
|
7638
|
-
|
|
8993
|
+
busyBar(false)),
|
|
7639
8994
|
toastEl)
|
|
7640
8995
|
}
|
|
7641
8996
|
return root(
|
|
7642
|
-
|
|
7643
|
-
h(EmptyArt),
|
|
7644
|
-
h('b', null, t('empty')),
|
|
7645
|
-
st && st.hint ? h('button', {
|
|
7646
|
-
className: 'ddp-go',
|
|
7647
|
-
onClick: () => post('open', { session: sid, target: st.hint }).then((s) => { if (poll) poll.set(s) }, (e) => say(t('openFailed', { msg: e.message }))),
|
|
7648
|
-
}, t('hintOpen', { name: st.hint.split(/[\\/]/).pop() })) : null,
|
|
7649
|
-
h('ol', { className: 'ddp-steps' }, h('li', null, t('step1')), h('li', null, t('step2')), h('li', null, t('step3'))),
|
|
7650
|
-
h('div', { className: 'ddp-tries' }, h('span', null, t('tryHead')),
|
|
7651
|
-
['try1', 'try2', 'try3'].map((k) => h('button', { key: k, className: 'ddp-try', onClick: () => insert(t(k), 'insertedSend') }, t(k)))),
|
|
7652
|
-
h('p', null, t(browse ? 'emptyHintPick' : 'emptyHint')),
|
|
7653
|
-
h(AddressForm, { t, sid, className: 'ddp-form', onDone: () => {}, onBrowse: browse }),
|
|
7654
|
-
h('p', { className: 'ddp-refline' }, t('refEmpty'), ' · ', h('button', { className: 'lk', onClick: pickRef }, t('refChoose'))),
|
|
7655
|
-
h('p', { className: 'ddp-refline' }, t('sysEmptyLine'), ' · ', h('button', { className: 'lk', onClick: () => openPane('systems') }, t('sysOpen'))),
|
|
7656
|
-
h('p', { className: 'ddp-refline' }, t('libLine'), ' · ', h('button', { className: 'lk', onClick: () => { setLibFocus(''); libDefaultThen((k) => setLib((c) => c || k)) } }, t('libOpen'))),
|
|
7657
|
-
h(TplStart, { t, onOpen: (id) => { setLibFocus(id); setLib('market') }, onMore: () => { setLibFocus(''); setLib('market') } }),
|
|
7658
|
-
h('div', { className: 'ddp-stylepick' }, h('b', null, t('stylesHead')), h('p', null, t('stylesTip')), h(StyleCards, { t, onPick: (x) => insert(formatStyle(t, x, false)) }))),
|
|
8997
|
+
homeView(false),
|
|
7659
8998
|
pane === 'systems' || pane === 'pack' || pane === 'share' || pane === 'zip' ? sidePane() : null,
|
|
7660
8999
|
toastEl)
|
|
7661
9000
|
}
|
|
7662
9001
|
|
|
9002
|
+
// 上下两排浮块中间那块的高度:单页、演示、对比按它缩放
|
|
9003
|
+
const ah = Math.max(0, box.h - TOP_R - BOT_R)
|
|
9004
|
+
const cvBody = !demo && (inMulti || (canvas && !(ver && cmp)))
|
|
7663
9005
|
let body
|
|
9006
|
+
let busyGap = false
|
|
7664
9007
|
if (demo) {
|
|
7665
9008
|
const fill = kind === 'web'
|
|
7666
9009
|
const [dw, dh] = deviceBox(kind, size)
|
|
7667
|
-
const z = fill ? 1 : Math.max(0.05, Math.min(1, (box.w - 32) / dw, (
|
|
9010
|
+
const z = fill ? 1 : Math.max(0.05, Math.min(1, (box.w - 32) / dw, (ah - 16 - 46) / dh))
|
|
7668
9011
|
const top = demo.layers[demo.layers.length - 1]
|
|
7669
9012
|
body = h('div', { className: 'ddp-demo' }, ...demo.layers.map((l) => {
|
|
7670
9013
|
const isTop = l.id === top.id
|
|
@@ -7701,6 +9044,8 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7701
9044
|
body = h(Canvas, {
|
|
7702
9045
|
key: 'multi',
|
|
7703
9046
|
t,
|
|
9047
|
+
top: TOP_R,
|
|
9048
|
+
bottom: BOT_R,
|
|
7704
9049
|
screens: fake,
|
|
7705
9050
|
items,
|
|
7706
9051
|
kind,
|
|
@@ -7738,7 +9083,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7738
9083
|
const old = now ? { key: now.key, id: `v${ver.n}|${now.key}`, v: ver.n, url: vbase + encodeURIComponent(now.key), label: now.label } : null
|
|
7739
9084
|
const fill = kind === 'web'
|
|
7740
9085
|
const [dw, dh] = deviceBox(kind, size)
|
|
7741
|
-
const z = fill ? 1 : Math.max(0.05, Math.min(1, (box.w - 32) / dw, (
|
|
9086
|
+
const z = fill ? 1 : Math.max(0.05, Math.min(1, (box.w - 32) / dw, (ah - 16 - (ver ? 46 : 0)) / dh))
|
|
7742
9087
|
// 两边的小标签不跟着画面缩小(缩了看不清);手机、平板躲开状态栏
|
|
7743
9088
|
const tagAt = (side) => ({ top: kind === 'app' || kind === 'miniprogram' || kind === 'tablet' ? 60 : 10, transform: `scale(${1 / z})`, transformOrigin: 'top ' + side })
|
|
7744
9089
|
const screenOf = (sc) => h(Screen, { t, remote, onPageDrag: pageDrag, src: sc.url, label: labelOf(sc), busy: false, born: false, picking: false, dark, zoom: z, reloadSeq,
|
|
@@ -7759,17 +9104,23 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7759
9104
|
body = h(Canvas, {
|
|
7760
9105
|
key: 'grid',
|
|
7761
9106
|
t,
|
|
7762
|
-
|
|
9107
|
+
top: TOP_R + (ver ? 46 : 0), // 看旧版本时上面还有一条横幅
|
|
9108
|
+
bottom: BOT_R,
|
|
9109
|
+
screens: hideKey && !ver ? shown.filter((x) => x.key !== hideKey) : shown,
|
|
7763
9110
|
actions,
|
|
7764
9111
|
badge,
|
|
9112
|
+
tag: tagOf,
|
|
7765
9113
|
kind,
|
|
7766
9114
|
size,
|
|
7767
9115
|
ghost,
|
|
9116
|
+
draft: ghost && dr ? dr.url : '',
|
|
9117
|
+
onDraftEnd: draftEnd,
|
|
9118
|
+
pulse,
|
|
7768
9119
|
picking: picking || textEdit || styleEdit,
|
|
7769
9120
|
sel: sel || focus,
|
|
7770
9121
|
labelOf: shortOf,
|
|
7771
9122
|
reveal,
|
|
7772
|
-
inset: ck || styleEdit || panel ||
|
|
9123
|
+
inset: ck || styleEdit || panel || pane || hist || phone ? 296 : 0, // 右边的面板 276 宽,离边 10
|
|
7773
9124
|
render: (sc, z) => renderScreen(sc, z, false),
|
|
7774
9125
|
flows,
|
|
7775
9126
|
onSelect: setSel,
|
|
@@ -7779,30 +9130,41 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7779
9130
|
const fill = kind === 'web'
|
|
7780
9131
|
const [dw, dh] = deviceBox(kind, size)
|
|
7781
9132
|
// 右边开着面板(体检、调样式、设计规范、换风格):手机这类有外框的让到面板左边(侧栏够宽时),别被挡住
|
|
7782
|
-
const
|
|
9133
|
+
const sideOn = !!(ck || styleEdit || panel || pane || hist || phone) && box.w - 296 >= 240
|
|
9134
|
+
const aside = !fill && sideOn ? 296 : 0
|
|
9135
|
+
// 生成中:右侧栏窄(或开着面板)时状态条在上排浮块下面,手机这类给它让一条;宽的时候它在上排中间,不用让。网页不让(一让就重排)
|
|
9136
|
+
busyGap = busy && !fill && (box.w < 860 || sideOn)
|
|
7783
9137
|
// 画笔标注开着:网页按开标注时的大小定住(侧栏拉窄了就整体缩小,不重排),画的才对得上
|
|
7784
9138
|
const lock = fill && mark && markSize ? markSize : null
|
|
7785
|
-
const z = lock ? Math.max(0.05, Math.min(1, box.w / lock.w,
|
|
9139
|
+
const z = lock ? Math.max(0.05, Math.min(1, box.w / lock.w, ah / lock.h)) : fill ? 1 : Math.max(0.05, Math.min(1, (box.w - aside - 32) / dw, (ah - 16 - (ver || hintTop || busyGap ? 46 : 0)) / dh))
|
|
7786
9140
|
const one = ver ? shown.find((x) => x.key === focus) || shown[0] || { key: focus, url, label: st.label } : { key: focus, url, label: st.label }
|
|
9141
|
+
// 单屏时模型在写新的一屏:先看着它写(没在选、改字、调样式、标注的时候),写完换过去
|
|
9142
|
+
const watch = dr && !ver && !demo && !picking && !textEdit && !styleEdit && !mark && !lock
|
|
9143
|
+
if (watch) singleDraftR.current = dr.key || '*' // 还不知道是哪屏:名字到了再说
|
|
7787
9144
|
body = h('div', { className: 'ddp-single', style: aside ? { right: aside } : undefined },
|
|
7788
9145
|
h('div', { className: 'ddp-fit' + (fill && !lock ? ' fill' : ''), style: fill && !lock ? undefined : { width: lock ? lock.w : dw, height: lock ? lock.h : dh, transform: `translate(-50%, -50%) scale(${z})` } },
|
|
7789
|
-
|
|
9146
|
+
watch
|
|
9147
|
+
? h(Device, { key: 'draft', kind, w: size[0], h: size[1], fill, label: '', os: meta.os },
|
|
9148
|
+
h(GhostDraw, { key: 'gd', t, kind, pulse }),
|
|
9149
|
+
h(DraftFrame, { key: 'df', url: dr.url, z: fill ? 1 : z, lang: t('lang'), onEnd: draftEnd }))
|
|
9150
|
+
: renderScreen(one, z, fill)))
|
|
7790
9151
|
}
|
|
7791
9152
|
|
|
7792
9153
|
let chip = null
|
|
7793
|
-
const
|
|
9154
|
+
const sideOpen = !!(ck || styleEdit || panel || pane || hist || phone) && box.w - 296 >= 240 // 右边开着面板:提示放在左边那块的中间
|
|
9155
|
+
const side = sideOpen ? ' side' : ''
|
|
7794
9156
|
const hintText = picking ? t(canvas || inMulti ? 'pickAny' : 'pickOne') : textEdit ? t(canvas || inMulti ? 'textAny' : 'textOne') : styleEdit && !sty ? t(canvas ? 'styleAny' : 'styleOne') : ''
|
|
7795
9157
|
if (flash) chip = h('div', { className: 'ddp-chip ok' + side, key: 'ok' }, h(I.check), h('span', null, flash))
|
|
7796
|
-
else if (busy) chip = busyChip(side)
|
|
7797
9158
|
|
|
7798
9159
|
return root(
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
h('div', { className: 'ddp-body' + (ver || demo || hintTop ? ' vb' : '') }, body),
|
|
9160
|
+
h('div', { className: 'ddp-stage isl' + (busy ? ' bz' : ''), ref: stageRef },
|
|
9161
|
+
h('div', { className: 'ddp-body' + (cvBody ? '' : ' pad' + (ver || demo || hintTop || busyGap ? ' vb' : '') + (ease ? ' ease' : '')) }, body),
|
|
7802
9162
|
busy ? h('div', { className: 'ddp-glow' }) : null,
|
|
7803
9163
|
busy ? h('div', { className: 'ddp-ring' }) : null,
|
|
9164
|
+
busy ? busyBar(sideOpen) : null,
|
|
7804
9165
|
// 右边开着面板时提示条居中在面板左边那块,别被面板盖住一半
|
|
7805
|
-
|
|
9166
|
+
// 提示只占一个位置:有新消息(toast)时它先让开,消息走了再回来
|
|
9167
|
+
hintText && !toast ? h('div', { className: 'ddp-hint' + (sideOpen ? ' side' : '') }, hintText) : null,
|
|
7806
9168
|
ck ? h(CheckPanel, { t, ck, onFocus: focusIssue, onFix: fixIssues, onAgain: runCheck, onClose: closeCheck }) : null,
|
|
7807
9169
|
styleEdit ? h(StylePanel, {
|
|
7808
9170
|
t,
|
|
@@ -7815,11 +9177,40 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7815
9177
|
onSave: saveStyle,
|
|
7816
9178
|
onClose: () => setStyleEdit(false),
|
|
7817
9179
|
}) : null,
|
|
7818
|
-
panel ? h(
|
|
7819
|
-
|
|
7820
|
-
|
|
9180
|
+
panel ? h(LookPanel, {
|
|
9181
|
+
t,
|
|
9182
|
+
tab: lookTab,
|
|
9183
|
+
setTab: setLookTab,
|
|
9184
|
+
tokens,
|
|
9185
|
+
tv,
|
|
9186
|
+
onTv: (name, v) => setTv((c) => ({ ...c, [name]: v })),
|
|
9187
|
+
onTokAsk: (tok) => insert(formatToken(tok, stRef.current)),
|
|
9188
|
+
onTokMake: () => insert(t('tokensAskText') + '\n', 'insertedSend'),
|
|
9189
|
+
tw: tweaks,
|
|
9190
|
+
live: tw,
|
|
9191
|
+
onTw: setTweak,
|
|
9192
|
+
onTwMake: (errs) => insert(errs && errs.length ? t('twFixText', { errs: errs.join(';') }) + '\n' : t('twMakeText') + '\n', 'insertedSend'),
|
|
9193
|
+
write: !fileTarget ? { ok: false, tip: t('twNoWrite') } : ver ? { ok: false, tip: t('twVer') } : busy ? { ok: false, tip: t('styleBusyTip') } : { ok: true, tip: t('lookSaveTip') },
|
|
9194
|
+
onReset: () => { setTw({}); setTv({}) },
|
|
9195
|
+
onAsk: () => insert(formatLook(t, tlist, tw, tokens, tv), 'insertedSend'),
|
|
9196
|
+
onSave: saveTweaks,
|
|
9197
|
+
onPickStyle: (x) => { setPanel(false); insert(formatStyle(t, x, true), 'insertedSend') },
|
|
9198
|
+
onRef: pickRef,
|
|
9199
|
+
onSaveStyle: saveMyStyle,
|
|
9200
|
+
onInspire: () => { setPanel(false); setLib('inspire') },
|
|
9201
|
+
brand: lookTab === 'brand' ? h(SystemsPanel, {
|
|
9202
|
+
t,
|
|
9203
|
+
bare: true,
|
|
9204
|
+
rev: (st && st.turns) || 0,
|
|
9205
|
+
hasDesign: !!url,
|
|
9206
|
+
onExtract: (src) => insert(t('sysExtractText', { src }) + '\n', 'insertedSend'),
|
|
9207
|
+
onApply: (x) => insert(t('sysApplyText', { name: x.name, dir: x.dir }) + '\n', 'insertedSend'),
|
|
9208
|
+
onSay: say,
|
|
9209
|
+
onClose: () => setPanel(false),
|
|
9210
|
+
}) : null,
|
|
9211
|
+
onClose: () => setPanel(false),
|
|
9212
|
+
}) : null,
|
|
7821
9213
|
pane ? sidePane() : null,
|
|
7822
|
-
styles ? h(StylesPanel, { t, onPick: (x) => { setStyles(false); insert(formatStyle(t, x, true), 'insertedSend') }, onClose: () => setStyles(false) }) : null,
|
|
7823
9214
|
phone ? h(PhonePanel, {
|
|
7824
9215
|
t,
|
|
7825
9216
|
sid,
|
|
@@ -7833,19 +9224,23 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7833
9224
|
mark && !markOff ? h(MarkLayer, { t, sending: markBusy, frames: markFrames, onCancel: () => { markRun.current++; setMark(false) }, onSend: sendMark, onCopy: canCopyImage() ? copyMark : null }) : null,
|
|
7834
9225
|
demo ? h('div', { className: 'ddp-banner' },
|
|
7835
9226
|
h(I.play),
|
|
7836
|
-
h('
|
|
7837
|
-
h('
|
|
7838
|
-
|
|
7839
|
-
h('button', { className: 'ddp-btn',
|
|
9227
|
+
h('b', null, t('demoOn')),
|
|
9228
|
+
h('span', { className: 'nm' }, dropBrand((infoOf(demoKey) || {}).title || demoKey.replace(/\.html?$/i, ''))),
|
|
9229
|
+
demo.stack.length < 2 ? h('em', null, t('demoHow')) : null,
|
|
9230
|
+
h('button', { className: 'ddp-btn', ...tipA(t('demoBack')), disabled: demo.stack.length < 2, onClick: demoBack }, h(I.back)),
|
|
9231
|
+
h('button', { className: 'ddp-btn', ...tipA(t('demoRestart')), disabled: demo.stack.length < 2, onClick: demoRestart }, h(I.refresh)),
|
|
9232
|
+
h('button', { className: 'ddp-btn', ...tipA(t('demoExit'), '', 'Esc'), onClick: () => setDemo(null) }, h(I.close))) : null,
|
|
7840
9233
|
ver ? h('div', { className: 'ddp-banner' },
|
|
7841
9234
|
h(I.history),
|
|
7842
9235
|
h('span', null, t('viewing', { n: ver.n, time: hhmm(ver.at) })),
|
|
7843
|
-
h('button', { className: 'ddp-go' + (cmp ? ' on' : ''), onClick: () => setCmp(!cmp) }, t('compare')),
|
|
9236
|
+
h('button', { className: 'ddp-go sec' + (cmp ? ' on' : ''), onClick: () => setCmp(!cmp) }, t('compare')),
|
|
7844
9237
|
h('button', { className: 'ddp-go pri', onClick: doRestore }, t('restore')),
|
|
7845
9238
|
h('button', { className: 'ddp-btn', title: t('backNow'), onClick: () => { setVer(null); setCmp(false) } }, h(I.close))) : null,
|
|
7846
9239
|
chip,
|
|
7847
|
-
toast ? h('div', { className: 'ddp-toast' + (mark ? ' mk' : ver || demo
|
|
7848
|
-
toast.act ? h('button', { onClick: () => { toast.act.run(); setToast('') } }, toast.act.label) : null) : null
|
|
9240
|
+
toast ? h('div', { className: 'ddp-toast' + (mark ? ' mk' : ver || demo ? ' low' : '') + (sideOpen && !mark ? ' side' : ''), role: 'status' }, h('span', null, toast.text),
|
|
9241
|
+
toast.act ? h('button', { onClick: () => { toast.act.run(); setToast('') } }, toast.act.label) : null) : null,
|
|
9242
|
+
...islands,
|
|
9243
|
+
h(HoverTip, { key: 'tip' })))
|
|
7849
9244
|
}
|
|
7850
9245
|
|
|
7851
9246
|
// ── 标题栏按钮:打开预览,顺便在模型开了新页面时把右侧栏弹出来 ──────────
|
|
@@ -7894,7 +9289,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7894
9289
|
ctx.effect(() => ctx.sidebarRightTabs.register({
|
|
7895
9290
|
id: TAB_ID,
|
|
7896
9291
|
kind: KIND,
|
|
7897
|
-
title: () => t('
|
|
9292
|
+
title: () => t('tabTitle'),
|
|
7898
9293
|
keepMounted: true,
|
|
7899
9294
|
guide: [{ id: 'open', order: 30, title: () => t('title'), description: () => t('guideDesc'), icon: GuideIcon }],
|
|
7900
9295
|
}), 'dsh-design-studio: tab type')
|
|
@@ -7971,7 +9366,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7971
9366
|
// 注意:这里是客户端**服务名**(短的),不是包名
|
|
7972
9367
|
exports.inject = INJECT
|
|
7973
9368
|
// 测试用
|
|
7974
|
-
exports._test = { MarketDetail, dragKind, droppedOf, pickInfo, pickLabel, pickRanges, pickRule, chipOf, chipRanges, makeT, ZH, EN, formatCheck, formatTweaks, markHit, markPath, STYLES, formatStyle, accentUse, styleLook, padInsert, pageOf, pxOf, styleChanges, formatStyleAsk, takesOf, formatCheckTool, poller, frameOf, paletteOf, formatRef, layoutRow, keyOfPath, flowsOf, flowLines, flowMarks, formatPick, whereOf, parseMeta, sizeOf, layoutGrid, fitCam, tokenKind, formatToken, splitTitles, pickColors, hhmm, formatTpl, formatPrep, styleFromTokens, myStyleOf, isZipFile }
|
|
9369
|
+
exports._test = { pulseStep, MarketDetail, dragKind, droppedOf, pickInfo, pickLabel, pickRanges, pickRule, chipOf, chipRanges, makeT, ZH, EN, formatCheck, formatTweaks, markHit, markPath, STYLES, formatStyle, accentUse, autoStyle, styleLook, padInsert, pageOf, pxOf, styleChanges, formatStyleAsk, takesOf, formatCheckTool, poller, frameOf, paletteOf, formatRef, layoutRow, keyOfPath, flowsOf, flowLines, flowMarks, formatPick, whereOf, parseMeta, sizeOf, layoutGrid, fitCam, tokenKind, formatToken, splitTitles, pickColors, hhmm, formatTpl, formatPrep, styleFromTokens, myStyleOf, isZipFile }
|
|
7975
9370
|
return module.exports
|
|
7976
9371
|
},
|
|
7977
9372
|
})
|