diff --git a/src/components/video-editor/AnnotationOverlay.tsx b/src/components/video-editor/AnnotationOverlay.tsx index 71600a2..7da795d 100644 --- a/src/components/video-editor/AnnotationOverlay.tsx +++ b/src/components/video-editor/AnnotationOverlay.tsx @@ -1,3 +1,4 @@ +import { useRef } from "react"; import { Rnd } from "react-rnd"; import type { AnnotationRegion } from "./types"; import { cn } from "@/lib/utils"; @@ -42,6 +43,8 @@ export function AnnotationOverlay({ isSelected }); + const isDraggingRef = useRef(false); + const renderContent = () => { switch (annotation.type) { case 'text': @@ -107,10 +110,18 @@ export function AnnotationOverlay({ { + isDraggingRef.current = true; + }} onDragStop={(_e, d) => { 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; + }, 100); }} onResizeStop={(_e, _direction, ref, _delta, position) => { const xPercent = (position.x / containerWidth) * 100; @@ -120,7 +131,10 @@ export function AnnotationOverlay({ onPositionChange(annotation.id, { x: xPercent, y: yPercent }); onSizeChange(annotation.id, { width: widthPercent, height: heightPercent }); }} - onClick={() => onClick(annotation.id)} + onClick={() => { + if (isDraggingRef.current) return; + onClick(annotation.id); + }} bounds="parent" className={cn( "cursor-move transition-all", diff --git a/src/components/video-editor/AnnotationSettingsPanel.tsx b/src/components/video-editor/AnnotationSettingsPanel.tsx index 133e31b..bc84a36 100644 --- a/src/components/video-editor/AnnotationSettingsPanel.tsx +++ b/src/components/video-editor/AnnotationSettingsPanel.tsx @@ -1,7 +1,7 @@ import { useState, useRef } from "react"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Button } from "@/components/ui/button"; -import { Trash2, Type, Image as ImageIcon, Upload, Bold, Italic, Underline, AlignLeft, AlignCenter, AlignRight, ChevronDown } from "lucide-react"; +import { Trash2, Type, Image as ImageIcon, Upload, Bold, Italic, Underline, AlignLeft, AlignCenter, AlignRight, ChevronDown, Info } from "lucide-react"; import { toast } from "sonner"; import Colorful from '@uiw/react-color-colorful'; import { hsvaToHex, hexToHsva } from '@uiw/color-convert'; @@ -351,6 +351,18 @@ export function AnnotationSettingsPanel({ Delete Annotation + +
+
+ + Shortcuts & Tips +
+
    +
  • Move playhead to overlapping annotation section and select an item.
  • +
  • Use Tab to cycle through overlapping items.
  • +
  • Use Shift+Tab to cycle backwards.
  • +
+
); diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 037770e..15917db 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -348,6 +348,22 @@ export default function VideoEditor() { ), ); }, []); + + // Global Tab prevention + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Tab') { + // Allow tab only in inputs/textareas + if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { + return; + } + e.preventDefault(); + } + }; + + window.addEventListener('keydown', handleKeyDown, { capture: true }); + return () => window.removeEventListener('keydown', handleKeyDown, { capture: true }); + }, []); useEffect(() => { if (selectedZoomId && !zoomRegions.some((region) => region.id === selectedZoomId)) { diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index bb51f34..7c0231a 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -800,6 +800,9 @@ const VideoPlayback = forwardRef(({ {(() => { const filtered = (annotationRegions || []).filter((annotation) => { if (typeof annotation.startMs !== 'number' || typeof annotation.endMs !== 'number') return false; + + if (annotation.id === selectedAnnotationId) return true; + const timeMs = Math.round(currentTime * 1000); return timeMs >= annotation.startMs && timeMs <= annotation.endMs; }); diff --git a/src/components/video-editor/timeline/Item.tsx b/src/components/video-editor/timeline/Item.tsx index 8f7242f..af06107 100644 --- a/src/components/video-editor/timeline/Item.tsx +++ b/src/components/video-editor/timeline/Item.tsx @@ -32,7 +32,8 @@ export default function Item({ isSelected = false, onSelect, zoomDepth = 1, - variant = 'zoom' + variant = 'zoom', + children }: ItemProps) { const { setNodeRef, attributes, listeners, itemStyle, itemContentStyle } = useItem({ id, @@ -107,7 +108,7 @@ export default function Item({ <> - Note + {children} )} diff --git a/src/components/video-editor/timeline/TimelineEditor.tsx b/src/components/video-editor/timeline/TimelineEditor.tsx index 568388c..ab2ddc7 100644 --- a/src/components/video-editor/timeline/TimelineEditor.tsx +++ b/src/components/video-editor/timeline/TimelineEditor.tsx @@ -688,8 +688,7 @@ export default function TimelineEditor({ onSelectAnnotation?.(overlapping[nextIndex].id); } } - } - + } if ((e.key === 'd' || e.key === 'D') && (e.ctrlKey || e.metaKey)) { if (selectedKeyframeId) { deleteSelectedKeyframe(); @@ -743,7 +742,7 @@ export default function TimelineEditor({ const preview = region.content.trim() || 'Empty text'; label = preview.length > 20 ? `${preview.substring(0, 20)}...` : preview; } else if (region.type === 'image') { - label = '🖼️ Image'; + label = 'Image'; } else { label = 'Annotation'; }