mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 12:54:29 +00:00
Run `fallow fix --auto-fixable` to remove `export` keywords from symbols fallow's reachability analysis identifies as unused. Keeps only the cases where the symbol is still referenced internally in its own file (so removing `export` doesn't surface a new oxlint `no-unused-vars` error). Result: fallow dead-code findings drop from 276 → 208 (68 fewer unused exports), with no behavior change — each symbol is still defined and used exactly the same way within its file. Reverted ~20 files where fallow's auto-fix would have created cascading "declared but never used" lint errors — those are cases where the symbol isn't used at all, and properly cleaning them up means deleting the declaration, not just dropping `export`. Better to land that as a separate, narrower PR rather than mixing it into a mechanical de-export. Also reverted four false positives where fallow missed real consumers: - `captureCost.ts` (renderOrchestrator has two separate import blocks from the same module; fallow only saw the first) - `propertyPanelHelpers.ts`, `domEditingLayers.ts` (real internal uses fallow's reachability missed) - `render.ts` (functions imported via `await import()` dynamic import, which fallow's static analysis doesn't follow) Test plan: bun run --filter '*' typecheck (clean), oxlint + oxfmt clean, cli/core/studio/engine vitest suites pass (335 + 917 + 576 + 605 tests).
117 lines
4.1 KiB
TypeScript
117 lines
4.1 KiB
TypeScript
import type { TimelineElement } from "../player";
|
|
|
|
const TIMELINE_INSPECTOR_BOUNDARY_EPSILON_SECONDS = 0.08;
|
|
|
|
const AUDIO_TIMELINE_TAGS = new Set(["audio", "music", "sfx", "sound", "narration"]);
|
|
const AUDIO_SOURCE_EXT_RE = /\.(aac|flac|m4a|mp3|ogg|opus|wav)(?:[?#].*)?$/i;
|
|
|
|
export function getTimelineElementKey(
|
|
element: Pick<TimelineElement, "id" | "key"> | null | undefined,
|
|
): string | null {
|
|
if (!element) return null;
|
|
return element.key ?? element.id;
|
|
}
|
|
|
|
export function isAudioTimelineElement(
|
|
element: Pick<TimelineElement, "tag" | "src"> | null | undefined,
|
|
): boolean {
|
|
if (!element) return false;
|
|
const tag = element.tag.trim().toLowerCase();
|
|
if (AUDIO_TIMELINE_TAGS.has(tag)) return true;
|
|
return Boolean(element.src && AUDIO_SOURCE_EXT_RE.test(element.src));
|
|
}
|
|
|
|
export function canInspectTimelineElement(
|
|
element: Pick<TimelineElement, "tag" | "src"> | null | undefined,
|
|
): boolean {
|
|
return !isAudioTimelineElement(element);
|
|
}
|
|
|
|
export function shouldShowTimelineInspectorBounds(
|
|
currentTime: number,
|
|
element: Pick<TimelineElement, "start" | "duration"> | null | undefined,
|
|
epsilonSeconds = TIMELINE_INSPECTOR_BOUNDARY_EPSILON_SECONDS,
|
|
): boolean {
|
|
if (!element) return false;
|
|
if (!Number.isFinite(currentTime)) return false;
|
|
if (!Number.isFinite(element.start) || !Number.isFinite(element.duration)) return false;
|
|
const start = Math.max(0, element.start);
|
|
const end = Math.max(start, start + Math.max(0, element.duration));
|
|
const epsilon = Math.max(0, epsilonSeconds);
|
|
return Math.abs(currentTime - start) <= epsilon || Math.abs(currentTime - end) <= epsilon;
|
|
}
|
|
|
|
export function isTimelineElementActiveAtTime(
|
|
currentTime: number,
|
|
element: Pick<TimelineElement, "start" | "duration"> | null | undefined,
|
|
epsilonSeconds = TIMELINE_INSPECTOR_BOUNDARY_EPSILON_SECONDS,
|
|
): boolean {
|
|
if (!element) return false;
|
|
if (!Number.isFinite(currentTime)) return false;
|
|
if (!Number.isFinite(element.start) || !Number.isFinite(element.duration)) return false;
|
|
const start = Math.max(0, element.start);
|
|
const end = Math.max(start, start + Math.max(0, element.duration));
|
|
const epsilon = Math.max(0, epsilonSeconds);
|
|
return currentTime >= start - epsilon && currentTime <= end + epsilon;
|
|
}
|
|
|
|
export interface TimelineLayerVisibility {
|
|
visible: boolean;
|
|
compositeOpacity: number;
|
|
hasBox: boolean;
|
|
inViewport: boolean;
|
|
}
|
|
|
|
export function getTimelineLayerVisibilityInPreview(
|
|
element: HTMLElement,
|
|
options: { minCompositeOpacity?: number } = {},
|
|
): TimelineLayerVisibility {
|
|
const hidden: TimelineLayerVisibility = {
|
|
visible: false,
|
|
compositeOpacity: 0,
|
|
hasBox: false,
|
|
inViewport: false,
|
|
};
|
|
if (!element.isConnected) return hidden;
|
|
const doc = element.ownerDocument;
|
|
const win = doc.defaultView;
|
|
if (!win) return hidden;
|
|
|
|
const minCompositeOpacity = options.minCompositeOpacity ?? 0.01;
|
|
let compositeOpacity = 1;
|
|
let current: HTMLElement | null = element;
|
|
while (current && current !== doc.body && current !== doc.documentElement) {
|
|
const style = win.getComputedStyle(current);
|
|
if (style.display === "none" || style.visibility === "hidden") {
|
|
return { ...hidden, compositeOpacity };
|
|
}
|
|
compositeOpacity *= Number.parseFloat(style.opacity || "1");
|
|
if (compositeOpacity <= minCompositeOpacity) {
|
|
return { ...hidden, compositeOpacity };
|
|
}
|
|
current = current.parentElement;
|
|
}
|
|
|
|
const rect = element.getBoundingClientRect();
|
|
const hasBox = rect.width > 0.5 && rect.height > 0.5;
|
|
if (!hasBox) return { visible: false, compositeOpacity, hasBox, inViewport: false };
|
|
|
|
const viewportWidth = win.innerWidth || doc.documentElement.clientWidth;
|
|
const viewportHeight = win.innerHeight || doc.documentElement.clientHeight;
|
|
const inViewport =
|
|
rect.right > 0 && rect.bottom > 0 && rect.left < viewportWidth && rect.top < viewportHeight;
|
|
return {
|
|
visible: inViewport,
|
|
compositeOpacity,
|
|
hasBox,
|
|
inViewport,
|
|
};
|
|
}
|
|
|
|
export function isTimelineLayerVisibleInPreview(
|
|
element: HTMLElement,
|
|
options: { minCompositeOpacity?: number } = {},
|
|
): boolean {
|
|
return getTimelineLayerVisibilityInPreview(element, options).visible;
|
|
}
|