0.18.0: clip instances get stable transform frame + track hide

Each clip instance now renders an invisible frame rect sized to the
union of its layers' footprints across every keyframe. The
transformer wraps that frame so its handles stay on the clip's own
outer constraints instead of chasing the currently-visible inner
shape, and the rect doubles as a hit region so empty-playhead
frames remain selectable. A plain click on a timeline clip-block
also selects the instance (drag still takes over past the 2px
dead-zone).

Clip tracks pick up the same editor-hide affordance as layer
tracks: new eye button on the track-name, setClipTrackEditorHidden
action, and filters in the Konva stackItems + render.ts so
hidden-track instances vanish in the editor. ExportDialog's
skip-hidden opt-in now also strips hidden clip tracks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.7 committed 2026-04-24 14:11:43 +02:00
1 parent a858f94bb7
commit 6784b76607
10 files changed
+764 -7

No files matched your search

+23
View File
@@ -42,6 +42,7 @@ const skipHidden = ref(false)
const hasHiddenLayers = computed(() => {
if (store.scene.layers.some((l) => l.editorHidden)) return true
if (store.scene.clipTracks?.some((t) => t.editorHidden)) return true
for (const clip of store.scene.clips ?? []) {
if (clip.layers.some((l) => l.editorHidden)) return true
}
@@ -51,6 +52,26 @@ const hasHiddenLayers = computed(() => {
return false
})
/** Strip every instance that belongs to an editor-hidden clip track,
* then drop the track itself. Shared between project + single-scene
* clones so both payload shapes stay in sync. */
function stripHiddenClipTracks(page: {
clipTracks?: { id: string; editorHidden?: boolean }[]
clipInstances?: { clipTrackId: string }[]
}) {
if (!page.clipTracks?.length) return
const hiddenIds = new Set(
page.clipTracks.filter((t) => t.editorHidden).map((t) => t.id),
)
if (!hiddenIds.size) return
if (page.clipInstances) {
page.clipInstances = page.clipInstances.filter(
(ci) => !hiddenIds.has(ci.clipTrackId),
)
}
page.clipTracks = page.clipTracks.filter((t) => !hiddenIds.has(t.id))
}
function projectClone() {
// Project-shaped payload for standalone static exports — the
// embedded runtime handles multi-scene playback natively.
@@ -58,6 +79,7 @@ function projectClone() {
if (!skipHidden.value) return project
for (const page of project.scenes) {
page.layers = page.layers.filter((l: Layer) => !l.editorHidden)
stripHiddenClipTracks(page)
}
if (Array.isArray(project.clips)) {
for (const clip of project.clips) {
@@ -82,6 +104,7 @@ function activeSceneClone() {
const scene = JSON.parse(JSON.stringify(store.scene))
if (!skipHidden.value) return scene
scene.layers = scene.layers.filter((l: Layer) => !l.editorHidden)
stripHiddenClipTracks(scene)
if (Array.isArray(scene.clips)) {
for (const clip of scene.clips) {
clip.layers = clip.layers.filter((l: Layer) => !l.editorHidden)