0.9.1: jumpToScene action + auto-play on scene switch

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.7 committed 2026-04-22 07:44:43 +02:00
1 parent b57994c754
commit b9f2ba9e47
9 files changed
+436 -15

No files matched your search

+25
View File
@@ -19,6 +19,10 @@ const markerNames = computed(() =>
(store.scene.markers ?? []).map((m) => m.name),
)
const sceneOptions = computed(() =>
store.project.scenes.map((s) => ({ id: s.id, name: s.name })),
)
const ACTION_TYPES: Action['type'][] = [
'play',
'pause',
@@ -26,6 +30,7 @@ const ACTION_TYPES: Action['type'][] = [
'jumpTo',
'jumpToFrame',
'jumpToMarker',
'jumpToScene',
'script',
]
@@ -45,6 +50,14 @@ function makeDefault(type: Action['type']): Action {
return { type: 'jumpToFrame', frame: 0 }
case 'jumpToMarker':
return { type: 'jumpToMarker', name: markerNames.value[0] ?? '' }
case 'jumpToScene': {
// Default to the first scene that ISN'T the current one so the
// action is immediately useful; fall back to the first if there's
// only one scene.
const others = sceneOptions.value.filter((s) => s.id !== store.project.activeSceneId)
const target = others[0] ?? sceneOptions.value[0]
return { type: 'jumpToScene', sceneId: target?.id ?? '' }
}
case 'script':
return { type: 'script', source: '' }
}
@@ -152,6 +165,18 @@ function onTypeChange(index: number, newType: Action['type']) {
</select>
</label>
</div>
<div v-else-if="a.type === 'jumpToScene'" class="params">
<label>
{{ t('actions.param.scene') }}
<select
:value="a.sceneId"
@change="setAt(i, { type: 'jumpToScene', sceneId: ($event.target as HTMLSelectElement).value })"
>
<option v-if="!sceneOptions.length" value="" disabled>{{ t('actions.noScenes') }}</option>
<option v-for="s in sceneOptions" :key="s.id" :value="s.id">{{ s.name }}</option>
</select>
</label>
</div>
<div v-else-if="a.type === 'script'" class="params">
<label class="block">
{{ t('actions.param.source') }}
+44 -8
View File
@@ -42,17 +42,53 @@ async function startPreview() {
const end = rangeOut.value
const head = store.playhead
const initialTime = head < start || head >= end ? start : head
player = mountPlayer(el, store.scene, {
loop: true,
onFrame(time: number, paused: boolean) {
localTime.value = time
playing.value = !paused
},
})
player.seek(initialTime)
mountAt(el, initialTime, true)
requestBrowserFullscreen()
}
/** Mount (or re-mount) the player against `store.scene` and seek to `time`.
* Used both by the initial `startPreview` and when a scripted / button
* `jumpToScene` swaps the active page under us — we blow away the old
* 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, {
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)
},
})
player.seek(time)
if (!wasPlaying) player.pause()
}
watch(
() => store.project.activeSceneId,
() => {
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
destroyPlayer()
nextTick(() => {
if (!props.modelValue) return
const still = frameEl.value
if (!still) return
mountAt(still, keepTime, true)
})
},
)
function stopPreview() {
destroyPlayer()
playing.value = false