@deepblend/dsh-blender-ui 0.3.7 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/lib/client.js +68 -14
  2. package/package.json +2 -2
package/lib/client.js CHANGED
@@ -603,7 +603,9 @@ window.__ModuleLoader__.load({
603
603
  'visual.noSecondOpinion': '没有第二意见。',
604
604
  'visual.none': '这个 revision 还没有视觉评审。可以用 blender_visual_review 跑一次。',
605
605
  'revisions.current': '当前',
606
- 'revisions.compare': '对比',
606
+ 'revisions.compare': '与当前版对比',
607
+ 'revisions.compareCurrent': '与另一版对比',
608
+ 'revisions.compareHint': '选择已保存版本与当前版对比;选择当前版会对比相邻的另一版。需要至少两版,查看不会改动作品。',
607
609
  'revisions.restore': '恢复',
608
610
  'host.reading': '读取 Host 状态…',
609
611
  'diagnostics.exportHint': '导出一份可以附在问题里的诊断信息(版本、配置、项目与任务的摘要)',
@@ -1168,7 +1170,9 @@ window.__ModuleLoader__.load({
1168
1170
  'visual.noSecondOpinion': 'No second opinion.',
1169
1171
  'visual.none': 'This revision has no visual review yet. Run one with blender_visual_review.',
1170
1172
  'revisions.current': 'current',
1171
- 'revisions.compare': 'compare',
1173
+ 'revisions.compare': 'Compare with current',
1174
+ 'revisions.compareCurrent': 'Compare with another version',
1175
+ 'revisions.compareHint': 'Compare a saved version with the current one. The current version uses its nearest other version. At least two versions are needed; viewing does not change the project.',
1172
1176
  'revisions.restore': 'restore',
1173
1177
  'host.reading': 'Reading Host state…',
1174
1178
  'diagnostics.exportHint': 'Export diagnostics you can attach to a bug report (versions, config, a summary of projects and jobs)',
@@ -2657,6 +2661,22 @@ window.__ModuleLoader__.load({
2657
2661
  try { const value = storage?.getItem(APPEARANCE_KEY); return APPEARANCES.includes(value) ? value : 'system' } catch { return 'system' }
2658
2662
  }
2659
2663
 
2664
+ function previewRevisionPair(state) {
2665
+ const revisions = state.previews?.revisions || []
2666
+ const pick = wanted => revisions.some(entry => entry.revision === wanted) ? wanted : revisions.at(-1)?.revision || null
2667
+ return { left: pick(state.compareLeft || state.currentRevision), right: pick(state.compareRight || state.currentRevision) }
2668
+ }
2669
+ function revisionComparisonTarget(state, revision) {
2670
+ if (!state.activeProjectId) return null
2671
+ const saved = new Set((state.selected?.revisions || []).map(entry => entry.revision))
2672
+ const available = [...new Set((state.previews?.revisions || []).map(entry => entry.revision).filter(id => saved.has(id)))]
2673
+ .sort((a, b) => Number(a.slice(1)) - Number(b.slice(1)))
2674
+ if (!available.includes(revision) || !available.includes(state.currentRevision)) return null
2675
+ const index = available.indexOf(revision)
2676
+ const left = revision === state.currentRevision ? available[index - 1] || available[index + 1] : revision
2677
+ return left ? { left, right: state.currentRevision } : null
2678
+ }
2679
+
2660
2680
  function createWorkbenchStore(options) {
2661
2681
  const settings = options || {}
2662
2682
  const fetchImpl = settings.fetch ?? ((...args) => fetch(...args))
@@ -2674,6 +2694,7 @@ window.__ModuleLoader__.load({
2674
2694
  let tick = 0
2675
2695
  let live = false
2676
2696
  let loadSequence = 0
2697
+ let diffSequence = 0
2677
2698
  let creationAttempt = null
2678
2699
  const draftStorage = creationDraftStorage(settings)
2679
2700
  let creationDraftId = newCreationKey(), creationDraftRoot = null, persistedCreationSignature = null
@@ -2732,7 +2753,14 @@ window.__ModuleLoader__.load({
2732
2753
  for (const listener of [...listeners]) listener(snapshot)
2733
2754
  }
2734
2755
  /** Change the snapshot and tell everyone. @param {object} patch */
2735
- const set = (patch) => { data = { ...data, ...patch }; notify() }
2756
+ const set = (patch) => {
2757
+ if (['projectsRoot', 'activeProjectId', 'currentRevision', 'view', 'compareMode', 'compareLeft', 'compareRight']
2758
+ .some(key => Object.hasOwn(patch, key) && patch[key] !== data[key])) {
2759
+ diffSequence += 1
2760
+ patch = { ...patch, diff: null, diffError: null }
2761
+ }
2762
+ data = { ...data, ...patch }; notify()
2763
+ }
2736
2764
  /** Change one nested table. @param {'forms'|'busy'|'notices'} table @param {string} key @param {any} value */
2737
2765
  const setIn = (table, key, value) => { data = { ...data, [table]: { ...data[table], [key]: value } }; notify() }
2738
2766
 
@@ -3264,8 +3292,15 @@ window.__ModuleLoader__.load({
3264
3292
  pickCompare: (side, revision) => set(side === 'left'
3265
3293
  ? { compareLeft: revision, diff: null, diffError: null }
3266
3294
  : { compareRight: revision, diff: null, diffError: null }),
3267
- /** Hand off from 版本 to 预览对比 with that revision on the left. */
3268
- compareFrom: (revision) => set({ compareLeft: revision, view: 'preview' }),
3295
+ /** View a saved version against the current one, without changing the project. */
3296
+ compareFrom: (revision) => {
3297
+ const pair = revisionComparisonTarget(data, revision)
3298
+ if (pair) {
3299
+ diffSequence += 1
3300
+ set({ compareLeft: pair.left, compareRight: pair.right, compareMode: 'revisions', view: 'preview',
3301
+ diff: null, diffError: null, editorComparison: null })
3302
+ }
3303
+ },
3269
3304
 
3270
3305
  updateBrief: (field, value, referenceId) => {
3271
3306
  const projectId = data.activeProjectId, current = data.briefDrafts[projectId]
@@ -3565,14 +3600,32 @@ window.__ModuleLoader__.load({
3565
3600
  },
3566
3601
 
3567
3602
  diff: async (from, to) => {
3568
- set({ diffError: null })
3603
+ const projectId = data.activeProjectId, pair = previewRevisionPair(data)
3604
+ if (!running || !projectId || data.view !== 'preview' || data.compareMode !== 'revisions' || pair.left !== from || pair.right !== to) return
3605
+ const sequence = ++diffSequence
3606
+ const stamp = JSON.stringify([data.projectsRoot, projectId, data.currentRevision, from, to])
3607
+ const current = () => {
3608
+ const selected = previewRevisionPair(data)
3609
+ return running && sequence === diffSequence && data.view === 'preview' && data.compareMode === 'revisions'
3610
+ && selected.left === from && selected.right === to
3611
+ && JSON.stringify([data.projectsRoot, data.activeProjectId, data.currentRevision, from, to]) === stamp
3612
+ }
3613
+ set({ diff: null, diffError: null })
3569
3614
  try {
3570
- const response = await fetchImpl(`${projectRoute(target(), '/diff')}?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`, { headers: { accept: 'application/json' } })
3615
+ const response = await fetchImpl(`${projectRoute(projectId, '/diff')}?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`, { headers: { accept: 'application/json' } })
3571
3616
  const parsed = await response.json()
3572
- if (parsed && parsed.ok) set({ diff: parsed.diff })
3573
- else set({ diff: null, diffError: (parsed && parsed.error) || { code: `HTTP_${response.status}`, message: t('preview.diffFailed') } })
3617
+ if (!current()) return
3618
+ if (response.ok && parsed?.ok) {
3619
+ if (parsed.projectId !== undefined && parsed.projectId !== projectId
3620
+ || parsed.diff?.projectId !== undefined && parsed.diff.projectId !== projectId
3621
+ || parsed.diff?.fromRevision !== from || parsed.diff?.toRevision !== to
3622
+ || parsed.fromRevision !== undefined && parsed.fromRevision !== from
3623
+ || parsed.toRevision !== undefined && parsed.toRevision !== to) {
3624
+ set({ diff: null, diffError: { code: 'UI_DIFF_MISMATCH', message: t('preview.diffFailed') } })
3625
+ } else set({ diff: parsed.diff })
3626
+ } else set({ diff: null, diffError: parsed?.error || { code: `HTTP_${response.status}`, message: t('preview.diffFailed') } })
3574
3627
  } catch (error) {
3575
- set({ diff: null, diffError: { code: 'UI_FETCH_FAILED', message: String((error && error.message) || error) } })
3628
+ if (current()) set({ diff: null, diffError: { code: 'UI_FETCH_FAILED', message: String((error && error.message) || error) } })
3576
3629
  }
3577
3630
  },
3578
3631
 
@@ -3799,6 +3852,7 @@ window.__ModuleLoader__.load({
3799
3852
  void load()
3800
3853
  },
3801
3854
  stop() {
3855
+ diffSequence += 1
3802
3856
  closeCreationDraft()
3803
3857
  draftLifecycle?.removeEventListener?.('pagehide', closeCreationDraft)
3804
3858
  draftLifecycle?.removeEventListener?.('pageshow', reopenCreationDraft)
@@ -4825,9 +4879,7 @@ window.__ModuleLoader__.load({
4825
4879
  const previews = state.previews
4826
4880
  if (previews === null) return el('div', { 'data-view': 'preview', className: 'db-muted' }, t('common.noProject'))
4827
4881
  const revisions = previews.revisions
4828
- const pick = wanted => (revisions.some(entry => entry.revision === wanted) ? wanted : (((revisions[revisions.length - 1] || {}).revision) || null))
4829
- const left = pick(state.compareLeft || state.currentRevision)
4830
- const right = pick(state.compareRight || state.currentRevision)
4882
+ const { left, right } = previewRevisionPair(state)
4831
4883
  const entryOf = id => revisions.find(entry => entry.revision === id) || null
4832
4884
 
4833
4885
  /** One image (or the reason there is none), with its digest and its time. */
@@ -5174,6 +5226,7 @@ window.__ModuleLoader__.load({
5174
5226
  ErrorBox({ error: state.error }),
5175
5227
  el('div', { className: 'db-card' },
5176
5228
  el('h4', null, t('revisions.title', { count: revisions.length })),
5229
+ el('p', { className: 'db-muted' }, t('revisions.compareHint')),
5177
5230
  el('ul', { className: 'db-list' }, revisions.map(entry => el('li', { key: entry.revision, 'data-revision': entry.revision },
5178
5231
  el('div', { className: 'db-inline' },
5179
5232
  el('span', { className: 'db-mono' }, entry.revision),
@@ -5181,7 +5234,8 @@ window.__ModuleLoader__.load({
5181
5234
  el('span', { className: 'db-muted' }, entry.kind || '—'),
5182
5235
  el('span', { className: 'db-muted' }, formatTime(entry.createdAt)),
5183
5236
  el('span', { className: 'db-muted db-mono' }, `digest ${shortDigest(entry.digest)}`),
5184
- Button({ action: `compare-from:${entry.revision}`, onClick: () => actions.compareFrom(entry.revision), children: t('revisions.compare') }),
5237
+ Button({ action: `compare-from:${entry.revision}`, disabled: revisionComparisonTarget(state, entry.revision) === null,
5238
+ onClick: () => actions.compareFrom(entry.revision), children: entry.isCurrent ? t('revisions.compareCurrent') : t('revisions.compare') }),
5185
5239
  entry.isCurrent ? null : Button({ action: `restore:${entry.revision}`, disabled: state.busy.restore, onClick: () => actions.restoreRevision(entry.revision), children: t('revisions.restore') }),
5186
5240
  ),
5187
5241
  el('div', { className: 'db-muted' }, entry.summary || t('common.noNote')),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepblend/dsh-blender-ui",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
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.3.7"
22
+ "@deepblend/dsh-blender-contracts": "0.3.8"
23
23
  },
24
24
  "license": "MIT",
25
25
  "repository": {