mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 23:03:09 +00:00
refactor(studio): reduce complexity in timeline editing helpers
Extract resolveResizePlaybackStart, simplify patchIframeDomTiming to accept attr tuples, inline findIframeElement. Reduces CRAP scores in the resize handler lambda and DOM patching functions.
This commit is contained in:
@@ -55,45 +55,52 @@ function buildPatchTarget(element: { domId?: string; selector?: string; selector
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findIframeElement(
|
|
||||||
iframe: HTMLIFrameElement | null,
|
|
||||||
element: { domId?: string; selector?: string; selectorIndex?: number },
|
|
||||||
): Element | null {
|
|
||||||
const doc = iframe?.contentDocument;
|
|
||||||
if (!doc) return null;
|
|
||||||
if (element.domId) return doc.getElementById(element.domId);
|
|
||||||
if (!element.selector) return null;
|
|
||||||
return doc.querySelectorAll(element.selector)[element.selectorIndex ?? 0] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TIMING_ATTR_MAP: Record<string, string> = {
|
|
||||||
start: "data-start",
|
|
||||||
duration: "data-duration",
|
|
||||||
track: "data-track-index",
|
|
||||||
};
|
|
||||||
|
|
||||||
function patchIframeDomTiming(
|
function patchIframeDomTiming(
|
||||||
iframe: HTMLIFrameElement | null,
|
iframe: HTMLIFrameElement | null,
|
||||||
element: TimelineElement,
|
element: TimelineElement,
|
||||||
updates: { start?: number; duration?: number; track?: number; playbackStart?: number },
|
attrs: Array<[string, string]>,
|
||||||
): void {
|
): void {
|
||||||
try {
|
try {
|
||||||
const el = findIframeElement(iframe, element);
|
const doc = iframe?.contentDocument;
|
||||||
|
if (!doc) return;
|
||||||
|
const el = element.domId
|
||||||
|
? doc.getElementById(element.domId)
|
||||||
|
: element.selector
|
||||||
|
? (doc.querySelectorAll(element.selector)[element.selectorIndex ?? 0] ?? null)
|
||||||
|
: null;
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
for (const [key, attr] of Object.entries(TIMING_ATTR_MAP)) {
|
for (const [name, value] of attrs) el.setAttribute(name, value);
|
||||||
const val = updates[key as keyof typeof updates];
|
|
||||||
if (val != null) el.setAttribute(attr, formatTimelineAttributeNumber(val));
|
|
||||||
}
|
|
||||||
if (updates.playbackStart != null) {
|
|
||||||
const attr =
|
|
||||||
element.playbackStartAttr === "playback-start" ? "data-playback-start" : "data-media-start";
|
|
||||||
el.setAttribute(attr, formatTimelineAttributeNumber(updates.playbackStart));
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
// Cross-origin or mid-navigation — safe to ignore, file is already saved.
|
// Cross-origin or mid-navigation — safe to ignore, file is already saved.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveResizePlaybackStart(
|
||||||
|
original: string,
|
||||||
|
target: PatchTarget,
|
||||||
|
element: TimelineElement,
|
||||||
|
updates: Pick<TimelineElement, "start" | "playbackStart">,
|
||||||
|
): { attrName: string; value: number } | null {
|
||||||
|
if (updates.playbackStart != null) {
|
||||||
|
const attrName =
|
||||||
|
element.playbackStartAttr === "playback-start" ? "playback-start" : "media-start";
|
||||||
|
return { attrName, value: updates.playbackStart };
|
||||||
|
}
|
||||||
|
const trimDelta = updates.start - element.start;
|
||||||
|
if (trimDelta === 0) return null;
|
||||||
|
const raw =
|
||||||
|
readAttributeByTarget(original, target, "playback-start") ??
|
||||||
|
readAttributeByTarget(original, target, "media-start");
|
||||||
|
const current = raw != null ? parseFloat(raw) : undefined;
|
||||||
|
if (current == null || !Number.isFinite(current)) return null;
|
||||||
|
const attrName =
|
||||||
|
element.playbackStartAttr === "playback-start" ? "playback-start" : "media-start";
|
||||||
|
return {
|
||||||
|
attrName,
|
||||||
|
value: Math.max(0, current + trimDelta * Math.max(element.playbackRate ?? 1, 0.1)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
type PatchTarget = NonNullable<ReturnType<typeof buildPatchTarget>>;
|
type PatchTarget = NonNullable<ReturnType<typeof buildPatchTarget>>;
|
||||||
|
|
||||||
interface PersistTimelineEditInput {
|
interface PersistTimelineEditInput {
|
||||||
@@ -208,7 +215,10 @@ export function useTimelineEditing({
|
|||||||
|
|
||||||
const handleTimelineElementMove = useCallback(
|
const handleTimelineElementMove = useCallback(
|
||||||
(element: TimelineElement, updates: Pick<TimelineElement, "start" | "track">) => {
|
(element: TimelineElement, updates: Pick<TimelineElement, "start" | "track">) => {
|
||||||
patchIframeDomTiming(previewIframeRef.current, element, updates);
|
patchIframeDomTiming(previewIframeRef.current, element, [
|
||||||
|
["data-start", formatTimelineAttributeNumber(updates.start)],
|
||||||
|
["data-track-index", String(updates.track)],
|
||||||
|
]);
|
||||||
enqueueEdit(element, "Move timeline clip", (original, target) => {
|
enqueueEdit(element, "Move timeline clip", (original, target) => {
|
||||||
let patched = applyPatchByTarget(original, target, {
|
let patched = applyPatchByTarget(original, target, {
|
||||||
type: "attribute",
|
type: "attribute",
|
||||||
@@ -230,28 +240,12 @@ export function useTimelineEditing({
|
|||||||
element: TimelineElement,
|
element: TimelineElement,
|
||||||
updates: Pick<TimelineElement, "start" | "duration" | "playbackStart">,
|
updates: Pick<TimelineElement, "start" | "duration" | "playbackStart">,
|
||||||
) => {
|
) => {
|
||||||
patchIframeDomTiming(previewIframeRef.current, element, updates);
|
patchIframeDomTiming(previewIframeRef.current, element, [
|
||||||
|
["data-start", formatTimelineAttributeNumber(updates.start)],
|
||||||
|
["data-duration", formatTimelineAttributeNumber(updates.duration)],
|
||||||
|
]);
|
||||||
enqueueEdit(element, "Resize timeline clip", (original, target) => {
|
enqueueEdit(element, "Resize timeline clip", (original, target) => {
|
||||||
const playbackStartAttrName =
|
const pbs = resolveResizePlaybackStart(original, target, element, updates);
|
||||||
element.playbackStartAttr === "playback-start" ? "playback-start" : "media-start";
|
|
||||||
const currentPlaybackStartValue =
|
|
||||||
readAttributeByTarget(original, target, "playback-start") ??
|
|
||||||
readAttributeByTarget(original, target, "media-start");
|
|
||||||
const currentPlaybackStart =
|
|
||||||
currentPlaybackStartValue != null ? parseFloat(currentPlaybackStartValue) : undefined;
|
|
||||||
const trimDelta = updates.start - element.start;
|
|
||||||
const fallbackPlaybackStart =
|
|
||||||
updates.playbackStart == null &&
|
|
||||||
trimDelta !== 0 &&
|
|
||||||
Number.isFinite(currentPlaybackStart) &&
|
|
||||||
currentPlaybackStart != null
|
|
||||||
? Math.max(
|
|
||||||
0,
|
|
||||||
currentPlaybackStart + trimDelta * Math.max(element.playbackRate ?? 1, 0.1),
|
|
||||||
)
|
|
||||||
: undefined;
|
|
||||||
const nextPlaybackStart = updates.playbackStart ?? fallbackPlaybackStart;
|
|
||||||
|
|
||||||
let patched = applyPatchByTarget(original, target, {
|
let patched = applyPatchByTarget(original, target, {
|
||||||
type: "attribute",
|
type: "attribute",
|
||||||
property: "start",
|
property: "start",
|
||||||
@@ -262,11 +256,11 @@ export function useTimelineEditing({
|
|||||||
property: "duration",
|
property: "duration",
|
||||||
value: formatTimelineAttributeNumber(updates.duration),
|
value: formatTimelineAttributeNumber(updates.duration),
|
||||||
});
|
});
|
||||||
if (nextPlaybackStart != null) {
|
if (pbs) {
|
||||||
patched = applyPatchByTarget(patched, target, {
|
patched = applyPatchByTarget(patched, target, {
|
||||||
type: "attribute",
|
type: "attribute",
|
||||||
property: playbackStartAttrName,
|
property: pbs.attrName,
|
||||||
value: formatTimelineAttributeNumber(nextPlaybackStart),
|
value: formatTimelineAttributeNumber(pbs.value),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return patched;
|
return patched;
|
||||||
|
|||||||
Reference in New Issue
Block a user