Refactor and select and move button

This commit is contained in:
Reece 2025-12-17 14:30:14 +00:00
parent aa78a41026
commit e9a795eed2
5 changed files with 1738 additions and 1375 deletions

View File

@ -4097,10 +4097,7 @@ stampSettings = "Stamp Settings"
savingCopy = "Preparing download..."
saveFailed = "Unable to save copy"
saveReady = "Download ready"
resumeTooltip = "Resume placement"
resume = "Resume placement"
pauseTooltip = "Pause placement"
pause = "Pause placement"
selectAndMove = "Select and Move"
undo = "Undo"
redo = "Redo"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,154 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import type { AnnotationAPI, AnnotationToolId } from '@app/components/viewer/viewerTypes';
interface UseAnnotationSelectionParams {
annotationApiRef: React.RefObject<AnnotationAPI | null>;
deriveToolFromAnnotation: (annotation: any) => AnnotationToolId | undefined;
activeToolRef: React.MutableRefObject<AnnotationToolId>;
manualToolSwitch: React.MutableRefObject<boolean>;
setActiveTool: (toolId: AnnotationToolId) => void;
setSelectedTextDraft: (text: string) => void;
setSelectedFontSize: (size: number) => void;
setInkWidth: (value: number) => void;
setShapeThickness: (value: number) => void;
setTextColor: (value: string) => void;
setTextBackgroundColor: (value: string) => void;
setNoteBackgroundColor: (value: string) => void;
}
export function useAnnotationSelection({
annotationApiRef,
deriveToolFromAnnotation,
activeToolRef,
manualToolSwitch,
setActiveTool,
setSelectedTextDraft,
setSelectedFontSize,
setInkWidth,
setShapeThickness,
setTextColor,
setTextBackgroundColor,
setNoteBackgroundColor,
}: UseAnnotationSelectionParams) {
const [selectedAnn, setSelectedAnn] = useState<any | null>(null);
const [selectedAnnId, setSelectedAnnId] = useState<string | null>(null);
const selectedAnnIdRef = useRef<string | null>(null);
const applySelectionFromAnnotation = useCallback(
(ann: any | null) => {
const annObject = ann?.object ?? ann ?? null;
const annId = annObject?.id ?? null;
const type = annObject?.type;
selectedAnnIdRef.current = annId;
setSelectedAnnId(annId);
setSelectedAnn(ann || null);
if (annObject?.contents !== undefined) {
setSelectedTextDraft(annObject.contents ?? '');
}
if (annObject?.fontSize !== undefined) {
setSelectedFontSize(annObject.fontSize ?? 14);
}
if (type === 3) {
const derivedTool = deriveToolFromAnnotation(annObject);
const background = annObject?.backgroundColor as string | undefined;
if (annObject?.textColor) {
setTextColor(annObject.textColor);
}
if (derivedTool === 'note') {
if (background) {
setNoteBackgroundColor(background);
}
} else {
setTextBackgroundColor(background || '');
}
}
if (type === 15 && annObject?.strokeWidth !== undefined) {
setInkWidth(annObject.strokeWidth ?? 2);
} else if (type >= 4 && type <= 8 && annObject?.strokeWidth !== undefined) {
setShapeThickness(annObject.strokeWidth ?? 1);
}
const matchingTool = deriveToolFromAnnotation(annObject);
if (matchingTool && matchingTool !== activeToolRef.current && !manualToolSwitch.current) {
setActiveTool(matchingTool);
}
},
[
activeToolRef,
deriveToolFromAnnotation,
manualToolSwitch,
setActiveTool,
setInkWidth,
setNoteBackgroundColor,
setSelectedFontSize,
setSelectedTextDraft,
setShapeThickness,
setTextBackgroundColor,
setTextColor,
]
);
useEffect(() => {
const api = annotationApiRef.current as any;
if (!api) return;
if (typeof api.onAnnotationEvent === 'function') {
const handler = (event: any) => {
const ann = event?.annotation ?? event?.selectedAnnotation ?? null;
switch (event?.type) {
case 'select':
case 'selected':
applySelectionFromAnnotation(ann ?? api.getSelectedAnnotation?.());
break;
case 'deselect':
case 'clearSelection':
applySelectionFromAnnotation(null);
break;
case 'delete':
case 'remove':
if (ann?.id && ann.id === selectedAnnIdRef.current) {
applySelectionFromAnnotation(null);
}
break;
case 'update':
case 'change':
if (selectedAnnIdRef.current) {
const current = api.getSelectedAnnotation?.();
if (current) {
applySelectionFromAnnotation(current);
}
}
break;
default:
break;
}
};
const unsubscribe = api.onAnnotationEvent(handler);
return () => {
if (typeof unsubscribe === 'function') {
unsubscribe();
}
};
}
const interval = setInterval(() => {
const ann = api.getSelectedAnnotation?.();
if ((ann?.object?.id ?? null) !== selectedAnnIdRef.current) {
applySelectionFromAnnotation(ann ?? null);
}
}, 350);
return () => clearInterval(interval);
}, [annotationApiRef, applySelectionFromAnnotation]);
return {
selectedAnn,
selectedAnnId,
selectedAnnIdRef,
setSelectedAnn,
setSelectedAnnId,
applySelectionFromAnnotation,
};
}

View File

@ -0,0 +1,324 @@
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<string, unknown>;
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<string>('');
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(1);
const buildToolOptions = useCallback<BuildToolOptionsFn>(
(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,
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,
};
};