diff --git a/src/components/video-editor/AnnotationOverlay.tsx b/src/components/video-editor/AnnotationOverlay.tsx index 7da795d..c1f743a 100644 --- a/src/components/video-editor/AnnotationOverlay.tsx +++ b/src/components/video-editor/AnnotationOverlay.tsx @@ -2,6 +2,14 @@ import { useRef } from "react"; import { Rnd } from "react-rnd"; import type { AnnotationRegion } from "./types"; import { cn } from "@/lib/utils"; +import { + FaArrowUp, FaArrowDown, FaArrowLeft, FaArrowRight, + FaCircle, FaSquare, FaStar, FaHeart, FaPlay +} from "react-icons/fa"; +import { + BsArrowUpRight, BsArrowDownRight, BsArrowDownLeft, BsArrowUpLeft +} from "react-icons/bs"; +import { BiRectangle } from "react-icons/bi"; interface AnnotationOverlayProps { annotation: AnnotationRegion; @@ -45,6 +53,84 @@ export function AnnotationOverlay({ const isDraggingRef = useRef(false); + const renderArrow = () => { + const direction = annotation.figureData?.arrowDirection || 'right'; + const color = annotation.figureData?.color || '#34B27B'; + const iconProps = { + style: { + width: '100%', + height: '100%', + color, + filter: 'drop-shadow(0 2px 8px rgba(0,0,0,0.3))' + } + }; + + switch (direction) { + case 'up': return ; + case 'down': return ; + case 'left': return ; + case 'right': return ; + case 'up-right': return ; + case 'up-left': return ; + case 'down-right': return ; + case 'down-left': return ; + default: return ; + } + }; + + const renderShape = () => { + const shapeType = annotation.figureData?.shapeType || 'circle'; + const color = annotation.figureData?.color || '#34B27B'; + const filled = annotation.figureData?.filled ?? true; + const strokeWidth = annotation.figureData?.strokeWidth || 4; + + const shapeStyle: React.CSSProperties = { + width: '100%', + height: '100%', + color: filled ? color : 'transparent', + stroke: color, + strokeWidth: filled ? 0 : strokeWidth, + filter: 'drop-shadow(0 2px 8px rgba(0,0,0,0.3))' + }; + + const IconComponent = (() => { + switch (shapeType) { + case 'circle': return FaCircle; + case 'square': return FaSquare; + case 'triangle': return FaPlay; + case 'rectangle': return BiRectangle; + case 'star': return FaStar; + case 'heart': return FaHeart; + default: return FaCircle; + } + })(); + + return filled ? ( + + ) : ( + + {shapeType === 'circle' && ( + + )} + {shapeType === 'square' && ( + + )} + {shapeType === 'rectangle' && ( + + )} + {shapeType === 'triangle' && ( + + )} + {shapeType === 'star' && ( + + )} + {shapeType === 'heart' && ( + + )} + + ); + }; + const renderContent = () => { switch (annotation.type) { case 'text': @@ -69,7 +155,6 @@ export function AnnotationOverlay({ textAlign: annotation.style.textAlign, wordBreak: 'break-word', whiteSpace: 'pre-wrap', - textShadow: '0 2px 8px rgba(0,0,0,0.5), 0 0 2px rgba(0,0,0,0.8)', boxDecorationBreak: 'clone', WebkitBoxDecorationBreak: 'clone', padding: '0.1em 0.2em', @@ -82,8 +167,6 @@ export function AnnotationOverlay({ ); - - case 'image': if (annotation.content && annotation.content.startsWith('data:image')) { return ( @@ -101,6 +184,44 @@ export function AnnotationOverlay({ ); + case 'figure': + if (!annotation.figureData) { + return ( +
+ No figure data +
+ ); + } + + const figureType = annotation.figureData.figureType; + + if (figureType === 'arrow') { + return ( +
+ {renderArrow()} +
+ ); + } + + if (figureType === 'shape') { + return ( +
+ {renderShape()} +
+ ); + } + + if (figureType === 'emoji') { + const emojiSize = annotation.figureData.emojiSize || 64; + return ( +
+ {annotation.figureData.emoji || '😊'} +
+ ); + } + + return null; + default: return null; } @@ -197,6 +318,7 @@ export function AnnotationOverlay({ "w-full h-full rounded-lg", annotation.type === 'text' && "bg-transparent", annotation.type === 'image' && "bg-transparent", + annotation.type === 'figure' && "bg-transparent", isSelected && "shadow-lg" )} > diff --git a/src/components/video-editor/AnnotationSettingsPanel.tsx b/src/components/video-editor/AnnotationSettingsPanel.tsx index bc84a36..c8fdb6b 100644 --- a/src/components/video-editor/AnnotationSettingsPanel.tsx +++ b/src/components/video-editor/AnnotationSettingsPanel.tsx @@ -1,20 +1,34 @@ 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, Info } from "lucide-react"; +import { Trash2, Type, Image as ImageIcon, Upload, Bold, Italic, Underline, AlignLeft, AlignCenter, AlignRight, ChevronDown, Info, Shapes, Smile } from "lucide-react"; import { toast } from "sonner"; import Colorful from '@uiw/react-color-colorful'; import { hsvaToHex, hexToHsva } from '@uiw/color-convert'; -import type { AnnotationRegion, AnnotationType } from "./types"; +import type { AnnotationRegion, AnnotationType, FigureType, ArrowDirection, ShapeType, FigureData } from "./types"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; +import { Switch } from "@/components/ui/switch"; +import { Slider } from "@/components/ui/slider"; +import { cn } from "@/lib/utils"; +import EmojiPicker, { EmojiClickData } from 'emoji-picker-react'; +import { + FaArrowUp, FaArrowDown, FaArrowLeft, FaArrowRight, + FaCircle, FaSquare, FaStar, FaHeart +} from "react-icons/fa"; +import { + BsArrowUpRight, BsArrowDownRight, BsArrowDownLeft, BsArrowUpLeft +} from "react-icons/bs"; +import { FaPlay } from "react-icons/fa"; +import { BiRectangle } from "react-icons/bi"; interface AnnotationSettingsPanelProps { annotation: AnnotationRegion; onContentChange: (content: string) => void; onTypeChange: (type: AnnotationType) => void; onStyleChange: (style: Partial) => void; + onFigureDataChange?: (figureData: FigureData) => void; onDelete: () => void; } @@ -36,11 +50,16 @@ export function AnnotationSettingsPanel({ onContentChange, onTypeChange, onStyleChange, + onFigureDataChange, onDelete, }: AnnotationSettingsPanelProps) { const fileInputRef = useRef(null); const [textColorHsva, setTextColorHsva] = useState(hexToHsva(annotation.style.color)); const [bgColorHsva, setBgColorHsva] = useState(hexToHsva(annotation.style.backgroundColor || '#00000000')); + const [figureColorHsva, setFigureColorHsva] = useState( + hexToHsva(annotation.figureData?.color || '#34B27B') + ); + const [showEmojiPicker, setShowEmojiPicker] = useState(false); @@ -92,7 +111,7 @@ export function AnnotationSettingsPanel({ {/* Type Selector */} onTypeChange(value as AnnotationType)} className="mb-6"> - + Text @@ -101,6 +120,10 @@ export function AnnotationSettingsPanel({ Image + + + Figure + {/* Text Content */} @@ -108,7 +131,7 @@ export function AnnotationSettingsPanel({