@deepblend/dsh-blender-ui 0.3.8 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +243 -20
- package/lib/index.js +2 -0
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -333,6 +333,24 @@ window.__ModuleLoader__.load({
|
|
|
333
333
|
"photo.cameraChanges": "相机",
|
|
334
334
|
"photo.lightChanges": "灯光",
|
|
335
335
|
"photo.restoreFailed": "恢复失败,已保留摄影记录:{message}",
|
|
336
|
+
"photo.reuseUnavailable": "无法读取完整摄影设置,请重新选择来源。",
|
|
337
|
+
"photo.reuseTitle": "复用已保存作品的摄影设置",
|
|
338
|
+
"photo.reuseHelp": "原坐标以米复用,不自动缩放或取景。替换本作品全部灯光和所选相机的可编辑设置;模型、材质、世界、动画、其他相机与交付设置保留。套用后检查构图,再明确保存。",
|
|
339
|
+
"photo.reuseProject": "来源作品",
|
|
340
|
+
"photo.reuseRevision": "来源保存版本",
|
|
341
|
+
"photo.reuseCamera": "来源相机",
|
|
342
|
+
"photo.reuseTarget": "瞄准目标作品中的对象",
|
|
343
|
+
"photo.reuseReading": "正在读取已保存摄影设置…",
|
|
344
|
+
"photo.reuseSource": "来源:{project} · {revision} · {digest} · {count} 盏灯",
|
|
345
|
+
"photo.reuseReplace": "替换当前未提交的摄影草稿(其他编辑保留)",
|
|
346
|
+
"photo.reuseStale": "目标草稿来源已变化,请重新读取摄影设置。",
|
|
347
|
+
"photo.reuseApplied": "已套用到草稿,尚未保存。请检查构图与灯光。",
|
|
348
|
+
"photo.reuseSame": "当前摄影设置已与来源一致,无需再次保存。",
|
|
349
|
+
"photo.reuseReload": "重新读取来源",
|
|
350
|
+
"photo.reuseApply": "套用到摄影草稿",
|
|
351
|
+
"photo.reuseUpgrade": "更新插件并重启 DSH 后即可复用摄影设置。",
|
|
352
|
+
"photo.reuseNote": "摄影设置从 {project} · {revision} · {digest} · 相机 {camera} 开始复用;目标对象 {target}。保存前可继续调整草稿。",
|
|
353
|
+
"photo.reuseTargetRequired": "请选择目标作品中的瞄准对象。",
|
|
336
354
|
'editor.title': '编辑对象',
|
|
337
355
|
'editor.unknownSettings': '这张预览缺少实测渲染设置,无法比较编辑差异。',
|
|
338
356
|
'editor.missingBaseline': '没有同相机、同帧和同渲染设置的编辑前预览,暂时无法比较差异。',
|
|
@@ -542,6 +560,9 @@ window.__ModuleLoader__.load({
|
|
|
542
560
|
'revisions.currentSuffix': ' (当前)',
|
|
543
561
|
'revisions.missing': '没有这个 revision',
|
|
544
562
|
'preview.noneForRevision': '这个 revision 还没有预览图(渲染一次预览即可)。',
|
|
563
|
+
'preview.reading': '正在读取所选项目的预览…',
|
|
564
|
+
'preview.readFailed': '未能读取这个项目的预览。刷新后重试;保存版本和未提交修改仍保留。',
|
|
565
|
+
'preview.sourceMismatch': '预览回执与所选项目不一致。刷新后重试。',
|
|
545
566
|
'preview.noPrevious': '还没有上一次渲染:再点一次「渲染预览」,或者先在某个更早的 revision 上渲一次。',
|
|
546
567
|
'preview.notRenderedHere': '这个 revision 还没有由面板渲过预览(上方的「渲染预览」会生成第一张)。',
|
|
547
568
|
'preview.rendering': '渲染中…',
|
|
@@ -900,6 +921,24 @@ window.__ModuleLoader__.load({
|
|
|
900
921
|
"photo.cameraChanges": "cameras",
|
|
901
922
|
"photo.lightChanges": "lighting",
|
|
902
923
|
"photo.restoreFailed": "Restore failed; the photography record is retained: {message}",
|
|
924
|
+
"photo.reuseUnavailable": "Complete photography settings are unavailable. Select the source again.",
|
|
925
|
+
"photo.reuseTitle": "Reuse photography from saved work",
|
|
926
|
+
"photo.reuseHelp": "Reuse absolute coordinates in metres, without automatic scaling or framing. Replace all lights and the selected camera’s editable settings. Keep models, materials, world, animation, other cameras and delivery settings. Check the framing before explicitly saving.",
|
|
927
|
+
"photo.reuseProject": "Source work",
|
|
928
|
+
"photo.reuseRevision": "Saved source version",
|
|
929
|
+
"photo.reuseCamera": "Source camera",
|
|
930
|
+
"photo.reuseTarget": "Aim at an object in the destination work",
|
|
931
|
+
"photo.reuseReading": "Reading saved photography settings…",
|
|
932
|
+
"photo.reuseSource": "Source: {project} · {revision} · {digest} · {count} lights",
|
|
933
|
+
"photo.reuseReplace": "Replace the unsaved photography draft (keep other edits)",
|
|
934
|
+
"photo.reuseStale": "The destination draft source changed. Read the photography settings again.",
|
|
935
|
+
"photo.reuseApplied": "Applied to the draft; not saved yet. Check the framing and lighting.",
|
|
936
|
+
"photo.reuseSame": "The current photography matches the source; no new save is needed.",
|
|
937
|
+
"photo.reuseReload": "Read source again",
|
|
938
|
+
"photo.reuseApply": "Apply to photography draft",
|
|
939
|
+
"photo.reuseUpgrade": "Update the plugin and restart DSH to reuse photography settings.",
|
|
940
|
+
"photo.reuseNote": "Photography started from {project} · {revision} · {digest} · camera {camera}; destination aim object {target}. The draft may be adjusted before saving.",
|
|
941
|
+
"photo.reuseTargetRequired": "Choose an aim target in the destination work.",
|
|
903
942
|
'editor.title': 'Edit object',
|
|
904
943
|
'editor.unknownSettings': 'Measured render settings are missing. This preview cannot establish the edit difference.',
|
|
905
944
|
'editor.missingBaseline': 'No before preview has the same camera, frame and measured render settings. The edit difference is unavailable.',
|
|
@@ -1109,6 +1148,9 @@ window.__ModuleLoader__.load({
|
|
|
1109
1148
|
'revisions.currentSuffix': ' (current)',
|
|
1110
1149
|
'revisions.missing': 'no such revision',
|
|
1111
1150
|
'preview.noneForRevision': 'This revision has no preview image yet (render a preview and it will).',
|
|
1151
|
+
'preview.reading': 'Reading previews for the selected project…',
|
|
1152
|
+
'preview.readFailed': 'Could not read this project’s previews. Refresh to retry; saved versions and unsaved edits are kept.',
|
|
1153
|
+
'preview.sourceMismatch': 'The preview response does not match the selected project. Refresh to retry.',
|
|
1112
1154
|
'preview.noPrevious': 'No previous render yet: press “Render preview” again, or render once on an earlier revision.',
|
|
1113
1155
|
'preview.notRenderedHere': 'This revision has no preview rendered from this panel yet (the “Render preview” button above makes the first one).',
|
|
1114
1156
|
'preview.rendering': 'Rendering…',
|
|
@@ -2142,10 +2184,10 @@ window.__ModuleLoader__.load({
|
|
|
2142
2184
|
return state.editorDrafts?.[editorKey(state.activeProjectId, state.editorEntityId)] || null
|
|
2143
2185
|
}
|
|
2144
2186
|
|
|
2145
|
-
function createPhotographyDraft(scene, projectId, previous = {}) {
|
|
2187
|
+
function createPhotographyDraft(scene, projectId, previous = {}, projectsRoot = null) {
|
|
2146
2188
|
const cameras = (scene.nodes.cameras || []).map(item => editorClone(item.definition)).filter(Boolean)
|
|
2147
2189
|
const lights = (scene.nodes.lights || []).map(item => editorClone(item.definition)).filter(Boolean)
|
|
2148
|
-
return { projectId, baseRevision: scene.revision, cameras, lights, original: editorClone({ cameras, lights }),
|
|
2190
|
+
return { projectId, projectsRoot, baseRevision: scene.revision, cameras, lights, original: editorClone({ cameras, lights }),
|
|
2149
2191
|
entities: editorClone(scene.nodes.entities), tracks: editorClone(scene.nodes.animationTracks || []),
|
|
2150
2192
|
cameraId: cameras.some(item => item.id === previous.cameraId) ? previous.cameraId : scene.project.activeCamera || cameras[0]?.id || '',
|
|
2151
2193
|
lightId: lights.some(item => item.id === previous.lightId) ? previous.lightId : lights[0]?.id || '',
|
|
@@ -2155,6 +2197,8 @@ window.__ModuleLoader__.load({
|
|
|
2155
2197
|
const photographyDraftFor = state => state.photographyDrafts?.[state.activeProjectId] || null
|
|
2156
2198
|
const photographyBusy = work => Boolean(work?.saving || work?.previewing || work?.restoring)
|
|
2157
2199
|
const photographyAim = camera => camera?.targetEntityId !== undefined ? 'entity' : camera?.targetPoint !== undefined ? 'point' : 'free'
|
|
2200
|
+
const photographySameLights = (a, b) => creationSignature(Object.fromEntries(a.map(item => [item.id, item])))
|
|
2201
|
+
=== creationSignature(Object.fromEntries(b.map(item => [item.id, item])))
|
|
2158
2202
|
const photographyAimLocked = (draft, camera) => EDITOR_AXES.some(axis => editorTrackLocked(draft, 'camera', camera.id, `rotationEuler.${axis}`))
|
|
2159
2203
|
function photographyErrors(draft) {
|
|
2160
2204
|
const errors = [], bad = key => errors.push(key), number = (value, min, inclusive = true) => Number.isFinite(value) && (inclusive ? value >= min : value > min)
|
|
@@ -2175,14 +2219,14 @@ window.__ModuleLoader__.load({
|
|
|
2175
2219
|
if (item.targetPoint !== undefined && !vector(item.targetPoint)) bad(`${item.id}.targetPoint`)
|
|
2176
2220
|
if (item.targetEntityId !== undefined && item.targetPoint !== undefined) bad(`${item.id}.target`)
|
|
2177
2221
|
const targetChanged = !editorEqual([item.targetEntityId, item.targetPoint], [original?.targetEntityId, original?.targetPoint])
|
|
2178
|
-
if (targetChanged &&
|
|
2222
|
+
if (targetChanged && photographyAimLocked(draft, item)) bad(`${item.id}.target`)
|
|
2179
2223
|
} else {
|
|
2180
2224
|
if (!number(editorNumberDefault(item.energy, 100), 0)) bad(`${item.id}.energy`)
|
|
2181
2225
|
if (!Array.isArray(item.color ?? [1, 1, 1]) || ![3, 4].includes((item.color ?? [1, 1, 1]).length)
|
|
2182
2226
|
|| !(item.color ?? [1, 1, 1]).every(value => number(value, 0) && value <= 1)) bad(`${item.id}.color`)
|
|
2183
2227
|
if (item.type !== 'sun' && !number(editorNumberDefault(item.size, item.type === 'area' ? 1 : .25), 0, false)) bad(`${item.id}.size`)
|
|
2184
|
-
if (!original && item.type !== 'area') bad(`${item.id}.type`)
|
|
2185
|
-
if (
|
|
2228
|
+
if (!draft.reusedLights && ((!original && item.type !== 'area') || (original && item.type !== original.type))) bad(`${item.id}.type`)
|
|
2229
|
+
if (!['area', 'point', 'spot', 'sun'].includes(item.type)) bad(`${item.id}.type`)
|
|
2186
2230
|
}
|
|
2187
2231
|
}
|
|
2188
2232
|
if (!draft.cameras.some(camera => camera.id === draft.cameraId)) bad('camera')
|
|
@@ -2194,11 +2238,18 @@ window.__ModuleLoader__.load({
|
|
|
2194
2238
|
const errors = photographyErrors(draft)
|
|
2195
2239
|
if (errors.length) throw new Error(t('photo.invalid', { fields: errors.join(', ') }))
|
|
2196
2240
|
const operations = []
|
|
2241
|
+
const replaceLights = draft.reusedLights && !photographySameLights(draft.lights, draft.original.lights)
|
|
2197
2242
|
for (const [kind, items, fields] of [['camera', draft.cameras, ['lens', 'targetEntityId', 'targetPoint']], ['light', draft.lights, ['energy', 'color', 'size']]]) {
|
|
2243
|
+
if (kind === 'light' && replaceLights) {
|
|
2244
|
+
for (const light of draft.original.lights) operations.push({ op: 'light.remove', lightId: light.id })
|
|
2245
|
+
for (const light of items) operations.push({ op: 'light.add', light: editorClone(light) })
|
|
2246
|
+
continue
|
|
2247
|
+
}
|
|
2198
2248
|
for (const item of items) {
|
|
2199
2249
|
const original = draft.original[`${kind}s`].find(old => old.id === item.id)
|
|
2200
2250
|
if (!original) { operations.push({ op: 'light.add', light: editorClone(item) }); continue }
|
|
2201
2251
|
const changes = {}
|
|
2252
|
+
if (kind === 'camera' && photographyAim(item) === 'free' && photographyAim(original) !== 'free') changes.clearTarget = true
|
|
2202
2253
|
for (const key of fields) if (item[key] !== undefined && !editorEqual(item[key], original[key])) changes[key] = editorClone(item[key])
|
|
2203
2254
|
const transform = {}
|
|
2204
2255
|
for (const key of ['location', 'rotationEuler']) if (!editorEqual(item.transform?.[key], original.transform?.[key])) transform[key] = editorClone(item.transform[key])
|
|
@@ -2206,10 +2257,40 @@ window.__ModuleLoader__.load({
|
|
|
2206
2257
|
if (Object.keys(changes).length) operations.push({ op: `${kind}.update`, [`${kind}Id`]: item.id, ...changes })
|
|
2207
2258
|
}
|
|
2208
2259
|
}
|
|
2209
|
-
|
|
2260
|
+
const note = draft.reuseSource ? t('photo.reuseNote', draft.reuseSource) : null
|
|
2261
|
+
return { projectId: draft.projectId, baseRevision: draft.baseRevision, actor: 'ui', saveCheckpoint: true, renderPreview: false, operations, ...(note ? { note } : {}) }
|
|
2210
2262
|
}
|
|
2211
2263
|
function photographyDirty(draft) {
|
|
2212
|
-
return Boolean(draft &&
|
|
2264
|
+
return Boolean(draft && (creationSignature(draft.cameras) !== creationSignature(draft.original.cameras)
|
|
2265
|
+
|| !photographySameLights(draft.lights, draft.original.lights)))
|
|
2266
|
+
}
|
|
2267
|
+
function reusePhotography(draft, source, cameraId, targetEntityId) {
|
|
2268
|
+
const camera = source?.nodes?.cameras?.find(item => item.id === cameraId)?.definition
|
|
2269
|
+
const target = draft?.cameras?.find(item => item.id === draft.cameraId)
|
|
2270
|
+
if (!camera || !target || !Array.isArray(source.nodes.lights)) throw new Error(t('photo.reuseUnavailable'))
|
|
2271
|
+
const next = editorClone(draft), selected = next.cameras.find(item => item.id === next.cameraId)
|
|
2272
|
+
selected.lens = editorNumberDefault(camera.lens, 50)
|
|
2273
|
+
selected.transform ||= {}
|
|
2274
|
+
selected.transform.location = editorClone(camera.transform?.location || [0, 0, 0])
|
|
2275
|
+
delete selected.targetEntityId; delete selected.targetPoint
|
|
2276
|
+
if (photographyAim(camera) === 'entity') {
|
|
2277
|
+
if (!next.entities.some(item => item.id === targetEntityId)) throw new Error(t('photo.reuseTargetRequired'))
|
|
2278
|
+
selected.targetEntityId = targetEntityId
|
|
2279
|
+
} else if (photographyAim(camera) === 'point') selected.targetPoint = editorClone(camera.targetPoint)
|
|
2280
|
+
if (photographyAim(camera) === 'free') selected.transform.rotationEuler = editorClone(camera.transform?.rotationEuler || [0, 0, 0])
|
|
2281
|
+
else {
|
|
2282
|
+
const original = next.original.cameras.find(item => item.id === selected.id)
|
|
2283
|
+
if (original?.transform?.rotationEuler) selected.transform.rotationEuler = editorClone(original.transform.rotationEuler)
|
|
2284
|
+
else delete selected.transform.rotationEuler
|
|
2285
|
+
}
|
|
2286
|
+
next.lights = source.nodes.lights.map(item => editorClone(item.definition))
|
|
2287
|
+
if (next.lights.some(item => !item)) throw new Error(t('photo.reuseUnavailable'))
|
|
2288
|
+
next.lightId = next.lights.some(item => item.id === next.lightId) ? next.lightId : next.lights[0]?.id || ''
|
|
2289
|
+
next.reusedLights = true
|
|
2290
|
+
next.reuseSource = { project: source.project.id, revision: source.revision, digest: source.digest, camera: cameraId, target: targetEntityId || '—' }
|
|
2291
|
+
const errors = photographyErrors(next)
|
|
2292
|
+
if (errors.length) throw new Error(t('photo.invalid', { fields: errors.join(', ') }))
|
|
2293
|
+
return next
|
|
2213
2294
|
}
|
|
2214
2295
|
function inspectionReceiptValid(receipt, request, digest) {
|
|
2215
2296
|
const artifacts = receipt?.artifacts, view = request.views[0]
|
|
@@ -2522,7 +2603,7 @@ window.__ModuleLoader__.load({
|
|
|
2522
2603
|
if (typeof payload?.hostApiVersion === 'number' && payload.hostApiVersion < EXPECTED_HOST_API) {
|
|
2523
2604
|
return { status: 'stale', payload: null, error: staleHostError(expectedRoute, { payload, status: response.status }) }
|
|
2524
2605
|
}
|
|
2525
|
-
if (payload && payload.ok) return { status: 'ok', payload, error: null }
|
|
2606
|
+
if (payload && payload.ok && response.ok !== false) return { status: 'ok', payload, error: null }
|
|
2526
2607
|
return {
|
|
2527
2608
|
status: 'error',
|
|
2528
2609
|
payload: null,
|
|
@@ -2602,6 +2683,8 @@ window.__ModuleLoader__.load({
|
|
|
2602
2683
|
unfinishedJobs: [],
|
|
2603
2684
|
previews: null,
|
|
2604
2685
|
artifactBase: '',
|
|
2686
|
+
previewContext: null,
|
|
2687
|
+
previewReadStatus: 'idle',
|
|
2605
2688
|
view: 'projects',
|
|
2606
2689
|
projectId: null,
|
|
2607
2690
|
activeProjectId: null,
|
|
@@ -2614,6 +2697,7 @@ window.__ModuleLoader__.load({
|
|
|
2614
2697
|
imageDownloads: {},
|
|
2615
2698
|
creationWork: null,
|
|
2616
2699
|
creationRequestProtocol: null,
|
|
2700
|
+
cameraTargetResetSupported: false,
|
|
2617
2701
|
creationDrafts: [],
|
|
2618
2702
|
creationDraftStorageStatus: null,
|
|
2619
2703
|
creationDraftMissingRecipe: null,
|
|
@@ -2635,6 +2719,7 @@ window.__ModuleLoader__.load({
|
|
|
2635
2719
|
photographyDrafts: {},
|
|
2636
2720
|
photographyWork: {},
|
|
2637
2721
|
photographyEdits: {},
|
|
2722
|
+
photographyReuse: {},
|
|
2638
2723
|
editorEntityId: null,
|
|
2639
2724
|
editorDrafts: {},
|
|
2640
2725
|
editorLastEdits: {},
|
|
@@ -2703,6 +2788,8 @@ window.__ModuleLoader__.load({
|
|
|
2703
2788
|
const draftLifecycle = settings.creationDraftLifecycle ?? globalThis.window
|
|
2704
2789
|
let fileChoiceSignature = null, fileChoicesStopped = false
|
|
2705
2790
|
const photographyControllers = new Map()
|
|
2791
|
+
const photographySourceControllers = new Map()
|
|
2792
|
+
let photographySourceSequence = 0
|
|
2706
2793
|
const photographyWork = (projectId, changes) => set({ photographyWork: { ...data.photographyWork, [projectId]: { ...data.photographyWork[projectId], ...changes } } })
|
|
2707
2794
|
const inspectionControllers = new Map()
|
|
2708
2795
|
const imageDownloadControllers = new Map()
|
|
@@ -2754,6 +2841,21 @@ window.__ModuleLoader__.load({
|
|
|
2754
2841
|
}
|
|
2755
2842
|
/** Change the snapshot and tell everyone. @param {object} patch */
|
|
2756
2843
|
const set = (patch) => {
|
|
2844
|
+
// Automatic edit images are source snapshots too. They cannot override
|
|
2845
|
+
// fresh rows from another store, even when its project/revision IDs match.
|
|
2846
|
+
if (Object.hasOwn(patch, 'projectsRoot') && patch.projectsRoot !== data.projectsRoot) {
|
|
2847
|
+
for (const controller of photographySourceControllers.values()) controller.abort()
|
|
2848
|
+
photographySourceControllers.clear()
|
|
2849
|
+
patch = { ...patch, editorComparison: null, photographyReuse: {} }
|
|
2850
|
+
}
|
|
2851
|
+
// Retire the old source before selection/creation navigation can expose
|
|
2852
|
+
// it with a new project. A load supplies the complete new source atomically.
|
|
2853
|
+
if (!Object.hasOwn(patch, 'previews') && ['projectId', 'projectsRoot', 'activeProjectId']
|
|
2854
|
+
.some(key => Object.hasOwn(patch, key) && patch[key] !== data[key])) {
|
|
2855
|
+
const requested = Object.hasOwn(patch, 'projectId') ? patch.projectId : data.projectId
|
|
2856
|
+
const active = Object.hasOwn(patch, 'activeProjectId') ? patch.activeProjectId : data.activeProjectId
|
|
2857
|
+
patch = { ...patch, previews: null, artifactBase: '', previewContext: null, previewReadStatus: requested || active ? 'loading' : 'idle' }
|
|
2858
|
+
}
|
|
2757
2859
|
if (['projectsRoot', 'activeProjectId', 'currentRevision', 'view', 'compareMode', 'compareLeft', 'compareRight']
|
|
2758
2860
|
.some(key => Object.hasOwn(patch, key) && patch[key] !== data[key])) {
|
|
2759
2861
|
diffSequence += 1
|
|
@@ -2841,11 +2943,13 @@ window.__ModuleLoader__.load({
|
|
|
2841
2943
|
recipeCatalog: payload.recipeCatalog ?? { recipes: [], errors: [] },
|
|
2842
2944
|
projectsRoot: payload.projectsRoot ?? null,
|
|
2843
2945
|
creationRequestProtocol: payload.creationRequestProtocol ?? null,
|
|
2946
|
+
cameraTargetResetSupported: payload.cameraTargetResetSupported === true,
|
|
2844
2947
|
selected: payload.selected ?? null,
|
|
2845
2948
|
activeProjectId: active,
|
|
2846
2949
|
currentRevision: payload.selected ? payload.selected.currentRevision : null,
|
|
2847
2950
|
jobs: payload.selected ? payload.selected.jobs || [] : [],
|
|
2848
2951
|
unfinishedJobs: payload.selected ? payload.selected.unfinishedJobs || [] : [],
|
|
2952
|
+
previews: null, artifactBase: '', previewContext: null, previewReadStatus: active === null ? 'idle' : 'loading',
|
|
2849
2953
|
}
|
|
2850
2954
|
live = patch.unfinishedJobs.length > 0
|
|
2851
2955
|
|
|
@@ -2861,11 +2965,22 @@ window.__ModuleLoader__.load({
|
|
|
2861
2965
|
}
|
|
2862
2966
|
const previewResult = await readRoute(fetchImpl, `${projectRoute(active, '/previews')}?t=${tick}`, 'project.previews')
|
|
2863
2967
|
if (previewResult.status === 'ok') {
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2968
|
+
const result = previewResult.payload
|
|
2969
|
+
if (result.previews?.projectId === active && Array.isArray(result.previews.revisions)
|
|
2970
|
+
&& (result.projectId === undefined || result.projectId === active)
|
|
2971
|
+
&& result.artifactBase === `/deepblend/artifacts/${encodeURIComponent(active)}/`) {
|
|
2972
|
+
patch.previews = result.previews
|
|
2973
|
+
patch.artifactBase = result.artifactBase
|
|
2974
|
+
patch.previewContext = { projectId: active, projectsRoot: patch.projectsRoot }
|
|
2975
|
+
patch.previewReadStatus = 'ok'
|
|
2976
|
+
} else {
|
|
2977
|
+
patch.previewReadStatus = 'error'
|
|
2978
|
+
patch.error = { code: 'UI_PREVIEW_MISMATCH', message: t('preview.sourceMismatch') }
|
|
2979
|
+
}
|
|
2980
|
+
} else {
|
|
2981
|
+
patch.previewReadStatus = 'error'
|
|
2868
2982
|
patch.error = previewResult.error
|
|
2983
|
+
if (previewResult.status === 'stale') patch.status = 'stale'
|
|
2869
2984
|
}
|
|
2870
2985
|
} else {
|
|
2871
2986
|
patch.previews = null
|
|
@@ -2876,7 +2991,7 @@ window.__ModuleLoader__.load({
|
|
|
2876
2991
|
if (patch.selected?.scene && active) {
|
|
2877
2992
|
const photography = data.photographyDrafts[active]
|
|
2878
2993
|
if ((!photography || !photographyDirty(photography)) && !data.photographyWork[active]?.saving) {
|
|
2879
|
-
patch.photographyDrafts = { ...data.photographyDrafts, [active]: createPhotographyDraft(patch.selected.scene, active, photography) }
|
|
2994
|
+
patch.photographyDrafts = { ...data.photographyDrafts, [active]: createPhotographyDraft(patch.selected.scene, active, photography, patch.projectsRoot) }
|
|
2880
2995
|
}
|
|
2881
2996
|
const draft = data.briefDrafts[active], work = data.briefWork[active]
|
|
2882
2997
|
if (!draft || !briefDirty(draft) && !work?.uploading && !work?.saving) {
|
|
@@ -3003,6 +3118,46 @@ window.__ModuleLoader__.load({
|
|
|
3003
3118
|
|
|
3004
3119
|
const acceptsFileChoice = (source, field) => !fileChoicesStopped && typeof source === 'string'
|
|
3005
3120
|
&& FILE_FIELDS.includes(field) && source === fileChoiceKey(data, field) && fileChoiceAllowed(data, field)
|
|
3121
|
+
const loadPhotographySource = async requestedRevision => {
|
|
3122
|
+
const draft = photographyDraftFor(data), projectId = data.activeProjectId, selection = data.photographyReuse[projectId]
|
|
3123
|
+
if (!data.cameraTargetResetSupported || !draft || !selection?.sourceProjectId || photographyBusy(data.photographyWork[projectId])
|
|
3124
|
+
|| draft.baseRevision !== data.selected?.scene?.revision || draft.projectsRoot !== data.projectsRoot) return
|
|
3125
|
+
photographySourceControllers.get(projectId)?.abort()
|
|
3126
|
+
const controller = new AbortController(), sequence = ++photographySourceSequence
|
|
3127
|
+
photographySourceControllers.set(projectId, controller)
|
|
3128
|
+
const root = data.projectsRoot, baseRevision = draft.baseRevision, sourceProjectId = selection.sourceProjectId
|
|
3129
|
+
const alive = () => running && !controller.signal.aborted && photographySourceControllers.get(projectId) === controller && data.projectsRoot === root
|
|
3130
|
+
const update = changes => set({ photographyReuse: { ...data.photographyReuse, [projectId]: { ...data.photographyReuse[projectId], ...changes } } })
|
|
3131
|
+
update({ source: null, loading: true, error: null, applied: false, unchanged: false, replaceDraft: false, projectsRoot: root, baseRevision, sequence })
|
|
3132
|
+
const deadline = setTimeout(() => controller.abort(), 30000)
|
|
3133
|
+
const read = (path, route) => readRoute((url, options) => fetchImpl(url, { ...options, signal: controller.signal }), path, route)
|
|
3134
|
+
try {
|
|
3135
|
+
const listed = await read(projectRoute(sourceProjectId, '/revisions'), 'project.revisions')
|
|
3136
|
+
if (!alive()) return
|
|
3137
|
+
const list = listed.payload
|
|
3138
|
+
if (listed.status !== 'ok' || list?.projectId !== sourceProjectId || !Array.isArray(list.revisions)
|
|
3139
|
+
|| list.revisions.some(item => !/^r[0-9]{4,}$/.test(item.revision))) throw new Error(listed.error?.message || t('photo.reuseUnavailable'))
|
|
3140
|
+
const revision = requestedRevision || list.currentRevision, saved = list.revisions.find(item => item.revision === revision)
|
|
3141
|
+
if (!saved) throw new Error(t('photo.reuseUnavailable'))
|
|
3142
|
+
update({ revisions: editorClone(list.revisions), revision })
|
|
3143
|
+
const result = await read(`${projectRoute(sourceProjectId, '/scene')}?revision=${encodeURIComponent(revision)}`, 'project.scene')
|
|
3144
|
+
if (!alive()) return
|
|
3145
|
+
const payload = result.payload, scene = payload?.scene
|
|
3146
|
+
if (result.status !== 'ok' || payload?.projectId !== sourceProjectId || payload.revision !== revision
|
|
3147
|
+
|| scene?.revision !== revision || scene?.project?.id !== sourceProjectId || scene.digest !== payload.digest
|
|
3148
|
+
|| !/^[a-f0-9]{64}$/.test(payload.digest || '') || (saved.digest && saved.digest !== payload.digest)
|
|
3149
|
+
|| !Array.isArray(scene.nodes?.cameras) || !Array.isArray(scene.nodes?.lights)) throw new Error(result.error?.message || t('photo.reuseUnavailable'))
|
|
3150
|
+
update({ source: editorClone(scene), cameraId: scene.nodes.cameras.find(item => item.id === scene.project.activeCamera)?.id || scene.nodes.cameras[0]?.id || '', targetEntityId: '', error: null })
|
|
3151
|
+
} catch (error) {
|
|
3152
|
+
if (alive()) update({ source: null, error: error.message || String(error) })
|
|
3153
|
+
} finally {
|
|
3154
|
+
clearTimeout(deadline)
|
|
3155
|
+
if (photographySourceControllers.get(projectId) === controller) {
|
|
3156
|
+
photographySourceControllers.delete(projectId)
|
|
3157
|
+
if (running && data.projectsRoot === root) update({ loading: false, ...(controller.signal.aborted ? { source: null, error: t('photo.reuseUnavailable') } : {}) })
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3006
3161
|
const actions = {
|
|
3007
3162
|
acceptsFileChoice,
|
|
3008
3163
|
setAppearance: value => {
|
|
@@ -3406,7 +3561,43 @@ window.__ModuleLoader__.load({
|
|
|
3406
3561
|
resetPhotography: () => {
|
|
3407
3562
|
const projectId = data.activeProjectId, scene = data.selected?.scene
|
|
3408
3563
|
if (!scene || photographyBusy(data.photographyWork[projectId])) return
|
|
3409
|
-
set({ photographyDrafts: { ...data.photographyDrafts, [projectId]: createPhotographyDraft(scene, projectId, photographyDraftFor(data)) }
|
|
3564
|
+
set({ photographyDrafts: { ...data.photographyDrafts, [projectId]: createPhotographyDraft(scene, projectId, photographyDraftFor(data), data.projectsRoot) },
|
|
3565
|
+
photographyReuse: { ...data.photographyReuse, ...(data.photographyReuse[projectId] ? { [projectId]: { ...data.photographyReuse[projectId], applied: false, unchanged: false, replaceDraft: false } } : {}) } })
|
|
3566
|
+
},
|
|
3567
|
+
setPhotographySource: (field, value) => {
|
|
3568
|
+
const projectId = data.activeProjectId, selection = data.photographyReuse[projectId] || {}
|
|
3569
|
+
if (!data.cameraTargetResetSupported || !photographyDraftFor(data) || photographyBusy(data.photographyWork[projectId])) return
|
|
3570
|
+
if (field === 'project') {
|
|
3571
|
+
if (value && !data.projects.some(item => item.projectId === value && !item.creationPending)) return
|
|
3572
|
+
photographySourceControllers.get(projectId)?.abort(); photographySourceControllers.delete(projectId)
|
|
3573
|
+
set({ photographyReuse: { ...data.photographyReuse, [projectId]: { sourceProjectId: value, source: null, revisions: [], revision: '', cameraId: '', targetEntityId: '', replaceDraft: false } } })
|
|
3574
|
+
if (value) return loadPhotographySource()
|
|
3575
|
+
} else if (field === 'revision') {
|
|
3576
|
+
if (!selection.revisions?.some(item => item.revision === value)) return
|
|
3577
|
+
return loadPhotographySource(value)
|
|
3578
|
+
} else if (field === 'camera' && selection.source?.nodes.cameras.some(item => item.id === value)) {
|
|
3579
|
+
set({ photographyReuse: { ...data.photographyReuse, [projectId]: { ...selection, cameraId: value, targetEntityId: '', applied: false, unchanged: false } } })
|
|
3580
|
+
} else if (field === 'target' && (!value || photographyDraftFor(data).entities.some(item => item.id === value))) {
|
|
3581
|
+
set({ photographyReuse: { ...data.photographyReuse, [projectId]: { ...selection, targetEntityId: value, applied: false, unchanged: false } } })
|
|
3582
|
+
} else if (field === 'replace' && typeof value === 'boolean') {
|
|
3583
|
+
set({ photographyReuse: { ...data.photographyReuse, [projectId]: { ...selection, replaceDraft: value } } })
|
|
3584
|
+
}
|
|
3585
|
+
},
|
|
3586
|
+
reloadPhotographySource: () => loadPhotographySource(data.photographyReuse[data.activeProjectId]?.revision),
|
|
3587
|
+
reusePhotography: () => {
|
|
3588
|
+
const projectId = data.activeProjectId, selection = data.photographyReuse[projectId], draft = photographyDraftFor(data)
|
|
3589
|
+
if (!data.cameraTargetResetSupported || !draft || !selection?.source || selection.loading || photographyBusy(data.photographyWork[projectId])
|
|
3590
|
+
|| selection.projectsRoot !== data.projectsRoot || draft.projectsRoot !== data.projectsRoot
|
|
3591
|
+
|| selection.baseRevision !== draft.baseRevision || draft.baseRevision !== data.selected?.scene?.revision
|
|
3592
|
+
|| (photographyDirty(draft) && !selection.replaceDraft)) return
|
|
3593
|
+
try {
|
|
3594
|
+
const next = reusePhotography(draft, selection.source, selection.cameraId, selection.targetEntityId)
|
|
3595
|
+
set({ photographyDrafts: { ...data.photographyDrafts, [projectId]: next }, photographyReuse: {
|
|
3596
|
+
...data.photographyReuse, [projectId]: { ...selection, replaceDraft: false, error: null, applied: photographyDirty(next), unchanged: !photographyDirty(next) },
|
|
3597
|
+
} })
|
|
3598
|
+
} catch (error) {
|
|
3599
|
+
set({ photographyReuse: { ...data.photographyReuse, [projectId]: { ...selection, applied: false, error: error.message || String(error) } } })
|
|
3600
|
+
}
|
|
3410
3601
|
},
|
|
3411
3602
|
updatePhotography: (kind, path, value) => {
|
|
3412
3603
|
const current = photographyDraftFor(data)
|
|
@@ -3423,7 +3614,7 @@ window.__ModuleLoader__.load({
|
|
|
3423
3614
|
} else if (kind === 'camera' && path.length === 1 && path[0] === 'aim') {
|
|
3424
3615
|
if (!['free', 'entity', 'point'].includes(value) || photographyAimLocked(draft, item)) return
|
|
3425
3616
|
const original = draft.original.cameras.find(camera => camera.id === item.id)
|
|
3426
|
-
if (value === 'free' && photographyAim(original) !== 'free') return
|
|
3617
|
+
if (value === 'free' && photographyAim(original) !== 'free' && !data.cameraTargetResetSupported) return
|
|
3427
3618
|
delete item.targetEntityId; delete item.targetPoint
|
|
3428
3619
|
if (value === 'entity') item.targetEntityId = draft.entities.find(entity => entity.id === data.editorEntityId)?.id || data.selected?.scene?.project?.reviewSubjectId || draft.entities[0]?.id || ''
|
|
3429
3620
|
if (value === 'point') item.targetPoint = [0, 0, 0]
|
|
@@ -3439,7 +3630,8 @@ window.__ModuleLoader__.load({
|
|
|
3439
3630
|
else if (kind === 'light' && path[0] === 'color' && path.length === 2 && [0, 1, 2].includes(path[1])) { item.color ||= [1, 1, 1]; item.color[path[1]] = value }
|
|
3440
3631
|
else return
|
|
3441
3632
|
}
|
|
3442
|
-
set({ photographyDrafts: { ...data.photographyDrafts, [current.projectId]: draft }
|
|
3633
|
+
set({ photographyDrafts: { ...data.photographyDrafts, [current.projectId]: draft },
|
|
3634
|
+
photographyReuse: { ...data.photographyReuse, ...(data.photographyReuse[current.projectId] ? { [current.projectId]: { ...data.photographyReuse[current.projectId], unchanged: false } } : {}) } })
|
|
3443
3635
|
},
|
|
3444
3636
|
addPhotographyLight: () => {
|
|
3445
3637
|
const current = photographyDraftFor(data)
|
|
@@ -3463,7 +3655,9 @@ window.__ModuleLoader__.load({
|
|
|
3463
3655
|
setIn('busy', 'editor', false)
|
|
3464
3656
|
if (outcome.ok) {
|
|
3465
3657
|
const saved = outcome.payload.revision, drafts = { ...data.photographyDrafts, [projectId]: { ...editorClone(draft), baseRevision: outcome.payload.revision.revision, original: editorClone({ cameras: draft.cameras, lights: draft.lights }) } }
|
|
3466
|
-
set({ photographyDrafts: drafts,
|
|
3658
|
+
set({ photographyDrafts: drafts,
|
|
3659
|
+
photographyReuse: { ...data.photographyReuse, ...(data.photographyReuse[projectId] ? { [projectId]: { ...data.photographyReuse[projectId], baseRevision: saved.revision, applied: false, unchanged: false, replaceDraft: false } } : {}) },
|
|
3660
|
+
photographyEdits: { ...data.photographyEdits, [projectId]: {
|
|
3467
3661
|
before: draft.baseRevision, after: saved.revision, digest: saved.digest, cameraId: draft.cameraId, frame: draft.frame, samples: draft.samples,
|
|
3468
3662
|
changes: [...new Set(patch.operations.map(operation => operation.op.split('.')[0]))] } } })
|
|
3469
3663
|
photographyWork(projectId, { saving: false, error: null, artifact: null })
|
|
@@ -3852,6 +4046,7 @@ window.__ModuleLoader__.load({
|
|
|
3852
4046
|
void load()
|
|
3853
4047
|
},
|
|
3854
4048
|
stop() {
|
|
4049
|
+
loadSequence += 1
|
|
3855
4050
|
diffSequence += 1
|
|
3856
4051
|
closeCreationDraft()
|
|
3857
4052
|
draftLifecycle?.removeEventListener?.('pagehide', closeCreationDraft)
|
|
@@ -3862,6 +4057,8 @@ window.__ModuleLoader__.load({
|
|
|
3862
4057
|
if (timer !== null) { clearInterval(timer); timer = null }
|
|
3863
4058
|
for (const controller of photographyControllers.values()) controller.abort()
|
|
3864
4059
|
photographyControllers.clear()
|
|
4060
|
+
for (const controller of photographySourceControllers.values()) controller.abort()
|
|
4061
|
+
photographySourceControllers.clear()
|
|
3865
4062
|
for (const controller of inspectionControllers.values()) controller.abort()
|
|
3866
4063
|
inspectionControllers.clear()
|
|
3867
4064
|
for (const controller of imageDownloadControllers.values()) controller.abort('stopped')
|
|
@@ -4496,18 +4693,38 @@ window.__ModuleLoader__.load({
|
|
|
4496
4693
|
{ factor, locked: locked || editorTrackLocked(draft, kind, kind === 'camera' ? camera.id : light.id, `${field}.${axis}`) })))
|
|
4497
4694
|
const aim = photographyAim(camera), originalCamera = draft?.original.cameras.find(item => item.id === camera?.id)
|
|
4498
4695
|
const aimLocked = camera && photographyAimLocked(draft, camera)
|
|
4696
|
+
const reuse = state.photographyReuse?.[projectId] || {}, sourceCamera = reuse.source?.nodes.cameras.find(item => item.id === reuse.cameraId)?.definition
|
|
4697
|
+
const reuseStale = reuse.source && (reuse.projectsRoot !== state.projectsRoot || reuse.baseRevision !== draft?.baseRevision || draft?.projectsRoot !== state.projectsRoot)
|
|
4499
4698
|
return el('section', { className: 'db-card', 'data-photography-editor': projectId },
|
|
4500
4699
|
el('h4', null, t('photo.title')), el('p', { className: 'db-muted' }, t('photo.help')),
|
|
4501
4700
|
draft ? el('div', null,
|
|
4502
4701
|
el('p', { className: 'db-muted', 'data-photography-base': draft.baseRevision }, t('photo.base', { revision: draft.baseRevision })),
|
|
4503
4702
|
conflict ? el('p', { className: 'db-error', 'data-photography-conflict': true }, t('photo.conflict', { before: draft.baseRevision, current: state.selected?.scene?.revision })) : null,
|
|
4703
|
+
state.cameraTargetResetSupported ? el('div', { 'data-photography-reuse': true },
|
|
4704
|
+
el('h4', null, t('photo.reuseTitle')), el('p', { className: 'db-muted' }, t('photo.reuseHelp')),
|
|
4705
|
+
choose(t('photo.reuseProject'), 'source-project', reuse.sourceProjectId || '', [['', '—'], ...state.projects.filter(item => !item.creationPending).map(item => [item.projectId, item.title || item.projectId])], value => actions.setPhotographySource('project', value)),
|
|
4706
|
+
choose(t('photo.reuseRevision'), 'source-revision', reuse.revision || '', [['', '—'], ...(reuse.revisions || []).map(item => [item.revision, item.revision])], value => actions.setPhotographySource('revision', value), reuse.loading || !reuse.revisions?.length),
|
|
4707
|
+
reuse.loading ? el('p', { role: 'status' }, t('photo.reuseReading')) : null,
|
|
4708
|
+
reuse.source ? el('div', { 'data-photography-source': `${reuse.sourceProjectId}:${reuse.revision}` },
|
|
4709
|
+
el('p', { className: 'db-muted' }, t('photo.reuseSource', { project: reuse.sourceProjectId, revision: reuse.revision, digest: shortDigest(reuse.source.digest), count: reuse.source.nodes.lights.length })),
|
|
4710
|
+
choose(t('photo.reuseCamera'), 'source-camera', reuse.cameraId, reuse.source.nodes.cameras.map(item => [item.id, item.id]), value => actions.setPhotographySource('camera', value)),
|
|
4711
|
+
photographyAim(sourceCamera) === 'entity' ? choose(t('photo.reuseTarget'), 'source-target', reuse.targetEntityId || '', [['', '—'], ...draft.entities.map(item => [item.id, item.id])], value => actions.setPhotographySource('target', value)) : null,
|
|
4712
|
+
photographyDirty(draft) ? el('label', { className: 'db-row' }, el('input', { type: 'checkbox', 'data-field': 'photo-source-replace', checked: Boolean(reuse.replaceDraft), disabled: disabled || reuse.loading,
|
|
4713
|
+
onChange: event => actions.setPhotographySource('replace', Boolean(event.target.checked)) }), t('photo.reuseReplace')) : null,
|
|
4714
|
+
reuseStale ? el('p', { className: 'db-error' }, t('photo.reuseStale')) : null,
|
|
4715
|
+
reuse.applied ? el('p', { role: 'status', 'data-photography-reused': true }, t('photo.reuseApplied')) : null,
|
|
4716
|
+
reuse.unchanged ? el('p', { role: 'status', 'data-photography-reuse-unchanged': true }, t('photo.reuseSame')) : null) : null,
|
|
4717
|
+
reuse.error ? el('p', { className: 'db-error', role: 'status', 'data-photography-source-error': true }, reuse.error) : null,
|
|
4718
|
+
el('div', { className: 'db-inline' },
|
|
4719
|
+
Button({ action: 'photo-source-reload', disabled: disabled || !reuse.sourceProjectId || reuse.loading, onClick: actions.reloadPhotographySource, children: t('photo.reuseReload') }),
|
|
4720
|
+
Button({ action: 'photo-reuse', disabled: disabled || reuse.loading || !reuse.source || reuseStale || (photographyDirty(draft) && !reuse.replaceDraft) || (photographyAim(sourceCamera) === 'entity' && !reuse.targetEntityId), onClick: actions.reusePhotography, children: t('photo.reuseApply') }))) : el('p', { className: 'db-muted' }, t('photo.reuseUpgrade')),
|
|
4504
4721
|
el('div', { className: 'db-grid' },
|
|
4505
4722
|
el('div', null,
|
|
4506
4723
|
choose(t('photo.camera'), 'camera', draft.cameraId, draft.cameras.map(item => [item.id, item.id]), id => actions.selectPhotography('camera', id)),
|
|
4507
4724
|
camera ? el('div', null,
|
|
4508
4725
|
numeric(t('photo.lens'), 'camera-lens', editorNumberDefault(camera.lens, 50), update('camera', ['lens']), { min: .001 }),
|
|
4509
4726
|
vector('camera', 'location', camera.transform?.location, t('photo.position')),
|
|
4510
|
-
choose(t('photo.aim'), 'camera-aim', aim, [...(photographyAim(originalCamera) === 'free' ? [['free', t('photo.free')]] : []), ['entity', t('photo.entity')], ['point', t('photo.point')]], update('camera', ['aim']), aimLocked),
|
|
4727
|
+
choose(t('photo.aim'), 'camera-aim', aim, [...(state.cameraTargetResetSupported || photographyAim(originalCamera) === 'free' ? [['free', t('photo.free')]] : []), ['entity', t('photo.entity')], ['point', t('photo.point')]], update('camera', ['aim']), aimLocked),
|
|
4511
4728
|
aim === 'entity' ? choose(t('photo.target'), 'camera-target', camera.targetEntityId, draft.entities.map(item => [item.id, item.id]), update('camera', ['targetEntityId']), aimLocked) : null,
|
|
4512
4729
|
aim === 'point' ? vector('camera', 'targetPoint', camera.targetPoint, t('photo.targetPoint'), 1, aimLocked) : null,
|
|
4513
4730
|
vector('camera', 'rotationEuler', camera.transform?.rotationEuler, t('photo.rotation'), 180 / Math.PI, aim !== 'free'),
|
|
@@ -4877,7 +5094,13 @@ window.__ModuleLoader__.load({
|
|
|
4877
5094
|
const state = ctx.state
|
|
4878
5095
|
const actions = ctx.actions
|
|
4879
5096
|
const previews = state.previews
|
|
4880
|
-
if (previews === null)
|
|
5097
|
+
if (previews === null) {
|
|
5098
|
+
if (state.previewReadStatus === 'error') return el('div', { 'data-view': 'preview', 'data-preview-state': 'error', className: 'db-card' },
|
|
5099
|
+
el('div', { className: 'db-muted' }, t('preview.readFailed')),
|
|
5100
|
+
Button({ action: 'preview-reload', onClick: actions.reload, children: t('common.refresh') }))
|
|
5101
|
+
if (state.previewReadStatus === 'loading') return el('div', { 'data-view': 'preview', 'data-preview-state': 'loading', className: 'db-muted' }, t('preview.reading'))
|
|
5102
|
+
return el('div', { 'data-view': 'preview', className: 'db-muted' }, t('common.noProject'))
|
|
5103
|
+
}
|
|
4881
5104
|
const revisions = previews.revisions
|
|
4882
5105
|
const { left, right } = previewRevisionPair(state)
|
|
4883
5106
|
const entryOf = id => revisions.find(entry => entry.revision === id) || null
|
|
@@ -5915,7 +6138,7 @@ window.__ModuleLoader__.load({
|
|
|
5915
6138
|
inspection: { form: inspectionForm, valid: inspectionFormValid },
|
|
5916
6139
|
assetLibrary: { selection: assetBundleSelection, createDraft: createAssetDraft, buildPatch: buildAssetPatch, error: assetDraftError, key: assetKey },
|
|
5917
6140
|
referenceBrief: { createDraft: createBriefDraft, buildPatch: buildBriefPatch, dirty: briefDirty, valid: briefValid },
|
|
5918
|
-
photographyEditor: { createDraft: createPhotographyDraft, buildPatch: buildPhotographyPatch, errors: photographyErrors, dirty: photographyDirty, draftFor: photographyDraftFor },
|
|
6141
|
+
photographyEditor: { createDraft: createPhotographyDraft, buildPatch: buildPhotographyPatch, errors: photographyErrors, dirty: photographyDirty, draftFor: photographyDraftFor, reuse: reusePhotography },
|
|
5919
6142
|
sceneEditor: { createDraft: createEditorDraft, buildPatch: buildEditorPatch, errors: editorErrors, dirty: editorDirty, previewPair: editorPreviewPair, draftFor: editorDraftFor },
|
|
5920
6143
|
renderView,
|
|
5921
6144
|
mountStandalone,
|
package/lib/index.js
CHANGED
|
@@ -633,6 +633,7 @@ export function createHandlers(ctx) {
|
|
|
633
633
|
projects: listed.projects.map(record => buildProjectView(record)),
|
|
634
634
|
projectsRoot: listed.projectsRoot,
|
|
635
635
|
creationRequestProtocol: studio().creationRequestProtocol ?? null,
|
|
636
|
+
cameraTargetResetSupported: studio().cameraTargetResetSupported === true,
|
|
636
637
|
selected: projectId === null || creationPending
|
|
637
638
|
? null
|
|
638
639
|
: await buildProjectState(studio(), projectId, query.revision ?? undefined, approvalThreshold()),
|
|
@@ -669,6 +670,7 @@ export function createHandlers(ctx) {
|
|
|
669
670
|
'project.scene': async ({ params, query }) => {
|
|
670
671
|
const detail = await studio().getScene(params.projectId, { revision: query.revision, full: true })
|
|
671
672
|
return {
|
|
673
|
+
projectId: params.projectId,
|
|
672
674
|
revision: detail.revision,
|
|
673
675
|
digest: detail.digest,
|
|
674
676
|
scene: buildSceneTree(detail.spec, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepblend/dsh-blender-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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.
|
|
22
|
+
"@deepblend/dsh-blender-contracts": "0.4.0"
|
|
23
23
|
},
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"repository": {
|