0.13.0: unify all track kinds — same height, same drag, same grouping
- Scripts bumped from 26px to 30px so the five track kinds read as one visual list; clip / script / audio tracks lose their kind-specific gradient backgrounds now that they're no longer fixed-position chrome. - Audio tracks pick up the standard drag attributes + handlers; they now participate in the same trackOrder flow as layer / clip / script tracks and can be dropped into groups. - normalizeScenePage accepts scripts + audio as group children (used to silently strip them); the per-kind ordered appending is replaced with a single tail loop so no kind is hardcoded to the bottom. - moveTrackIntoGroup color cascade now covers all four groupable kinds (scripts + audio inherit group colour like layers + clip tracks). - Group expansion in Timeline emits scripts + audio as first-class sub-track children, with the shared in-group / sub-track classes and group-aware drag handlers. - Tests: move-into-group + color cascade for scripts + audio, legacy childIds roundtrip preserves both kinds, audio track is draggable, script track is 30px. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
354dd8a4f0
commit
86ec7aa835
5 files changed
+227
-63
No files matched your search
@@ -2950,6 +2950,63 @@ export function registerAllTests(): void {
|
||||
const inG2 = g2.childIds.filter((id) => id === l.id).length
|
||||
expect(inG1 + inG2).toBe(1)
|
||||
})
|
||||
|
||||
test('moveTrackIntoGroup accepts script tracks as children', () => {
|
||||
const s = makeFreshStore()
|
||||
const st = s.addScriptTrack('Logic')
|
||||
const g = s.addTrackGroup('G')
|
||||
s.moveTrackIntoGroup(st.id, g.id)
|
||||
expect(g.childIds).toContain(st.id)
|
||||
expect((s.scene.trackOrder ?? []).includes(st.id)).toBe(false)
|
||||
})
|
||||
|
||||
test('moveTrackIntoGroup accepts audio tracks as children', () => {
|
||||
const s = makeFreshStore()
|
||||
const at = s.addAudioTrack('FX')
|
||||
const g = s.addTrackGroup('G')
|
||||
s.moveTrackIntoGroup(at.id, g.id)
|
||||
expect(g.childIds).toContain(at.id)
|
||||
expect((s.scene.trackOrder ?? []).includes(at.id)).toBe(false)
|
||||
})
|
||||
|
||||
test('group color cascades to script + audio children alongside layers', () => {
|
||||
const s = makeFreshStore()
|
||||
const a = s.addAsset({ kind: 'rect', name: 'Mix', width: 10, height: 10, fill: '#000' })
|
||||
const l = s.addLayer(a.id)!
|
||||
const st = s.addScriptTrack('Script')
|
||||
const at = s.addAudioTrack('Audio')
|
||||
const g = s.addTrackGroup('G')
|
||||
s.setTrackColor(g.id, '#ff8800')
|
||||
s.moveTrackIntoGroup(l.id, g.id)
|
||||
s.moveTrackIntoGroup(st.id, g.id)
|
||||
s.moveTrackIntoGroup(at.id, g.id)
|
||||
expect(l.color).toBe('#ff8800')
|
||||
expect(st.color).toBe('#ff8800')
|
||||
expect(at.color).toBe('#ff8800')
|
||||
// None of them were explicitly recoloured — all inherit, none pin.
|
||||
expect(l.colorOverridden).toBe(undefined)
|
||||
expect(st.colorOverridden).toBe(undefined)
|
||||
expect(at.colorOverridden).toBe(undefined)
|
||||
})
|
||||
|
||||
test('normalizeScene preserves script + audio tracks as group children', () => {
|
||||
// Legacy loads used to strip anything other than layers / clip
|
||||
// tracks from childIds. All four non-group kinds must survive
|
||||
// the roundtrip now.
|
||||
const s = makeFreshStore()
|
||||
const st = s.addScriptTrack('S')
|
||||
const at = s.addAudioTrack('A')
|
||||
const g = s.addTrackGroup('G')
|
||||
const snap = JSON.parse(JSON.stringify(s.scene))
|
||||
snap.groups = [{ id: g.id, name: 'G', childIds: [st.id, at.id] }]
|
||||
snap.trackOrder = [g.id]
|
||||
s.loadScene(snap)
|
||||
const loaded = s.scene.groups?.find((x) => x.id === g.id)!
|
||||
expect(loaded.childIds).toContain(st.id)
|
||||
expect(loaded.childIds).toContain(at.id)
|
||||
expect((s.scene.trackOrder ?? []).includes(st.id)).toBe(false)
|
||||
expect((s.scene.trackOrder ?? []).includes(at.id)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
suite('scene store — duplicateTrack', () => {
|
||||
@@ -5022,6 +5079,34 @@ export function registerAllTests(): void {
|
||||
handle.unmount()
|
||||
})
|
||||
|
||||
test('audio track name carries the draggable attribute so it joins drag-reorder', async () => {
|
||||
// Audio tracks used to render with no @dragstart, which left them
|
||||
// outside the stack-reorder flow. They now share the same hooks
|
||||
// as layer / clip / script tracks.
|
||||
const s = makeFreshStore()
|
||||
s.addAudioTrack('Draggable')
|
||||
const handle = mountComponent(Timeline, {})
|
||||
await nextTick()
|
||||
const name = handle.host.querySelector('.audio-track .track-name') as HTMLElement
|
||||
expect(name).toBeTruthy()
|
||||
expect(name.getAttribute('draggable')).toBe('true')
|
||||
handle.unmount()
|
||||
})
|
||||
|
||||
test('script track matches the default 30px track height', async () => {
|
||||
// Regression: scripts used to be 26px, now unified with the other
|
||||
// kinds so the timeline stack reads as a single visual list.
|
||||
const s = makeFreshStore()
|
||||
s.addScriptTrack('S')
|
||||
const handle = mountComponent(Timeline, {})
|
||||
await nextTick()
|
||||
const st = handle.host.querySelector('.script-track') as HTMLElement
|
||||
expect(st).toBeTruthy()
|
||||
const h = getComputedStyle(st).height
|
||||
expect(h).toBe('30px')
|
||||
handle.unmount()
|
||||
})
|
||||
|
||||
test('audio track co-exists with other track kinds in the tracks container', async () => {
|
||||
const s = makeFreshStore()
|
||||
const a = s.addAsset({ kind: 'rect', name: 'R', width: 10, height: 10, fill: '#111' })
|
||||
|
||||
Reference in new issue
Block a user