opencut_ui: inline clip reorder controls on the timeline
Reordering was Edit-menu-only (Move Clip Left/Right). Added ◀ ▶ buttons to each clip's head bar (next to Remove) that call the same moveTimelineClip, disabled at the ends. Works for both generative and video clips. (Add was already on the timeline toolbar via + Add → Clip.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a534f8bb6f
commit
247ab84e34
@@ -938,6 +938,9 @@ body.tl-spacing { cursor: grabbing; user-select: none; }
|
||||
.clip-src { font-size: 12px; color: var(--faint); flex-shrink: 0; }
|
||||
.clip-dur { font-family: var(--font-mono); font-size: 10px; color: var(--faint); white-space: nowrap; }
|
||||
.clip-rm.btn.tiny { padding: 1px 6px; font-size: 10px; line-height: 1.25; flex-shrink: 0; }
|
||||
.clip-move.btn.tiny { padding: 1px 5px; font-size: 9px; line-height: 1.25; flex-shrink: 0; color: var(--muted); }
|
||||
.clip-move.btn.tiny:hover { color: var(--amber-2); }
|
||||
.clip-move.disabled { opacity: .3; cursor: default; pointer-events: none; }
|
||||
.clip-text { font-size: 12px; line-height: 1.35; color: var(--text); overflow: hidden;
|
||||
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
|
||||
.clip-text.empty { color: var(--faint); font-style: italic; }
|
||||
|
||||
@@ -47,9 +47,21 @@
|
||||
head.append(el("span", "clip-src", SRC_ICON[srcType] || "▦"));
|
||||
}
|
||||
|
||||
function appendClipHeadBar(head, durSec, fps, onRemove, removeTitle) {
|
||||
// `reorder` (optional): { canLeft, canRight, onLeft, onRight } adds inline move
|
||||
// controls so clips can be reordered directly on the timeline (not just the Edit menu).
|
||||
function appendClipHeadBar(head, durSec, fps, onRemove, removeTitle, reorder) {
|
||||
const bar = el("div", "clip-head-bar");
|
||||
bar.append(el("span", "clip-dur", timecode(durSec, fps)));
|
||||
if (reorder) {
|
||||
const mk = (glyph, can, fn, title) => {
|
||||
const b = el("button", "clip-move btn ghost tiny" + (can ? "" : " disabled"), glyph);
|
||||
b.type = "button"; b.title = title; b.disabled = !can;
|
||||
if (can) b.onclick = (e) => { e.stopPropagation(); fn(); };
|
||||
return b;
|
||||
};
|
||||
bar.append(mk("◀", reorder.canLeft, reorder.onLeft, "Move clip left"));
|
||||
bar.append(mk("▶", reorder.canRight, reorder.onRight, "Move clip right"));
|
||||
}
|
||||
const rm = el("button", "clip-rm btn ghost tiny danger", "Remove");
|
||||
rm.type = "button";
|
||||
rm.title = removeTitle || "Remove clip (Delete / Backspace)";
|
||||
@@ -530,7 +542,7 @@
|
||||
}
|
||||
|
||||
// ── clip ───────────────────────────────────────────────────────────────────────
|
||||
function clipEl(st, p, scene, index, leftPx, widthPx) {
|
||||
function clipEl(st, p, scene, index, total, leftPx, widthPx) {
|
||||
const unitCuts = (p.scenes || []).filter((s) => (s.gen_unit_id || s.id) === (scene.gen_unit_id || scene.id)).length;
|
||||
const subclip = (scene.cut_offset_frames || 0) > 0;
|
||||
const src = sceneSourceForClip(scene, p);
|
||||
@@ -645,7 +657,12 @@
|
||||
const head = el("div", "clip-head");
|
||||
head.append(el("span", "clip-no", S.isVideoClip(scene) ? "V" : p2(index + 1)));
|
||||
appendSrcBadge(head, srcType);
|
||||
appendClipHeadBar(head, sDur(scene, p), sFps(scene, p), () => S.removeScene(scene.id));
|
||||
appendClipHeadBar(head, sDur(scene, p), sFps(scene, p), () => S.removeScene(scene.id), null, {
|
||||
canLeft: index > 0,
|
||||
canRight: total != null && index < total - 1,
|
||||
onLeft: () => S.moveTimelineClip(scene.id, -1),
|
||||
onRight: () => S.moveTimelineClip(scene.id, 1),
|
||||
});
|
||||
clip.append(head);
|
||||
|
||||
const root = unitCuts > 1
|
||||
@@ -1897,7 +1914,7 @@
|
||||
lay.forEach(({ seg, o, d }) => {
|
||||
if (seg.kind === "gap") return;
|
||||
if (seg.kind === "ghost") track.append(ghostClipEl(st, p, seg.ghost, o * pxPerSec, d * pxPerSec));
|
||||
else track.append(clipEl(st, p, seg.scene, scenes.indexOf(seg.scene), o * pxPerSec, d * pxPerSec));
|
||||
else track.append(clipEl(st, p, seg.scene, scenes.indexOf(seg.scene), scenes.length, o * pxPerSec, d * pxPerSec));
|
||||
});
|
||||
for (let i = 0; i < scenes.length - 1; i++) {
|
||||
const prevSeg = segs.find((s) => s.kind === "scene" && s.scene.id === scenes[i].id);
|
||||
|
||||
Reference in New Issue
Block a user