diff --git a/apps/studio/src/ClipPicture.tsx b/apps/studio/src/ClipPicture.tsx index 491b3c5..3cd9fb8 100644 --- a/apps/studio/src/ClipPicture.tsx +++ b/apps/studio/src/ClipPicture.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "preact/hooks"; import { clipLayersOf, layersAt, type Layer } from "@openclapper/vocab"; import { fetchLayer } from "./api"; import { Composition, useShotLayers } from "./compose/stack"; -import { stepBake } from "./format"; +import { stepBake, stepStale } from "./format"; import { Framed, type MaskMode } from "./Framed"; import { presentationFrame } from "./FramingSelect"; import type { ResolvedClip } from "./TimelineParts"; @@ -28,12 +28,15 @@ export function useClipLayers(own: readonly Layer[]): Layer[] { /** * A photo clip's picture (epic #275): live on the stage and in the clip panel (the step's layers - * from the shot, then the clip's own), its step's bake in the strip (a layers glyph says it has - * its own on top). + * from the shot, then the clip's own). The strip uses the step's bake when that shows everything, + * and composes live when the clip has its own layers or the bake is not made yet, so a thumb never + * drops what the stage shows. */ export function ClipPicture({ c, mode, maxHeight, live }: { c: PhotoClip; mode: MaskMode; maxHeight?: string; live?: boolean }) { const frame = presentationFrame(c.photo, c.pres); - if (!live) return ; + const bake = stepBake(c.shot, c.step); + const needsLive = live || c.ownLayers.length > 0 || (!bake && stepStale(c.shot, c.step)); + if (!needsLive) return ; return ; } diff --git a/docs/features/timeline.md b/docs/features/timeline.md index 3daa743..edc2e3e 100644 --- a/docs/features/timeline.md +++ b/docs/features/timeline.md @@ -44,7 +44,7 @@ On the desktop Timeline is one window (`apps/TimelineApp.tsx`) for all of the pr - **Head**: name, total length, the **Presentation lock** ("Per clip" or one mode for all), **Review**: "Approve all · n" while shots in the cut are unreviewed (approves those), else "Back to review · n" for the approved ones (archived shots are left alone; one bulk request, Undo in the toast), save status, menu (Rename…, Delete timeline…). Changes **autosave** 600 ms after the last edit and at once when leaving the page; the server's answer replaces the clip list, so clips of deleted photos disappear on the next save. - **Preview**: the selected clip, large, in its presentation, composed live (its step's layers, then its own; a sketch clip's sketch on white). The stage keeps **one height** for every clip (`--tl-stage-h`): shots, sketches and placeholders letterbox into it, so the layout never jumps. The grip under the stage **resizes** it (remembered per browser in `localStorage["timelineStageH"]`; double-click goes back to the default, which fills the window). Transport: first · previous · **Play/Pause** (`Space`) · next · last, the playhead clock over the total, "clip n of m · 3.0 s", **Placeholder** (`P`) and **Shots** (`N`: the browser ⇄ the clip panel). Playback steps through the clips at their hold times (100 ms ticks) and stops at the end; Play at the end starts over. -- **Strip**: one block per clip, its width proportional to the hold time (44 px a second by default; zoomed out a clip keeps at least 20–64 px), with a thumbnail (its step's bake; a photo clip's carries its shot's state marker), the shot's title (and its place in the block for sequences; a layers icon marks a clip with layers of its own) and the hold time. Placeholders are dashed. A ruler labels the time (the spacing adapts to the zoom); **dragging along the ruler scrubs** (the preview follows; playback, if running, continues from there). +- **Strip**: one block per clip, its width proportional to the hold time (44 px a second by default; zoomed out a clip keeps at least 20–64 px), with a thumbnail (its step's bake, composed live instead while the step is not baked yet or when the clip has layers of its own; a photo clip's carries its shot's state marker), the shot's title (and its place in the block for sequences; a layers icon marks a clip with layers of its own) and the hold time. Placeholders are dashed. A ruler labels the time (the spacing adapts to the zoom); **dragging along the ruler scrubs** (the preview follows; playback, if running, continues from there). - **Zoom**: Ctrl/⌘ + scroll over the strip zooms around the pointer; the bar above has −, a slider (double-click: default), +, and **Fit** (the whole cut in the width). Remembered in `localStorage["timelineZoom"]`. - **Playhead**: one red line across ruler and clips with a **knob above the strip**; drag the knob to scrub. - **Height**: the grip under the strip sets the thumbnail height (double-click resets; `localStorage["timelineStripH"]`). Click selects, double-click opens the shot view with the timeline's shots as its list, right-click opens the clip menu (Re-frame, Layers or Sketch it, Approve / Archive / Back to review for the clip's shot, Open shot, Extract as own shot…, Duplicate, Remove). **Drag a clip** to reorder (an accent line marks the drop position); shots drop in from the browser.