mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-12 07:09:59 +00:00
fix(ci): scope LOC check to studio, split useTimelinePlayer + hyperframes-player under 500 LOC (#750)
* refactor: split useTimelinePlayer.ts and hyperframes-player.ts into focused modules (<500 LOC each) * fix(ci): scope 500 LOC check to packages/studio, add allowlist for grandfathered files * feat(cli): Linux ARM64 support — auto-install Chromium on DGX Spark / GB10 / Jetson Chrome Headless Shell has no Linux ARM64 binary. On arm64 Linux: - Detects the platform automatically - Tries to auto-install system Chromium via apt-get (works on Ubuntu/Debian ARM) - Falls back to clear manual instructions with exact commands - 'hyperframes browser ensure' guides through the setup interactively - After setup, all render commands work without any flags * fix(ci): disable Windows Defender real-time monitoring to prevent EPERM builds Path exclusions are insufficient — Defender re-scans new files created during bun install before the exclusion takes effect. Disable real-time monitoring for the entire job duration instead (standard CI practice). * refactor(studio): split all files >500 LOC + extract useToast, delete allowlist All 11 large files split into focused modules under 500 LOC. App.tsx extracted toast logic into useToast hook (493 LOC now). .filesize-allowlist deleted — no longer needed. * fix: remove unused imports from split files, extract useToast from App.tsx App.tsx: 504 → 493 lines (toast logic extracted to useToast hook) timelineDOM.ts: remove unused imports from re-export pattern MotionPanel.tsx: remove unused clampStudioCustomEasePoints import studioMotionOps.ts: remove unused StudioGsapMotionDirection import * fix(ci): use Set-MpPreference to fully disable Windows Defender (both jobs) * fix(producer): use node --experimental-strip-types instead of tsx for build:fonts Eliminates the tsx binary dependency that Windows Defender locks during bun install, causing EPERM errors. Node 22.6+ strips TypeScript types natively with no external binary. * chore: remove .filesize-allowlist — App.tsx is now 493 lines (<500) * fix(ci): disable Windows Defender before checkout to prevent all EPERM races * fix(producer): skip build:fonts if fontData.generated.ts already exists The generated file is tracked in git, so CI doesn't need to regenerate it. This avoids @fontsource/inter node_modules access on Windows which triggers EPERM from Defender scanning during bun install.
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
import { type DomEditSelection, findElementForSelection } from "./domEditing";
|
||||
|
||||
export interface OverlayRect {
|
||||
left: number;
|
||||
top: number;
|
||||
width: number;
|
||||
height: number;
|
||||
editScaleX: number;
|
||||
editScaleY: number;
|
||||
}
|
||||
|
||||
export interface GroupOverlayItem {
|
||||
key: string;
|
||||
selection: DomEditSelection;
|
||||
element: HTMLElement;
|
||||
rect: OverlayRect;
|
||||
}
|
||||
|
||||
export type ResolvedElementRef = {
|
||||
current: { key: string; element: HTMLElement } | null;
|
||||
};
|
||||
|
||||
export function isElementVisibleForOverlay(el: HTMLElement): boolean {
|
||||
const win = el.ownerDocument.defaultView;
|
||||
if (!win) return true;
|
||||
let current: HTMLElement | null = el;
|
||||
while (current) {
|
||||
const computed = win.getComputedStyle(current);
|
||||
if (computed.display === "none" || computed.visibility === "hidden") return false;
|
||||
const opacity = Number.parseFloat(computed.opacity);
|
||||
if (Number.isFinite(opacity) && opacity <= 0.01) return false;
|
||||
current = current.parentElement;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function readPositiveDimension(value: string | null): number | null {
|
||||
if (!value) return null;
|
||||
const parsed = Number.parseFloat(value);
|
||||
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
||||
}
|
||||
|
||||
function findSourceBoundary(element: HTMLElement): HTMLElement | null {
|
||||
let current: HTMLElement | null = element;
|
||||
while (current) {
|
||||
if (
|
||||
current.hasAttribute("data-composition-file") ||
|
||||
current.hasAttribute("data-composition-src")
|
||||
) {
|
||||
return current;
|
||||
}
|
||||
current = current.parentElement;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function resolveDomEditCoordinateScale(input: {
|
||||
rootScaleX: number;
|
||||
rootScaleY: number;
|
||||
sourceRectWidth?: number;
|
||||
sourceRectHeight?: number;
|
||||
sourceWidth?: number | null;
|
||||
sourceHeight?: number | null;
|
||||
}): { scaleX: number; scaleY: number } {
|
||||
const rootScaleX = input.rootScaleX > 0 ? input.rootScaleX : 1;
|
||||
const rootScaleY = input.rootScaleY > 0 ? input.rootScaleY : 1;
|
||||
const sourceScaleX =
|
||||
input.sourceRectWidth && input.sourceRectWidth > 0 && input.sourceWidth && input.sourceWidth > 0
|
||||
? (input.sourceRectWidth * rootScaleX) / input.sourceWidth
|
||||
: rootScaleX;
|
||||
const sourceScaleY =
|
||||
input.sourceRectHeight &&
|
||||
input.sourceRectHeight > 0 &&
|
||||
input.sourceHeight &&
|
||||
input.sourceHeight > 0
|
||||
? (input.sourceRectHeight * rootScaleY) / input.sourceHeight
|
||||
: rootScaleY;
|
||||
return {
|
||||
scaleX: sourceScaleX > 0 ? sourceScaleX : rootScaleX,
|
||||
scaleY: sourceScaleY > 0 ? sourceScaleY : rootScaleY,
|
||||
};
|
||||
}
|
||||
|
||||
export function toOverlayRect(
|
||||
overlayEl: HTMLDivElement,
|
||||
iframe: HTMLIFrameElement,
|
||||
element: HTMLElement,
|
||||
): OverlayRect | null {
|
||||
const iframeRect = iframe.getBoundingClientRect();
|
||||
const overlayRect = overlayEl.getBoundingClientRect();
|
||||
const doc = iframe.contentDocument;
|
||||
const root =
|
||||
doc?.querySelector<HTMLElement>("[data-composition-id]") ?? doc?.documentElement ?? null;
|
||||
const rootRect = root?.getBoundingClientRect();
|
||||
const rootWidth = rootRect?.width;
|
||||
const rootHeight = rootRect?.height;
|
||||
if (!rootWidth || !rootHeight) return null;
|
||||
|
||||
const elementRect = element.getBoundingClientRect();
|
||||
const rootScaleX = iframeRect.width / rootWidth;
|
||||
const rootScaleY = iframeRect.height / rootHeight;
|
||||
const sourceBoundary = findSourceBoundary(element);
|
||||
const sourceBoundaryRect = sourceBoundary?.getBoundingClientRect();
|
||||
const editScale = resolveDomEditCoordinateScale({
|
||||
rootScaleX,
|
||||
rootScaleY,
|
||||
sourceRectWidth: sourceBoundaryRect?.width,
|
||||
sourceRectHeight: sourceBoundaryRect?.height,
|
||||
sourceWidth: readPositiveDimension(sourceBoundary?.getAttribute("data-width") ?? null),
|
||||
sourceHeight: readPositiveDimension(sourceBoundary?.getAttribute("data-height") ?? null),
|
||||
});
|
||||
|
||||
return {
|
||||
left: iframeRect.left - overlayRect.left + (elementRect.left - rootRect.left) * rootScaleX,
|
||||
top: iframeRect.top - overlayRect.top + (elementRect.top - rootRect.top) * rootScaleY,
|
||||
width: elementRect.width * rootScaleX,
|
||||
height: elementRect.height * rootScaleY,
|
||||
editScaleX: editScale.scaleX,
|
||||
editScaleY: editScale.scaleY,
|
||||
};
|
||||
}
|
||||
|
||||
const OVERLAY_RECT_EPSILON_PX = 0.5;
|
||||
|
||||
export function rectsEqual(a: OverlayRect | null, b: OverlayRect | null): boolean {
|
||||
if (a === b) return true;
|
||||
if (!a || !b) return false;
|
||||
return (
|
||||
Math.abs(a.left - b.left) < OVERLAY_RECT_EPSILON_PX &&
|
||||
Math.abs(a.top - b.top) < OVERLAY_RECT_EPSILON_PX &&
|
||||
Math.abs(a.width - b.width) < OVERLAY_RECT_EPSILON_PX &&
|
||||
Math.abs(a.height - b.height) < OVERLAY_RECT_EPSILON_PX &&
|
||||
Math.abs(a.editScaleX - b.editScaleX) < 0.001 &&
|
||||
Math.abs(a.editScaleY - b.editScaleY) < 0.001
|
||||
);
|
||||
}
|
||||
|
||||
export function groupOverlayItemsEqual(a: GroupOverlayItem[], b: GroupOverlayItem[]): boolean {
|
||||
if (a === b) return true;
|
||||
if (a.length !== b.length) return false;
|
||||
return a.every((item, index) => {
|
||||
const other = b[index];
|
||||
return Boolean(
|
||||
other &&
|
||||
item.key === other.key &&
|
||||
item.element === other.element &&
|
||||
item.selection === other.selection &&
|
||||
rectsEqual(item.rect, other.rect),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveDomEditGroupOverlayRect(rects: OverlayRect[]): OverlayRect | null {
|
||||
const first = rects[0];
|
||||
if (!first) return null;
|
||||
|
||||
let left = first.left;
|
||||
let top = first.top;
|
||||
let right = first.left + first.width;
|
||||
let bottom = first.top + first.height;
|
||||
|
||||
for (const rect of rects.slice(1)) {
|
||||
left = Math.min(left, rect.left);
|
||||
top = Math.min(top, rect.top);
|
||||
right = Math.max(right, rect.left + rect.width);
|
||||
bottom = Math.max(bottom, rect.top + rect.height);
|
||||
}
|
||||
|
||||
return {
|
||||
left,
|
||||
top,
|
||||
width: right - left,
|
||||
height: bottom - top,
|
||||
editScaleX: 1,
|
||||
editScaleY: 1,
|
||||
};
|
||||
}
|
||||
|
||||
export function filterNestedDomEditGroupItems<T extends { element: HTMLElement }>(items: T[]): T[] {
|
||||
return items.filter(
|
||||
(item) => !items.some((other) => other !== item && other.element.contains(item.element)),
|
||||
);
|
||||
}
|
||||
|
||||
export function selectionCacheKey(
|
||||
selection: Pick<DomEditSelection, "id" | "selector" | "selectorIndex" | "sourceFile">,
|
||||
): string {
|
||||
return [
|
||||
selection.sourceFile ?? "",
|
||||
selection.id ?? "",
|
||||
selection.selector ?? "",
|
||||
selection.selectorIndex ?? "",
|
||||
].join("|");
|
||||
}
|
||||
|
||||
export function resolveElementForOverlay(
|
||||
doc: Document,
|
||||
sel: DomEditSelection,
|
||||
activeCompositionPath: string | null,
|
||||
cacheRef: ResolvedElementRef,
|
||||
): HTMLElement | null {
|
||||
const key = selectionCacheKey(sel);
|
||||
const cached = cacheRef.current;
|
||||
if (cached?.key === key && cached.element.isConnected && cached.element.ownerDocument === doc) {
|
||||
return cached.element;
|
||||
}
|
||||
|
||||
const next = findElementForSelection(doc, sel, activeCompositionPath);
|
||||
cacheRef.current = next ? { key, element: next } : null;
|
||||
return next;
|
||||
}
|
||||
Reference in New Issue
Block a user