@pixerate/editor-react 0.60.0 → 0.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -205,6 +205,18 @@ function VideoTransport({ controller, editable = true, className = "" }) {
|
|
|
205
205
|
" / ",
|
|
206
206
|
(0, import_video_editor4.formatVideoTimecode)(duration, project.fps)
|
|
207
207
|
] }),
|
|
208
|
+
(0, import_video_editor4.videoProjectHasProxies)(project) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
209
|
+
"select",
|
|
210
|
+
{
|
|
211
|
+
className: "rounded border border-border bg-transparent px-1 py-0.5 text-xs",
|
|
212
|
+
value: String(state.previewScale),
|
|
213
|
+
onChange: (e) => controller.setPreviewScale((0, import_video_editor4.parseVideoPreviewScale)(e.target.value)),
|
|
214
|
+
"aria-label": "Preview size",
|
|
215
|
+
title: "Play proxies in the preview (the export always uses the originals)",
|
|
216
|
+
"data-preview-scale": "",
|
|
217
|
+
children: import_video_editor4.VIDEO_PREVIEW_SCALE_OPTIONS.map((o) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: o.value, children: o.label }, o.value))
|
|
218
|
+
}
|
|
219
|
+
),
|
|
208
220
|
editable && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
209
221
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-1 h-4 w-px bg-border" }),
|
|
210
222
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: btn, onClick: () => controller.splitAtPlayhead(), disabled: !canEdit, title: "Split at the playhead (S)", "data-split": "", children: "Split" }),
|
|
@@ -253,6 +265,27 @@ function ClipWaveform({ controller, clip, asset, width, height }) {
|
|
|
253
265
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { className: "pointer-events-none absolute inset-0", width, height, "aria-hidden": "true", "data-waveform": clip.id, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d, fill: "currentColor", fillOpacity: 0.45 }) });
|
|
254
266
|
}
|
|
255
267
|
var isField = (el) => el instanceof HTMLElement && /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName);
|
|
268
|
+
function useCachedRanges(controller, project, px) {
|
|
269
|
+
const [ranges, setRanges] = (0, import_react4.useState)([]);
|
|
270
|
+
(0, import_react4.useEffect)(() => {
|
|
271
|
+
const cache = controller.frameCache;
|
|
272
|
+
if (!cache) return setRanges([]);
|
|
273
|
+
let timer = null;
|
|
274
|
+
const read = () => {
|
|
275
|
+
timer = null;
|
|
276
|
+
setRanges(cache.cachedRanges(project, { step: 2e3 / px }));
|
|
277
|
+
};
|
|
278
|
+
read();
|
|
279
|
+
const off = cache.subscribe(() => {
|
|
280
|
+
timer ??= setTimeout(read, 250);
|
|
281
|
+
});
|
|
282
|
+
return () => {
|
|
283
|
+
off();
|
|
284
|
+
if (timer) clearTimeout(timer);
|
|
285
|
+
};
|
|
286
|
+
}, [controller, project, px]);
|
|
287
|
+
return ranges;
|
|
288
|
+
}
|
|
256
289
|
function VideoTimeline({ controller, pxPerSecond, onPxPerSecondChange, editable = true, waveforms = "audio", className = "" }) {
|
|
257
290
|
const state = useVideoEditorState(controller);
|
|
258
291
|
const { project, duration, time, selection, readOnly, error } = state;
|
|
@@ -264,10 +297,12 @@ function VideoTimeline({ controller, pxPerSecond, onPxPerSecondChange, editable
|
|
|
264
297
|
const rows = (0, import_react4.useRef)(/* @__PURE__ */ new Map());
|
|
265
298
|
const ruler = (0, import_react4.useRef)(null);
|
|
266
299
|
const assets = (0, import_video_editor5.videoAssetMap)(project);
|
|
300
|
+
const sequences = (0, import_video_editor5.videoSequenceMap)(project);
|
|
267
301
|
const msToPx = (ms) => ms * px / 1e3;
|
|
268
302
|
const span = Math.max(duration + 5e3, 1e4);
|
|
269
303
|
const width = msToPx(span);
|
|
270
304
|
const ticks = (0, import_video_editor5.videoRulerTicks)(span, px, project.fps);
|
|
305
|
+
const cached = useCachedRanges(controller, project, px);
|
|
271
306
|
const seekAt = (clientX) => {
|
|
272
307
|
const box = ruler.current.getBoundingClientRect();
|
|
273
308
|
controller.seek(Math.max(0, (clientX - box.left) * 1e3 / px));
|
|
@@ -429,6 +464,7 @@ function VideoTimeline({ controller, pxPerSecond, onPxPerSecondChange, editable
|
|
|
429
464
|
},
|
|
430
465
|
children: [
|
|
431
466
|
ticks.map((t) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: `absolute bottom-0 w-px ${t.major ? "h-2.5 bg-muted-foreground/70" : "h-1 bg-muted-foreground/40"}`, style: { left: t.x }, "data-tick": t.time, children: t.label && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "absolute -top-3.5 left-1 whitespace-nowrap tabular-nums", children: t.label }) }, t.time)),
|
|
467
|
+
cached.map((r) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "pointer-events-none absolute bottom-0 h-[3px] bg-emerald-500/70", style: { left: msToPx(r.start), width: Math.max(1, msToPx(r.end - r.start)) }, title: "Frames cached: plays and scrubs without waiting", "data-cached-range": "" }, r.start)),
|
|
432
468
|
(project.markers ?? []).map((m) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
433
469
|
"div",
|
|
434
470
|
{
|
|
@@ -471,7 +507,7 @@ function VideoTimeline({ controller, pxPerSecond, onPxPerSecondChange, editable
|
|
|
471
507
|
children: clips.map(({ clip, home, start, duration: length, dragging }) => {
|
|
472
508
|
const asset = clip.type === "media" ? assets.get(clip.asset) : void 0;
|
|
473
509
|
const selected = selection.includes(clip.id);
|
|
474
|
-
const label = (0, import_video_editor5.videoClipLabel)(clip, asset);
|
|
510
|
+
const label = (0, import_video_editor5.videoClipLabel)(clip, asset, clip.type === "sequence" ? sequences.get(clip.sequence) : void 0);
|
|
475
511
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
476
512
|
"div",
|
|
477
513
|
{
|
|
@@ -486,6 +522,7 @@ function VideoTimeline({ controller, pxPerSecond, onPxPerSecondChange, editable
|
|
|
486
522
|
},
|
|
487
523
|
title: `${label} \xB7 ${(0, import_video_editor5.formatVideoSeconds)(start)}\u2013${(0, import_video_editor5.formatVideoSeconds)(start + length)}`,
|
|
488
524
|
"data-clip": clip.id,
|
|
525
|
+
"data-compound": clip.type === "sequence" ? clip.sequence : void 0,
|
|
489
526
|
"data-selected": selected ? "" : void 0,
|
|
490
527
|
"data-dragging": dragging ? "" : void 0,
|
|
491
528
|
"aria-selected": selected,
|
|
@@ -637,7 +674,7 @@ function ClipInspector({ controller, className = "" }) {
|
|
|
637
674
|
const transition = clip.transition;
|
|
638
675
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: `flex flex-col gap-1.5 ${className}`, "data-clip-inspector": "", "data-clip-id": clip.id, children: [
|
|
639
676
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-2 text-xs", children: [
|
|
640
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "font-medium", children: clip.type === "media" ? "Media clip" : clip.type === "text" ? "Title" : "Colour clip" }),
|
|
677
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "font-medium", children: clip.type === "media" ? "Media clip" : clip.type === "text" ? "Title" : clip.type === "sequence" ? "Compound clip" : "Colour clip" }),
|
|
641
678
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("code", { className: "text-muted-foreground", children: clip.id }),
|
|
642
679
|
at.track.locked && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-amber-500", children: "locked track" })
|
|
643
680
|
] }),
|
|
@@ -785,6 +822,14 @@ function ClipInspector({ controller, className = "" }) {
|
|
|
785
822
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "Font size", name: "fontSize", children: number("fontSize", "Font size", { min: 1, step: 2 }) }),
|
|
786
823
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "Background", name: "background", children: text("background", "Background") })
|
|
787
824
|
] }),
|
|
825
|
+
clip.type === "sequence" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
826
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "Sequence", name: "sequence", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "truncate", title: "A compound clip plays its sequence as one picture and sound; its clips cannot be edited here yet", children: [
|
|
827
|
+
project.sequences?.find((q) => q.id === clip.sequence)?.name ?? clip.sequence,
|
|
828
|
+
" \xB7 from ",
|
|
829
|
+
(0, import_video_editor6.formatVideoSeconds)(clip.in ?? 0)
|
|
830
|
+
] }) }),
|
|
831
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "Muted", name: "muted", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("input", { type: "checkbox", className: "justify-self-start", checked: !!clip.muted, disabled, "aria-label": "Muted", onChange: (e) => set("muted", e.target.checked || void 0, e.target.checked ? "Mute clip" : "Unmute clip") }) })
|
|
832
|
+
] }),
|
|
788
833
|
clip.type === "color" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "Colour", name: "color", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Commit, { value: clip.color, "aria-label": "Colour", disabled, onCommit: (raw) => raw.trim() && set("color", raw.trim(), "Change colour") }) }),
|
|
789
834
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "Transition in", name: "transition", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
790
835
|
"select",
|
|
@@ -933,6 +978,7 @@ function MediaBin({ controller, editable = true, fileToUrl, probe = import_video
|
|
|
933
978
|
project.assets.length ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { className: "flex flex-col gap-1", "aria-label": "Media assets", children: project.assets.map((asset) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("li", { className: "flex items-center gap-2 text-xs", "data-asset": asset.id, children: [
|
|
934
979
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "rounded bg-muted px-1 text-[10px] uppercase text-muted-foreground", "data-asset-kind": "", children: asset.kind }),
|
|
935
980
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "min-w-0 flex-1 truncate", title: asset.src, children: asset.name ?? asset.id }),
|
|
981
|
+
asset.proxies?.length ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "rounded bg-muted px-1 text-[10px] text-muted-foreground", title: `Proxies: ${asset.proxies.map((p) => `${p.scale * 100}%`).join(", ")} (the preview can play these; exports use the original)`, "data-asset-proxies": "", children: "proxy" }) : null,
|
|
936
982
|
asset.duration !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "tabular-nums text-muted-foreground", children: (0, import_video_editor7.formatVideoSeconds)(asset.duration) }),
|
|
937
983
|
editable && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: btn3, disabled: !canEdit, onClick: () => addToTimeline(asset), "data-add-to-timeline": "", children: "Add to timeline" })
|
|
938
984
|
] }, asset.id)) }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
|