Merge pull request #613 from auberginewly/feat/zoom-hold-preview

feat(zoom): hold-to-preview button for zoom focus editing (prototype for #612)
This commit is contained in:
Sid
2026-05-22 20:16:28 -07:00
committed by GitHub
14 changed files with 55 additions and 1 deletions
@@ -238,6 +238,8 @@ interface SettingsPanelProps {
selectedZoomCustomScale?: number | null; selectedZoomCustomScale?: number | null;
onZoomCustomScaleChange?: (scale: number) => void; onZoomCustomScaleChange?: (scale: number) => void;
onZoomCustomScaleCommit?: () => void; onZoomCustomScaleCommit?: () => void;
onZoomPreviewStart?: () => void;
onZoomPreviewEnd?: () => void;
selectedZoomFocusMode?: ZoomFocusMode | null; selectedZoomFocusMode?: ZoomFocusMode | null;
onZoomFocusModeChange?: (mode: ZoomFocusMode) => void; onZoomFocusModeChange?: (mode: ZoomFocusMode) => void;
selectedZoomFocus?: ZoomFocus | null; selectedZoomFocus?: ZoomFocus | null;
@@ -367,6 +369,8 @@ export function SettingsPanel({
selectedZoomCustomScale, selectedZoomCustomScale,
onZoomCustomScaleChange, onZoomCustomScaleChange,
onZoomCustomScaleCommit, onZoomCustomScaleCommit,
onZoomPreviewStart,
onZoomPreviewEnd,
selectedZoomFocusMode, selectedZoomFocusMode,
onZoomFocusModeChange, onZoomFocusModeChange,
selectedZoomFocus, selectedZoomFocus,
@@ -949,6 +953,31 @@ export function SettingsPanel({
</div> </div>
</div> </div>
)} )}
{zoomEnabled && onZoomPreviewStart && onZoomPreviewEnd && (
<Button
type="button"
onPointerDown={() => onZoomPreviewStart()}
onPointerUp={() => onZoomPreviewEnd()}
onPointerLeave={() => onZoomPreviewEnd()}
onPointerCancel={() => onZoomPreviewEnd()}
onKeyDown={(e) => {
if ((e.key === " " || e.key === "Enter") && !e.repeat) {
e.preventDefault();
onZoomPreviewStart();
}
}}
onKeyUp={(e) => {
if (e.key === " " || e.key === "Enter") {
e.preventDefault();
onZoomPreviewEnd();
}
}}
onBlur={() => onZoomPreviewEnd()}
className="h-7 w-full select-none rounded-md border border-white/[0.08] bg-white/[0.04] text-[10px] font-semibold text-slate-300 transition-all duration-150 ease-out hover:bg-white/[0.08] hover:text-slate-100 active:border-[#34B27B]/50 active:bg-[#34B27B] active:text-white cursor-pointer"
>
{t("zoom.previewHold")}
</Button>
)}
{zoomEnabled && {zoomEnabled &&
selectedZoomFocusMode !== "auto" && selectedZoomFocusMode !== "auto" &&
selectedZoomFocus && selectedZoomFocus &&
@@ -195,6 +195,7 @@ export default function VideoEditor() {
const durationRef = useRef(duration); const durationRef = useRef(duration);
durationRef.current = duration; durationRef.current = duration;
const [selectedZoomId, setSelectedZoomId] = useState<string | null>(null); const [selectedZoomId, setSelectedZoomId] = useState<string | null>(null);
const [isPreviewingZoom, setIsPreviewingZoom] = useState(false);
const [selectedTrimId, setSelectedTrimId] = useState<string | null>(null); const [selectedTrimId, setSelectedTrimId] = useState<string | null>(null);
const [selectedSpeedId, setSelectedSpeedId] = useState<string | null>(null); const [selectedSpeedId, setSelectedSpeedId] = useState<string | null>(null);
const [selectedAnnotationId, setSelectedAnnotationId] = useState<string | null>(null); const [selectedAnnotationId, setSelectedAnnotationId] = useState<string | null>(null);
@@ -2120,6 +2121,7 @@ export default function VideoEditor() {
cursorMotionBlur={cursorMotionBlur} cursorMotionBlur={cursorMotionBlur}
cursorClickBounce={cursorClickBounce} cursorClickBounce={cursorClickBounce}
cursorClipToBounds={cursorClipToBounds} cursorClipToBounds={cursorClipToBounds}
isPreviewingZoom={isPreviewingZoom}
/> />
</div> </div>
</div> </div>
@@ -2155,6 +2157,8 @@ export default function VideoEditor() {
} }
onZoomCustomScaleChange={handleZoomCustomScaleChange} onZoomCustomScaleChange={handleZoomCustomScaleChange}
onZoomCustomScaleCommit={handleZoomCustomScaleCommit} onZoomCustomScaleCommit={handleZoomCustomScaleCommit}
onZoomPreviewStart={() => setIsPreviewingZoom(true)}
onZoomPreviewEnd={() => setIsPreviewingZoom(false)}
selectedZoomFocusMode={ selectedZoomFocusMode={
selectedZoomId selectedZoomId
? (zoomRegions.find((z) => z.id === selectedZoomId)?.focusMode ?? "manual") ? (zoomRegions.find((z) => z.id === selectedZoomId)?.focusMode ?? "manual")
+11 -1
View File
@@ -150,6 +150,9 @@ interface VideoPlaybackProps {
cursorMotionBlur?: number; cursorMotionBlur?: number;
cursorClickBounce?: number; cursorClickBounce?: number;
cursorClipToBounds?: boolean; cursorClipToBounds?: boolean;
// When true, render the selected zoom at the playhead even while paused —
// lets the editor preview the zoom effect without leaving the focus-edit view.
isPreviewingZoom?: boolean;
} }
export interface VideoPlaybackRef { export interface VideoPlaybackRef {
@@ -271,6 +274,7 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
cursorMotionBlur = DEFAULT_CURSOR_SETTINGS.motionBlur, cursorMotionBlur = DEFAULT_CURSOR_SETTINGS.motionBlur,
cursorClickBounce = DEFAULT_CURSOR_SETTINGS.clickBounce, cursorClickBounce = DEFAULT_CURSOR_SETTINGS.clickBounce,
cursorClipToBounds = DEFAULT_CURSOR_SETTINGS.clipToBounds, cursorClipToBounds = DEFAULT_CURSOR_SETTINGS.clipToBounds,
isPreviewingZoom = false,
}, },
ref, ref,
) => { ) => {
@@ -342,6 +346,7 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
const cursorMotionBlurRef = useRef(cursorMotionBlur); const cursorMotionBlurRef = useRef(cursorMotionBlur);
const cursorClickBounceRef = useRef(cursorClickBounce); const cursorClickBounceRef = useRef(cursorClickBounce);
const cursorClipToBoundsRef = useRef(cursorClipToBounds); const cursorClipToBoundsRef = useRef(cursorClipToBounds);
const isPreviewingZoomRef = useRef(isPreviewingZoom);
const motionBlurStateRef = useRef<MotionBlurState>(createMotionBlurState()); const motionBlurStateRef = useRef<MotionBlurState>(createMotionBlurState());
const onTimeUpdateRef = useRef(onTimeUpdate); const onTimeUpdateRef = useRef(onTimeUpdate);
const onPlayStateChangeRef = useRef(onPlayStateChange); const onPlayStateChangeRef = useRef(onPlayStateChange);
@@ -833,6 +838,10 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
cursorClipToBoundsRef.current = cursorClipToBounds; cursorClipToBoundsRef.current = cursorClipToBounds;
}, [cursorClipToBounds]); }, [cursorClipToBounds]);
useEffect(() => {
isPreviewingZoomRef.current = isPreviewingZoom;
}, [isPreviewingZoom]);
// Sync cursor overlay config when settings change // Sync cursor overlay config when settings change
useEffect(() => { useEffect(() => {
const overlay = cursorOverlayRef.current; const overlay = cursorOverlayRef.current;
@@ -1310,7 +1319,8 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
// If a zoom is selected but video is not playing, show default unzoomed view // If a zoom is selected but video is not playing, show default unzoomed view
const selectedId = selectedZoomIdRef.current; const selectedId = selectedZoomIdRef.current;
const hasSelectedZoom = selectedId !== null; const hasSelectedZoom = selectedId !== null;
const shouldShowUnzoomedView = hasSelectedZoom && !isPlayingRef.current; const shouldShowUnzoomedView =
hasSelectedZoom && !isPlayingRef.current && !isPreviewingZoomRef.current;
if (region && strength > 0 && !shouldShowUnzoomedView) { if (region && strength > 0 && !shouldShowUnzoomedView) {
const zoomScale = blendedScale ?? ZOOM_DEPTH_SCALES[region.depth]; const zoomScale = blendedScale ?? ZOOM_DEPTH_SCALES[region.depth];
+1
View File
@@ -1,5 +1,6 @@
{ {
"zoom": { "zoom": {
"previewHold": "اضغط مع الاستمرار لمعاينة تأثير التكبير",
"level": "مستوى التكبير", "level": "مستوى التكبير",
"selectRegion": "حدد منطقة التكبير للتعديل", "selectRegion": "حدد منطقة التكبير للتعديل",
"deleteZoom": "حذف التكبير", "deleteZoom": "حذف التكبير",
+1
View File
@@ -1,5 +1,6 @@
{ {
"zoom": { "zoom": {
"previewHold": "Hold to preview zoom effect",
"level": "Zoom Level", "level": "Zoom Level",
"customScale": "Custom Zoom", "customScale": "Custom Zoom",
"selectRegion": "Select a zoom region to adjust", "selectRegion": "Select a zoom region to adjust",
+1
View File
@@ -1,5 +1,6 @@
{ {
"zoom": { "zoom": {
"previewHold": "Mantener para previsualizar el efecto de zoom",
"level": "Nivel de zoom", "level": "Nivel de zoom",
"selectRegion": "Selecciona una región de zoom para ajustar", "selectRegion": "Selecciona una región de zoom para ajustar",
"deleteZoom": "Eliminar zoom", "deleteZoom": "Eliminar zoom",
+1
View File
@@ -1,5 +1,6 @@
{ {
"zoom": { "zoom": {
"previewHold": "Maintenir pour prévisualiser leffet de zoom",
"level": "Niveau de zoom", "level": "Niveau de zoom",
"selectRegion": "Sélectionnez une région de zoom à ajuster", "selectRegion": "Sélectionnez une région de zoom à ajuster",
"deleteZoom": "Supprimer le zoom", "deleteZoom": "Supprimer le zoom",
+1
View File
@@ -1,5 +1,6 @@
{ {
"zoom": { "zoom": {
"previewHold": "押している間ズーム効果をプレビュー",
"level": "ズーム倍率", "level": "ズーム倍率",
"selectRegion": "ズーム範囲を選択して調整", "selectRegion": "ズーム範囲を選択して調整",
"deleteZoom": "ズームを削除", "deleteZoom": "ズームを削除",
+1
View File
@@ -1,5 +1,6 @@
{ {
"zoom": { "zoom": {
"previewHold": "누르고 있으면 줌 효과 미리보기",
"level": "줌 레벨", "level": "줌 레벨",
"customScale": "커스텀 줌", "customScale": "커스텀 줌",
"selectRegion": "조정할 줌 구간을 선택하세요", "selectRegion": "조정할 줌 구간을 선택하세요",
+1
View File
@@ -1,5 +1,6 @@
{ {
"zoom": { "zoom": {
"previewHold": "Удерживайте для предпросмотра эффекта зума",
"level": "Уровень масштабирования", "level": "Уровень масштабирования",
"selectRegion": "Выберите область масштабирования для настройки", "selectRegion": "Выберите область масштабирования для настройки",
"deleteZoom": "Удалить масштабирование", "deleteZoom": "Удалить масштабирование",
+1
View File
@@ -1,5 +1,6 @@
{ {
"zoom": { "zoom": {
"previewHold": "Yakınlaştırma efektini önizlemek için basılı tutun",
"level": "Yakınlaştırma Seviyesi", "level": "Yakınlaştırma Seviyesi",
"selectRegion": "Ayarlamak için bir yakınlaştırma bölgesi seçin", "selectRegion": "Ayarlamak için bir yakınlaştırma bölgesi seçin",
"deleteZoom": "Yakınlaştırmayı Sil", "deleteZoom": "Yakınlaştırmayı Sil",
+1
View File
@@ -1,5 +1,6 @@
{ {
"zoom": { "zoom": {
"previewHold": "Giữ để xem trước hiệu ứng phóng to",
"level": "Mức độ thu phóng", "level": "Mức độ thu phóng",
"selectRegion": "Chọn vùng thu phóng để điều chỉnh", "selectRegion": "Chọn vùng thu phóng để điều chỉnh",
"deleteZoom": "Xóa thu phóng", "deleteZoom": "Xóa thu phóng",
+1
View File
@@ -1,5 +1,6 @@
{ {
"zoom": { "zoom": {
"previewHold": "按住预览放大效果",
"level": "缩放级别", "level": "缩放级别",
"selectRegion": "选择要调整的缩放区域", "selectRegion": "选择要调整的缩放区域",
"deleteZoom": "删除缩放", "deleteZoom": "删除缩放",
+1
View File
@@ -1,5 +1,6 @@
{ {
"zoom": { "zoom": {
"previewHold": "按住預覽放大效果",
"level": "縮放級別", "level": "縮放級別",
"selectRegion": "選擇要調整的縮放區域", "selectRegion": "選擇要調整的縮放區域",
"deleteZoom": "刪除縮放", "deleteZoom": "刪除縮放",