Rescue Paperjet v1 implementation

This commit is contained in:
Elijah 2026-08-14 21:05:22 -07:00
parent 7ff5c8130d
commit db9e2ca51b
83 changed files with 6186 additions and 3894 deletions

View file

@ -1,4 +1,6 @@
import { useState, useRef, useEffect } from 'react';
import * as fabric from 'fabric';
import type { Canvas } from 'fabric';
import { useEditorStore } from '../store';
import type { TextAnnotation, TextProps } from '../../../lib/annotations/types';
import type { ViewportParams } from '../../../lib/coords';
@ -17,6 +19,7 @@ import {
interface TextFormatToolbarProps {
annotationId: string;
viewportParams: ViewportParams;
canvas: Canvas;
}
const FONTS = ['Liberation Sans', 'Outfit', 'Plus Jakarta Sans', 'Arial', 'Times New Roman', 'Courier New'];
@ -24,7 +27,7 @@ const SIZES = [6, 7, 8, 10, 12, 14, 16, 18, 24, 36, 48, 72];
const COLORS = ['#000000', '#EF4444', '#3B82F6', '#10B981', '#F59E0B', '#ffffff'];
const HIGHLIGHTS = ['transparent', '#FEF08A', '#BBF7D0', '#BFDBFE', '#FBCFE8', '#000000'];
export function TextFormatToolbar({ annotationId, viewportParams }: TextFormatToolbarProps) {
export function TextFormatToolbar({ annotationId, viewportParams, canvas }: TextFormatToolbarProps) {
const { annotations, updateAnnotation, deleteAnnotation, addAnnotation, setDefaultTextProps, draftAnnotation } = useEditorStore();
const [activeDropdown, setActiveDropdown] = useState<'font' | 'size' | 'color' | 'highlight' | null>(null);
@ -51,11 +54,9 @@ export function TextFormatToolbar({ annotationId, viewportParams }: TextFormatTo
const top = pt.y - 48; // 48px above
const left = pt.x;
const applyStyle = (styleName: string, value: any, globalPropName: keyof TextProps, globalValue?: any) => {
const canvas = (window as any).__fabricCanvas as any;
if (canvas) {
const activeObj = canvas.getActiveObject();
const applyStyle = (styleName: string, value: unknown, globalPropName: keyof TextProps, globalValue?: unknown) => {
const activeObj = canvas.getActiveObject() as (fabric.Textbox & { id?: string; customHeight?: number }) | undefined;
if (activeObj) {
if (activeObj && activeObj.id === annotationId) {
const isStructural = styleName === 'fontSize' || styleName === 'fontFamily';
@ -81,7 +82,7 @@ export function TextFormatToolbar({ annotationId, viewportParams }: TextFormatTo
activeObj.styles = {};
if (activeObj.hiddenTextarea) {
if (styleName === 'fontSize') activeObj.hiddenTextarea.style.fontSize = `${value}px`;
if (styleName === 'fontFamily') activeObj.hiddenTextarea.style.fontFamily = value;
if (styleName === 'fontFamily') activeObj.hiddenTextarea.style.fontFamily = String(value);
}
}
} else {
@ -100,7 +101,7 @@ export function TextFormatToolbar({ annotationId, viewportParams }: TextFormatTo
for (const line in activeObj.styles) {
for (const char in activeObj.styles[line]) {
if (activeObj.styles[line][char]) {
delete activeObj.styles[line][char][styleName];
delete (activeObj.styles[line][char] as Record<string, unknown>)[styleName];
}
}
}
@ -110,8 +111,8 @@ export function TextFormatToolbar({ annotationId, viewportParams }: TextFormatTo
// Critical: Fabric 7 heavily caches text. We MUST mark it dirty to force a redraw!
activeObj.dirty = true;
if ((activeObj as any)._forceClearCache !== undefined) {
(activeObj as any)._forceClearCache = true;
if ('_forceClearCache' in activeObj) {
(activeObj as typeof activeObj & { _forceClearCache?: boolean })._forceClearCache = true;
}
// Remove manual height constraint so the box can grow with the new font size
@ -123,7 +124,7 @@ export function TextFormatToolbar({ annotationId, viewportParams }: TextFormatTo
}
const finalGlobalValue = globalValue !== undefined ? globalValue : value;
const newProps = { [globalPropName]: finalGlobalValue } as any;
const newProps = { [globalPropName]: finalGlobalValue } as Partial<TextProps>;
setDefaultTextProps(newProps);
// Always update store so the toolbar displays the new value
@ -154,12 +155,9 @@ export function TextFormatToolbar({ annotationId, viewportParams }: TextFormatTo
const handleDelete = () => {
if (isDraft) {
useEditorStore.getState().setDraftAnnotation(null);
const canvas = (window as any).__fabricCanvas as any;
if (canvas) {
const activeObj = canvas.getActiveObject();
if (activeObj && activeObj.id === annotationId) {
canvas.remove(activeObj);
}
const activeObj = canvas.getActiveObject();
if (activeObj && (activeObj as fabric.FabricObject & { id?: string }).id === annotationId) {
canvas.remove(activeObj);
}
} else {
deleteAnnotation(annotationId);
@ -220,7 +218,7 @@ export function TextFormatToolbar({ annotationId, viewportParams }: TextFormatTo
<button
key={s}
className={`w-full text-center px-3 py-1 text-xs hover:bg-blue-50 ${props.fontSize === s ? 'bg-blue-50 text-blue-600 font-medium' : ''}`}
onClick={() => { applyStyle('fontSize', s * viewportParams.scale * (viewportParams.dpr || 1), 'fontSize', s); setActiveDropdown(null); }}
onClick={() => { applyStyle('fontSize', s * viewportParams.scale, 'fontSize', s); setActiveDropdown(null); }}
>
{s}
</button>