macro-clips slice 1: types + store for buttons, scripts, markers
- ButtonClip/ButtonInstance, ScriptTrack/ScriptFrame, Marker, Action union - new optional scene collections (buttonClips, buttonInstances, scriptTracks, scriptFrames, markers); normalizeScene initializes them on load - store actions for all new entities, marker name + button label uniqueness, time clamping, cascading deletes (script frames with their track, button instances with their clip), script tracks join trackOrder + setTrackLocked - design doc docs/macro-clips-and-scripts.md - tests cover add/remove/rename/retime paths, uniqueness, sort order, label collisions, and normalization Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
d4c4830fe2
commit
a75e9151ed
4 files changed
+952
No files matched your search
@@ -2987,4 +2987,243 @@ export function registerAllTests(): void {
|
||||
expect(outside[3] > 200).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
suite('scene store — markers', () => {
|
||||
test('addMarker defaults the name when none provided', () => {
|
||||
const s = makeFreshStore()
|
||||
const m = s.addMarker(2)
|
||||
expect(m.name).toBe('Marker')
|
||||
expect(m.time).toBe(2)
|
||||
expect(s.scene.markers).toHaveLength(1)
|
||||
})
|
||||
|
||||
test('addMarker de-dupes colliding names with suffix', () => {
|
||||
const s = makeFreshStore()
|
||||
s.addMarker(1, 'Intro')
|
||||
s.addMarker(2, 'Intro')
|
||||
s.addMarker(3, 'Intro')
|
||||
const names = (s.scene.markers ?? []).map((m) => m.name)
|
||||
expect(names).toEqual(['Intro', 'Intro 2', 'Intro 3'])
|
||||
})
|
||||
|
||||
test('addMarker clamps time to [0, duration]', () => {
|
||||
const s = makeFreshStore()
|
||||
s.setDuration(10)
|
||||
const a = s.addMarker(-5)
|
||||
const b = s.addMarker(99)
|
||||
expect(a.time).toBe(0)
|
||||
expect(b.time).toBe(10)
|
||||
})
|
||||
|
||||
test('addMarker keeps the list sorted by time', () => {
|
||||
const s = makeFreshStore()
|
||||
s.addMarker(5, 'C')
|
||||
s.addMarker(1, 'A')
|
||||
s.addMarker(3, 'B')
|
||||
const times = (s.scene.markers ?? []).map((m) => m.time)
|
||||
expect(times).toEqual([1, 3, 5])
|
||||
})
|
||||
|
||||
test('renameMarker succeeds on unique, rejects duplicates', () => {
|
||||
const s = makeFreshStore()
|
||||
const a = s.addMarker(1, 'A')
|
||||
s.addMarker(2, 'B')
|
||||
expect(s.renameMarker(a.id, 'A2')).toBe(true)
|
||||
expect(s.renameMarker(a.id, 'B')).toBe(false)
|
||||
const byId = (s.scene.markers ?? []).find((m) => m.id === a.id)
|
||||
expect(byId?.name).toBe('A2')
|
||||
})
|
||||
|
||||
test('removeMarker drops the entry', () => {
|
||||
const s = makeFreshStore()
|
||||
const m = s.addMarker(1)
|
||||
s.removeMarker(m.id)
|
||||
expect(s.scene.markers).toHaveLength(0)
|
||||
})
|
||||
|
||||
test('setMarkerTime clamps and re-sorts', () => {
|
||||
const s = makeFreshStore()
|
||||
s.setDuration(10)
|
||||
const a = s.addMarker(1, 'A')
|
||||
const b = s.addMarker(2, 'B')
|
||||
s.setMarkerTime(a.id, 99)
|
||||
const sortedNames = (s.scene.markers ?? []).map((m) => m.name)
|
||||
expect(sortedNames).toEqual(['B', 'A'])
|
||||
const updated = (s.scene.markers ?? []).find((m) => m.id === a.id)!
|
||||
expect(updated.time).toBe(10)
|
||||
void b
|
||||
})
|
||||
|
||||
test('setMarkerColor sets and clears', () => {
|
||||
const s = makeFreshStore()
|
||||
const m = s.addMarker(1, 'A')
|
||||
s.setMarkerColor(m.id, '#ff8800')
|
||||
const get = () => s.scene.markers?.find((x) => x.id === m.id)
|
||||
expect(get()?.color).toBe('#ff8800')
|
||||
s.setMarkerColor(m.id, null)
|
||||
expect(get()?.color).toBe(undefined)
|
||||
})
|
||||
})
|
||||
|
||||
suite('scene store — script tracks & frames', () => {
|
||||
test('addScriptTrack appends and adds to trackOrder', () => {
|
||||
const s = makeFreshStore()
|
||||
const t = s.addScriptTrack('Triggers')
|
||||
expect(s.scene.scriptTracks).toHaveLength(1)
|
||||
expect(s.scene.trackOrder?.includes(t.id)).toBe(true)
|
||||
})
|
||||
|
||||
test('addScriptFrame clamps time and returns frame', () => {
|
||||
const s = makeFreshStore()
|
||||
s.setDuration(10)
|
||||
const t = s.addScriptTrack()
|
||||
const f = s.addScriptFrame(t.id, 99, [{ type: 'pause' }])
|
||||
expect(f).toBeTruthy()
|
||||
expect(f!.time).toBe(10)
|
||||
expect(f!.actions).toHaveLength(1)
|
||||
})
|
||||
|
||||
test('addScriptFrame returns null for unknown trackId', () => {
|
||||
const s = makeFreshStore()
|
||||
const f = s.addScriptFrame('no-such-id', 1)
|
||||
expect(f).toBe(null)
|
||||
})
|
||||
|
||||
test('script frames stay sorted by time', () => {
|
||||
const s = makeFreshStore()
|
||||
const t = s.addScriptTrack()
|
||||
s.addScriptFrame(t.id, 5)
|
||||
s.addScriptFrame(t.id, 1)
|
||||
s.addScriptFrame(t.id, 3)
|
||||
const times = (s.scene.scriptFrames ?? []).map((f) => f.time)
|
||||
expect(times).toEqual([1, 3, 5])
|
||||
})
|
||||
|
||||
test('removeScriptTrack cascades its frames and trackOrder entry', () => {
|
||||
const s = makeFreshStore()
|
||||
const t = s.addScriptTrack()
|
||||
s.addScriptFrame(t.id, 1)
|
||||
s.addScriptFrame(t.id, 2)
|
||||
s.removeScriptTrack(t.id)
|
||||
expect(s.scene.scriptTracks).toHaveLength(0)
|
||||
expect(s.scene.scriptFrames).toHaveLength(0)
|
||||
expect(s.scene.trackOrder?.includes(t.id)).toBe(false)
|
||||
})
|
||||
|
||||
test('setScriptFrameActions replaces actions', () => {
|
||||
const s = makeFreshStore()
|
||||
const t = s.addScriptTrack()
|
||||
const f = s.addScriptFrame(t.id, 1, [{ type: 'play' }])!
|
||||
s.setScriptFrameActions(f.id, [{ type: 'pause' }, { type: 'rewindToLoop' }])
|
||||
const updated = s.scene.scriptFrames?.find((x) => x.id === f.id)
|
||||
expect(updated?.actions).toHaveLength(2)
|
||||
expect(updated?.actions[0].type).toBe('pause')
|
||||
})
|
||||
|
||||
test('setScriptFrameTime clamps and re-sorts', () => {
|
||||
const s = makeFreshStore()
|
||||
s.setDuration(10)
|
||||
const t = s.addScriptTrack()
|
||||
const a = s.addScriptFrame(t.id, 1)!
|
||||
const b = s.addScriptFrame(t.id, 2)!
|
||||
s.setScriptFrameTime(a.id, 99)
|
||||
const times = (s.scene.scriptFrames ?? []).map((f) => f.time)
|
||||
expect(times).toEqual([2, 10])
|
||||
void b
|
||||
})
|
||||
})
|
||||
|
||||
suite('scene store — button clips & instances', () => {
|
||||
test('addButtonClip creates 4 empty state stacks', () => {
|
||||
const s = makeFreshStore()
|
||||
const bc = s.addButtonClip('Play')
|
||||
expect(bc.name).toBe('Play')
|
||||
expect(bc.states.normal).toHaveLength(0)
|
||||
expect(bc.states.hover).toHaveLength(0)
|
||||
expect(bc.states.pressed).toHaveLength(0)
|
||||
expect(bc.states.disabled).toHaveLength(0)
|
||||
})
|
||||
|
||||
test('addButtonInstance validates clip and track ids', () => {
|
||||
const s = makeFreshStore()
|
||||
const bc = s.addButtonClip('B')
|
||||
const ct = s.addClipTrack()
|
||||
expect(s.addButtonInstance('no-clip', ct.id, 0)).toBe(null)
|
||||
expect(s.addButtonInstance(bc.id, 'no-track', 0)).toBe(null)
|
||||
const inst = s.addButtonInstance(bc.id, ct.id, 0)
|
||||
expect(inst).toBeTruthy()
|
||||
expect(s.scene.buttonInstances).toHaveLength(1)
|
||||
})
|
||||
|
||||
test('removeButtonClip cascades its instances', () => {
|
||||
const s = makeFreshStore()
|
||||
const bc = s.addButtonClip('B')
|
||||
const ct = s.addClipTrack()
|
||||
s.addButtonInstance(bc.id, ct.id, 0)
|
||||
s.addButtonInstance(bc.id, ct.id, 1)
|
||||
s.removeButtonClip(bc.id)
|
||||
expect(s.scene.buttonClips).toHaveLength(0)
|
||||
expect(s.scene.buttonInstances).toHaveLength(0)
|
||||
})
|
||||
|
||||
test('setButtonInstanceLabel enforces scene-wide uniqueness', () => {
|
||||
const s = makeFreshStore()
|
||||
const bc = s.addButtonClip('B')
|
||||
const ct = s.addClipTrack()
|
||||
const a = s.addButtonInstance(bc.id, ct.id, 0)!
|
||||
const b = s.addButtonInstance(bc.id, ct.id, 1)!
|
||||
expect(s.setButtonInstanceLabel(a.id, 'play')).toBe(true)
|
||||
expect(s.setButtonInstanceLabel(b.id, 'play')).toBe(false)
|
||||
expect(s.setButtonInstanceLabel(b.id, 'mute')).toBe(true)
|
||||
const getA = s.scene.buttonInstances?.find((x) => x.id === a.id)
|
||||
const getB = s.scene.buttonInstances?.find((x) => x.id === b.id)
|
||||
expect(getA?.label).toBe('play')
|
||||
expect(getB?.label).toBe('mute')
|
||||
})
|
||||
|
||||
test('setButtonInstanceOnClick + setButtonInstanceDisabled', () => {
|
||||
const s = makeFreshStore()
|
||||
const bc = s.addButtonClip('B')
|
||||
const ct = s.addClipTrack()
|
||||
const inst = s.addButtonInstance(bc.id, ct.id, 0)!
|
||||
s.setButtonInstanceOnClick(inst.id, [{ type: 'pause' }])
|
||||
s.setButtonInstanceDisabled(inst.id, true)
|
||||
const get = () => s.scene.buttonInstances?.find((x) => x.id === inst.id)
|
||||
expect(get()?.onClick).toHaveLength(1)
|
||||
expect(get()?.disabled).toBe(true)
|
||||
s.setButtonInstanceOnClick(inst.id, [])
|
||||
s.setButtonInstanceDisabled(inst.id, false)
|
||||
expect(get()?.onClick).toBe(undefined)
|
||||
expect(get()?.disabled).toBe(undefined)
|
||||
})
|
||||
})
|
||||
|
||||
suite('scene store — macro model normalization', () => {
|
||||
test('loadScene initializes new arrays when missing', () => {
|
||||
const s = makeFreshStore()
|
||||
const bare = {
|
||||
version: 1 as const,
|
||||
canvas: { width: 640, height: 480, background: '#ffffff' },
|
||||
duration: 5,
|
||||
fps: 30,
|
||||
assets: [],
|
||||
layers: [],
|
||||
}
|
||||
s.loadScene(bare)
|
||||
expect(s.scene.buttonClips).toEqual([])
|
||||
expect(s.scene.buttonInstances).toEqual([])
|
||||
expect(s.scene.scriptTracks).toEqual([])
|
||||
expect(s.scene.scriptFrames).toEqual([])
|
||||
expect(s.scene.markers).toEqual([])
|
||||
})
|
||||
|
||||
test('setTrackLocked also works on script tracks', () => {
|
||||
const s = makeFreshStore()
|
||||
const t = s.addScriptTrack()
|
||||
s.setTrackLocked(t.id, true)
|
||||
expect(s.scene.scriptTracks?.find((x) => x.id === t.id)?.locked).toBe(true)
|
||||
s.setTrackLocked(t.id, false)
|
||||
expect(s.scene.scriptTracks?.find((x) => x.id === t.id)?.locked).toBe(undefined)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,16 +1,22 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import type {
|
||||
Action,
|
||||
AnimatableProps,
|
||||
Asset,
|
||||
BackgroundVideo,
|
||||
ButtonClip,
|
||||
ButtonInstance,
|
||||
Clip,
|
||||
ClipInstance,
|
||||
ClipTrack,
|
||||
Easing,
|
||||
Keyframe,
|
||||
Layer,
|
||||
Marker,
|
||||
MaskMode,
|
||||
Scene,
|
||||
ScriptFrame,
|
||||
ScriptTrack,
|
||||
TrackGroup,
|
||||
} from '@/types/scene'
|
||||
import { DEFAULT_BASE_PROPS } from '@/types/scene'
|
||||
@@ -112,6 +118,11 @@ function normalizeScene(scene: Scene): Scene {
|
||||
scene.clipTracks ||= []
|
||||
scene.clipInstances ||= []
|
||||
scene.groups ||= []
|
||||
scene.buttonClips ||= []
|
||||
scene.buttonInstances ||= []
|
||||
scene.scriptTracks ||= []
|
||||
scene.scriptFrames ||= []
|
||||
scene.markers ||= []
|
||||
if (!scene.canvas.background) scene.canvas.background = '#ffffff'
|
||||
|
||||
const known = new Set<string>()
|
||||
@@ -1001,12 +1012,194 @@ export const useSceneStore = defineStore('scene', {
|
||||
else delete clipTrack.locked
|
||||
return
|
||||
}
|
||||
const scriptTrack = this.scene.scriptTracks?.find((t) => t.id === trackId)
|
||||
if (scriptTrack) {
|
||||
if (locked) scriptTrack.locked = true
|
||||
else delete scriptTrack.locked
|
||||
return
|
||||
}
|
||||
const group = this.scene.groups?.find((g) => g.id === trackId)
|
||||
if (group) {
|
||||
if (locked) group.locked = true
|
||||
else delete group.locked
|
||||
}
|
||||
},
|
||||
|
||||
// -------- Markers --------------------------------------------------
|
||||
|
||||
addMarker(time: number, name?: string, color?: string | null): Marker {
|
||||
const t = Math.max(0, Math.min(this.scene.duration, time))
|
||||
const markers = (this.scene.markers ||= [])
|
||||
const used = new Set(markers.map((m) => m.name))
|
||||
let base = (name ?? '').trim() || 'Marker'
|
||||
let final = base
|
||||
let i = 2
|
||||
while (used.has(final)) final = `${base} ${i++}`
|
||||
const m: Marker = { id: uid(), name: final, time: t }
|
||||
if (color != null) m.color = color
|
||||
markers.push(m)
|
||||
markers.sort((a, b) => a.time - b.time)
|
||||
return m
|
||||
},
|
||||
removeMarker(id: string) {
|
||||
if (!this.scene.markers) return
|
||||
this.scene.markers = this.scene.markers.filter((m) => m.id !== id)
|
||||
},
|
||||
renameMarker(id: string, name: string): boolean {
|
||||
const markers = this.scene.markers
|
||||
if (!markers) return false
|
||||
const target = markers.find((m) => m.id === id)
|
||||
if (!target) return false
|
||||
const next = name.trim()
|
||||
if (!next) return false
|
||||
if (next === target.name) return true
|
||||
if (markers.some((m) => m.id !== id && m.name === next)) return false
|
||||
target.name = next
|
||||
return true
|
||||
},
|
||||
setMarkerTime(id: string, time: number) {
|
||||
const marker = this.scene.markers?.find((m) => m.id === id)
|
||||
if (!marker) return
|
||||
marker.time = Math.max(0, Math.min(this.scene.duration, time))
|
||||
this.scene.markers?.sort((a, b) => a.time - b.time)
|
||||
},
|
||||
setMarkerColor(id: string, color: string | null) {
|
||||
const marker = this.scene.markers?.find((m) => m.id === id)
|
||||
if (!marker) return
|
||||
if (color == null) delete marker.color
|
||||
else marker.color = color
|
||||
},
|
||||
|
||||
// -------- Script tracks & frames -----------------------------------
|
||||
|
||||
addScriptTrack(name?: string): ScriptTrack {
|
||||
const track: ScriptTrack = { id: uid(), name }
|
||||
;(this.scene.scriptTracks ||= []).push(track)
|
||||
;(this.scene.trackOrder ||= []).push(track.id)
|
||||
return track
|
||||
},
|
||||
removeScriptTrack(id: string) {
|
||||
if (this.scene.scriptTracks) {
|
||||
this.scene.scriptTracks = this.scene.scriptTracks.filter((t) => t.id !== id)
|
||||
}
|
||||
if (this.scene.scriptFrames) {
|
||||
this.scene.scriptFrames = this.scene.scriptFrames.filter((f) => f.trackId !== id)
|
||||
}
|
||||
if (this.scene.trackOrder) {
|
||||
this.scene.trackOrder = this.scene.trackOrder.filter((tid) => tid !== id)
|
||||
}
|
||||
},
|
||||
setScriptTrackName(id: string, name: string) {
|
||||
const track = this.scene.scriptTracks?.find((t) => t.id === id)
|
||||
if (track) track.name = name.trim() || undefined
|
||||
},
|
||||
addScriptFrame(
|
||||
trackId: string,
|
||||
time: number,
|
||||
actions: Action[] = [],
|
||||
label?: string,
|
||||
): ScriptFrame | null {
|
||||
const track = this.scene.scriptTracks?.find((t) => t.id === trackId)
|
||||
if (!track) return null
|
||||
const t = Math.max(0, Math.min(this.scene.duration, time))
|
||||
const frame: ScriptFrame = {
|
||||
id: uid(),
|
||||
trackId,
|
||||
time: t,
|
||||
actions: [...actions],
|
||||
}
|
||||
if (label) frame.label = label
|
||||
;(this.scene.scriptFrames ||= []).push(frame)
|
||||
this.scene.scriptFrames.sort((a, b) => a.time - b.time)
|
||||
return frame
|
||||
},
|
||||
removeScriptFrame(id: string) {
|
||||
if (!this.scene.scriptFrames) return
|
||||
this.scene.scriptFrames = this.scene.scriptFrames.filter((f) => f.id !== id)
|
||||
},
|
||||
setScriptFrameActions(id: string, actions: Action[]) {
|
||||
const frame = this.scene.scriptFrames?.find((f) => f.id === id)
|
||||
if (frame) frame.actions = [...actions]
|
||||
},
|
||||
setScriptFrameTime(id: string, time: number) {
|
||||
const frame = this.scene.scriptFrames?.find((f) => f.id === id)
|
||||
if (!frame) return
|
||||
frame.time = Math.max(0, Math.min(this.scene.duration, time))
|
||||
this.scene.scriptFrames?.sort((a, b) => a.time - b.time)
|
||||
},
|
||||
|
||||
// -------- Button clips & instances ---------------------------------
|
||||
|
||||
addButtonClip(name?: string): ButtonClip {
|
||||
const clip: ButtonClip = {
|
||||
id: uid(),
|
||||
name: name?.trim() || 'Button',
|
||||
states: { normal: [], hover: [], pressed: [], disabled: [] },
|
||||
}
|
||||
;(this.scene.buttonClips ||= []).push(clip)
|
||||
return clip
|
||||
},
|
||||
removeButtonClip(id: string) {
|
||||
if (this.scene.buttonClips) {
|
||||
this.scene.buttonClips = this.scene.buttonClips.filter((c) => c.id !== id)
|
||||
}
|
||||
if (this.scene.buttonInstances) {
|
||||
this.scene.buttonInstances = this.scene.buttonInstances.filter(
|
||||
(bi) => bi.buttonClipId !== id,
|
||||
)
|
||||
}
|
||||
},
|
||||
setButtonClipName(id: string, name: string) {
|
||||
const clip = this.scene.buttonClips?.find((c) => c.id === id)
|
||||
if (clip) clip.name = name.trim() || clip.name
|
||||
},
|
||||
addButtonInstance(
|
||||
buttonClipId: string,
|
||||
clipTrackId: string,
|
||||
startTime: number,
|
||||
): ButtonInstance | null {
|
||||
if (!this.scene.buttonClips?.some((c) => c.id === buttonClipId)) return null
|
||||
if (!this.scene.clipTracks?.some((t) => t.id === clipTrackId)) return null
|
||||
const inst: ButtonInstance = {
|
||||
id: uid(),
|
||||
buttonClipId,
|
||||
clipTrackId,
|
||||
startTime: Math.max(0, Math.min(this.scene.duration, startTime)),
|
||||
}
|
||||
;(this.scene.buttonInstances ||= []).push(inst)
|
||||
return inst
|
||||
},
|
||||
removeButtonInstance(id: string) {
|
||||
if (!this.scene.buttonInstances) return
|
||||
this.scene.buttonInstances = this.scene.buttonInstances.filter((bi) => bi.id !== id)
|
||||
},
|
||||
setButtonInstanceLabel(id: string, label: string): boolean {
|
||||
const instances = this.scene.buttonInstances
|
||||
if (!instances) return false
|
||||
const target = instances.find((bi) => bi.id === id)
|
||||
if (!target) return false
|
||||
const next = label.trim()
|
||||
if (!next) {
|
||||
delete target.label
|
||||
return true
|
||||
}
|
||||
if (next === target.label) return true
|
||||
if (instances.some((bi) => bi.id !== id && bi.label === next)) return false
|
||||
target.label = next
|
||||
return true
|
||||
},
|
||||
setButtonInstanceOnClick(id: string, actions: Action[]) {
|
||||
const target = this.scene.buttonInstances?.find((bi) => bi.id === id)
|
||||
if (!target) return
|
||||
if (actions.length) target.onClick = [...actions]
|
||||
else delete target.onClick
|
||||
},
|
||||
setButtonInstanceDisabled(id: string, disabled: boolean) {
|
||||
const target = this.scene.buttonInstances?.find((bi) => bi.id === id)
|
||||
if (!target) return
|
||||
if (disabled) target.disabled = true
|
||||
else delete target.disabled
|
||||
},
|
||||
setLayerSeparated(layerId: string, separated: boolean) {
|
||||
const layer = this.activeLayers.find((l) => l.id === layerId)
|
||||
if (!layer) return
|
||||
|
||||
@@ -162,6 +162,84 @@ export interface BackgroundVideo {
|
||||
opacity?: number
|
||||
}
|
||||
|
||||
// ---- Macro clips & scripts ------------------------------------------------
|
||||
|
||||
export type ButtonState = 'normal' | 'hover' | 'pressed' | 'disabled'
|
||||
|
||||
export interface StateTransition {
|
||||
from: ButtonState
|
||||
to: ButtonState
|
||||
duration: number
|
||||
easing: Easing
|
||||
}
|
||||
|
||||
export interface ButtonHitArea {
|
||||
width: number
|
||||
height: number
|
||||
offsetX?: number
|
||||
offsetY?: number
|
||||
}
|
||||
|
||||
export interface ButtonClip {
|
||||
id: string
|
||||
name: string
|
||||
states: {
|
||||
normal: Layer[]
|
||||
hover: Layer[]
|
||||
pressed: Layer[]
|
||||
disabled: Layer[]
|
||||
}
|
||||
transitions?: StateTransition[]
|
||||
hitArea?: ButtonHitArea
|
||||
}
|
||||
|
||||
export interface ButtonInstance {
|
||||
id: string
|
||||
buttonClipId: string
|
||||
clipTrackId: string
|
||||
startTime: number
|
||||
/** Unique, user-facing handle used in dropdowns and script DSL. */
|
||||
label?: string
|
||||
onClick?: Action[]
|
||||
/** Initial disabled state at runtime start. */
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export interface ScriptTrack {
|
||||
id: string
|
||||
name?: string
|
||||
color?: string | null
|
||||
locked?: boolean
|
||||
}
|
||||
|
||||
export interface ScriptFrame {
|
||||
id: string
|
||||
trackId: string
|
||||
time: number
|
||||
actions: Action[]
|
||||
label?: string
|
||||
}
|
||||
|
||||
export interface Marker {
|
||||
id: string
|
||||
name: string
|
||||
time: number
|
||||
color?: string | null
|
||||
}
|
||||
|
||||
export type Action =
|
||||
| { type: 'play' }
|
||||
| { type: 'pause' }
|
||||
| { type: 'rewindToLoop' }
|
||||
| { type: 'jumpTo'; time: number }
|
||||
| { type: 'jumpToFrame'; frame: number }
|
||||
| { type: 'jumpToMarker'; name: string }
|
||||
| { type: 'setButtonState'; instanceId: string; state: ButtonState }
|
||||
| { type: 'setButtonDisabled'; instanceId: string; disabled: boolean }
|
||||
| { type: 'script'; source: string }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface Scene {
|
||||
version: 1
|
||||
canvas: {
|
||||
@@ -183,6 +261,11 @@ export interface Scene {
|
||||
trackOrder?: string[]
|
||||
groups?: TrackGroup[]
|
||||
backgroundVideo?: BackgroundVideo
|
||||
buttonClips?: ButtonClip[]
|
||||
buttonInstances?: ButtonInstance[]
|
||||
scriptTracks?: ScriptTrack[]
|
||||
scriptFrames?: ScriptFrame[]
|
||||
markers?: Marker[]
|
||||
}
|
||||
|
||||
export const DEFAULT_BASE_PROPS: AnimatableProps = {
|
||||
|
||||
Reference in new issue
Block a user