final annotation settings

This commit is contained in:
Siddharth
2025-11-30 21:14:55 -07:00
parent 79e40cef68
commit 6ac712eaac
6 changed files with 469 additions and 24 deletions
@@ -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 <FaArrowUp {...iconProps} />;
case 'down': return <FaArrowDown {...iconProps} />;
case 'left': return <FaArrowLeft {...iconProps} />;
case 'right': return <FaArrowRight {...iconProps} />;
case 'up-right': return <BsArrowUpRight {...iconProps} />;
case 'up-left': return <BsArrowUpLeft {...iconProps} />;
case 'down-right': return <BsArrowDownRight {...iconProps} />;
case 'down-left': return <BsArrowDownLeft {...iconProps} />;
default: return <FaArrowRight {...iconProps} />;
}
};
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 ? (
<IconComponent style={shapeStyle} />
) : (
<svg viewBox="0 0 100 100" style={{ width: '100%', height: '100%' }}>
{shapeType === 'circle' && (
<circle cx="50" cy="50" r="40" fill="none" stroke={color} strokeWidth={strokeWidth} />
)}
{shapeType === 'square' && (
<rect x="10" y="10" width="80" height="80" fill="none" stroke={color} strokeWidth={strokeWidth} />
)}
{shapeType === 'rectangle' && (
<rect x="5" y="20" width="90" height="60" fill="none" stroke={color} strokeWidth={strokeWidth} />
)}
{shapeType === 'triangle' && (
<polygon points="50,10 90,90 10,90" fill="none" stroke={color} strokeWidth={strokeWidth} />
)}
{shapeType === 'star' && (
<polygon points="50,5 61,38 95,38 68,59 79,92 50,71 21,92 32,59 5,38 39,38" fill="none" stroke={color} strokeWidth={strokeWidth} />
)}
{shapeType === 'heart' && (
<path d="M50,85 C50,85 10,60 10,35 C10,20 20,10 30,10 C40,10 50,20 50,20 C50,20 60,10 70,10 C80,10 90,20 90,35 C90,60 50,85 50,85 Z" fill="none" stroke={color} strokeWidth={strokeWidth} />
)}
</svg>
);
};
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({
</div>
);
case 'image':
if (annotation.content && annotation.content.startsWith('data:image')) {
return (
@@ -101,6 +184,44 @@ export function AnnotationOverlay({
</div>
);
case 'figure':
if (!annotation.figureData) {
return (
<div className="w-full h-full flex items-center justify-center text-slate-400 text-sm">
No figure data
</div>
);
}
const figureType = annotation.figureData.figureType;
if (figureType === 'arrow') {
return (
<div className="w-full h-full flex items-center justify-center p-2">
{renderArrow()}
</div>
);
}
if (figureType === 'shape') {
return (
<div className="w-full h-full flex items-center justify-center p-2">
{renderShape()}
</div>
);
}
if (figureType === 'emoji') {
const emojiSize = annotation.figureData.emojiSize || 64;
return (
<div className="w-full h-full flex items-center justify-center" style={{ fontSize: `${emojiSize}px` }}>
{annotation.figureData.emoji || '😊'}
</div>
);
}
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"
)}
>
@@ -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<AnnotationRegion['style']>) => void;
onFigureDataChange?: (figureData: FigureData) => void;
onDelete: () => void;
}
@@ -36,11 +50,16 @@ export function AnnotationSettingsPanel({
onContentChange,
onTypeChange,
onStyleChange,
onFigureDataChange,
onDelete,
}: AnnotationSettingsPanelProps) {
const fileInputRef = useRef<HTMLInputElement>(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 */}
<Tabs value={annotation.type} onValueChange={(value) => onTypeChange(value as AnnotationType)} className="mb-6">
<TabsList className="mb-4 bg-white/5 border border-white/5 p-1 w-full grid grid-cols-2 h-auto rounded-xl">
<TabsList className="mb-4 bg-white/5 border border-white/5 p-1 w-full grid grid-cols-3 h-auto rounded-xl">
<TabsTrigger value="text" className="data-[state=active]:bg-[#34B27B] data-[state=active]:text-white text-slate-400 py-2 rounded-lg transition-all gap-2">
<Type className="w-4 h-4" />
Text
@@ -101,6 +120,10 @@ export function AnnotationSettingsPanel({
<ImageIcon className="w-4 h-4" />
Image
</TabsTrigger>
<TabsTrigger value="figure" className="data-[state=active]:bg-[#34B27B] data-[state=active]:text-white text-slate-400 py-2 rounded-lg transition-all gap-2">
<Shapes className="w-4 h-4" />
Figure
</TabsTrigger>
</TabsList>
{/* Text Content */}
@@ -108,7 +131,7 @@ export function AnnotationSettingsPanel({
<div>
<label className="text-xs font-medium text-slate-200 mb-2 block">Text Content</label>
<textarea
value={annotation.content}
value={annotation.textContent || annotation.content}
onChange={(e) => onContentChange(e.target.value)}
placeholder="Enter your text..."
rows={5}
@@ -339,9 +362,246 @@ export function AnnotationSettingsPanel({
Supported formats: JPG, PNG, GIF, WebP
</p>
</TabsContent>
<TabsContent value="figure" className="mt-0 space-y-4">
<div>
<label className="text-xs font-medium text-slate-200 mb-2 block">Figure Type</label>
<Tabs
value={annotation.figureData?.figureType || 'arrow'}
onValueChange={(value) => {
const newFigureData: FigureData = {
...annotation.figureData!,
figureType: value as FigureType,
};
onFigureDataChange?.(newFigureData);
}}
className="w-full"
>
<TabsList className="w-full grid grid-cols-3 bg-white/5 border border-white/5 p-1 h-auto rounded-lg">
<TabsTrigger value="arrow" className="data-[state=active]:bg-[#34B27B] data-[state=active]:text-white text-slate-400 py-2 rounded-md transition-all gap-1.5 text-xs">
<FaArrowRight className="w-3 h-3" />
Arrow
</TabsTrigger>
<TabsTrigger value="shape" className="data-[state=active]:bg-[#34B27B] data-[state=active]:text-white text-slate-400 py-2 rounded-md transition-all gap-1.5 text-xs">
<FaCircle className="w-3 h-3" />
Shape
</TabsTrigger>
<TabsTrigger value="emoji" className="data-[state=active]:bg-[#34B27B] data-[state=active]:text-white text-slate-400 py-2 rounded-md transition-all gap-1.5 text-xs">
<Smile className="w-3 h-3" />
Emoji
</TabsTrigger>
</TabsList>
<TabsContent value="arrow" className="mt-4 space-y-4">
<div>
<label className="text-xs font-medium text-slate-200 mb-3 block">Arrows</label>
<div className="grid grid-cols-4 gap-2">
{[
{ value: 'up' as ArrowDirection, icon: FaArrowUp },
{ value: 'down' as ArrowDirection, icon: FaArrowDown },
{ value: 'left' as ArrowDirection, icon: FaArrowLeft },
{ value: 'right' as ArrowDirection, icon: FaArrowRight },
{ value: 'up-right' as ArrowDirection, icon: BsArrowUpRight },
{ value: 'up-left' as ArrowDirection, icon: BsArrowUpLeft },
{ value: 'down-right' as ArrowDirection, icon: BsArrowDownRight },
{ value: 'down-left' as ArrowDirection, icon: BsArrowDownLeft },
].map(({ value, icon: Icon }) => (
<button
key={value}
onClick={() => {
const newFigureData: FigureData = {
...annotation.figureData!,
arrowDirection: value,
};
onFigureDataChange?.(newFigureData);
}}
className={cn(
"h-16 rounded-lg border flex flex-col items-center justify-center gap-1 transition-all",
annotation.figureData?.arrowDirection === value
? "bg-[#34B27B] border-[#34B27B] text-white"
: "bg-white/5 border-white/10 text-slate-400 hover:bg-white/10 hover:border-white/20"
)}
>
<Icon className="w-5 h-5" />
</button>
))}
</div>
</div>
</TabsContent>
<TabsContent value="shape" className="mt-4 space-y-4">
<div>
<label className="text-xs font-medium text-slate-200 mb-3 block">Shape Type</label>
<div className="grid grid-cols-3 gap-2">
{[
{ value: 'circle' as ShapeType, icon: FaCircle, label: 'Circle' },
{ value: 'square' as ShapeType, icon: FaSquare, label: 'Square' },
{ value: 'rectangle' as ShapeType, icon: BiRectangle, label: 'Rectangle' },
{ value: 'triangle' as ShapeType, icon: FaPlay, label: 'Triangle' },
{ value: 'star' as ShapeType, icon: FaStar, label: 'Star' },
{ value: 'heart' as ShapeType, icon: FaHeart, label: 'Heart' },
].map(({ value, icon: Icon, label }) => (
<button
key={value}
onClick={() => {
const newFigureData: FigureData = {
...annotation.figureData!,
shapeType: value,
};
onFigureDataChange?.(newFigureData);
}}
className={cn(
"h-16 rounded-lg border flex flex-col items-center justify-center gap-1 transition-all",
annotation.figureData?.shapeType === value
? "bg-[#34B27B] border-[#34B27B] text-white"
: "bg-white/5 border-white/10 text-slate-400 hover:bg-white/10 hover:border-white/20"
)}
>
<Icon className="w-5 h-5" />
<span className="text-[10px]">{label}</span>
</button>
))}
</div>
</div>
<div className="flex items-center justify-between p-3 bg-white/5 rounded-lg border border-white/5">
<label className="text-xs font-medium text-slate-200">Filled</label>
<Switch
checked={annotation.figureData?.filled ?? true}
onCheckedChange={(checked) => {
const newFigureData: FigureData = {
...annotation.figureData!,
filled: checked,
};
onFigureDataChange?.(newFigureData);
}}
/>
</div>
{!annotation.figureData?.filled && (
<div>
<label className="text-xs font-medium text-slate-200 mb-2 block">
Stroke Width: {annotation.figureData?.strokeWidth || 4}px
</label>
<Slider
value={[annotation.figureData?.strokeWidth || 4]}
onValueChange={([value]) => {
const newFigureData: FigureData = {
...annotation.figureData!,
strokeWidth: value,
};
onFigureDataChange?.(newFigureData);
}}
min={1}
max={20}
step={1}
className="w-full"
/>
</div>
)}
</TabsContent>
<TabsContent value="emoji" className="mt-4 space-y-4">
<div>
<label className="text-xs font-medium text-slate-200 mb-2 block">Selected Emoji</label>
<div className="flex items-center gap-3">
<div className="flex-1 h-16 bg-white/5 border border-white/10 rounded-lg flex items-center justify-center text-4xl">
{annotation.figureData?.emoji || '😊'}
</div>
<Button
onClick={() => setShowEmojiPicker(!showEmojiPicker)}
variant="outline"
className="h-16 px-6 bg-[#34B27B] text-white border-[#34B27B] hover:bg-[#2a9163] hover:border-[#2a9163]"
>
{showEmojiPicker ? 'Close' : 'Pick'}
</Button>
</div>
</div>
<div>
<label className="text-xs font-medium text-slate-200 mb-2 block">
Emoji Size: {annotation.figureData?.emojiSize || 64}px
</label>
<Slider
value={[annotation.figureData?.emojiSize || 64]}
onValueChange={([value]) => {
const newFigureData: FigureData = {
...annotation.figureData!,
emojiSize: value,
};
onFigureDataChange?.(newFigureData);
}}
min={16}
max={200}
step={4}
className="w-full"
/>
</div>
{showEmojiPicker && (
<div className="border border-white/10 rounded-lg overflow-hidden">
<EmojiPicker
onEmojiClick={(emojiData: EmojiClickData) => {
const newFigureData: FigureData = {
...annotation.figureData!,
emoji: emojiData.emoji,
};
onFigureDataChange?.(newFigureData);
setShowEmojiPicker(false);
}}
width="100%"
height={300}
searchPlaceHolder="Search emoji..."
previewConfig={{ showPreview: false }}
/>
</div>
)}
</TabsContent>
</Tabs>
</div>
{annotation.figureData?.figureType !== 'emoji' && (
<div>
<label className="text-xs font-medium text-slate-200 mb-2 block">Color</label>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
className="w-full h-10 justify-start gap-2 bg-white/5 border-white/10 hover:bg-white/10"
>
<div
className="w-5 h-5 rounded-full border border-white/20"
style={{ backgroundColor: annotation.figureData?.color || '#34B27B' }}
/>
<span className="text-xs text-slate-300 truncate flex-1 text-left">
{annotation.figureData?.color || '#34B27B'}
</span>
<ChevronDown className="h-3 w-3 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0 border-none bg-transparent shadow-xl">
<div className="p-2 bg-[#1a1a1c] border border-white/10 rounded-xl">
<Colorful
color={figureColorHsva}
disableAlpha={true}
onChange={(color) => {
setFigureColorHsva(color.hsva);
const newFigureData: FigureData = {
...annotation.figureData!,
color: hsvaToHex(color.hsva),
};
onFigureDataChange?.(newFigureData);
}}
style={{ width: '100%', borderRadius: '8px' }}
/>
</div>
</PopoverContent>
</Popover>
</div>
)}
</TabsContent>
</Tabs>
{/* Delete Button */}
<Button
onClick={onDelete}
variant="destructive"
@@ -11,6 +11,10 @@ export function KeyboardShortcutsHelp() {
<span className="text-slate-400">Add Zoom</span>
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">Z</kbd>
</div>
<div className="flex items-center justify-between">
<span className="text-slate-400">Add Annotation</span>
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">A</kbd>
</div>
<div className="flex items-center justify-between">
<span className="text-slate-400">Add Keyframe</span>
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">F</kbd>
@@ -73,6 +73,7 @@ interface SettingsPanelProps {
onAnnotationContentChange?: (id: string, content: string) => void;
onAnnotationTypeChange?: (id: string, type: AnnotationType) => void;
onAnnotationStyleChange?: (id: string, style: Partial<AnnotationRegion['style']>) => void;
onAnnotationFigureDataChange?: (id: string, figureData: any) => void;
onAnnotationDelete?: (id: string) => void;
}
@@ -114,6 +115,7 @@ export function SettingsPanel({
onAnnotationContentChange,
onAnnotationTypeChange,
onAnnotationStyleChange,
onAnnotationFigureDataChange,
onAnnotationDelete,
}: SettingsPanelProps) {
const [wallpaperPaths, setWallpaperPaths] = useState<string[]>([]);
@@ -204,6 +206,7 @@ export function SettingsPanel({
onContentChange={(content) => onAnnotationContentChange(selectedAnnotation.id, content)}
onTypeChange={(type) => onAnnotationTypeChange(selectedAnnotation.id, type)}
onStyleChange={(style) => onAnnotationStyleChange(selectedAnnotation.id, style)}
onFigureDataChange={onAnnotationFigureDataChange ? (figureData) => onAnnotationFigureDataChange(selectedAnnotation.id, figureData) : undefined}
onDelete={() => onAnnotationDelete(selectedAnnotation.id)}
/>
);
+45 -14
View File
@@ -19,12 +19,14 @@ import {
DEFAULT_ANNOTATION_POSITION,
DEFAULT_ANNOTATION_SIZE,
DEFAULT_ANNOTATION_STYLE,
DEFAULT_FIGURE_DATA,
type ZoomDepth,
type ZoomFocus,
type ZoomRegion,
type TrimRegion,
type AnnotationRegion,
type CropRegion,
type FigureData,
} from "./types";
import { VideoExporter, type ExportProgress } from "@/lib/exporter";
import { type AspectRatio, getAspectRatioValue } from "@/utils/aspectRatioUtils";
@@ -291,11 +293,18 @@ export default function VideoEditor() {
const handleAnnotationContentChange = useCallback((id: string, content: string) => {
console.log('[VideoEditor] Annotation content changed:', { id, content });
setAnnotationRegions((prev) => {
const updated = prev.map((region) =>
region.id === id
? { ...region, content }
: region,
);
const updated = prev.map((region) => {
if (region.id !== id) return region;
// Store content in type-specific fields
if (region.type === 'text') {
return { ...region, content, textContent: content };
} else if (region.type === 'image') {
return { ...region, content, imageContent: content };
} else {
return { ...region, content };
}
});
console.log('[VideoEditor] Updated annotation regions:', updated);
return updated;
});
@@ -304,15 +313,25 @@ export default function VideoEditor() {
const handleAnnotationTypeChange = useCallback((id: string, type: AnnotationRegion['type']) => {
console.log('[VideoEditor] Annotation type changed:', { id, type });
setAnnotationRegions((prev) => {
const updated = prev.map((region) =>
region.id === id
? {
...region,
type,
content: type === 'text' ? 'Enter text...' : ''
}
: region,
);
const updated = prev.map((region) => {
if (region.id !== id) return region;
const updatedRegion = { ...region, type };
// Restore content from type-specific storage
if (type === 'text') {
updatedRegion.content = region.textContent || 'Enter text...';
} else if (type === 'image') {
updatedRegion.content = region.imageContent || '';
} else if (type === 'figure') {
updatedRegion.content = '';
if (!region.figureData) {
updatedRegion.figureData = { ...DEFAULT_FIGURE_DATA };
}
}
return updatedRegion;
});
console.log('[VideoEditor] Updated annotation regions after type change:', updated);
return updated;
});
@@ -329,6 +348,17 @@ export default function VideoEditor() {
);
}, []);
const handleAnnotationFigureDataChange = useCallback((id: string, figureData: FigureData) => {
console.log('Annotation figure data changed:', { id, figureData });
setAnnotationRegions((prev) =>
prev.map((region) =>
region.id === id
? { ...region, figureData }
: region,
),
);
}, []);
const handleAnnotationPositionChange = useCallback((id: string, position: { x: number; y: number }) => {
setAnnotationRegions((prev) =>
prev.map((region) =>
@@ -686,6 +716,7 @@ export default function VideoEditor() {
onAnnotationContentChange={handleAnnotationContentChange}
onAnnotationTypeChange={handleAnnotationTypeChange}
onAnnotationStyleChange={handleAnnotationStyleChange}
onAnnotationFigureDataChange={handleAnnotationFigureDataChange}
onAnnotationDelete={handleAnnotationDelete}
/>
</div>
+27 -2
View File
@@ -19,9 +19,22 @@ export interface TrimRegion {
endMs: number;
}
export type AnnotationType = 'text' | 'image';
export type AnnotationType = 'text' | 'image' | 'figure';
export type FigureType = 'arrow' | 'shape' | 'emoji';
export type ArrowDirection = 'up' | 'down' | 'left' | 'right' | 'up-right' | 'up-left' | 'down-right' | 'down-left';
export type ShapeType = 'circle' | 'square' | 'rectangle' | 'triangle' | 'star' | 'heart';
export interface FigureData {
figureType: FigureType;
arrowDirection?: ArrowDirection;
shapeType?: ShapeType;
emoji?: string;
emojiSize?: number;
color: string;
strokeWidth: number;
filled: boolean;
}
export interface AnnotationPosition {
x: number;
@@ -49,11 +62,14 @@ export interface AnnotationRegion {
startMs: number;
endMs: number;
type: AnnotationType;
content: string;
content: string; // Legacy - still used for current type
textContent?: string; // Separate storage for text
imageContent?: string; // Separate storage for image data URL
position: AnnotationPosition;
size: AnnotationSize;
style: AnnotationTextStyle;
zIndex: number;
figureData?: FigureData;
}
export const DEFAULT_ANNOTATION_POSITION: AnnotationPosition = {
@@ -77,6 +93,15 @@ export const DEFAULT_ANNOTATION_STYLE: AnnotationTextStyle = {
textAlign: 'center',
};
export const DEFAULT_FIGURE_DATA: FigureData = {
figureType: 'arrow',
arrowDirection: 'right',
color: '#34B27B',
strokeWidth: 4,
filled: true,
emojiSize: 64,
};
export interface CropRegion {