Files
RUWIO/src/lib/scriptDsl.ts
T
thandClaude Opus 4.7 31604052ca revert macro-clips button feature (keep markers + scripts)
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>
2026-04-20 23:37:49 +02:00

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 })
}