@deepblend/dsh-blender-ui 0.3.6 → 0.3.7
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 +78 -16
- 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': '选中的配方已更新或不可用,请重新选择。',
|
|
@@ -1043,7 +1043,7 @@ window.__ModuleLoader__.load({
|
|
|
1043
1043
|
'recipes.select': 'Use this recipe',
|
|
1044
1044
|
'recipes.selected': 'Selected',
|
|
1045
1045
|
'recipes.parameters': 'Customize recipe',
|
|
1046
|
-
'recipes.previewNote': 'Images show default parameters. Creating a project renders your settings; time depends on your device.',
|
|
1046
|
+
'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
1047
|
'recipes.createPreview': 'Create and preview',
|
|
1048
1048
|
'recipes.creatingPreview': 'Creating and rendering…',
|
|
1049
1049
|
'recipes.stale': 'The selected recipe changed or is unavailable. Please select it again.',
|
|
@@ -1399,6 +1399,34 @@ window.__ModuleLoader__.load({
|
|
|
1399
1399
|
const CREATION_DRAFT_BYTES = 262144
|
|
1400
1400
|
const creationDraftPrefix = root => `${CREATION_DRAFT_VERSION}:${encodeURIComponent(root)}:`
|
|
1401
1401
|
const creationRecipeReference = recipe => recipe ? { id: recipe.id, version: recipe.version, digest: recipe.digest } : null
|
|
1402
|
+
const recipeChoiceIdentity = recipe => creationSignature(creationRecipeReference(recipe))
|
|
1403
|
+
const copyRecipeParameters = values => Object.fromEntries(Object.entries(values).map(([key, value]) => [key, Array.isArray(value) ? [...value] : value]))
|
|
1404
|
+
const rememberRecipeChoice = (choices, recipe, parameters) => !recipe ? choices : [
|
|
1405
|
+
...choices.filter(choice => recipeChoiceIdentity(choice.recipe) !== recipeChoiceIdentity(recipe)),
|
|
1406
|
+
{ recipe: creationRecipeReference(recipe), parameters: copyRecipeParameters(parameters) },
|
|
1407
|
+
]
|
|
1408
|
+
function validRecipeChoices(choices) {
|
|
1409
|
+
if (choices === undefined) return true // Existing 0.3.6 browser drafts contain only their selected recipe.
|
|
1410
|
+
if (!Array.isArray(choices)) return false
|
|
1411
|
+
const seen = new Set()
|
|
1412
|
+
return choices.every(choice => {
|
|
1413
|
+
if (!choice || !choice.recipe || !validCreationRecipeReference(choice.recipe)
|
|
1414
|
+
|| Object.keys(choice).some(key => !['recipe', 'parameters'].includes(key))
|
|
1415
|
+
|| !choice.parameters || typeof choice.parameters !== 'object' || Array.isArray(choice.parameters)) return false
|
|
1416
|
+
const key = recipeChoiceIdentity(choice.recipe)
|
|
1417
|
+
if (seen.has(key)) return false
|
|
1418
|
+
seen.add(key)
|
|
1419
|
+
return Object.values(choice.parameters).every(value => value === '' || Number.isFinite(value)
|
|
1420
|
+
|| Array.isArray(value) && value.length === 3 && value.every(v => Number.isFinite(v) && v >= 0 && v <= 1))
|
|
1421
|
+
})
|
|
1422
|
+
}
|
|
1423
|
+
function recipeChoiceFits(recipe, parameters) {
|
|
1424
|
+
return Object.keys(parameters).length === recipe.parameters.length && recipe.parameters.every(parameter => {
|
|
1425
|
+
const value = parameters[parameter.id]
|
|
1426
|
+
return parameter.type === 'color' ? Array.isArray(value) && value.length === 3 && value.every(v => Number.isFinite(v) && v >= 0 && v <= 1)
|
|
1427
|
+
: value === '' || Number.isFinite(value)
|
|
1428
|
+
})
|
|
1429
|
+
}
|
|
1402
1430
|
function creationDraftJson(value) {
|
|
1403
1431
|
if (value === null || typeof value === 'string' || typeof value === 'boolean') return
|
|
1404
1432
|
if (typeof value === 'number' && Number.isFinite(value)) return
|
|
@@ -1429,7 +1457,8 @@ window.__ModuleLoader__.load({
|
|
|
1429
1457
|
|| !Number.isFinite(Date.parse(draft.savedAt)) || !forms || typeof forms.title !== 'string'
|
|
1430
1458
|
|| typeof forms.goal !== 'string' || !validCreationRecipeReference(forms.recipe)
|
|
1431
1459
|
|| Object.keys(forms).some(key => !['title', 'goal', 'recipe', 'recipeParameters'].includes(key))
|
|
1432
|
-
|| !forms.recipeParameters || typeof forms.recipeParameters !== 'object' || Array.isArray(forms.recipeParameters)
|
|
1460
|
+
|| !forms.recipeParameters || typeof forms.recipeParameters !== 'object' || Array.isArray(forms.recipeParameters)
|
|
1461
|
+
|| !validRecipeChoices(draft.recipeChoices)) return null
|
|
1433
1462
|
const attempt = draft.attempt
|
|
1434
1463
|
if (attempt !== null) {
|
|
1435
1464
|
const body = attempt?.body
|
|
@@ -2607,6 +2636,7 @@ window.__ModuleLoader__.load({
|
|
|
2607
2636
|
editorLastEdits: {},
|
|
2608
2637
|
editorComparison: null,
|
|
2609
2638
|
patchDrafts: {},
|
|
2639
|
+
recipeChoices: [],
|
|
2610
2640
|
forms: { title: '', goal: '', recipe: null, recipeParameters: {}, patch: null, frameStart: '', frameEnd: '', profile: 'preview' },
|
|
2611
2641
|
busy: { create: false, patch: false, editor: false, render: false, restore: false },
|
|
2612
2642
|
notices: { projects: null, scene: null, preview: null, jobs: null, revisions: null },
|
|
@@ -2693,6 +2723,8 @@ window.__ModuleLoader__.load({
|
|
|
2693
2723
|
const assetWork = (projectId, changes) => set({ assetWork: { ...data.assetWork, [projectId]: { ...data.assetWork[projectId], ...changes } } })
|
|
2694
2724
|
|
|
2695
2725
|
const notify = () => {
|
|
2726
|
+
if (!creationDraftClosed) data.recipeChoices = rememberRecipeChoice(data.recipeChoices,
|
|
2727
|
+
data.forms.recipe || data.creationDraftMissingRecipe, data.forms.recipeParameters)
|
|
2696
2728
|
persistCreationDraft()
|
|
2697
2729
|
const signature = JSON.stringify([data.activeProjectId, data.selected?.scene?.revision, data.view, ...FILE_FIELDS.map(field => fileChoiceAllowed(data, field))])
|
|
2698
2730
|
if (signature !== fileChoiceSignature) { data.fileChoiceGeneration += 1; fileChoiceSignature = signature }
|
|
@@ -2715,19 +2747,21 @@ window.__ModuleLoader__.load({
|
|
|
2715
2747
|
if (creationDraftClosed || creationDraftWriting || !creationDraftRoot || data.projectsRoot !== creationDraftRoot) return
|
|
2716
2748
|
creationDraftWriting = true
|
|
2717
2749
|
try {
|
|
2718
|
-
const forms = { ...creationDraftForms(data.forms), recipe: creationRecipeReference(data.forms.recipe) || data.creationDraftMissingRecipe }
|
|
2750
|
+
const forms = { ...creationDraftForms(data.forms), recipe: creationRecipeReference(data.forms.recipe) || data.creationDraftMissingRecipe }
|
|
2751
|
+
creationDraftJson(data.recipeChoices)
|
|
2752
|
+
const signature = JSON.stringify([forms, creationAttempt, data.recipeChoices])
|
|
2719
2753
|
if (signature === persistedCreationSignature) return
|
|
2720
2754
|
const reading = refreshCreationDrafts()
|
|
2721
2755
|
if (!reading.available) { data.creationDraftStorageStatus = 'unavailable'; return }
|
|
2722
2756
|
const key = creationDraftPrefix(creationDraftRoot) + creationDraftId
|
|
2723
|
-
if (!forms.title && !forms.goal && !forms.recipe && !Object.keys(forms.recipeParameters).length && !creationAttempt) {
|
|
2757
|
+
if (!forms.title && !forms.goal && !forms.recipe && !Object.keys(forms.recipeParameters).length && !creationAttempt && !data.recipeChoices.length) {
|
|
2724
2758
|
draftStorage.removeItem(key); data.creationDraftStorageStatus = null
|
|
2725
2759
|
} else {
|
|
2726
2760
|
if (!reading.entries.some(entry => entry.id === creationDraftId) && reading.entries.length >= 20) {
|
|
2727
2761
|
data.creationDraftStorageStatus = 'full'; return
|
|
2728
2762
|
}
|
|
2729
2763
|
const raw = JSON.stringify({ schemaVersion: CREATION_DRAFT_VERSION, projectsRoot: creationDraftRoot,
|
|
2730
|
-
id: creationDraftId, ownerActive: true, savedAt: new Date().toISOString(), forms, attempt: creationAttempt })
|
|
2764
|
+
id: creationDraftId, ownerActive: true, savedAt: new Date().toISOString(), forms, attempt: creationAttempt, recipeChoices: data.recipeChoices })
|
|
2731
2765
|
if (new Blob([raw]).size > CREATION_DRAFT_BYTES) { data.creationDraftStorageStatus = 'tooLarge'; return }
|
|
2732
2766
|
draftStorage.setItem(key, raw)
|
|
2733
2767
|
if (draftStorage.getItem(key) !== raw) throw new Error('Draft write did not persist')
|
|
@@ -2832,7 +2866,7 @@ window.__ModuleLoader__.load({
|
|
|
2832
2866
|
// Switching stores cannot carry an old store's request identity into a new one.
|
|
2833
2867
|
if (creationDraftRoot !== null) {
|
|
2834
2868
|
creationAttempt = null; restoredCreationSource = null; creationDraftId = newCreationKey()
|
|
2835
|
-
patch.creationWork = null; patch.creationDraftMissingRecipe = null
|
|
2869
|
+
patch.creationWork = null; patch.creationDraftMissingRecipe = null; patch.recipeChoices = []
|
|
2836
2870
|
patch.forms = { ...data.forms, title: '', goal: '', recipe: null, recipeParameters: {} }
|
|
2837
2871
|
}
|
|
2838
2872
|
creationDraftRoot = typeof patch.projectsRoot === 'string' && patch.projectsRoot ? patch.projectsRoot : null
|
|
@@ -2873,6 +2907,9 @@ window.__ModuleLoader__.load({
|
|
|
2873
2907
|
if (attemptRoot !== creationDraftRoot) { reload(); return }
|
|
2874
2908
|
if (outcome.ok) {
|
|
2875
2909
|
const sameInputs = creationSignature(creationBody()) === attempt.signature
|
|
2910
|
+
if (attempt.body.recipe) data.recipeChoices = data.recipeChoices.filter(choice =>
|
|
2911
|
+
recipeChoiceIdentity(choice.recipe) !== recipeChoiceIdentity(attempt.body.recipe)
|
|
2912
|
+
|| creationSignature(choice.parameters) !== creationSignature(attempt.body.recipe.parameters))
|
|
2876
2913
|
set({ ...(sameInputs ? { forms: { ...data.forms, title: '', goal: '', recipe: null, recipeParameters: {} } } : {}),
|
|
2877
2914
|
creationWork: null, projectId: outcome.payload.project.projectId,
|
|
2878
2915
|
...(attempt.body.renderPreview ? { view: 'preview', compareMode: 'result' } : {}) })
|
|
@@ -2882,7 +2919,17 @@ window.__ModuleLoader__.load({
|
|
|
2882
2919
|
const sourceKey = creationDraftPrefix(creationDraftRoot) + restoredCreationSource.id
|
|
2883
2920
|
const source = parseCreationDraft(restoredCreationSource.raw, creationDraftRoot, restoredCreationSource.id)
|
|
2884
2921
|
if (source && creationSignature(creationRequestBody(source.forms)) === attempt.signature
|
|
2885
|
-
&& draftStorage?.getItem(sourceKey) === restoredCreationSource.raw)
|
|
2922
|
+
&& draftStorage?.getItem(sourceKey) === restoredCreationSource.raw) {
|
|
2923
|
+
const remaining = (source.recipeChoices || []).filter(choice => !attempt.body.recipe
|
|
2924
|
+
|| recipeChoiceIdentity(choice.recipe) !== recipeChoiceIdentity(attempt.body.recipe)
|
|
2925
|
+
|| creationSignature(choice.parameters) !== creationSignature(attempt.body.recipe.parameters))
|
|
2926
|
+
if (remaining.length) {
|
|
2927
|
+
const raw = JSON.stringify({ ...source, savedAt: new Date().toISOString(), attempt: null, recipeChoices: remaining,
|
|
2928
|
+
forms: { title: '', goal: '', recipe: null, recipeParameters: {} } })
|
|
2929
|
+
draftStorage.setItem(sourceKey, raw)
|
|
2930
|
+
if (draftStorage.getItem(sourceKey) !== raw) throw new Error('Draft write did not persist')
|
|
2931
|
+
} else draftStorage.removeItem(sourceKey)
|
|
2932
|
+
}
|
|
2886
2933
|
} catch { data.creationDraftStorageStatus = 'unavailable' }
|
|
2887
2934
|
}
|
|
2888
2935
|
restoredCreationSource = null
|
|
@@ -3197,10 +3244,21 @@ window.__ModuleLoader__.load({
|
|
|
3197
3244
|
} else assetWork(projectId, { error: `${outcome.error.code}: ${outcome.error.message}` })
|
|
3198
3245
|
reload()
|
|
3199
3246
|
},
|
|
3200
|
-
selectRecipe: recipe =>
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3247
|
+
selectRecipe: recipe => {
|
|
3248
|
+
if (recipe && data.forms.recipe && recipeChoiceIdentity(recipe) === recipeChoiceIdentity(data.forms.recipe)) return
|
|
3249
|
+
const saved = recipe ? data.recipeChoices.find(choice => recipeChoiceIdentity(choice.recipe) === recipeChoiceIdentity(recipe)) : null
|
|
3250
|
+
if (saved && !recipeChoiceFits(recipe, saved.parameters)) { set({ creationDraftStorageStatus: 'invalid' }); return }
|
|
3251
|
+
set({ creationDraftMissingRecipe: null, forms: { ...data.forms, recipe,
|
|
3252
|
+
recipeParameters: recipe ? saved ? copyRecipeParameters(saved.parameters)
|
|
3253
|
+
: Object.fromEntries(recipe.parameters.map(parameter => [parameter.id, Array.isArray(parameter.default) ? [...parameter.default] : parameter.default])) : {},
|
|
3254
|
+
title: !data.forms.title || data.forms.title === data.forms.recipe?.title ? recipe?.title || '' : data.forms.title,
|
|
3255
|
+
} })
|
|
3256
|
+
},
|
|
3257
|
+
resetRecipe: () => {
|
|
3258
|
+
const recipe = data.forms.recipe
|
|
3259
|
+
if (recipe) set({ forms: { ...data.forms, recipeParameters: Object.fromEntries(recipe.parameters.map(parameter =>
|
|
3260
|
+
[parameter.id, Array.isArray(parameter.default) ? [...parameter.default] : parameter.default])) } })
|
|
3261
|
+
},
|
|
3204
3262
|
setRecipeParameter: (id, value) => setIn('forms', 'recipeParameters', { ...data.forms.recipeParameters, [id]: value }),
|
|
3205
3263
|
setCompareMode: (compareMode) => set({ compareMode }),
|
|
3206
3264
|
pickCompare: (side, revision) => set(side === 'left'
|
|
@@ -3557,10 +3615,14 @@ window.__ModuleLoader__.load({
|
|
|
3557
3615
|
const reading = readCreationDrafts(draftStorage, creationDraftRoot), source = reading.entries.find(entry => entry.id === id)
|
|
3558
3616
|
if (!source?.draft) { set({ creationDraftStorageStatus: 'invalid' }); return }
|
|
3559
3617
|
const saved = source.draft, ref = saved.forms.recipe
|
|
3618
|
+
if ((saved.recipeChoices || []).some(choice => {
|
|
3619
|
+
const available = data.recipeCatalog.recipes.find(recipe => recipeChoiceIdentity(recipe) === recipeChoiceIdentity(choice.recipe))
|
|
3620
|
+
return available && !recipeChoiceFits(available, choice.parameters)
|
|
3621
|
+
})) { set({ creationDraftStorageStatus: 'invalid' }); return }
|
|
3560
3622
|
const recipe = ref ? data.recipeCatalog.recipes.find(recipe => recipe.id === ref.id && recipe.version === ref.version && recipe.digest === ref.digest) : null
|
|
3561
3623
|
creationDraftId = newCreationKey(); restoredCreationSource = { id, raw: source.raw }; persistedCreationSignature = null
|
|
3562
3624
|
creationAttempt = saved.attempt ? editorClone(saved.attempt) : null
|
|
3563
|
-
set({ view: 'projects', forms: { ...data.forms, ...editorClone(saved.forms), recipe: recipe || null },
|
|
3625
|
+
set({ view: 'projects', recipeChoices: editorClone(saved.recipeChoices || []), forms: { ...data.forms, ...editorClone(saved.forms), recipe: recipe || null },
|
|
3564
3626
|
creationDraftMissingRecipe: ref && !recipe ? ref : null,
|
|
3565
3627
|
creationWork: creationAttempt ? { status: 'error', title: creationAttempt.body.title, code: 'UI_FETCH_FAILED' } : null,
|
|
3566
3628
|
notices: { ...data.notices, projects: { kind: 'creation', ok: true, message: t('projects.draftRestored') } } })
|
|
@@ -3584,7 +3646,7 @@ window.__ModuleLoader__.load({
|
|
|
3584
3646
|
clearCreationDraft: () => {
|
|
3585
3647
|
if (data.busy.create) return
|
|
3586
3648
|
creationAttempt = null; restoredCreationSource = null; persistedCreationSignature = null
|
|
3587
|
-
set({ forms: { ...data.forms, title: '', goal: '', recipe: null, recipeParameters: {} }, creationWork: null,
|
|
3649
|
+
set({ recipeChoices: [], forms: { ...data.forms, title: '', goal: '', recipe: null, recipeParameters: {} }, creationWork: null,
|
|
3588
3650
|
creationDraftMissingRecipe: null, notices: { ...data.notices, projects: null } })
|
|
3589
3651
|
},
|
|
3590
3652
|
|
|
@@ -3772,7 +3834,7 @@ window.__ModuleLoader__.load({
|
|
|
3772
3834
|
function CreationDraftsView({ state, actions }) {
|
|
3773
3835
|
const messages = { saved: t('projects.draftSaved'), unavailable: t('projects.draftUnavailable'), full: t('projects.draftFull'),
|
|
3774
3836
|
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)
|
|
3837
|
+
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
3838
|
return el('div', { 'data-creation-drafts': true },
|
|
3777
3839
|
el('p', { className: 'db-muted' }, t('projects.draftHint')),
|
|
3778
3840
|
(hasInputs || state.creationDraftStorageStatus !== 'saved') && messages[state.creationDraftStorageStatus] ? el('p', {
|
|
@@ -3942,7 +4004,7 @@ window.__ModuleLoader__.load({
|
|
|
3942
4004
|
})),
|
|
3943
4005
|
selected ? el('fieldset', { className: 'db-recipe-parameters' },
|
|
3944
4006
|
el('legend', null, t('recipes.parameters')),
|
|
3945
|
-
Button({ action: 'reset-recipe', onClick:
|
|
4007
|
+
Button({ action: 'reset-recipe', onClick: actions.resetRecipe, children: t('recipes.reset') }),
|
|
3946
4008
|
el('div', { className: 'db-inline', style: { flexWrap: 'wrap' } }, selected.parameters.map(parameter =>
|
|
3947
4009
|
el('label', { key: parameter.id, style: { display: 'flex', flexDirection: 'column', gap: '5px' } },
|
|
3948
4010
|
parameter.title,
|
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.7",
|
|
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.7"
|
|
23
23
|
},
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"repository": {
|