import { useCallback, useMemo, useState } from 'react'; import type { AnnotationToolId } from '@app/components/viewer/viewerTypes'; type Size = { width: number; height: number }; export type BuildToolOptionsExtras = { includeMetadata?: boolean; stampImageData?: string; stampImageSize?: Size | null; }; interface StyleState { inkColor: string; inkWidth: number; highlightColor: string; highlightOpacity: number; freehandHighlighterWidth: number; underlineColor: string; underlineOpacity: number; strikeoutColor: string; strikeoutOpacity: number; squigglyColor: string; squigglyOpacity: number; textColor: string; textSize: number; textAlignment: 'left' | 'center' | 'right'; textBackgroundColor: string; noteBackgroundColor: string; shapeStrokeColor: string; shapeFillColor: string; shapeOpacity: number; shapeStrokeOpacity: number; shapeFillOpacity: number; shapeThickness: number; } interface StyleActions { setInkColor: (value: string) => void; setInkWidth: (value: number) => void; setHighlightColor: (value: string) => void; setHighlightOpacity: (value: number) => void; setFreehandHighlighterWidth: (value: number) => void; setUnderlineColor: (value: string) => void; setUnderlineOpacity: (value: number) => void; setStrikeoutColor: (value: string) => void; setStrikeoutOpacity: (value: number) => void; setSquigglyColor: (value: string) => void; setSquigglyOpacity: (value: number) => void; setTextColor: (value: string) => void; setTextSize: (value: number) => void; setTextAlignment: (value: 'left' | 'center' | 'right') => void; setTextBackgroundColor: (value: string) => void; setNoteBackgroundColor: (value: string) => void; setShapeStrokeColor: (value: string) => void; setShapeFillColor: (value: string) => void; setShapeOpacity: (value: number) => void; setShapeStrokeOpacity: (value: number) => void; setShapeFillOpacity: (value: number) => void; setShapeThickness: (value: number) => void; } export type BuildToolOptionsFn = ( toolId: AnnotationToolId, extras?: BuildToolOptionsExtras ) => Record; export interface AnnotationStyleStateReturn { styleState: StyleState; styleActions: StyleActions; buildToolOptions: BuildToolOptionsFn; getActiveColor: (target: string | null) => string; } export const useAnnotationStyleState = ( cssToPdfSize?: (size: Size) => Size ): AnnotationStyleStateReturn => { const [inkColor, setInkColor] = useState('#1f2933'); const [inkWidth, setInkWidth] = useState(2); const [highlightColor, setHighlightColor] = useState('#ffd54f'); const [highlightOpacity, setHighlightOpacity] = useState(60); const [freehandHighlighterWidth, setFreehandHighlighterWidth] = useState(6); const [underlineColor, setUnderlineColor] = useState('#ffb300'); const [underlineOpacity, setUnderlineOpacity] = useState(100); const [strikeoutColor, setStrikeoutColor] = useState('#e53935'); const [strikeoutOpacity, setStrikeoutOpacity] = useState(100); const [squigglyColor, setSquigglyColor] = useState('#00acc1'); const [squigglyOpacity, setSquigglyOpacity] = useState(100); const [textColor, setTextColor] = useState('#111111'); const [textSize, setTextSize] = useState(14); const [textAlignment, setTextAlignment] = useState<'left' | 'center' | 'right'>('left'); const [textBackgroundColor, setTextBackgroundColor] = useState(''); const [noteBackgroundColor, setNoteBackgroundColor] = useState('#ffd54f'); const [shapeStrokeColor, setShapeStrokeColor] = useState('#cf5b5b'); const [shapeFillColor, setShapeFillColor] = useState('#0000ff'); const [shapeOpacity, setShapeOpacity] = useState(50); const [shapeStrokeOpacity, setShapeStrokeOpacity] = useState(50); const [shapeFillOpacity, setShapeFillOpacity] = useState(50); const [shapeThickness, setShapeThickness] = useState(2); const buildToolOptions = useCallback( (toolId, extras) => { const includeMetadata = extras?.includeMetadata ?? true; const metadata = includeMetadata ? { customData: { toolId, annotationToolId: toolId, source: 'annotate', author: 'User', createdAt: new Date().toISOString(), modifiedAt: new Date().toISOString(), }, } : {}; switch (toolId) { case 'ink': return { color: inkColor, thickness: inkWidth, ...metadata }; case 'inkHighlighter': return { color: highlightColor, opacity: highlightOpacity / 100, thickness: freehandHighlighterWidth, ...metadata, }; case 'highlight': return { color: highlightColor, opacity: highlightOpacity / 100, ...metadata }; case 'underline': return { color: underlineColor, opacity: underlineOpacity / 100, ...metadata }; case 'strikeout': return { color: strikeoutColor, opacity: strikeoutOpacity / 100, ...metadata }; case 'squiggly': return { color: squigglyColor, opacity: squigglyOpacity / 100, ...metadata }; case 'text': { const textAlignNumber = textAlignment === 'left' ? 0 : textAlignment === 'center' ? 1 : 2; return { color: textColor, textColor: textColor, fontSize: textSize, textAlign: textAlignNumber, ...(textBackgroundColor ? { fillColor: textBackgroundColor } : {}), ...metadata, }; } case 'note': { const noteFillColor = noteBackgroundColor || 'transparent'; return { color: textColor, fillColor: noteFillColor, opacity: 1, ...metadata, }; } case 'square': case 'circle': case 'polygon': return { color: shapeFillColor, strokeColor: shapeStrokeColor, opacity: shapeOpacity / 100, strokeOpacity: shapeStrokeOpacity / 100, fillOpacity: shapeFillOpacity / 100, borderWidth: shapeThickness, ...metadata, }; case 'line': case 'polyline': case 'lineArrow': return { color: shapeStrokeColor, strokeColor: shapeStrokeColor, opacity: shapeStrokeOpacity / 100, borderWidth: shapeThickness, ...metadata, }; case 'stamp': { const pdfSize = extras?.stampImageSize && cssToPdfSize ? cssToPdfSize(extras.stampImageSize) : undefined; return { imageSrc: extras?.stampImageData, ...(pdfSize ? { imageSize: pdfSize } : {}), ...metadata, }; } default: return { ...metadata }; } }, [ cssToPdfSize, freehandHighlighterWidth, highlightColor, highlightOpacity, inkColor, inkWidth, noteBackgroundColor, shapeFillColor, shapeFillOpacity, shapeOpacity, shapeStrokeColor, shapeStrokeOpacity, shapeThickness, squigglyColor, squigglyOpacity, strikeoutColor, strikeoutOpacity, textAlignment, textBackgroundColor, textColor, textSize, underlineColor, underlineOpacity, ] ); const getActiveColor = useCallback( (target: string | null) => { if (target === 'ink') return inkColor; if (target === 'highlight' || target === 'inkHighlighter') return highlightColor; if (target === 'underline') return underlineColor; if (target === 'strikeout') return strikeoutColor; if (target === 'squiggly') return squigglyColor; if (target === 'shapeStroke') return shapeStrokeColor; if (target === 'shapeFill') return shapeFillColor; if (target === 'textBackground') return textBackgroundColor || '#ffffff'; if (target === 'noteBackground') return noteBackgroundColor || '#ffffff'; return textColor; }, [ highlightColor, inkColor, noteBackgroundColor, shapeFillColor, shapeStrokeColor, squigglyColor, strikeoutColor, textBackgroundColor, textColor, underlineColor, ] ); const styleState: StyleState = useMemo( () => ({ inkColor, inkWidth, highlightColor, highlightOpacity, freehandHighlighterWidth, underlineColor, underlineOpacity, strikeoutColor, strikeoutOpacity, squigglyColor, squigglyOpacity, textColor, textSize, textAlignment, textBackgroundColor, noteBackgroundColor, shapeStrokeColor, shapeFillColor, shapeOpacity, shapeStrokeOpacity, shapeFillOpacity, shapeThickness, }), [ freehandHighlighterWidth, highlightColor, highlightOpacity, inkColor, inkWidth, noteBackgroundColor, shapeFillColor, shapeFillOpacity, shapeOpacity, shapeStrokeColor, shapeStrokeOpacity, shapeThickness, squigglyColor, squigglyOpacity, strikeoutColor, strikeoutOpacity, textAlignment, textBackgroundColor, textColor, textSize, underlineColor, underlineOpacity, ] ); const styleActions: StyleActions = { setInkColor, setInkWidth, setHighlightColor, setHighlightOpacity, setFreehandHighlighterWidth, setUnderlineColor, setUnderlineOpacity, setStrikeoutColor, setStrikeoutOpacity, setSquigglyColor, setSquigglyOpacity, setTextColor, setTextSize, setTextAlignment, setTextBackgroundColor, setNoteBackgroundColor, setShapeStrokeColor, setShapeFillColor, setShapeOpacity, setShapeStrokeOpacity, setShapeFillOpacity, setShapeThickness, }; return { styleState, styleActions, buildToolOptions, getActiveColor, }; };