0.10.3: preloader + multi-scene export parity, jumpToScene rewinds
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
87de1344c4
commit
fcb8edbb88
6 files changed
+461
-102
No files matched your search
@@ -47,7 +47,34 @@ const hasHiddenLayers = computed(() => {
|
||||
return false
|
||||
})
|
||||
|
||||
function sceneClone() {
|
||||
function projectClone() {
|
||||
// Project-shaped payload for standalone static exports — the
|
||||
// embedded runtime handles multi-scene playback natively.
|
||||
const project = JSON.parse(JSON.stringify(store.project))
|
||||
if (!skipHidden.value) return project
|
||||
for (const page of project.scenes) {
|
||||
page.layers = page.layers.filter((l: Layer) => !l.editorHidden)
|
||||
}
|
||||
if (Array.isArray(project.clips)) {
|
||||
for (const clip of project.clips) {
|
||||
clip.layers = clip.layers.filter((l: Layer) => !l.editorHidden)
|
||||
}
|
||||
}
|
||||
if (Array.isArray(project.assets)) {
|
||||
for (const asset of project.assets as ButtonAsset[]) {
|
||||
if (asset && asset.kind === 'button' && Array.isArray(asset.layers)) {
|
||||
asset.layers = asset.layers.filter((l: Layer) => !l.editorHidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
return project
|
||||
}
|
||||
|
||||
function activeSceneClone() {
|
||||
// Scene-shaped payload for linear render paths (webm/mp4 server).
|
||||
// Video is a single timeline — we only render the currently active
|
||||
// page. A user who wants a different scene rendered needs to switch
|
||||
// it in the drawer first.
|
||||
const scene = JSON.parse(JSON.stringify(store.scene))
|
||||
if (!skipHidden.value) return scene
|
||||
scene.layers = scene.layers.filter((l: Layer) => !l.editorHidden)
|
||||
@@ -67,7 +94,7 @@ function sceneClone() {
|
||||
}
|
||||
|
||||
function exportScene() {
|
||||
downloadSceneExport(sceneClone())
|
||||
downloadSceneExport(projectClone())
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
@@ -77,7 +104,7 @@ function exportRuntime() {
|
||||
}
|
||||
|
||||
function exportHtml() {
|
||||
downloadExampleHtml(sceneClone())
|
||||
downloadExampleHtml(projectClone())
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
@@ -92,7 +119,7 @@ async function exportWebm() {
|
||||
}
|
||||
store.pause()
|
||||
try {
|
||||
await downloadSceneAsVideo(sceneClone(), {
|
||||
await downloadSceneAsVideo(activeSceneClone(), {
|
||||
signal: controller.signal,
|
||||
onProgress: (p) => {
|
||||
if (progress.value) progress.value.progress = p
|
||||
@@ -120,7 +147,7 @@ async function exportMp4Server() {
|
||||
}
|
||||
store.pause()
|
||||
try {
|
||||
await downloadFromServer(sceneClone(), {
|
||||
await downloadFromServer(activeSceneClone(), {
|
||||
signal: controller.signal,
|
||||
onProgress: (p) => {
|
||||
if (progress.value) progress.value.progress = p
|
||||
|
||||
@@ -56,17 +56,17 @@ async function startPreview() {
|
||||
* player and rebuild at the same playhead so visual continuity stays
|
||||
* intact across the switch. */
|
||||
function mountAt(el: HTMLElement, time: number, wasPlaying: boolean) {
|
||||
player = mountPlayer(el, store.scene, {
|
||||
player = mountPlayer(el, store.project, {
|
||||
loop: true,
|
||||
onFrame(t: number, paused: boolean) {
|
||||
localTime.value = t
|
||||
playing.value = !paused
|
||||
},
|
||||
onJumpToScene(sceneId: string) {
|
||||
// Route the runtime's jump into the store. The watcher below
|
||||
// picks up the activeSceneId change and re-mounts the player
|
||||
// against the new page at the current time.
|
||||
store.switchScene(sceneId)
|
||||
// Route the runtime's jump into the store and restart the
|
||||
// target scene at its in-point — script/button jumps are
|
||||
// "transition to new chapter", not "keep scrubbing".
|
||||
store.switchScene(sceneId, { resetPlayhead: true })
|
||||
},
|
||||
})
|
||||
player.seek(time)
|
||||
@@ -79,10 +79,12 @@ watch(
|
||||
if (!props.modelValue) return
|
||||
const el = frameEl.value
|
||||
if (!el) return
|
||||
// Every scene switch auto-plays on the new page — matches the
|
||||
// store-level switchScene contract. We keep the current playhead
|
||||
// so animation continuity stays intact across the swap.
|
||||
const keepTime = player ? player.getTime() : store.playhead
|
||||
// Seek the remounted player to whatever `store.playhead` is after
|
||||
// the switch. Drawer clicks leave it unchanged; scripted/button
|
||||
// jumps reset it to the in-point via resetPlayhead. Reading the
|
||||
// store (not the old player's currentT) means both paths land on
|
||||
// the expected frame.
|
||||
const keepTime = store.playhead
|
||||
destroyPlayer()
|
||||
nextTick(() => {
|
||||
if (!props.modelValue) return
|
||||
|
||||
Reference in new issue
Block a user