From 2993a57853dbcbe00063a4090471be35b600f379 Mon Sep 17 00:00:00 2001
From: auberginewly <3127221787@qq.com>
Date: Tue, 19 May 2026 10:48:44 +0800
Subject: [PATCH 1/4] feat(zoom): add hold-to-preview button for zoom focus
editing
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
When a zoom region is selected and paused, the editor shows the full
un-zoomed frame for focus-point placement. This adds a press-and-hold
"Preview" button so editors can momentarily see the zoomed result at the
current focus + depth — like a before/after compare — without entering
playback.
- VideoPlayback: new transient isPreviewingZoom prop; shouldShowUnzoomedView
now also requires !isPreviewingZoom, so the zoom transform is applied at
the playhead while previewing
- VideoEditor: isPreviewingZoom state wired to VideoPlayback and to
onZoomPreviewStart/End handlers
- SettingsPanel: hold button in the zoom controls (pointer down/up/leave/
cancel)
- i18n: zoom.previewHold added across all 11 locales
Prototype for #612 — placement (panel vs overlay) and hold-vs-toggle still
open for maintainer direction.
---
src/components/video-editor/SettingsPanel.tsx | 16 ++++++++++++++++
src/components/video-editor/VideoEditor.tsx | 4 ++++
src/components/video-editor/VideoPlayback.tsx | 12 +++++++++++-
src/i18n/locales/ar/settings.json | 1 +
src/i18n/locales/en/settings.json | 1 +
src/i18n/locales/es/settings.json | 1 +
src/i18n/locales/fr/settings.json | 1 +
src/i18n/locales/ja-JP/settings.json | 1 +
src/i18n/locales/ko-KR/settings.json | 1 +
src/i18n/locales/ru/settings.json | 1 +
src/i18n/locales/tr/settings.json | 1 +
src/i18n/locales/vi/settings.json | 1 +
src/i18n/locales/zh-CN/settings.json | 1 +
src/i18n/locales/zh-TW/settings.json | 1 +
14 files changed, 42 insertions(+), 1 deletion(-)
diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx
index 1001d4b7..1b37499c 100644
--- a/src/components/video-editor/SettingsPanel.tsx
+++ b/src/components/video-editor/SettingsPanel.tsx
@@ -230,6 +230,8 @@ interface SettingsPanelProps {
selectedZoomCustomScale?: number | null;
onZoomCustomScaleChange?: (scale: number) => void;
onZoomCustomScaleCommit?: () => void;
+ onZoomPreviewStart?: () => void;
+ onZoomPreviewEnd?: () => void;
selectedZoomFocusMode?: ZoomFocusMode | null;
onZoomFocusModeChange?: (mode: ZoomFocusMode) => void;
selectedZoomFocus?: ZoomFocus | null;
@@ -359,6 +361,8 @@ export function SettingsPanel({
selectedZoomCustomScale,
onZoomCustomScaleChange,
onZoomCustomScaleCommit,
+ onZoomPreviewStart,
+ onZoomPreviewEnd,
selectedZoomFocusMode,
onZoomFocusModeChange,
selectedZoomFocus,
@@ -940,6 +944,18 @@ export function SettingsPanel({
)}
+ {zoomEnabled && (onZoomPreviewStart || onZoomPreviewEnd) && (
+
+ )}
{zoomEnabled &&
selectedZoomFocusMode !== "auto" &&
selectedZoomFocus &&
diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx
index 15d582d3..818f663c 100644
--- a/src/components/video-editor/VideoEditor.tsx
+++ b/src/components/video-editor/VideoEditor.tsx
@@ -194,6 +194,7 @@ export default function VideoEditor() {
const durationRef = useRef(duration);
durationRef.current = duration;
const [selectedZoomId, setSelectedZoomId] = useState(null);
+ const [isPreviewingZoom, setIsPreviewingZoom] = useState(false);
const [selectedTrimId, setSelectedTrimId] = useState(null);
const [selectedSpeedId, setSelectedSpeedId] = useState(null);
const [selectedAnnotationId, setSelectedAnnotationId] = useState(null);
@@ -2112,6 +2113,7 @@ export default function VideoEditor() {
cursorMotionBlur={cursorMotionBlur}
cursorClickBounce={cursorClickBounce}
cursorClipToBounds={cursorClipToBounds}
+ isPreviewingZoom={isPreviewingZoom}
/>
@@ -2147,6 +2149,8 @@ export default function VideoEditor() {
}
onZoomCustomScaleChange={handleZoomCustomScaleChange}
onZoomCustomScaleCommit={handleZoomCustomScaleCommit}
+ onZoomPreviewStart={() => setIsPreviewingZoom(true)}
+ onZoomPreviewEnd={() => setIsPreviewingZoom(false)}
selectedZoomFocusMode={
selectedZoomId
? (zoomRegions.find((z) => z.id === selectedZoomId)?.focusMode ?? "manual")
diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx
index 45ca9dfd..0c58572f 100644
--- a/src/components/video-editor/VideoPlayback.tsx
+++ b/src/components/video-editor/VideoPlayback.tsx
@@ -149,6 +149,9 @@ interface VideoPlaybackProps {
cursorMotionBlur?: number;
cursorClickBounce?: number;
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 {
@@ -270,6 +273,7 @@ const VideoPlayback = forwardRef(
cursorMotionBlur = DEFAULT_CURSOR_MOTION_BLUR,
cursorClickBounce = DEFAULT_CURSOR_CLICK_BOUNCE,
cursorClipToBounds = false,
+ isPreviewingZoom = false,
},
ref,
) => {
@@ -341,6 +345,7 @@ const VideoPlayback = forwardRef(
const cursorMotionBlurRef = useRef(cursorMotionBlur);
const cursorClickBounceRef = useRef(cursorClickBounce);
const cursorClipToBoundsRef = useRef(cursorClipToBounds);
+ const isPreviewingZoomRef = useRef(isPreviewingZoom);
const motionBlurStateRef = useRef(createMotionBlurState());
const onTimeUpdateRef = useRef(onTimeUpdate);
const onPlayStateChangeRef = useRef(onPlayStateChange);
@@ -832,6 +837,10 @@ const VideoPlayback = forwardRef(
cursorClipToBoundsRef.current = cursorClipToBounds;
}, [cursorClipToBounds]);
+ useEffect(() => {
+ isPreviewingZoomRef.current = isPreviewingZoom;
+ }, [isPreviewingZoom]);
+
// Sync cursor overlay config when settings change
useEffect(() => {
const overlay = cursorOverlayRef.current;
@@ -1309,7 +1318,8 @@ const VideoPlayback = forwardRef(
// If a zoom is selected but video is not playing, show default unzoomed view
const selectedId = selectedZoomIdRef.current;
const hasSelectedZoom = selectedId !== null;
- const shouldShowUnzoomedView = hasSelectedZoom && !isPlayingRef.current;
+ const shouldShowUnzoomedView =
+ hasSelectedZoom && !isPlayingRef.current && !isPreviewingZoomRef.current;
if (region && strength > 0 && !shouldShowUnzoomedView) {
const zoomScale = blendedScale ?? ZOOM_DEPTH_SCALES[region.depth];
diff --git a/src/i18n/locales/ar/settings.json b/src/i18n/locales/ar/settings.json
index 1b4907c1..45789272 100644
--- a/src/i18n/locales/ar/settings.json
+++ b/src/i18n/locales/ar/settings.json
@@ -1,5 +1,6 @@
{
"zoom": {
+ "previewHold": "اضغط مع الاستمرار للمعاينة",
"level": "مستوى التكبير",
"selectRegion": "حدد منطقة التكبير للتعديل",
"deleteZoom": "حذف التكبير",
diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json
index 9877d211..b5fc2db7 100644
--- a/src/i18n/locales/en/settings.json
+++ b/src/i18n/locales/en/settings.json
@@ -1,5 +1,6 @@
{
"zoom": {
+ "previewHold": "Hold to preview",
"level": "Zoom Level",
"customScale": "Custom Zoom",
"selectRegion": "Select a zoom region to adjust",
diff --git a/src/i18n/locales/es/settings.json b/src/i18n/locales/es/settings.json
index d03c0c72..6401db8a 100644
--- a/src/i18n/locales/es/settings.json
+++ b/src/i18n/locales/es/settings.json
@@ -1,5 +1,6 @@
{
"zoom": {
+ "previewHold": "Mantener para previsualizar",
"level": "Nivel de zoom",
"selectRegion": "Selecciona una región de zoom para ajustar",
"deleteZoom": "Eliminar zoom",
diff --git a/src/i18n/locales/fr/settings.json b/src/i18n/locales/fr/settings.json
index 8a53d44f..5af86913 100644
--- a/src/i18n/locales/fr/settings.json
+++ b/src/i18n/locales/fr/settings.json
@@ -1,5 +1,6 @@
{
"zoom": {
+ "previewHold": "Maintenir pour prévisualiser",
"level": "Niveau de zoom",
"selectRegion": "Sélectionnez une région de zoom à ajuster",
"deleteZoom": "Supprimer le zoom",
diff --git a/src/i18n/locales/ja-JP/settings.json b/src/i18n/locales/ja-JP/settings.json
index e7e15fab..5e2ee8b5 100644
--- a/src/i18n/locales/ja-JP/settings.json
+++ b/src/i18n/locales/ja-JP/settings.json
@@ -1,5 +1,6 @@
{
"zoom": {
+ "previewHold": "押している間プレビュー",
"level": "ズーム倍率",
"selectRegion": "ズーム範囲を選択して調整",
"deleteZoom": "ズームを削除",
diff --git a/src/i18n/locales/ko-KR/settings.json b/src/i18n/locales/ko-KR/settings.json
index c56df1dc..5e0c3769 100644
--- a/src/i18n/locales/ko-KR/settings.json
+++ b/src/i18n/locales/ko-KR/settings.json
@@ -1,5 +1,6 @@
{
"zoom": {
+ "previewHold": "누르고 있으면 미리보기",
"level": "줌 레벨",
"selectRegion": "조정할 줌 구간을 선택하세요",
"deleteZoom": "줌 삭제",
diff --git a/src/i18n/locales/ru/settings.json b/src/i18n/locales/ru/settings.json
index 3e77999c..f781ce5b 100644
--- a/src/i18n/locales/ru/settings.json
+++ b/src/i18n/locales/ru/settings.json
@@ -1,5 +1,6 @@
{
"zoom": {
+ "previewHold": "Удерживайте для предпросмотра",
"level": "Уровень масштабирования",
"selectRegion": "Выберите область масштабирования для настройки",
"deleteZoom": "Удалить масштабирование",
diff --git a/src/i18n/locales/tr/settings.json b/src/i18n/locales/tr/settings.json
index 8d998c30..a5fb9f67 100644
--- a/src/i18n/locales/tr/settings.json
+++ b/src/i18n/locales/tr/settings.json
@@ -1,5 +1,6 @@
{
"zoom": {
+ "previewHold": "Önizlemek için basılı tutun",
"level": "Yakınlaştırma Seviyesi",
"selectRegion": "Ayarlamak için bir yakınlaştırma bölgesi seçin",
"deleteZoom": "Yakınlaştırmayı Sil",
diff --git a/src/i18n/locales/vi/settings.json b/src/i18n/locales/vi/settings.json
index 84497621..76e27087 100644
--- a/src/i18n/locales/vi/settings.json
+++ b/src/i18n/locales/vi/settings.json
@@ -1,5 +1,6 @@
{
"zoom": {
+ "previewHold": "Giữ để xem trước",
"level": "Mức độ thu phóng",
"selectRegion": "Chọn vùng thu phóng để điều chỉnh",
"deleteZoom": "Xóa thu phóng",
diff --git a/src/i18n/locales/zh-CN/settings.json b/src/i18n/locales/zh-CN/settings.json
index 62dcae7c..4c5ea2da 100644
--- a/src/i18n/locales/zh-CN/settings.json
+++ b/src/i18n/locales/zh-CN/settings.json
@@ -1,5 +1,6 @@
{
"zoom": {
+ "previewHold": "按住预览",
"level": "缩放级别",
"selectRegion": "选择要调整的缩放区域",
"deleteZoom": "删除缩放",
diff --git a/src/i18n/locales/zh-TW/settings.json b/src/i18n/locales/zh-TW/settings.json
index 100c7564..decda1bc 100644
--- a/src/i18n/locales/zh-TW/settings.json
+++ b/src/i18n/locales/zh-TW/settings.json
@@ -1,5 +1,6 @@
{
"zoom": {
+ "previewHold": "按住預覽",
"level": "縮放級別",
"selectRegion": "選擇要調整的縮放區域",
"deleteZoom": "刪除縮放",
From 24ce67b5a7297efb360b73cd3e42571682e06ae8 Mon Sep 17 00:00:00 2001
From: auberginewly <3127221787@qq.com>
Date: Tue, 19 May 2026 10:51:22 +0800
Subject: [PATCH 2/4] i18n(zoom): clarify previewHold wording to "preview zoom
effect"
Make the label explicit about what holding previews (the zoom effect),
across all 11 locales.
---
src/i18n/locales/ar/settings.json | 2 +-
src/i18n/locales/en/settings.json | 2 +-
src/i18n/locales/es/settings.json | 2 +-
src/i18n/locales/fr/settings.json | 2 +-
src/i18n/locales/ja-JP/settings.json | 2 +-
src/i18n/locales/ko-KR/settings.json | 2 +-
src/i18n/locales/ru/settings.json | 2 +-
src/i18n/locales/tr/settings.json | 2 +-
src/i18n/locales/vi/settings.json | 2 +-
src/i18n/locales/zh-CN/settings.json | 2 +-
src/i18n/locales/zh-TW/settings.json | 2 +-
11 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/src/i18n/locales/ar/settings.json b/src/i18n/locales/ar/settings.json
index 45789272..76729d84 100644
--- a/src/i18n/locales/ar/settings.json
+++ b/src/i18n/locales/ar/settings.json
@@ -1,6 +1,6 @@
{
"zoom": {
- "previewHold": "اضغط مع الاستمرار للمعاينة",
+ "previewHold": "اضغط مع الاستمرار لمعاينة تأثير التكبير",
"level": "مستوى التكبير",
"selectRegion": "حدد منطقة التكبير للتعديل",
"deleteZoom": "حذف التكبير",
diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json
index b5fc2db7..477b4598 100644
--- a/src/i18n/locales/en/settings.json
+++ b/src/i18n/locales/en/settings.json
@@ -1,6 +1,6 @@
{
"zoom": {
- "previewHold": "Hold to preview",
+ "previewHold": "Hold to preview zoom effect",
"level": "Zoom Level",
"customScale": "Custom Zoom",
"selectRegion": "Select a zoom region to adjust",
diff --git a/src/i18n/locales/es/settings.json b/src/i18n/locales/es/settings.json
index 6401db8a..09fabad4 100644
--- a/src/i18n/locales/es/settings.json
+++ b/src/i18n/locales/es/settings.json
@@ -1,6 +1,6 @@
{
"zoom": {
- "previewHold": "Mantener para previsualizar",
+ "previewHold": "Mantener para previsualizar el efecto de zoom",
"level": "Nivel de zoom",
"selectRegion": "Selecciona una región de zoom para ajustar",
"deleteZoom": "Eliminar zoom",
diff --git a/src/i18n/locales/fr/settings.json b/src/i18n/locales/fr/settings.json
index 5af86913..6009765f 100644
--- a/src/i18n/locales/fr/settings.json
+++ b/src/i18n/locales/fr/settings.json
@@ -1,6 +1,6 @@
{
"zoom": {
- "previewHold": "Maintenir pour prévisualiser",
+ "previewHold": "Maintenir pour prévisualiser l’effet de zoom",
"level": "Niveau de zoom",
"selectRegion": "Sélectionnez une région de zoom à ajuster",
"deleteZoom": "Supprimer le zoom",
diff --git a/src/i18n/locales/ja-JP/settings.json b/src/i18n/locales/ja-JP/settings.json
index 5e2ee8b5..f1a604c9 100644
--- a/src/i18n/locales/ja-JP/settings.json
+++ b/src/i18n/locales/ja-JP/settings.json
@@ -1,6 +1,6 @@
{
"zoom": {
- "previewHold": "押している間プレビュー",
+ "previewHold": "押している間ズーム効果をプレビュー",
"level": "ズーム倍率",
"selectRegion": "ズーム範囲を選択して調整",
"deleteZoom": "ズームを削除",
diff --git a/src/i18n/locales/ko-KR/settings.json b/src/i18n/locales/ko-KR/settings.json
index 5e0c3769..86fde766 100644
--- a/src/i18n/locales/ko-KR/settings.json
+++ b/src/i18n/locales/ko-KR/settings.json
@@ -1,6 +1,6 @@
{
"zoom": {
- "previewHold": "누르고 있으면 미리보기",
+ "previewHold": "누르고 있으면 줌 효과 미리보기",
"level": "줌 레벨",
"selectRegion": "조정할 줌 구간을 선택하세요",
"deleteZoom": "줌 삭제",
diff --git a/src/i18n/locales/ru/settings.json b/src/i18n/locales/ru/settings.json
index f781ce5b..1ca59865 100644
--- a/src/i18n/locales/ru/settings.json
+++ b/src/i18n/locales/ru/settings.json
@@ -1,6 +1,6 @@
{
"zoom": {
- "previewHold": "Удерживайте для предпросмотра",
+ "previewHold": "Удерживайте для предпросмотра эффекта зума",
"level": "Уровень масштабирования",
"selectRegion": "Выберите область масштабирования для настройки",
"deleteZoom": "Удалить масштабирование",
diff --git a/src/i18n/locales/tr/settings.json b/src/i18n/locales/tr/settings.json
index a5fb9f67..5adc2420 100644
--- a/src/i18n/locales/tr/settings.json
+++ b/src/i18n/locales/tr/settings.json
@@ -1,6 +1,6 @@
{
"zoom": {
- "previewHold": "Önizlemek için basılı tutun",
+ "previewHold": "Yakınlaştırma efektini önizlemek için basılı tutun",
"level": "Yakınlaştırma Seviyesi",
"selectRegion": "Ayarlamak için bir yakınlaştırma bölgesi seçin",
"deleteZoom": "Yakınlaştırmayı Sil",
diff --git a/src/i18n/locales/vi/settings.json b/src/i18n/locales/vi/settings.json
index 76e27087..ba690465 100644
--- a/src/i18n/locales/vi/settings.json
+++ b/src/i18n/locales/vi/settings.json
@@ -1,6 +1,6 @@
{
"zoom": {
- "previewHold": "Giữ để xem trước",
+ "previewHold": "Giữ để xem trước hiệu ứng phóng to",
"level": "Mức độ thu phóng",
"selectRegion": "Chọn vùng thu phóng để điều chỉnh",
"deleteZoom": "Xóa thu phóng",
diff --git a/src/i18n/locales/zh-CN/settings.json b/src/i18n/locales/zh-CN/settings.json
index 4c5ea2da..642d78f5 100644
--- a/src/i18n/locales/zh-CN/settings.json
+++ b/src/i18n/locales/zh-CN/settings.json
@@ -1,6 +1,6 @@
{
"zoom": {
- "previewHold": "按住预览",
+ "previewHold": "按住预览放大效果",
"level": "缩放级别",
"selectRegion": "选择要调整的缩放区域",
"deleteZoom": "删除缩放",
diff --git a/src/i18n/locales/zh-TW/settings.json b/src/i18n/locales/zh-TW/settings.json
index decda1bc..825169dc 100644
--- a/src/i18n/locales/zh-TW/settings.json
+++ b/src/i18n/locales/zh-TW/settings.json
@@ -1,6 +1,6 @@
{
"zoom": {
- "previewHold": "按住預覽",
+ "previewHold": "按住預覽放大效果",
"level": "縮放級別",
"selectRegion": "選擇要調整的縮放區域",
"deleteZoom": "刪除縮放",
From a686fa012a3c132e918bbdd5aece7411ee574696 Mon Sep 17 00:00:00 2001
From: auberginewly <3127221787@qq.com>
Date: Tue, 19 May 2026 11:02:28 +0800
Subject: [PATCH 3/4] =?UTF-8?q?fix(zoom):=20address=20PR=20review=20?=
=?UTF-8?q?=E2=80=94=20preview=20selected=20zoom=20+=20keyboard=20a11y?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- VideoPlayback: while holding Preview, render the SELECTED zoom at full
strength regardless of the playhead, instead of whatever findDominantRegion
returns at currentTime (which is none/another zoom when the playhead is
outside the selection). Uses getZoomScale/getRotation3D for the region's
configured scale and 3D preset.
- SettingsPanel: require both onZoomPreviewStart && onZoomPreviewEnd to render
the button (full lifecycle), and add keyboard support — Space/Enter keydown
(repeat-guarded) starts preview, keyup/blur ends it.
---
src/components/video-editor/SettingsPanel.tsx | 23 +++++++++++++++----
src/components/video-editor/VideoPlayback.tsx | 19 ++++++++++++++-
2 files changed, 36 insertions(+), 6 deletions(-)
diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx
index 1b37499c..383fc48c 100644
--- a/src/components/video-editor/SettingsPanel.tsx
+++ b/src/components/video-editor/SettingsPanel.tsx
@@ -944,13 +944,26 @@ export function SettingsPanel({
)}
- {zoomEnabled && (onZoomPreviewStart || onZoomPreviewEnd) && (
+ {zoomEnabled && onZoomPreviewStart && onZoomPreviewEnd && (