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
+32
-18
@@ -314,11 +314,16 @@ function normalizeScenePage(page: ScenePage): ScenePage {
|
||||
for (const st of page.scriptTracks) known.add(st.id)
|
||||
for (const at of page.audioTracks) known.add(at.id)
|
||||
|
||||
const layerAndClipIds = new Set<string>()
|
||||
for (const l of page.layers) layerAndClipIds.add(l.id)
|
||||
for (const t of page.clipTracks) layerAndClipIds.add(t.id)
|
||||
// Any non-group track may live inside a group — layers, clip tracks,
|
||||
// script tracks and audio tracks are all first-class citizens. Groups
|
||||
// themselves stay at the top level (no nested groups).
|
||||
const groupableIds = new Set<string>()
|
||||
for (const l of page.layers) groupableIds.add(l.id)
|
||||
for (const t of page.clipTracks) groupableIds.add(t.id)
|
||||
for (const st of page.scriptTracks) groupableIds.add(st.id)
|
||||
for (const at of page.audioTracks) groupableIds.add(at.id)
|
||||
for (const g of page.groups) {
|
||||
g.childIds = g.childIds.filter((id) => layerAndClipIds.has(id))
|
||||
g.childIds = g.childIds.filter((id) => groupableIds.has(id))
|
||||
}
|
||||
const inGroup = new Set<string>()
|
||||
for (const g of page.groups) {
|
||||
@@ -330,25 +335,29 @@ function normalizeScenePage(page: ScenePage): ScenePage {
|
||||
}
|
||||
|
||||
page.trackOrder = page.trackOrder.filter((id) => known.has(id))
|
||||
// Fallback for legacy / corrupted scenes where some ids are known but
|
||||
// absent from trackOrder. Layers prepend (historically stage content
|
||||
// sits on top); everything else appends in DOM order with no
|
||||
// per-kind segregation — users can reorder freely afterwards.
|
||||
const inOrder = new Set(page.trackOrder)
|
||||
for (const l of page.layers) {
|
||||
if (!inGroup.has(l.id) && !page.trackOrder.includes(l.id)) {
|
||||
if (!inGroup.has(l.id) && !inOrder.has(l.id)) {
|
||||
page.trackOrder.unshift(l.id)
|
||||
inOrder.add(l.id)
|
||||
}
|
||||
}
|
||||
for (const t of page.clipTracks) {
|
||||
if (!inGroup.has(t.id) && !page.trackOrder.includes(t.id)) {
|
||||
page.trackOrder.push(t.id)
|
||||
const tail: Array<{ id: string }> = [
|
||||
...page.clipTracks,
|
||||
...page.scriptTracks,
|
||||
...page.audioTracks,
|
||||
...page.groups,
|
||||
]
|
||||
for (const item of tail) {
|
||||
if (!inGroup.has(item.id) && !inOrder.has(item.id)) {
|
||||
page.trackOrder.push(item.id)
|
||||
inOrder.add(item.id)
|
||||
}
|
||||
}
|
||||
for (const g of page.groups) {
|
||||
if (!page.trackOrder.includes(g.id)) page.trackOrder.push(g.id)
|
||||
}
|
||||
for (const st of page.scriptTracks) {
|
||||
if (!page.trackOrder.includes(st.id)) page.trackOrder.push(st.id)
|
||||
}
|
||||
for (const at of page.audioTracks) {
|
||||
if (!page.trackOrder.includes(at.id)) page.trackOrder.push(at.id)
|
||||
}
|
||||
// Orphan audio clips (pointing at a missing track or missing asset) would
|
||||
// never render anyway — drop them so serialised projects stay clean.
|
||||
const audioTrackIds = new Set(page.audioTracks.map((t) => t.id))
|
||||
@@ -2746,12 +2755,17 @@ export const useSceneStore = defineStore('scene', {
|
||||
// Inherit the target group's color when the moved track hasn't
|
||||
// been explicitly recoloured by the user. Matches the cascade
|
||||
// behaviour of setTrackColor so a track joining a coloured group
|
||||
// visually lines up with its siblings.
|
||||
// visually lines up with its siblings — applies to all four
|
||||
// groupable kinds, not just layers + clip tracks.
|
||||
if (target.color != null) {
|
||||
const l = this.scene.layers.find((x) => x.id === trackId)
|
||||
if (l && !l.colorOverridden) l.color = target.color
|
||||
const ct = this.scene.clipTracks?.find((x) => x.id === trackId)
|
||||
if (ct && !ct.colorOverridden) ct.color = target.color
|
||||
const st = this.scene.scriptTracks?.find((x) => x.id === trackId)
|
||||
if (st && !st.colorOverridden) st.color = target.color
|
||||
const at = this.scene.audioTracks?.find((x) => x.id === trackId)
|
||||
if (at && !at.colorOverridden) at.color = target.color
|
||||
}
|
||||
},
|
||||
moveTrackOutOfGroup(trackId: string, toTopLevelIndex?: number) {
|
||||
|
||||
Reference in new issue
Block a user