@deepblend/dsh-blender-ui 0.3.6 → 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.
- package/lib/client.js +146 -30
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -478,7 +478,7 @@ window.__ModuleLoader__.load({
|
|
|
478
478
|
'recipes.select': '使用这个配方',
|
|
479
479
|
'recipes.selected': '已选择',
|
|
480
480
|
'recipes.parameters': '调整配方',
|
|
481
|
-
'recipes.previewNote': '
|
|
481
|
+
'recipes.previewNote': '示例图展示默认参数。切换配方会保留各自的调整,恢复默认值只影响当前配方。创建后会渲染你的设置,耗时取决于设备。',
|
|
482
482
|
'recipes.createPreview': '创建并生成预览',
|
|
483
483
|
'recipes.creatingPreview': '正在创建并渲染…',
|
|
484
484
|
'recipes.stale': '选中的配方已更新或不可用,请重新选择。',
|
|
@@ -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': '导出一份可以附在问题里的诊断信息(版本、配置、项目与任务的摘要)',
|
|
@@ -1043,7 +1045,7 @@ window.__ModuleLoader__.load({
|
|
|
1043
1045
|
'recipes.select': 'Use this recipe',
|
|
1044
1046
|
'recipes.selected': 'Selected',
|
|
1045
1047
|
'recipes.parameters': 'Customize recipe',
|
|
1046
|
-
'recipes.previewNote': 'Images show default parameters. Creating a project renders your settings; time depends on your device.',
|
|
1048
|
+
'recipes.previewNote': 'Images show default parameters. Switching recipes keeps each set of edits; reset affects only the current recipe. Creating a project renders your settings; time depends on your device.',
|
|
1047
1049
|
'recipes.createPreview': 'Create and preview',
|
|
1048
1050
|
'recipes.creatingPreview': 'Creating and rendering…',
|
|
1049
1051
|
'recipes.stale': 'The selected recipe changed or is unavailable. Please select it again.',
|
|
@@ -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': '
|
|
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)',
|
|
@@ -1399,6 +1403,34 @@ window.__ModuleLoader__.load({
|
|
|
1399
1403
|
const CREATION_DRAFT_BYTES = 262144
|
|
1400
1404
|
const creationDraftPrefix = root => `${CREATION_DRAFT_VERSION}:${encodeURIComponent(root)}:`
|
|
1401
1405
|
const creationRecipeReference = recipe => recipe ? { id: recipe.id, version: recipe.version, digest: recipe.digest } : null
|
|
1406
|
+
const recipeChoiceIdentity = recipe => creationSignature(creationRecipeReference(recipe))
|
|
1407
|
+
const copyRecipeParameters = values => Object.fromEntries(Object.entries(values).map(([key, value]) => [key, Array.isArray(value) ? [...value] : value]))
|
|
1408
|
+
const rememberRecipeChoice = (choices, recipe, parameters) => !recipe ? choices : [
|
|
1409
|
+
...choices.filter(choice => recipeChoiceIdentity(choice.recipe) !== recipeChoiceIdentity(recipe)),
|
|
1410
|
+
{ recipe: creationRecipeReference(recipe), parameters: copyRecipeParameters(parameters) },
|
|
1411
|
+
]
|
|
1412
|
+
function validRecipeChoices(choices) {
|
|
1413
|
+
if (choices === undefined) return true // Existing 0.3.6 browser drafts contain only their selected recipe.
|
|
1414
|
+
if (!Array.isArray(choices)) return false
|
|
1415
|
+
const seen = new Set()
|
|
1416
|
+
return choices.every(choice => {
|
|
1417
|
+
if (!choice || !choice.recipe || !validCreationRecipeReference(choice.recipe)
|
|
1418
|
+
|| Object.keys(choice).some(key => !['recipe', 'parameters'].includes(key))
|
|
1419
|
+
|| !choice.parameters || typeof choice.parameters !== 'object' || Array.isArray(choice.parameters)) return false
|
|
1420
|
+
const key = recipeChoiceIdentity(choice.recipe)
|
|
1421
|
+
if (seen.has(key)) return false
|
|
1422
|
+
seen.add(key)
|
|
1423
|
+
return Object.values(choice.parameters).every(value => value === '' || Number.isFinite(value)
|
|
1424
|
+
|| Array.isArray(value) && value.length === 3 && value.every(v => Number.isFinite(v) && v >= 0 && v <= 1))
|
|
1425
|
+
})
|
|
1426
|
+
}
|
|
1427
|
+
function recipeChoiceFits(recipe, parameters) {
|
|
1428
|
+
return Object.keys(parameters).length === recipe.parameters.length && recipe.parameters.every(parameter => {
|
|
1429
|
+
const value = parameters[parameter.id]
|
|
1430
|
+
return parameter.type === 'color' ? Array.isArray(value) && value.length === 3 && value.every(v => Number.isFinite(v) && v >= 0 && v <= 1)
|
|
1431
|
+
: value === '' || Number.isFinite(value)
|
|
1432
|
+
})
|
|
1433
|
+
}
|
|
1402
1434
|
function creationDraftJson(value) {
|
|
1403
1435
|
if (value === null || typeof value === 'string' || typeof value === 'boolean') return
|
|
1404
1436
|
if (typeof value === 'number' && Number.isFinite(value)) return
|
|
@@ -1429,7 +1461,8 @@ window.__ModuleLoader__.load({
|
|
|
1429
1461
|
|| !Number.isFinite(Date.parse(draft.savedAt)) || !forms || typeof forms.title !== 'string'
|
|
1430
1462
|
|| typeof forms.goal !== 'string' || !validCreationRecipeReference(forms.recipe)
|
|
1431
1463
|
|| Object.keys(forms).some(key => !['title', 'goal', 'recipe', 'recipeParameters'].includes(key))
|
|
1432
|
-
|| !forms.recipeParameters || typeof forms.recipeParameters !== 'object' || Array.isArray(forms.recipeParameters)
|
|
1464
|
+
|| !forms.recipeParameters || typeof forms.recipeParameters !== 'object' || Array.isArray(forms.recipeParameters)
|
|
1465
|
+
|| !validRecipeChoices(draft.recipeChoices)) return null
|
|
1433
1466
|
const attempt = draft.attempt
|
|
1434
1467
|
if (attempt !== null) {
|
|
1435
1468
|
const body = attempt?.body
|
|
@@ -2607,6 +2640,7 @@ window.__ModuleLoader__.load({
|
|
|
2607
2640
|
editorLastEdits: {},
|
|
2608
2641
|
editorComparison: null,
|
|
2609
2642
|
patchDrafts: {},
|
|
2643
|
+
recipeChoices: [],
|
|
2610
2644
|
forms: { title: '', goal: '', recipe: null, recipeParameters: {}, patch: null, frameStart: '', frameEnd: '', profile: 'preview' },
|
|
2611
2645
|
busy: { create: false, patch: false, editor: false, render: false, restore: false },
|
|
2612
2646
|
notices: { projects: null, scene: null, preview: null, jobs: null, revisions: null },
|
|
@@ -2627,6 +2661,22 @@ window.__ModuleLoader__.load({
|
|
|
2627
2661
|
try { const value = storage?.getItem(APPEARANCE_KEY); return APPEARANCES.includes(value) ? value : 'system' } catch { return 'system' }
|
|
2628
2662
|
}
|
|
2629
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
|
+
|
|
2630
2680
|
function createWorkbenchStore(options) {
|
|
2631
2681
|
const settings = options || {}
|
|
2632
2682
|
const fetchImpl = settings.fetch ?? ((...args) => fetch(...args))
|
|
@@ -2644,6 +2694,7 @@ window.__ModuleLoader__.load({
|
|
|
2644
2694
|
let tick = 0
|
|
2645
2695
|
let live = false
|
|
2646
2696
|
let loadSequence = 0
|
|
2697
|
+
let diffSequence = 0
|
|
2647
2698
|
let creationAttempt = null
|
|
2648
2699
|
const draftStorage = creationDraftStorage(settings)
|
|
2649
2700
|
let creationDraftId = newCreationKey(), creationDraftRoot = null, persistedCreationSignature = null
|
|
@@ -2693,6 +2744,8 @@ window.__ModuleLoader__.load({
|
|
|
2693
2744
|
const assetWork = (projectId, changes) => set({ assetWork: { ...data.assetWork, [projectId]: { ...data.assetWork[projectId], ...changes } } })
|
|
2694
2745
|
|
|
2695
2746
|
const notify = () => {
|
|
2747
|
+
if (!creationDraftClosed) data.recipeChoices = rememberRecipeChoice(data.recipeChoices,
|
|
2748
|
+
data.forms.recipe || data.creationDraftMissingRecipe, data.forms.recipeParameters)
|
|
2696
2749
|
persistCreationDraft()
|
|
2697
2750
|
const signature = JSON.stringify([data.activeProjectId, data.selected?.scene?.revision, data.view, ...FILE_FIELDS.map(field => fileChoiceAllowed(data, field))])
|
|
2698
2751
|
if (signature !== fileChoiceSignature) { data.fileChoiceGeneration += 1; fileChoiceSignature = signature }
|
|
@@ -2700,7 +2753,14 @@ window.__ModuleLoader__.load({
|
|
|
2700
2753
|
for (const listener of [...listeners]) listener(snapshot)
|
|
2701
2754
|
}
|
|
2702
2755
|
/** Change the snapshot and tell everyone. @param {object} patch */
|
|
2703
|
-
const set = (patch) => {
|
|
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
|
+
}
|
|
2704
2764
|
/** Change one nested table. @param {'forms'|'busy'|'notices'} table @param {string} key @param {any} value */
|
|
2705
2765
|
const setIn = (table, key, value) => { data = { ...data, [table]: { ...data[table], [key]: value } }; notify() }
|
|
2706
2766
|
|
|
@@ -2715,19 +2775,21 @@ window.__ModuleLoader__.load({
|
|
|
2715
2775
|
if (creationDraftClosed || creationDraftWriting || !creationDraftRoot || data.projectsRoot !== creationDraftRoot) return
|
|
2716
2776
|
creationDraftWriting = true
|
|
2717
2777
|
try {
|
|
2718
|
-
const forms = { ...creationDraftForms(data.forms), recipe: creationRecipeReference(data.forms.recipe) || data.creationDraftMissingRecipe }
|
|
2778
|
+
const forms = { ...creationDraftForms(data.forms), recipe: creationRecipeReference(data.forms.recipe) || data.creationDraftMissingRecipe }
|
|
2779
|
+
creationDraftJson(data.recipeChoices)
|
|
2780
|
+
const signature = JSON.stringify([forms, creationAttempt, data.recipeChoices])
|
|
2719
2781
|
if (signature === persistedCreationSignature) return
|
|
2720
2782
|
const reading = refreshCreationDrafts()
|
|
2721
2783
|
if (!reading.available) { data.creationDraftStorageStatus = 'unavailable'; return }
|
|
2722
2784
|
const key = creationDraftPrefix(creationDraftRoot) + creationDraftId
|
|
2723
|
-
if (!forms.title && !forms.goal && !forms.recipe && !Object.keys(forms.recipeParameters).length && !creationAttempt) {
|
|
2785
|
+
if (!forms.title && !forms.goal && !forms.recipe && !Object.keys(forms.recipeParameters).length && !creationAttempt && !data.recipeChoices.length) {
|
|
2724
2786
|
draftStorage.removeItem(key); data.creationDraftStorageStatus = null
|
|
2725
2787
|
} else {
|
|
2726
2788
|
if (!reading.entries.some(entry => entry.id === creationDraftId) && reading.entries.length >= 20) {
|
|
2727
2789
|
data.creationDraftStorageStatus = 'full'; return
|
|
2728
2790
|
}
|
|
2729
2791
|
const raw = JSON.stringify({ schemaVersion: CREATION_DRAFT_VERSION, projectsRoot: creationDraftRoot,
|
|
2730
|
-
id: creationDraftId, ownerActive: true, savedAt: new Date().toISOString(), forms, attempt: creationAttempt })
|
|
2792
|
+
id: creationDraftId, ownerActive: true, savedAt: new Date().toISOString(), forms, attempt: creationAttempt, recipeChoices: data.recipeChoices })
|
|
2731
2793
|
if (new Blob([raw]).size > CREATION_DRAFT_BYTES) { data.creationDraftStorageStatus = 'tooLarge'; return }
|
|
2732
2794
|
draftStorage.setItem(key, raw)
|
|
2733
2795
|
if (draftStorage.getItem(key) !== raw) throw new Error('Draft write did not persist')
|
|
@@ -2832,7 +2894,7 @@ window.__ModuleLoader__.load({
|
|
|
2832
2894
|
// Switching stores cannot carry an old store's request identity into a new one.
|
|
2833
2895
|
if (creationDraftRoot !== null) {
|
|
2834
2896
|
creationAttempt = null; restoredCreationSource = null; creationDraftId = newCreationKey()
|
|
2835
|
-
patch.creationWork = null; patch.creationDraftMissingRecipe = null
|
|
2897
|
+
patch.creationWork = null; patch.creationDraftMissingRecipe = null; patch.recipeChoices = []
|
|
2836
2898
|
patch.forms = { ...data.forms, title: '', goal: '', recipe: null, recipeParameters: {} }
|
|
2837
2899
|
}
|
|
2838
2900
|
creationDraftRoot = typeof patch.projectsRoot === 'string' && patch.projectsRoot ? patch.projectsRoot : null
|
|
@@ -2873,6 +2935,9 @@ window.__ModuleLoader__.load({
|
|
|
2873
2935
|
if (attemptRoot !== creationDraftRoot) { reload(); return }
|
|
2874
2936
|
if (outcome.ok) {
|
|
2875
2937
|
const sameInputs = creationSignature(creationBody()) === attempt.signature
|
|
2938
|
+
if (attempt.body.recipe) data.recipeChoices = data.recipeChoices.filter(choice =>
|
|
2939
|
+
recipeChoiceIdentity(choice.recipe) !== recipeChoiceIdentity(attempt.body.recipe)
|
|
2940
|
+
|| creationSignature(choice.parameters) !== creationSignature(attempt.body.recipe.parameters))
|
|
2876
2941
|
set({ ...(sameInputs ? { forms: { ...data.forms, title: '', goal: '', recipe: null, recipeParameters: {} } } : {}),
|
|
2877
2942
|
creationWork: null, projectId: outcome.payload.project.projectId,
|
|
2878
2943
|
...(attempt.body.renderPreview ? { view: 'preview', compareMode: 'result' } : {}) })
|
|
@@ -2882,7 +2947,17 @@ window.__ModuleLoader__.load({
|
|
|
2882
2947
|
const sourceKey = creationDraftPrefix(creationDraftRoot) + restoredCreationSource.id
|
|
2883
2948
|
const source = parseCreationDraft(restoredCreationSource.raw, creationDraftRoot, restoredCreationSource.id)
|
|
2884
2949
|
if (source && creationSignature(creationRequestBody(source.forms)) === attempt.signature
|
|
2885
|
-
&& draftStorage?.getItem(sourceKey) === restoredCreationSource.raw)
|
|
2950
|
+
&& draftStorage?.getItem(sourceKey) === restoredCreationSource.raw) {
|
|
2951
|
+
const remaining = (source.recipeChoices || []).filter(choice => !attempt.body.recipe
|
|
2952
|
+
|| recipeChoiceIdentity(choice.recipe) !== recipeChoiceIdentity(attempt.body.recipe)
|
|
2953
|
+
|| creationSignature(choice.parameters) !== creationSignature(attempt.body.recipe.parameters))
|
|
2954
|
+
if (remaining.length) {
|
|
2955
|
+
const raw = JSON.stringify({ ...source, savedAt: new Date().toISOString(), attempt: null, recipeChoices: remaining,
|
|
2956
|
+
forms: { title: '', goal: '', recipe: null, recipeParameters: {} } })
|
|
2957
|
+
draftStorage.setItem(sourceKey, raw)
|
|
2958
|
+
if (draftStorage.getItem(sourceKey) !== raw) throw new Error('Draft write did not persist')
|
|
2959
|
+
} else draftStorage.removeItem(sourceKey)
|
|
2960
|
+
}
|
|
2886
2961
|
} catch { data.creationDraftStorageStatus = 'unavailable' }
|
|
2887
2962
|
}
|
|
2888
2963
|
restoredCreationSource = null
|
|
@@ -3197,17 +3272,35 @@ window.__ModuleLoader__.load({
|
|
|
3197
3272
|
} else assetWork(projectId, { error: `${outcome.error.code}: ${outcome.error.message}` })
|
|
3198
3273
|
reload()
|
|
3199
3274
|
},
|
|
3200
|
-
selectRecipe: recipe =>
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3275
|
+
selectRecipe: recipe => {
|
|
3276
|
+
if (recipe && data.forms.recipe && recipeChoiceIdentity(recipe) === recipeChoiceIdentity(data.forms.recipe)) return
|
|
3277
|
+
const saved = recipe ? data.recipeChoices.find(choice => recipeChoiceIdentity(choice.recipe) === recipeChoiceIdentity(recipe)) : null
|
|
3278
|
+
if (saved && !recipeChoiceFits(recipe, saved.parameters)) { set({ creationDraftStorageStatus: 'invalid' }); return }
|
|
3279
|
+
set({ creationDraftMissingRecipe: null, forms: { ...data.forms, recipe,
|
|
3280
|
+
recipeParameters: recipe ? saved ? copyRecipeParameters(saved.parameters)
|
|
3281
|
+
: Object.fromEntries(recipe.parameters.map(parameter => [parameter.id, Array.isArray(parameter.default) ? [...parameter.default] : parameter.default])) : {},
|
|
3282
|
+
title: !data.forms.title || data.forms.title === data.forms.recipe?.title ? recipe?.title || '' : data.forms.title,
|
|
3283
|
+
} })
|
|
3284
|
+
},
|
|
3285
|
+
resetRecipe: () => {
|
|
3286
|
+
const recipe = data.forms.recipe
|
|
3287
|
+
if (recipe) set({ forms: { ...data.forms, recipeParameters: Object.fromEntries(recipe.parameters.map(parameter =>
|
|
3288
|
+
[parameter.id, Array.isArray(parameter.default) ? [...parameter.default] : parameter.default])) } })
|
|
3289
|
+
},
|
|
3204
3290
|
setRecipeParameter: (id, value) => setIn('forms', 'recipeParameters', { ...data.forms.recipeParameters, [id]: value }),
|
|
3205
3291
|
setCompareMode: (compareMode) => set({ compareMode }),
|
|
3206
3292
|
pickCompare: (side, revision) => set(side === 'left'
|
|
3207
3293
|
? { compareLeft: revision, diff: null, diffError: null }
|
|
3208
3294
|
: { compareRight: revision, diff: null, diffError: null }),
|
|
3209
|
-
/**
|
|
3210
|
-
compareFrom: (revision) =>
|
|
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
|
+
},
|
|
3211
3304
|
|
|
3212
3305
|
updateBrief: (field, value, referenceId) => {
|
|
3213
3306
|
const projectId = data.activeProjectId, current = data.briefDrafts[projectId]
|
|
@@ -3507,14 +3600,32 @@ window.__ModuleLoader__.load({
|
|
|
3507
3600
|
},
|
|
3508
3601
|
|
|
3509
3602
|
diff: async (from, to) => {
|
|
3510
|
-
|
|
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 })
|
|
3511
3614
|
try {
|
|
3512
|
-
const response = await fetchImpl(`${projectRoute(
|
|
3615
|
+
const response = await fetchImpl(`${projectRoute(projectId, '/diff')}?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`, { headers: { accept: 'application/json' } })
|
|
3513
3616
|
const parsed = await response.json()
|
|
3514
|
-
if (
|
|
3515
|
-
|
|
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') } })
|
|
3516
3627
|
} catch (error) {
|
|
3517
|
-
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) } })
|
|
3518
3629
|
}
|
|
3519
3630
|
},
|
|
3520
3631
|
|
|
@@ -3557,10 +3668,14 @@ window.__ModuleLoader__.load({
|
|
|
3557
3668
|
const reading = readCreationDrafts(draftStorage, creationDraftRoot), source = reading.entries.find(entry => entry.id === id)
|
|
3558
3669
|
if (!source?.draft) { set({ creationDraftStorageStatus: 'invalid' }); return }
|
|
3559
3670
|
const saved = source.draft, ref = saved.forms.recipe
|
|
3671
|
+
if ((saved.recipeChoices || []).some(choice => {
|
|
3672
|
+
const available = data.recipeCatalog.recipes.find(recipe => recipeChoiceIdentity(recipe) === recipeChoiceIdentity(choice.recipe))
|
|
3673
|
+
return available && !recipeChoiceFits(available, choice.parameters)
|
|
3674
|
+
})) { set({ creationDraftStorageStatus: 'invalid' }); return }
|
|
3560
3675
|
const recipe = ref ? data.recipeCatalog.recipes.find(recipe => recipe.id === ref.id && recipe.version === ref.version && recipe.digest === ref.digest) : null
|
|
3561
3676
|
creationDraftId = newCreationKey(); restoredCreationSource = { id, raw: source.raw }; persistedCreationSignature = null
|
|
3562
3677
|
creationAttempt = saved.attempt ? editorClone(saved.attempt) : null
|
|
3563
|
-
set({ view: 'projects', forms: { ...data.forms, ...editorClone(saved.forms), recipe: recipe || null },
|
|
3678
|
+
set({ view: 'projects', recipeChoices: editorClone(saved.recipeChoices || []), forms: { ...data.forms, ...editorClone(saved.forms), recipe: recipe || null },
|
|
3564
3679
|
creationDraftMissingRecipe: ref && !recipe ? ref : null,
|
|
3565
3680
|
creationWork: creationAttempt ? { status: 'error', title: creationAttempt.body.title, code: 'UI_FETCH_FAILED' } : null,
|
|
3566
3681
|
notices: { ...data.notices, projects: { kind: 'creation', ok: true, message: t('projects.draftRestored') } } })
|
|
@@ -3584,7 +3699,7 @@ window.__ModuleLoader__.load({
|
|
|
3584
3699
|
clearCreationDraft: () => {
|
|
3585
3700
|
if (data.busy.create) return
|
|
3586
3701
|
creationAttempt = null; restoredCreationSource = null; persistedCreationSignature = null
|
|
3587
|
-
set({ forms: { ...data.forms, title: '', goal: '', recipe: null, recipeParameters: {} }, creationWork: null,
|
|
3702
|
+
set({ recipeChoices: [], forms: { ...data.forms, title: '', goal: '', recipe: null, recipeParameters: {} }, creationWork: null,
|
|
3588
3703
|
creationDraftMissingRecipe: null, notices: { ...data.notices, projects: null } })
|
|
3589
3704
|
},
|
|
3590
3705
|
|
|
@@ -3737,6 +3852,7 @@ window.__ModuleLoader__.load({
|
|
|
3737
3852
|
void load()
|
|
3738
3853
|
},
|
|
3739
3854
|
stop() {
|
|
3855
|
+
diffSequence += 1
|
|
3740
3856
|
closeCreationDraft()
|
|
3741
3857
|
draftLifecycle?.removeEventListener?.('pagehide', closeCreationDraft)
|
|
3742
3858
|
draftLifecycle?.removeEventListener?.('pageshow', reopenCreationDraft)
|
|
@@ -3772,7 +3888,7 @@ window.__ModuleLoader__.load({
|
|
|
3772
3888
|
function CreationDraftsView({ state, actions }) {
|
|
3773
3889
|
const messages = { saved: t('projects.draftSaved'), unavailable: t('projects.draftUnavailable'), full: t('projects.draftFull'),
|
|
3774
3890
|
tooLarge: t('projects.draftTooLarge'), invalid: t('projects.draftInvalid') }
|
|
3775
|
-
const hasInputs = Boolean(state.forms.title || state.forms.goal || state.forms.recipe || state.creationWork || state.creationDraftMissingRecipe || Object.keys(state.forms.recipeParameters).length)
|
|
3891
|
+
const hasInputs = Boolean(state.forms.title || state.forms.goal || state.forms.recipe || state.creationWork || state.creationDraftMissingRecipe || state.recipeChoices.length || Object.keys(state.forms.recipeParameters).length)
|
|
3776
3892
|
return el('div', { 'data-creation-drafts': true },
|
|
3777
3893
|
el('p', { className: 'db-muted' }, t('projects.draftHint')),
|
|
3778
3894
|
(hasInputs || state.creationDraftStorageStatus !== 'saved') && messages[state.creationDraftStorageStatus] ? el('p', {
|
|
@@ -3942,7 +4058,7 @@ window.__ModuleLoader__.load({
|
|
|
3942
4058
|
})),
|
|
3943
4059
|
selected ? el('fieldset', { className: 'db-recipe-parameters' },
|
|
3944
4060
|
el('legend', null, t('recipes.parameters')),
|
|
3945
|
-
Button({ action: 'reset-recipe', onClick:
|
|
4061
|
+
Button({ action: 'reset-recipe', onClick: actions.resetRecipe, children: t('recipes.reset') }),
|
|
3946
4062
|
el('div', { className: 'db-inline', style: { flexWrap: 'wrap' } }, selected.parameters.map(parameter =>
|
|
3947
4063
|
el('label', { key: parameter.id, style: { display: 'flex', flexDirection: 'column', gap: '5px' } },
|
|
3948
4064
|
parameter.title,
|
|
@@ -4763,9 +4879,7 @@ window.__ModuleLoader__.load({
|
|
|
4763
4879
|
const previews = state.previews
|
|
4764
4880
|
if (previews === null) return el('div', { 'data-view': 'preview', className: 'db-muted' }, t('common.noProject'))
|
|
4765
4881
|
const revisions = previews.revisions
|
|
4766
|
-
const
|
|
4767
|
-
const left = pick(state.compareLeft || state.currentRevision)
|
|
4768
|
-
const right = pick(state.compareRight || state.currentRevision)
|
|
4882
|
+
const { left, right } = previewRevisionPair(state)
|
|
4769
4883
|
const entryOf = id => revisions.find(entry => entry.revision === id) || null
|
|
4770
4884
|
|
|
4771
4885
|
/** One image (or the reason there is none), with its digest and its time. */
|
|
@@ -5112,6 +5226,7 @@ window.__ModuleLoader__.load({
|
|
|
5112
5226
|
ErrorBox({ error: state.error }),
|
|
5113
5227
|
el('div', { className: 'db-card' },
|
|
5114
5228
|
el('h4', null, t('revisions.title', { count: revisions.length })),
|
|
5229
|
+
el('p', { className: 'db-muted' }, t('revisions.compareHint')),
|
|
5115
5230
|
el('ul', { className: 'db-list' }, revisions.map(entry => el('li', { key: entry.revision, 'data-revision': entry.revision },
|
|
5116
5231
|
el('div', { className: 'db-inline' },
|
|
5117
5232
|
el('span', { className: 'db-mono' }, entry.revision),
|
|
@@ -5119,7 +5234,8 @@ window.__ModuleLoader__.load({
|
|
|
5119
5234
|
el('span', { className: 'db-muted' }, entry.kind || '—'),
|
|
5120
5235
|
el('span', { className: 'db-muted' }, formatTime(entry.createdAt)),
|
|
5121
5236
|
el('span', { className: 'db-muted db-mono' }, `digest ${shortDigest(entry.digest)}`),
|
|
5122
|
-
Button({ action: `compare-from:${entry.revision}`,
|
|
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') }),
|
|
5123
5239
|
entry.isCurrent ? null : Button({ action: `restore:${entry.revision}`, disabled: state.busy.restore, onClick: () => actions.restoreRevision(entry.revision), children: t('revisions.restore') }),
|
|
5124
5240
|
),
|
|
5125
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.
|
|
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.
|
|
22
|
+
"@deepblend/dsh-blender-contracts": "0.3.8"
|
|
23
23
|
},
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"repository": {
|