The button authoring ended up too far from the intended model after three correction passes. Pulling it out entirely so we can rebuild from a clean spec. Markers, script tracks, script frames, the Action editor, the Script DSL and the runtime for all of the above stay — they work. Removed: - types: ButtonState, StateTransition, ButtonHitArea, ButtonClip, ButtonInstance, setButtonState/setButtonDisabled Action variants, Scene.buttonClips/buttonInstances - store: activeButtonClip* getters, all add/remove/setButton* actions, seedDefaultButtonClip, enterButtonEdit/setActiveButtonState/ exitButtonEdit, button-label resolution in actionRuntime - useButtonRuntime composable, buttonBlend helper, ButtonInstanceDialog, sidebar Buttons section, Timeline button rendering + pointer handlers, CanvasStage button rendering + pointer state machine, exportPlayer button runtime + hit-testing - DSL commands state/enable/disable and their parse errors - 35 button-related tests + helpers; stale testing doc; stale design doc Re-applied three uncommitted fixes that were unrelated to the button work: - clicking a marker no longer jumps the playhead (only drag / dblclick act on it) - script tracks get a right-click context menu (rename + delete, color in the existing palette) - onTracksPointerDown excludes .script-lane / .script-frame so clicks on empty lanes or on frame dots reach the frame-open path instead of being swallowed by box-select pointer capture Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
80 lines
2.3 KiB
TypeScript
80 lines
2.3 KiB
TypeScript
import type { Action } from '@/types/scene'
|
|
|
|
export interface ParseError {
|
|
line: number
|
|
message: string
|
|
}
|
|
|
|
export interface ParseResult {
|
|
actions: Action[]
|
|
errors: ParseError[]
|
|
}
|
|
|
|
/**
|
|
* Parse a small line-based DSL into Action[]. Comments start with `//`
|
|
* and each non-empty line is one command. Known commands:
|
|
*
|
|
* play | pause | rewind
|
|
* jump <seconds> (e.g. `jump 2.5`)
|
|
* jump #<frame> (e.g. `jump #135`)
|
|
* jump @<marker> (e.g. `jump @intro`)
|
|
*/
|
|
export function parseScript(source: string): ParseResult {
|
|
const actions: Action[] = []
|
|
const errors: ParseError[] = []
|
|
const lines = source.split(/\r?\n/)
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const stripped = stripComment(lines[i]).trim()
|
|
if (!stripped) continue
|
|
const res = parseLine(stripped)
|
|
if (res.error) errors.push({ line: i + 1, message: res.error })
|
|
else if (res.action) actions.push(res.action)
|
|
}
|
|
return { actions, errors }
|
|
}
|
|
|
|
function stripComment(line: string): string {
|
|
const i = line.indexOf('//')
|
|
return i < 0 ? line : line.slice(0, i)
|
|
}
|
|
|
|
function parseLine(line: string): { action?: Action; error?: string } {
|
|
const tokens = line.split(/\s+/).filter(Boolean)
|
|
if (!tokens.length) return {}
|
|
const cmd = tokens[0].toLowerCase()
|
|
switch (cmd) {
|
|
case 'play':
|
|
return ok({ type: 'play' })
|
|
case 'pause':
|
|
return ok({ type: 'pause' })
|
|
case 'rewind':
|
|
return ok({ type: 'rewindToLoop' })
|
|
case 'jump':
|
|
return parseJump(tokens)
|
|
default:
|
|
return { error: `unknown command "${cmd}"` }
|
|
}
|
|
}
|
|
|
|
function ok(action: Action): { action: Action } {
|
|
return { action }
|
|
}
|
|
|
|
function parseJump(tokens: string[]): { action?: Action; error?: string } {
|
|
if (tokens.length < 2) return { error: 'jump: missing argument' }
|
|
const arg = tokens[1]
|
|
if (arg.startsWith('#')) {
|
|
const frame = Number(arg.slice(1))
|
|
if (!Number.isFinite(frame)) return { error: `jump: invalid frame "${arg}"` }
|
|
return ok({ type: 'jumpToFrame', frame })
|
|
}
|
|
if (arg.startsWith('@')) {
|
|
const name = arg.slice(1)
|
|
if (!name) return { error: 'jump: missing marker name after @' }
|
|
return ok({ type: 'jumpToMarker', name })
|
|
}
|
|
const time = Number(arg)
|
|
if (!Number.isFinite(time)) return { error: `jump: invalid time "${arg}"` }
|
|
return ok({ type: 'jumpTo', time })
|
|
}
|