mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-09 03:16:38 +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.
156 lines
4.9 KiB
TypeScript
156 lines
4.9 KiB
TypeScript
/**
|
|
* Gesture-begin functions: startGroupDrag and startGesture.
|
|
* These are pure "start a new gesture" operations — no draft rect updates.
|
|
*/
|
|
import { type DomEditSelection } from "./domEditing";
|
|
import {
|
|
createManualOffsetDragMember,
|
|
restoreManualOffsetDragMembers,
|
|
type ManualOffsetDragMember,
|
|
} from "./manualOffsetDrag";
|
|
import {
|
|
beginStudioManualEditGesture,
|
|
captureStudioBoxSize,
|
|
captureStudioPathOffset,
|
|
captureStudioRotation,
|
|
readStudioBoxSize,
|
|
readStudioRotation,
|
|
} from "./manualEdits";
|
|
import {
|
|
type OverlayRect,
|
|
filterNestedDomEditGroupItems,
|
|
selectionCacheKey,
|
|
} from "./domEditOverlayGeometry";
|
|
import { type GestureKind, type GestureState } from "./domEditOverlayGestures";
|
|
import type { UseDomEditOverlayGesturesOptions } from "./useDomEditOverlayGestures";
|
|
|
|
export function startGroupDrag(
|
|
e: React.PointerEvent<HTMLElement>,
|
|
opts: UseDomEditOverlayGesturesOptions,
|
|
): boolean {
|
|
const items = opts.groupOverlayItemsRef.current;
|
|
if (items.length <= 1) return false;
|
|
|
|
const blockedSelection = items.find(
|
|
(item) => !item.selection.capabilities.canApplyManualOffset,
|
|
)?.selection;
|
|
if (blockedSelection) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
opts.onBlockedMoveRef.current(blockedSelection);
|
|
return false;
|
|
}
|
|
|
|
opts.onManualDragStartRef.current?.();
|
|
const dragItems = filterNestedDomEditGroupItems(items);
|
|
const members: ManualOffsetDragMember[] = [];
|
|
for (const item of dragItems) {
|
|
const result = createManualOffsetDragMember({
|
|
key: item.key,
|
|
selection: item.selection,
|
|
element: item.element,
|
|
rect: item.rect,
|
|
});
|
|
if (!result.ok) {
|
|
restoreManualOffsetDragMembers(members);
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
opts.onBlockedMoveRef.current(result.selection);
|
|
return false;
|
|
}
|
|
members.push(result.member);
|
|
}
|
|
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
e.currentTarget.setPointerCapture(e.pointerId);
|
|
opts.rafPausedRef.current = true;
|
|
opts.groupGestureRef.current = {
|
|
startX: e.clientX,
|
|
startY: e.clientY,
|
|
originItems: items,
|
|
members,
|
|
};
|
|
return true;
|
|
}
|
|
|
|
export function startGesture(
|
|
kind: GestureKind,
|
|
e: React.PointerEvent<HTMLElement>,
|
|
opts: UseDomEditOverlayGesturesOptions,
|
|
options?: { selection?: DomEditSelection; rect?: OverlayRect | null },
|
|
): boolean {
|
|
const sel = options?.selection ?? opts.selectionRef.current;
|
|
const rect = options?.rect ?? opts.overlayRectRef.current;
|
|
const box = opts.boxRef.current;
|
|
const overlayEl = opts.overlayRef.current;
|
|
if (!sel || !rect) return false;
|
|
if (kind !== "drag" && !box) return false;
|
|
const mode: GestureState["mode"] =
|
|
kind === "rotate" ? "rotation" : kind === "drag" ? "path-offset" : "box-size";
|
|
if (kind === "drag" && !sel.capabilities.canApplyManualOffset) return false;
|
|
if (kind === "resize" && !sel.capabilities.canApplyManualSize) return false;
|
|
if (kind === "rotate" && !sel.capabilities.canApplyManualRotation) return false;
|
|
if (kind === "resize" && (!Number.isFinite(rect.width) || !Number.isFinite(rect.height)))
|
|
return false;
|
|
|
|
const size = readStudioBoxSize(sel.element);
|
|
const rotation = readStudioRotation(sel.element);
|
|
const actualWidth = size.width > 0 ? size.width : rect.width / rect.editScaleX;
|
|
const actualHeight = size.height > 0 ? size.height : rect.height / rect.editScaleY;
|
|
let initialPathOffset = captureStudioPathOffset(sel.element);
|
|
let manualEditDragToken: string | undefined;
|
|
let pathOffsetMember: ManualOffsetDragMember | undefined;
|
|
|
|
if (kind === "drag") {
|
|
opts.onManualDragStartRef.current?.();
|
|
const result = createManualOffsetDragMember({
|
|
key: selectionCacheKey(sel),
|
|
selection: sel,
|
|
element: sel.element,
|
|
rect,
|
|
});
|
|
if (!result.ok) {
|
|
opts.onBlockedMoveRef.current(result.selection);
|
|
return false;
|
|
}
|
|
pathOffsetMember = result.member;
|
|
initialPathOffset = result.member.initialPathOffset;
|
|
manualEditDragToken = result.member.gestureToken;
|
|
} else {
|
|
manualEditDragToken = beginStudioManualEditGesture(sel.element);
|
|
}
|
|
|
|
const overlayBounds = overlayEl?.getBoundingClientRect();
|
|
const centerX = (overlayBounds?.left ?? 0) + rect.left + rect.width / 2;
|
|
const centerY = (overlayBounds?.top ?? 0) + rect.top + rect.height / 2;
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
e.currentTarget.setPointerCapture(e.pointerId);
|
|
opts.rafPausedRef.current = true;
|
|
opts.gestureRef.current = {
|
|
kind,
|
|
mode,
|
|
selection: sel,
|
|
startX: e.clientX,
|
|
startY: e.clientY,
|
|
centerX,
|
|
centerY,
|
|
initialPathOffset,
|
|
initialRotation: captureStudioRotation(sel.element),
|
|
initialBoxSize: captureStudioBoxSize(sel.element),
|
|
pathOffsetMember,
|
|
originLeft: rect.left,
|
|
originTop: rect.top,
|
|
originWidth: rect.width,
|
|
originHeight: rect.height,
|
|
actualWidth,
|
|
actualHeight,
|
|
actualRotation: rotation.angle,
|
|
editScaleX: rect.editScaleX,
|
|
editScaleY: rect.editScaleY,
|
|
manualEditDragToken,
|
|
};
|
|
return true;
|
|
}
|