mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-09 03:16:38 +00:00
feat(studio): marquee multi-selection + off-canvas indicators (#1693)
* chore(studio): remove all console.* calls from studio package * chore(studio): address review — remove dead stubs, restore consent notice - Delete empty if-blocks left after console removal (snapTargetCollection, Player asset-poll, useTimelineSyncCallbacks 5s probe, useGestureRecording dev guard + now-unused isDevBuild) and the stale "surface in dev" comment. - Drop the dangling no-console pragma + dead duplicate-id branch in sourcePatcher. - Restore the one-time telemetry consent disclosure in showNoticeOnce (kept behind a pragma — it is a user-facing notice, not debug noise). - Remove the missed timelineIcons console.warn while preserving the `tag || "div"` null-safety fallback. - Route caption auto-save failures (a data-loss path) through telemetry instead of swallowing silently. - Restore the accidentally-clobbered css-var-fonts output.mp4 fixture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(runtime): immediateRender for set tweens + array timeline normalization - Set tweens now emit immediateRender:true so they render on page load without requiring the runtime to seek past position 0 - Runtime IIFE normalizes array timelines (window.__timelines = [tl]) to keyed objects, and auto-adds data-start on root elements - Drag teardown clears translate:none to prevent #1673 fly-off - Position-only set tweens hidden from timeline diamonds (3 cache paths) - Parser: ease-only keyframe update preserves existing properties * fix(runtime): address review — restore perf gate, debug surface, scrub restore - Restore the #1651 skipForInjectedVideo gate in media.ts that was dropped on restack — avoids ~2400 wasted per-tick seeks on video-heavy renders. - Restore the console.debug body + docstring bullet of swallow() in diagnostics.ts: the __hfDebug opt-in debug surface had been gutted to an empty if-block. - Rebind: after the progress-cycle set() kick, seek to state.currentTime via totalTime() instead of snapping to 0, so a rebind after scrub / soft-reload restore keeps the playhead. - Array __timelines normalization + data-start default now resolve the root via a shared findRootCompositionEl() that honors data-root="true" first (matches resolveRootCompositionElement, which now delegates to it). - Ease-only keyframe update leaves a primitive (non-object) keyframe value untouched instead of wiping it to {}; add a preservation unit test. - Document the boundDuration<=0 progress(1) kick + restore the STATIC-case comment in gsapRuntimeBridge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(studio): marquee multi-selection + off-canvas indicators - Click+drag on empty canvas draws dashed selection rectangle - SAT/OBB intersection handles rotated/scaled/skewed elements - Shift+marquee adds to existing selection - Click on empty canvas deselects - Off-canvas elements show dashed outline indicators (clickable) - Dashed border only shows outside canvas, solid inside (clip-path) - 12 geometry unit tests * feat(studio): address review — group-aware off-canvas indicators + fixes - Off-canvas indicator suppression now skips every selected element (primary AND marquee group members), not just the primary, so group members no longer render a doubled overlay (group rect + dashed indicator). - Drop selection from the off-canvas layout effect deps; the selected-element filter runs at render time. Avoids re-walking geometry on each selection change. - applyMarqueeSelection now honors STUDIO_INSPECTOR_PANELS_ENABLED. - Restore the stale-selection clear in useDomEditPreviewSync when the selected element no longer resolves after a re-sync. Drag-release stays handled by suppressNextBoxClickRef. - Off-canvas indicator is keyboard-accessible; canvas cursor driven by marquee rect state, not a render-time ref read. - Rename partiallyOutside -> extendsOutsideComp + comment the clip-path hit-test. - Extract OffCanvasIndicators into its own component (DomEditOverlay was already over the 600-LOC cap on this branch; extraction brings it under). - Declare onUpdateKeyframeEase on PropertyPanelProps so this branch typechecks standalone (handler + wiring already here; only the type had leaked upstack). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
6987447a75
commit
8ae010bf51
@@ -85,6 +85,7 @@ export interface UseDomSelectionReturn {
|
||||
handleTimelineElementSelect: (element: TimelineElement | null) => Promise<void>;
|
||||
refreshDomEditSelectionFromPreview: (selection: DomEditSelection) => Promise<void>;
|
||||
refreshDomEditGroupSelectionsFromPreview: (selections: DomEditSelection[]) => Promise<void>;
|
||||
applyMarqueeSelection: (selections: DomEditSelection[], additive: boolean) => void;
|
||||
}
|
||||
|
||||
// ── Hook ──
|
||||
@@ -419,6 +420,50 @@ export function useDomSelection({
|
||||
applyDomSelection(null, { revealPanel: false });
|
||||
}, [applyDomSelection, captionEditMode]);
|
||||
|
||||
const applyMarqueeSelection = useCallback(
|
||||
// fallow-ignore-next-line complexity
|
||||
(selections: DomEditSelection[], additive: boolean) => {
|
||||
// Honor the inspector-panels kill switch like applyDomSelection does, so
|
||||
// marquee can't land selections while the inspector UI is suppressed.
|
||||
if (!STUDIO_INSPECTOR_PANELS_ENABLED) {
|
||||
domEditSelectionRef.current = null;
|
||||
domEditGroupSelectionsRef.current = [];
|
||||
setDomEditSelection(null);
|
||||
setDomEditGroupSelections([]);
|
||||
return;
|
||||
}
|
||||
if (selections.length === 0) {
|
||||
if (!additive) applyDomSelection(null, { revealPanel: false });
|
||||
return;
|
||||
}
|
||||
const current = domEditSelectionRef.current;
|
||||
const currentGroup = domEditGroupSelectionsRef.current;
|
||||
let nextGroup: DomEditSelection[];
|
||||
if (additive) {
|
||||
nextGroup = seedDomEditGroupWithSelection(currentGroup, current);
|
||||
for (const s of selections) {
|
||||
if (!domEditSelectionInGroup(nextGroup, s)) nextGroup = [...nextGroup, s];
|
||||
}
|
||||
} else {
|
||||
nextGroup = selections;
|
||||
}
|
||||
const nextSelection = additive && current ? current : selections[0];
|
||||
domEditSelectionRef.current = nextSelection;
|
||||
domEditGroupSelectionsRef.current = nextGroup;
|
||||
setDomEditSelection(nextSelection);
|
||||
setDomEditGroupSelections(nextGroup);
|
||||
const nextTimelineId =
|
||||
findMatchingTimelineElementId(nextSelection, timelineElements) ??
|
||||
findTimelineIdByAncestor(
|
||||
nextSelection.element,
|
||||
timelineElements,
|
||||
nextSelection.sourceFile || "index.html",
|
||||
);
|
||||
setSelectedTimelineElementId(nextTimelineId);
|
||||
},
|
||||
[applyDomSelection, timelineElements, setSelectedTimelineElementId],
|
||||
);
|
||||
|
||||
// Disabled inspector effect
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
useEffect(() => {
|
||||
@@ -451,5 +496,6 @@ export function useDomSelection({
|
||||
handleTimelineElementSelect,
|
||||
refreshDomEditSelectionFromPreview,
|
||||
refreshDomEditGroupSelectionsFromPreview,
|
||||
applyMarqueeSelection,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user