dsh-design-studio 0.11.0 → 0.12.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/lib/client.js +1893 -539
- 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/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: '下一个(→)',
|
|
@@ -396,29 +436,111 @@ window.__ModuleLoader__.load({
|
|
|
396
436
|
phoneAuto: '只有扫了这个码的手机能打开,30 分钟没人看自动关。',
|
|
397
437
|
phoneNoLan: '电脑没连上 Wi-Fi 或有线网,手机连不过来。',
|
|
398
438
|
phoneFail: '没打开:{msg}',
|
|
399
|
-
phoneHelp: '
|
|
439
|
+
phoneHelp: '确认在同一个 Wi-Fi;手机开着代理 / VPN 的先关掉;电脑问「是否允许传入连接」时点允许。',
|
|
440
|
+
phoneTrouble: '打不开?',
|
|
400
441
|
zoomIn: '放大',
|
|
401
442
|
zoomOut: '缩小',
|
|
402
443
|
fit: '适合画布',
|
|
403
444
|
openScreen: '双击只看这一屏',
|
|
404
|
-
generating: '
|
|
445
|
+
generating: '设计中…',
|
|
405
446
|
waiting: '模型正在工作,页面写出来就显示在这里',
|
|
447
|
+
waitingLive: '页面正在一段段写出来,写完就能点着改',
|
|
406
448
|
refresh: '刷新',
|
|
407
449
|
external: '在浏览器里打开',
|
|
408
450
|
more: '更多',
|
|
409
|
-
demoShort: '
|
|
451
|
+
demoShort: '试用',
|
|
452
|
+
// 浮块按钮悬停说明:名字下面这一句
|
|
453
|
+
dPick: '点一个元素或拖出一块区域,发给模型改',
|
|
454
|
+
dText: '点页面里的字直接改,回车保存',
|
|
455
|
+
dStyle: '点一个元素,拖滑块调字号、颜色、圆角和间距',
|
|
456
|
+
dMark: '画圈、画箭头、写字,拼成一张图发给模型',
|
|
457
|
+
dPlay: '像真的 App 一样点,按钮会跳到对应的屏',
|
|
458
|
+
dLib: '存过的设计、风格和模板,也能逛设计市场',
|
|
459
|
+
dLook: '改配色、字体,或整套换个风格',
|
|
460
|
+
dCheck: '找出文字溢出、重叠、看不清、按钮太小',
|
|
461
|
+
dCheckN: '上次找到 {n} 个问题,点开看',
|
|
462
|
+
dExport: '转成代码、存图片 / PDF / PPT、打包分享',
|
|
463
|
+
dMore: '版本、在手机上看、刷新、在浏览器里打开',
|
|
464
|
+
dZoom: '按住 {mod} 滚动也能缩放',
|
|
465
|
+
dScreen: '换一屏、看全部、多尺寸、换类型',
|
|
466
|
+
toolsT: '工具',
|
|
410
467
|
editAddr: '换一个地址',
|
|
468
|
+
textShort: '改字',
|
|
469
|
+
styleShort: '样式',
|
|
470
|
+
markShort: '画笔',
|
|
471
|
+
viewT: '视图',
|
|
472
|
+
canvasShort: '画布',
|
|
473
|
+
singleShort: '单页',
|
|
474
|
+
sizesShort: '多尺寸',
|
|
475
|
+
look: '外观',
|
|
476
|
+
lookTip: '外观:配色、字体、整套风格',
|
|
477
|
+
deliver: '导出',
|
|
478
|
+
deliverTip: '导出:转成代码、存成图片 / PDF / PPT、打包分享',
|
|
479
|
+
exFiles: '存成文件',
|
|
480
|
+
checkShort: '体检',
|
|
481
|
+
zoomT: '缩放',
|
|
482
|
+
zoomReal: '实际大小',
|
|
483
|
+
actX: '导出',
|
|
484
|
+
actV: '再出两版',
|
|
485
|
+
actO: '单看',
|
|
486
|
+
deliverCode: '代码',
|
|
487
|
+
deliverShare: '分享',
|
|
488
|
+
screensT: '换一屏 / 打开别的页面',
|
|
489
|
+
screensHd: '屏幕',
|
|
490
|
+
screensAll: '全部 {n} 屏',
|
|
491
|
+
screensN: '{name} · {n} 屏',
|
|
492
|
+
openOther: '打开别的页面…',
|
|
493
|
+
phoneOnTip: '手机预览开着',
|
|
494
|
+
lookSet: '整套',
|
|
495
|
+
lookStyle: '换风格',
|
|
496
|
+
lookBrand: '品牌',
|
|
497
|
+
lookColorTip: '点色块直接改,所有屏一起变',
|
|
498
|
+
lookTokTip: '点一下放进输入框,跟模型说怎么改',
|
|
499
|
+
lookTw: '调节滑块',
|
|
500
|
+
lookTwNone: '还没有专属滑块(留白、圆角、排法……)。',
|
|
501
|
+
lookN: '改了 {n} 处,还没保存',
|
|
502
|
+
lookSave: '直接保存',
|
|
503
|
+
lookSaveTip: '写进设计文件(不经过模型;改前改后各存一版)',
|
|
504
|
+
lookAskHead: '〔外观〕我在右侧外观面板里把这几个设计变量改成了:',
|
|
505
|
+
lookAskTail: '照这个改:改 tokens.css(或样式里 :root 上)的默认值,页面里写死的同一种颜色也换成变量。',
|
|
506
|
+
lookInspire: '灵感库',
|
|
507
|
+
lookRef: '照参考图做…',
|
|
508
|
+
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: '区块',
|
|
509
|
+
tkn_bg: '背景', tkn_surface: '卡片底', tkn_text: '正文字', tkn_muted: '次要字', tkn_line: '分隔线', tkn_primary: '主色', tkn_accent: '强调色', tkn_onPrimary: '主色上的字', tkn_link: '链接', tkn_danger: '警示色', tkn_success: '成功色', tkn_warning: '提醒色',
|
|
510
|
+
tkn_xs: '注释', tkn_sm: '小字', tkn_md: '中号字', tkn_body: '正文', tkn_lg: '大字', tkn_title: '标题', tkn_display: '大标题',
|
|
511
|
+
tkn_rSm: '小圆角', tkn_rMd: '中圆角', tkn_rLg: '大圆角', tkn_rFull: '全圆', tkn_r: '圆角',
|
|
512
|
+
tkn_space: '间距', tkn_gutter: '页边距',
|
|
513
|
+
tkn_fHead: '标题字体', tkn_fBody: '正文字体', tkn_fMono: '等宽字体',
|
|
514
|
+
tkn_shadow: '阴影', tkn_light: '浅', tkn_dark: '深', tkn_hover: '悬停',
|
|
515
|
+
phoneOnShort: '开着',
|
|
516
|
+
sizesNoShort: '固定画幅',
|
|
411
517
|
working: '模型正在工作',
|
|
412
518
|
asking: '模型在等你回答(在对话里)',
|
|
413
519
|
writing: '正在写 {file}',
|
|
520
|
+
curLayout: '排版',
|
|
521
|
+
curColor: '配色',
|
|
522
|
+
curCopy: '文案',
|
|
523
|
+
stThink: '在想…',
|
|
524
|
+
stNew: '正在做第 {n} 屏',
|
|
525
|
+
stEdit: '正在改「{name}」',
|
|
526
|
+
stWriteAny: '正在改文件',
|
|
527
|
+
stCheck: '正在体检',
|
|
528
|
+
stLook: '正在看效果',
|
|
529
|
+
stPreview: '正在打开预览',
|
|
530
|
+
stBrand: '正在提取品牌',
|
|
531
|
+
stCode: '正在转成代码',
|
|
532
|
+
stCompare: '正在逐像素对比',
|
|
533
|
+
stShell: '正在跑命令',
|
|
534
|
+
stRead: '正在看文件',
|
|
535
|
+
stWeb: '正在查资料',
|
|
536
|
+
stSkill: '正在看设计规范',
|
|
537
|
+
stTime: '已用时 {time}',
|
|
538
|
+
stLines: '已写 {n} 行',
|
|
414
539
|
born: '已生成 {name}',
|
|
415
540
|
updated: '已更新{file}',
|
|
416
541
|
updatedN: '已更新{file} · {n} 处改动',
|
|
417
542
|
updatedWhole: '已更新{file} · 整页重排',
|
|
418
|
-
|
|
419
|
-
step2: '点「选择」,点元素或拖出一块区域',
|
|
420
|
-
step3: '在输入框补一句要怎么改,发出去',
|
|
421
|
-
tryHead: '不知道怎么开口?点一句试试',
|
|
543
|
+
tryHead: '试试这样说',
|
|
422
544
|
try1: '帮我设计一个社区健身房的会员 App',
|
|
423
545
|
try2: '做一个精品咖啡店的落地页',
|
|
424
546
|
try3: '做一张周末市集的活动海报',
|
|
@@ -427,7 +549,7 @@ window.__ModuleLoader__.load({
|
|
|
427
549
|
copied: '已复制,粘贴到输入框里',
|
|
428
550
|
failed: '没放进去:{msg}',
|
|
429
551
|
openFailed: '打不开:{msg}',
|
|
430
|
-
headerTip: '
|
|
552
|
+
headerTip: '设计台',
|
|
431
553
|
tweaks: '调节滑块',
|
|
432
554
|
twTip: '模型给这套设计做的几个维度:拖的时候所有屏一起变,还没写进文件。',
|
|
433
555
|
twEmpty: '这套设计还没有专属滑块。让模型挑几个最值得试的维度(留白、圆角、主色、排法……),做成滑块放在这里。',
|
|
@@ -439,15 +561,16 @@ window.__ModuleLoader__.load({
|
|
|
439
561
|
twAskTail: '照这个改设计(改样式源文件,tweaks.json 里对应的 value 也改成新值)。',
|
|
440
562
|
twAskTip: '让模型照这几个值改设计(可以顺手调整相关的地方)',
|
|
441
563
|
twSaveTip: 'CSS 变量写进 tokens.css 的默认值,属性写到每屏的 <html> 上(改前改后各存一版)',
|
|
442
|
-
twNoWrite: '
|
|
564
|
+
twNoWrite: '开发服务器的页面不能直接保存,发到对话让模型改',
|
|
565
|
+
twVer: '正在看旧版本:回到当前版本再保存',
|
|
443
566
|
twMoved: '设计已经换了,没写回;再拖一次',
|
|
444
|
-
twSaved: '
|
|
445
|
-
twMissed: '{names}
|
|
567
|
+
twSaved: '已保存 {n} 项',
|
|
568
|
+
twMissed: '{names} 在样式里没找到默认值,没保存:发到对话让模型改',
|
|
446
569
|
systems: '品牌设计系统',
|
|
447
570
|
sysOpen: '品牌设计系统…',
|
|
448
|
-
sysEmptyLine: '
|
|
571
|
+
sysEmptyLine: '用自己的品牌:',
|
|
449
572
|
sysTip: '从你的网站或项目里提取的颜色、字体、圆角和组件。设为默认后,做新设计都照它来。',
|
|
450
|
-
sysEmpty: '
|
|
573
|
+
sysEmpty: '还没有。在上面填你的网站地址或项目文件夹,让模型提一套。',
|
|
451
574
|
sysDefault: '默认',
|
|
452
575
|
sysSet: '设为默认',
|
|
453
576
|
sysUnset: '不再默认',
|
|
@@ -461,7 +584,9 @@ window.__ModuleLoader__.load({
|
|
|
461
584
|
handoff: '转成代码',
|
|
462
585
|
hoTip: '把设计做成真项目的代码:先整理交接说明和截图,再一屏一屏写,每屏写完和设计稿逐像素比,对上了再做下一屏。',
|
|
463
586
|
hoFw: '用什么做',
|
|
464
|
-
hoSame: '
|
|
587
|
+
hoSame: '跟现有项目一样:{label}',
|
|
588
|
+
hoIn: '在 {dir} 里',
|
|
589
|
+
hoEdit: '点一下改路径',
|
|
465
590
|
hoHtml: '纯 HTML / CSS',
|
|
466
591
|
hoUni: 'uni-app(小程序 + 网页版)',
|
|
467
592
|
hoWeapp: '微信原生小程序',
|
|
@@ -480,10 +605,16 @@ window.__ModuleLoader__.load({
|
|
|
480
605
|
// 设计库
|
|
481
606
|
library: '设计库',
|
|
482
607
|
libBack: '回到画布',
|
|
608
|
+
homeBtn: '首页',
|
|
609
|
+
dHome: '回首页:新做一套、接着做别的、逛设计库',
|
|
610
|
+
homeBack: '回到「{name}」',
|
|
611
|
+
askNewLine: '(另起一套新设计,放新文件夹里,别改现在这套)',
|
|
612
|
+
askNewNote: '新做一套,现在这套不会被改',
|
|
613
|
+
wkHere: '正在看',
|
|
483
614
|
libMarket: '市场',
|
|
484
615
|
libWorks: '我的作品',
|
|
485
616
|
libInspire: '灵感',
|
|
486
|
-
libLine: '
|
|
617
|
+
libLine: '以前做的设计、存下的灵感、朋友给的设计包:',
|
|
487
618
|
libOpen: '打开设计库',
|
|
488
619
|
libClose: '返回',
|
|
489
620
|
libFail: '没办成:{msg}',
|
|
@@ -497,7 +628,10 @@ window.__ModuleLoader__.load({
|
|
|
497
628
|
mkOffline: '连不上市场,下面是上次看到的。',
|
|
498
629
|
mkOff: '「设计库不联网」开着:不访问任何网址,只显示下载过的。要联网,去「插件」页关掉「设计库不联网」。',
|
|
499
630
|
mkNeedUpdate: '市场里有新东西要新版设计台才能看:去「插件」页点「更新」。',
|
|
500
|
-
mkAt: '
|
|
631
|
+
mkAt: '目录更新于 {ago}',
|
|
632
|
+
mkUse0: '可商用',
|
|
633
|
+
mkUseBY: '可商用 · 要署名',
|
|
634
|
+
mkSortT: '排序',
|
|
501
635
|
mkSeedNote: '这是设计台自带的一批',
|
|
502
636
|
mkRefresh: '刷新',
|
|
503
637
|
mkTypeTpl: '模板',
|
|
@@ -554,6 +688,9 @@ window.__ModuleLoader__.load({
|
|
|
554
688
|
wkNow: '正在看',
|
|
555
689
|
wkOpen: '打开',
|
|
556
690
|
wkReveal: '在文件夹里显示',
|
|
691
|
+
wkRevealShort: '文件夹',
|
|
692
|
+
wkZipShort: 'zip',
|
|
693
|
+
wkShareShort: '分享',
|
|
557
694
|
wkPin: '置顶',
|
|
558
695
|
wkUnpin: '取消置顶',
|
|
559
696
|
wkZip: '打成 zip',
|
|
@@ -572,7 +709,7 @@ window.__ModuleLoader__.load({
|
|
|
572
709
|
agoHour: '{n} 小时前',
|
|
573
710
|
agoDay: '{n} 天前',
|
|
574
711
|
inMyStyles: '我的风格',
|
|
575
|
-
inSaveStyle: '
|
|
712
|
+
inSaveStyle: '存当前设计',
|
|
576
713
|
inSaveStyleTip: '存下现在这套的配色、字体、圆角,以后做新设计一键用',
|
|
577
714
|
inStyleSaved: '已存成风格「{name}」',
|
|
578
715
|
inStyleNone: '这套设计没用设计变量(tokens.css),存不了风格',
|
|
@@ -644,7 +781,8 @@ window.__ModuleLoader__.load({
|
|
|
644
781
|
exZip: '打成 zip(发给朋友)',
|
|
645
782
|
exShare: '分享到设计市场',
|
|
646
783
|
shTitle: '分享到设计市场',
|
|
647
|
-
|
|
784
|
+
shGh: '投稿要有 GitHub 账号',
|
|
785
|
+
shTip: '交上去的东西是公开的:别人能看、能拿去用。先查一遍,过了打成 zip,再到 GitHub 上交。',
|
|
648
786
|
shName: '名字',
|
|
649
787
|
shDesc: '一句话介绍',
|
|
650
788
|
shDescPh: '比如:街角茶饮店的三屏点单 App',
|
|
@@ -653,7 +791,7 @@ window.__ModuleLoader__.load({
|
|
|
653
791
|
shAgreePrivate: '里面没有真实的个人信息(手机号、邮箱、证件号、地址、真人照片)',
|
|
654
792
|
shAgreeRules: '同意按选的授权公开;上架后随时能撤,但别人已经复制走的收不回',
|
|
655
793
|
shBYNote: 'CC BY 的署名就是你的 GitHub 用户名。',
|
|
656
|
-
shGo: '
|
|
794
|
+
shGo: '检查并投稿',
|
|
657
795
|
shChecking: '在查…',
|
|
658
796
|
shOk: '查过了,打好了:{name}(在「下载」里)',
|
|
659
797
|
shSteps: '还差三步:',
|
|
@@ -673,10 +811,44 @@ window.__ModuleLoader__.load({
|
|
|
673
811
|
title: 'Design Studio',
|
|
674
812
|
blockedLink: 'Blocked an outside link: {href} (designs from the gallery cannot leave the page)',
|
|
675
813
|
blockedDownload: 'Blocked a download (designs from the gallery cannot download files)',
|
|
676
|
-
|
|
814
|
+
tabTitle: 'Design',
|
|
815
|
+
guideDesc: 'Design pages, apps and posters; click to change, then turn into code',
|
|
677
816
|
empty: 'Nothing to preview yet',
|
|
678
|
-
|
|
679
|
-
|
|
817
|
+
emptyHead: 'What do you want to make?',
|
|
818
|
+
emptySub: 'Describe it in a sentence. Screens appear on the canvas one by one; click anything to change it, then turn it into code.',
|
|
819
|
+
homeTab: 'Start',
|
|
820
|
+
askPh: 'e.g. {ex}',
|
|
821
|
+
askTab: 'use this',
|
|
822
|
+
exAuto: 'An ordering mini program for a corner noodle shop, 3 screens, warm hand-made style',
|
|
823
|
+
exWeb: 'Home page for an independent coffee roaster: signature beans, store map, booking',
|
|
824
|
+
exApp: 'Member app for a neighborhood gym: classes, check-ins, membership card',
|
|
825
|
+
exMini: 'Ordering mini program for a noodle shop: menu, cart, pickup number',
|
|
826
|
+
exTablet: 'Tablet app for kindergarten teachers: roll call, timetable, parent messages',
|
|
827
|
+
exDesktop: 'Desktop bookkeeping app for a small team: transactions, reports, settings',
|
|
828
|
+
exPoster: 'Poster for a weekend night run in the city, fresh sporty style',
|
|
829
|
+
exSlides: 'Five-slide launch deck: problem, solution, numbers, pricing, next steps',
|
|
830
|
+
exEmail: 'Birthday-month offer email for members: one big image, a short note, one button',
|
|
831
|
+
askKindLine: 'Type: {k}',
|
|
832
|
+
askMore: 'More ways to start',
|
|
833
|
+
askMoreD: 'Open existing HTML, use your own brand, open from the library',
|
|
834
|
+
askOpenHtml: 'Open existing HTML…',
|
|
835
|
+
askBrand: 'Use your own brand',
|
|
836
|
+
askLib: 'Open from the library',
|
|
837
|
+
askRef: 'Reference',
|
|
838
|
+
askRefD: 'Design from a screenshot or mockup: pick one, or drop / paste it',
|
|
839
|
+
askStyleAuto: 'Style: auto',
|
|
840
|
+
askStyle: 'Style: {name}',
|
|
841
|
+
askStyleD: 'Pick a style first, or let the model decide',
|
|
842
|
+
askStylePick: 'Pick a style',
|
|
843
|
+
askStyleAutoBtn: 'Auto (let the model decide)',
|
|
844
|
+
askSend: 'Start designing',
|
|
845
|
+
askSent: 'Sent. The first screen is on its way',
|
|
846
|
+
homeRecent: 'Continue',
|
|
847
|
+
homeRecentAll: 'All designs →',
|
|
848
|
+
waysHtml: 'Open existing HTML',
|
|
849
|
+
moreWays: 'Other ways to start',
|
|
850
|
+
emptyHint: 'Enter a file path or a local port (like 5173)',
|
|
851
|
+
emptyHintPick: 'Click "Browse…", drop it here, or enter a file path / local port (like 5173)',
|
|
680
852
|
browse: 'Browse…',
|
|
681
853
|
browseTip: 'Pick a local HTML file to open',
|
|
682
854
|
pathNone: "Couldn't get this file's path. Type its path to open it.",
|
|
@@ -726,7 +898,7 @@ window.__ModuleLoader__.load({
|
|
|
726
898
|
refPick: 'Restyle from a reference…',
|
|
727
899
|
refChoose: 'Choose one',
|
|
728
900
|
refPickTip: 'Pick a reference image to restyle after (or drop / paste an image into the preview)',
|
|
729
|
-
refEmpty: '
|
|
901
|
+
refEmpty: 'Design from a reference image: drop it, paste it, or',
|
|
730
902
|
refDrop: 'Drop to use as a reference',
|
|
731
903
|
refDropSub: 'Saved to refs/ in the design folder; its colors go into the input',
|
|
732
904
|
refNotImage: 'Images only (PNG / JPG / WebP / GIF)',
|
|
@@ -772,9 +944,10 @@ window.__ModuleLoader__.load({
|
|
|
772
944
|
gShadow: 'Shadows',
|
|
773
945
|
gOther: 'Other',
|
|
774
946
|
exportT: 'Export',
|
|
775
|
-
exFrame: '
|
|
776
|
-
exRaw: '
|
|
777
|
-
exLong: '
|
|
947
|
+
exFrame: '{name} · in frame',
|
|
948
|
+
exRaw: '{name} · plain',
|
|
949
|
+
exLong: '{name} · full page',
|
|
950
|
+
exThisName: 'This screen',
|
|
778
951
|
exShow: 'Showcase of all screens',
|
|
779
952
|
exPdf: 'PDF · one page per screen',
|
|
780
953
|
exPptx: 'PowerPoint · one slide per screen (as images)',
|
|
@@ -785,7 +958,8 @@ window.__ModuleLoader__.load({
|
|
|
785
958
|
checking: 'Checking…',
|
|
786
959
|
checkNone: 'No problems found',
|
|
787
960
|
checkSum: '{s} screens · {n} problems',
|
|
788
|
-
checkFix: '
|
|
961
|
+
checkFix: 'Send to chat',
|
|
962
|
+
checkFixTip: 'Send these issues to the chat for the model to fix',
|
|
789
963
|
checkAgain: 'Check again',
|
|
790
964
|
checkFail: 'Check failed: {msg}',
|
|
791
965
|
checkNote: 'Only measurable layout problems; whether it looks good is still up to you',
|
|
@@ -884,17 +1058,19 @@ window.__ModuleLoader__.load({
|
|
|
884
1058
|
cmpNow: 'Now',
|
|
885
1059
|
dark: 'Dark mode',
|
|
886
1060
|
variants: 'Two more takes',
|
|
887
|
-
pickThis: 'Use this
|
|
1061
|
+
pickThis: 'Use this take: move the other takes to drafts/ and build the rest',
|
|
888
1062
|
pickShort: 'Use this',
|
|
1063
|
+
takeN: 'Take {l}',
|
|
889
1064
|
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
1065
|
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
1066
|
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
1067
|
exThis: 'Export this screen',
|
|
893
1068
|
demo: 'Play: tap buttons in the page to move between screens',
|
|
894
|
-
demoOn: 'Playing
|
|
1069
|
+
demoOn: 'Playing',
|
|
1070
|
+
demoHow: 'tap buttons to move',
|
|
895
1071
|
demoBack: 'Back',
|
|
896
1072
|
demoRestart: 'Start over',
|
|
897
|
-
demoExit: 'Stop playing
|
|
1073
|
+
demoExit: 'Stop playing',
|
|
898
1074
|
textEdit: 'Edit text: click any text in the page, Enter to save (Esc to stop)',
|
|
899
1075
|
mark: 'Mark up: draw circles, arrows and notes on the page, then hand it to the model as one image',
|
|
900
1076
|
mkPen: 'Pen',
|
|
@@ -909,7 +1085,7 @@ window.__ModuleLoader__.load({
|
|
|
909
1085
|
mkUndo: 'Undo',
|
|
910
1086
|
mkRedo: 'Redo',
|
|
911
1087
|
mkClear: 'Clear all',
|
|
912
|
-
mkHint: 'Draw on the page, then
|
|
1088
|
+
mkHint: 'Draw on the page, then click Send to chat',
|
|
913
1089
|
mkHow_pen: 'Press and drag to draw freehand',
|
|
914
1090
|
mkHow_hl: 'Drag over what needs changing to highlight it',
|
|
915
1091
|
mkHow_arrow: 'Press at the start and drag to what you mean',
|
|
@@ -920,13 +1096,13 @@ window.__ModuleLoader__.load({
|
|
|
920
1096
|
mkCoach: 'Draw right on the page',
|
|
921
1097
|
mkCoach1: 'Pick a tool above: pen, arrow, circle, text…',
|
|
922
1098
|
mkCoach2: 'Press and drag on the page to mark what to change',
|
|
923
|
-
mkCoach3: 'Then click "Send to
|
|
1099
|
+
mkCoach3: 'Then click "Send to chat" and add a line on what to do',
|
|
924
1100
|
mkCancel: 'Cancel',
|
|
925
|
-
mkSend: 'Send to
|
|
1101
|
+
mkSend: 'Send to chat',
|
|
926
1102
|
mkCopy: 'Copy',
|
|
927
1103
|
mkCopyTip: 'Copy the marked-up image to paste somewhere else',
|
|
928
1104
|
mkCopied: 'Markup image copied. Paste it anywhere',
|
|
929
|
-
mkCopyFail: 'Could not copy to the clipboard. Click Copy again, or Send to
|
|
1105
|
+
mkCopyFail: 'Could not copy to the clipboard. Click Copy again, or Send to chat',
|
|
930
1106
|
mkSending: 'Building the image…',
|
|
931
1107
|
mkTextPh: 'Type a note, Enter to finish',
|
|
932
1108
|
mkEmpty: 'Nothing drawn yet',
|
|
@@ -937,7 +1113,7 @@ window.__ModuleLoader__.load({
|
|
|
937
1113
|
mkIn: 'Markup added to the input. Say what to change, then send',
|
|
938
1114
|
mkMany: 'Too many screens marked: captured the first {n}, the rest are gray',
|
|
939
1115
|
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
|
|
1116
|
+
mkNoInput: 'Could not add it to the input. Click the chat input, then Send to chat again',
|
|
941
1117
|
textSaved: 'Saved to {file}',
|
|
942
1118
|
textAsk: 'Change the text "{before}" to "{after}" on the page ({where}; only this text, nothing else).\n',
|
|
943
1119
|
styleEdit: 'Style: click an element, then drag sliders for size, colors, radius and spacing (Esc to stop)',
|
|
@@ -961,14 +1137,14 @@ window.__ModuleLoader__.load({
|
|
|
961
1137
|
styleVar: 'Token {name}',
|
|
962
1138
|
styleGradient: 'It is a gradient now; picking a color makes it solid',
|
|
963
1139
|
styleReset: 'Reset',
|
|
964
|
-
styleAsk: '
|
|
965
|
-
styleSave: 'Save
|
|
1140
|
+
styleAsk: 'Send to chat',
|
|
1141
|
+
styleSave: 'Save',
|
|
966
1142
|
styleSaveTip: 'Writes into this element\'s style attribute in the source (a version is kept before and after)',
|
|
967
1143
|
styleAskTip: 'Let the model move it into the stylesheet (using tokens where it can)',
|
|
968
|
-
styleNoWrite: 'Dev-server pages
|
|
1144
|
+
styleNoWrite: 'Dev-server pages cannot be saved here; send it to the chat',
|
|
969
1145
|
styleBusyTip: 'The model is working; save when it stops',
|
|
970
1146
|
styleSaved: 'Style saved to {file}',
|
|
971
|
-
styleMiss: 'Could not find this element in the source (maybe a script made it);
|
|
1147
|
+
styleMiss: 'Could not find this element in the source (maybe a script made it); sending it to the chat instead',
|
|
972
1148
|
styleN: '{n} changed',
|
|
973
1149
|
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
1150
|
ruleFrom: '- {props}: currently set by {sel} ({at})',
|
|
@@ -979,11 +1155,13 @@ window.__ModuleLoader__.load({
|
|
|
979
1155
|
styleArrow: '{prop} {from} → {to}',
|
|
980
1156
|
styleJoin: '; ',
|
|
981
1157
|
lang: 'en',
|
|
982
|
-
stylesHead: '
|
|
983
|
-
stylesTip: '
|
|
1158
|
+
stylesHead: 'Or pick a style first',
|
|
1159
|
+
stylesTip: 'Open one to preview, click "Use this style", then say what to make',
|
|
1160
|
+
stylesMore: 'More styles ({n})',
|
|
1161
|
+
stylesLess: 'Show less',
|
|
984
1162
|
stylesOpen: 'Try another style…',
|
|
985
1163
|
stylesTitle: 'Change the style',
|
|
986
|
-
stylesSwap: 'Pick one to see a sample; "Use this style" has the model restyle the current design (
|
|
1164
|
+
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
1165
|
stylesAll: 'All styles',
|
|
988
1166
|
stylePrev: 'Previous (←)',
|
|
989
1167
|
styleNext: 'Next (→)',
|
|
@@ -1026,29 +1204,110 @@ window.__ModuleLoader__.load({
|
|
|
1026
1204
|
phoneAuto: 'Only a phone that scanned this code can open it. Closes after 30 minutes idle.',
|
|
1027
1205
|
phoneNoLan: 'This computer is not on Wi-Fi or a wired network, so a phone cannot reach it.',
|
|
1028
1206
|
phoneFail: 'Could not start: {msg}',
|
|
1029
|
-
phoneHelp:
|
|
1207
|
+
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.',
|
|
1208
|
+
phoneTrouble: "Won't open?",
|
|
1030
1209
|
zoomIn: 'Zoom in',
|
|
1031
1210
|
zoomOut: 'Zoom out',
|
|
1032
1211
|
fit: 'Fit',
|
|
1033
1212
|
openScreen: 'Double-click to view this screen alone',
|
|
1034
|
-
generating: '
|
|
1213
|
+
generating: 'Designing…',
|
|
1035
1214
|
waiting: 'The model is working. The page shows up here once it is written',
|
|
1215
|
+
waitingLive: 'The page is being written piece by piece—click to edit once it’s done',
|
|
1036
1216
|
refresh: 'Reload',
|
|
1037
1217
|
external: 'Open in browser',
|
|
1038
1218
|
more: 'More',
|
|
1039
1219
|
demoShort: 'Play',
|
|
1220
|
+
dPick: 'Click an element or drag a region to send it to the model',
|
|
1221
|
+
dText: 'Click any text in the page and type, Enter to save',
|
|
1222
|
+
dStyle: 'Click an element, then drag sliders for size, colors, radius and spacing',
|
|
1223
|
+
dMark: 'Draw circles, arrows and notes, sent to the model as one image',
|
|
1224
|
+
dPlay: 'Click through it like a real app; buttons jump to their screens',
|
|
1225
|
+
dLib: 'Saved designs, styles and templates, plus the design market',
|
|
1226
|
+
dLook: 'Change colors and fonts, or switch the whole style',
|
|
1227
|
+
dCheck: 'Find overflowing or overlapping text, low contrast and tiny tap targets',
|
|
1228
|
+
dCheckN: '{n} problems last time. Click to see them',
|
|
1229
|
+
dExport: 'Build as code, save images / PDF / PPT, or pack and share',
|
|
1230
|
+
dMore: 'Versions, view on phone, refresh, open in browser',
|
|
1231
|
+
dZoom: 'Hold {mod} and scroll to zoom too',
|
|
1232
|
+
dScreen: 'Switch screens, see all, sizes or type',
|
|
1233
|
+
toolsT: 'Tools',
|
|
1040
1234
|
editAddr: 'Change address',
|
|
1235
|
+
textShort: 'Text',
|
|
1236
|
+
styleShort: 'Style',
|
|
1237
|
+
markShort: 'Draw',
|
|
1238
|
+
viewT: 'View',
|
|
1239
|
+
canvasShort: 'Canvas',
|
|
1240
|
+
singleShort: 'Single',
|
|
1241
|
+
sizesShort: 'Sizes',
|
|
1242
|
+
look: 'Look',
|
|
1243
|
+
lookTip: 'Look: colors, fonts and overall style',
|
|
1244
|
+
deliver: 'Export',
|
|
1245
|
+
deliverTip: 'Export: build as code, save images / PDF / PPT, pack and share',
|
|
1246
|
+
exFiles: 'Save as file',
|
|
1247
|
+
checkShort: 'Check',
|
|
1248
|
+
zoomT: 'Zoom',
|
|
1249
|
+
zoomReal: 'Actual size',
|
|
1250
|
+
actX: 'Export',
|
|
1251
|
+
actV: 'More takes',
|
|
1252
|
+
actO: 'Open',
|
|
1253
|
+
deliverCode: 'Code',
|
|
1254
|
+
deliverShare: 'Share',
|
|
1255
|
+
screensT: 'Switch screen / open another page',
|
|
1256
|
+
screensHd: 'Screens',
|
|
1257
|
+
screensAll: 'All {n}',
|
|
1258
|
+
screensN: '{name} · {n} screens',
|
|
1259
|
+
openOther: 'Open another page…',
|
|
1260
|
+
phoneOnTip: 'Phone preview is on',
|
|
1261
|
+
lookSet: 'Overall',
|
|
1262
|
+
lookStyle: 'Restyle',
|
|
1263
|
+
lookBrand: 'Brand',
|
|
1264
|
+
lookColorTip: 'Click a swatch to change it; every screen follows',
|
|
1265
|
+
lookTokTip: 'Click to put it in the input, then tell the model how to change it',
|
|
1266
|
+
lookTw: 'Tweaks',
|
|
1267
|
+
lookTwNone: 'No tweaks for this design yet (spacing, corners, layout…).',
|
|
1268
|
+
lookN: '{n} change(s), not saved',
|
|
1269
|
+
lookSave: 'Save',
|
|
1270
|
+
lookSaveTip: 'Write into the design files (no model; a version is kept before and after)',
|
|
1271
|
+
lookAskHead: '[Look] I changed these design tokens in the Look panel:',
|
|
1272
|
+
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.',
|
|
1273
|
+
lookInspire: 'Inspiration',
|
|
1274
|
+
lookRef: 'From a reference image…',
|
|
1275
|
+
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',
|
|
1276
|
+
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',
|
|
1277
|
+
tkn_xs: 'Caption', tkn_sm: 'Small', tkn_md: 'Medium', tkn_body: 'Body', tkn_lg: 'Large', tkn_title: 'Title', tkn_display: 'Display',
|
|
1278
|
+
tkn_rSm: 'Small corners', tkn_rMd: 'Medium corners', tkn_rLg: 'Large corners', tkn_rFull: 'Pill', tkn_r: 'Corners',
|
|
1279
|
+
tkn_space: 'Spacing', tkn_gutter: 'Page margin',
|
|
1280
|
+
tkn_fHead: 'Heading font', tkn_fBody: 'Body font', tkn_fMono: 'Mono font',
|
|
1281
|
+
tkn_shadow: 'Shadow', tkn_light: 'light', tkn_dark: 'dark', tkn_hover: 'hover',
|
|
1282
|
+
phoneOnShort: 'On',
|
|
1283
|
+
sizesNoShort: 'Fixed size',
|
|
1041
1284
|
working: 'The model is working',
|
|
1042
1285
|
asking: 'The model is waiting for your answer (in the chat)',
|
|
1043
1286
|
writing: 'Writing {file}',
|
|
1287
|
+
curLayout: 'Layout',
|
|
1288
|
+
curColor: 'Color',
|
|
1289
|
+
curCopy: 'Copy',
|
|
1290
|
+
stThink: 'Thinking…',
|
|
1291
|
+
stNew: 'Designing screen {n}',
|
|
1292
|
+
stEdit: 'Editing "{name}"',
|
|
1293
|
+
stWriteAny: 'Editing files',
|
|
1294
|
+
stCheck: 'Checking the design',
|
|
1295
|
+
stLook: 'Looking at the result',
|
|
1296
|
+
stPreview: 'Opening the preview',
|
|
1297
|
+
stBrand: 'Extracting the brand',
|
|
1298
|
+
stCode: 'Turning it into code',
|
|
1299
|
+
stCompare: 'Comparing pixel by pixel',
|
|
1300
|
+
stShell: 'Running a command',
|
|
1301
|
+
stRead: 'Reading files',
|
|
1302
|
+
stWeb: 'Looking things up',
|
|
1303
|
+
stSkill: 'Reading the design guide',
|
|
1304
|
+
stTime: '{time} elapsed',
|
|
1305
|
+
stLines: '{n} lines so far',
|
|
1044
1306
|
born: 'Generated {name}',
|
|
1045
1307
|
updated: 'Updated{file}',
|
|
1046
1308
|
updatedN: 'Updated{file} · {n} changes',
|
|
1047
1309
|
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',
|
|
1310
|
+
tryHead: 'Try saying',
|
|
1052
1311
|
try1: 'Design a member app for a neighborhood gym',
|
|
1053
1312
|
try2: 'Make a landing page for a specialty coffee shop',
|
|
1054
1313
|
try3: 'Make a poster for a weekend street market',
|
|
@@ -1069,15 +1328,16 @@ window.__ModuleLoader__.load({
|
|
|
1069
1328
|
twAskTail: 'Change the design to match (edit the style sources and update the matching values in tweaks.json).',
|
|
1070
1329
|
twAskTip: 'Let the model apply these values (and adjust related details)',
|
|
1071
1330
|
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
|
|
1331
|
+
twNoWrite: 'Dev-server pages cannot be saved here. Send it to the chat instead',
|
|
1332
|
+
twVer: 'Viewing an old version. Go back to the current one to save',
|
|
1073
1333
|
twMoved: 'The design changed, so nothing was written. Try again',
|
|
1074
|
-
twSaved: '
|
|
1075
|
-
twMissed: 'No default found in the styles for {names}, not
|
|
1334
|
+
twSaved: 'Saved {n}',
|
|
1335
|
+
twMissed: 'No default found in the styles for {names}, not saved. Send it to the chat instead',
|
|
1076
1336
|
systems: 'Brand design systems',
|
|
1077
1337
|
sysOpen: 'Brand design systems…',
|
|
1078
|
-
sysEmptyLine: '
|
|
1338
|
+
sysEmptyLine: 'Use your own brand:',
|
|
1079
1339
|
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
|
|
1340
|
+
sysEmpty: 'None yet. Enter your website or project folder above and let the model extract one.',
|
|
1081
1341
|
sysDefault: 'Default',
|
|
1082
1342
|
sysSet: 'Make default',
|
|
1083
1343
|
sysUnset: 'Stop using',
|
|
@@ -1091,7 +1351,9 @@ window.__ModuleLoader__.load({
|
|
|
1091
1351
|
handoff: 'Build as code',
|
|
1092
1352
|
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
1353
|
hoFw: 'Build with',
|
|
1094
|
-
hoSame: 'Same as the project
|
|
1354
|
+
hoSame: 'Same as the project: {label}',
|
|
1355
|
+
hoIn: 'in {dir}',
|
|
1356
|
+
hoEdit: 'Click to edit the path',
|
|
1095
1357
|
hoHtml: 'Plain HTML / CSS',
|
|
1096
1358
|
hoUni: 'uni-app (mini program + H5)',
|
|
1097
1359
|
hoWeapp: 'WeChat native mini program',
|
|
@@ -1109,10 +1371,16 @@ window.__ModuleLoader__.load({
|
|
|
1109
1371
|
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
1372
|
library: 'Library',
|
|
1111
1373
|
libBack: 'Back to canvas',
|
|
1374
|
+
homeBtn: 'Home',
|
|
1375
|
+
dHome: 'Go home: start a new design, pick up another one, browse the library',
|
|
1376
|
+
homeBack: 'Back to "{name}"',
|
|
1377
|
+
askNewLine: '(Start a new design in a new folder; leave the current one as is.)',
|
|
1378
|
+
askNewNote: 'This starts a new design; the current one stays as is',
|
|
1379
|
+
wkHere: 'Open now',
|
|
1112
1380
|
libMarket: 'Gallery',
|
|
1113
1381
|
libWorks: 'My designs',
|
|
1114
1382
|
libInspire: 'Inspiration',
|
|
1115
|
-
libLine: 'Past designs, saved inspiration, design packs from friends',
|
|
1383
|
+
libLine: 'Past designs, saved inspiration, design packs from friends:',
|
|
1116
1384
|
libOpen: 'Open the library',
|
|
1117
1385
|
libClose: 'Back',
|
|
1118
1386
|
libFail: 'That did not work: {msg}',
|
|
@@ -1126,7 +1394,10 @@ window.__ModuleLoader__.load({
|
|
|
1126
1394
|
mkOffline: 'Cannot reach the gallery; showing what you saw last time.',
|
|
1127
1395
|
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
1396
|
mkNeedUpdate: 'Some gallery items need a newer Design Studio: click Update on the Plugins page.',
|
|
1129
|
-
mkAt: 'Catalog
|
|
1397
|
+
mkAt: 'Catalog updated {ago}',
|
|
1398
|
+
mkUse0: 'Free to use',
|
|
1399
|
+
mkUseBY: 'Free to use · credit',
|
|
1400
|
+
mkSortT: 'Sort',
|
|
1130
1401
|
mkSeedNote: 'This is the set bundled with the studio',
|
|
1131
1402
|
mkRefresh: 'Refresh',
|
|
1132
1403
|
mkTypeTpl: 'Templates',
|
|
@@ -1183,6 +1454,9 @@ window.__ModuleLoader__.load({
|
|
|
1183
1454
|
wkNow: 'Open now',
|
|
1184
1455
|
wkOpen: 'Open',
|
|
1185
1456
|
wkReveal: 'Show in folder',
|
|
1457
|
+
wkRevealShort: 'Folder',
|
|
1458
|
+
wkZipShort: 'Zip',
|
|
1459
|
+
wkShareShort: 'Share',
|
|
1186
1460
|
wkPin: 'Pin',
|
|
1187
1461
|
wkUnpin: 'Unpin',
|
|
1188
1462
|
wkZip: 'Save as zip',
|
|
@@ -1201,7 +1475,7 @@ window.__ModuleLoader__.load({
|
|
|
1201
1475
|
agoHour: '{n} h ago',
|
|
1202
1476
|
agoDay: '{n} d ago',
|
|
1203
1477
|
inMyStyles: 'My styles',
|
|
1204
|
-
inSaveStyle: 'Save
|
|
1478
|
+
inSaveStyle: 'Save current',
|
|
1205
1479
|
inSaveStyleTip: 'Keep its colors, fonts and corners to reuse in one click',
|
|
1206
1480
|
inStyleSaved: 'Saved as style "{name}"',
|
|
1207
1481
|
inStyleNone: 'This design has no design tokens (tokens.css), so there is no style to save',
|
|
@@ -1273,7 +1547,8 @@ window.__ModuleLoader__.load({
|
|
|
1273
1547
|
exZip: 'Save as zip (to send)',
|
|
1274
1548
|
exShare: 'Share to the gallery',
|
|
1275
1549
|
shTitle: 'Share to the gallery',
|
|
1276
|
-
|
|
1550
|
+
shGh: 'You need a GitHub account to submit',
|
|
1551
|
+
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
1552
|
shName: 'Name',
|
|
1278
1553
|
shDesc: 'One-line description',
|
|
1279
1554
|
shDescPh: 'e.g. A three-screen ordering app for a corner tea shop',
|
|
@@ -1282,7 +1557,7 @@ window.__ModuleLoader__.load({
|
|
|
1282
1557
|
shAgreePrivate: 'It contains no real personal data (phone, email, ID numbers, address, photos of real people)',
|
|
1283
1558
|
shAgreeRules: 'I agree to publish it under the chosen license; it can be taken down any time, but copies others already made stay',
|
|
1284
1559
|
shBYNote: 'For CC BY, the credit is your GitHub username.',
|
|
1285
|
-
shGo: 'Check and
|
|
1560
|
+
shGo: 'Check and submit',
|
|
1286
1561
|
shChecking: 'Checking…',
|
|
1287
1562
|
shOk: 'Checked and saved: {name} (in Downloads)',
|
|
1288
1563
|
shSteps: 'Three more steps:',
|
|
@@ -1315,6 +1590,8 @@ window.__ModuleLoader__.load({
|
|
|
1315
1590
|
// ── 样式(DSH 设计变量,深浅主题跟着走)──────────────────────────────────
|
|
1316
1591
|
|
|
1317
1592
|
const ACCENT = '#4d6bfe'
|
|
1593
|
+
const TOP_R = 52 // 画布上面一排浮块占的高度:单页、演示、对比让开它,画布排的时候也让开
|
|
1594
|
+
const BOT_R = 64 // 底下一排(工具坞、视图、缩放)占的高度
|
|
1318
1595
|
const PICK_SRC = 'dsh-design-studio' // 输入框里点选标签的来源名(样式里也用)
|
|
1319
1596
|
// 输入框里的各种标签:图标按来源名分(DSH 的标签上只带来源名),消息里和悬停卡片按 data-i
|
|
1320
1597
|
const CHIP_ICON = {
|
|
@@ -1335,11 +1612,73 @@ window.__ModuleLoader__.load({
|
|
|
1335
1612
|
const EASE = 'cubic-bezier(.2,.8,.2,1)'
|
|
1336
1613
|
const CSS = `
|
|
1337
1614
|
.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-
|
|
1615
|
+
/* 浮在画布上的几块:上面一排(左上 设计库 + 屏名,右上 外观 体检 交付 更多)、底部中间工具坞、左下视图和类型、右下缩放 */
|
|
1616
|
+
.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);
|
|
1617
|
+
box-shadow:0 6px 20px rgba(0,0,0,.12),0 0 0 .5px var(--dsw-alias-border-l3)}
|
|
1618
|
+
.ddp-isl .ddp-btn{height:30px;min-width:30px;border-radius:8px}
|
|
1619
|
+
.ddp-isl .ddp-btn svg{width:16px;height:16px}
|
|
1620
|
+
.ddp-isl .ddp-btn .cr{width:10px;height:10px}
|
|
1621
|
+
.ddp-isl .ddp-sep{height:18px;margin:0 3px}
|
|
1622
|
+
.ddp-isl .ddp-menu{top:calc(100% + 8px)}
|
|
1623
|
+
.ddp-top{position:absolute;left:10px;right:10px;top:10px;z-index:9;display:flex;align-items:flex-start;gap:8px;pointer-events:none}
|
|
1624
|
+
.ddp-top>.ddp-isl{pointer-events:auto}
|
|
1625
|
+
.ddp-top>.tl{flex:0 1 auto;min-width:0}
|
|
1626
|
+
.ddp-top>.tl.wide{flex:1 1 auto;max-width:560px}
|
|
1627
|
+
.ddp-top>.tl.wide .ddp-input{height:30px}
|
|
1628
|
+
.ddp-top>.tr{margin-left:auto;flex:none}
|
|
1629
|
+
.ddp-isl .ddp-btn.pri{padding:0 8px 0 9px;background:${ACCENT};color:#fff;font-weight:500}
|
|
1630
|
+
.ddp-isl .ddp-btn.pri:hover,.ddp-isl .ddp-btn.pri.sel{background:#3d5bf0;color:#fff}
|
|
1631
|
+
.ddp-isl .ddp-btn.pri .cr{opacity:.8}
|
|
1632
|
+
.ddp-dock{position:absolute;left:50%;bottom:10px;transform:translateX(-50%);padding:4px;gap:2px}
|
|
1633
|
+
.ddp-dock .ddp-sep{height:26px}
|
|
1634
|
+
/* 悬停说明:深底浅字(跟提示消息一个配色,深色模式自己反过来);第一行名字 + 快捷键键帽,第二行一句用途
|
|
1635
|
+
说明控制在两行内,text-wrap:pretty 不让一个字单独掉到下一行 */
|
|
1636
|
+
.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;
|
|
1637
|
+
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)}
|
|
1638
|
+
.ddp-tipbox.on{display:flex;animation:ddp-tip .12s ease}
|
|
1639
|
+
.ddp-tipbox .nm{display:flex;align-items:center;gap:10px}
|
|
1640
|
+
.ddp-tipbox b{font-weight:600}
|
|
1641
|
+
.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}
|
|
1642
|
+
.ddp-tipbox span{opacity:.72;text-wrap:pretty}
|
|
1643
|
+
@keyframes ddp-tip{from{opacity:0}}
|
|
1644
|
+
.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;
|
|
1645
|
+
color:var(--dsw-alias-label-secondary);font:inherit;font-size:11px;line-height:13px;cursor:pointer;flex:none;white-space:nowrap}
|
|
1646
|
+
.ddp-tool svg{width:18px;height:18px;flex:none}
|
|
1647
|
+
.ddp-tool:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary)}
|
|
1648
|
+
.ddp-tool.on,.ddp-tool.on:hover:not(:disabled){background:${ACCENT};color:#fff}
|
|
1649
|
+
.ddp-tool:disabled{opacity:.4;cursor:default}
|
|
1650
|
+
.ddp-isl .ddp-btn.lab{padding:0 9px 0 7px}
|
|
1651
|
+
.ddp-isl .ddp-btn .lb{font-size:12px}
|
|
1652
|
+
/* 左上菜单里的「类型」:四列小格,一点就换 */
|
|
1653
|
+
.ddp-menu.l{min-width:268px}
|
|
1654
|
+
.ddp-kinds{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:2px;padding:0 2px 4px}
|
|
1655
|
+
.ddp-menu .ddp-kinds button{flex-direction:column;justify-content:center;gap:4px;height:50px;padding:0 2px;font-size:11px;text-align:center}
|
|
1656
|
+
.ddp-menu .ddp-kinds button span{max-width:100%}
|
|
1657
|
+
.ddp-menu .ddp-kinds button.sel{background:rgba(77,107,254,.12)}
|
|
1658
|
+
/* 标注时它有自己的上下两条:浮块都收起来 */
|
|
1659
|
+
.ddp-stage:has(>.ddp-mark)>.ddp-top,.ddp-stage:has(>.ddp-mark)>.ddp-isl{display:none}
|
|
1660
|
+
/* 上下两排浮块让出来:提示、消息、横幅、生成中、右边的面板都挪到两排中间 */
|
|
1661
|
+
.ddp-stage.isl>.ddp-hint{top:58px}
|
|
1662
|
+
.ddp-stage.isl>.ddp-toast:not(.mk){top:58px}
|
|
1663
|
+
.ddp-stage.isl>.ddp-toast.low{top:104px}
|
|
1664
|
+
.ddp-stage.isl>.ddp-banner{top:58px}
|
|
1665
|
+
.ddp-stage.isl>.ddp-chip{bottom:76px}
|
|
1666
|
+
.ddp-stage.isl>.ddp-chip.side{left:calc((100% - 296px) / 2)}
|
|
1667
|
+
.ddp-stage.isl>.ddp-tokens{top:58px;max-height:calc(100% - 58px - 74px)}
|
|
1668
|
+
.ddp-kind.ddp-where{flex:0 1 auto;min-width:80px;max-width:320px}
|
|
1669
|
+
.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}
|
|
1670
|
+
.ddp-addr:hover,.ddp-addr.sel{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}
|
|
1671
|
+
.ddp-addr span.lb2 em{color:var(--dsw-alias-label-tertiary);font-weight:400}
|
|
1672
|
+
.ddp-addr span{flex:1;min-width:0;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
1673
|
+
/* 窄了先截名字,「· 3 屏」「· B」留着 */
|
|
1674
|
+
.ddp-addr span.lb2{display:flex;overflow:visible}
|
|
1675
|
+
.ddp-addr span.lb2 b{min-width:0;font-weight:inherit;overflow:hidden;text-overflow:ellipsis}
|
|
1676
|
+
.ddp-addr span.lb2 em{flex:none;font-style:normal;white-space:pre}
|
|
1677
|
+
.ddp-addr .cr{width:10px;height:10px;flex:none;opacity:.55}
|
|
1678
|
+
.ddp-menu.l{left:0;right:auto;max-width:min(320px,calc(100cqw - 16px))}
|
|
1679
|
+
.ddp-menu .ls{max-height:min(360px,55vh);overflow-y:auto}
|
|
1680
|
+
.ddp-menu button>span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
1681
|
+
.ddp-menu .sp{height:.5px;margin:4px 6px;background:var(--dsw-alias-border-l3)}
|
|
1343
1682
|
.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
1683
|
.ddp-dot.off{background:var(--dsw-alias-label-tertiary);box-shadow:none}
|
|
1345
1684
|
.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 +1710,6 @@ window.__ModuleLoader__.load({
|
|
|
1371
1710
|
.ddp-menu button.sel,.ddp-menu button.sel svg{color:${ACCENT}}
|
|
1372
1711
|
.ddp-menu em{margin-left:auto;font-style:normal;color:var(--dsw-alias-label-tertiary);font-size:11px;font-variant-numeric:tabular-nums}
|
|
1373
1712
|
.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
1713
|
.ddp-scr{position:absolute;inset:0;overflow:hidden}
|
|
1376
1714
|
.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
1715
|
.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 +1741,12 @@ window.__ModuleLoader__.load({
|
|
|
1403
1741
|
.ddp-cap.w .sp{stroke:rgba(255,255,255,.25)}
|
|
1404
1742
|
.ddp-cap.w .rg{stroke:#fff}
|
|
1405
1743
|
.ddp-body{position:absolute;inset:0}
|
|
1406
|
-
.ddp-body.
|
|
1744
|
+
.ddp-body.pad{top:${TOP_R}px;bottom:${BOT_R}px}
|
|
1745
|
+
.ddp-body.pad.vb{top:${TOP_R + 46}px}
|
|
1746
|
+
.ddp-body.pad.ease{transition:top .42s ${EASE}}
|
|
1747
|
+
.ddp-body.pad.ease .ddp-fit{transition:transform .42s ${EASE}}
|
|
1748
|
+
/* 网页单页夹在上下两排浮块中间:描一条细边,和画布底色分开 */
|
|
1749
|
+
.ddp-body.pad .ddp-dev.fill{box-shadow:0 0 0 .5px var(--dsw-alias-border-l3)}
|
|
1407
1750
|
.ddp-single{position:absolute;inset:0;overflow:hidden}
|
|
1408
1751
|
.ddp-fit{position:absolute;left:50%;top:50%;transform-origin:center center}
|
|
1409
1752
|
.ddp-fit.fill{left:0;top:0;width:100%;height:100%;transform:none}
|
|
@@ -1421,15 +1764,64 @@ window.__ModuleLoader__.load({
|
|
|
1421
1764
|
.ddp-lab span{overflow:hidden;text-overflow:ellipsis}
|
|
1422
1765
|
.ddp-cell.sel .ddp-lab{color:${ACCENT};font-weight:500}
|
|
1423
1766
|
.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
1767
|
.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
1768
|
.ddp-cell.ghost .ddp-lab{color:${ACCENT}}
|
|
1427
1769
|
@keyframes ddp-ghost{50%{opacity:.62}}
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
.ddp-
|
|
1431
|
-
|
|
1432
|
-
.ddp-
|
|
1770
|
+
/* 生成中的占位:鼠标框版面 */
|
|
1771
|
+
.ddp-gd{position:absolute;inset:0;z-index:2;overflow:hidden;background:var(--dsw-alias-bg-layer-1);pointer-events:none}
|
|
1772
|
+
.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));
|
|
1773
|
+
background:color-mix(in srgb,var(--c) 9%,transparent);overflow:hidden}
|
|
1774
|
+
.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}
|
|
1775
|
+
.ddp-gd .c{position:absolute;z-index:2;display:block;width:0;height:0}
|
|
1776
|
+
.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))}
|
|
1777
|
+
.ddp-gd .c path{fill:var(--c);stroke:#fff;stroke-width:1.5;stroke-linejoin:round}
|
|
1778
|
+
.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));
|
|
1779
|
+
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;
|
|
1780
|
+
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)}
|
|
1781
|
+
.ddp-gd .c.flip em{left:auto;right:calc(-4px / var(--z, 1))}
|
|
1782
|
+
/* 边写边看:正在写的那页盖在占位上,页面报到了才淡入 */
|
|
1783
|
+
.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}
|
|
1784
|
+
.ddp-draft.on{opacity:1}
|
|
1785
|
+
.ddp-fitd{position:absolute;inset:0;z-index:3;overflow:hidden}
|
|
1786
|
+
.ddp-fitd>div{position:absolute;left:50%;top:50%;transform-origin:center center}
|
|
1787
|
+
/* 顶上的状态条:在做什么 · 用了多久;三个点是三只鼠标的颜色 */
|
|
1788
|
+
.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;
|
|
1789
|
+
padding:0 12px 0 11px;border-radius:999px;overflow:hidden;background:rgba(18,18,24,.84);color:#f1f1f4;font-size:12px;line-height:16px;
|
|
1790
|
+
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}}
|
|
1791
|
+
.ddp-busy.side{left:calc((100% - 296px) / 2);max-width:calc(100% - 320px)}
|
|
1792
|
+
.ddp-stage.isl>.ddp-busy{top:58px}
|
|
1793
|
+
/* 右侧栏够宽:状态条放进上排两块浮块中间,不挡画布 */
|
|
1794
|
+
@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}}
|
|
1795
|
+
.ddp-busy .dots{display:flex;gap:3px;flex:none}
|
|
1796
|
+
.ddp-busy .dots i{width:5px;height:5px;border-radius:50%;background:#6d87ff;animation:ddp-hop 1.2s ease-in-out infinite}
|
|
1797
|
+
.ddp-busy .dots i:nth-child(2){background:#ff8a4c;animation-delay:.15s}
|
|
1798
|
+
.ddp-busy .dots i:nth-child(3){background:#2fd3a0;animation-delay:.3s}
|
|
1799
|
+
.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)}
|
|
1800
|
+
.ddp-busy .tx{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
|
|
1801
|
+
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}
|
|
1802
|
+
.ddp-busy.ask .tx{background:none;color:#f1f1f4;animation:none}
|
|
1803
|
+
.ddp-busy .sub{min-width:0;flex:0 1000 auto;color:rgba(241,241,244,.5);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
1804
|
+
.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}
|
|
1805
|
+
.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}
|
|
1806
|
+
@keyframes ddp-hop{0%,60%,100%{transform:translateY(0)}30%{transform:translateY(-3px)}}
|
|
1807
|
+
@keyframes ddp-sweep{from{background-position:-60% 0}to{background-position:160% 0}}
|
|
1808
|
+
@keyframes ddp-drop{from{opacity:0;transform:translate(-50%,-8px)}}
|
|
1809
|
+
/* 状态条占了顶上那格:提示、消息、横幅往下挪一格 */
|
|
1810
|
+
.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}
|
|
1811
|
+
.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}
|
|
1812
|
+
.ddp-stage.isl.bz>.ddp-toast.low{top:146px}
|
|
1813
|
+
.ddp-stage.isl.bz>.ddp-tokens{max-height:calc(100% - 100px - 74px)}
|
|
1814
|
+
@media (prefers-reduced-motion:reduce){
|
|
1815
|
+
.ddp-busy,.ddp-busy *,.ddp-gd *,.ddp-gd .b::after{animation:none!important}
|
|
1816
|
+
.ddp-busy .bar{display:none}
|
|
1817
|
+
.ddp-body.pad.ease,.ddp-body.pad.ease .ddp-fit{transition:none}
|
|
1818
|
+
}
|
|
1819
|
+
.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);
|
|
1820
|
+
box-shadow:0 6px 20px rgba(0,0,0,.12),0 0 0 .5px var(--dsw-alias-border-l3)}
|
|
1821
|
+
.ddp-zoom .ddp-btn{height:30px;min-width:30px;padding:0 4px;border-radius:8px}
|
|
1822
|
+
.ddp-zoom .ddp-btn svg{width:15px;height:15px}
|
|
1823
|
+
.ddp-zoom .ddp-btn .lb{min-width:30px;text-align:right;font-variant-numeric:tabular-nums}
|
|
1824
|
+
.ddp-zoom .ddp-menu{top:auto;bottom:calc(100% + 8px);min-width:168px}
|
|
1433
1825
|
.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
1826
|
background:rgba(18,18,24,.84);color:#ececf0;font-size:12px;line-height:18px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
|
|
1435
1827
|
box-shadow:0 6px 18px rgba(0,0,0,.22);backdrop-filter:blur(8px);pointer-events:none;animation:ddp-in .2s ease}
|
|
@@ -1487,7 +1879,7 @@ window.__ModuleLoader__.load({
|
|
|
1487
1879
|
.ddp-mk-bar button.go{background:${ACCENT};color:#fff;font-weight:600}
|
|
1488
1880
|
.ddp-mk-bar button.go:hover:not(:disabled){background:#3d5bf0}
|
|
1489
1881
|
.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%;
|
|
1882
|
+
.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
1883
|
.ddp-mk-coach b{display:block;margin-bottom:6px;font-size:14px;font-weight:600;color:#fff}
|
|
1492
1884
|
.ddp-mk-coach ol{margin:0;padding:0 0 0 1.3em;color:rgba(244,244,245,.86)}
|
|
1493
1885
|
.ddp-mk-coach li+li{margin-top:2px}
|
|
@@ -1499,38 +1891,30 @@ window.__ModuleLoader__.load({
|
|
|
1499
1891
|
@keyframes ddp-shine{100%{transform:translateX(100%)}}
|
|
1500
1892
|
.ddp-wait{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;padding:24px}
|
|
1501
1893
|
.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}
|
|
1894
|
+
.ddp-wait .card.k-app,.ddp-wait .card.k-miniprogram{width:auto;height:min(62%,560px);aspect-ratio:390/844;border-radius:22px}
|
|
1895
|
+
.ddp-wait .card.k-poster{width:auto;height:min(62%,560px);aspect-ratio:3/4}
|
|
1896
|
+
.ddp-wait .card.k-slides{aspect-ratio:16/9}
|
|
1897
|
+
.ddp-wait .card.k-email{width:min(60%,380px);aspect-ratio:3/4}
|
|
1898
|
+
.ddp-wait .card:has(.ddp-gd){animation:none}
|
|
1502
1899
|
.ddp-wait p{margin:0;color:var(--dsw-alias-label-tertiary);text-align:center;line-height:1.6}
|
|
1503
1900
|
.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}
|
|
1901
|
+
.ddp-home form{display:flex;flex-wrap:wrap;gap:6px;width:100%;margin:0}
|
|
1522
1902
|
.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
1903
|
.ddp-browse:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}
|
|
1524
1904
|
.ddp-drop .in.no{border-color:var(--dsw-alias-label-tertiary)}
|
|
1525
1905
|
.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
|
|
1906
|
+
.ddp-go{height:26px;padding:0 12px;border:0;border-radius:999px;background:${ACCENT};color:#fff;font:inherit;cursor:pointer}
|
|
1907
|
+
.ddp-go:hover{filter:brightness(.94)}
|
|
1908
|
+
.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))}
|
|
1909
|
+
.ddp-go.sec:hover{filter:none;background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}
|
|
1910
|
+
.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
1911
|
.ddp-err{color:var(--dsw-alias-state-error-primary,#e5484d);font-size:11.5px}
|
|
1528
1912
|
.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
1913
|
.ddp-head:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary)}
|
|
1530
1914
|
.ddp-head svg{width:16px;height:16px}
|
|
1531
1915
|
.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
1916
|
.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;
|
|
1917
|
+
.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
1918
|
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
1919
|
.ddp-tokens header{display:flex;align-items:center;gap:8px;padding:10px 8px 2px 14px;font-size:13px;font-weight:500}
|
|
1536
1920
|
.ddp-tokens header b{flex:1;font-weight:500}
|
|
@@ -1539,7 +1923,9 @@ window.__ModuleLoader__.load({
|
|
|
1539
1923
|
.ddp-tokens .tip{margin:0;padding:0 14px 8px;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}
|
|
1540
1924
|
.ddp-tokens .bd{flex:1;min-height:0;overflow:auto;padding:0 8px 10px}
|
|
1541
1925
|
.ddp-tokens h4{margin:10px 6px 4px;font-size:11px;font-weight:500;color:var(--dsw-alias-label-tertiary)}
|
|
1542
|
-
.ddp-phone{
|
|
1926
|
+
.ddp-phone details{margin-top:4px;text-align:left}
|
|
1927
|
+
.ddp-phone details summary{cursor:pointer;text-align:center}
|
|
1928
|
+
.ddp-phone details p{margin-top:4px}
|
|
1543
1929
|
.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
1930
|
.ddp-phone .bd>*{flex:none}
|
|
1545
1931
|
.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 +1946,9 @@ window.__ModuleLoader__.load({
|
|
|
1560
1946
|
.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
1947
|
.ddp-tokens footer button:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary)}
|
|
1562
1948
|
.ddp-tokens footer svg{width:15px;height:15px;flex:none}
|
|
1949
|
+
/* 所有面板一样的禁用样子:描边、浅字,不像能点;主按钮都是 DSH 的蓝 */
|
|
1950
|
+
.ddp-tokens footer button:disabled{background:transparent;color:var(--dsw-alias-label-tertiary);box-shadow:none;cursor:default;opacity:1;filter:none}
|
|
1951
|
+
.ddp-tokens footer .go:disabled{box-shadow:inset 0 0 0 1px var(--dsw-alias-separator-primary,rgba(127,127,127,.3))}
|
|
1563
1952
|
.ddp{position:relative;outline:none}
|
|
1564
1953
|
.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
1954
|
.ddp-drop *{pointer-events:none}
|
|
@@ -1567,9 +1956,8 @@ window.__ModuleLoader__.load({
|
|
|
1567
1956
|
.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
1957
|
.ddp-drop .in svg{width:28px;height:28px;color:${ACCENT};margin-bottom:4px}
|
|
1569
1958
|
.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}
|
|
1959
|
+
.ddp-home .lk{border:0;padding:0;background:transparent;color:${ACCENT};font:inherit;cursor:pointer}
|
|
1960
|
+
.ddp-home .lk:hover{text-decoration:underline;text-underline-offset:2px}
|
|
1573
1961
|
.ddp-ck{align-items:flex-start}
|
|
1574
1962
|
.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
1963
|
.ddp-ck .no.hi{background:#e5484d}
|
|
@@ -1577,10 +1965,10 @@ window.__ModuleLoader__.load({
|
|
|
1577
1965
|
.ddp-tok.ddp-ck .vl{white-space:normal;overflow-wrap:anywhere}
|
|
1578
1966
|
.ddp-check .ok{display:flex;align-items:center;gap:8px;padding:14px 6px;color:var(--dsw-alias-label-secondary)}
|
|
1579
1967
|
.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
|
|
1968
|
+
.ddp-check footer.row,.ddp-style footer.row,.ddp-look footer.row{display:flex;gap:6px}
|
|
1969
|
+
.ddp-check footer.row button,.ddp-style footer.row button,.ddp-look footer.row button{flex:1;justify-content:center;padding:0 6px}
|
|
1970
|
+
.ddp-check footer .go,.ddp-style footer .go,.ddp-look footer .go{background:${ACCENT};color:#fff}
|
|
1971
|
+
.ddp-check footer .go:hover,.ddp-style footer .go:hover,.ddp-look footer .go:hover{background:${ACCENT};color:#fff;filter:brightness(.94)}
|
|
1584
1972
|
.ddp-check footer button:disabled,.ddp-style footer button:disabled{opacity:.45;cursor:default}
|
|
1585
1973
|
.ddp-style .bd{padding:0 14px 10px}
|
|
1586
1974
|
.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 +2036,114 @@ window.__ModuleLoader__.load({
|
|
|
1648
2036
|
.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
2037
|
.ddp-samview .row .ddp-go{margin-left:auto;height:28px;font-size:12px}
|
|
1650
2038
|
.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-
|
|
2039
|
+
/* ── 首页:顶上页签、一句问话、大输入框、接着做、模板 ── */
|
|
2040
|
+
.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%)}
|
|
2041
|
+
.ddp-homebar{flex:none;display:flex;align-items:center;gap:8px;height:48px;padding:0 12px 0 16px}
|
|
2042
|
+
.ddp-homebar .nm{display:flex;align-items:center;gap:8px;min-width:0}
|
|
2043
|
+
.ddp-homebar .nm b{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);white-space:nowrap}
|
|
2044
|
+
.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)}
|
|
2045
|
+
.ddp-homebar .seg{margin-left:auto}
|
|
2046
|
+
.ddp-homebar>.ddp-btn{margin-left:-6px}
|
|
2047
|
+
.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;
|
|
2048
|
+
color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;cursor:pointer;flex:0 1 auto}
|
|
2049
|
+
.ddp-homeback svg{width:15px;height:15px;flex:none}
|
|
2050
|
+
.ddp-homeback span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
2051
|
+
.ddp-homeback:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary)}
|
|
2052
|
+
.ddp-homebar.led .nm{display:none}
|
|
2053
|
+
/* 开着设计时叠上来的首页:淡入、轻轻放大;几块依次浮上来;收起反过来 */
|
|
2054
|
+
.ddp-home.over~.ddp-libwrap{z-index:11}
|
|
2055
|
+
.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}
|
|
2056
|
+
.ddp-home.over.out{animation:ddp-homeout .19s ease both;pointer-events:none}
|
|
2057
|
+
.ddp-home .ddp-start>*{animation:ddp-rise .5s ${EASE} both}
|
|
2058
|
+
.ddp-home .ddp-start>:nth-child(2){animation-delay:.05s}
|
|
2059
|
+
.ddp-home .ddp-start>:nth-child(3){animation-delay:.1s}
|
|
2060
|
+
.ddp-home .ddp-start>:nth-child(4){animation-delay:.15s}
|
|
2061
|
+
.ddp-home .ddp-start>:nth-child(n+5){animation-delay:.2s}
|
|
2062
|
+
@keyframes ddp-homein{from{opacity:0;transform:scale(.985)}}
|
|
2063
|
+
@keyframes ddp-homeout{to{opacity:0;transform:scale(.985)}}
|
|
2064
|
+
@keyframes ddp-rise{from{opacity:0;transform:translateY(10px)}}
|
|
2065
|
+
.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}
|
|
2066
|
+
.ddp-asknote svg{width:13px;height:13px;flex:none}
|
|
2067
|
+
.ddp-recent .wk{position:relative}
|
|
2068
|
+
.ddp-recent .wk.here .im{box-shadow:0 0 0 2px ${ACCENT}}
|
|
2069
|
+
.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)}
|
|
2070
|
+
@media (prefers-reduced-motion:reduce){.ddp-home.over,.ddp-home.over.out,.ddp-home .ddp-start>*{animation:none}}
|
|
2071
|
+
.ddp-lib>.ddp-homebar{border-bottom:1px solid var(--dsw-alias-separator-primary,rgba(127,127,127,.16))}
|
|
2072
|
+
.ddp-homebd{flex:1;min-height:0;overflow-y:auto;padding:0 20px 36px}
|
|
2073
|
+
.ddp-start{width:100%;max-width:600px;margin:0 auto;display:flex;flex-direction:column;gap:32px;text-align:left}
|
|
2074
|
+
.ddp-hero{margin:clamp(12px,7vh,52px) 0 -14px;text-align:center}
|
|
2075
|
+
.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}
|
|
2076
|
+
.ddp-hero p{margin:8px auto 0;max-width:440px;font-size:13px;line-height:20px;text-wrap:balance;word-break:keep-all}
|
|
2077
|
+
.ddp-start>.ddp-go{align-self:center;margin:-16px 0}
|
|
2078
|
+
.ddp-askwrap{display:flex;flex-direction:column;gap:14px}
|
|
2079
|
+
.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}
|
|
2080
|
+
.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)}
|
|
2081
|
+
.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}
|
|
2082
|
+
.ddp-askta::placeholder{color:var(--dsw-alias-label-tertiary)}
|
|
2083
|
+
.ddp-ask>.row{display:flex;align-items:center;gap:6px;padding:6px 10px 10px}
|
|
2084
|
+
.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}
|
|
2085
|
+
.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)}
|
|
2086
|
+
.ddp-ask .chip>span{min-width:0;overflow:hidden;text-overflow:ellipsis}
|
|
2087
|
+
.ddp-ask .chip svg{flex:none;width:14px;height:14px}
|
|
2088
|
+
.ddp-ask .chip .cr{width:10px;height:10px;opacity:.6}
|
|
2089
|
+
.ddp-ask .chip.on{background:color-mix(in srgb,${ACCENT} 13%,transparent);color:${ACCENT}}
|
|
2090
|
+
.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)}
|
|
2091
|
+
.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)}
|
|
2092
|
+
.ddp-askplus>.ddp-btn:hover,.ddp-askplus>.ddp-btn.sel{background:color-mix(in srgb,var(--dsw-alias-label-primary) 9%,transparent)}
|
|
2093
|
+
.ddp-askplus>.ddp-btn svg{width:15px;height:15px}
|
|
2094
|
+
.ddp-askplus .ddp-menu{left:0;right:auto;min-width:200px}
|
|
2095
|
+
.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}
|
|
2096
|
+
.ddp-ask .tabhint:hover{color:var(--dsw-alias-label-secondary)}
|
|
2097
|
+
.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}
|
|
2098
|
+
.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}
|
|
2099
|
+
.ddp-ask .tabhint+.send{margin-left:8px}
|
|
2100
|
+
.ddp-ask .send:disabled{opacity:.3;cursor:default}
|
|
2101
|
+
.ddp-ask .send:hover:not(:disabled){filter:brightness(1.08)}
|
|
2102
|
+
.ddp-ask .send svg{width:17px;height:17px}
|
|
2103
|
+
.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}
|
|
2104
|
+
.ddp-askpop .hd{display:flex;align-items:baseline;gap:8px;margin-bottom:10px}
|
|
2105
|
+
.ddp-askpop .hd b{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary)}
|
|
2106
|
+
.ddp-askpop .hd .lk{margin-left:auto;font-size:12px}
|
|
2107
|
+
.ddp-askpop .ddp-styles{grid-template-columns:repeat(auto-fill,minmax(92px,1fr));gap:10px}
|
|
2108
|
+
.ddp-askkinds{display:flex;flex-wrap:wrap;justify-content:center;gap:5px}
|
|
2109
|
+
.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}
|
|
2110
|
+
.ddp-askkinds button:hover{border-color:var(--dsw-alias-label-tertiary);color:var(--dsw-alias-label-primary)}
|
|
2111
|
+
.ddp-askkinds button.on{border-color:${ACCENT};background:color-mix(in srgb,${ACCENT} 10%,transparent);color:${ACCENT}}
|
|
2112
|
+
.ddp-askkinds svg{width:13px;height:13px}
|
|
2113
|
+
.ddp-homesec>.hd{display:flex;align-items:baseline;gap:8px;margin-bottom:12px}
|
|
2114
|
+
.ddp-homesec>.hd b{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary)}
|
|
2115
|
+
.ddp-homesec>.hd .lk{margin-left:auto;font-size:12px}
|
|
2116
|
+
.ddp-recent,.ddp-tplstart .row{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px 12px}
|
|
2117
|
+
.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}
|
|
2118
|
+
.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}
|
|
2119
|
+
.ddp-recent .im{aspect-ratio:16/10}
|
|
2120
|
+
.ddp-tplstart .im{aspect-ratio:4/5}
|
|
2121
|
+
.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}
|
|
2122
|
+
.ddp-recent .im svg,.ddp-tplstart .im svg{width:22px;height:22px}
|
|
2123
|
+
.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)}
|
|
2124
|
+
.ddp-recent .wk:focus-visible,.ddp-tplstart .tp:focus-visible{outline:none}
|
|
2125
|
+
.ddp-recent .tx,.ddp-tplstart .tx{display:flex;flex-direction:column;gap:1px;min-width:0;padding:0 2px}
|
|
2126
|
+
.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}
|
|
2127
|
+
.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}
|
|
2128
|
+
@container (max-width:470px){
|
|
2129
|
+
.ddp-homebar .nm b{display:none}
|
|
2130
|
+
.ddp-homebd{padding:0 14px 28px}
|
|
2131
|
+
.ddp-hero h1{font-size:22px;line-height:30px}
|
|
2132
|
+
.ddp-recent,.ddp-tplstart .row{grid-template-columns:repeat(2,minmax(0,1fr))}
|
|
2133
|
+
.ddp-recent .wk:nth-child(3){display:none}
|
|
2134
|
+
.ddp-ask .tabhint{display:none}
|
|
2135
|
+
.ddp-ask .tabhint+.send{margin-left:auto}
|
|
2136
|
+
}
|
|
2137
|
+
@container (max-width:340px){.ddp-ask .chip.ref>span{display:none}.ddp-ask .chip.ref{padding:0 8px}}
|
|
2138
|
+
.ddp-more-st{display:block;margin-top:10px;font-size:12px}
|
|
2139
|
+
.ddp-ways{padding-top:16px;border-top:.5px solid var(--dsw-alias-separator-primary,rgba(127,127,127,.3))}
|
|
2140
|
+
.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}
|
|
2141
|
+
.ddp-ways summary::-webkit-details-marker{display:none}
|
|
2142
|
+
.ddp-ways summary:hover{color:var(--dsw-alias-label-primary)}
|
|
2143
|
+
.ddp-ways summary svg{width:12px;height:12px;transform:rotate(-90deg);transition:transform .15s ${EASE}}
|
|
2144
|
+
.ddp-ways[open] summary svg{transform:none}
|
|
2145
|
+
.ddp-ways .in{display:flex;flex-direction:column;gap:10px;margin-top:12px}
|
|
2146
|
+
.ddp-ways .in p{margin:0;line-height:1.55}
|
|
1659
2147
|
.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
2148
|
.ddp-tok:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}
|
|
1661
2149
|
.ddp-tok .pv{width:24px;height:24px;flex:none;display:flex;align-items:center;justify-content:center;overflow:hidden}
|
|
@@ -1699,13 +2187,18 @@ window.__ModuleLoader__.load({
|
|
|
1699
2187
|
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
2188
|
.ddp-banner>svg{width:14px;height:14px;flex:none;color:${ACCENT}}
|
|
1701
2189
|
.ddp-banner>span{overflow:hidden;text-overflow:ellipsis;min-width:0;margin-right:2px}
|
|
1702
|
-
|
|
1703
|
-
.ddp-banner
|
|
1704
|
-
.ddp-banner
|
|
2190
|
+
/* 试用横幅:「试用中」蓝字、屏名正文、提示淡;窄了先收提示 */
|
|
2191
|
+
.ddp-banner>b{flex:none;font-weight:600;color:${ACCENT}}
|
|
2192
|
+
.ddp-banner>.nm{color:var(--dsw-alias-label-primary)}
|
|
2193
|
+
.ddp-banner>em{flex:none;margin-right:4px;font-style:normal;color:var(--dsw-alias-label-tertiary)}
|
|
2194
|
+
@container (max-width:420px){.ddp-banner>em{display:none}}
|
|
2195
|
+
.ddp-banner .ddp-go{height:24px;padding:0 10px;font-size:12px;flex:none}
|
|
2196
|
+
.ddp-banner .ddp-go.sec.on{background:rgba(77,107,254,.12);color:${ACCENT};box-shadow:inset 0 0 0 1px ${ACCENT}}
|
|
1705
2197
|
.ddp-banner .ddp-btn{height:24px;min-width:24px;border-radius:999px}
|
|
1706
2198
|
.ddp-toast{display:flex;align-items:center;gap:10px}
|
|
1707
2199
|
.ddp-toast.low{top:56px}
|
|
1708
2200
|
.ddp-toast.mk{top:auto;bottom:62px}
|
|
2201
|
+
.ddp-toast.side{right:308px}
|
|
1709
2202
|
.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
2203
|
.ddp-cmp{position:absolute;inset:0;overflow:hidden}
|
|
1711
2204
|
.ddp-cmp>.ly{position:absolute;inset:0}
|
|
@@ -1722,20 +2215,60 @@ window.__ModuleLoader__.load({
|
|
|
1722
2215
|
.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
2216
|
.ddp-cmp .tag.l{left:10px}
|
|
1724
2217
|
.ddp-cmp .tag.r{right:10px;background:${ACCENT}}
|
|
1725
|
-
|
|
2218
|
+
/* 屏头小按钮:悬停或选中那屏才出来(平时不占地方,给屏名和「用这版」留位置) */
|
|
2219
|
+
.ddp-act{margin-left:auto;display:none;gap:1px;flex:none}
|
|
2220
|
+
.ddp-cell:hover .ddp-act,.ddp-cell.sel .ddp-act{display:flex}
|
|
2221
|
+
.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}
|
|
2222
|
+
.ddp-cell.sel .ddp-vt{background:rgba(77,107,254,.14);color:${ACCENT}}
|
|
1726
2223
|
.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
2224
|
background:rgba(77,107,254,.12);color:${ACCENT};font:inherit;font-size:11px;font-weight:500;cursor:pointer}
|
|
1728
2225
|
.ddp-take:hover{background:rgba(77,107,254,.2)}
|
|
1729
2226
|
.ddp-take svg{width:12px;height:12px;flex:none}
|
|
1730
2227
|
/* 画布缩得很小时只留 ✓,屏名先保住 */
|
|
1731
2228
|
.ddp-lab{container-type:inline-size}
|
|
1732
|
-
.ddp-lab>span:first-
|
|
1733
|
-
@container (max-width:
|
|
2229
|
+
.ddp-lab>span:first-of-type{min-width:3em}
|
|
2230
|
+
@container (max-width:130px){.ddp-take{padding:0 4px}.ddp-take span{display:none}}
|
|
2231
|
+
@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
2232
|
.ddp-act .ddp-btn{height:22px;min-width:22px;padding:0 4px;color:${ACCENT}}
|
|
1735
2233
|
.ddp-act .ddp-btn svg{width:13px;height:13px}
|
|
1736
|
-
|
|
2234
|
+
/* 屏头的小按钮:屏够宽就带字(导出 / 再出两版 / 单看),窄了只剩图标,悬停看说明 */
|
|
2235
|
+
.ddp-act .lb{display:none;font-size:11px}
|
|
2236
|
+
@container (min-width:300px){.ddp-act .lb{display:inline}.ddp-act{gap:4px}}
|
|
1737
2237
|
.ddp-tw .row:first-child{border-top:0}
|
|
1738
2238
|
.ddp-sys .bd{display:flex;flex-direction:column;gap:8px;padding:2px 10px 10px}
|
|
2239
|
+
.ddp-sys.bare{flex:1;min-height:0;display:flex;flex-direction:column}
|
|
2240
|
+
.ddp-sys>form{display:flex;gap:6px;flex:none;padding:0 10px 10px}
|
|
2241
|
+
/* 外观面板:三页切换;整套里颜色两列、点色块直接改 */
|
|
2242
|
+
.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))}
|
|
2243
|
+
.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}
|
|
2244
|
+
.ddp-lktabs button:hover{color:var(--dsw-alias-label-primary)}
|
|
2245
|
+
.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)}
|
|
2246
|
+
.ddp-look h4{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 6px;margin-top:12px}
|
|
2247
|
+
.ddp-look .bd>h4:first-child{margin-top:4px}
|
|
2248
|
+
.ddp-look h4 span{font-weight:400}
|
|
2249
|
+
.ddp-lkc{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:2px}
|
|
2250
|
+
.ddp-lkc .it{display:flex;align-items:center;gap:8px;min-width:0;padding:4px 6px;border-radius:7px;cursor:pointer}
|
|
2251
|
+
.ddp-lkc .it:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}
|
|
2252
|
+
.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}
|
|
2253
|
+
.ddp-lkc .sw input{position:absolute;inset:0;width:100%;height:100%;margin:0;padding:0;border:0;opacity:0;cursor:pointer}
|
|
2254
|
+
.ddp-lkc .it.on .sw{box-shadow:0 0 0 1.5px ${ACCENT}}
|
|
2255
|
+
.ddp-lkc .tx{flex:1;min-width:0;display:flex;flex-direction:column}
|
|
2256
|
+
.ddp-lkc .nm{font-size:12px;line-height:16px;color:var(--dsw-alias-label-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
2257
|
+
.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}
|
|
2258
|
+
.ddp-lkc .it.on .vl{color:${ACCENT}}
|
|
2259
|
+
.ddp-look .ddp-tok .nm{font:12px/16px inherit}
|
|
2260
|
+
.ddp-lktw{padding:0 6px}
|
|
2261
|
+
.ddp-lktw .row:first-child{border-top:0;padding-top:2px}
|
|
2262
|
+
.ddp-lk-none{margin:0 6px;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.6}
|
|
2263
|
+
.ddp-tokens .lk{border:0;padding:0;background:transparent;color:${ACCENT};font:inherit;cursor:pointer}
|
|
2264
|
+
.ddp-tokens .lk:hover{text-decoration:underline;text-underline-offset:2px}
|
|
2265
|
+
.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))}
|
|
2266
|
+
.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}
|
|
2267
|
+
.ddp-lk-more button:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary)}
|
|
2268
|
+
.ddp-lk-more svg{width:15px;height:15px;flex:none}
|
|
2269
|
+
.ddp-look footer.row{flex-wrap:wrap;align-items:center}
|
|
2270
|
+
.ddp-look footer.row .cnt{flex-basis:100%;padding:0 2px 2px;font-size:11px;color:var(--dsw-alias-label-tertiary)}
|
|
2271
|
+
.ddp-style .who b{margin-right:2px;font-weight:500;color:var(--dsw-alias-label-primary)}
|
|
1739
2272
|
.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
2273
|
.ddp-sysc.def{box-shadow:inset 0 0 0 1.5px ${ACCENT}}
|
|
1741
2274
|
.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 +2285,42 @@ window.__ModuleLoader__.load({
|
|
|
1752
2285
|
.ddp-sys form{display:flex;gap:6px}
|
|
1753
2286
|
.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
2287
|
.ddp-sys form input:focus,.ddp-ho input[type=text]:focus{border-color:${ACCENT}}
|
|
1755
|
-
.ddp-
|
|
1756
|
-
.ddp-
|
|
2288
|
+
.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}
|
|
2289
|
+
.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
2290
|
.ddp-ho .bd{padding:0 14px 10px}
|
|
1758
2291
|
.ddp-ho .row{padding:9px 0 10px;border-top:1px solid var(--dsw-alias-separator-primary,rgba(127,127,127,.16))}
|
|
1759
2292
|
.ddp-ho .row:first-child{border-top:0}
|
|
1760
2293
|
.ddp-ho label{display:block;margin-bottom:7px;font-size:12px;color:var(--dsw-alias-label-secondary)}
|
|
1761
2294
|
.ddp-ho .pathrow{display:flex;gap:6px;align-items:center}
|
|
2295
|
+
.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}
|
|
2296
|
+
.ddp-ho .pathv:hover{border-color:var(--dsw-alias-label-tertiary)}
|
|
2297
|
+
.ddp-ho .pathv svg{width:14px;height:14px;flex:none;color:var(--dsw-alias-label-secondary)}
|
|
2298
|
+
.ddp-ho .pathv b{font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:none;max-width:60%}
|
|
2299
|
+
.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
2300
|
.ddp-ho .moved{margin:6px 0 0;font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary)}
|
|
1763
2301
|
.ddp-ho .moved.bad{color:var(--dsw-alias-state-error-primary,#d54941)}
|
|
1764
2302
|
.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
2303
|
.ddp-dirbtn:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3,rgba(127,127,127,.5))}
|
|
1766
2304
|
.ddp-dirbtn:disabled{opacity:.5;cursor:default}
|
|
1767
2305
|
.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))}
|
|
2306
|
+
.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
2307
|
.ddp-ho input[type=text]{width:100%;box-sizing:border-box;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}
|
|
1771
2308
|
.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
2309
|
.ddp-ho .opt:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}
|
|
1773
2310
|
.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
2311
|
.ddp-ho .opt.on{color:var(--dsw-alias-label-primary)}
|
|
1775
2312
|
.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:
|
|
2313
|
+
.ddp-ho footer .go{justify-content:center;background:${ACCENT};color:#fff}
|
|
2314
|
+
.ddp-ho footer .go:hover{background:${ACCENT};color:#fff;filter:brightness(.94)}
|
|
2315
|
+
|
|
2316
|
+
.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
2317
|
.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)}
|
|
2318
|
+
.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))}
|
|
2319
|
+
.ddp-lib>header:not(.ddp-homebar) b{font-size:13px;font-weight:600;white-space:nowrap}
|
|
2320
|
+
.ddp-lib .seg,.ddp-homebar .seg{margin-left:auto;display:inline-flex;padding:2px;border-radius:8px;background:var(--dsw-alias-bg-layer-2)}
|
|
2321
|
+
.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}
|
|
2322
|
+
.ddp-lib .seg button:hover,.ddp-homebar .seg button:hover{color:var(--dsw-alias-label-primary)}
|
|
2323
|
+
.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
2324
|
.ddp-lib>.bd{flex:1;min-height:0;overflow:auto;padding:14px}
|
|
1788
2325
|
.ddp-pack .lk{border:0;padding:0;background:transparent;color:${ACCENT};font:inherit;cursor:pointer}
|
|
1789
2326
|
.ddp-pack .lk:hover{text-decoration:underline;text-underline-offset:2px}
|
|
@@ -1796,11 +2333,11 @@ window.__ModuleLoader__.load({
|
|
|
1796
2333
|
.ddp-libnone p{margin:0;max-width:320px}
|
|
1797
2334
|
.ddp-libfoot{margin:18px 0 0;font-size:11px;line-height:1.5;color:var(--dsw-alias-label-tertiary);text-align:center}
|
|
1798
2335
|
.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}
|
|
2336
|
+
.ddp-work{display:flex;flex-direction:column;gap:4px;min-width:0;container-type:inline-size}
|
|
1800
2337
|
.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
2338
|
.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
2339
|
.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}
|
|
2340
|
+
.ddp-work .th img{position:absolute;inset:0;z-index:1;width:100%;height:100%;object-fit:cover;object-position:left top}
|
|
1804
2341
|
.ddp-work .th .ic{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:var(--dsw-alias-label-tertiary)}
|
|
1805
2342
|
.ddp-work .th .ic svg{width:26px;height:26px}
|
|
1806
2343
|
.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 +2348,17 @@ window.__ModuleLoader__.load({
|
|
|
1811
2348
|
.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
2349
|
.ddp-work .pin{flex:none;color:#f5a524;font-size:11px}
|
|
1813
2350
|
.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}
|
|
2351
|
+
.ddp-work .acts{display:flex;flex-wrap:wrap;align-items:center;gap:2px;min-width:0;margin-left:-4px}
|
|
2352
|
+
/* 卡片窄(侧栏窄、多列):操作只留图标(悬停有说明),别挤进旁边那件 */
|
|
2353
|
+
@container (max-width:259px){.ddp-work .acts button svg+span{display:none}}
|
|
1815
2354
|
.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
2355
|
.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
2356
|
.ddp-work .acts button:disabled{opacity:.45;cursor:default}
|
|
1818
2357
|
.ddp-work .acts button.on{color:#f5a524}
|
|
1819
2358
|
.ddp-work .acts svg,.ddp-insp .rm svg,.ddp-market .rm svg{width:14px;height:14px}
|
|
1820
2359
|
.ddp-work .acts .rm{margin-left:auto}
|
|
2360
|
+
.ddp-work .acts button span{font-size:11px}
|
|
2361
|
+
.ddp-work .acts button svg+span{margin-left:3px}
|
|
1821
2362
|
.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
2363
|
.ddp-insp h4{margin:18px 0 8px;font-size:12px;font-weight:500;color:var(--dsw-alias-label-secondary)}
|
|
1823
2364
|
.ddp-insp h4:first-child{margin-top:0}
|
|
@@ -1835,11 +2376,23 @@ window.__ModuleLoader__.load({
|
|
|
1835
2376
|
.ddp-imgs{display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:8px}
|
|
1836
2377
|
.ddp-imgs:empty{display:none}
|
|
1837
2378
|
.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 .
|
|
2379
|
+
.ddp-market .ddp-mkbar .tabs{display:flex;gap:14px;flex:none}
|
|
2380
|
+
.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}
|
|
2381
|
+
.ddp-market .ddp-mkbar .tabs button em{font-style:normal;font-size:11px;color:var(--dsw-alias-label-tertiary)}
|
|
2382
|
+
.ddp-market .ddp-mkbar .tabs button:hover{color:var(--dsw-alias-label-primary)}
|
|
2383
|
+
.ddp-market .ddp-mkbar .tabs button.on{color:var(--dsw-alias-label-primary);font-weight:500;border-bottom-color:${ACCENT}}
|
|
2384
|
+
.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
2385
|
.ddp-market .ddp-mkbar .q:focus{border-color:${ACCENT}}
|
|
1841
|
-
|
|
1842
|
-
.ddp-
|
|
2386
|
+
/* 窄了:搜索框缩成一个放大镜,点进去再展开 */
|
|
2387
|
+
@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)}}
|
|
2388
|
+
.ddp-mkkinds{display:flex;align-items:center;gap:6px;margin:0 0 12px;overflow-x:auto;scrollbar-width:none}
|
|
2389
|
+
.ddp-mkkinds::-webkit-scrollbar{display:none}
|
|
2390
|
+
.ddp-mkkinds>button{flex:none}
|
|
2391
|
+
.ddp-mkkinds button:disabled{opacity:.4;cursor:default}
|
|
2392
|
+
.ddp-mkbar .sort{display:flex;gap:2px;flex:none}
|
|
2393
|
+
.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}
|
|
2394
|
+
.ddp-mkbar .sort button:hover{color:var(--dsw-alias-label-primary)}
|
|
2395
|
+
.ddp-mkbar .sort button.on{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
|
|
1843
2396
|
.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
2397
|
.ddp-mkkinds button:hover,.ddp-mkdots button:hover{color:var(--dsw-alias-label-primary)}
|
|
1845
2398
|
.ddp-mkkinds button.on,.ddp-mkdots button.on{background:${ACCENT};color:#fff}
|
|
@@ -1859,25 +2412,14 @@ window.__ModuleLoader__.load({
|
|
|
1859
2412
|
.ddp-mkdots{display:flex;flex-wrap:wrap;gap:6px}
|
|
1860
2413
|
.ddp-mkd .acts{display:flex;gap:8px;flex-wrap:wrap;margin-top:4px}
|
|
1861
2414
|
.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
|
|
2415
|
+
.ddp-pbtn{background:${ACCENT};color:#fff;font-weight:600}
|
|
2416
|
+
.ddp-pbtn:hover:not(:disabled){filter:brightness(.94)}
|
|
1863
2417
|
.ddp-sbtn{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
|
|
1864
2418
|
.ddp-pbtn:disabled,.ddp-sbtn:disabled{opacity:.5;cursor:default}
|
|
1865
2419
|
.ddp-mkd .facts{margin:6px 0 0;padding-left:18px;font-size:12px;line-height:1.6;color:var(--dsw-alias-label-secondary)}
|
|
1866
2420
|
.ddp-mkd .ddp-libfoot a{color:${ACCENT};text-decoration:none}
|
|
1867
2421
|
.ddp-mkd .ddp-libfoot a:hover{text-decoration:underline}
|
|
1868
2422
|
.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
2423
|
.ddp-img{position:relative;aspect-ratio:1;border-radius:8px;overflow:hidden;background:var(--dsw-alias-bg-layer-2)}
|
|
1882
2424
|
.ddp-img img{display:block;width:100%;height:100%;object-fit:cover}
|
|
1883
2425
|
.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 +2438,8 @@ window.__ModuleLoader__.load({
|
|
|
1896
2438
|
.ddp-urlform{display:flex;gap:6px}
|
|
1897
2439
|
.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
2440
|
.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{
|
|
2441
|
+
.ddp-urlform button{flex:none;height:28px;padding:0 12px;border:0;border-radius:8px;background:${ACCENT};color:#fff;font:12px inherit;cursor:pointer}
|
|
2442
|
+
.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
2443
|
.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
2444
|
.ddp-pack .which svg{width:14px;height:14px;flex:none;color:var(--dsw-alias-label-tertiary)}
|
|
1903
2445
|
.ddp-pack .which span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
@@ -1905,6 +2447,8 @@ window.__ModuleLoader__.load({
|
|
|
1905
2447
|
.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
2448
|
.ddp-pack .lic label.on{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
|
|
1907
2449
|
.ddp-pack .lic input{flex:none;margin:3px 0 0;accent-color:${ACCENT}}
|
|
2450
|
+
.ddp-share .tip.gh{display:flex;align-items:center;gap:6px;margin-bottom:0;color:var(--dsw-alias-label-primary);font-weight:500}
|
|
2451
|
+
.ddp-share .tip.gh svg{width:14px;height:14px;flex:none;color:${ACCENT}}
|
|
1908
2452
|
.ddp-share .agree{display:flex;flex-direction:column;gap:2px;margin-top:10px}
|
|
1909
2453
|
.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
2454
|
.ddp-share .agree label:hover{color:var(--dsw-alias-label-primary)}
|
|
@@ -1925,14 +2469,13 @@ window.__ModuleLoader__.load({
|
|
|
1925
2469
|
.ddp-pack .note ul{margin:2px 0 0;padding-left:16px}
|
|
1926
2470
|
.ddp-pack footer.row{display:flex;gap:6px}
|
|
1927
2471
|
.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
|
-
|
|
2472
|
+
.ddp-pack footer .go{background:${ACCENT};color:#fff}
|
|
2473
|
+
.ddp-pack footer .go:hover{background:${ACCENT};color:#fff;filter:brightness(.94)}
|
|
2474
|
+
|
|
1931
2475
|
.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}}
|
|
2476
|
+
/* 浮块窄了:先截屏名,到 440 再收按钮上的字,400 收工具坞里的演示(演示时横幅上能退出) */
|
|
2477
|
+
@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}}
|
|
2478
|
+
@container (max-width:380px){.ddp-tool{width:32px}.ddp-kind.ddp-where{min-width:64px}}
|
|
1936
2479
|
/* 标注底栏:右侧栏窄时「这支笔怎么用」折成两行,不被三个按钮挤成省略号 */
|
|
1937
2480
|
@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
2481
|
/* 画标注时预览不动,画布右下角的缩放按钮被标注层盖着也点不到:先收起来,免得和底栏叠在一起 */
|
|
@@ -2006,9 +2549,11 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
2006
2549
|
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
2550
|
play: () => svg([h('path', { key: 1, d: 'M8 5.5v13l10.5-6.5z', strokeLinejoin: 'round' })]),
|
|
2008
2551
|
back: () => svg([h('path', { key: 1, d: 'M14.5 6l-6 6 6 6', strokeWidth: 2.2 })]),
|
|
2552
|
+
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
2553
|
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
2554
|
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
2555
|
plus: () => svg([h('path', { key: 1, d: 'M12 5v14M5 12h14' })]),
|
|
2556
|
+
send: () => svg([h('path', { key: 1, d: 'M12 19V5.5M6 11.5l6-6 6 6', strokeWidth: 2.2 })]),
|
|
2012
2557
|
minus: () => svg([h('path', { key: 1, d: 'M5 12h14' })]),
|
|
2013
2558
|
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
2559
|
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 +2575,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
2030
2575
|
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
2576
|
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
2577
|
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' })]),
|
|
2578
|
+
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
2579
|
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
2580
|
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
2581
|
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),
|
|
@@ -2612,6 +3158,106 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
2612
3158
|
}
|
|
2613
3159
|
|
|
2614
3160
|
/** 点了一个变量 → 输入框里的一段字 */
|
|
3161
|
+
/** 设计变量的人话名字:--bg → 背景,--fs-micro → 注释,--radius-md → 中圆角;认不出的把名字去掉 -- 和连字符 */
|
|
3162
|
+
function tokenLabel(t, tok) {
|
|
3163
|
+
const raw = String(tok.name).replace(/^--/, '')
|
|
3164
|
+
const words = raw.toLowerCase().split(/[-_]+/).filter(Boolean)
|
|
3165
|
+
const has = (re) => words.some((w) => re.test(w))
|
|
3166
|
+
const kind = tokenKind(tok)
|
|
3167
|
+
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))
|
|
3168
|
+
const mod = (left) => {
|
|
3169
|
+
const out = []
|
|
3170
|
+
for (const w of left) {
|
|
3171
|
+
if (/^(soft|light|lighter|pale|tint|weak|subtle|faint|bg|50|100|200)$/.test(w)) out.push(t('tkn_light'))
|
|
3172
|
+
else if (/^(dark|darker|deep|strong|bold|700|800|900)$/.test(w)) out.push(t('tkn_dark'))
|
|
3173
|
+
else if (/^(hover|active|pressed|focus)$/.test(w)) out.push(t('tkn_hover'))
|
|
3174
|
+
else out.push(w)
|
|
3175
|
+
}
|
|
3176
|
+
return [...new Set(out)].join(' ')
|
|
3177
|
+
}
|
|
3178
|
+
const name = (key, re) => { const m = mod(rest(re)); return m ? `${t('tkn_' + key)} · ${m}` : t('tkn_' + key) }
|
|
3179
|
+
if (kind === 'color') {
|
|
3180
|
+
if (words.includes('on') && words.length > 1) return t('tkn_onPrimary')
|
|
3181
|
+
const roles = [
|
|
3182
|
+
[/^(muted|subtle|dim|faint|placeholder|secondary-text|text2|hint)$/, 'muted'],
|
|
3183
|
+
[/^(line|border|divider|stroke|outline|hairline|sep|separator|rule)$/, 'line'],
|
|
3184
|
+
[/^(surface|card|panel|elev|elevated|sheet|raised|layer|paper)$/, 'surface'],
|
|
3185
|
+
[/^(text|fg|ink|foreground|body|copy)$/, 'text'],
|
|
3186
|
+
[/^(bg|background|page|canvas|base|backdrop)$/, 'bg'],
|
|
3187
|
+
[/^(primary|brand|main|theme)$/, 'primary'],
|
|
3188
|
+
[/^(accent|highlight|secondary)$/, 'accent'],
|
|
3189
|
+
[/^(link|links)$/, 'link'],
|
|
3190
|
+
[/^(danger|error|destructive|alert)$/, 'danger'],
|
|
3191
|
+
[/^(success|positive|ok)$/, 'success'],
|
|
3192
|
+
[/^(warning|warn|caution)$/, 'warning'],
|
|
3193
|
+
]
|
|
3194
|
+
for (const [re, key] of roles) if (has(re)) return name(key, re)
|
|
3195
|
+
} else if (kind === 'size') {
|
|
3196
|
+
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']]
|
|
3197
|
+
for (const [re, key] of sizes) if (has(re)) return name(key, re)
|
|
3198
|
+
} else if (kind === 'radius') {
|
|
3199
|
+
const rs = [[/^(xs|sm|small)$/, 'rSm'], [/^(md|base|medium|default)$/, 'rMd'], [/^(lg|xl|large|2xl)$/, 'rLg'], [/^(full|pill|round|circle|999)$/, 'rFull']]
|
|
3200
|
+
for (const [re, key] of rs) if (has(re)) return name(key, re)
|
|
3201
|
+
const m = mod(rest(/^$/))
|
|
3202
|
+
return m ? `${t('tkn_r')} ${m}` : t('tkn_r')
|
|
3203
|
+
} else if (kind === 'space') {
|
|
3204
|
+
if (has(/^(gutter|page|edge|inset)$/)) return t('tkn_gutter')
|
|
3205
|
+
const m = rest(/^$/).join(' ')
|
|
3206
|
+
return m ? `${t('tkn_space')} ${m}` : t('tkn_space')
|
|
3207
|
+
} else if (kind === 'font') {
|
|
3208
|
+
if (has(/^(head|heading|display|title|serif|brand)$/)) return t('tkn_fHead')
|
|
3209
|
+
if (has(/^(mono|code)$/)) return t('tkn_fMono')
|
|
3210
|
+
if (has(/^(body|text|sans|base|ui)$/)) return t('tkn_fBody')
|
|
3211
|
+
} else if (kind === 'shadow') {
|
|
3212
|
+
const m = rest(/^$/).join(' ')
|
|
3213
|
+
return m ? `${t('tkn_shadow')} ${m}` : t('tkn_shadow')
|
|
3214
|
+
}
|
|
3215
|
+
return raw.replace(/[-_]+/g, ' ')
|
|
3216
|
+
}
|
|
3217
|
+
|
|
3218
|
+
/** 一套变量的人话名字(名字 → 名字):撞名的后面带上原名的尾巴,比如「卡片底(paper)」 */
|
|
3219
|
+
function tokenLabels(t, tokens) {
|
|
3220
|
+
const out = new Map()
|
|
3221
|
+
const seen = new Map()
|
|
3222
|
+
for (const tok of tokens || []) {
|
|
3223
|
+
const l = tokenLabel(t, tok)
|
|
3224
|
+
out.set(tok.name, l)
|
|
3225
|
+
seen.set(l, (seen.get(l) || 0) + 1)
|
|
3226
|
+
}
|
|
3227
|
+
for (const tok of tokens || []) {
|
|
3228
|
+
const l = out.get(tok.name)
|
|
3229
|
+
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, '')})`)
|
|
3230
|
+
}
|
|
3231
|
+
return out
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
/** 调样式面板头上:这是个什么(按钮「去预约」),不是一串选择器 */
|
|
3235
|
+
function elemLabel(t, name, text) {
|
|
3236
|
+
const n = String(name || '').toLowerCase()
|
|
3237
|
+
const tag = (/^[a-z][a-z0-9]*/.exec(n) || [''])[0]
|
|
3238
|
+
const cls = n.slice(tag.length)
|
|
3239
|
+
let k = 'box'
|
|
3240
|
+
if (/btn|button/.test(cls) || tag === 'button') k = 'button'
|
|
3241
|
+
else if (/card/.test(cls)) k = 'card'
|
|
3242
|
+
else if (/tag|chip|badge|pill/.test(cls)) k = 'tag'
|
|
3243
|
+
else if (/avatar/.test(cls)) k = 'avatar'
|
|
3244
|
+
else if (/icon|ico\b/.test(cls) || tag === 'svg' || tag === 'i') k = 'icon'
|
|
3245
|
+
else if (tag === 'a') k = 'a'
|
|
3246
|
+
else if (tag === 'img' || tag === 'picture' || tag === 'video') k = 'img'
|
|
3247
|
+
else if (/^h[1-6]$/.test(tag)) k = 'h'
|
|
3248
|
+
else if (tag === 'p') k = 'p'
|
|
3249
|
+
else if (tag === 'input' || tag === 'textarea' || tag === 'select') k = 'input'
|
|
3250
|
+
else if (tag === 'li') k = 'li'
|
|
3251
|
+
else if (tag === 'ul' || tag === 'ol') k = 'ul'
|
|
3252
|
+
else if (tag === 'nav' || /nav|tabbar|tab-bar/.test(cls)) k = 'nav'
|
|
3253
|
+
else if (tag === 'header') k = 'header'
|
|
3254
|
+
else if (tag === 'footer') k = 'footer'
|
|
3255
|
+
else if (/^(span|b|strong|em|small|label|time)$/.test(tag)) k = 'text'
|
|
3256
|
+
const s = String(text || '').replace(/\s+/g, ' ').trim()
|
|
3257
|
+
const short = [...s].length > 14 ? [...s].slice(0, 13).join('') + '…' : s
|
|
3258
|
+
return { role: t('el_' + k), text: short }
|
|
3259
|
+
}
|
|
3260
|
+
|
|
2615
3261
|
function formatToken(tok, st) {
|
|
2616
3262
|
return `〔设计变量〕${tok.name}: ${String(tok.value).replace(/\s*\n\s*/g, ' ')}(${whereOf(tok.href, st)})\n`
|
|
2617
3263
|
}
|
|
@@ -3569,6 +4215,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
3569
4215
|
const KIND_LIST = ['web', 'app', 'miniprogram', 'tablet', 'desktop', 'poster', 'slides', 'email']
|
|
3570
4216
|
const KIND_KEY = { web: 'kindWeb', app: 'kindApp', miniprogram: 'kindMini', tablet: 'kindTablet', desktop: 'kindDesktop', poster: 'kindPoster', slides: 'kindSlides', email: 'kindEmail' }
|
|
3571
4217
|
const GHOST_MS = 90000 // 这么久没出新屏就撤掉「生成中…」占位
|
|
4218
|
+
const HOLD_MAX = 15000 // 草稿放到一半真文件就落地了:最多再放这么久(一般几秒放完自己说一声)
|
|
3572
4219
|
const EDIT_GRACE_MS = 3000 // 退出改字后这么久里,页面交上来的最后一处改动还收
|
|
3573
4220
|
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
4221
|
// 外框:pad 边框厚度、top 顶上一条(浏览器栏)、r 外圆角、ir 屏幕圆角、island 灵动岛离顶的距离、capsule 小程序右上角的胶囊、win 桌面软件的窗口按钮
|
|
@@ -3662,7 +4309,8 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
3662
4309
|
|
|
3663
4310
|
/** 整块居中放进舞台(最大 100%) */
|
|
3664
4311
|
function fitCam(L, sw, sh) {
|
|
3665
|
-
|
|
4312
|
+
// 排的时候按整块舞台算的缩放;右边被面板挡着时 sw 变窄,再缩到放得下
|
|
4313
|
+
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
4314
|
return { z, x: (sw - L.bw * z) / 2, y: PAD_TOP + Math.max(0, (sh - PAD_TOP - PAD - L.bh * z) / 2) }
|
|
3667
4315
|
}
|
|
3668
4316
|
|
|
@@ -4003,6 +4651,226 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
4003
4651
|
|
|
4004
4652
|
// ── 小部件 ───────────────────────────────────────────────────────────────
|
|
4005
4653
|
|
|
4654
|
+
// 模型在做什么:按它调的工具说人话(写文件另算,见 busyPhase)
|
|
4655
|
+
const TOOL_PHASE = [
|
|
4656
|
+
[/^check_design$/, 'stCheck'], [/^look_design$/, 'stLook'], [/^preview_page$/, 'stPreview'], [/^extract_design_system$/, 'stBrand'],
|
|
4657
|
+
[/^handoff_design$/, 'stCode'], [/^compare_design$/, 'stCompare'], [/^ask_user_question$/, 'asking'],
|
|
4658
|
+
[/^(bash|shell|sh|zsh|powershell|pwsh|exec|run_command|terminal)$/i, 'stShell'],
|
|
4659
|
+
[/^skill$/i, 'stSkill'],
|
|
4660
|
+
[/web|fetch|browse|http/i, 'stWeb'],
|
|
4661
|
+
[/^(write|edit|multi_?edit|create_file|str_replace\w*|apply_patch|patch)$/i, 'stWriteAny'],
|
|
4662
|
+
[/read|grep|glob|list|^ls$|find|search|view/i, 'stRead'],
|
|
4663
|
+
]
|
|
4664
|
+
const clock = (ms) => {
|
|
4665
|
+
const s = Math.max(0, Math.floor(ms / 1000))
|
|
4666
|
+
const p2 = (n) => String(n).padStart(2, '0')
|
|
4667
|
+
return s >= 3600 ? `${Math.floor(s / 3600)}:${p2(Math.floor(s / 60) % 60)}:${p2(s % 60)}` : `${Math.floor(s / 60)}:${p2(s % 60)}`
|
|
4668
|
+
}
|
|
4669
|
+
|
|
4670
|
+
/** 顶上的状态条:在做什么 · 第几屏 · 用了多久(自己每秒走一下,不带着整个右侧栏重画) */
|
|
4671
|
+
function BusyBar({ t, phase, since, side }) {
|
|
4672
|
+
const [, tick] = useState(0)
|
|
4673
|
+
useEffect(() => {
|
|
4674
|
+
const id = setInterval(() => tick((n) => n + 1), 1000)
|
|
4675
|
+
return () => clearInterval(id)
|
|
4676
|
+
}, [])
|
|
4677
|
+
const tm = clock(Date.now() - since)
|
|
4678
|
+
return h('div', { className: 'ddp-busy' + (phase.asking ? ' ask' : '') + (side ? ' side' : '') },
|
|
4679
|
+
phase.asking ? h('i', { className: 'q', 'aria-hidden': true }) : h('span', { className: 'dots', 'aria-hidden': true }, h('i'), h('i'), h('i')),
|
|
4680
|
+
h('span', { className: 'tx', role: 'status' }, phase.text),
|
|
4681
|
+
phase.sub ? h('span', { className: 'sub' }, phase.sub) : null,
|
|
4682
|
+
h('span', { className: 'tm', title: t('stTime', { time: tm }) }, tm),
|
|
4683
|
+
phase.asking ? null : h('i', { className: 'bar', 'aria-hidden': true }))
|
|
4684
|
+
}
|
|
4685
|
+
|
|
4686
|
+
// 生成中的占位:三只带名字的鼠标轮流框出版面(按类型给个大概的样子),框里闪骨架,一遍遍画。位置都是百分比,放哪都行
|
|
4687
|
+
const GD_PLAN = {
|
|
4688
|
+
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]],
|
|
4689
|
+
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]],
|
|
4690
|
+
poster: [[8, 6, 84, 50], [8, 61, 70, 8], [8, 72, 50, 4], [8, 84, 36, 7]],
|
|
4691
|
+
slides: [[6, 10, 60, 13], [6, 32, 40, 52], [52, 32, 42, 52]],
|
|
4692
|
+
email: [[10, 4, 80, 7], [10, 14, 80, 30], [10, 48, 60, 5], [10, 56, 80, 14], [10, 75, 30, 6]],
|
|
4693
|
+
}
|
|
4694
|
+
const GD_C = ['#4d6bfe', '#e8590c', '#0ca678']
|
|
4695
|
+
/** 占位里的鼠标什么时候动:换了在做的事、或者又写了 15 行就加一;不在生成就不动 */
|
|
4696
|
+
function pulseStep(pr, busy, key, ln) {
|
|
4697
|
+
if (!busy) {
|
|
4698
|
+
pr.key = ''
|
|
4699
|
+
pr.lines = 0
|
|
4700
|
+
return pr.n
|
|
4701
|
+
}
|
|
4702
|
+
if (ln < pr.lines) pr.lines = ln
|
|
4703
|
+
if (key !== pr.key) {
|
|
4704
|
+
pr.key = key
|
|
4705
|
+
pr.lines = ln
|
|
4706
|
+
pr.n++
|
|
4707
|
+
} else if (ln - pr.lines >= 15) {
|
|
4708
|
+
pr.lines = ln
|
|
4709
|
+
pr.n++
|
|
4710
|
+
}
|
|
4711
|
+
return pr.n
|
|
4712
|
+
}
|
|
4713
|
+
const planOf = (kind) => GD_PLAN[kind === 'miniprogram' ? 'app' : kind === 'tablet' || kind === 'desktop' ? 'web' : kind] || GD_PLAN.web
|
|
4714
|
+
const reduced = () => { try { return matchMedia('(prefers-reduced-motion: reduce)').matches } catch { return false } }
|
|
4715
|
+
|
|
4716
|
+
/**
|
|
4717
|
+
* 生成中的占位。三只带名字的鼠标先把版面框一遍,框完就停住——不循环;之后模型每真有一点动静
|
|
4718
|
+
* (换了在做的事、又写了一截),才有一只去重新框一块。模型停着,它们也停着。
|
|
4719
|
+
*/
|
|
4720
|
+
function GhostDraw({ t, kind, pulse = 0 }) {
|
|
4721
|
+
const ref = useRef(null)
|
|
4722
|
+
const plan = planOf(kind)
|
|
4723
|
+
const S = useRef(null)
|
|
4724
|
+
const EASE_K = 'cubic-bezier(.3,.7,.2,1)'
|
|
4725
|
+
useEffect(() => {
|
|
4726
|
+
const el = ref.current
|
|
4727
|
+
if (!el) return
|
|
4728
|
+
const boxes = [...el.querySelectorAll('.b')]
|
|
4729
|
+
const curs = [...el.querySelectorAll('.c')]
|
|
4730
|
+
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() }
|
|
4731
|
+
S.current = s
|
|
4732
|
+
if (s.still) return
|
|
4733
|
+
const STEP = 520
|
|
4734
|
+
const MOVE = 320
|
|
4735
|
+
const DRAG = 420
|
|
4736
|
+
const paths = curs.map((_, i) => [{ ms: 0, x: s.pos[i].x, y: s.pos[i].y, o: 0 }])
|
|
4737
|
+
plan.forEach(([x, y, w, hh], i) => {
|
|
4738
|
+
const s0 = 250 + i * STEP
|
|
4739
|
+
const path = paths[i % curs.length]
|
|
4740
|
+
const last = path[path.length - 1]
|
|
4741
|
+
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 })
|
|
4742
|
+
const box = boxes[i]
|
|
4743
|
+
if (box) {
|
|
4744
|
+
s.box[i] = box.animate([
|
|
4745
|
+
{ opacity: 0, clipPath: 'inset(0 100% 100% 0)' },
|
|
4746
|
+
{ offset: 0.001, opacity: 1, clipPath: 'inset(0 100% 100% 0)', easing: EASE_K },
|
|
4747
|
+
{ opacity: 1, clipPath: 'inset(0 0 0 0)' },
|
|
4748
|
+
], { duration: DRAG, delay: s0 + MOVE, fill: 'both' })
|
|
4749
|
+
}
|
|
4750
|
+
})
|
|
4751
|
+
let end = 0
|
|
4752
|
+
curs.forEach((c, i) => {
|
|
4753
|
+
const path = paths[i]
|
|
4754
|
+
const D = path[path.length - 1].ms || 1
|
|
4755
|
+
end = Math.max(end, D)
|
|
4756
|
+
const kf = []
|
|
4757
|
+
for (const p of path) {
|
|
4758
|
+
const o = p.ms / D
|
|
4759
|
+
if (kf.length && o <= kf[kf.length - 1].offset) continue
|
|
4760
|
+
kf.push({ offset: o, left: p.x + '%', top: p.y + '%', opacity: p.o, easing: EASE_K })
|
|
4761
|
+
}
|
|
4762
|
+
if (kf[kf.length - 1].offset < 1) kf.push({ ...kf[kf.length - 1], offset: 1 })
|
|
4763
|
+
const last = path[path.length - 1]
|
|
4764
|
+
s.pos[i] = { x: last.x, y: last.y }
|
|
4765
|
+
c.classList.toggle('flip', last.x > 78)
|
|
4766
|
+
s.cur[i] = c.animate(kf, { duration: D, fill: 'forwards' })
|
|
4767
|
+
})
|
|
4768
|
+
s.till = performance.now() + end
|
|
4769
|
+
return () => { s.cur.forEach((a) => a && a.cancel()); s.box.forEach((a) => a && a.cancel()) }
|
|
4770
|
+
}, [plan])
|
|
4771
|
+
// 模型真有动静:轮到的那只去重新框一块(框过的那块先收起再跟着鼠标拉开)
|
|
4772
|
+
useEffect(() => {
|
|
4773
|
+
const s = S.current
|
|
4774
|
+
if (!s || s.still || pulse === s.seen) return
|
|
4775
|
+
s.seen = pulse
|
|
4776
|
+
const now = performance.now()
|
|
4777
|
+
if (now < s.till) return // 头一遍还没框完 / 上一下还没做完:这一下不插队
|
|
4778
|
+
const k = pulse % s.curs.length
|
|
4779
|
+
let j = Math.floor(Math.random() * plan.length)
|
|
4780
|
+
if (j === s.last) j = (j + 1) % plan.length
|
|
4781
|
+
s.last = j
|
|
4782
|
+
const [x, y, w, hh] = plan[j]
|
|
4783
|
+
const MOVE = 420
|
|
4784
|
+
const DRAG = 480
|
|
4785
|
+
const from = s.pos[k]
|
|
4786
|
+
const to = { x: Math.min(97, x + w), y: Math.min(97, y + hh) }
|
|
4787
|
+
const c = s.curs[k]
|
|
4788
|
+
const prev = s.cur[k]
|
|
4789
|
+
s.cur[k] = c.animate([
|
|
4790
|
+
{ left: from.x + '%', top: from.y + '%', opacity: 1, easing: EASE_K },
|
|
4791
|
+
{ offset: MOVE / (MOVE + DRAG), left: x + '%', top: y + '%', opacity: 1, easing: EASE_K },
|
|
4792
|
+
{ left: to.x + '%', top: to.y + '%', opacity: 1 },
|
|
4793
|
+
], { duration: MOVE + DRAG, fill: 'forwards' })
|
|
4794
|
+
if (prev) prev.cancel()
|
|
4795
|
+
s.pos[k] = to
|
|
4796
|
+
c.classList.toggle('flip', to.x > 78)
|
|
4797
|
+
const box = s.boxes[j]
|
|
4798
|
+
if (box) {
|
|
4799
|
+
const pb = s.box[j]
|
|
4800
|
+
s.box[j] = box.animate([
|
|
4801
|
+
{ opacity: 1, clipPath: 'inset(0 100% 100% 0)', easing: EASE_K },
|
|
4802
|
+
{ opacity: 1, clipPath: 'inset(0 0 0 0)' },
|
|
4803
|
+
], { duration: DRAG, delay: MOVE, fill: 'both' })
|
|
4804
|
+
if (pb) pb.cancel()
|
|
4805
|
+
}
|
|
4806
|
+
s.till = now + MOVE + DRAG
|
|
4807
|
+
}, [pulse])
|
|
4808
|
+
const names = [t('curLayout'), t('curColor'), t('curCopy')]
|
|
4809
|
+
return h('div', { className: 'ddp-gd', ref, 'aria-hidden': true },
|
|
4810
|
+
...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] } })),
|
|
4811
|
+
...GD_C.map((c, i) => h('span', { key: 'c' + i, className: 'c', style: { '--c': c, left: (30 + i * 20) + '%', top: '112%' } },
|
|
4812
|
+
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' })),
|
|
4813
|
+
h('em', null, names[i]))))
|
|
4814
|
+
}
|
|
4815
|
+
|
|
4816
|
+
/**
|
|
4817
|
+
* 边写边看:预览服务器上正在写的那一页(/某屏.html?__ddp_draft=…),浏览器边收边画。
|
|
4818
|
+
* 页面里的小脚本报到后才显出来(之前看到的是底下的占位),画布缩放告诉它,鼠标和字看起来一样大。
|
|
4819
|
+
*/
|
|
4820
|
+
function DraftFrame({ url, z = 1, lang, onEnd }) {
|
|
4821
|
+
const ref = useRef(null)
|
|
4822
|
+
const [live, setLive] = useState(false)
|
|
4823
|
+
const zr = useRef(z)
|
|
4824
|
+
zr.current = z
|
|
4825
|
+
const endR = useRef(onEnd)
|
|
4826
|
+
endR.current = onEnd
|
|
4827
|
+
const src = url + (url.includes('?') ? '&' : '?') + 'lang=' + (lang === 'en' ? 'en' : 'zh')
|
|
4828
|
+
let origin = '*'
|
|
4829
|
+
try { origin = new URL(url).origin } catch { /* 地址不对就不发 */ }
|
|
4830
|
+
const tell = (zz) => {
|
|
4831
|
+
const f = ref.current
|
|
4832
|
+
try { if (f && f.contentWindow) f.contentWindow.postMessage({ dshDraft: 'zoom', z: zz }, origin) } catch { /* 页面已经换了 */ }
|
|
4833
|
+
}
|
|
4834
|
+
useEffect(() => {
|
|
4835
|
+
setLive(false)
|
|
4836
|
+
const end = () => { if (endR.current) endR.current(url) }
|
|
4837
|
+
// 一直没报到(草稿已经收走、给的是真文件):别干等,算放完了
|
|
4838
|
+
let quiet = setTimeout(end, 6000)
|
|
4839
|
+
const on = (e) => {
|
|
4840
|
+
const f = ref.current
|
|
4841
|
+
if (!f || e.source !== f.contentWindow || !e.data) return
|
|
4842
|
+
if (e.data.dshDraft === 'end') return end() // 写完、鼠标也退场了:可以换真文件了
|
|
4843
|
+
if (e.data.dshDraft !== 'hello') return
|
|
4844
|
+
clearTimeout(quiet)
|
|
4845
|
+
quiet = null
|
|
4846
|
+
setLive(true)
|
|
4847
|
+
tell(zr.current)
|
|
4848
|
+
}
|
|
4849
|
+
addEventListener('message', on)
|
|
4850
|
+
return () => { clearTimeout(quiet); removeEventListener('message', on) }
|
|
4851
|
+
}, [src])
|
|
4852
|
+
useEffect(() => { if (live) tell(z) }, [z, live])
|
|
4853
|
+
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 })
|
|
4854
|
+
}
|
|
4855
|
+
|
|
4856
|
+
/** 没有外框的地方(第一屏的等待卡片):按设备大小排好,再整体缩进容器里 */
|
|
4857
|
+
function FitDraft({ url, kind, lang, onEnd }) {
|
|
4858
|
+
const ref = useRef(null)
|
|
4859
|
+
const [sc, setSc] = useState(0)
|
|
4860
|
+
const [w, hh] = sizeOf('', kind)
|
|
4861
|
+
useEffect(() => {
|
|
4862
|
+
const el = ref.current
|
|
4863
|
+
if (!el) return
|
|
4864
|
+
const m = () => setSc(Math.min(el.clientWidth / w, el.clientHeight / hh) || 0)
|
|
4865
|
+
m()
|
|
4866
|
+
const ro = new ResizeObserver(m)
|
|
4867
|
+
ro.observe(el)
|
|
4868
|
+
return () => ro.disconnect()
|
|
4869
|
+
}, [w, hh])
|
|
4870
|
+
return h('div', { ref, className: 'ddp-fitd' },
|
|
4871
|
+
sc ? h('div', { style: { width: w, height: hh, transform: `translate(-50%, -50%) scale(${sc})` } }, h(DraftFrame, { url, z: sc, lang, onEnd })) : null)
|
|
4872
|
+
}
|
|
4873
|
+
|
|
4006
4874
|
function Skeleton({ gone }) {
|
|
4007
4875
|
return h('div', { className: 'ddp-skel' + (gone ? ' gone' : '') },
|
|
4008
4876
|
h('b', { style: { height: 22, width: '38%' } }),
|
|
@@ -4057,23 +4925,6 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
4057
4925
|
err ? h('div', { className: 'ddp-err', style: className ? { width: '100%' } : { position: 'absolute', top: 40, left: 12 } }, err) : null)
|
|
4058
4926
|
}
|
|
4059
4927
|
|
|
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
4928
|
/**
|
|
4078
4929
|
* 外框:网页是浏览器窗口,App / 平板是带圆角边框的设备(App 还有灵动岛,小程序再加右上角的胶囊),海报 / 幻灯片 / 邮件是一张卡片。
|
|
4079
4930
|
* fill = 铺满舞台(单页看网页)。不管哪种,里面那屏都在同一个位置(换类型不重载页面)。top:页面顶栏右边是深色(胶囊画白的)
|
|
@@ -4252,9 +5103,9 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
4252
5103
|
setCur(id)
|
|
4253
5104
|
const p = P.current
|
|
4254
5105
|
const build = first ? !!p.born : !!(changes && changes.whole && p.busy)
|
|
4255
|
-
postTo(id, { type: '
|
|
5106
|
+
postTo(id, { type: 'zoom', z: p.zoom || 1 }) // 先定缩放:生成动画里的鼠标、名字按它定大小
|
|
5107
|
+
postTo(id, { type: 'reveal', build, lang: p.t && p.t('lang') === 'en' ? 'en' : 'zh' })
|
|
4256
5108
|
postTo(id, { type: 'busy', on: !!p.busy })
|
|
4257
|
-
postTo(id, { type: 'zoom', z: p.zoom || 1 })
|
|
4258
5109
|
if (p.picking) postTo(id, pickMsg())
|
|
4259
5110
|
if (p.demo) postTo(id, { type: 'demo', on: true })
|
|
4260
5111
|
if (p.textEdit) postTo(id, { type: 'edit', on: true, hint: false })
|
|
@@ -4506,27 +5357,6 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
4506
5357
|
}
|
|
4507
5358
|
|
|
4508
5359
|
/** 类型菜单:网页 / 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
5360
|
|
|
4531
5361
|
// ── 画笔标注:一层透明画布盖在预览上,画的东西按台面上的坐标(CSS 像素)一笔一笔记着,能撤销、能擦 ──
|
|
4532
5362
|
// 一笔:pen / hl { p: [[x, y]…] } · arrow / rect / oval { a: [x, y], b: [x, y] } · text { x, y, f, v };都带颜色 c,线宽 w
|
|
@@ -4793,9 +5623,9 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
4793
5623
|
const [pal, setPal] = useState(false) // 颜色板开着
|
|
4794
5624
|
const [hist, setHist] = useState({ list: [[]], i: 0 }) // 每画一笔存一份整张的,撤销 / 重做就是前后挪
|
|
4795
5625
|
const [txt, setTxt] = useState(null) // 正在写的字:{ x, y, v }
|
|
4796
|
-
//
|
|
5626
|
+
// 第一次打开时在底栏上面教一下怎么用(画了第一笔就收起;以后底栏那句够用了)
|
|
4797
5627
|
const [coach] = useState(() => {
|
|
4798
|
-
try { const n = Number(localStorage.getItem('ddp-mk-coach')) || 0; if (n >=
|
|
5628
|
+
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
5629
|
return true
|
|
4800
5630
|
})
|
|
4801
5631
|
const shapes = hist.list[hist.i]
|
|
@@ -5062,39 +5892,76 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5062
5892
|
h('button', { type: 'button', className: 'go' + (shapes.length || (txt && txt.v.trim()) ? '' : ' idle'), disabled: sending, onClick: send }, t('mkSend')))))
|
|
5063
5893
|
}
|
|
5064
5894
|
|
|
5065
|
-
/**
|
|
5066
|
-
function
|
|
5895
|
+
/** 设计变量的小预览(字号、圆角、间距、阴影、字体) */
|
|
5896
|
+
function tokenPreview(kind, v) {
|
|
5897
|
+
const px = Math.max(0, parseFloat(v) * (/rem|em$/.test(v) ? 16 : 1)) || 0
|
|
5898
|
+
switch (kind) {
|
|
5899
|
+
case 'font': return h('span', { style: { fontFamily: v, fontSize: 15, fontWeight: 600 } }, 'Aa')
|
|
5900
|
+
case 'size': return h('span', { style: { fontSize: Math.min(22, px), fontWeight: 600, lineHeight: 1 } }, '字')
|
|
5901
|
+
case 'space': return h('i', { className: 'bar', style: { width: Math.max(2, Math.min(24, px / 2)) } })
|
|
5902
|
+
case 'radius': return h('i', { className: 'rd', style: { borderTopLeftRadius: Math.min(14, px) } })
|
|
5903
|
+
case 'shadow': return h('i', { className: 'sh', style: { boxShadow: v } })
|
|
5904
|
+
default: return null
|
|
5905
|
+
}
|
|
5906
|
+
}
|
|
5907
|
+
|
|
5908
|
+
/**
|
|
5909
|
+
* 外观面板:整套(配色直接改、别的变量交给模型、调节滑块)|换风格(风格卡、参考图、我的风格)|品牌(设计系统)。
|
|
5910
|
+
* 整套里改的颜色和拖的滑块先在页面上看效果,底下「直接保存」写进文件,或「交给模型」。
|
|
5911
|
+
*/
|
|
5912
|
+
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
5913
|
const groups = {}
|
|
5068
5914
|
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
|
-
|
|
5915
|
+
const names = tokenLabels(t, tokens)
|
|
5916
|
+
const list = (tw && tw.list) || []
|
|
5917
|
+
const errs = (tw && tw.errors) || []
|
|
5918
|
+
const n = Object.keys(tv).length + list.filter((k) => hasOwn(live, k.id)).length
|
|
5919
|
+
const tabs = [['set', t('lookSet')], ['style', t('lookStyle')], ['brand', t('lookBrand')]]
|
|
5920
|
+
let body
|
|
5921
|
+
if (tab === 'style') {
|
|
5922
|
+
body = [
|
|
5923
|
+
h('p', { className: 'tip', key: 'tip' }, t('stylesSwap')),
|
|
5924
|
+
h('div', { className: 'bd', key: 'bd' }, h(StyleCards, { t, onPick: onPickStyle }),
|
|
5925
|
+
h('div', { className: 'ddp-lk-more' },
|
|
5926
|
+
onRef ? h('button', { title: t('refPickTip'), onClick: onRef }, h(I.image), h('span', null, t('lookRef'))) : null,
|
|
5927
|
+
onSaveStyle && tokens.length ? h('button', { title: t('inSaveStyleTip'), onClick: onSaveStyle }, h(I.plus), h('span', null, t('tkSaveStyle'))) : null,
|
|
5928
|
+
onInspire ? h('button', { onClick: onInspire }, h(I.box), h('span', null, t('lookInspire'))) : null)),
|
|
5929
|
+
]
|
|
5930
|
+
} else if (tab === 'brand') {
|
|
5931
|
+
body = [brand]
|
|
5932
|
+
} else {
|
|
5933
|
+
const colors = groups.color || []
|
|
5934
|
+
body = [
|
|
5935
|
+
h('div', { className: 'bd', key: 'bd' },
|
|
5936
|
+
tokens.length ? null : h('div', { className: 'none' }, t('tokensEmpty'), h('div', null, h('button', { className: 'ddp-go', onClick: onTokMake }, t('tokensAsk')))),
|
|
5937
|
+
colors.length ? h('h4', null, t('gColor'), h('span', null, t('lookColorTip'))) : null,
|
|
5938
|
+
colors.length ? h('div', { className: 'ddp-lkc' }, ...colors.map((tok) => {
|
|
5939
|
+
const v = hasOwn(tv, tok.name) ? tv[tok.name] : tok.value
|
|
5940
|
+
return h('label', { key: tok.name, className: 'it' + (hasOwn(tv, tok.name) ? ' on' : ''), title: `${tok.name}: ${v}` },
|
|
5941
|
+
h('span', { className: 'sw', style: { background: v } },
|
|
5942
|
+
h('input', { type: 'color', value: colorHex(v, tokens), 'aria-label': names.get(tok.name), onChange: (e) => onTv(tok.name, e.target.value) })),
|
|
5943
|
+
h('span', { className: 'tx' }, h('span', { className: 'nm' }, names.get(tok.name)), h('span', { className: 'vl' }, v)))
|
|
5944
|
+
})) : null,
|
|
5945
|
+
...TOKEN_GROUPS.filter((g) => g !== 'color' && groups[g]).map((g) => h('div', { key: g },
|
|
5087
5946
|
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
|
-
|
|
5947
|
+
...groups[g].map((tok) => h('button', { key: tok.name, className: 'ddp-tok', title: `${tok.name}: ${tok.value}\n${t('lookTokTip')}`, onClick: () => onTokAsk(tok) },
|
|
5948
|
+
h('span', { className: g === 'shadow' ? 'pv lt' : 'pv' }, tokenPreview(g, tok.value)),
|
|
5949
|
+
h('span', { className: 'tx' }, h('span', { className: 'nm' }, names.get(tok.name)), h('span', { className: 'vl' }, tok.value)))))),
|
|
5950
|
+
h('h4', null, t('lookTw')),
|
|
5951
|
+
list.length
|
|
5952
|
+
? 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)
|
|
5953
|
+
: h('p', { className: 'ddp-lk-none' }, errs.length ? errs.join(';') : t('lookTwNone'), ' ', h('button', { className: 'lk', onClick: () => onTwMake(errs) }, t(errs.length ? 'twFix' : 'twMake')))),
|
|
5954
|
+
n ? h('footer', { className: 'row', key: 'ft' },
|
|
5955
|
+
h('span', { className: 'cnt' }, t('lookN', { n })),
|
|
5956
|
+
h('button', { onClick: onReset }, h('span', null, t('styleReset'))),
|
|
5957
|
+
h('button', { onClick: onAsk, title: t('twAskTip') }, h('span', null, t('styleAsk'))),
|
|
5958
|
+
h('button', { className: 'go', onClick: onSave, disabled: !write.ok, title: write.ok ? t('lookSaveTip') : write.tip }, h('span', null, t('lookSave')))) : null,
|
|
5959
|
+
]
|
|
5960
|
+
}
|
|
5961
|
+
return h('div', { className: 'ddp-tokens ddp-look', onPointerDown: (e) => e.stopPropagation() },
|
|
5962
|
+
h('header', null, h(I.palette), h('b', null, t('look')), h('button', { className: 'ddp-btn', title: '×', onClick: onClose }, h(I.close))),
|
|
5963
|
+
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))),
|
|
5964
|
+
...body)
|
|
5098
5965
|
}
|
|
5099
5966
|
|
|
5100
5967
|
/** 设计体检的结果:按屏列出问题(和页面上的编号一样),点一条在页面上滚过去、那个框闪一下 */
|
|
@@ -5118,7 +5985,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5118
5985
|
n ? h('p', { className: 'tip' }, t('checkNote')) : null,
|
|
5119
5986
|
h('footer', { className: 'row' },
|
|
5120
5987
|
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
|
|
5988
|
+
n ? h('button', { className: 'go', onClick: onFix, disabled: ck.running, title: t('checkFixTip') }, h(I.sparkle), h('span', null, t('checkFix'))) : null))
|
|
5122
5989
|
}
|
|
5123
5990
|
|
|
5124
5991
|
/**
|
|
@@ -5136,13 +6003,15 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5136
6003
|
// 现在用的是哪个变量:改过的看改成了什么,没改的看样式表里怎么写的
|
|
5137
6004
|
const varOf = (k) => { const m = VAR_ONLY.exec(String(cur(k) || '').trim()); return m ? m[1] : k in sty.live ? '' : (sty.vars || {})[k] || '' }
|
|
5138
6005
|
const set = (props, v) => onSet(Object.fromEntries(props.map((p) => [p, v])))
|
|
6006
|
+
const names = tokenLabels(t, tokens)
|
|
5139
6007
|
const chips = (kind, props) => (groups[kind] || []).slice(0, 10).map((tok) => h('button', {
|
|
5140
6008
|
key: tok.name,
|
|
5141
6009
|
className: 'chip' + (kind === 'color' ? ' c' : '') + (varOf(props[0]) === tok.name ? ' on' : ''),
|
|
5142
6010
|
title: `${tok.name}: ${tok.value}`,
|
|
5143
6011
|
onClick: () => set(props, `var(${tok.name})`),
|
|
5144
|
-
}, kind === 'color' ? h('i', { style: { background: tok.value } }) : tok.name
|
|
5145
|
-
const
|
|
6012
|
+
}, kind === 'color' ? h('i', { style: { background: tok.value } }) : names.get(tok.name)))
|
|
6013
|
+
const nice = (v) => names.get(v) || v.replace(/^--/, '')
|
|
6014
|
+
const shown = (k, text) => (varOf(k) ? h('em', { className: 'v', title: t('styleVar', { name: varOf(k) }) }, nice(varOf(k))) : h('em', null, text))
|
|
5146
6015
|
const slider = (key, props, max, kind) => {
|
|
5147
6016
|
const v = Math.round(pxOf(cur(props[0]), tokens) ?? 0)
|
|
5148
6017
|
const c = kind ? chips(kind, props) : []
|
|
@@ -5160,7 +6029,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5160
6029
|
const w = Math.min(700, Math.max(400, Math.round(parseInt(cur('font-weight'), 10) / 100) * 100 || 400))
|
|
5161
6030
|
const n = Object.keys(sty.live).length
|
|
5162
6031
|
return box(
|
|
5163
|
-
h('p', { className: 'who', title: sty.selector }, h('
|
|
6032
|
+
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
6033
|
h('div', { className: 'bd' },
|
|
5165
6034
|
slider('styleFont', ['font-size'], 72, 'size'),
|
|
5166
6035
|
h('div', { className: 'row', key: 'w' },
|
|
@@ -5173,10 +6042,10 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5173
6042
|
slider('stylePadX', ['padding-left', 'padding-right'], 48, 'space'),
|
|
5174
6043
|
sty.layout ? slider('styleGap', ['gap'], 48, 'space') : null),
|
|
5175
6044
|
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: !
|
|
6045
|
+
n ? h('footer', { className: 'row' },
|
|
6046
|
+
h('button', { onClick: onReset }, h('span', null, t('styleReset'))),
|
|
6047
|
+
h('button', { onClick: onAsk, title: t('styleAskTip') }, h('span', null, t('styleAsk'))),
|
|
6048
|
+
h('button', { className: 'go', onClick: onSave, disabled: !write.ok, title: write.ok ? t('lookSaveTip') : write.tip }, h('span', null, t('styleSave')))) : null)
|
|
5180
6049
|
}
|
|
5181
6050
|
|
|
5182
6051
|
/** 风格样板:用这个风格画的一小屏(顶栏、标题、按钮、商品卡、数据卡);卡片上缩小着放,点开是原大 */
|
|
@@ -5207,12 +6076,13 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5207
6076
|
}
|
|
5208
6077
|
|
|
5209
6078
|
/** 一排风格卡:每张是缩小的样板;点一张看原大样板(左右翻),「用这个风格」才交给 onPick */
|
|
5210
|
-
function StyleCards({ t, onPick }) {
|
|
6079
|
+
function StyleCards({ t, onPick, limit }) {
|
|
5211
6080
|
const en = t('lang') === 'en'
|
|
5212
6081
|
// 内置的 20 张 + 设计库 › 灵感里存的「我的风格」
|
|
5213
6082
|
const mine = useInspire().filter((x) => x.type === 'style' && x.style && x.style.c).map(myStyleOf)
|
|
5214
6083
|
const LIST = mine.length ? [...STYLES, ...mine] : STYLES
|
|
5215
6084
|
const [on, setOn] = useState(-1) // 正在看样板的那个;-1 是看全部
|
|
6085
|
+
const [all, setAll] = useState(false) // 空白页只放前 limit 张,点「更多风格」全展开
|
|
5216
6086
|
const was = useRef(-1)
|
|
5217
6087
|
const box = useRef(null)
|
|
5218
6088
|
useEffect(() => {
|
|
@@ -5224,6 +6094,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5224
6094
|
// 列表和样板高矮不一样,外面滚动的位置会变:把样板 / 刚才那张卡滚进来
|
|
5225
6095
|
if (was.current >= 0 || on >= 0) (on >= 0 ? el : f || el).scrollIntoView({ block: 'nearest' })
|
|
5226
6096
|
if (on >= 0) was.current = on
|
|
6097
|
+
if (on >= 0 && limit && on >= limit) setAll(true) // 样板里翻到了后面的卡:回全部时要看得到它
|
|
5227
6098
|
}, [on])
|
|
5228
6099
|
const n = LIST.length
|
|
5229
6100
|
if (on >= 0) {
|
|
@@ -5245,17 +6116,12 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5245
6116
|
h('button', { className: 'ddp-btn nx', title: t('styleNext'), onClick: () => go(1) }, h(I.back)),
|
|
5246
6117
|
h('button', { className: 'ddp-go', onClick: () => onPick(x) }, t('styleUse'))))
|
|
5247
6118
|
}
|
|
5248
|
-
|
|
6119
|
+
const cut = limit && !all && n > limit
|
|
6120
|
+
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
6121
|
h('span', { className: 'pv', 'aria-hidden': 'true' }, h(StyleSample, { t, x })),
|
|
5250
6122
|
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 })))
|
|
6123
|
+
if (!limit || n <= limit) return grid
|
|
6124
|
+
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
6125
|
}
|
|
5260
6126
|
|
|
5261
6127
|
// ── 调节滑块、品牌设计系统、转成代码 ─────────────────────────────────────
|
|
@@ -5283,41 +6149,32 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5283
6149
|
return t('twAskHead') + '\n' + rows.join('\n') + '\n' + t('twAskTail') + '\n'
|
|
5284
6150
|
}
|
|
5285
6151
|
|
|
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
|
-
}
|
|
6152
|
+
/** 调节滑块的一行:模型给这套设计做的维度(tweaks.json),拖的时候所有屏一起变 */
|
|
6153
|
+
function tweakRow(t, k, live, onSet) {
|
|
6154
|
+
const v = hasOwn(live, k.id) ? live[k.id] : k.value
|
|
6155
|
+
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)))
|
|
6156
|
+
const note = k.note ? h('div', { className: 'note' }, k.note) : null
|
|
6157
|
+
if (k.kind === 'range') {
|
|
5310
6158
|
return h('div', { className: 'row', key: k.id }, lab,
|
|
5311
|
-
h('
|
|
6159
|
+
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
6160
|
}
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
6161
|
+
if (k.kind === 'color') {
|
|
6162
|
+
return h('div', { className: 'row', key: k.id }, lab,
|
|
6163
|
+
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)
|
|
6164
|
+
}
|
|
6165
|
+
return h('div', { className: 'row', key: k.id }, lab,
|
|
6166
|
+
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)
|
|
6167
|
+
}
|
|
6168
|
+
|
|
6169
|
+
/** 外观面板里改的:滑块 + 直接改的设计变量 → 交给模型的一段字 */
|
|
6170
|
+
function formatLook(t, list, live, tokens, tv) {
|
|
6171
|
+
let out = Object.keys(live).length ? formatTweaks(t, list, live) : ''
|
|
6172
|
+
const rows = Object.entries(tv).map(([name, v]) => {
|
|
6173
|
+
const tok = tokens.find((x) => x.name === name)
|
|
6174
|
+
return `- ${name}(${tok ? tokenLabel(t, tok) : name}):${tok ? tok.value : '?'} → ${v}`
|
|
6175
|
+
})
|
|
6176
|
+
if (rows.length) out += t('lookAskHead') + '\n' + rows.join('\n') + '\n' + t('lookAskTail') + '\n'
|
|
6177
|
+
return out
|
|
5321
6178
|
}
|
|
5322
6179
|
|
|
5323
6180
|
async function getJson(path) {
|
|
@@ -5348,7 +6205,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5348
6205
|
return h('button', { type: 'button', className: 'ddp-dirbtn', title: t('pickDir'), 'aria-label': t('pickDir'), disabled: busy, onClick: pick }, h(I.folder))
|
|
5349
6206
|
}
|
|
5350
6207
|
|
|
5351
|
-
function SystemsPanel({ t, rev, hasDesign, onExtract, onApply, onSay, onClose }) {
|
|
6208
|
+
function SystemsPanel({ t, rev, hasDesign, onExtract, onApply, onSay, onClose, bare }) {
|
|
5352
6209
|
const [data, setData] = useState(null)
|
|
5353
6210
|
const [err, setErr] = useState('')
|
|
5354
6211
|
const [src, setSrc] = useState('')
|
|
@@ -5366,9 +6223,13 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5366
6223
|
onExtract(v)
|
|
5367
6224
|
setSrc('')
|
|
5368
6225
|
}
|
|
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))),
|
|
6226
|
+
return h('div', { className: bare ? 'ddp-sys bare' : 'ddp-tokens ddp-sys', onPointerDown: (e) => e.stopPropagation() },
|
|
6227
|
+
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
6228
|
h('p', { className: 'tip' }, t('sysTip')),
|
|
6229
|
+
h('form', { onSubmit: go },
|
|
6230
|
+
h('input', { value: src, placeholder: t('sysPh'), 'aria-label': t('sysPh'), onChange: (e) => setSrc(e.target.value) }),
|
|
6231
|
+
h(DirButton, { t, onPicked: setSrc, onSay }),
|
|
6232
|
+
h('button', { type: 'submit', disabled: !src.trim() }, t('sysExtract'))),
|
|
5372
6233
|
h('div', { className: 'bd' },
|
|
5373
6234
|
err ? h('div', { className: 'none' }, err) : null,
|
|
5374
6235
|
data && !list.length ? h('div', { className: 'none' }, t('sysEmpty')) : null,
|
|
@@ -5379,12 +6240,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5379
6240
|
x.sources && x.sources.length ? h('small', { title: x.sources.join('\n') }, x.sources[0]) : null,
|
|
5380
6241
|
h('div', { className: 'acts' },
|
|
5381
6242
|
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')))))
|
|
6243
|
+
hasDesign ? h('button', { onClick: () => onApply(x) }, t('sysApply')) : null)))))
|
|
5388
6244
|
}
|
|
5389
6245
|
|
|
5390
6246
|
const FW_LIST = ['react', 'vue', 'next', 'html']
|
|
@@ -5392,6 +6248,12 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5392
6248
|
// 小程序的设计:先列 uni-app、原生小程序
|
|
5393
6249
|
const FW_MINI = ['uniapp', 'weapp']
|
|
5394
6250
|
|
|
6251
|
+
/** 「uni-app(HBuilderX,Vue 3)」→「uni-app · HBuilderX · Vue 3」:放进「跟现有项目一样:…」里别括号套括号 */
|
|
6252
|
+
const dirParts = (x) => String(x || '').split(/[\\/]+/).filter(Boolean)
|
|
6253
|
+
const dirName = (x) => dirParts(x).pop() || String(x || '')
|
|
6254
|
+
const dirParent = (x) => { const q = dirParts(x); return q.length > 1 ? q[q.length - 2] : '' }
|
|
6255
|
+
const flatLabel = (x) => String(x || '').replace(/\s*[((]\s*/g, ' · ').replace(/\s*[,,、]\s*/g, ' · ').replace(/\s*[))]\s*/g, ' ').replace(/\s+/g, ' ').trim()
|
|
6256
|
+
|
|
5395
6257
|
/** 转成代码:用什么做、放哪、转几屏;点了把一段话放进输入框,模型调 handoff_design 照交接包做,每屏做完 compare_design */
|
|
5396
6258
|
function HandoffPanel({ t, sid, root, fileTarget, count, cur, onGo, onSay, onClose }) {
|
|
5397
6259
|
const [info, setInfo] = useState(null)
|
|
@@ -5400,6 +6262,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5400
6262
|
const [one, setOne] = useState(false)
|
|
5401
6263
|
const [moved, setMoved] = useState(null) // 选的文件夹不能直接放:{ dest, why }('nested' 在里面新建、'beside' 放设计目录旁边)
|
|
5402
6264
|
const [checking, setChecking] = useState('') // 'busy' 正在问服务器这个文件夹;别的字是没问成的原因
|
|
6265
|
+
const [typing, setTyping] = useState(false) // 路径平时只显示文件夹名,点了才变输入框
|
|
5403
6266
|
// 现在是哪套设计、第几次选:选文件夹的窗口开着时换了设计、或者又选了一次 / 手改了路径,旧的结果作废
|
|
5404
6267
|
// hand:用户自己点过框架(换设计前一直听用户的,不再按选的文件夹自动换)
|
|
5405
6268
|
const live = useRef({ root, seq: 0, hand: false })
|
|
@@ -5450,7 +6313,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5450
6313
|
const p = info.project || {}
|
|
5451
6314
|
const same = !!p.framework
|
|
5452
6315
|
// 「跟现有项目一样」认得出 HBuilderX 的 uni-app、微信原生小程序(没有 package.json 也行)
|
|
5453
|
-
const sameLabel = p.framework === 'weapp' ? t('hoWeapp') : p.label
|
|
6316
|
+
const sameLabel = flatLabel(p.framework === 'weapp' ? t('hoWeapp') : p.label)
|
|
5454
6317
|
const list = info.kind === 'miniprogram' ? [...FW_MINI, ...FW_LIST] : FW_LIST
|
|
5455
6318
|
const name = (k) => (k === 'html' ? t('hoHtml') : k === 'uniapp' ? t('hoUni') : k === 'weapp' ? t('hoWeapp') : FW_LABEL[k])
|
|
5456
6319
|
const opts = [...(same ? [['same', t('hoSame', { label: sameLabel })]] : []), ...list.map((k) => [k, name(k)])]
|
|
@@ -5462,7 +6325,9 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5462
6325
|
...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
6326
|
h('div', { className: 'row' }, h('label', null, t('hoDest')),
|
|
5464
6327
|
h('div', { className: 'pathrow' },
|
|
5465
|
-
|
|
6328
|
+
typing || !dest.trim()
|
|
6329
|
+
? 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() } })
|
|
6330
|
+
: 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
6331
|
h(DirButton, { t, onPicked: pickDest, onSay })),
|
|
5467
6332
|
checking ? h('p', { className: 'moved' + (checking === 'busy' ? '' : ' bad') }, checking === 'busy' ? t('hoChecking') : checking)
|
|
5468
6333
|
: moved && moved.dest === dest ? h('p', { className: 'moved' }, t(moved.why === 'beside' ? 'hoBeside' : 'hoMoved', { name: moved.dest.split(/[\\/]/).pop() })) : null),
|
|
@@ -5487,40 +6352,155 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5487
6352
|
window.removeEventListener('keydown', esc)
|
|
5488
6353
|
}
|
|
5489
6354
|
}, [open])
|
|
6355
|
+
// 菜单比剩下的地方高(屏多、侧栏矮):限高、整个菜单能滚,底下的入口也点得到
|
|
6356
|
+
react.useLayoutEffect(() => {
|
|
6357
|
+
const wrap = ref.current
|
|
6358
|
+
const m = open && wrap ? wrap.querySelector(':scope > .ddp-menu') : null
|
|
6359
|
+
const box = wrap ? wrap.closest('.ddp-stage, .ddp') : null
|
|
6360
|
+
if (!m || !box) return
|
|
6361
|
+
const b = box.getBoundingClientRect()
|
|
6362
|
+
const r = m.getBoundingClientRect()
|
|
6363
|
+
const up = r.bottom <= wrap.getBoundingClientRect().top + 1
|
|
6364
|
+
const room = Math.floor(up ? r.bottom - b.top - 8 : b.bottom - r.top - 8)
|
|
6365
|
+
if (r.height > room) { m.style.maxHeight = Math.max(120, room) + 'px'; m.style.overflowY = 'auto' }
|
|
6366
|
+
}, [open])
|
|
5490
6367
|
}
|
|
5491
6368
|
|
|
5492
6369
|
/** 导出菜单:这一屏(带外框 / 纯画面 / 整页长图)、整套展示图 */
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
useDismiss(open, setOpen, ref)
|
|
6370
|
+
/** 导出的几种:这一屏(带外框 / 纯画面 / 整页长图)、整套展示图、PDF、PPT */
|
|
6371
|
+
function exportItems(t, kind, count, name) {
|
|
5496
6372
|
const items = []
|
|
5497
|
-
|
|
5498
|
-
items.push(['
|
|
5499
|
-
|
|
6373
|
+
const n = { name: name || t('exThisName') } // 写出是哪一屏,别只说「这一屏」
|
|
6374
|
+
if (FRAMED[kind]) items.push(['frame', I.single, t('exFrame', n)])
|
|
6375
|
+
items.push(['raw', I.image, t('exRaw', n)])
|
|
6376
|
+
if (kind !== 'poster' && kind !== 'slides') items.push(['long', I.long, t('exLong', n)]) // 海报、幻灯片画幅是定的,没有「整页」
|
|
5500
6377
|
if (count > 1) items.push(['show', I.canvas, t('exShow')])
|
|
5501
6378
|
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)
|
|
6379
|
+
return items
|
|
5509
6380
|
}
|
|
5510
6381
|
|
|
5511
6382
|
/**
|
|
5512
|
-
*
|
|
5513
|
-
* items: [
|
|
6383
|
+
* 工具栏上的下拉菜单(视图、交付、更多都用它)。
|
|
6384
|
+
* items: [类名, 图标, 名字, 选中, 不可用, 点了做什么(字符串就是在浏览器里打开的地址), 右边小字]
|
|
6385
|
+
* 字符串 'hd:名字' 是小标题,'sep' 是分隔线
|
|
5514
6386
|
*/
|
|
5515
|
-
|
|
6387
|
+
// 浮块按钮的悬停说明:系统 title 要等一秒多、只有一行灰字;这里停一下就出,上面名字、下面一句用途
|
|
6388
|
+
// 用法:按钮带上 tipA(名字, 说明);HoverTip 放进浮块所在的那层,自己盯着里面带 data-tip 的东西
|
|
6389
|
+
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 })
|
|
6390
|
+
function HoverTip() {
|
|
6391
|
+
const ref = useRef(null)
|
|
6392
|
+
const [tip, setTip] = useState(null) // { n, d, k, cx, y, up }
|
|
6393
|
+
useEffect(() => {
|
|
6394
|
+
const el = ref.current
|
|
6395
|
+
const box = el && el.parentElement
|
|
6396
|
+
if (!box) return
|
|
6397
|
+
let cur = null // 鼠标停在哪个按钮上
|
|
6398
|
+
let timer = 0
|
|
6399
|
+
let showing = false
|
|
6400
|
+
let warmTill = 0 // 刚看过一个:挪到旁边的按钮立刻出,不再等
|
|
6401
|
+
const show = (b) => {
|
|
6402
|
+
if (!b.isConnected || b.getAttribute('aria-expanded') === 'true') return
|
|
6403
|
+
const r = b.getBoundingClientRect()
|
|
6404
|
+
const s = box.getBoundingClientRect()
|
|
6405
|
+
const up = r.top + r.height / 2 > s.top + s.height / 2 // 下半截的按钮往上弹
|
|
6406
|
+
showing = true
|
|
6407
|
+
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 })
|
|
6408
|
+
}
|
|
6409
|
+
const hide = () => {
|
|
6410
|
+
clearTimeout(timer)
|
|
6411
|
+
if (showing) warmTill = Date.now() + 600
|
|
6412
|
+
showing = false
|
|
6413
|
+
setTip(null)
|
|
6414
|
+
}
|
|
6415
|
+
const enter = (b) => {
|
|
6416
|
+
if (b === cur) return
|
|
6417
|
+
cur = b
|
|
6418
|
+
clearTimeout(timer)
|
|
6419
|
+
if (!b) return hide()
|
|
6420
|
+
if (showing || Date.now() < warmTill) show(b)
|
|
6421
|
+
else timer = setTimeout(() => show(b), 350)
|
|
6422
|
+
}
|
|
6423
|
+
const over = (e) => { const b = e.target.closest ? e.target.closest('[data-tip]') : null; enter(b && box.contains(b) ? b : null) }
|
|
6424
|
+
const leave = () => enter(null)
|
|
6425
|
+
const down = () => { clearTimeout(timer); showing = false; warmTill = 0; setTip(null) } // 点了就收,等鼠标挪开再说
|
|
6426
|
+
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) } }
|
|
6427
|
+
const blur = () => { cur = null; hide() }
|
|
6428
|
+
const key = (e) => { if (e.key === 'Escape') down() }
|
|
6429
|
+
box.addEventListener('pointerover', over)
|
|
6430
|
+
box.addEventListener('pointerleave', leave)
|
|
6431
|
+
box.addEventListener('pointerdown', down, true)
|
|
6432
|
+
box.addEventListener('focusin', focus)
|
|
6433
|
+
box.addEventListener('focusout', blur)
|
|
6434
|
+
box.addEventListener('keydown', key, true)
|
|
6435
|
+
return () => {
|
|
6436
|
+
clearTimeout(timer)
|
|
6437
|
+
box.removeEventListener('pointerover', over)
|
|
6438
|
+
box.removeEventListener('pointerleave', leave)
|
|
6439
|
+
box.removeEventListener('pointerdown', down, true)
|
|
6440
|
+
box.removeEventListener('focusin', focus)
|
|
6441
|
+
box.removeEventListener('focusout', blur)
|
|
6442
|
+
box.removeEventListener('keydown', key, true)
|
|
6443
|
+
}
|
|
6444
|
+
}, [])
|
|
6445
|
+
// 量出自己多宽再摆(画出来之前):横着别出边,上弹的贴着按钮上沿
|
|
6446
|
+
react.useLayoutEffect(() => {
|
|
6447
|
+
const el = ref.current
|
|
6448
|
+
if (!el || !tip) return
|
|
6449
|
+
const W = el.parentElement ? el.parentElement.clientWidth : 0
|
|
6450
|
+
const w = el.offsetWidth
|
|
6451
|
+
el.style.left = Math.max(8, Math.min(W - w - 8, tip.cx - w / 2)) + 'px'
|
|
6452
|
+
el.style.top = (tip.up ? tip.y - el.offsetHeight : tip.y) + 'px'
|
|
6453
|
+
}, [tip])
|
|
6454
|
+
// 说明已经在按钮的 aria-label 里,读屏不用再念一遍
|
|
6455
|
+
return h('div', { ref, className: 'ddp-tipbox' + (tip ? ' on' : ''), 'aria-hidden': 'true' },
|
|
6456
|
+
tip ? h('div', { className: 'nm' }, h('b', null, tip.n), tip.k ? h('kbd', null, tip.k) : null) : null,
|
|
6457
|
+
tip && tip.d ? h('span', null, tip.d) : null)
|
|
6458
|
+
}
|
|
6459
|
+
|
|
6460
|
+
function DropMenu({ t, cls, btnCls, title, tip, icon, label, caret, has, open, setOpen, disabled, items, foot }) {
|
|
5516
6461
|
const ref = useRef(null)
|
|
5517
6462
|
useDismiss(open, setOpen, ref)
|
|
5518
|
-
return h('div', { className: 'ddp-kind
|
|
5519
|
-
h('button', { className: 'ddp-btn' + (open ? ' sel' : '') + (has ? ' has' : ''), title:
|
|
6463
|
+
return h('div', { className: 'ddp-kind' + (cls ? ' ' + cls : ''), ref },
|
|
6464
|
+
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) },
|
|
6465
|
+
icon ? h(icon) : null, label ? h('span', { className: 'lb' }, label) : null, caret ? h(I.caret) : null),
|
|
5520
6466
|
open ? h('div', { className: 'ddp-menu', role: 'menu' },
|
|
5521
|
-
...items.filter(Boolean).map((
|
|
5522
|
-
|
|
5523
|
-
|
|
6467
|
+
...items.filter(Boolean).map((x, i) => {
|
|
6468
|
+
if (typeof x === 'string') {
|
|
6469
|
+
if (x.startsWith('hd:')) return h('div', { key: 'h' + i, className: 'hd' }, x.slice(3))
|
|
6470
|
+
return h('div', { key: 's' + i, className: 'sp ' + x.slice(4), role: 'separator' })
|
|
6471
|
+
}
|
|
6472
|
+
const [tier, Icon, name, on, off, run, em] = x
|
|
6473
|
+
const kids = [h(Icon), h('span', null, name), em != null && em !== '' ? h('em', null, em) : null]
|
|
6474
|
+
return typeof run === 'string'
|
|
6475
|
+
? h('a', { key: name, className: tier, role: 'menuitem', href: run, target: '_blank', rel: 'noopener noreferrer', onClick: () => setOpen(false) }, ...kids)
|
|
6476
|
+
: h('button', { key: name, className: tier + (on ? ' sel' : ''), role: 'menuitem', disabled: off, onClick: () => { setOpen(false); run() } }, ...kids)
|
|
6477
|
+
}),
|
|
6478
|
+
foot ? h('div', { className: 'ft' }, foot) : null) : null)
|
|
6479
|
+
}
|
|
6480
|
+
|
|
6481
|
+
// 左上屏名 ▾:换屏(画布 / 某一屏)、多尺寸、类型、打开别的页面——「在看什么、怎么看」都在这一个菜单里
|
|
6482
|
+
function ScreenMenu({ t, label, title, tip, src, busy, list, cur, canvas, open, setOpen, onPick, onAll, onOther, sizes, kind, onKind }) {
|
|
6483
|
+
const ref = useRef(null)
|
|
6484
|
+
useDismiss(open, setOpen, ref)
|
|
6485
|
+
const n = list.length
|
|
6486
|
+
const close = (run) => () => { setOpen(false); run() }
|
|
6487
|
+
return h('div', { className: 'ddp-kind ddp-where', ref },
|
|
6488
|
+
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) },
|
|
6489
|
+
h('i', { className: 'ddp-dot' + (busy ? ' wait' : '') }),
|
|
6490
|
+
(() => { const m = /^(.+?)( · [^·]+)$/.exec(label || ''); return m ? h('span', { className: 'lb2' }, h('b', null, m[1]), h('em', null, m[2])) : h('span', null, label) })(),
|
|
6491
|
+
h(I.caret)),
|
|
6492
|
+
open ? h('div', { className: 'ddp-menu l', role: 'menu' },
|
|
6493
|
+
h('div', { className: 'hd' }, n > 1 ? t('screensHd') : t('screensT')),
|
|
6494
|
+
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),
|
|
6495
|
+
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)) },
|
|
6496
|
+
h(I.single), h('span', null, x.label)))),
|
|
6497
|
+
sizes ? h('div', { className: 'sp', role: 'separator' }) : null,
|
|
6498
|
+
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,
|
|
6499
|
+
onKind ? h('div', { className: 'hd' }, t('kind')) : null,
|
|
6500
|
+
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)) },
|
|
6501
|
+
h(I[k]), h('span', null, t(KIND_KEY[k]))))) : null,
|
|
6502
|
+
h('div', { className: 'sp', role: 'separator' }),
|
|
6503
|
+
h('button', { role: 'menuitem', onClick: close(onOther) }, h(I.folder), h('span', null, t('openOther')))) : null)
|
|
5524
6504
|
}
|
|
5525
6505
|
|
|
5526
6506
|
/** 二维码矩阵(一行一串 0 / 1)→ SVG(四周留 4 格白边) */
|
|
@@ -5553,10 +6533,12 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5553
6533
|
seen ? h('span', { key: 'st', className: 'st ok' }, h(I.check), t('phoneSeen')) : h('span', { key: 'st', className: 'st' }, h('i'), t('phoneWait')),
|
|
5554
6534
|
h('p', { key: 'fl', className: 'fine' }, t('phoneFollow')),
|
|
5555
6535
|
h('div', { key: 'row', className: 'row' },
|
|
5556
|
-
h('button', { className: 'ddp-go', onClick:
|
|
5557
|
-
h('button', { className: 'ddp-go', onClick:
|
|
5558
|
-
h('
|
|
5559
|
-
|
|
6536
|
+
h('button', { className: 'ddp-go sec', onClick: onStop }, t('phoneStop')),
|
|
6537
|
+
h('button', { className: 'ddp-go', onClick: () => onCopy(res.url) }, t('phoneCopy'))),
|
|
6538
|
+
h('details', { key: 'more', className: 'fine', open: false },
|
|
6539
|
+
h('summary', null, t('phoneTrouble')),
|
|
6540
|
+
h('p', null, t('phoneHelp')),
|
|
6541
|
+
h('p', null, t('phoneAuto'))),
|
|
5560
6542
|
]
|
|
5561
6543
|
}
|
|
5562
6544
|
return h('div', { className: 'ddp-tokens ddp-phone', onPointerDown: (e) => e.stopPropagation() },
|
|
@@ -5652,9 +6634,9 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5652
6634
|
w.exists
|
|
5653
6635
|
? [
|
|
5654
6636
|
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)),
|
|
6637
|
+
h('button', { key: 'r', title: t('wkReveal'), onClick: () => op(w, 'reveal') }, h(I.folder), h('span', null, t('wkRevealShort'))),
|
|
6638
|
+
h('button', { key: 'z', title: t('wkZip'), onClick: () => onPack(w.dir) }, h(I.download), h('span', null, t('wkZipShort'))),
|
|
6639
|
+
h('button', { key: 's', title: t('wkShare'), onClick: () => onShare(w.dir) }, h(I.share), h('span', null, t('wkShareShort'))),
|
|
5658
6640
|
h('button', { key: 'p', title: t(w.pinned ? 'wkUnpin' : 'wkPin'), className: w.pinned ? 'on' : '', onClick: () => op(w, w.pinned ? 'unpin' : 'pin') }, '★'),
|
|
5659
6641
|
]
|
|
5660
6642
|
: dirPicker() ? h('button', { key: 'l', onClick: () => relocate(w) }, t('wkRelocate')) : null,
|
|
@@ -5797,28 +6779,108 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5797
6779
|
}
|
|
5798
6780
|
const mkRevoked = (v, id, rev) => !!v && (v.revoked || []).some((r) => r.id === id && (!r.rev || !rev || r.rev === rev))
|
|
5799
6781
|
|
|
6782
|
+
// 首页顶上一条:左边名字,右边「开始 / 市场 / 我的作品 / 灵感」——设计库并进首页的页签
|
|
6783
|
+
const HOME_TABS = [['', 'homeTab'], ['market', 'libMarket'], ['works', 'libWorks'], ['inspire', 'libInspire']]
|
|
6784
|
+
function HomeBar({ t, tab, onTab, lead }) {
|
|
6785
|
+
// 左右方向键在几栏之间挪焦点,回车 / 空格再打开(按到哪就开哪会一路弹设计库)
|
|
6786
|
+
const onKey = (e) => {
|
|
6787
|
+
if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(e.key)) return
|
|
6788
|
+
const bs = [...e.currentTarget.querySelectorAll('[role=tab]')]
|
|
6789
|
+
const i = Math.max(0, bs.indexOf(document.activeElement))
|
|
6790
|
+
const n = e.key === 'Home' ? 0 : e.key === 'End' ? bs.length - 1 : (i + (e.key === 'ArrowRight' ? 1 : -1) + bs.length) % bs.length
|
|
6791
|
+
e.preventDefault()
|
|
6792
|
+
if (bs[n]) bs[n].focus()
|
|
6793
|
+
}
|
|
6794
|
+
return h('header', { className: 'ddp-homebar' + (lead ? ' led' : '') },
|
|
6795
|
+
lead || null,
|
|
6796
|
+
h('div', { className: 'nm' }, h('i', { className: 'mk', 'aria-hidden': true }), h('b', null, t('title'))),
|
|
6797
|
+
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)))))
|
|
6798
|
+
}
|
|
6799
|
+
|
|
6800
|
+
// 首页大输入框:说一句 + 类型 + 风格 → onSend(发给模型)。没写字时 Tab 用占位里的例子
|
|
6801
|
+
const ASK_KINDS = ['app', 'miniprogram', 'web', 'poster', 'slides', 'tablet', 'desktop', 'email']
|
|
6802
|
+
const ASK_EX = { web: 'exWeb', app: 'exApp', miniprogram: 'exMini', tablet: 'exTablet', desktop: 'exDesktop', poster: 'exPoster', slides: 'exSlides', email: 'exEmail' }
|
|
6803
|
+
function HomeAsk({ t, onSend, onRef, plus, draft, note, autoFocus }) {
|
|
6804
|
+
const d0 = (draft && draft.current) || {}
|
|
6805
|
+
const [text, setText] = useState(d0.text || '')
|
|
6806
|
+
const [kind, setKind] = useState(d0.kind || '')
|
|
6807
|
+
const [sty, setSty] = useState(d0.sty || null)
|
|
6808
|
+
useEffect(() => { if (draft) draft.current = { text, kind, sty } }, [text, kind, sty])
|
|
6809
|
+
useEffect(() => { if (autoFocus && ta.current) ta.current.focus({ preventScroll: true }) }, [])
|
|
6810
|
+
const [pop, setPop] = useState('') // 'plus' | 'style'
|
|
6811
|
+
const ta = useRef(null)
|
|
6812
|
+
const card = useRef(null)
|
|
6813
|
+
useDismiss(pop === 'style', () => setPop(''), card)
|
|
6814
|
+
const en = t('lang') === 'en'
|
|
6815
|
+
const ex = t(ASK_EX[kind] || 'exAuto')
|
|
6816
|
+
const back = () => { if (ta.current) ta.current.focus() }
|
|
6817
|
+
const go = () => {
|
|
6818
|
+
const s = text.trim()
|
|
6819
|
+
if (!s) return back()
|
|
6820
|
+
if (onSend({ text: s, kind, style: sty }) !== false) setText('')
|
|
6821
|
+
}
|
|
6822
|
+
const onKey = (e) => {
|
|
6823
|
+
if (e.nativeEvent && e.nativeEvent.isComposing) return
|
|
6824
|
+
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); go() } else if (e.key === 'Tab' && !e.shiftKey && !text) { e.preventDefault(); setText(ex) }
|
|
6825
|
+
}
|
|
6826
|
+
return h('div', { className: 'ddp-askwrap' },
|
|
6827
|
+
h('div', { className: 'ddp-ask', ref: card },
|
|
6828
|
+
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 }),
|
|
6829
|
+
h('div', { className: 'row' },
|
|
6830
|
+
h(DropMenu, { t, cls: 'ddp-askplus', tip: [t('askMore'), t('askMoreD')], icon: I.plus, open: pop === 'plus', setOpen: (v) => setPop(v ? 'plus' : ''), items: plus }),
|
|
6831
|
+
h('button', { className: 'chip ref', ...tipA(t('askRef'), t('askRefD')), onClick: onRef }, h(I.image), h('span', null, t('askRef'))),
|
|
6832
|
+
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') },
|
|
6833
|
+
sty ? h('i', { className: 'sw', style: { background: `linear-gradient(135deg,${sty.c.primary} 0 50%,${sty.c.accent} 50%)` } }) : h(I.palette),
|
|
6834
|
+
h('span', null, sty ? t('askStyle', { name: en ? sty.en : sty.zh }) : t('askStyleAuto')), h(I.caret)),
|
|
6835
|
+
!text ? h('button', { className: 'tabhint', tabIndex: -1, onClick: () => { setText(ex); back() } }, h('kbd', null, 'Tab'), t('askTab')) : null,
|
|
6836
|
+
h('button', { className: 'send', disabled: !text.trim(), ...tipA(t('askSend'), '', '↵'), onClick: go }, h(I.send))),
|
|
6837
|
+
pop === 'style' ? h('div', { className: 'ddp-askpop', role: 'dialog', 'aria-label': t('askStylePick') },
|
|
6838
|
+
h('div', { className: 'hd' }, h('b', null, t('askStylePick')), h('button', { className: 'lk', onClick: () => { setSty(null); setPop(''); back() } }, t('askStyleAutoBtn'))),
|
|
6839
|
+
h(StyleCards, { t, onPick: (x) => { setSty(x); setPop(''); back() } })) : null),
|
|
6840
|
+
note ? h('p', { className: 'ddp-asknote' }, h(I.info), h('span', null, note)) : null,
|
|
6841
|
+
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) },
|
|
6842
|
+
h(I[k]), h('span', null, t(KIND_KEY[k]))))))
|
|
6843
|
+
}
|
|
6844
|
+
|
|
6845
|
+
// 首页「接着做」:最近的三套(找不到文件夹的不放),点了直接打开
|
|
6846
|
+
function HomeWorks({ t, onOpen, onAll, cur, onBack }) {
|
|
6847
|
+
const norm = (x) => String(x || '').replace(/[\\/]+$/, '')
|
|
6848
|
+
const [list, setList] = useState(null)
|
|
6849
|
+
useEffect(() => {
|
|
6850
|
+
let dead = false
|
|
6851
|
+
getJson('works').then((d) => { if (!dead) setList(d.works || []) }, () => { if (!dead) setList([]) })
|
|
6852
|
+
return () => { dead = true }
|
|
6853
|
+
}, [])
|
|
6854
|
+
const ws = (list || []).filter((w) => w.exists).slice(0, 3)
|
|
6855
|
+
if (!ws.length) return null
|
|
6856
|
+
return h('section', { className: 'ddp-homesec' },
|
|
6857
|
+
h('div', { className: 'hd' }, h('b', null, t('homeRecent')), h('button', { className: 'lk', onClick: onAll }, t('homeRecentAll'))),
|
|
6858
|
+
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)) },
|
|
6859
|
+
here ? h('em', { className: 'now' }, t('wkHere')) : null,
|
|
6860
|
+
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)),
|
|
6861
|
+
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(' · ')))) })))
|
|
6862
|
+
}
|
|
6863
|
+
|
|
5800
6864
|
function TplStart({ t, onOpen, onMore }) {
|
|
5801
6865
|
const v = useMarketView()
|
|
5802
|
-
const list = v && v.designs ? [...v.designs].sort((a, b) => (b.featured - a.featured) || (
|
|
6866
|
+
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
6867
|
if (!list.length) return null
|
|
5804
|
-
return h('
|
|
6868
|
+
return h('section', { className: 'ddp-homesec ddp-tplstart' },
|
|
5805
6869
|
h('div', { className: 'hd' }, h('b', null, t('tplStartHead')), h('button', { className: 'lk', onClick: onMore }, t('tplStartMore'))),
|
|
5806
6870
|
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
6871
|
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: '
|
|
6872
|
+
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
6873
|
}
|
|
5810
6874
|
|
|
5811
|
-
function MarketTab({ t,
|
|
6875
|
+
function MarketTab({ t, cur, setCur, onUseTpl, onUseStyle, onSay }) {
|
|
5812
6876
|
const [d, setD] = useState(null)
|
|
5813
6877
|
const [err, setErr] = useState('')
|
|
5814
6878
|
const [typePick, setType] = useState('template')
|
|
5815
6879
|
const [kind, setKind] = useState('')
|
|
5816
6880
|
const [q, setQ] = useState('')
|
|
5817
6881
|
const [sort, setSort] = useState('featured')
|
|
5818
|
-
const [cur, setCur] = useState(focus || '')
|
|
5819
6882
|
const [busy, setBusy] = useState(false)
|
|
5820
6883
|
const mine = useInspire() // 收藏过的风格打勾(按市场 id 认)
|
|
5821
|
-
useEffect(() => { if (focus) setCur(focus) }, [focus])
|
|
5822
6884
|
const load = (refresh) => {
|
|
5823
6885
|
if (refresh) setBusy(true)
|
|
5824
6886
|
return loadMarket(refresh).then((x) => { setD(x); setErr('') }, (e) => setErr(e.message)).finally(() => setBusy(false))
|
|
@@ -5858,27 +6920,25 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5858
6920
|
.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
6921
|
const kinds = MK_KINDS.filter((k) => d.designs.some((e) => e.kind === k))
|
|
5860
6922
|
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(' · ')
|
|
6923
|
+
const by = e.author && !/^orderg-x$/i.test(e.author.login || '') ? e.author.name || e.author.login : '' // 官方做的不写作者(每张都一样)
|
|
6924
|
+
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
6925
|
return h('div', { key: e.id, className: 'ddp-work ddp-mk' },
|
|
5864
6926
|
h('button', { className: 'th', title: mkPick(t, e.description) || mkPick(t, e.title), onClick: () => setCur(e.id) },
|
|
5865
6927
|
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
6928
|
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))
|
|
6929
|
+
e.featured && sort !== 'featured' ? h('em', null, t('mkSortFeatured')) : null), // 按精选排时精选的本来就在前面,不再每张标
|
|
6930
|
+
h('div', { className: 'nm' }, h('b', { title: mkPick(t, e.title) }, mkPick(t, e.title))),
|
|
6931
|
+
h('div', { className: 'mt', title: meta }, meta))
|
|
5872
6932
|
}
|
|
5873
6933
|
return h('div', { className: 'ddp-market' },
|
|
5874
6934
|
h('div', { className: 'ddp-mkbar' },
|
|
5875
|
-
types.length > 1 ? h('div', { className: '
|
|
6935
|
+
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
6936
|
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
|
-
|
|
6937
|
+
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),
|
|
6938
|
+
// 类型一排横着滑;设计系统、风格不分类型:灰着,不收起来(下面的卡片不跳)
|
|
6939
|
+
kinds.length > 1 ? h('div', { className: 'ddp-mkkinds' },
|
|
6940
|
+
kinds.length > 1 ? [h('button', { key: '', className: kind === '' && type === 'template' ? 'on' : '', disabled: type !== 'template', onClick: () => setKind('') }, t('mkAllKinds')),
|
|
6941
|
+
...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
6942
|
d.off || d.error || d.needUpdate ? h('p', { className: 'ddp-libtip warn' }, d.needUpdate ? t('mkNeedUpdate') : d.off ? t('mkOff') : t('mkOffline')) : null,
|
|
5883
6943
|
!list.length ? h('div', { className: 'ddp-libnone' }, t('mkNoResult'))
|
|
5884
6944
|
: type === 'style'
|
|
@@ -5947,7 +7007,6 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5947
7007
|
: 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
7008
|
: e.thumb ? h('img', { src: `${API}mthumb?id=${encodeURIComponent(e.id)}&r=${encodeURIComponent(e.rev || '')}`, alt: '' }) : null
|
|
5949
7009
|
return h('div', { className: 'ddp-mkd' },
|
|
5950
|
-
h('button', { className: 'lk back', onClick: onBack }, '← ', t('mkBack')),
|
|
5951
7010
|
h('h3', null, mkPick(t, e.title)),
|
|
5952
7011
|
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
7012
|
mkPick(t, e.description) ? h('p', null, mkPick(t, e.description)) : null,
|
|
@@ -5967,18 +7026,26 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
5967
7026
|
e.derivedFrom ? h('li', null, t('mkDerived', { x: String(e.derivedFrom) })) : null),
|
|
5968
7027
|
h('p', { className: 'ddp-libfoot' },
|
|
5969
7028
|
h('a', { href: mkUrl(e), target: '_blank', rel: 'noopener noreferrer' }, t('mkSource')), ' · ',
|
|
5970
|
-
h('a', { href: report, target: '_blank', rel: 'noopener noreferrer' }, t('mkReport'))
|
|
7029
|
+
h('a', { href: report, target: '_blank', rel: 'noopener noreferrer', title: e.id }, t('mkReport'))))
|
|
5971
7030
|
}
|
|
5972
7031
|
|
|
5973
|
-
function LibraryView({ t, tab, setTab, focus, hasDesign, curRoot, onClose, onOpen, onPack, onShare, onSaveStyle, onUseStyle, onUseTpl, onRef, onExtract, onMarket, onSay }) {
|
|
7032
|
+
function LibraryView({ t, tab, setTab, focus, hasDesign, homey, curRoot, onClose, onOpen, onPack, onShare, onSaveStyle, onUseStyle, onUseTpl, onRef, onExtract, onMarket, onSay }) {
|
|
5974
7033
|
const tabs = [['market', t('libMarket')], ['works', t('libWorks')], ['inspire', t('libInspire')]]
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
7034
|
+
const [mkCur, setMkCur] = useState(focus || '') // 市场里点开的那一套(详情页)
|
|
7035
|
+
useEffect(() => { if (focus) setMkCur(focus) }, [focus])
|
|
7036
|
+
const inDetail = tab === 'market' && !!mkCur
|
|
7037
|
+
// 只有一个返回:在详情页回列表,不然回画布 / 空白页
|
|
7038
|
+
const back = () => (inDetail ? setMkCur('') : onClose())
|
|
7039
|
+
const backTip = inDetail ? t('mkBack') : t(hasDesign ? 'libBack' : 'libClose')
|
|
7040
|
+
return h('div', { className: 'ddp-lib', onPointerDown: (e) => e.stopPropagation(), onKeyDown: (e) => { if (e.key === 'Escape') { e.stopPropagation(); back() } } },
|
|
7041
|
+
homey
|
|
7042
|
+
? 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 })
|
|
7043
|
+
: h('header', null,
|
|
7044
|
+
h('button', { className: 'ddp-btn', title: backTip, 'aria-label': backTip, onClick: back }, h(I.back)),
|
|
7045
|
+
h('b', null, t('library')),
|
|
7046
|
+
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
7047
|
h('div', { className: 'bd' },
|
|
5981
|
-
tab === 'market' ? h(MarketTab, { t,
|
|
7048
|
+
tab === 'market' ? h(MarketTab, { t, cur: mkCur, setCur: setMkCur, onUseTpl, onUseStyle, onSay })
|
|
5982
7049
|
: tab === 'works' ? h(WorksTab, { t, curRoot, onOpen, onPack, onShare, onMarket, onSay })
|
|
5983
7050
|
: h(InspireTab, { t, hasDesign, onSaveStyle, onUseStyle, onRef, onExtract, onMarket, onSay })))
|
|
5984
7051
|
}
|
|
@@ -6031,7 +7098,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6031
7098
|
const SHARE_AGREE = [['own', 'shAgreeOwn'], ['brand', 'shAgreeBrand'], ['private', 'shAgreePrivate'], ['rules', 'shAgreeRules']]
|
|
6032
7099
|
|
|
6033
7100
|
/** 分享到设计市场:填名字、介绍、授权,勾四个确认 → 按分享的规矩查(个人信息硬拦)→ 打 zip → 打开预填好的 GitHub 投稿页 */
|
|
6034
|
-
function SharePanel({ t, sid, root, onFix, onSay, onClose }) {
|
|
7101
|
+
function SharePanel({ t, sid, root, brand, onFix, onSay, onClose }) {
|
|
6035
7102
|
const L = t('lang') === 'en' ? 'en' : 'zh'
|
|
6036
7103
|
const [info, setInfo] = useState(null)
|
|
6037
7104
|
const [title, setTitle] = useState('')
|
|
@@ -6048,7 +7115,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6048
7115
|
post('pack', { session: sid, dir: root, info: true, lang: L }).then((d) => {
|
|
6049
7116
|
if (!on) return
|
|
6050
7117
|
setInfo(d)
|
|
6051
|
-
setTitle(d.title || '')
|
|
7118
|
+
setTitle(d.title && d.title !== baseName(root) ? d.title : brand || d.title || '')
|
|
6052
7119
|
setDesc(d.description || '')
|
|
6053
7120
|
setLic(d.license === 'CC-BY-4.0' ? 'CC-BY-4.0' : 'CC0-1.0')
|
|
6054
7121
|
}, (e) => { if (on) setInfo({ error: e.message }) })
|
|
@@ -6069,6 +7136,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6069
7136
|
const done = res && res.ok && res.share
|
|
6070
7137
|
return h('div', { className: 'ddp-tokens ddp-pack ddp-share', onPointerDown: (e) => e.stopPropagation() },
|
|
6071
7138
|
h('header', null, h(I.share), h('b', null, t('shTitle')), h('button', { className: 'ddp-btn', title: '×', onClick: onClose }, h(I.close))),
|
|
7139
|
+
h('p', { className: 'tip gh' }, h(I.info), h('span', null, t('shGh'))),
|
|
6072
7140
|
h('p', { className: 'tip' }, t('shTip')),
|
|
6073
7141
|
h('div', { className: 'bd' },
|
|
6074
7142
|
root ? h('p', { className: 'which', title: root }, h(I.folder), h('span', null, baseName(root))) : h('div', { className: 'none' }, t('pkNoDesign')),
|
|
@@ -6125,6 +7193,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6125
7193
|
const [cam, setCam] = useState(null)
|
|
6126
7194
|
const [anim, setAnim] = useState(false)
|
|
6127
7195
|
const [drag, setDrag] = useState(false)
|
|
7196
|
+
const [zoomMenu, setZoomMenu] = useState(false)
|
|
6128
7197
|
const auto = useRef(true)
|
|
6129
7198
|
const camRef = useRef(cam)
|
|
6130
7199
|
camRef.current = cam
|
|
@@ -6135,8 +7204,15 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6135
7204
|
// 多尺寸:每块自己的类型和大小(items[i] = { kind, size })
|
|
6136
7205
|
const items = props.items || null
|
|
6137
7206
|
const boxes = items ? items.map((it) => deviceBox(it.kind, it.size)) : null
|
|
6138
|
-
|
|
6139
|
-
const
|
|
7207
|
+
// 上下各有一排浮块:排的时候只用中间那块高度,镜头整体往下挪 rt
|
|
7208
|
+
const rt = props.top || 0
|
|
7209
|
+
const bh = Math.max(0, box.h - rt - (props.bottom || 0))
|
|
7210
|
+
const L = box.w > 0 && bh > 0 ? (boxes ? layoutRow(boxes, box.w, bh) : layoutGrid(n, dw, dh, box.w, bh)) : null
|
|
7211
|
+
// 右边开着面板(inset 像素被挡着):排法不变,镜头只用左边看得见的那块(太窄就不让,照旧整块)
|
|
7212
|
+
const ins = props.inset && box.w - props.inset >= 240 ? props.inset : 0
|
|
7213
|
+
const vw = box.w - ins
|
|
7214
|
+
const layoutKey = L ? [n, L.cols, dw, dh, box.w, box.h, rt, bh, ins, boxes ? boxes.join(';') : ''].join('|') : ''
|
|
7215
|
+
const fitAt = () => { const c = fitCam(L, vw, bh); return { ...c, y: c.y + rt } }
|
|
6140
7216
|
|
|
6141
7217
|
useEffect(() => {
|
|
6142
7218
|
const el = ref.current
|
|
@@ -6154,16 +7230,16 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6154
7230
|
if (!L) return
|
|
6155
7231
|
if (auto.current || !camRef.current) {
|
|
6156
7232
|
setAnim(!!camRef.current)
|
|
6157
|
-
setCam(
|
|
6158
|
-
}
|
|
7233
|
+
setCam(fitAt())
|
|
7234
|
+
} else if (ins && props.sel) showKey(props.sel) // 自己挪过镜头:只把选中的那屏挪出来,别被面板挡着
|
|
6159
7235
|
}, [layoutKey])
|
|
6160
7236
|
|
|
6161
7237
|
// 让某一屏露出来(体检里点了一条):整屏不在看得见的地方就把镜头挪过去;右边 inset 像素被面板挡着
|
|
6162
7238
|
const rv = props.reveal
|
|
6163
|
-
|
|
7239
|
+
function showKey(key) {
|
|
6164
7240
|
const c = camRef.current
|
|
6165
|
-
if (!
|
|
6166
|
-
const i = screens.findIndex((x) => x.key ===
|
|
7241
|
+
if (!key || !L || !c) return
|
|
7242
|
+
const i = screens.findIndex((x) => x.key === key)
|
|
6167
7243
|
if (i < 0) return
|
|
6168
7244
|
const [cw, ch] = boxes ? boxes[i] : [dw, dh]
|
|
6169
7245
|
const vw = Math.max(120, box.w - (props.inset || 0))
|
|
@@ -6174,12 +7250,13 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6174
7250
|
let x = c.x
|
|
6175
7251
|
let y = c.y
|
|
6176
7252
|
if (x0 < 8 || x0 + w > vw - 8) x += (w > vw - 16 ? 8 : (vw - w) / 2) - x0
|
|
6177
|
-
if (y0 < LAB || y0 + hh >
|
|
7253
|
+
if (y0 < rt + LAB || y0 + hh > rt + bh - 8) y += (hh > bh - LAB - 8 ? rt + LAB : rt + (bh - hh) / 2) - y0
|
|
6178
7254
|
if (x === c.x && y === c.y) return
|
|
6179
7255
|
auto.current = false
|
|
6180
7256
|
setAnim(true)
|
|
6181
7257
|
setCam({ ...c, x, y })
|
|
6182
|
-
}
|
|
7258
|
+
}
|
|
7259
|
+
useEffect(() => { if (rv) showKey(rv.key) }, [rv && rv.n])
|
|
6183
7260
|
|
|
6184
7261
|
const zoomAt = (factor, px, py, smooth) => {
|
|
6185
7262
|
const c = camRef.current
|
|
@@ -6199,7 +7276,9 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6199
7276
|
if (!c) return
|
|
6200
7277
|
if (e.ctrlKey || e.metaKey) {
|
|
6201
7278
|
const r = el.getBoundingClientRect()
|
|
6202
|
-
|
|
7279
|
+
// 触控板捏合每次只给几个像素;鼠标滚轮(Windows 上 Ctrl + 滚轮)一格就是 100 上下,不收住一格放大近 3 倍
|
|
7280
|
+
const dy = e.deltaMode === 1 ? e.deltaY * 16 : e.deltaY
|
|
7281
|
+
zoomAt(Math.exp(-Math.max(-25, Math.min(25, dy)) * 0.01), e.clientX - r.left, e.clientY - r.top, false)
|
|
6203
7282
|
} else {
|
|
6204
7283
|
auto.current = false
|
|
6205
7284
|
setAnim(false)
|
|
@@ -6247,7 +7326,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6247
7326
|
if (!L) return
|
|
6248
7327
|
auto.current = true
|
|
6249
7328
|
setAnim(true)
|
|
6250
|
-
setCam(
|
|
7329
|
+
setCam(fitAt())
|
|
6251
7330
|
}
|
|
6252
7331
|
|
|
6253
7332
|
const z = cam ? cam.z : 1
|
|
@@ -6266,7 +7345,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6266
7345
|
className: 'ddp-cell' + (on ? ' sel' : ''),
|
|
6267
7346
|
style: { left: L.pos[i].x, top: L.pos[i].y, width: cw, height: ch },
|
|
6268
7347
|
},
|
|
6269
|
-
lab(sc.key, [h('span', { key: 's' }, labelOf(sc)), !picking && props.badge ? props.badge(sc) : null,
|
|
7348
|
+
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
7349
|
props.render(sc, z, i),
|
|
6271
7350
|
picking ? null : h('div', { className: 'ddp-shield', 'data-key': sc.key, title: items ? '' : t('openScreen'), style: { borderRadius: fr.r } })))
|
|
6272
7351
|
})
|
|
@@ -6274,7 +7353,9 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6274
7353
|
const p = L.pos[n - 1]
|
|
6275
7354
|
cells.push(h('div', { key: '__ghost', className: 'ddp-cell ghost', style: { left: p.x, top: p.y, width: dw, height: dh } },
|
|
6276
7355
|
lab('', [h('i', { key: 'i', className: 'sp' }), h('span', { key: 's' }, t('generating'))]),
|
|
6277
|
-
h(Device, { kind, w: size[0], h: size[1], label: '' },
|
|
7356
|
+
h(Device, { kind, w: size[0], h: size[1], label: '' },
|
|
7357
|
+
h(GhostDraw, { key: 'gd', t, kind, pulse: props.pulse || 0 }),
|
|
7358
|
+
props.draft ? h(DraftFrame, { key: 'df', url: props.draft, z, lang: t('lang'), onEnd: props.onDraftEnd }) : null)))
|
|
6278
7359
|
}
|
|
6279
7360
|
}
|
|
6280
7361
|
let flowSvg = null
|
|
@@ -6296,11 +7377,15 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6296
7377
|
className: 'ddp-world' + (anim ? ' anim' : ''),
|
|
6297
7378
|
style: { transform: `translate(${cam.x}px, ${cam.y}px) scale(${z})`, '--z': z },
|
|
6298
7379
|
}, ...cells, flowSvg) : null,
|
|
7380
|
+
// 右下缩放:「43% ▾」,点开是放大 / 缩小 / 适合画布 / 实际大小
|
|
6299
7381
|
h('div', { className: 'ddp-zoom' },
|
|
6300
|
-
h(
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
7382
|
+
h(DropMenu, { t, tip: [t('zoomT'), t('dZoom', { mod: MOD.replace(/\+$/, '') })], label: Math.round(z * 100) + '%', caret: true, open: zoomMenu, setOpen: setZoomMenu, items: [
|
|
7383
|
+
['', I.plus, t('zoomIn'), false, z >= ZMAX, () => center(1.25)],
|
|
7384
|
+
['', I.minus, t('zoomOut'), false, z <= ZMIN, () => center(1 / 1.25)],
|
|
7385
|
+
'sep',
|
|
7386
|
+
['', I.fit, t('fit'), false, false, fit],
|
|
7387
|
+
['', I.single, t('zoomReal'), Math.abs(z - 1) < 0.005, false, () => center(1 / z)],
|
|
7388
|
+
] })))
|
|
6304
7389
|
}
|
|
6305
7390
|
|
|
6306
7391
|
// ── 右侧栏标签页 ─────────────────────────────────────────────────────────
|
|
@@ -6317,7 +7402,6 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6317
7402
|
const [styleEdit, setStyleEdit] = useState(false) // 调样式:点元素、拖滑块,写回它的 style 属性
|
|
6318
7403
|
const [sty, setSty] = useState(null) // 调样式选中的元素:{ key, url, name, selector, text, loc, base, vars, gradient, layout, live }
|
|
6319
7404
|
const [editing, setEditing] = useState(false)
|
|
6320
|
-
const [menu, setMenu] = useState(false)
|
|
6321
7405
|
const [toast, setToast] = useState('')
|
|
6322
7406
|
const [flash, setFlash] = useState('')
|
|
6323
7407
|
const [reloadSeq, setReloadSeq] = useState(0)
|
|
@@ -6326,14 +7410,19 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6326
7410
|
const setTop = (k, dk) => setTops((cur) => (cur[k] === dk ? cur : { ...cur, [k]: dk }))
|
|
6327
7411
|
const [sel, setSel] = useState('')
|
|
6328
7412
|
const [panel, setPanel] = useState(false)
|
|
6329
|
-
const [
|
|
7413
|
+
const [lookTab, setLookTab] = useState('set') // 外观面板开在哪一页:set 整套 / style 换风格 / brand 品牌
|
|
7414
|
+
const [tv, setTv] = useState({}) // 外观面板里直接改的设计变量:名字 → 值(还没写进文件)
|
|
6330
7415
|
const [dropping, setDropping] = useState('') // 正拖着文件进来:dragKind 给的种类
|
|
6331
7416
|
const [dropSoft, setDropSoft] = useState(false) // 拖动在预览页面里:蒙层只是提示、不挡事件(松手由页面转告)
|
|
6332
7417
|
const softTimer = useRef(0)
|
|
6333
7418
|
const refInput = useRef(null)
|
|
6334
7419
|
const pageInput = useRef(null)
|
|
7420
|
+
const homeRef = useRef(null)
|
|
7421
|
+
const homeKind = useRef('') // 首页发出去时选的类型:第一屏出来前的占位按它画
|
|
6335
7422
|
const [exMenu, setExMenu] = useState(false)
|
|
6336
|
-
const [more, setMore] = useState(false) //
|
|
7423
|
+
const [more, setMore] = useState(false) // ⋯ 菜单(窄的时候收起来的几个按钮也在这里)
|
|
7424
|
+
const [scrMenu, setScrMenu] = useState(false)
|
|
7425
|
+
const brandMem = useRef({}) // 目录 → 产品名(画布上认出来的),单页时去掉标题里的它
|
|
6337
7426
|
const [hist, setHist] = useState(false) // 版本面板
|
|
6338
7427
|
const [versions, setVersions] = useState([])
|
|
6339
7428
|
const [ver, setVer] = useState(null) // 正在看的旧版本
|
|
@@ -6349,6 +7438,27 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6349
7438
|
const [demo, setDemo] = useState(null) // 演示:{ stack: [屏…], layers: [{ id, key, dir, shown }] }
|
|
6350
7439
|
const [pane, setPane] = useState('') // 右边的面板:tweaks 调节滑块、systems 品牌设计系统、handoff 转成代码、pack 打成 zip、zip 设计包没过检查
|
|
6351
7440
|
const [lib, setLib] = useState('') // 设计库:'' 关着;开着时是哪一栏(market / works / inspire)
|
|
7441
|
+
// 开着设计时叠上来的首页:'' 没开 / 'on' 开着 / 'off' 正在收起(放完收起动画再拿掉)
|
|
7442
|
+
const [homeAt, setHomeAt] = useState('')
|
|
7443
|
+
const homeDraft = useRef({ text: '', kind: '', sty: null }) // 首页输入框里写了一半的:收起再打开还在
|
|
7444
|
+
const homeOpen = () => { setLib(''); setLibFocus(''); setHomeAt('on') }
|
|
7445
|
+
const homeClose = () => {
|
|
7446
|
+
setHomeAt((c) => (c === 'on' ? 'off' : c))
|
|
7447
|
+
setTimeout(() => setHomeAt((c) => (c === 'off' ? '' : c)), reduced() ? 0 : 190)
|
|
7448
|
+
}
|
|
7449
|
+
const homeAway = () => { if (homeAt === 'on') homeClose() } // 从首页打开了设计、进了打包面板:首页收起,别挡着
|
|
7450
|
+
// 首页 / 设计库叠在上面时,Esc 只归它们:底下的调样式、改字、点选、试用别跟着退(没存的改动会丢)
|
|
7451
|
+
const overlayUp = () => { try { return !!document.querySelector('.ddp-home.over, .ddp-libwrap') } catch { return false } }
|
|
7452
|
+
useEffect(() => {
|
|
7453
|
+
if (homeAt !== 'on') return undefined
|
|
7454
|
+
const esc = (e) => {
|
|
7455
|
+
if (e.key !== 'Escape' || e.defaultPrevented) return
|
|
7456
|
+
if (document.querySelector('.ddp-home.over [aria-expanded="true"], .ddp-libwrap')) return // 先收菜单、设计库
|
|
7457
|
+
homeClose()
|
|
7458
|
+
}
|
|
7459
|
+
window.addEventListener('keydown', esc)
|
|
7460
|
+
return () => window.removeEventListener('keydown', esc)
|
|
7461
|
+
}, [homeAt])
|
|
6352
7462
|
const [libFocus, setLibFocus] = useState('') // 从空白页「从模板开始」点进来的那一套
|
|
6353
7463
|
const libDefault = () => (marketStore.has ? 'market' : 'works')
|
|
6354
7464
|
// 市场还没读过(刚开页面):等读完(本地缓存,很快)再定开哪一页
|
|
@@ -6387,6 +7497,10 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6387
7497
|
// 这一轮开始时已有的屏记下来:这轮里出了新的一屏,就在画布末尾留个「生成中…」等下一屏;
|
|
6388
7498
|
// 一阵子(GHOST_MS)没再出新屏多半是在改了,占位撤掉;正在写一个还没有的 HTML 时一定留着
|
|
6389
7499
|
const marks = useRef({ site: '', born: new Map(), built: new Set(), turnKeys: null, lastNew: 0 })
|
|
7500
|
+
const draftedR = useRef(new Map()) // 边写边看过的屏 → 什么时候:真文件出来时不再播一遍生成动画
|
|
7501
|
+
const pulseR = useRef({ key: '', lines: 0, n: 0 }) // 模型真有动静就加一(占位里的鼠标按它动)
|
|
7502
|
+
const singleDraftR = useRef('') // 单屏里正在看着写的那屏:写完换过去看它
|
|
7503
|
+
const playR = useRef({ url: '', key: '', hold: 0, end: false, meta: '', lump: false }) // 在放的草稿(见下面 dr)
|
|
6390
7504
|
const M = marks.current
|
|
6391
7505
|
if (M.site !== site) {
|
|
6392
7506
|
M.site = site
|
|
@@ -6402,13 +7516,27 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6402
7516
|
M.born.set(sc.key, busy)
|
|
6403
7517
|
if (busy) M.lastNew = Date.now()
|
|
6404
7518
|
}
|
|
6405
|
-
const isBorn = (key) => !!M.born.get(key) && !M.built.has(key)
|
|
7519
|
+
const isBorn = (key) => !!M.born.get(key) && !M.built.has(key) && !(Date.now() - (draftedR.current.get(key) || 0) < 180000)
|
|
7520
|
+
// 状态条从哪一刻开始计时:服务端说这一轮开始多久了(右侧栏中途才打开也对得上)
|
|
7521
|
+
const turnT = useRef({ on: false, at: 0 })
|
|
7522
|
+
if (busy && !turnT.current.on) turnT.current = { on: true, at: Date.now() - Math.max(0, Number(st && st.turnFor) || 0) }
|
|
7523
|
+
else if (!busy && turnT.current.on) turnT.current = { on: false, at: 0 }
|
|
7524
|
+
// 开始/结束生成时单屏的设备要让位(给状态条腾一条):这一下缓过去,别一跳
|
|
7525
|
+
const [ease, setEase] = useState(false)
|
|
7526
|
+
const busySeen = useRef(busy)
|
|
7527
|
+
useEffect(() => {
|
|
7528
|
+
if (busySeen.current === busy) return
|
|
7529
|
+
busySeen.current = busy
|
|
7530
|
+
setEase(true)
|
|
7531
|
+
const id = setTimeout(() => setEase(false), 520)
|
|
7532
|
+
return () => clearTimeout(id)
|
|
7533
|
+
}, [busy])
|
|
6406
7534
|
|
|
6407
7535
|
const infoOf = (key) => info[site + '|' + key] || null
|
|
6408
7536
|
const ik = (sc) => (sc.v ? 'v' + sc.v + '|' : '') + sc.key // 旧版本的屏单独记标题
|
|
6409
7537
|
const focus = (st && st.focus) || ''
|
|
6410
7538
|
const main = infoOf(focus) || (screens[0] && infoOf(screens[0].key)) || {}
|
|
6411
|
-
const meta = parseMeta(main.meta || '')
|
|
7539
|
+
const meta = parseMeta(main.meta || (st && st.draft && st.draft.meta) || (playR.current.end ? '' : playR.current.meta) || '') // 第一屏还在放草稿(真的没摆出来):按草稿开头写的类型
|
|
6412
7540
|
const tokens = main.tokens || []
|
|
6413
7541
|
const kind = (st && st.kind) || meta.kind || 'web'
|
|
6414
7542
|
const size = sizeOf((st && st.size) || meta.size, kind)
|
|
@@ -6416,14 +7544,68 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6416
7544
|
const canvas = view === 'canvas'
|
|
6417
7545
|
const writingNew = !!(st && st.writing) && /\.html?$/i.test(st.writing) && !screens.some((s) => s.key === st.writing)
|
|
6418
7546
|
const fresh = !!M.turnKeys && screens.some((s) => !M.turnKeys.has(s.key)) && Date.now() - M.lastNew < GHOST_MS
|
|
6419
|
-
|
|
7547
|
+
// 边写边看:模型正在写一屏新的,预览服务器上已经有它的草稿(远程看的时候本机地址打不开,还是放占位)。
|
|
7548
|
+
// 正文先到的草稿 key 是空的(还不知道是哪屏),名字到了才填上;写完落地后草稿还留一会儿,靠它认出新屏
|
|
7549
|
+
const sd = st && st.draft && st.draft.url && (busy || st.draft.live) && !away(st.draft.url) ? st.draft : null // 说完了还报的:刚写完的那份
|
|
7550
|
+
// 看着放的那份草稿:整页一下子到的(lump,预览服务器几秒里一截截放出来),真文件落地了也先放完再换。
|
|
7551
|
+
// 一路流着写的早看过了,落地就换真文件(不等放完:页面引的外部字体卡着的话 load 要等很久)。
|
|
7552
|
+
// 右侧栏头一回看到它时它得是还在写 / 刚写完的(live),中途才打开右侧栏的不补放旧的
|
|
7553
|
+
const PL = playR.current
|
|
7554
|
+
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 }
|
|
7555
|
+
else if (sd && sd.url === PL.url) {
|
|
7556
|
+
if (sd.key) PL.key = sd.key
|
|
7557
|
+
if (sd.meta) PL.meta = sd.meta
|
|
7558
|
+
if (sd.lump) PL.lump = true
|
|
7559
|
+
}
|
|
7560
|
+
const play = playR.current
|
|
7561
|
+
const landed = (key) => !!key && screens.some((x) => x.key === key)
|
|
7562
|
+
const playing = !!play.url && play.lump && !play.end && !(sd && sd.url !== play.url) && (!play.hold || Date.now() - play.hold < HOLD_MAX)
|
|
7563
|
+
const dr = sd && !landed(sd.key) ? sd : playing ? { key: play.key, url: play.url } : null
|
|
7564
|
+
if (dr && dr.url === play.url && !play.hold && (!sd || landed(dr.key))) play.hold = Date.now() // 从这时起是「放完再换」:最多等 HOLD_MAX
|
|
7565
|
+
const hideKey = dr && landed(dr.key) ? dr.key : '' // 放着的这屏已经落地:画布上先不摆真的那块(草稿就在占位那格里放)
|
|
7566
|
+
if (sd && sd.key) draftedR.current.set(sd.key, Date.now())
|
|
7567
|
+
const ghost = canvas && !ver && ((busy && (writingNew || fresh)) || !!dr)
|
|
6420
7568
|
const [, setTick] = useState(0)
|
|
7569
|
+
const draftEnd = (u) => {
|
|
7570
|
+
const p = playR.current
|
|
7571
|
+
if (p.url !== u || p.end) return
|
|
7572
|
+
p.end = true
|
|
7573
|
+
setTick((n) => n + 1)
|
|
7574
|
+
}
|
|
7575
|
+
useEffect(() => {
|
|
7576
|
+
if (!play.hold || play.end) return
|
|
7577
|
+
const id = setTimeout(() => setTick((n) => n + 1), Math.max(0, play.hold + HOLD_MAX - Date.now()) + 50)
|
|
7578
|
+
return () => clearTimeout(id)
|
|
7579
|
+
}, [play.url, play.hold, play.end])
|
|
6421
7580
|
useEffect(() => {
|
|
6422
7581
|
if (!fresh || writingNew) return
|
|
6423
7582
|
const id = setTimeout(() => setTick((n) => n + 1), Math.max(0, M.lastNew + GHOST_MS - Date.now()) + 50)
|
|
6424
7583
|
return () => clearTimeout(id)
|
|
6425
7584
|
}, [fresh, writingNew, M.lastNew])
|
|
6426
7585
|
const labelOf = (sc) => (infoOf(ik(sc)) || {}).title || sc.label
|
|
7586
|
+
// 状态条上那句:在等你回答 / 正在做第几屏 / 正在改哪屏 / 按调的工具说 / 没调工具就是在想
|
|
7587
|
+
const busyPhase = () => {
|
|
7588
|
+
if (!st) return { text: t('working') }
|
|
7589
|
+
if (st.asking) return { text: t('asking'), asking: true }
|
|
7590
|
+
const newN = screens.length + 1 // 按画布上排第几屏说(已有 3 屏、再出一屏 = 第 4 屏),和新屏出来的位置对得上
|
|
7591
|
+
const w = st.writing || ''
|
|
7592
|
+
const ln = Number(st.lines) || 0
|
|
7593
|
+
const more = ln >= 10 ? t('stLines', { n: ln }) : '' // 写了多少行:写大文件的几分钟里看得到在动
|
|
7594
|
+
if (w) {
|
|
7595
|
+
if (!/\.html?$/i.test(w)) return { text: t('writing', { file: w }), sub: more }
|
|
7596
|
+
const sc = screens.find((x) => x.key === w)
|
|
7597
|
+
return sc ? { text: t('stEdit', { name: labelOf(sc) }), sub: more } : { text: t('stNew', { n: newN }), sub: more ? `${w} · ${more}` : w }
|
|
7598
|
+
}
|
|
7599
|
+
if (st.draft && !st.draft.key && /^(write|create_file)$/i.test(String(st.tool || ''))) return { text: t('stNew', { n: newN }), sub: more } // 正文先到:看得出是新的一屏,名字还没到
|
|
7600
|
+
const tool = String(st.tool || '')
|
|
7601
|
+
if (!tool) return { text: t('stThink') }
|
|
7602
|
+
const m = TOOL_PHASE.find(([re]) => re.test(tool))
|
|
7603
|
+
if (m && m[1] === 'stWriteAny' && !url) return { text: t('stNew', { n: 1 }), sub: more } // 第一屏还没出来:写的就是它
|
|
7604
|
+
return { text: m ? t(m[1]) : t('working'), sub: m && m[1] === 'stWriteAny' ? more : '' }
|
|
7605
|
+
}
|
|
7606
|
+
const busyBar = (side) => h(BusyBar, { key: 'busy', t, phase: busyPhase(), since: turnT.current.at || Date.now(), side })
|
|
7607
|
+
// 占位里的鼠标什么时候动:换了在做的事、或者又写了 15 行——模型停着它们就停着
|
|
7608
|
+
const pulse = pulseStep(pulseR.current, busy, busy ? busyPhase().text + '|' + ((st && st.tool) || '') : '', Number(st && st.lines) || 0)
|
|
6427
7609
|
// 画布上的标题去掉几屏都带的产品名(「首页 · 梅林练」→「首页」);产品名留给导出的展示图做大标题
|
|
6428
7610
|
const shortOf = (sc) => {
|
|
6429
7611
|
const sp = splitTitles(shown.map(labelOf))
|
|
@@ -6448,8 +7630,10 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6448
7630
|
return Object.keys(out).length === Object.keys(c).length ? c : out
|
|
6449
7631
|
})
|
|
6450
7632
|
}, [tsig])
|
|
6451
|
-
|
|
6452
|
-
|
|
7633
|
+
const twGen = useRef(0) // 换一次设计加一:晚到的保存回包认得出是上一套的
|
|
7634
|
+
useEffect(() => { twGen.current++; setTw({}); setTv({}) }, [site])
|
|
7635
|
+
// 拖的滑块 + 外观面板里直接改的颜色:一起发给页面先看效果
|
|
7636
|
+
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
7637
|
|
|
6454
7638
|
// 看旧版本:画布上换成那一版的几屏(按现在的顺序,那一版独有的接在后面)
|
|
6455
7639
|
const vbase = ver ? `${origin}/__dsh_design__/v/${ver.n}/` : ''
|
|
@@ -6490,6 +7674,8 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6490
7674
|
try {
|
|
6491
7675
|
await openTarget(sid, p)
|
|
6492
7676
|
setEditing(false)
|
|
7677
|
+
homeAway() // 从叠着的首页 / 设计库里打开的:收起来,看新打开的
|
|
7678
|
+
setLib('')
|
|
6493
7679
|
} catch (e) {
|
|
6494
7680
|
say(t('openFailed', { msg: e.message }))
|
|
6495
7681
|
}
|
|
@@ -6561,6 +7747,16 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6561
7747
|
if (!patch.focus && poll && poll.state) poll.set({ ...poll.state, ...patch })
|
|
6562
7748
|
post('view', { session: sid, ...patch }).then((s) => poll && poll.set(s), (e) => say(t('openFailed', { msg: e.message })))
|
|
6563
7749
|
}
|
|
7750
|
+
// 单屏里看着写完的那屏:真文件出来了就换过去看它(不然写完一闪又回到原来那屏)
|
|
7751
|
+
useEffect(() => {
|
|
7752
|
+
let k = singleDraftR.current
|
|
7753
|
+
if (k === '*' && sd && sd.key) k = singleDraftR.current = sd.key
|
|
7754
|
+
if (!k || dr) return
|
|
7755
|
+
if (screens.some((x) => x.key === k)) {
|
|
7756
|
+
singleDraftR.current = ''
|
|
7757
|
+
if (!canvas && focus !== k) change({ view: 'single', focus: k })
|
|
7758
|
+
} else if (!busy) singleDraftR.current = ''
|
|
7759
|
+
})
|
|
6564
7760
|
|
|
6565
7761
|
// 量舞台大小,单页时按比例缩放
|
|
6566
7762
|
useEffect(() => {
|
|
@@ -6674,7 +7870,6 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6674
7870
|
setPicking(false)
|
|
6675
7871
|
setTextEdit(false)
|
|
6676
7872
|
setPanel(false)
|
|
6677
|
-
setStyles(false)
|
|
6678
7873
|
setHist(false)
|
|
6679
7874
|
setPhone(false)
|
|
6680
7875
|
closeCheck()
|
|
@@ -6718,7 +7913,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6718
7913
|
}
|
|
6719
7914
|
useEffect(() => {
|
|
6720
7915
|
if (!styleEdit) return undefined
|
|
6721
|
-
const onKey = (e) => { if (e.key === 'Escape') setStyleEdit(false) }
|
|
7916
|
+
const onKey = (e) => { if (e.key === 'Escape' && !overlayUp()) setStyleEdit(false) }
|
|
6722
7917
|
window.addEventListener('keydown', onKey)
|
|
6723
7918
|
return () => window.removeEventListener('keydown', onKey)
|
|
6724
7919
|
}, [styleEdit])
|
|
@@ -6734,14 +7929,14 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6734
7929
|
|
|
6735
7930
|
useEffect(() => {
|
|
6736
7931
|
if (!textEdit) return undefined
|
|
6737
|
-
const onKey = (e) => { if (e.key === 'Escape') setTextEdit(false) }
|
|
7932
|
+
const onKey = (e) => { if (e.key === 'Escape' && !overlayUp()) setTextEdit(false) }
|
|
6738
7933
|
window.addEventListener('keydown', onKey)
|
|
6739
7934
|
return () => window.removeEventListener('keydown', onKey)
|
|
6740
7935
|
}, [textEdit])
|
|
6741
7936
|
|
|
6742
7937
|
useEffect(() => {
|
|
6743
7938
|
if (!picking) return undefined
|
|
6744
|
-
const onKey = (e) => { if (e.key === 'Escape') setPicking(false) }
|
|
7939
|
+
const onKey = (e) => { if (e.key === 'Escape' && !overlayUp()) setPicking(false) }
|
|
6745
7940
|
window.addEventListener('keydown', onKey)
|
|
6746
7941
|
return () => window.removeEventListener('keydown', onKey)
|
|
6747
7942
|
}, [picking])
|
|
@@ -6798,7 +7993,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6798
7993
|
}
|
|
6799
7994
|
useEffect(() => {
|
|
6800
7995
|
if (!demo) return undefined
|
|
6801
|
-
const onKey = (e) => { if (e.key === 'Escape') setDemo(null) }
|
|
7996
|
+
const onKey = (e) => { if (e.key === 'Escape' && !overlayUp()) setDemo(null) }
|
|
6802
7997
|
window.addEventListener('keydown', onKey)
|
|
6803
7998
|
return () => window.removeEventListener('keydown', onKey)
|
|
6804
7999
|
}, [!!demo])
|
|
@@ -6964,15 +8159,21 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
6964
8159
|
insert(g.letter ? t('pickText', { name: labelOf(sc), where, others, base: g.stem }) : t('pickKeep', { name: labelOf(sc), where, others }), 'insertedSend')
|
|
6965
8160
|
}
|
|
6966
8161
|
|
|
6967
|
-
//
|
|
8162
|
+
// 同一屏的几个版本:标 A / B / C(原版或 -a 是 A)
|
|
8163
|
+
const takeTag = (sc) => {
|
|
8164
|
+
const g = !sc.v && takesOf(shown.map((x) => x.key), sc.key)
|
|
8165
|
+
return g ? (g.letter || 'a').toUpperCase() : ''
|
|
8166
|
+
}
|
|
8167
|
+
const tagOf = (sc) => { const L = takeTag(sc); return L ? h('b', { key: 'g', className: 'ddp-vt', title: t('takeN', { l: L }) }, L) : null }
|
|
8168
|
+
// 几个方向并排时每屏标题旁都挂着「用这版」,不用先点中那一屏才看得到
|
|
6968
8169
|
const badge = (sc) => !sc.v && takesOf(shown.map((x) => x.key), sc.key)
|
|
6969
8170
|
? h('button', { key: 'p', className: 'ddp-take', title: t('pickThis'), onClick: () => pickDirection(sc) }, h(I.check), h('span', null, t('pickShort')))
|
|
6970
8171
|
: null
|
|
6971
8172
|
|
|
6972
8173
|
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)),
|
|
8174
|
+
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'))),
|
|
8175
|
+
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'))),
|
|
8176
|
+
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
8177
|
]
|
|
6977
8178
|
|
|
6978
8179
|
// 对比:拖中间的把手
|
|
@@ -7097,14 +8298,13 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7097
8298
|
setTextEdit(false)
|
|
7098
8299
|
setStyleEdit(false)
|
|
7099
8300
|
setPanel(false)
|
|
7100
|
-
setStyles(false)
|
|
7101
8301
|
setHist(false)
|
|
7102
8302
|
setPhone(false)
|
|
7103
8303
|
closeCheck()
|
|
7104
8304
|
setPane('')
|
|
7105
8305
|
// 之前点选 / 调样式留下的「已选」框别夹在标注里
|
|
7106
8306
|
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)
|
|
8307
|
+
setMarkSize(box.w && box.h > TOP_R + BOT_R ? { w: box.w, h: box.h - TOP_R - BOT_R } : null)
|
|
7108
8308
|
setMark(true)
|
|
7109
8309
|
}
|
|
7110
8310
|
// 换了设计、看旧版本、演示:画的东西对不上了,收掉
|
|
@@ -7294,13 +8494,14 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7294
8494
|
useEffect(() => { if (checkOff && ck) closeCheck() }, [checkOff])
|
|
7295
8495
|
|
|
7296
8496
|
const toggleDemo = () => (demo ? setDemo(null) : startDemo())
|
|
7297
|
-
const
|
|
8497
|
+
const openLook = (tab) => { if (tab) setLookTab(tab); setPanel(true); setHist(false); setPhone(false); closeCheck(); setStyleEdit(false) }
|
|
8498
|
+
const toggleHist = () => { setHist(!hist); setPanel(false); setPhone(false); closeCheck(); setStyleEdit(false) }
|
|
7298
8499
|
// 右边同一时间只开一个面板:别的开了,这几个关掉;开这几个时别的关掉
|
|
7299
|
-
const oldOpen = !!(panel ||
|
|
8500
|
+
const oldOpen = !!(panel || hist || phone || ck || styleEdit)
|
|
7300
8501
|
useEffect(() => { if (oldOpen) setPane('') }, [oldOpen])
|
|
7301
8502
|
const openPane = (p) => {
|
|
7302
8503
|
const next = pane === p ? '' : p
|
|
7303
|
-
if (next) { setPanel(false);
|
|
8504
|
+
if (next) { setPanel(false); setHist(false); setPhone(false); closeCheck(); setStyleEdit(false) }
|
|
7304
8505
|
setPane(next)
|
|
7305
8506
|
}
|
|
7306
8507
|
const setTweak = (k, v) => setTw((c) => {
|
|
@@ -7308,12 +8509,24 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7308
8509
|
return { ...c, [k.id]: v }
|
|
7309
8510
|
})
|
|
7310
8511
|
const saveTweaks = async () => {
|
|
8512
|
+
if (ver) return say(t('twVer')) // 旧版本的屏不收调节:存了改的是当前设计
|
|
7311
8513
|
try {
|
|
7312
8514
|
// 带上拖的是哪套设计:请求路上会话换了设计,服务端就不写
|
|
7313
|
-
const
|
|
8515
|
+
const sentTw = tw
|
|
8516
|
+
const sentTv = tv
|
|
8517
|
+
const gen = twGen.current
|
|
8518
|
+
const r = await post('tweak', { session: sid, root: st && st.target ? st.target.root : '', values: sentTw, vars: sentTv })
|
|
7314
8519
|
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')) }))
|
|
8520
|
+
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
8521
|
else showFlash(t('twSaved', { n: r.changed.length }))
|
|
8522
|
+
// 写进去的(设计变量、滑块):放掉,页面重新读文件就是新值了(滑块和配色管同一个颜色时以配色为准,滑块的旧值也得放掉);
|
|
8523
|
+
// 没写进去的留着,还能交给模型;请求路上又改过的也留着
|
|
8524
|
+
if (r.changed && r.changed.length && gen === twGen.current) {
|
|
8525
|
+
const ids = new Set(r.changed.map((x) => x.id))
|
|
8526
|
+
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 }
|
|
8527
|
+
setTv((c) => drop(c, sentTv))
|
|
8528
|
+
setTw((c) => drop(c, sentTw))
|
|
8529
|
+
}
|
|
7317
8530
|
} catch (e) {
|
|
7318
8531
|
say(t('failed', { msg: e.message }))
|
|
7319
8532
|
}
|
|
@@ -7330,58 +8543,88 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7330
8543
|
}), 'insertedSend')
|
|
7331
8544
|
setPane('')
|
|
7332
8545
|
}
|
|
7333
|
-
const togglePhone = () => { setPhone(!phone); setPanel(false);
|
|
8546
|
+
const togglePhone = () => { setPhone(!phone); setPanel(false); setHist(false); closeCheck(); setStyleEdit(false) }
|
|
7334
8547
|
const reload = () => setReloadSeq((x) => x + 1)
|
|
7335
8548
|
|
|
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
|
-
|
|
8549
|
+
// 浮在画布上的几块,各管一件事,按钮都带字:左上 设计库 | 屏名 ▾(换屏、多尺寸、类型都在这个菜单里);
|
|
8550
|
+
// 右上 外观 体检 | 导出 ▾ ⋯;底部中间 选择 改字 样式 画笔 | 试用;右下缩放(画布自己带)
|
|
8551
|
+
// 鼠标停一下出说明卡(HoverTip):名字 + 一句用途
|
|
8552
|
+
// 窄了(样式表里):先截屏名,再收字
|
|
8553
|
+
const curKey = canvas ? '' : (focus || (screens[0] && screens[0].key) || '')
|
|
8554
|
+
const curSc = screens.find((x) => x.key === curKey)
|
|
8555
|
+
// 产品名:画布上几屏标题都带它时认得出来;单页只拿得到一屏的标题,用记下的那个把它去掉(「订单 · 巷口面馆」→「订单」)
|
|
8556
|
+
const brand0 = splitTitles(shown.map(labelOf)).brand
|
|
8557
|
+
if (brand0) brandMem.current[site] = brand0
|
|
8558
|
+
const brand = brand0 || brandMem.current[site] || ''
|
|
8559
|
+
const dropBrand = (x) => {
|
|
8560
|
+
if (!brand || x === brand) return x
|
|
8561
|
+
const p = x.split(/\s*[·||—]\s*|\s+[-–]\s+/).map((y) => y.trim()).filter(Boolean)
|
|
8562
|
+
return p.length >= 2 && (p[0] === brand || p[p.length - 1] === brand) ? p.filter((y) => y !== brand).join(' · ') : x
|
|
8563
|
+
}
|
|
8564
|
+
const stLabel = (st && st.label) || ''
|
|
8565
|
+
const scrLabel = canvas && screens.length > 1
|
|
8566
|
+
? t('screensN', { name: brand || stLabel, n: screens.length })
|
|
8567
|
+
: curSc ? dropBrand(shortOf(curSc)) + (takeTag(curSc) ? ' · ' + takeTag(curSc) : '') : stLabel
|
|
8568
|
+
// 菜单里点某一屏:单看这一屏(「全部 n 屏」回画布)
|
|
8569
|
+
const pickScreen = (key) => {
|
|
8570
|
+
setMulti(false)
|
|
8571
|
+
if (demo) setDemo(null)
|
|
8572
|
+
setSel(key)
|
|
8573
|
+
if (canvas || key !== focus) change({ view: 'single', focus: key })
|
|
8574
|
+
}
|
|
8575
|
+
// 导出菜单写出是哪一屏(和 doExport 取同一屏)
|
|
8576
|
+
const exSc = shown.find((x) => x.key === (canvas ? sel || focus : focus)) || shown[0]
|
|
8577
|
+
const exName = exSc ? dropBrand(shortOf(exSc)) + (takeTag(exSc) ? ' · ' + takeTag(exSc) : '') : ''
|
|
8578
|
+
const libOpen = () => { setLibFocus(''); if (lib) setLib(''); else libDefaultThen((k) => setLib((c) => c || k)) }
|
|
8579
|
+
const dockTight = box.w > 0 && box.w <= 400 // 侧栏很窄:底部工具坞只放得下四个,「试用」进「更多」
|
|
8580
|
+
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))
|
|
8581
|
+
const islands = editing
|
|
8582
|
+
? [h('div', { key: 'top', className: 'ddp-top' },
|
|
8583
|
+
h('div', { className: 'ddp-isl tl wide' },
|
|
8584
|
+
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 })))]
|
|
8585
|
+
: [
|
|
8586
|
+
h('div', { key: 'top', className: 'ddp-top' },
|
|
8587
|
+
h('div', { className: 'ddp-isl tl' },
|
|
8588
|
+
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'))),
|
|
8589
|
+
h('span', { className: 'ddp-sep' }),
|
|
8590
|
+
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,
|
|
8591
|
+
onPick: pickScreen, onAll: () => { setMulti(false); if (demo) setDemo(null); change({ view: 'canvas' }) }, onOther: () => setEditing(true),
|
|
8592
|
+
sizes: mark ? null : { on: inMulti, off: !sizeSet || !!ver, tip: sizeSet ? t('sizes') : t('sizesNo'), note: sizeSet ? '' : t('sizesNoShort'), run: () => setMulti(!inMulti) },
|
|
8593
|
+
kind, onKind: mark ? null : (k) => change({ kind: k }) })),
|
|
8594
|
+
h('div', { className: 'ddp-isl tr' },
|
|
8595
|
+
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'))),
|
|
8596
|
+
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()) },
|
|
8597
|
+
h(I.stetho), h('span', { className: 'lb' }, t('checkShort')), ckCount && !ck && !checkOff ? h('span', { className: 'bdg' }, ckCount > 99 ? '99+' : String(ckCount)) : null),
|
|
8598
|
+
h('span', { className: 'ddp-sep' }),
|
|
8599
|
+
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: [
|
|
8600
|
+
'hd:' + t('deliverCode'),
|
|
8601
|
+
['', I.code, t('handoff'), pane === 'handoff', !!ver, () => openPane('handoff')],
|
|
8602
|
+
'hd:' + t('exFiles'),
|
|
8603
|
+
...exportItems(t, kind, shown.length, exName).map(([mode, Icon, label]) => ['', Icon, label, false, false, () => doExport(mode)]),
|
|
8604
|
+
fileTarget ? 'hd:' + t('deliverShare') : null, // 只有本地的设计才能打包、分享
|
|
8605
|
+
fileTarget ? ['', I.box, t('exZip'), false, false, () => doExport('zip')] : null,
|
|
8606
|
+
fileTarget ? ['', I.share, t('exShare'), false, false, () => doExport('share')] : null,
|
|
8607
|
+
], foot: t('exWhere') }),
|
|
8608
|
+
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: [
|
|
8609
|
+
dockTight ? ['', I.play, t('demoShort'), !!demo, !url || !!ver, toggleDemo] : null, // 侧栏太窄,底部工具坞放不下「试用」:挪到这里
|
|
8610
|
+
dockTight ? 'sep' : null,
|
|
8611
|
+
['', I.history, t('history'), hist || !!ver, !fileTarget, toggleHist],
|
|
8612
|
+
['', I.scan, t('phone'), phone, !url, togglePhone, phoneOn ? t('phoneOnShort') : ''],
|
|
8613
|
+
anyDark || dark ? ['', I.moon, t('dark'), dark, false, () => setDark(!dark)] : null,
|
|
8614
|
+
'sep',
|
|
8615
|
+
['', I.refresh, t('refresh'), false, false, reload],
|
|
8616
|
+
['', I.external, t('external'), false, !url, url],
|
|
8617
|
+
] }))),
|
|
8618
|
+
h('div', { key: 'dock', className: 'ddp-isl ddp-dock', role: 'toolbar', 'aria-label': t('toolsT') },
|
|
8619
|
+
tool('pick', picking, t('dPick'), !url || !!ver || !!demo || mark, togglePick, I.pick, t('pick')),
|
|
8620
|
+
tool('', textEdit, t('dText'), !url || !!ver || !!demo || busy || mark, toggleText, I.text, t('textShort')),
|
|
8621
|
+
tool('', styleEdit, t('dStyle'), styleOff || mark, toggleStyle, I.sliders, t('styleShort')),
|
|
8622
|
+
tool('', mark, t('dMark'), markOff, toggleMark, I.pen, t('markShort')),
|
|
8623
|
+
dockTight ? null : h('span', { className: 'ddp-sep dm' }),
|
|
8624
|
+
dockTight ? null : tool('dm', !!demo, t('dPlay'), !url || !!ver, toggleDemo, I.play, t('demoShort'))),
|
|
8625
|
+
]
|
|
7378
8626
|
|
|
7379
8627
|
// 模型用提问卡问你、在等回答:不转圈,换成一个橙点
|
|
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
8628
|
// ── 参考图:拖进来 / 粘贴 / 从设计规范面板选;存进设计目录的 refs/,尺寸和主要颜色放进输入框 ──
|
|
7386
8629
|
const addRef = async (file) => {
|
|
7387
8630
|
if (!file || !/^image\//.test(file.type)) return say(t('refNotImage'))
|
|
@@ -7477,9 +8720,10 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7477
8720
|
setTab: (k) => { setLibFocus(''); setLib(k) },
|
|
7478
8721
|
focus: libFocus,
|
|
7479
8722
|
hasDesign: !!url,
|
|
8723
|
+
homey: !url || homeAt === 'on',
|
|
7480
8724
|
curRoot: fileTarget ? st.target.root : '',
|
|
7481
8725
|
onClose: () => { setLib(''); setLibFocus('') },
|
|
7482
|
-
onOpen: (w) => openTarget(sid, w.dir).then(() => { setLib(''); setEditing(false) }, (e) => say(t('wkOpenFail', { msg: e.message }))),
|
|
8726
|
+
onOpen: (w) => openTarget(sid, w.dir).then(() => { setLib(''); setEditing(false); homeAway() }, (e) => say(t('wkOpenFail', { msg: e.message }))),
|
|
7483
8727
|
onPack: openPack,
|
|
7484
8728
|
onShare: openShare,
|
|
7485
8729
|
onSaveStyle: saveMyStyle,
|
|
@@ -7491,17 +8735,20 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7491
8735
|
onSay: say,
|
|
7492
8736
|
}),
|
|
7493
8737
|
toast ? h('div', { className: 'ddp-toast low' }, h('span', null, toast.text)) : null) : null
|
|
7494
|
-
|
|
8738
|
+
// 开着设计时点「首页」:首页叠在画布上(设计库再叠在它上面)
|
|
8739
|
+
const root = (...kids) => h('div', rootProps, ...kids, url && homeAt ? homeView(true) : null, libLayer, dropLayer, fileInput, pageFileInput)
|
|
7495
8740
|
|
|
7496
8741
|
// ── 设计库:打成 zip、拖进来的设计包、存成我的风格 ──
|
|
7497
8742
|
function openPack(dir) {
|
|
7498
8743
|
setPackDir(dir || '')
|
|
7499
8744
|
setLib('')
|
|
8745
|
+
homeAway()
|
|
7500
8746
|
if (pane !== 'pack') openPane('pack')
|
|
7501
8747
|
}
|
|
7502
8748
|
function openShare(dir) {
|
|
7503
8749
|
setPackDir(dir || '')
|
|
7504
8750
|
setLib('')
|
|
8751
|
+
homeAway()
|
|
7505
8752
|
if (pane !== 'share') openPane('share')
|
|
7506
8753
|
}
|
|
7507
8754
|
const sameDir = (a, b) => String(a || '').replace(/[\\/]+$/, '') === String(b || '').replace(/[\\/]+$/, '')
|
|
@@ -7532,6 +8779,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7532
8779
|
if (d.error === 'check') {
|
|
7533
8780
|
setZipIssues({ errors: d.errors || [], more: d.more || 0 })
|
|
7534
8781
|
setLib('')
|
|
8782
|
+
homeAway()
|
|
7535
8783
|
if (pane !== 'zip') openPane('zip')
|
|
7536
8784
|
return undefined
|
|
7537
8785
|
}
|
|
@@ -7542,6 +8790,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7542
8790
|
}
|
|
7543
8791
|
setLib('')
|
|
7544
8792
|
setEditing(false)
|
|
8793
|
+
homeAway()
|
|
7545
8794
|
if (pane === 'zip' || pane === 'pack' || pane === 'share') { setPane(''); setZipIssues(null) }
|
|
7546
8795
|
if (poll && d.snap) poll.set(d.snap)
|
|
7547
8796
|
insert(formatTpl(t, d))
|
|
@@ -7560,6 +8809,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7560
8809
|
}
|
|
7561
8810
|
setLib('')
|
|
7562
8811
|
setEditing(false)
|
|
8812
|
+
homeAway()
|
|
7563
8813
|
if (pane === 'zip' || pane === 'pack' || pane === 'share') { setPane(''); setZipIssues(null) }
|
|
7564
8814
|
if (poll && d.snap) poll.set(d.snap)
|
|
7565
8815
|
insert(formatTpl(t, d))
|
|
@@ -7585,20 +8835,6 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7585
8835
|
|
|
7586
8836
|
// 调节滑块 / 品牌设计系统 / 转成代码(右边的面板)
|
|
7587
8837
|
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
8838
|
if (pane === 'systems') {
|
|
7603
8839
|
return h(SystemsPanel, {
|
|
7604
8840
|
t,
|
|
@@ -7614,7 +8850,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7614
8850
|
return h(PackPanel, { t, sid, root: packDir || (fileTarget ? st.target.root : ''), onFix: fixPack, onSay: say, onClose: () => setPane('') })
|
|
7615
8851
|
}
|
|
7616
8852
|
if (pane === 'share') {
|
|
7617
|
-
return h(SharePanel, { t, sid, root: packDir || (fileTarget ? st.target.root : ''), onFix: fixPack, onSay: say, onClose: () => setPane('') })
|
|
8853
|
+
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
8854
|
}
|
|
7619
8855
|
if (pane === 'zip' && zipIssues) {
|
|
7620
8856
|
return h(ZipIssues, { t, issues: zipIssues, onClose: () => { setPane(''); setZipIssues(null) } })
|
|
@@ -7625,46 +8861,112 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7625
8861
|
return null
|
|
7626
8862
|
}
|
|
7627
8863
|
|
|
8864
|
+
// 首页大输入框发出去:放进 DSH 的输入框直接发;输入框里本来有字、或旧版 DSH 没有 submit:放进去等用户自己发
|
|
8865
|
+
const homeSend = ({ text, kind, style }) => {
|
|
8866
|
+
const ia = props.inputActions
|
|
8867
|
+
const cel = composerEl()
|
|
8868
|
+
const was = cel ? composerText(cel) : null
|
|
8869
|
+
const had = was === null || !!was.trim() // 读不出来(有引用标签、分屏)当成有字:只放进去,不替用户发
|
|
8870
|
+
const msg = text + (kind ? '\n' + t('askKindLine', { k: t(KIND_KEY[kind]) }) : '') + (url ? '\n' + t('askNewLine') : '')
|
|
8871
|
+
homeKind.current = kind || ''
|
|
8872
|
+
if (!insert(msg, 'inserted', false)) {
|
|
8873
|
+
insert([msg, style ? formatStyle(t, style, false) : ''].filter(Boolean).join('\n\n')) // 插不进:整段复制
|
|
8874
|
+
return false
|
|
8875
|
+
}
|
|
8876
|
+
if (style) insert(formatStyle(t, style, false), 'inserted', false)
|
|
8877
|
+
if (url) homeClose() // 开着设计时从首页发的:收起首页,看着新的一套长出来
|
|
8878
|
+
if (had || !ia || typeof ia.submit !== 'function') { say(t('insertedSend')); return true }
|
|
8879
|
+
setTimeout(() => {
|
|
8880
|
+
try { ia.submit(); say(t('askSent')) } catch (e) { console.warn('[dsh-design-studio] submit 失败', e); say(t('insertedSend')) }
|
|
8881
|
+
}, 80)
|
|
8882
|
+
return true
|
|
8883
|
+
}
|
|
8884
|
+
// 「+」里的「打开已有的 HTML」:桌面版直接选文件;网页版展开下面的地址框
|
|
8885
|
+
const openHtml = () => {
|
|
8886
|
+
if (browse) return browse()
|
|
8887
|
+
const d = homeRef.current && homeRef.current.querySelector('.ddp-ways')
|
|
8888
|
+
if (!d) { if (url) { homeClose(); setEditing(true) } return } // 叠着的首页没有地址框:用左上角的
|
|
8889
|
+
d.open = true
|
|
8890
|
+
d.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
|
|
8891
|
+
const i = d.querySelector('input')
|
|
8892
|
+
if (i) i.focus({ preventScroll: true })
|
|
8893
|
+
}
|
|
8894
|
+
const toLib = (k, id) => { setLibFocus(id || ''); setLib(k) }
|
|
8895
|
+
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 },
|
|
8896
|
+
h(HomeBar, {
|
|
8897
|
+
t,
|
|
8898
|
+
tab: '',
|
|
8899
|
+
onTab: (k) => { if (k) toLib(k) },
|
|
8900
|
+
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,
|
|
8901
|
+
}),
|
|
8902
|
+
h('div', { className: 'ddp-homebd' },
|
|
8903
|
+
h('div', { className: 'ddp-start' },
|
|
8904
|
+
h('div', { className: 'ddp-hero' }, h('h1', null, t('emptyHead')), h('p', null, t('emptySub'))),
|
|
8905
|
+
!over && st && st.hint ? h('button', {
|
|
8906
|
+
className: 'ddp-go',
|
|
8907
|
+
onClick: () => post('open', { session: sid, target: st.hint }).then((x) => { if (poll) poll.set(x) }, (e) => say(t('openFailed', { msg: e.message }))),
|
|
8908
|
+
}, t('hintOpen', { name: st.hint.split(/[\\/]/).pop() })) : null,
|
|
8909
|
+
h(HomeAsk, {
|
|
8910
|
+
t,
|
|
8911
|
+
draft: homeDraft,
|
|
8912
|
+
autoFocus: over,
|
|
8913
|
+
note: over ? t('askNewNote') : '',
|
|
8914
|
+
onSend: homeSend,
|
|
8915
|
+
onRef: pickRef,
|
|
8916
|
+
plus: [
|
|
8917
|
+
['', I.page, t('askOpenHtml'), false, false, openHtml],
|
|
8918
|
+
['', I.palette, t('askBrand'), false, false, () => { if (over) homeClose(); openPane('systems') }],
|
|
8919
|
+
['', I.library, t('askLib'), false, false, () => { setLibFocus(''); libDefaultThen((k) => setLib((c) => c || k)) }],
|
|
8920
|
+
],
|
|
8921
|
+
}),
|
|
8922
|
+
h(HomeWorks, {
|
|
8923
|
+
t,
|
|
8924
|
+
cur: over && fileTarget ? st.target.root : '',
|
|
8925
|
+
onBack: homeClose,
|
|
8926
|
+
onOpen: (w) => openTarget(sid, w.dir).then(() => { setEditing(false); if (over) homeClose() }, (e) => say(t('wkOpenFail', { msg: e.message }))),
|
|
8927
|
+
onAll: () => toLib('works'),
|
|
8928
|
+
}),
|
|
8929
|
+
h(TplStart, { t, onOpen: (id) => toLib('market', id), onMore: () => toLib('market') }),
|
|
8930
|
+
over ? null : h('details', { className: 'ddp-ways' },
|
|
8931
|
+
h('summary', null, h(I.caret), t('waysHtml')),
|
|
8932
|
+
h('div', { className: 'in' },
|
|
8933
|
+
h('p', null, t(browse ? 'emptyHintPick' : 'emptyHint')),
|
|
8934
|
+
h(AddressForm, { t, sid, className: 'ddp-form', onDone: () => {}, onBrowse: browse }))))),
|
|
8935
|
+
h(HoverTip, { key: 'tip' }))
|
|
8936
|
+
|
|
7628
8937
|
if (!url) {
|
|
7629
8938
|
const toastEl = toast ? h('div', { className: 'ddp-toast' }, h('span', null, toast.text)) : null
|
|
7630
8939
|
if (st ? st.loading : !waited) return root(h('div', { className: 'ddp-stage', ref: stageRef }), toastEl)
|
|
7631
8940
|
if (busy) {
|
|
7632
|
-
//
|
|
8941
|
+
// 模型在做第一屏:先放一个生成中的占位(按选的类型画个大概的样子)
|
|
8942
|
+
const waitKind = homeKind.current || (st && st.kind) || 'web'
|
|
7633
8943
|
return root(
|
|
7634
|
-
h('div', { className: 'ddp-stage', ref: stageRef },
|
|
7635
|
-
h('div', { className: 'ddp-wait' },
|
|
8944
|
+
h('div', { className: 'ddp-stage bz', ref: stageRef },
|
|
8945
|
+
h('div', { className: 'ddp-wait' },
|
|
8946
|
+
h('div', { className: 'card k-' + waitKind },
|
|
8947
|
+
h(GhostDraw, { key: 'gd', t, kind: waitKind, pulse }),
|
|
8948
|
+
dr ? h(FitDraft, { key: 'df', url: dr.url, kind: waitKind, lang: t('lang'), onEnd: draftEnd }) : null),
|
|
8949
|
+
h('p', null, t(dr ? 'waitingLive' : 'waiting'))),
|
|
7636
8950
|
h('div', { className: 'ddp-glow' }),
|
|
7637
8951
|
h('div', { className: 'ddp-ring' }),
|
|
7638
|
-
|
|
8952
|
+
busyBar(false)),
|
|
7639
8953
|
toastEl)
|
|
7640
8954
|
}
|
|
7641
8955
|
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)) }))),
|
|
8956
|
+
homeView(false),
|
|
7659
8957
|
pane === 'systems' || pane === 'pack' || pane === 'share' || pane === 'zip' ? sidePane() : null,
|
|
7660
8958
|
toastEl)
|
|
7661
8959
|
}
|
|
7662
8960
|
|
|
8961
|
+
// 上下两排浮块中间那块的高度:单页、演示、对比按它缩放
|
|
8962
|
+
const ah = Math.max(0, box.h - TOP_R - BOT_R)
|
|
8963
|
+
const cvBody = !demo && (inMulti || (canvas && !(ver && cmp)))
|
|
7663
8964
|
let body
|
|
8965
|
+
let busyGap = false
|
|
7664
8966
|
if (demo) {
|
|
7665
8967
|
const fill = kind === 'web'
|
|
7666
8968
|
const [dw, dh] = deviceBox(kind, size)
|
|
7667
|
-
const z = fill ? 1 : Math.max(0.05, Math.min(1, (box.w - 32) / dw, (
|
|
8969
|
+
const z = fill ? 1 : Math.max(0.05, Math.min(1, (box.w - 32) / dw, (ah - 16 - 46) / dh))
|
|
7668
8970
|
const top = demo.layers[demo.layers.length - 1]
|
|
7669
8971
|
body = h('div', { className: 'ddp-demo' }, ...demo.layers.map((l) => {
|
|
7670
8972
|
const isTop = l.id === top.id
|
|
@@ -7701,6 +9003,8 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7701
9003
|
body = h(Canvas, {
|
|
7702
9004
|
key: 'multi',
|
|
7703
9005
|
t,
|
|
9006
|
+
top: TOP_R,
|
|
9007
|
+
bottom: BOT_R,
|
|
7704
9008
|
screens: fake,
|
|
7705
9009
|
items,
|
|
7706
9010
|
kind,
|
|
@@ -7738,7 +9042,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7738
9042
|
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
9043
|
const fill = kind === 'web'
|
|
7740
9044
|
const [dw, dh] = deviceBox(kind, size)
|
|
7741
|
-
const z = fill ? 1 : Math.max(0.05, Math.min(1, (box.w - 32) / dw, (
|
|
9045
|
+
const z = fill ? 1 : Math.max(0.05, Math.min(1, (box.w - 32) / dw, (ah - 16 - (ver ? 46 : 0)) / dh))
|
|
7742
9046
|
// 两边的小标签不跟着画面缩小(缩了看不清);手机、平板躲开状态栏
|
|
7743
9047
|
const tagAt = (side) => ({ top: kind === 'app' || kind === 'miniprogram' || kind === 'tablet' ? 60 : 10, transform: `scale(${1 / z})`, transformOrigin: 'top ' + side })
|
|
7744
9048
|
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 +9063,23 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7759
9063
|
body = h(Canvas, {
|
|
7760
9064
|
key: 'grid',
|
|
7761
9065
|
t,
|
|
7762
|
-
|
|
9066
|
+
top: TOP_R + (ver ? 46 : 0), // 看旧版本时上面还有一条横幅
|
|
9067
|
+
bottom: BOT_R,
|
|
9068
|
+
screens: hideKey && !ver ? shown.filter((x) => x.key !== hideKey) : shown,
|
|
7763
9069
|
actions,
|
|
7764
9070
|
badge,
|
|
9071
|
+
tag: tagOf,
|
|
7765
9072
|
kind,
|
|
7766
9073
|
size,
|
|
7767
9074
|
ghost,
|
|
9075
|
+
draft: ghost && dr ? dr.url : '',
|
|
9076
|
+
onDraftEnd: draftEnd,
|
|
9077
|
+
pulse,
|
|
7768
9078
|
picking: picking || textEdit || styleEdit,
|
|
7769
9079
|
sel: sel || focus,
|
|
7770
9080
|
labelOf: shortOf,
|
|
7771
9081
|
reveal,
|
|
7772
|
-
inset: ck || styleEdit || panel ||
|
|
9082
|
+
inset: ck || styleEdit || panel || pane || hist || phone ? 296 : 0, // 右边的面板 276 宽,离边 10
|
|
7773
9083
|
render: (sc, z) => renderScreen(sc, z, false),
|
|
7774
9084
|
flows,
|
|
7775
9085
|
onSelect: setSel,
|
|
@@ -7779,30 +9089,41 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7779
9089
|
const fill = kind === 'web'
|
|
7780
9090
|
const [dw, dh] = deviceBox(kind, size)
|
|
7781
9091
|
// 右边开着面板(体检、调样式、设计规范、换风格):手机这类有外框的让到面板左边(侧栏够宽时),别被挡住
|
|
7782
|
-
const
|
|
9092
|
+
const sideOn = !!(ck || styleEdit || panel || pane || hist || phone) && box.w - 296 >= 240
|
|
9093
|
+
const aside = !fill && sideOn ? 296 : 0
|
|
9094
|
+
// 生成中:右侧栏窄(或开着面板)时状态条在上排浮块下面,手机这类给它让一条;宽的时候它在上排中间,不用让。网页不让(一让就重排)
|
|
9095
|
+
busyGap = busy && !fill && (box.w < 860 || sideOn)
|
|
7783
9096
|
// 画笔标注开着:网页按开标注时的大小定住(侧栏拉窄了就整体缩小,不重排),画的才对得上
|
|
7784
9097
|
const lock = fill && mark && markSize ? markSize : null
|
|
7785
|
-
const z = lock ? Math.max(0.05, Math.min(1, box.w / lock.w,
|
|
9098
|
+
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
9099
|
const one = ver ? shown.find((x) => x.key === focus) || shown[0] || { key: focus, url, label: st.label } : { key: focus, url, label: st.label }
|
|
9100
|
+
// 单屏时模型在写新的一屏:先看着它写(没在选、改字、调样式、标注的时候),写完换过去
|
|
9101
|
+
const watch = dr && !ver && !demo && !picking && !textEdit && !styleEdit && !mark && !lock
|
|
9102
|
+
if (watch) singleDraftR.current = dr.key || '*' // 还不知道是哪屏:名字到了再说
|
|
7787
9103
|
body = h('div', { className: 'ddp-single', style: aside ? { right: aside } : undefined },
|
|
7788
9104
|
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
|
-
|
|
9105
|
+
watch
|
|
9106
|
+
? h(Device, { key: 'draft', kind, w: size[0], h: size[1], fill, label: '', os: meta.os },
|
|
9107
|
+
h(GhostDraw, { key: 'gd', t, kind, pulse }),
|
|
9108
|
+
h(DraftFrame, { key: 'df', url: dr.url, z: fill ? 1 : z, lang: t('lang'), onEnd: draftEnd }))
|
|
9109
|
+
: renderScreen(one, z, fill)))
|
|
7790
9110
|
}
|
|
7791
9111
|
|
|
7792
9112
|
let chip = null
|
|
7793
|
-
const
|
|
9113
|
+
const sideOpen = !!(ck || styleEdit || panel || pane || hist || phone) && box.w - 296 >= 240 // 右边开着面板:提示放在左边那块的中间
|
|
9114
|
+
const side = sideOpen ? ' side' : ''
|
|
7794
9115
|
const hintText = picking ? t(canvas || inMulti ? 'pickAny' : 'pickOne') : textEdit ? t(canvas || inMulti ? 'textAny' : 'textOne') : styleEdit && !sty ? t(canvas ? 'styleAny' : 'styleOne') : ''
|
|
7795
9116
|
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
9117
|
|
|
7798
9118
|
return root(
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
h('div', { className: 'ddp-body' + (ver || demo || hintTop ? ' vb' : '') }, body),
|
|
9119
|
+
h('div', { className: 'ddp-stage isl' + (busy ? ' bz' : ''), ref: stageRef },
|
|
9120
|
+
h('div', { className: 'ddp-body' + (cvBody ? '' : ' pad' + (ver || demo || hintTop || busyGap ? ' vb' : '') + (ease ? ' ease' : '')) }, body),
|
|
7802
9121
|
busy ? h('div', { className: 'ddp-glow' }) : null,
|
|
7803
9122
|
busy ? h('div', { className: 'ddp-ring' }) : null,
|
|
9123
|
+
busy ? busyBar(sideOpen) : null,
|
|
7804
9124
|
// 右边开着面板时提示条居中在面板左边那块,别被面板盖住一半
|
|
7805
|
-
|
|
9125
|
+
// 提示只占一个位置:有新消息(toast)时它先让开,消息走了再回来
|
|
9126
|
+
hintText && !toast ? h('div', { className: 'ddp-hint' + (sideOpen ? ' side' : '') }, hintText) : null,
|
|
7806
9127
|
ck ? h(CheckPanel, { t, ck, onFocus: focusIssue, onFix: fixIssues, onAgain: runCheck, onClose: closeCheck }) : null,
|
|
7807
9128
|
styleEdit ? h(StylePanel, {
|
|
7808
9129
|
t,
|
|
@@ -7815,11 +9136,40 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7815
9136
|
onSave: saveStyle,
|
|
7816
9137
|
onClose: () => setStyleEdit(false),
|
|
7817
9138
|
}) : null,
|
|
7818
|
-
panel ? h(
|
|
7819
|
-
|
|
7820
|
-
|
|
9139
|
+
panel ? h(LookPanel, {
|
|
9140
|
+
t,
|
|
9141
|
+
tab: lookTab,
|
|
9142
|
+
setTab: setLookTab,
|
|
9143
|
+
tokens,
|
|
9144
|
+
tv,
|
|
9145
|
+
onTv: (name, v) => setTv((c) => ({ ...c, [name]: v })),
|
|
9146
|
+
onTokAsk: (tok) => insert(formatToken(tok, stRef.current)),
|
|
9147
|
+
onTokMake: () => insert(t('tokensAskText') + '\n', 'insertedSend'),
|
|
9148
|
+
tw: tweaks,
|
|
9149
|
+
live: tw,
|
|
9150
|
+
onTw: setTweak,
|
|
9151
|
+
onTwMake: (errs) => insert(errs && errs.length ? t('twFixText', { errs: errs.join(';') }) + '\n' : t('twMakeText') + '\n', 'insertedSend'),
|
|
9152
|
+
write: !fileTarget ? { ok: false, tip: t('twNoWrite') } : ver ? { ok: false, tip: t('twVer') } : busy ? { ok: false, tip: t('styleBusyTip') } : { ok: true, tip: t('lookSaveTip') },
|
|
9153
|
+
onReset: () => { setTw({}); setTv({}) },
|
|
9154
|
+
onAsk: () => insert(formatLook(t, tlist, tw, tokens, tv), 'insertedSend'),
|
|
9155
|
+
onSave: saveTweaks,
|
|
9156
|
+
onPickStyle: (x) => { setPanel(false); insert(formatStyle(t, x, true), 'insertedSend') },
|
|
9157
|
+
onRef: pickRef,
|
|
9158
|
+
onSaveStyle: saveMyStyle,
|
|
9159
|
+
onInspire: () => { setPanel(false); setLib('inspire') },
|
|
9160
|
+
brand: lookTab === 'brand' ? h(SystemsPanel, {
|
|
9161
|
+
t,
|
|
9162
|
+
bare: true,
|
|
9163
|
+
rev: (st && st.turns) || 0,
|
|
9164
|
+
hasDesign: !!url,
|
|
9165
|
+
onExtract: (src) => insert(t('sysExtractText', { src }) + '\n', 'insertedSend'),
|
|
9166
|
+
onApply: (x) => insert(t('sysApplyText', { name: x.name, dir: x.dir }) + '\n', 'insertedSend'),
|
|
9167
|
+
onSay: say,
|
|
9168
|
+
onClose: () => setPanel(false),
|
|
9169
|
+
}) : null,
|
|
9170
|
+
onClose: () => setPanel(false),
|
|
9171
|
+
}) : null,
|
|
7821
9172
|
pane ? sidePane() : null,
|
|
7822
|
-
styles ? h(StylesPanel, { t, onPick: (x) => { setStyles(false); insert(formatStyle(t, x, true), 'insertedSend') }, onClose: () => setStyles(false) }) : null,
|
|
7823
9173
|
phone ? h(PhonePanel, {
|
|
7824
9174
|
t,
|
|
7825
9175
|
sid,
|
|
@@ -7833,19 +9183,23 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7833
9183
|
mark && !markOff ? h(MarkLayer, { t, sending: markBusy, frames: markFrames, onCancel: () => { markRun.current++; setMark(false) }, onSend: sendMark, onCopy: canCopyImage() ? copyMark : null }) : null,
|
|
7834
9184
|
demo ? h('div', { className: 'ddp-banner' },
|
|
7835
9185
|
h(I.play),
|
|
7836
|
-
h('
|
|
7837
|
-
h('
|
|
7838
|
-
|
|
7839
|
-
h('button', { className: 'ddp-btn',
|
|
9186
|
+
h('b', null, t('demoOn')),
|
|
9187
|
+
h('span', { className: 'nm' }, dropBrand((infoOf(demoKey) || {}).title || demoKey.replace(/\.html?$/i, ''))),
|
|
9188
|
+
demo.stack.length < 2 ? h('em', null, t('demoHow')) : null,
|
|
9189
|
+
h('button', { className: 'ddp-btn', ...tipA(t('demoBack')), disabled: demo.stack.length < 2, onClick: demoBack }, h(I.back)),
|
|
9190
|
+
h('button', { className: 'ddp-btn', ...tipA(t('demoRestart')), disabled: demo.stack.length < 2, onClick: demoRestart }, h(I.refresh)),
|
|
9191
|
+
h('button', { className: 'ddp-btn', ...tipA(t('demoExit'), '', 'Esc'), onClick: () => setDemo(null) }, h(I.close))) : null,
|
|
7840
9192
|
ver ? h('div', { className: 'ddp-banner' },
|
|
7841
9193
|
h(I.history),
|
|
7842
9194
|
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')),
|
|
9195
|
+
h('button', { className: 'ddp-go sec' + (cmp ? ' on' : ''), onClick: () => setCmp(!cmp) }, t('compare')),
|
|
7844
9196
|
h('button', { className: 'ddp-go pri', onClick: doRestore }, t('restore')),
|
|
7845
9197
|
h('button', { className: 'ddp-btn', title: t('backNow'), onClick: () => { setVer(null); setCmp(false) } }, h(I.close))) : null,
|
|
7846
9198
|
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
|
|
9199
|
+
toast ? h('div', { className: 'ddp-toast' + (mark ? ' mk' : ver || demo ? ' low' : '') + (sideOpen && !mark ? ' side' : ''), role: 'status' }, h('span', null, toast.text),
|
|
9200
|
+
toast.act ? h('button', { onClick: () => { toast.act.run(); setToast('') } }, toast.act.label) : null) : null,
|
|
9201
|
+
...islands,
|
|
9202
|
+
h(HoverTip, { key: 'tip' })))
|
|
7849
9203
|
}
|
|
7850
9204
|
|
|
7851
9205
|
// ── 标题栏按钮:打开预览,顺便在模型开了新页面时把右侧栏弹出来 ──────────
|
|
@@ -7894,7 +9248,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7894
9248
|
ctx.effect(() => ctx.sidebarRightTabs.register({
|
|
7895
9249
|
id: TAB_ID,
|
|
7896
9250
|
kind: KIND,
|
|
7897
|
-
title: () => t('
|
|
9251
|
+
title: () => t('tabTitle'),
|
|
7898
9252
|
keepMounted: true,
|
|
7899
9253
|
guide: [{ id: 'open', order: 30, title: () => t('title'), description: () => t('guideDesc'), icon: GuideIcon }],
|
|
7900
9254
|
}), 'dsh-design-studio: tab type')
|
|
@@ -7971,7 +9325,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
|
|
|
7971
9325
|
// 注意:这里是客户端**服务名**(短的),不是包名
|
|
7972
9326
|
exports.inject = INJECT
|
|
7973
9327
|
// 测试用
|
|
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 }
|
|
9328
|
+
exports._test = { pulseStep, 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 }
|
|
7975
9329
|
return module.exports
|
|
7976
9330
|
},
|
|
7977
9331
|
})
|