mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-08 19:06:04 +00:00
* 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.
139 lines
4.0 KiB
TypeScript
139 lines
4.0 KiB
TypeScript
import type { DomEditSelection } from "./domEditing";
|
|
import type {
|
|
StudioBoxSizeSnapshot,
|
|
StudioPathOffsetSnapshot,
|
|
StudioRotationSnapshot,
|
|
} from "./manualEdits";
|
|
import type { ManualOffsetDragMember } from "./manualOffsetDrag";
|
|
import type { GroupOverlayItem } from "./domEditOverlayGeometry";
|
|
|
|
export type GestureKind = "drag" | "resize" | "rotate";
|
|
|
|
export const BLOCKED_MOVE_THRESHOLD_PX = 4;
|
|
export const MIN_RESIZE_EDGE_PX = 20;
|
|
const ROTATION_COMMIT_EPSILON_DEGREES = 0.05;
|
|
const ROTATION_SNAP_DEGREES = 15;
|
|
|
|
export interface GestureState {
|
|
kind: GestureKind;
|
|
mode: "path-offset" | "box-size" | "rotation";
|
|
selection: DomEditSelection;
|
|
startX: number;
|
|
startY: number;
|
|
centerX: number;
|
|
centerY: number;
|
|
initialPathOffset: StudioPathOffsetSnapshot;
|
|
initialRotation: StudioRotationSnapshot;
|
|
initialBoxSize: StudioBoxSizeSnapshot;
|
|
pathOffsetMember?: ManualOffsetDragMember;
|
|
originLeft: number;
|
|
originTop: number;
|
|
originWidth: number;
|
|
originHeight: number;
|
|
actualWidth: number;
|
|
actualHeight: number;
|
|
actualRotation: number;
|
|
editScaleX: number;
|
|
editScaleY: number;
|
|
manualEditDragToken?: string;
|
|
}
|
|
|
|
export interface GroupGestureState {
|
|
startX: number;
|
|
startY: number;
|
|
originItems: GroupOverlayItem[];
|
|
members: ManualOffsetDragMember[];
|
|
}
|
|
|
|
export interface BlockedMoveState {
|
|
pointerId: number;
|
|
startX: number;
|
|
startY: number;
|
|
notified: boolean;
|
|
}
|
|
|
|
export type FocusableDomEditOverlay = {
|
|
focus(options?: FocusOptions): void;
|
|
};
|
|
|
|
export function focusDomEditOverlayElement(element: FocusableDomEditOverlay | null): void {
|
|
element?.focus({ preventScroll: true });
|
|
}
|
|
|
|
export function resolveDomEditResizeGesture(input: {
|
|
originWidth: number;
|
|
originHeight: number;
|
|
actualWidth: number;
|
|
actualHeight: number;
|
|
scaleX: number;
|
|
scaleY: number;
|
|
dx: number;
|
|
dy: number;
|
|
uniform: boolean;
|
|
}): { overlayWidth: number; overlayHeight: number; width: number; height: number } {
|
|
const scaleX = input.scaleX > 0 ? input.scaleX : 1;
|
|
const scaleY = input.scaleY > 0 ? input.scaleY : 1;
|
|
|
|
if (input.uniform) {
|
|
const deltaX = input.dx / scaleX;
|
|
const deltaY = input.dy / scaleY;
|
|
const delta = Math.abs(deltaX) >= Math.abs(deltaY) ? deltaX : deltaY;
|
|
const side = Math.max(1, Math.max(input.actualWidth, input.actualHeight) + delta);
|
|
return {
|
|
overlayWidth: Math.max(MIN_RESIZE_EDGE_PX, side * scaleX),
|
|
overlayHeight: Math.max(MIN_RESIZE_EDGE_PX, side * scaleY),
|
|
width: side,
|
|
height: side,
|
|
};
|
|
}
|
|
|
|
return {
|
|
overlayWidth: Math.max(MIN_RESIZE_EDGE_PX, input.originWidth + input.dx),
|
|
overlayHeight: Math.max(MIN_RESIZE_EDGE_PX, input.originHeight + input.dy),
|
|
width: Math.max(1, input.actualWidth + input.dx / scaleX),
|
|
height: Math.max(1, input.actualHeight + input.dy / scaleY),
|
|
};
|
|
}
|
|
|
|
function pointerAngleDegrees(centerX: number, centerY: number, x: number, y: number): number {
|
|
return (Math.atan2(y - centerY, x - centerX) * 180) / Math.PI;
|
|
}
|
|
|
|
function normalizeAngleDelta(delta: number): number {
|
|
return ((((delta + 180) % 360) + 360) % 360) - 180;
|
|
}
|
|
|
|
function roundAngle(angle: number): number {
|
|
return Math.round(angle * 10) / 10;
|
|
}
|
|
|
|
export function resolveDomEditRotationGesture(input: {
|
|
centerX: number;
|
|
centerY: number;
|
|
startX: number;
|
|
startY: number;
|
|
currentX: number;
|
|
currentY: number;
|
|
actualAngle: number;
|
|
snap: boolean;
|
|
}): { angle: number } {
|
|
const startAngle = pointerAngleDegrees(input.centerX, input.centerY, input.startX, input.startY);
|
|
const currentAngle = pointerAngleDegrees(
|
|
input.centerX,
|
|
input.centerY,
|
|
input.currentX,
|
|
input.currentY,
|
|
);
|
|
const delta = normalizeAngleDelta(currentAngle - startAngle);
|
|
const angle = input.actualAngle + delta;
|
|
return {
|
|
angle: input.snap
|
|
? Math.round(angle / ROTATION_SNAP_DEGREES) * ROTATION_SNAP_DEGREES
|
|
: roundAngle(angle),
|
|
};
|
|
}
|
|
|
|
export function hasDomEditRotationChanged(initialAngle: number, nextAngle: number): boolean {
|
|
return Math.abs(nextAngle - initialAngle) >= ROTATION_COMMIT_EPSILON_DEGREES;
|
|
}
|