@deepblend/dsh-blender-ui 0.2.2 → 0.2.4
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/lib/client.js +454 -137
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -90,6 +90,323 @@ window.__ModuleLoader__.load({
|
|
|
90
90
|
const h = (...args) => react().createElement(...args)
|
|
91
91
|
|
|
92
92
|
// =========================================================================
|
|
93
|
+
// #region strings — every user-facing word in this file
|
|
94
|
+
//
|
|
95
|
+
// WHY A TABLE. The harness localizes itself: `dsh-client-locale` ships two locales (`zh`, `en`)
|
|
96
|
+
// and states its own fallback rule — "English is both the locale the UI opens in when the browser
|
|
97
|
+
// names no registered language (and for non-browser runs), and the dictionary consulted after the
|
|
98
|
+
// active locale misses a key ... because a browser naming no registered language is the reader
|
|
99
|
+
// least likely to read Chinese" (`FALLBACK_LOCALE = "en"`). This workbench used to hard-code
|
|
100
|
+
// Chinese in every one of these positions, so a reader the platform had deliberately routed to
|
|
101
|
+
// English got a Chinese screen — the one outcome the platform's own rule exists to prevent.
|
|
102
|
+
//
|
|
103
|
+
// The active locale arrives on the page: the locale plugin sets `document.documentElement.lang`
|
|
104
|
+
// (`snapshot.active === "zh" ? "zh-CN" : snapshot.active`), so this file reads the same signal the
|
|
105
|
+
// rest of the shell does rather than inventing a preference of its own.
|
|
106
|
+
//
|
|
107
|
+
// The two sides carry the SAME key set — the harness's own invariant, asserted in
|
|
108
|
+
// `contract/workbench-copy.test.mjs`, so a missing key cannot leave a hole in either direction.
|
|
109
|
+
const STRINGS = {
|
|
110
|
+
zh: {
|
|
111
|
+
'tab.projects': '项目',
|
|
112
|
+
'tab.scene': '场景树',
|
|
113
|
+
'tab.preview': '预览对比',
|
|
114
|
+
'tab.jobs': '任务',
|
|
115
|
+
'tab.revisions': '版本',
|
|
116
|
+
'common.unknown': '未知',
|
|
117
|
+
'workbench.restartHint': '重启 profile(dsh web)即可。',
|
|
118
|
+
'preview.diffFailed': 'diff 失败',
|
|
119
|
+
'preview.keptPrevious': ';上一张已留作「上一次渲染」,可以直接并排比较',
|
|
120
|
+
'preview.firstSheet': '(这是第一张;再渲染一次就能并排比较前后)',
|
|
121
|
+
'preview.isArtifact': '。预览是产物:替换同一路径上的旧图,不产生新的 revision。',
|
|
122
|
+
'jobs.cancelRequested': '取消已请求,但进程仍在',
|
|
123
|
+
'projects.empty': '这个工作区还没有项目。',
|
|
124
|
+
'projects.create': '新建项目(写操作经 Host)',
|
|
125
|
+
'projects.titlePlaceholder': '标题,例如 watch-commercial',
|
|
126
|
+
'projects.goal': '目标(可选)',
|
|
127
|
+
'projects.creating': '创建中…',
|
|
128
|
+
'projects.createButton': '创建',
|
|
129
|
+
'scene.currentRevision': '当前 revision',
|
|
130
|
+
'scene.revisionCount': 'revision 数',
|
|
131
|
+
'scene.frameRange': '帧范围',
|
|
132
|
+
'scene.activeCamera': '活动相机',
|
|
133
|
+
'scene.counts': '对象 / 材质 / 灯 / 相机',
|
|
134
|
+
'common.noProject': '尚未选择项目。',
|
|
135
|
+
'common.empty': '空',
|
|
136
|
+
'common.default': '默认',
|
|
137
|
+
'scene.entities': '实体 entities',
|
|
138
|
+
'scene.materials': '材质 materials',
|
|
139
|
+
'scene.lights': '灯光 lights',
|
|
140
|
+
'scene.cameras': '相机 cameras',
|
|
141
|
+
'scene.shots': '镜头 shots',
|
|
142
|
+
'scene.animationTracks': '动画轨道 animationTracks',
|
|
143
|
+
'scene.assets': '资产 assets',
|
|
144
|
+
'scene.patch': 'ScenePatch(写操作经 Host,原子提交为一个 revision)',
|
|
145
|
+
'scene.submitting': '提交中…',
|
|
146
|
+
'scene.submit': '提交',
|
|
147
|
+
'scene.resetTemplate': '重置模板',
|
|
148
|
+
'preview.thisRender': '本次渲染',
|
|
149
|
+
'preview.lastRender': '上一次渲染',
|
|
150
|
+
'common.noNote': '(无说明)',
|
|
151
|
+
'revisions.currentSuffix': ' (当前)',
|
|
152
|
+
'revisions.missing': '没有这个 revision',
|
|
153
|
+
'preview.noneForRevision': '这个 revision 还没有预览图(渲染一次预览即可)。',
|
|
154
|
+
'preview.noPrevious': '还没有上一次渲染:再点一次「渲染预览」,或者先在某个更早的 revision 上渲一次。',
|
|
155
|
+
'preview.notRenderedHere': '这个 revision 还没有由面板渲过预览(上方的「渲染预览」会生成第一张)。',
|
|
156
|
+
'preview.rendering': '渲染中…',
|
|
157
|
+
'preview.render': '渲染预览',
|
|
158
|
+
'preview.compare': '比较:',
|
|
159
|
+
'preview.lastVsThis': '上一次 vs 本次渲染',
|
|
160
|
+
'preview.twoRevisions': '两个 revision',
|
|
161
|
+
'preview.structuralDiff': '看结构差异',
|
|
162
|
+
'preview.identical': '结构完全相同',
|
|
163
|
+
'jobs.empty': '还没有渲染任务。',
|
|
164
|
+
'jobs.deliveryVerified': '交付已校验',
|
|
165
|
+
'jobs.cancel': '取消',
|
|
166
|
+
'jobs.resume': '继续渲染',
|
|
167
|
+
'jobs.startDelivery': '启动一次交付渲染(写操作经 Host)',
|
|
168
|
+
'jobs.frameStart': '帧起',
|
|
169
|
+
'jobs.frameEnd': '帧止',
|
|
170
|
+
'jobs.start': '启动',
|
|
171
|
+
'qa.passed': '技术校验通过',
|
|
172
|
+
'qa.none': '无技术校验记录',
|
|
173
|
+
'qa.title': '技术校验(validation.json)',
|
|
174
|
+
'qa.engine': '引擎',
|
|
175
|
+
'qa.objects': '对象',
|
|
176
|
+
'qa.materials': '材质',
|
|
177
|
+
'qa.cameras': '相机',
|
|
178
|
+
'qa.noErrors': '没有技术错误。',
|
|
179
|
+
'qa.notices': '编译器 notices',
|
|
180
|
+
'visual.title': '视觉评审(测量 + 模型 finding,两个来源不合并)',
|
|
181
|
+
'visual.score': '分数',
|
|
182
|
+
'common.yes': '通过',
|
|
183
|
+
'common.yesShort': '是',
|
|
184
|
+
'common.noShort': '否',
|
|
185
|
+
'visual.rounds': '轮次',
|
|
186
|
+
'visual.subjects': '主体',
|
|
187
|
+
'visual.views': '视角数',
|
|
188
|
+
'visual.reviewer': '审查器',
|
|
189
|
+
'visual.called': '已调用',
|
|
190
|
+
'visual.notCalled': '未调用',
|
|
191
|
+
'visual.noFindings': '测量没有发现问题。',
|
|
192
|
+
'visual.reviewerSilent': '审查器没有报告 finding。',
|
|
193
|
+
'visual.noSecondOpinion': '没有第二意见。',
|
|
194
|
+
'visual.none': '这个 revision 还没有视觉评审。可以用 blender_visual_review 跑一次。',
|
|
195
|
+
'revisions.current': '当前',
|
|
196
|
+
'revisions.compare': '对比',
|
|
197
|
+
'revisions.restore': '恢复',
|
|
198
|
+
'host.reading': '读取 Host 状态…',
|
|
199
|
+
'diagnostics.exportHint': '导出一份可以附在问题里的诊断信息(版本、配置、项目与任务的摘要)',
|
|
200
|
+
'diagnostics.export': '导出诊断',
|
|
201
|
+
'common.refresh': '刷新',
|
|
202
|
+
'blender.detecting': '检测 Blender…',
|
|
203
|
+
'common.warning': '警告',
|
|
204
|
+
'common.collapse': '收起',
|
|
205
|
+
'common.details': '详情',
|
|
206
|
+
'common.noRecord': '无记录',
|
|
207
|
+
'jobs.running': '(运行中…)',
|
|
208
|
+
'jobs.none': '无渲染任务',
|
|
209
|
+
'jobs.started': '任务 {jobId} 已启动({frames} 帧)',
|
|
210
|
+
'projects.updated': '当前 {revision} · 更新于 {when}',
|
|
211
|
+
'qa.errorCount': '技术错误 {count}',
|
|
212
|
+
'visual.scoreIs': '视觉评分 {score}',
|
|
213
|
+
'visual.notRun': '未跑视觉评审',
|
|
214
|
+
'host.observedResponse': '观察到的响应:{detail}。',
|
|
215
|
+
'host.stale': '本进程里的 blenderUi 比磁盘上的包旧:/deepblend/{route} 没有被这一版的宿主回答',
|
|
216
|
+
'host.staleDetail': '({detail}hostApiVersion={version},本 UI 需要 {needed})。',
|
|
217
|
+
'host.notJson': 'HTTP {status},{bytes} 字节,非 JSON',
|
|
218
|
+
'projects.created': '已创建 {id}',
|
|
219
|
+
'scene.patchInvalidJson': 'patch 不是合法 JSON:{message}',
|
|
220
|
+
'scene.committed': '已提交 {revision}(digest {digest})',
|
|
221
|
+
'preview.rendered': '已渲染 {views} 个视角 → 合成 {revision} 的 contact sheet',
|
|
222
|
+
'jobs.cancelled': '已取消 {jobId},进程实测已消失',
|
|
223
|
+
'revisions.restored': '已恢复 {from} 为 {to}',
|
|
224
|
+
'projects.unfinishedJobs': '{count} 个任务在跑',
|
|
225
|
+
'projects.revisionCount': '{count} 个 revision',
|
|
226
|
+
'projects.currentProject': '当前项目:{title}',
|
|
227
|
+
'scene.material': '材质 {id}',
|
|
228
|
+
'scene.track': '{target} · {property} · {keys} 关键帧',
|
|
229
|
+
'preview.renderedAt': ' · 渲染于 {when}',
|
|
230
|
+
'preview.lastRenderOf': '上一次渲染 · {revision}',
|
|
231
|
+
'preview.thisRenderOf': '本次渲染 · {revision}',
|
|
232
|
+
'preview.changeCount': '{count} 处结构变化',
|
|
233
|
+
'jobs.approval': '需审批:{frames} 帧 > 阈值 {threshold}',
|
|
234
|
+
'jobs.approvalShort': '需审批:{frames} 帧 > {threshold}',
|
|
235
|
+
'qa.noticeCount': '{count} 条 notices',
|
|
236
|
+
'workbench.title': '{panel} 工作台',
|
|
237
|
+
'jobs.unfinished': '{count} 个任务在跑',
|
|
238
|
+
'blender.probedAt': '探测于 {when}',
|
|
239
|
+
'visual.operationCount': '{count} 个操作',
|
|
240
|
+
'jobs.frameProgress': '{completed}/{expected} 帧 · {percent}%',
|
|
241
|
+
'jobs.estimated': '预计 {seconds} s',
|
|
242
|
+
'visual.counts': '测量 {measured} · 模型 finding {findings}',
|
|
243
|
+
'visual.reviewerFailed': '审查器失败:{message}',
|
|
244
|
+
'jobs.unfinishedShort': '{count} 个渲染在跑',
|
|
245
|
+
'jobs.frameRange': '帧 {start}–{end}',
|
|
246
|
+
'revisions.title': '版本({count})',
|
|
247
|
+
},
|
|
248
|
+
en: {
|
|
249
|
+
'tab.projects': 'Projects',
|
|
250
|
+
'tab.scene': 'Scene',
|
|
251
|
+
'tab.preview': 'Preview',
|
|
252
|
+
'tab.jobs': 'Jobs',
|
|
253
|
+
'tab.revisions': 'Revisions',
|
|
254
|
+
'common.unknown': 'unknown',
|
|
255
|
+
'workbench.restartHint': 'Restart the profile (`dsh web`) and it will be there.',
|
|
256
|
+
'preview.diffFailed': 'diff failed',
|
|
257
|
+
'preview.keptPrevious': '; the previous one is kept as “last render”, so they can be compared side by side',
|
|
258
|
+
'preview.firstSheet': '(this is the first; render once more to compare before and after)',
|
|
259
|
+
'preview.isArtifact': '. A preview is an artifact: it replaces the old image at the same path and creates no revision.',
|
|
260
|
+
'jobs.cancelRequested': 'cancellation requested, but the process is still there',
|
|
261
|
+
'projects.empty': 'This workspace has no projects yet.',
|
|
262
|
+
'projects.create': 'New project (the write goes through the Host)',
|
|
263
|
+
'projects.titlePlaceholder': 'title, e.g. watch-commercial',
|
|
264
|
+
'projects.goal': 'Goal (optional)',
|
|
265
|
+
'projects.creating': 'Creating…',
|
|
266
|
+
'projects.createButton': 'Create',
|
|
267
|
+
'scene.currentRevision': 'current revision',
|
|
268
|
+
'scene.revisionCount': 'revisions',
|
|
269
|
+
'scene.frameRange': 'frame range',
|
|
270
|
+
'scene.activeCamera': 'active camera',
|
|
271
|
+
'scene.counts': 'objects / materials / lights / cameras',
|
|
272
|
+
'common.noProject': 'No project selected.',
|
|
273
|
+
'common.empty': 'empty',
|
|
274
|
+
'common.default': 'default',
|
|
275
|
+
'scene.entities': 'entities',
|
|
276
|
+
'scene.materials': 'materials',
|
|
277
|
+
'scene.lights': 'lights',
|
|
278
|
+
'scene.cameras': 'cameras',
|
|
279
|
+
'scene.shots': 'shots',
|
|
280
|
+
'scene.animationTracks': 'animation tracks',
|
|
281
|
+
'scene.assets': 'assets',
|
|
282
|
+
'scene.patch': 'ScenePatch (the write goes through the Host and commits atomically as one revision)',
|
|
283
|
+
'scene.submitting': 'Submitting…',
|
|
284
|
+
'scene.submit': 'Submit',
|
|
285
|
+
'scene.resetTemplate': 'Reset template',
|
|
286
|
+
'preview.thisRender': 'this render',
|
|
287
|
+
'preview.lastRender': 'last render',
|
|
288
|
+
'common.noNote': '(no note)',
|
|
289
|
+
'revisions.currentSuffix': ' (current)',
|
|
290
|
+
'revisions.missing': 'no such revision',
|
|
291
|
+
'preview.noneForRevision': 'This revision has no preview image yet (render a preview and it will).',
|
|
292
|
+
'preview.noPrevious': 'No previous render yet: press “Render preview” again, or render once on an earlier revision.',
|
|
293
|
+
'preview.notRenderedHere': 'This revision has no preview rendered from this panel yet (the “Render preview” button above makes the first one).',
|
|
294
|
+
'preview.rendering': 'Rendering…',
|
|
295
|
+
'preview.render': 'Render preview',
|
|
296
|
+
'preview.compare': 'Compare:',
|
|
297
|
+
'preview.lastVsThis': 'last vs this render',
|
|
298
|
+
'preview.twoRevisions': 'two revisions',
|
|
299
|
+
'preview.structuralDiff': 'structural diff',
|
|
300
|
+
'preview.identical': 'structurally identical',
|
|
301
|
+
'jobs.empty': 'No render jobs yet.',
|
|
302
|
+
'jobs.deliveryVerified': 'delivery verified',
|
|
303
|
+
'jobs.cancel': 'Cancel',
|
|
304
|
+
'jobs.resume': 'Resume render',
|
|
305
|
+
'jobs.startDelivery': 'Start a delivery render (the write goes through the Host)',
|
|
306
|
+
'jobs.frameStart': 'from frame',
|
|
307
|
+
'jobs.frameEnd': 'to frame',
|
|
308
|
+
'jobs.start': 'Start',
|
|
309
|
+
'qa.passed': 'technical checks passed',
|
|
310
|
+
'qa.none': 'no technical check recorded',
|
|
311
|
+
'qa.title': 'Technical checks (validation.json)',
|
|
312
|
+
'qa.engine': 'engine',
|
|
313
|
+
'qa.objects': 'objects',
|
|
314
|
+
'qa.materials': 'materials',
|
|
315
|
+
'qa.cameras': 'cameras',
|
|
316
|
+
'qa.noErrors': 'No technical errors.',
|
|
317
|
+
'qa.notices': 'compiler notices',
|
|
318
|
+
'visual.title': 'Visual review (measurements + model findings; the two sources are not merged)',
|
|
319
|
+
'visual.score': 'score',
|
|
320
|
+
'common.yes': 'yes',
|
|
321
|
+
'common.yesShort': 'yes',
|
|
322
|
+
'common.noShort': 'no',
|
|
323
|
+
'visual.rounds': 'rounds',
|
|
324
|
+
'visual.subjects': 'subjects',
|
|
325
|
+
'visual.views': 'views',
|
|
326
|
+
'visual.reviewer': 'reviewer',
|
|
327
|
+
'visual.called': 'called',
|
|
328
|
+
'visual.notCalled': 'not called',
|
|
329
|
+
'visual.noFindings': 'The measurements found nothing.',
|
|
330
|
+
'visual.reviewerSilent': 'The reviewer reported no findings.',
|
|
331
|
+
'visual.noSecondOpinion': 'No second opinion.',
|
|
332
|
+
'visual.none': 'This revision has no visual review yet. Run one with blender_visual_review.',
|
|
333
|
+
'revisions.current': 'current',
|
|
334
|
+
'revisions.compare': 'compare',
|
|
335
|
+
'revisions.restore': 'restore',
|
|
336
|
+
'host.reading': 'Reading Host state…',
|
|
337
|
+
'diagnostics.exportHint': 'Export diagnostics you can attach to a bug report (versions, config, a summary of projects and jobs)',
|
|
338
|
+
'diagnostics.export': 'Export diagnostics',
|
|
339
|
+
'common.refresh': 'Refresh',
|
|
340
|
+
'blender.detecting': 'Detecting Blender…',
|
|
341
|
+
'common.warning': 'warning',
|
|
342
|
+
'common.collapse': 'collapse',
|
|
343
|
+
'common.details': 'details',
|
|
344
|
+
'common.noRecord': 'no record',
|
|
345
|
+
'jobs.running': '(running…)',
|
|
346
|
+
'jobs.none': 'no render job',
|
|
347
|
+
'jobs.started': 'Job {jobId} started ({frames} frames)',
|
|
348
|
+
'projects.updated': 'current {revision} · updated {when}',
|
|
349
|
+
'qa.errorCount': '{count} technical errors',
|
|
350
|
+
'visual.scoreIs': 'visual score {score}',
|
|
351
|
+
'visual.notRun': 'no visual review yet',
|
|
352
|
+
'host.observedResponse': 'the response observed: {detail}.',
|
|
353
|
+
'host.stale': 'the blenderUi in this process is older than the package on disk: /deepblend/{route} was not answered by this host',
|
|
354
|
+
'host.staleDetail': '({detail}hostApiVersion={version}, and this UI needs {needed}).',
|
|
355
|
+
'host.notJson': 'HTTP {status}, {bytes} bytes, not JSON',
|
|
356
|
+
'projects.created': 'created {id}',
|
|
357
|
+
'scene.patchInvalidJson': 'the patch is not valid JSON: {message}',
|
|
358
|
+
'scene.committed': 'committed {revision} (digest {digest})',
|
|
359
|
+
'preview.rendered': 'rendered {views} views → a contact sheet for {revision}',
|
|
360
|
+
'jobs.cancelled': 'cancelled {jobId}, and the process is measurably gone',
|
|
361
|
+
'revisions.restored': 'restored {from} as {to}',
|
|
362
|
+
'projects.unfinishedJobs': '{count} job(s) running',
|
|
363
|
+
'projects.revisionCount': '{count} revision(s)',
|
|
364
|
+
'projects.currentProject': 'current project: {title}',
|
|
365
|
+
'scene.material': 'material {id}',
|
|
366
|
+
'scene.track': '{target} · {property} · {keys} keyframes',
|
|
367
|
+
'preview.renderedAt': ' · rendered {when}',
|
|
368
|
+
'preview.lastRenderOf': 'last render · {revision}',
|
|
369
|
+
'preview.thisRenderOf': 'this render · {revision}',
|
|
370
|
+
'preview.changeCount': '{count} structural change(s)',
|
|
371
|
+
'jobs.approval': 'approval needed: {frames} frames > threshold {threshold}',
|
|
372
|
+
'jobs.approvalShort': 'approval needed: {frames} frames > {threshold}',
|
|
373
|
+
'qa.noticeCount': '{count} notice(s)',
|
|
374
|
+
'workbench.title': '{panel} workbench',
|
|
375
|
+
'jobs.unfinished': '{count} job(s) running',
|
|
376
|
+
'blender.probedAt': 'probed {when}',
|
|
377
|
+
'visual.operationCount': '{count} operation(s)',
|
|
378
|
+
'jobs.frameProgress': '{completed}/{expected} frames · {percent}%',
|
|
379
|
+
'jobs.estimated': 'about {seconds} s left',
|
|
380
|
+
'visual.counts': 'measured {measured} · model findings {findings}',
|
|
381
|
+
'visual.reviewerFailed': 'the reviewer failed: {message}',
|
|
382
|
+
'jobs.unfinishedShort': '{count} render(s) running',
|
|
383
|
+
'jobs.frameRange': 'frames {start}–{end}',
|
|
384
|
+
'revisions.title': 'Revisions ({count})',
|
|
385
|
+
},
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** The active locale id, lowercased and stripped of any region, or `en` when it is not ours. */
|
|
389
|
+
const LOCALE = (() => {
|
|
390
|
+
const named = typeof document === 'undefined' ? '' : String(document.documentElement?.lang ?? '')
|
|
391
|
+
const key = named.toLowerCase().split('-')[0]
|
|
392
|
+
return Object.prototype.hasOwnProperty.call(STRINGS, key) ? key : 'en'
|
|
393
|
+
})()
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* One string, in the active locale, with `{name}` placeholders filled from `params`.
|
|
397
|
+
*
|
|
398
|
+
* The fallback chain mirrors the platform's: the active locale, then English, then the key
|
|
399
|
+
* itself. The last one is for a key that exists in neither table — a hole a reader can SEE
|
|
400
|
+
* rather than a silent blank.
|
|
401
|
+
*/
|
|
402
|
+
const t = (key, params) => {
|
|
403
|
+
const table = STRINGS[LOCALE] ?? STRINGS.en
|
|
404
|
+
const raw = table[key] ?? STRINGS.en[key] ?? key
|
|
405
|
+
if (params === undefined) return raw
|
|
406
|
+
return raw.replace(/\{(\w+)\}/g, (whole, name) => (name in params ? String(params[name]) : whole))
|
|
407
|
+
}
|
|
408
|
+
// #endregion strings
|
|
409
|
+
|
|
93
410
|
// §B Vocabulary
|
|
94
411
|
// =========================================================================
|
|
95
412
|
|
|
@@ -105,12 +422,12 @@ window.__ModuleLoader__.load({
|
|
|
105
422
|
const POLL_IDLE_MS = 8000
|
|
106
423
|
|
|
107
424
|
const VIEWS = [
|
|
108
|
-
{ id: 'projects', label: '
|
|
109
|
-
{ id: 'scene', label: '
|
|
110
|
-
{ id: 'preview', label: '
|
|
111
|
-
{ id: 'jobs', label: '
|
|
425
|
+
{ id: 'projects', label: t('tab.projects') },
|
|
426
|
+
{ id: 'scene', label: t('tab.scene') },
|
|
427
|
+
{ id: 'preview', label: t('tab.preview') },
|
|
428
|
+
{ id: 'jobs', label: t('tab.jobs') },
|
|
112
429
|
{ id: 'qa', label: 'QA' },
|
|
113
|
-
{ id: 'revisions', label: '
|
|
430
|
+
{ id: 'revisions', label: t('tab.revisions') },
|
|
114
431
|
]
|
|
115
432
|
|
|
116
433
|
/** Every DeepBlend wire tool name this package draws a card for. */
|
|
@@ -400,14 +717,14 @@ window.__ModuleLoader__.load({
|
|
|
400
717
|
function staleHostError(expectedRoute, observed) {
|
|
401
718
|
const version = observed && observed.payload && observed.payload.hostApiVersion
|
|
402
719
|
? observed.payload.hostApiVersion
|
|
403
|
-
: '
|
|
404
|
-
const detail = observed && observed.detail ?
|
|
720
|
+
: t('common.unknown')
|
|
721
|
+
const detail = observed && observed.detail ? t('host.observedResponse', { detail: observed.detail }) : ''
|
|
405
722
|
return {
|
|
406
723
|
code: 'UI_HOST_API_STALE',
|
|
407
724
|
message:
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
'
|
|
725
|
+
t('host.stale', { route: expectedRoute }) +
|
|
726
|
+
t('host.staleDetail', { detail, version, needed: EXPECTED_HOST_API }) +
|
|
727
|
+
t('workbench.restartHint'),
|
|
411
728
|
}
|
|
412
729
|
}
|
|
413
730
|
|
|
@@ -443,7 +760,7 @@ window.__ModuleLoader__.load({
|
|
|
443
760
|
payload,
|
|
444
761
|
status: response.status,
|
|
445
762
|
detail: payload === null
|
|
446
|
-
?
|
|
763
|
+
? t('host.notJson', { status: response.status, bytes: text.length })
|
|
447
764
|
: `route=${JSON.stringify(payload.route)}`,
|
|
448
765
|
}),
|
|
449
766
|
}
|
|
@@ -646,7 +963,7 @@ window.__ModuleLoader__.load({
|
|
|
646
963
|
const response = await fetchImpl(`${projectRoute(target(), '/diff')}?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`, { headers: { accept: 'application/json' } })
|
|
647
964
|
const parsed = await response.json()
|
|
648
965
|
if (parsed && parsed.ok) set({ diff: parsed.diff })
|
|
649
|
-
else set({ diff: null, diffError: (parsed && parsed.error) || { code: `HTTP_${response.status}`, message: '
|
|
966
|
+
else set({ diff: null, diffError: (parsed && parsed.error) || { code: `HTTP_${response.status}`, message: t('preview.diffFailed') } })
|
|
650
967
|
} catch (error) {
|
|
651
968
|
set({ diff: null, diffError: { code: 'UI_FETCH_FAILED', message: String((error && error.message) || error) } })
|
|
652
969
|
}
|
|
@@ -661,7 +978,7 @@ window.__ModuleLoader__.load({
|
|
|
661
978
|
setIn('busy', 'create', false)
|
|
662
979
|
if (outcome.ok) {
|
|
663
980
|
set({ forms: { ...data.forms, title: '', goal: '' }, projectId: outcome.payload.project.projectId })
|
|
664
|
-
setIn('notices', 'projects', { ok: true, message:
|
|
981
|
+
setIn('notices', 'projects', { ok: true, message: t('projects.created', { id: outcome.payload.project.projectId }) })
|
|
665
982
|
reload()
|
|
666
983
|
} else {
|
|
667
984
|
setIn('notices', 'projects', { ok: false, message: `${outcome.error.code}: ${outcome.error.message}` })
|
|
@@ -674,14 +991,14 @@ window.__ModuleLoader__.load({
|
|
|
674
991
|
try {
|
|
675
992
|
patch = JSON.parse(text)
|
|
676
993
|
} catch (error) {
|
|
677
|
-
setIn('notices', 'scene', { ok: false, message:
|
|
994
|
+
setIn('notices', 'scene', { ok: false, message: t('scene.patchInvalidJson', { message: error.message }) })
|
|
678
995
|
return
|
|
679
996
|
}
|
|
680
997
|
setIn('busy', 'patch', true)
|
|
681
998
|
const outcome = await postJson(fetchImpl, projectRoute(target(), '/patch'), { patch })
|
|
682
999
|
setIn('busy', 'patch', false)
|
|
683
1000
|
if (outcome.ok) {
|
|
684
|
-
setIn('notices', 'scene', { ok: true, message:
|
|
1001
|
+
setIn('notices', 'scene', { ok: true, message: t('scene.committed', { revision: outcome.payload.revision.revision, digest: shortDigest(outcome.payload.revision.digest) }) })
|
|
685
1002
|
setIn('forms', 'patch', null)
|
|
686
1003
|
reload()
|
|
687
1004
|
} else {
|
|
@@ -697,11 +1014,11 @@ window.__ModuleLoader__.load({
|
|
|
697
1014
|
previewResult: outcome.ok
|
|
698
1015
|
? {
|
|
699
1016
|
ok: true,
|
|
700
|
-
message:
|
|
1017
|
+
message: t('preview.rendered', { views: outcome.payload.preview.views.length, revision: outcome.payload.preview.revision })
|
|
701
1018
|
+ (outcome.payload.preview.sheets && outcome.payload.preview.sheets.previous
|
|
702
|
-
? '
|
|
703
|
-
: '
|
|
704
|
-
+ '
|
|
1019
|
+
? t('preview.keptPrevious')
|
|
1020
|
+
: t('preview.firstSheet'))
|
|
1021
|
+
+ t('preview.isArtifact'),
|
|
705
1022
|
}
|
|
706
1023
|
: { ok: false, message: `${outcome.error.code}: ${outcome.error.message}` },
|
|
707
1024
|
})
|
|
@@ -718,7 +1035,7 @@ window.__ModuleLoader__.load({
|
|
|
718
1035
|
})
|
|
719
1036
|
setIn('busy', 'render', false)
|
|
720
1037
|
setIn('notices', 'jobs', outcome.ok
|
|
721
|
-
? { kind: 'render', ok: true, message:
|
|
1038
|
+
? { kind: 'render', ok: true, message: t('jobs.started', { jobId: outcome.payload.job.jobId, frames: outcome.payload.job.frames || '?' }) }
|
|
722
1039
|
: { kind: 'render', ok: false, message: `${outcome.error.code}: ${outcome.error.message}` })
|
|
723
1040
|
reload()
|
|
724
1041
|
},
|
|
@@ -732,8 +1049,8 @@ window.__ModuleLoader__.load({
|
|
|
732
1049
|
kind: 'cancel',
|
|
733
1050
|
ok: outcome.payload.cancelled.processGone !== false,
|
|
734
1051
|
message: outcome.payload.cancelled.processGone === false
|
|
735
|
-
? '
|
|
736
|
-
:
|
|
1052
|
+
? t('jobs.cancelRequested')
|
|
1053
|
+
: t('jobs.cancelled', { jobId }),
|
|
737
1054
|
}
|
|
738
1055
|
: { kind: 'cancel', ok: false, message: `${outcome.error.code}: ${outcome.error.message}` })
|
|
739
1056
|
reload()
|
|
@@ -744,7 +1061,7 @@ window.__ModuleLoader__.load({
|
|
|
744
1061
|
const outcome = await postJson(fetchImpl, projectRoute(target(), '/restore'), { revision })
|
|
745
1062
|
setIn('busy', 'restore', false)
|
|
746
1063
|
setIn('notices', 'revisions', outcome.ok
|
|
747
|
-
? { ok: true, message:
|
|
1064
|
+
? { ok: true, message: t('revisions.restored', { from: revision, to: outcome.payload.revision.revision }) }
|
|
748
1065
|
: { ok: false, message: `${outcome.error.code}: ${outcome.error.message}` })
|
|
749
1066
|
reload()
|
|
750
1067
|
},
|
|
@@ -787,9 +1104,9 @@ window.__ModuleLoader__.load({
|
|
|
787
1104
|
return el('div', { 'data-view': 'projects' },
|
|
788
1105
|
ErrorBox({ error: state.error }),
|
|
789
1106
|
el('div', { className: 'db-card' },
|
|
790
|
-
el('h4', null, '
|
|
1107
|
+
el('h4', null, t('tab.projects')),
|
|
791
1108
|
state.projects.length === 0
|
|
792
|
-
? el('div', { className: 'db-muted' }, '
|
|
1109
|
+
? el('div', { className: 'db-muted' }, t('projects.empty'))
|
|
793
1110
|
: el('ul', { className: 'db-list' }, state.projects.map(project => el('li', { key: project.projectId, 'data-project': project.projectId },
|
|
794
1111
|
el('div', { className: 'db-inline' },
|
|
795
1112
|
Button({
|
|
@@ -799,29 +1116,29 @@ window.__ModuleLoader__.load({
|
|
|
799
1116
|
children: project.title || project.projectId,
|
|
800
1117
|
}),
|
|
801
1118
|
el('span', { className: 'db-muted db-mono' }, project.projectId),
|
|
802
|
-
project.unfinishedJobs > 0 ? Badge({ tone: 'live', name: 'unfinished', children:
|
|
803
|
-
el('span', { className: 'db-muted' },
|
|
1119
|
+
project.unfinishedJobs > 0 ? Badge({ tone: 'live', name: 'unfinished', children: t('projects.unfinishedJobs', { count: project.unfinishedJobs }) }) : null,
|
|
1120
|
+
el('span', { className: 'db-muted' }, t('projects.revisionCount', { count: project.revisionCount })),
|
|
804
1121
|
),
|
|
805
|
-
el('div', { className: 'db-muted db-mono' },
|
|
1122
|
+
el('div', { className: 'db-muted db-mono' }, t('projects.updated', { revision: project.currentRevision || '—', when: formatTime(project.updatedAt) })),
|
|
806
1123
|
project.goal ? el('div', { className: 'db-muted' }, project.goal) : null,
|
|
807
1124
|
))),
|
|
808
1125
|
el('div', { className: 'db-muted db-mono', style: { marginTop: '6px' } }, `projectsRoot: ${state.projectsRoot || '—'}`),
|
|
809
1126
|
),
|
|
810
1127
|
|
|
811
1128
|
el('div', { className: 'db-card' },
|
|
812
|
-
el('h4', null, '
|
|
1129
|
+
el('h4', null, t('projects.create')),
|
|
813
1130
|
el('div', { className: 'db-inline' },
|
|
814
1131
|
el('input', {
|
|
815
1132
|
className: 'db-input',
|
|
816
1133
|
'data-field': 'project-title',
|
|
817
|
-
placeholder: '
|
|
1134
|
+
placeholder: t('projects.titlePlaceholder'),
|
|
818
1135
|
value: state.forms.title,
|
|
819
1136
|
onChange: event => actions.setForm('title', event.target.value),
|
|
820
1137
|
}),
|
|
821
1138
|
el('input', {
|
|
822
1139
|
className: 'db-input',
|
|
823
1140
|
'data-field': 'project-goal',
|
|
824
|
-
placeholder: '
|
|
1141
|
+
placeholder: t('projects.goal'),
|
|
825
1142
|
value: state.forms.goal,
|
|
826
1143
|
onChange: event => actions.setForm('goal', event.target.value),
|
|
827
1144
|
}),
|
|
@@ -830,28 +1147,28 @@ window.__ModuleLoader__.load({
|
|
|
830
1147
|
action: 'create-project',
|
|
831
1148
|
disabled: state.busy.create || state.forms.title.trim().length === 0,
|
|
832
1149
|
onClick: actions.createProject,
|
|
833
|
-
children: state.busy.create ? '
|
|
1150
|
+
children: state.busy.create ? t('projects.creating') : t('projects.createButton'),
|
|
834
1151
|
}),
|
|
835
1152
|
),
|
|
836
1153
|
state.notices.projects ? Notice(state.notices.projects, { marginTop: '8px' }) : null,
|
|
837
1154
|
),
|
|
838
1155
|
|
|
839
1156
|
state.selected ? el('div', { className: 'db-card' },
|
|
840
|
-
el('h4', null,
|
|
1157
|
+
el('h4', null, t('projects.currentProject', { title: state.selected.project.title })),
|
|
841
1158
|
KeyValues({ entries: [
|
|
842
1159
|
{ label: 'projectId', value: state.selected.project.projectId },
|
|
843
|
-
{ label: '
|
|
844
|
-
{ label: '
|
|
1160
|
+
{ label: t('scene.currentRevision'), value: state.selected.currentRevision },
|
|
1161
|
+
{ label: t('scene.revisionCount'), value: state.selected.project.revisionCount },
|
|
845
1162
|
{ label: 'digest', value: shortDigest(state.selected.scene.digest) },
|
|
846
|
-
{ label: '
|
|
847
|
-
{ label: '
|
|
848
|
-
{ label: '
|
|
1163
|
+
{ label: t('scene.frameRange'), value: `${state.selected.scene.project.frameStart}–${state.selected.scene.project.frameEnd} @ ${state.selected.scene.project.fps}fps` },
|
|
1164
|
+
{ label: t('scene.activeCamera'), value: state.selected.scene.project.activeCamera },
|
|
1165
|
+
{ label: t('scene.counts'), value: [
|
|
849
1166
|
state.selected.scene.counts.entities, state.selected.scene.counts.materials,
|
|
850
1167
|
state.selected.scene.counts.lights, state.selected.scene.counts.cameras,
|
|
851
1168
|
].join(' / ') },
|
|
852
1169
|
] }),
|
|
853
1170
|
el('div', { className: 'db-muted', style: { marginTop: '6px' } }, state.selected.qa.summary),
|
|
854
|
-
) : el('div', { className: 'db-card db-muted' }, '
|
|
1171
|
+
) : el('div', { className: 'db-card db-muted' }, t('common.noProject')),
|
|
855
1172
|
)
|
|
856
1173
|
}
|
|
857
1174
|
|
|
@@ -871,7 +1188,7 @@ window.__ModuleLoader__.load({
|
|
|
871
1188
|
const state = ctx.state
|
|
872
1189
|
const actions = ctx.actions
|
|
873
1190
|
const scene = state.selected ? state.selected.scene : null
|
|
874
|
-
if (scene === null) return el('div', { 'data-view': 'scene', className: 'db-muted' }, '
|
|
1191
|
+
if (scene === null) return el('div', { 'data-view': 'scene', className: 'db-muted' }, t('common.noProject'))
|
|
875
1192
|
|
|
876
1193
|
const template = JSON.stringify({
|
|
877
1194
|
baseRevision: state.selected.currentRevision,
|
|
@@ -886,7 +1203,7 @@ window.__ModuleLoader__.load({
|
|
|
886
1203
|
const section = (title, items, render) => el('div', { className: 'db-card', key: title },
|
|
887
1204
|
el('h4', null, `${title}(${items.length})`),
|
|
888
1205
|
items.length === 0
|
|
889
|
-
? el('div', { className: 'db-muted' }, '
|
|
1206
|
+
? el('div', { className: 'db-muted' }, t('common.empty'))
|
|
890
1207
|
: el('ul', { className: 'db-list' }, items.map(item => el('li', { key: item.id }, render(item)))))
|
|
891
1208
|
|
|
892
1209
|
return el('div', { 'data-view': 'scene' },
|
|
@@ -896,45 +1213,45 @@ window.__ModuleLoader__.load({
|
|
|
896
1213
|
el('strong', null, String(scene.project.title || scene.project.id || '')),
|
|
897
1214
|
Badge({ children: scene.revision }),
|
|
898
1215
|
el('span', { className: 'db-muted db-mono' }, `digest ${shortDigest(scene.digest)}`),
|
|
899
|
-
Badge({ children: `world ${scene.world ? `${(scene.world.color || []).join(',')} × ${scene.world.strength}` : '
|
|
1216
|
+
Badge({ children: `world ${scene.world ? `${(scene.world.color || []).join(',')} × ${scene.world.strength}` : t('common.default')}` }),
|
|
900
1217
|
),
|
|
901
1218
|
),
|
|
902
1219
|
el('div', { className: 'db-grid' },
|
|
903
|
-
section('
|
|
1220
|
+
section(t('scene.entities'), scene.nodes.entities, entity => el('div', null,
|
|
904
1221
|
el('span', { className: 'db-mono', 'data-node': `entity:${entity.id}` }, entity.id), ' ',
|
|
905
1222
|
el('span', { className: 'db-kind' }, entity.shape || entity.kind),
|
|
906
|
-
entity.materialId ? el('span', { className: 'db-muted' },
|
|
1223
|
+
entity.materialId ? el('span', { className: 'db-muted' }, t('scene.material', { id: entity.materialId })) : null,
|
|
907
1224
|
entity.locked ? Badge({ tone: 'warn', children: 'locked' }) : null,
|
|
908
1225
|
entity.tags.length > 0 ? el('span', { className: 'db-muted db-mono' }, ` tags=[${entity.tags.join(' ')}]`) : null,
|
|
909
1226
|
entity.transform ? el('div', { className: 'db-muted db-mono' }, `loc ${(entity.transform.location || []).map(value => Number(value).toFixed(3)).join(', ')}`) : null)),
|
|
910
|
-
section('
|
|
1227
|
+
section(t('scene.materials'), scene.nodes.materials, material => el('div', null,
|
|
911
1228
|
el('span', { className: 'db-mono', 'data-node': `material:${material.id}` }, material.id), ' ',
|
|
912
1229
|
el('span', { className: 'db-kind' }, material.shader),
|
|
913
1230
|
material.parameters ? el('span', { className: 'db-muted db-mono' }, Object.entries(material.parameters).slice(0, 4).map(([key, value]) => `${key}=${Array.isArray(value) ? `[${value.join(',')}]` : value}`).join(' ')) : null)),
|
|
914
|
-
section('
|
|
1231
|
+
section(t('scene.lights'), scene.nodes.lights, light => el('div', null,
|
|
915
1232
|
el('span', { className: 'db-mono', 'data-node': `light:${light.id}` }, light.id), ' ',
|
|
916
1233
|
el('span', { className: 'db-kind' }, light.type),
|
|
917
1234
|
el('span', { className: 'db-muted' }, `energy ${light.energy}`))),
|
|
918
|
-
section('
|
|
1235
|
+
section(t('scene.cameras'), scene.nodes.cameras, camera => el('div', null,
|
|
919
1236
|
el('span', { className: 'db-mono', 'data-node': `camera:${camera.id}` }, camera.id), ' ',
|
|
920
1237
|
camera.isActive ? Badge({ tone: 'ok', children: 'active' }) : null, ' ',
|
|
921
1238
|
el('span', { className: 'db-kind' }, camera.role || 'no role'),
|
|
922
1239
|
el('span', { className: 'db-muted' }, `lens ${camera.lens}`))),
|
|
923
|
-
section('
|
|
1240
|
+
section(t('scene.shots'), scene.nodes.shots, shot => el('div', null,
|
|
924
1241
|
el('span', { className: 'db-mono', 'data-node': `shot:${shot.id}` }, shot.id), ' ',
|
|
925
1242
|
el('span', { className: 'db-muted' }, `${shot.cameraId} ${(shot.frameRange || []).join('–')}`))),
|
|
926
|
-
section('
|
|
1243
|
+
section(t('scene.animationTracks'), scene.nodes.animationTracks, track => el('div', null,
|
|
927
1244
|
el('span', { className: 'db-mono', 'data-node': `track:${track.id}` }, track.id), ' ',
|
|
928
1245
|
el('span', { className: 'db-kind' }, track.targetKind),
|
|
929
|
-
el('span', { className: 'db-muted' },
|
|
930
|
-
section('
|
|
1246
|
+
el('span', { className: 'db-muted' }, t('scene.track', { target: track.targetId, property: track.property, keys: track.keyframes })))),
|
|
1247
|
+
section(t('scene.assets'), scene.nodes.assets, asset => el('div', null,
|
|
931
1248
|
el('span', { className: 'db-mono', 'data-node': `asset:${asset.id}` }, asset.id), ' ',
|
|
932
1249
|
el('span', { className: 'db-kind' }, asset.type),
|
|
933
1250
|
el('span', { className: 'db-muted db-mono' }, String(asset.path || '')))),
|
|
934
1251
|
),
|
|
935
1252
|
|
|
936
1253
|
el('div', { className: 'db-card' },
|
|
937
|
-
el('h4', null, '
|
|
1254
|
+
el('h4', null, t('scene.patch')),
|
|
938
1255
|
el('textarea', {
|
|
939
1256
|
className: 'db-area',
|
|
940
1257
|
'data-field': 'scene-patch',
|
|
@@ -948,9 +1265,9 @@ window.__ModuleLoader__.load({
|
|
|
948
1265
|
action: 'apply-patch',
|
|
949
1266
|
disabled: state.busy.patch || state.activeProjectId === null,
|
|
950
1267
|
onClick: actions.applyPatch,
|
|
951
|
-
children: state.busy.patch ? '
|
|
1268
|
+
children: state.busy.patch ? t('scene.submitting') : t('scene.submit'),
|
|
952
1269
|
}),
|
|
953
|
-
Button({ action: 'reset-patch', onClick: () => actions.setForm('patch', null), children: '
|
|
1270
|
+
Button({ action: 'reset-patch', onClick: () => actions.setForm('patch', null), children: t('scene.resetTemplate') }),
|
|
954
1271
|
Notice(state.notices.scene, { border: 0, padding: '0 6px', marginBottom: 0 }),
|
|
955
1272
|
),
|
|
956
1273
|
),
|
|
@@ -971,7 +1288,7 @@ window.__ModuleLoader__.load({
|
|
|
971
1288
|
if (entry === null) return null
|
|
972
1289
|
const sheets = entry.contactSheets || []
|
|
973
1290
|
const current = sheets.find(sheet => sheet.slot === 'preview-current')
|
|
974
|
-
if (current && current.path) return { ...current, label: '
|
|
1291
|
+
if (current && current.path) return { ...current, label: t('preview.thisRender') }
|
|
975
1292
|
const sheet = sheets[sheets.length - 1]
|
|
976
1293
|
if (sheet && sheet.path) return { ...sheet, label: 'contact sheet' }
|
|
977
1294
|
const list = entry.previews || []
|
|
@@ -1010,8 +1327,8 @@ window.__ModuleLoader__.load({
|
|
|
1010
1327
|
}
|
|
1011
1328
|
}
|
|
1012
1329
|
return {
|
|
1013
|
-
current: current === null ? null : { ...current, label: '
|
|
1014
|
-
previous: previous === null ? null : { ...previous, label: '
|
|
1330
|
+
current: current === null ? null : { ...current, label: t('preview.thisRender'), sourceRevision: entry.revision },
|
|
1331
|
+
previous: previous === null ? null : { ...previous, label: t('preview.lastRender'), sourceRevision: previousRevision },
|
|
1015
1332
|
}
|
|
1016
1333
|
}
|
|
1017
1334
|
|
|
@@ -1020,7 +1337,7 @@ window.__ModuleLoader__.load({
|
|
|
1020
1337
|
const state = ctx.state
|
|
1021
1338
|
const actions = ctx.actions
|
|
1022
1339
|
const previews = state.previews
|
|
1023
|
-
if (previews === null) return el('div', { 'data-view': 'preview', className: 'db-muted' }, '
|
|
1340
|
+
if (previews === null) return el('div', { 'data-view': 'preview', className: 'db-muted' }, t('common.noProject'))
|
|
1024
1341
|
const revisions = previews.revisions
|
|
1025
1342
|
const pick = wanted => (revisions.some(entry => entry.revision === wanted) ? wanted : (((revisions[revisions.length - 1] || {}).revision) || null))
|
|
1026
1343
|
const left = pick(state.compareLeft || state.currentRevision)
|
|
@@ -1046,23 +1363,23 @@ window.__ModuleLoader__.load({
|
|
|
1046
1363
|
src: artifactUrl(state.artifactBase, artifact),
|
|
1047
1364
|
}),
|
|
1048
1365
|
el('div', { className: 'db-muted db-mono', key: 'meta' },
|
|
1049
|
-
`${artifact.sourceRevision ? `${artifact.sourceRevision} ` : ''}${artifact.slot ? artifact.slot : (artifact.kind || 'artifact')} · ${artifact.sha256 ? String(artifact.sha256).slice(0, 10) : '—'}${when === null ? '' :
|
|
1366
|
+
`${artifact.sourceRevision ? `${artifact.sourceRevision} ` : ''}${artifact.slot ? artifact.slot : (artifact.kind || 'artifact')} · ${artifact.sha256 ? String(artifact.sha256).slice(0, 10) : '—'}${when === null ? '' : t('preview.renderedAt', { when })}`),
|
|
1050
1367
|
].filter(Boolean))
|
|
1051
1368
|
}
|
|
1052
1369
|
|
|
1053
|
-
const revisionMeta = entry => el('div', { className: 'db-muted', key: 'meta' }, `${entry.summary || '
|
|
1370
|
+
const revisionMeta = entry => el('div', { className: 'db-muted', key: 'meta' }, `${entry.summary || t('common.noNote')} · ${formatTime(entry.createdAt)}`)
|
|
1054
1371
|
|
|
1055
1372
|
/** The revision axis: two revisions side by side, each one's newest image. */
|
|
1056
1373
|
const revisionPane = (entry, side) => el('div', { className: 'db-shot', 'data-compare': side },
|
|
1057
|
-
el('h5', null, entry === null ? '—' : `${entry.revision}${entry.isCurrent ? '
|
|
1374
|
+
el('h5', null, entry === null ? '—' : `${entry.revision}${entry.isCurrent ? t('revisions.currentSuffix') : ''}`),
|
|
1058
1375
|
entry === null
|
|
1059
|
-
? el('div', { className: 'db-muted' }, '
|
|
1376
|
+
? el('div', { className: 'db-muted' }, t('revisions.missing'))
|
|
1060
1377
|
: [
|
|
1061
1378
|
revisionMeta(entry),
|
|
1062
1379
|
(() => {
|
|
1063
1380
|
const sheet = sheetOf(entry)
|
|
1064
1381
|
return sheet === null
|
|
1065
|
-
? el('div', { className: 'db-muted', key: 'none' }, '
|
|
1382
|
+
? el('div', { className: 'db-muted', key: 'none' }, t('preview.noneForRevision'))
|
|
1066
1383
|
: el('img', {
|
|
1067
1384
|
key: 'img',
|
|
1068
1385
|
'data-artifact': sheet.path,
|
|
@@ -1078,7 +1395,7 @@ window.__ModuleLoader__.load({
|
|
|
1078
1395
|
const sheet = sheetOf(entry)
|
|
1079
1396
|
const when = artifactTime(sheet)
|
|
1080
1397
|
return sheet === null ? null : el('div', { className: 'db-muted db-mono', key: 'sheet' },
|
|
1081
|
-
`${sheet.sha256 ? String(sheet.sha256).slice(0, 10) : '—'}${when === null ? '' :
|
|
1398
|
+
`${sheet.sha256 ? String(sheet.sha256).slice(0, 10) : '—'}${when === null ? '' : t('preview.renderedAt', { when })}`)
|
|
1082
1399
|
})(),
|
|
1083
1400
|
el('div', { className: 'db-muted db-mono', key: 'counts' }, `previews ${(entry.previews || []).length} · sheets ${(entry.contactSheets || []).length} · reviews ${(entry.reviews || []).length}`),
|
|
1084
1401
|
(entry.reviews || []).length > 0
|
|
@@ -1102,10 +1419,10 @@ window.__ModuleLoader__.load({
|
|
|
1102
1419
|
const pair = renderPairOf(entryOf(right), revisions)
|
|
1103
1420
|
const rendersMode = state.compareMode !== 'revisions'
|
|
1104
1421
|
const renderPairPanes = [
|
|
1105
|
-
imagePane('left', pair.previous === null ? '
|
|
1106
|
-
'
|
|
1107
|
-
imagePane('right',
|
|
1108
|
-
'
|
|
1422
|
+
imagePane('left', pair.previous === null ? t('preview.lastRender') : t('preview.lastRenderOf', { revision: pair.previous.sourceRevision }), pair.previous,
|
|
1423
|
+
t('preview.noPrevious')),
|
|
1424
|
+
imagePane('right', t('preview.thisRenderOf', { revision: right }), pair.current,
|
|
1425
|
+
t('preview.notRenderedHere')),
|
|
1109
1426
|
]
|
|
1110
1427
|
|
|
1111
1428
|
return el('div', { 'data-view': 'preview' },
|
|
@@ -1116,24 +1433,24 @@ window.__ModuleLoader__.load({
|
|
|
1116
1433
|
action: 'render-preview',
|
|
1117
1434
|
disabled: state.previewBusy || state.activeProjectId === null,
|
|
1118
1435
|
onClick: actions.renderPreview,
|
|
1119
|
-
children: state.previewBusy ? '
|
|
1436
|
+
children: state.previewBusy ? t('preview.rendering') : t('preview.render'),
|
|
1120
1437
|
}),
|
|
1121
1438
|
Notice(state.previewResult, { border: 0, padding: '0 6px', marginBottom: 0 }),
|
|
1122
1439
|
),
|
|
1123
1440
|
el('div', { className: 'db-tabs' },
|
|
1124
|
-
el('span', { className: 'db-muted' }, '
|
|
1441
|
+
el('span', { className: 'db-muted' }, t('preview.compare')),
|
|
1125
1442
|
el('button', {
|
|
1126
1443
|
type: 'button', className: 'db-btn', 'data-compare-mode': 'renders', 'data-active': String(rendersMode),
|
|
1127
1444
|
onClick: () => actions.setCompareMode('renders'),
|
|
1128
|
-
}, '
|
|
1445
|
+
}, t('preview.lastVsThis')),
|
|
1129
1446
|
el('button', {
|
|
1130
1447
|
type: 'button', className: 'db-btn', 'data-compare-mode': 'revisions', 'data-active': String(!rendersMode),
|
|
1131
1448
|
onClick: () => actions.setCompareMode('revisions'),
|
|
1132
|
-
}, '
|
|
1449
|
+
}, t('preview.twoRevisions')),
|
|
1133
1450
|
el('span', { style: { flex: 1 } }),
|
|
1134
1451
|
rendersMode
|
|
1135
1452
|
? el('span', { className: 'db-inline' },
|
|
1136
|
-
el('span', { className: 'db-muted' }, '
|
|
1453
|
+
el('span', { className: 'db-muted' }, t('tab.revisions')),
|
|
1137
1454
|
el('select', {
|
|
1138
1455
|
className: 'db-input', 'data-field': 'compare-revision', style: { width: 'auto' }, value: right || '',
|
|
1139
1456
|
onChange: event => {
|
|
@@ -1151,14 +1468,14 @@ window.__ModuleLoader__.load({
|
|
|
1151
1468
|
className: 'db-input', 'data-field': 'compare-right', style: { width: 'auto' }, value: right || '',
|
|
1152
1469
|
onChange: event => actions.pickCompare('right', event.target.value),
|
|
1153
1470
|
}, revisions.map(entry => el('option', { key: entry.revision, value: entry.revision }, entry.revision))),
|
|
1154
|
-
state.activeProjectId ? Button({ action: 'diff', onClick: () => actions.diff(left, right), children: '
|
|
1471
|
+
state.activeProjectId ? Button({ action: 'diff', onClick: () => actions.diff(left, right), children: t('preview.structuralDiff') }) : null),
|
|
1155
1472
|
),
|
|
1156
1473
|
el('div', { style: { paddingTop: '2px' } },
|
|
1157
1474
|
el('div', { className: 'db-grid' }, rendersMode
|
|
1158
1475
|
? renderPairPanes
|
|
1159
1476
|
: [revisionPane(entryOf(left), 'left'), revisionPane(entryOf(right), 'right')]),
|
|
1160
1477
|
state.diff ? el('div', { className: 'db-card', 'data-diff': state.diff.identical ? 'identical' : 'changed' },
|
|
1161
|
-
el('h4', null, `${state.diff.fromRevision} → ${state.diff.toRevision}:${state.diff.identical ? '
|
|
1478
|
+
el('h4', null, `${state.diff.fromRevision} → ${state.diff.toRevision}:${state.diff.identical ? t('preview.identical') : t('preview.changeCount', { count: state.diff.totalChanges })}`),
|
|
1162
1479
|
state.diff.identical ? null : el('div', null,
|
|
1163
1480
|
Object.entries(state.diff.collections)
|
|
1164
1481
|
.filter(([, entry]) => entry.added.length + entry.removed.length + entry.changed.length > 0)
|
|
@@ -1181,25 +1498,25 @@ window.__ModuleLoader__.load({
|
|
|
1181
1498
|
function JobsView(ctx) {
|
|
1182
1499
|
const state = ctx.state
|
|
1183
1500
|
const actions = ctx.actions
|
|
1184
|
-
if (state.activeProjectId === null) return el('div', { 'data-view': 'jobs', className: 'db-muted' }, '
|
|
1501
|
+
if (state.activeProjectId === null) return el('div', { 'data-view': 'jobs', className: 'db-muted' }, t('common.noProject'))
|
|
1185
1502
|
const jobs = state.jobs || []
|
|
1186
1503
|
|
|
1187
1504
|
return el('div', { 'data-view': 'jobs' },
|
|
1188
1505
|
ErrorBox({ error: state.error }),
|
|
1189
1506
|
el('div', { className: 'db-card' },
|
|
1190
|
-
el('h4', null, '
|
|
1507
|
+
el('h4', null, t('tab.jobs')),
|
|
1191
1508
|
jobs.length === 0
|
|
1192
|
-
? el('div', { className: 'db-muted' }, '
|
|
1509
|
+
? el('div', { className: 'db-muted' }, t('jobs.empty'))
|
|
1193
1510
|
: el('ul', { className: 'db-list' }, jobs.map(job => el('li', { key: job.jobId, 'data-job': job.jobId },
|
|
1194
1511
|
el('div', { className: 'db-inline' },
|
|
1195
1512
|
el('span', { className: 'db-mono' }, job.jobId),
|
|
1196
1513
|
Badge({ tone: statusTone(job.status), children: job.status }),
|
|
1197
1514
|
el('span', { className: 'db-muted' }, job.type),
|
|
1198
|
-
el('span', { className: 'db-muted db-mono' },
|
|
1199
|
-
job.approval && job.approval.required ? Badge({ tone: 'warn', name: 'approval', children:
|
|
1200
|
-
job.deliverable ? Badge({ tone: 'ok', children: '
|
|
1201
|
-
job.cancelable ? Button({ tone: 'danger', action: `cancel:${job.jobId}`, disabled: state.busy.render, onClick: () => actions.cancelJob(job.jobId), children: '
|
|
1202
|
-
job.resumable ? Button({ action: `resume:${job.jobId}`, disabled: state.busy.render, onClick: () => actions.startRender(job.jobId), children: '
|
|
1515
|
+
el('span', { className: 'db-muted db-mono' }, t('jobs.frameRange', { start: job.frameStart, end: job.frameEnd })),
|
|
1516
|
+
job.approval && job.approval.required ? Badge({ tone: 'warn', name: 'approval', children: t('jobs.approval', { frames: job.approval.frames, threshold: job.approval.threshold }) }) : null,
|
|
1517
|
+
job.deliverable ? Badge({ tone: 'ok', children: t('jobs.deliveryVerified') }) : null,
|
|
1518
|
+
job.cancelable ? Button({ tone: 'danger', action: `cancel:${job.jobId}`, disabled: state.busy.render, onClick: () => actions.cancelJob(job.jobId), children: t('jobs.cancel') }) : null,
|
|
1519
|
+
job.resumable ? Button({ action: `resume:${job.jobId}`, disabled: state.busy.render, onClick: () => actions.startRender(job.jobId), children: t('jobs.resume') }) : null,
|
|
1203
1520
|
),
|
|
1204
1521
|
ProgressBar({ percent: job.progress.percent }),
|
|
1205
1522
|
el('div', { className: 'db-muted', 'data-job-detail': job.jobId }, `${job.detail} · ${job.progress.percent}% · 缺失 ${job.progress.missing} · 损坏 ${job.progress.corrupt}`),
|
|
@@ -1209,11 +1526,11 @@ window.__ModuleLoader__.load({
|
|
|
1209
1526
|
),
|
|
1210
1527
|
|
|
1211
1528
|
el('div', { className: 'db-card' },
|
|
1212
|
-
el('h4', null, '
|
|
1529
|
+
el('h4', null, t('jobs.startDelivery')),
|
|
1213
1530
|
el('div', { className: 'db-inline' },
|
|
1214
|
-
el('label', { className: 'db-muted' }, '
|
|
1531
|
+
el('label', { className: 'db-muted' }, t('jobs.frameStart')),
|
|
1215
1532
|
el('input', { className: 'db-input', 'data-field': 'frame-start', style: { width: '90px' }, value: state.forms.frameStart, onChange: event => actions.setForm('frameStart', event.target.value) }),
|
|
1216
|
-
el('label', { className: 'db-muted' }, '
|
|
1533
|
+
el('label', { className: 'db-muted' }, t('jobs.frameEnd')),
|
|
1217
1534
|
el('input', { className: 'db-input', 'data-field': 'frame-end', style: { width: '90px' }, value: state.forms.frameEnd, onChange: event => actions.setForm('frameEnd', event.target.value) }),
|
|
1218
1535
|
el('label', { className: 'db-muted' }, 'profile'),
|
|
1219
1536
|
el('select', {
|
|
@@ -1222,7 +1539,7 @@ window.__ModuleLoader__.load({
|
|
|
1222
1539
|
},
|
|
1223
1540
|
el('option', { value: 'preview' }, 'preview'),
|
|
1224
1541
|
el('option', { value: 'final' }, 'final')),
|
|
1225
|
-
Button({ tone: 'primary', action: 'start-render', disabled: state.busy.render, onClick: () => actions.startRender(undefined), children: state.busy.render ? '
|
|
1542
|
+
Button({ tone: 'primary', action: 'start-render', disabled: state.busy.render, onClick: () => actions.startRender(undefined), children: state.busy.render ? t('scene.submitting') : t('jobs.start') }),
|
|
1226
1543
|
),
|
|
1227
1544
|
Notice(state.notices.jobs, { marginTop: '8px' }),
|
|
1228
1545
|
),
|
|
@@ -1233,7 +1550,7 @@ window.__ModuleLoader__.load({
|
|
|
1233
1550
|
function QaView(ctx) {
|
|
1234
1551
|
const state = ctx.state
|
|
1235
1552
|
const qa = state.selected ? state.selected.qa : null
|
|
1236
|
-
if (qa === null || qa === undefined) return el('div', { 'data-view': 'qa', className: 'db-muted' }, '
|
|
1553
|
+
if (qa === null || qa === undefined) return el('div', { 'data-view': 'qa', className: 'db-muted' }, t('common.noProject'))
|
|
1237
1554
|
const issueList = (issues, keyPrefix) => el('ul', { className: 'db-list' }, issues.map((issue, index) => el('li', { key: `${keyPrefix}${index}` },
|
|
1238
1555
|
el('div', { className: 'db-inline' },
|
|
1239
1556
|
Badge({ tone: issue.severity === 'critical' ? 'bad' : issue.severity === 'major' ? 'warn' : 'muted', children: issue.severity || '—' }),
|
|
@@ -1248,49 +1565,49 @@ window.__ModuleLoader__.load({
|
|
|
1248
1565
|
el('div', { className: 'db-card', 'data-qa-revision': qa.revision },
|
|
1249
1566
|
el('div', { className: 'db-inline' },
|
|
1250
1567
|
el('strong', null, `QA · ${qa.revision}`),
|
|
1251
|
-
Badge({ tone: qa.technical.ok ? 'ok' : 'bad', children: qa.technical.available ? (qa.technical.ok ? '
|
|
1252
|
-
qa.visual.available ? Badge({ tone: qa.visual.pass ? 'ok' : 'warn', children:
|
|
1253
|
-
qa.semantic.noticeCount > 0 ? Badge({ tone: 'warn', children:
|
|
1568
|
+
Badge({ tone: qa.technical.ok ? 'ok' : 'bad', children: qa.technical.available ? (qa.technical.ok ? t('qa.passed') : t('qa.errorCount', { count: qa.technical.errorCount })) : t('qa.none') }),
|
|
1569
|
+
qa.visual.available ? Badge({ tone: qa.visual.pass ? 'ok' : 'warn', children: t('visual.scoreIs', { score: qa.visual.score }) }) : Badge({ children: t('visual.notRun') }),
|
|
1570
|
+
qa.semantic.noticeCount > 0 ? Badge({ tone: 'warn', children: t('qa.noticeCount', { count: qa.semantic.noticeCount }) }) : null,
|
|
1254
1571
|
),
|
|
1255
1572
|
el('div', { className: 'db-muted', style: { marginTop: '4px' } }, qa.summary),
|
|
1256
1573
|
),
|
|
1257
1574
|
|
|
1258
1575
|
el('div', { className: 'db-card' },
|
|
1259
|
-
el('h4', null, '
|
|
1576
|
+
el('h4', null, t('qa.title')),
|
|
1260
1577
|
KeyValues({ entries: [
|
|
1261
|
-
{ label: '
|
|
1262
|
-
{ label: '
|
|
1263
|
-
{ label: '
|
|
1264
|
-
{ label: '
|
|
1265
|
-
{ label: '
|
|
1266
|
-
{ label: '
|
|
1578
|
+
{ label: t('qa.engine'), value: qa.technical.engine },
|
|
1579
|
+
{ label: t('scene.activeCamera'), value: qa.technical.activeCamera },
|
|
1580
|
+
{ label: t('scene.frameRange'), value: qa.technical.frameRange ? qa.technical.frameRange.join('–') : null },
|
|
1581
|
+
{ label: t('qa.objects'), value: qa.technical.counts ? qa.technical.counts.objects : null },
|
|
1582
|
+
{ label: t('qa.materials'), value: qa.technical.counts ? qa.technical.counts.materials : null },
|
|
1583
|
+
{ label: t('qa.cameras'), value: qa.technical.counts ? qa.technical.counts.cameraObjects : null },
|
|
1267
1584
|
] }),
|
|
1268
1585
|
qa.technical.errors.length === 0
|
|
1269
|
-
? el('div', { className: 'db-muted' }, '
|
|
1586
|
+
? el('div', { className: 'db-muted' }, t('qa.noErrors'))
|
|
1270
1587
|
: issueList(qa.technical.errors, 'tech'),
|
|
1271
1588
|
qa.semantic.notices.length === 0 ? null : el('div', { style: { marginTop: '8px' } },
|
|
1272
|
-
el('h4', null, '
|
|
1589
|
+
el('h4', null, t('qa.notices')),
|
|
1273
1590
|
el('ul', { className: 'db-list' }, qa.semantic.notices.map((notice, index) => el('li', { key: `n${index}` },
|
|
1274
1591
|
el('span', { className: 'db-mono' }, notice.code || ''), ' ', notice.message || '')))),
|
|
1275
1592
|
),
|
|
1276
1593
|
|
|
1277
1594
|
el('div', { className: 'db-card' },
|
|
1278
|
-
el('h4', null, '
|
|
1595
|
+
el('h4', null, t('visual.title')),
|
|
1279
1596
|
qa.visual.available
|
|
1280
1597
|
? el('div', null,
|
|
1281
1598
|
KeyValues({ entries: [
|
|
1282
|
-
{ label: '
|
|
1283
|
-
{ label: '
|
|
1284
|
-
{ label: '
|
|
1285
|
-
{ label: '
|
|
1286
|
-
{ label: '
|
|
1287
|
-
{ label: '
|
|
1599
|
+
{ label: t('visual.score'), value: qa.visual.score },
|
|
1600
|
+
{ label: t('common.yes'), value: qa.visual.pass ? t('common.yesShort') : t('common.noShort') },
|
|
1601
|
+
{ label: t('visual.rounds'), value: qa.visual.iteration },
|
|
1602
|
+
{ label: t('visual.subjects'), value: qa.visual.subjectId },
|
|
1603
|
+
{ label: t('visual.views'), value: qa.visual.viewCount },
|
|
1604
|
+
{ label: t('visual.reviewer'), value: qa.visual.reviewerAvailable ? (qa.visual.reviewerModel || t('visual.called')) : (qa.visual.reviewerError || t('visual.notCalled')) },
|
|
1288
1605
|
] }),
|
|
1289
|
-
qa.visual.measuredIssues.length === 0 ? el('div', { className: 'db-muted' }, '
|
|
1606
|
+
qa.visual.measuredIssues.length === 0 ? el('div', { className: 'db-muted' }, t('visual.noFindings')) : issueList(qa.visual.measuredIssues, 'm'),
|
|
1290
1607
|
qa.visual.findings.length === 0
|
|
1291
|
-
? el('div', { className: 'db-muted' }, qa.visual.reviewerAvailable ? '
|
|
1608
|
+
? el('div', { className: 'db-muted' }, qa.visual.reviewerAvailable ? t('visual.reviewerSilent') : t('visual.noSecondOpinion'))
|
|
1292
1609
|
: issueList(qa.visual.findings, 'f'))
|
|
1293
|
-
: el('div', { className: 'db-muted' }, '
|
|
1610
|
+
: el('div', { className: 'db-muted' }, t('visual.none')),
|
|
1294
1611
|
),
|
|
1295
1612
|
)
|
|
1296
1613
|
}
|
|
@@ -1299,24 +1616,24 @@ window.__ModuleLoader__.load({
|
|
|
1299
1616
|
function RevisionsView(ctx) {
|
|
1300
1617
|
const state = ctx.state
|
|
1301
1618
|
const actions = ctx.actions
|
|
1302
|
-
if (state.activeProjectId === null) return el('div', { 'data-view': 'revisions', className: 'db-muted' }, '
|
|
1619
|
+
if (state.activeProjectId === null) return el('div', { 'data-view': 'revisions', className: 'db-muted' }, t('common.noProject'))
|
|
1303
1620
|
const revisions = (state.selected && state.selected.revisions) || []
|
|
1304
1621
|
|
|
1305
1622
|
return el('div', { 'data-view': 'revisions' },
|
|
1306
1623
|
ErrorBox({ error: state.error }),
|
|
1307
1624
|
el('div', { className: 'db-card' },
|
|
1308
|
-
el('h4', null,
|
|
1625
|
+
el('h4', null, t('revisions.title', { count: revisions.length })),
|
|
1309
1626
|
el('ul', { className: 'db-list' }, revisions.map(entry => el('li', { key: entry.revision, 'data-revision': entry.revision },
|
|
1310
1627
|
el('div', { className: 'db-inline' },
|
|
1311
1628
|
el('span', { className: 'db-mono' }, entry.revision),
|
|
1312
|
-
entry.isCurrent ? Badge({ tone: 'ok', children: '
|
|
1629
|
+
entry.isCurrent ? Badge({ tone: 'ok', children: t('revisions.current') }) : null,
|
|
1313
1630
|
el('span', { className: 'db-muted' }, entry.kind || '—'),
|
|
1314
1631
|
el('span', { className: 'db-muted' }, formatTime(entry.createdAt)),
|
|
1315
1632
|
el('span', { className: 'db-muted db-mono' }, `digest ${shortDigest(entry.digest)}`),
|
|
1316
|
-
Button({ action: `compare-from:${entry.revision}`, onClick: () => actions.compareFrom(entry.revision), children: '
|
|
1317
|
-
entry.isCurrent ? null : Button({ action: `restore:${entry.revision}`, disabled: state.busy.restore, onClick: () => actions.restoreRevision(entry.revision), children: '
|
|
1633
|
+
Button({ action: `compare-from:${entry.revision}`, onClick: () => actions.compareFrom(entry.revision), children: t('revisions.compare') }),
|
|
1634
|
+
entry.isCurrent ? null : Button({ action: `restore:${entry.revision}`, disabled: state.busy.restore, onClick: () => actions.restoreRevision(entry.revision), children: t('revisions.restore') }),
|
|
1318
1635
|
),
|
|
1319
|
-
el('div', { className: 'db-muted' }, entry.summary || '
|
|
1636
|
+
el('div', { className: 'db-muted' }, entry.summary || t('common.noNote')),
|
|
1320
1637
|
el('div', { className: 'db-muted db-mono' }, `previews ${(entry.previews || []).length} · validation ${entry.validation ? (entry.validation.ok ? 'ok' : `${entry.validation.errorCount} errors`) : '—'}`),
|
|
1321
1638
|
))),
|
|
1322
1639
|
Notice(state.notices.revisions, { marginTop: '8px' }),
|
|
@@ -1352,16 +1669,16 @@ window.__ModuleLoader__.load({
|
|
|
1352
1669
|
const body = stale
|
|
1353
1670
|
? el('div', { className: 'db-body' }, ErrorBox({ error: state.error }))
|
|
1354
1671
|
: state.status === 'loading'
|
|
1355
|
-
? el('div', { className: 'db-body db-muted' }, '
|
|
1672
|
+
? el('div', { className: 'db-body db-muted' }, t('host.reading'))
|
|
1356
1673
|
: el('div', { className: 'db-body' }, renderView(ctx))
|
|
1357
1674
|
|
|
1358
1675
|
return el('div', { className: 'db-root', 'data-deepblend-panel': PANEL_ID },
|
|
1359
1676
|
el('div', { className: 'db-head' },
|
|
1360
|
-
el('span', { className: 'db-title' },
|
|
1677
|
+
el('span', { className: 'db-title' }, t('workbench.title', { panel: PANEL_LABEL })),
|
|
1361
1678
|
state.selected ? el('span', { className: 'db-muted' }, state.selected.project.title) : null,
|
|
1362
1679
|
state.selected ? Badge({ children: state.selected.currentRevision || '—' }) : null,
|
|
1363
1680
|
state.unfinishedJobs.length > 0
|
|
1364
|
-
? Badge({ tone: 'live', name: 'unfinished', children:
|
|
1681
|
+
? Badge({ tone: 'live', name: 'unfinished', children: t('jobs.unfinished', { count: state.unfinishedJobs.length }) })
|
|
1365
1682
|
: null,
|
|
1366
1683
|
state.hostApiVersion !== null && state.hostApiVersion !== EXPECTED_HOST_API
|
|
1367
1684
|
? Badge({ tone: 'warn', name: 'api', children: `hostApiVersion ${state.hostApiVersion} ≠ ${EXPECTED_HOST_API}` })
|
|
@@ -1376,9 +1693,9 @@ window.__ModuleLoader__.load({
|
|
|
1376
1693
|
href: ROUTES.diagnostics,
|
|
1377
1694
|
download: 'deepblend-diagnostics.json',
|
|
1378
1695
|
'data-action': 'export-diagnostics',
|
|
1379
|
-
title: '
|
|
1380
|
-
}, '
|
|
1381
|
-
Button({ action: 'reload', onClick: actions.reload, children: '
|
|
1696
|
+
title: t('diagnostics.exportHint'),
|
|
1697
|
+
}, t('diagnostics.export')),
|
|
1698
|
+
Button({ action: 'reload', onClick: actions.reload, children: t('common.refresh') }),
|
|
1382
1699
|
),
|
|
1383
1700
|
el('div', { className: 'db-nav' }, VIEWS.map(entry => el('button', {
|
|
1384
1701
|
key: entry.id,
|
|
@@ -1604,17 +1921,17 @@ window.__ModuleLoader__.load({
|
|
|
1604
1921
|
|
|
1605
1922
|
return h('div', { 'data-deepblend-settings': PANEL_ID, style: { padding: '4px 2px' } },
|
|
1606
1923
|
state.error !== null && state.error !== undefined ? h(RErrorBox, { error: state.error }) : null,
|
|
1607
|
-
state.status === 'loading' ? h('div', { className: 'db-muted' }, '
|
|
1924
|
+
state.status === 'loading' ? h('div', { className: 'db-muted' }, t('blender.detecting')) : null,
|
|
1608
1925
|
card === null ? null : h('div', null,
|
|
1609
1926
|
h('div', { className: 'db-inline' },
|
|
1610
1927
|
h('strong', null, card.title),
|
|
1611
1928
|
h(RBadge, { tone: card.status === 'ready' ? 'ok' : 'bad' }, card.statusLabel),
|
|
1612
|
-
card.probedAt ? h('span', { className: 'db-muted' },
|
|
1929
|
+
card.probedAt ? h('span', { className: 'db-muted' }, t('blender.probedAt', { when: formatTime(card.probedAt) })) : null,
|
|
1613
1930
|
),
|
|
1614
1931
|
h('div', { className: 'db-card', style: { marginTop: '8px' } },
|
|
1615
1932
|
card.rows.map(row => h(RRow, { key: row.label, label: row.label, value: row.value }))),
|
|
1616
1933
|
card.warnings.length === 0 ? null : h('div', { className: 'db-card' },
|
|
1617
|
-
h('h4', null, '
|
|
1934
|
+
h('h4', null, t('common.warning')),
|
|
1618
1935
|
h('ul', { className: 'db-list' }, card.warnings.map((entry, index) => h('li', { key: `w${index}` },
|
|
1619
1936
|
h('span', { className: 'db-mono' }, entry.code || ''), ' ', entry.message || '')))),
|
|
1620
1937
|
h('div', { className: 'db-muted db-mono' }, `hostApiVersion ${payload.hostApiVersion}`),
|
|
@@ -1731,17 +2048,17 @@ window.__ModuleLoader__.load({
|
|
|
1731
2048
|
h('strong', { className: 'db-mono' }, toolName),
|
|
1732
2049
|
projectId ? h('span', { className: 'db-muted db-mono' }, projectId) : null,
|
|
1733
2050
|
jobId ? h(RBadge, null, jobId) : null,
|
|
1734
|
-
Array.isArray(args.operations) ? h(RBadge, null,
|
|
1735
|
-
job && job.approval && job.approval.required ? h(RBadge, { tone: 'warn', name: 'approval' },
|
|
2051
|
+
Array.isArray(args.operations) ? h(RBadge, null, t('visual.operationCount', { count: args.operations.length })) : null,
|
|
2052
|
+
job && job.approval && job.approval.required ? h(RBadge, { tone: 'warn', name: 'approval' }, t('jobs.approvalShort', { frames: job.approval.frames, threshold: job.approval.threshold })) : null,
|
|
1736
2053
|
h('span', { style: { flex: 1 } }),
|
|
1737
|
-
settled ? h('button', { type: 'button', className: 'db-chip', 'data-action': 'toggle-card', onClick: () => setExpanded(value => !value) }, expanded ? '
|
|
2054
|
+
settled ? h('button', { type: 'button', className: 'db-chip', 'data-action': 'toggle-card', onClick: () => setExpanded(value => !value) }, expanded ? t('common.collapse') : t('common.details')) : null,
|
|
1738
2055
|
),
|
|
1739
2056
|
|
|
1740
2057
|
jobShaped && job !== null ? h('div', { 'data-tool-job': job.jobId },
|
|
1741
2058
|
h('div', { className: 'db-inline' },
|
|
1742
2059
|
h(RBadge, { tone: statusTone(job.status) }, job.status),
|
|
1743
|
-
h('span', { className: 'db-muted' },
|
|
1744
|
-
job.estimatedRemainingMs ? h('span', { className: 'db-muted' },
|
|
2060
|
+
h('span', { className: 'db-muted' }, t('jobs.frameProgress', { completed: job.progress.completed, expected: job.progress.expected, percent: job.progress.percent })),
|
|
2061
|
+
job.estimatedRemainingMs ? h('span', { className: 'db-muted' }, t('jobs.estimated', { seconds: Math.round(job.estimatedRemainingMs / 1000) })) : null,
|
|
1745
2062
|
),
|
|
1746
2063
|
h(RProgressBar, { percent: job.progress.percent }),
|
|
1747
2064
|
h('div', { className: 'db-muted' }, job.detail),
|
|
@@ -1750,13 +2067,13 @@ window.__ModuleLoader__.load({
|
|
|
1750
2067
|
|
|
1751
2068
|
reviewShaped && qa !== null ? h('div', { 'data-tool-qa': qa.revision },
|
|
1752
2069
|
h('div', { className: 'db-inline' },
|
|
1753
|
-
qa.visual.available ? h(RBadge, { tone: qa.visual.pass ? 'ok' : 'warn' },
|
|
1754
|
-
h(RBadge, { tone: qa.technical.ok ? 'ok' : 'bad' }, qa.technical.available ? (qa.technical.ok ? '
|
|
1755
|
-
h('span', { className: 'db-muted' },
|
|
2070
|
+
qa.visual.available ? h(RBadge, { tone: qa.visual.pass ? 'ok' : 'warn' }, t('visual.scoreIs', { score: qa.visual.score })) : h(RBadge, null, t('visual.notRun')),
|
|
2071
|
+
h(RBadge, { tone: qa.technical.ok ? 'ok' : 'bad' }, qa.technical.available ? (qa.technical.ok ? t('qa.passed') : t('qa.errorCount', { count: qa.technical.errorCount })) : t('common.noRecord')),
|
|
2072
|
+
h('span', { className: 'db-muted' }, t('visual.counts', { measured: qa.visual.measuredIssueCount, findings: qa.visual.findingCount })),
|
|
1756
2073
|
),
|
|
1757
2074
|
qa.visual.measuredIssues.slice(0, 4).map((issue, index) => h('div', { key: `i${index}`, className: 'db-muted db-mono' },
|
|
1758
2075
|
`[${issue.severity}] ${issue.code} ${issue.viewId || ''} :: ${issue.evidence || ''}`)),
|
|
1759
|
-
qa.visual.reviewerError ? h('div', { className: 'db-error' },
|
|
2076
|
+
qa.visual.reviewerError ? h('div', { className: 'db-error' }, t('visual.reviewerFailed', { message: qa.visual.reviewerError })) : null,
|
|
1760
2077
|
) : null,
|
|
1761
2078
|
|
|
1762
2079
|
reviewShaped && sheet !== null ? h('div', { style: { marginTop: '6px' } },
|
|
@@ -1767,9 +2084,9 @@ window.__ModuleLoader__.load({
|
|
|
1767
2084
|
src: artifactUrl(artifactBase, sheet),
|
|
1768
2085
|
style: { width: '100%', borderRadius: '6px', background: '#000' },
|
|
1769
2086
|
}),
|
|
1770
|
-
h('div', { className: 'db-muted db-mono' }, `${sheet.sha256 ? String(sheet.sha256).slice(0, 10) : '—'}${artifactTime(sheet) === null ? '' :
|
|
2087
|
+
h('div', { className: 'db-muted db-mono' }, `${sheet.sha256 ? String(sheet.sha256).slice(0, 10) : '—'}${artifactTime(sheet) === null ? '' : t('preview.renderedAt', { when: artifactTime(sheet) })}`)) : null,
|
|
1771
2088
|
|
|
1772
|
-
expanded || !settled ? h('pre', { className: 'db-pre' }, (resultText(props.block) ||
|
|
2089
|
+
expanded || !settled ? h('pre', { className: 'db-pre' }, (resultText(props.block) || t('jobs.running')).slice(0, 4000)) : null,
|
|
1773
2090
|
)
|
|
1774
2091
|
}
|
|
1775
2092
|
|
|
@@ -1785,7 +2102,7 @@ window.__ModuleLoader__.load({
|
|
|
1785
2102
|
title: projects.map(project => `${project.projectId}: ${project.unfinishedJobs || 0}`).join('\n'),
|
|
1786
2103
|
},
|
|
1787
2104
|
h('span', { className: 'db-dot', 'data-tone': unfinished > 0 ? 'live' : 'ok' }),
|
|
1788
|
-
unfinished > 0 ?
|
|
2105
|
+
unfinished > 0 ? t('jobs.unfinishedShort', { count: unfinished }) : t('jobs.none'),
|
|
1789
2106
|
)
|
|
1790
2107
|
}
|
|
1791
2108
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepblend/dsh-blender-ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "DeepBlend workbench UI: Host half (typed HTTP API) and Web Client half (sidebar, panels, tool cards).",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@deepblend/dsh-blender-contracts": "0.2.
|
|
22
|
+
"@deepblend/dsh-blender-contracts": "0.2.4"
|
|
23
23
|
},
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"repository": {
|