From bfa425b262379be5a8e856a36aeacde9e82ef5f2 Mon Sep 17 00:00:00 2001 From: Mahdy Arief Date: Sun, 29 Mar 2026 09:46:49 +0700 Subject: [PATCH 1/4] feat(editor): implement blur annotation with CSS-driven preview and robust canvas export pipeline --- .../video-editor/AnnotationOverlay.tsx | 17 ++- .../video-editor/AnnotationSettingsPanel.tsx | 49 +++++- src/components/video-editor/SettingsPanel.tsx | 15 +- src/components/video-editor/VideoEditor.tsx | 92 ++++++----- src/components/video-editor/VideoPlayback.tsx | 39 ++--- src/components/video-editor/types.ts | 3 +- src/lib/exporter/annotationRenderer.ts | 143 +++++++++++++----- 7 files changed, 246 insertions(+), 112 deletions(-) diff --git a/src/components/video-editor/AnnotationOverlay.tsx b/src/components/video-editor/AnnotationOverlay.tsx index 3260a542..31b2e043 100644 --- a/src/components/video-editor/AnnotationOverlay.tsx +++ b/src/components/video-editor/AnnotationOverlay.tsx @@ -50,8 +50,8 @@ export function AnnotationOverlay({
@@ -111,6 +111,17 @@ export function AnnotationOverlay({
); + case 'blur': + return ( +
+ ); + default: return null; } @@ -127,7 +138,7 @@ export function AnnotationOverlay({ const xPercent = (d.x / containerWidth) * 100; const yPercent = (d.y / containerHeight) * 100; onPositionChange(annotation.id, { x: xPercent, y: yPercent }); - + // Reset dragging flag after a short delay to prevent click event setTimeout(() => { isDraggingRef.current = false; diff --git a/src/components/video-editor/AnnotationSettingsPanel.tsx b/src/components/video-editor/AnnotationSettingsPanel.tsx index 2cddf970..2dbb8e67 100644 --- a/src/components/video-editor/AnnotationSettingsPanel.tsx +++ b/src/components/video-editor/AnnotationSettingsPanel.tsx @@ -21,6 +21,7 @@ interface AnnotationSettingsPanelProps { onTypeChange: (type: AnnotationType) => void; onStyleChange: (style: Partial) => void; onFigureDataChange?: (figureData: FigureData) => void; + onBlurIntensityChange?: (intensity: number) => void; onDelete: () => void; } @@ -43,6 +44,7 @@ export function AnnotationSettingsPanel({ onTypeChange, onStyleChange, onFigureDataChange, + onBlurIntensityChange, onDelete, }: AnnotationSettingsPanelProps) { const t = useScopedT('editor'); @@ -128,21 +130,29 @@ export function AnnotationSettingsPanel({ {/* Type Selector */} onTypeChange(value as AnnotationType)} className="mb-6"> - - - + + + {t('annotations.text')} - - + + {t('annotations.image')} - - + + {t('annotations.arrow')} + + + + + + + {t('annotations.blur', 'Blur')} + {/* Text Content */} @@ -499,6 +509,31 @@ export function AnnotationSettingsPanel({
+ +
+
+ + + {annotation.blurIntensity ?? 12}% + +
+ { + onBlurIntensityChange?.(value); + }} + min={1} + max={100} + step={1} + className="w-full" + /> +

+ {t('annotations.blurDescription', 'Obscure sensitive information by blurring the underlying video content.')} +

+
+