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:
Miguel Ángel
2026-05-19 20:54:45 -04:00
parent 0769dc4b9c
commit beb807493c
+47 -53
View File
@@ -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;