Files
hyperframes/packages/studio/src/components/editor/domEditOverlayStartGesture.ts
T
Miguel Ángel 91bdffffe6 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.
2026-05-13 01:48:12 +02:00

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;
}