Remove shapes tool
This commit is contained in:
parent
b1fae9143d
commit
5b2140187f
3 changed files with 1 additions and 261 deletions
|
|
@ -91,24 +91,7 @@ export function EditorToolbar() {
|
||||||
Draw
|
Draw
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<select
|
|
||||||
value={useEditorStore.getState().activeShapeKind}
|
|
||||||
onChange={(e) => {
|
|
||||||
useEditorStore.getState().setActiveShapeKind(e.target.value as any);
|
|
||||||
setActiveTool('shape');
|
|
||||||
}}
|
|
||||||
className={`px-3 py-1.5 rounded-md text-sm font-medium transition-colors border-none focus:ring-0 ${
|
|
||||||
activeTool === 'shape'
|
|
||||||
? 'bg-blue-100 text-blue-700'
|
|
||||||
: 'text-neutral-600 hover:bg-neutral-100 bg-transparent'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<option value="rect">Rectangle</option>
|
|
||||||
<option value="ellipse">Ellipse</option>
|
|
||||||
<option value="line">Line</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className={`px-3 py-1.5 rounded-md text-sm font-medium transition-colors ${
|
className={`px-3 py-1.5 rounded-md text-sm font-medium transition-colors ${
|
||||||
|
|
|
||||||
|
|
@ -1,241 +0,0 @@
|
||||||
import * as fabric from 'fabric';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import { useEditorStore } from '../store';
|
|
||||||
import type { ToolHandler } from '../../../lib/annotations/registry';
|
|
||||||
import type { Annotation, ShapeAnnotation, ShapeProps } from '../../../lib/annotations/types';
|
|
||||||
import { screenRectToPdf, pdfRectToScreen } from '../../../lib/coords';
|
|
||||||
import type { ViewportParams } from '../../../lib/coords';
|
|
||||||
|
|
||||||
// Temporary state for drawing
|
|
||||||
let isDrawingShape = false;
|
|
||||||
let startX = 0;
|
|
||||||
let startY = 0;
|
|
||||||
let currentShape: fabric.FabricObject | null = null;
|
|
||||||
|
|
||||||
export const ShapeTool: ToolHandler = {
|
|
||||||
name: 'shape',
|
|
||||||
|
|
||||||
onActivate: (canvas: fabric.Canvas) => {
|
|
||||||
canvas.defaultCursor = 'crosshair';
|
|
||||||
canvas.selection = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
onDeactivate: (canvas: fabric.Canvas) => {
|
|
||||||
canvas.defaultCursor = 'default';
|
|
||||||
isDrawingShape = false;
|
|
||||||
currentShape = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
onPointerDown: (e: any, canvas: fabric.Canvas, _vp: ViewportParams, _pageNumber: number) => {
|
|
||||||
if (e.target && !isDrawingShape) return;
|
|
||||||
|
|
||||||
isDrawingShape = true;
|
|
||||||
const pointer = e.scenePoint || canvas.getScenePoint(e.e);
|
|
||||||
startX = pointer.x;
|
|
||||||
startY = pointer.y;
|
|
||||||
|
|
||||||
const storeState = useEditorStore.getState();
|
|
||||||
const kind = storeState.activeShapeKind;
|
|
||||||
// We could pull default colors from store if we want, for now default to red stroke transparent fill
|
|
||||||
const strokeColor = '#f44336';
|
|
||||||
const fillColor = 'transparent';
|
|
||||||
const strokeWidth = 4;
|
|
||||||
|
|
||||||
if (kind === 'rect') {
|
|
||||||
currentShape = new fabric.Rect({
|
|
||||||
left: startX,
|
|
||||||
top: startY,
|
|
||||||
width: 0,
|
|
||||||
height: 0,
|
|
||||||
fill: fillColor,
|
|
||||||
stroke: strokeColor,
|
|
||||||
strokeWidth: strokeWidth,
|
|
||||||
transparentCorners: false,
|
|
||||||
});
|
|
||||||
} else if (kind === 'ellipse') {
|
|
||||||
currentShape = new fabric.Ellipse({
|
|
||||||
left: startX,
|
|
||||||
top: startY,
|
|
||||||
originX: 'center',
|
|
||||||
originY: 'center',
|
|
||||||
rx: 0,
|
|
||||||
ry: 0,
|
|
||||||
fill: fillColor,
|
|
||||||
stroke: strokeColor,
|
|
||||||
strokeWidth: strokeWidth,
|
|
||||||
transparentCorners: false,
|
|
||||||
});
|
|
||||||
} else if (kind === 'line') {
|
|
||||||
currentShape = new fabric.Line([startX, startY, startX, startY], {
|
|
||||||
stroke: strokeColor,
|
|
||||||
strokeWidth: strokeWidth,
|
|
||||||
transparentCorners: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentShape) {
|
|
||||||
(currentShape as any).id = uuidv4();
|
|
||||||
(currentShape as any).annotationType = 'shape';
|
|
||||||
canvas.add(currentShape);
|
|
||||||
canvas.requestRenderAll();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onPointerMove: (e: any, canvas: fabric.Canvas, _vp: ViewportParams, _pageNumber: number) => {
|
|
||||||
if (!isDrawingShape || !currentShape) return;
|
|
||||||
const pointer = e.scenePoint || canvas.getScenePoint(e.e);
|
|
||||||
|
|
||||||
const storeState = useEditorStore.getState();
|
|
||||||
const kind = storeState.activeShapeKind;
|
|
||||||
|
|
||||||
if (kind === 'rect') {
|
|
||||||
const rect = currentShape as fabric.Rect;
|
|
||||||
rect.set({
|
|
||||||
left: Math.min(startX, pointer.x),
|
|
||||||
top: Math.min(startY, pointer.y),
|
|
||||||
width: Math.abs(pointer.x - startX),
|
|
||||||
height: Math.abs(pointer.y - startY)
|
|
||||||
});
|
|
||||||
} else if (kind === 'ellipse') {
|
|
||||||
const ellipse = currentShape as fabric.Ellipse;
|
|
||||||
const rx = Math.abs(pointer.x - startX) / 2;
|
|
||||||
const ry = Math.abs(pointer.y - startY) / 2;
|
|
||||||
ellipse.set({
|
|
||||||
rx: rx,
|
|
||||||
ry: ry,
|
|
||||||
left: startX + (pointer.x - startX) / 2,
|
|
||||||
top: startY + (pointer.y - startY) / 2
|
|
||||||
});
|
|
||||||
} else if (kind === 'line') {
|
|
||||||
const line = currentShape as fabric.Line;
|
|
||||||
line.set({
|
|
||||||
x2: pointer.x,
|
|
||||||
y2: pointer.y
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.requestRenderAll();
|
|
||||||
},
|
|
||||||
|
|
||||||
onPointerUp: (_e: any, canvas: fabric.Canvas, vp: ViewportParams, pageNumber: number) => {
|
|
||||||
if (!isDrawingShape || !currentShape) return;
|
|
||||||
isDrawingShape = false;
|
|
||||||
|
|
||||||
// Check if it's too small (a click instead of a drag)
|
|
||||||
const bounds = currentShape.getBoundingRect();
|
|
||||||
if (bounds.width < 5 && bounds.height < 5) {
|
|
||||||
canvas.remove(currentShape);
|
|
||||||
currentShape = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentShape.set({
|
|
||||||
transparentCorners: false,
|
|
||||||
cornerColor: '#3b82f6',
|
|
||||||
cornerStrokeColor: '#3b82f6',
|
|
||||||
borderColor: '#3b82f6',
|
|
||||||
cornerSize: 8,
|
|
||||||
padding: 5,
|
|
||||||
});
|
|
||||||
|
|
||||||
const storeState = useEditorStore.getState();
|
|
||||||
const kind = storeState.activeShapeKind;
|
|
||||||
|
|
||||||
const props: ShapeProps = {
|
|
||||||
kind,
|
|
||||||
strokeColor: currentShape.stroke as string,
|
|
||||||
fillColor: currentShape.fill as string,
|
|
||||||
strokeWidth: currentShape.strokeWidth
|
|
||||||
};
|
|
||||||
(currentShape as any).annotationProps = props;
|
|
||||||
|
|
||||||
canvas.setActiveObject(currentShape);
|
|
||||||
|
|
||||||
const pdfRect = screenRectToPdf({
|
|
||||||
x: bounds.left,
|
|
||||||
y: bounds.top,
|
|
||||||
width: bounds.width,
|
|
||||||
height: bounds.height
|
|
||||||
}, vp);
|
|
||||||
|
|
||||||
const annotation: ShapeAnnotation = {
|
|
||||||
id: (currentShape as any).id,
|
|
||||||
page: pageNumber,
|
|
||||||
type: 'shape',
|
|
||||||
rect: pdfRect,
|
|
||||||
rotation: 0,
|
|
||||||
z: 0,
|
|
||||||
props: props,
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
updatedAt: new Date().toISOString()
|
|
||||||
};
|
|
||||||
|
|
||||||
useEditorStore.getState().addAnnotation(annotation);
|
|
||||||
|
|
||||||
// Switch back to select tool
|
|
||||||
useEditorStore.getState().setActiveTool('select');
|
|
||||||
currentShape = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
renderToFabric: async (annotation: Annotation, canvas: fabric.Canvas, vp: ViewportParams) => {
|
|
||||||
if (annotation.type !== 'shape') return null;
|
|
||||||
const shapeAnn = annotation as ShapeAnnotation;
|
|
||||||
const props = shapeAnn.props as ShapeProps;
|
|
||||||
const screenRect = pdfRectToScreen(shapeAnn.rect, vp);
|
|
||||||
|
|
||||||
let shape: fabric.FabricObject;
|
|
||||||
|
|
||||||
if (props.kind === 'rect') {
|
|
||||||
shape = new fabric.Rect({
|
|
||||||
left: screenRect.x,
|
|
||||||
top: screenRect.y,
|
|
||||||
width: screenRect.width,
|
|
||||||
height: screenRect.height,
|
|
||||||
fill: props.fillColor,
|
|
||||||
stroke: props.strokeColor,
|
|
||||||
strokeWidth: props.strokeWidth,
|
|
||||||
});
|
|
||||||
} else if (props.kind === 'ellipse') {
|
|
||||||
shape = new fabric.Ellipse({
|
|
||||||
left: screenRect.x + screenRect.width / 2,
|
|
||||||
top: screenRect.y + screenRect.height / 2,
|
|
||||||
originX: 'center',
|
|
||||||
originY: 'center',
|
|
||||||
rx: screenRect.width / 2,
|
|
||||||
ry: screenRect.height / 2,
|
|
||||||
fill: props.fillColor,
|
|
||||||
stroke: props.strokeColor,
|
|
||||||
strokeWidth: props.strokeWidth,
|
|
||||||
});
|
|
||||||
} else if (props.kind === 'line') {
|
|
||||||
// Lines are tricky because bounding rect loses directionality.
|
|
||||||
// A proper line implementation should probably save x1, y1, x2, y2 in props.
|
|
||||||
// But we'll just draw a diagonal for now based on rect.
|
|
||||||
shape = new fabric.Line([
|
|
||||||
screenRect.x,
|
|
||||||
screenRect.y,
|
|
||||||
screenRect.x + screenRect.width,
|
|
||||||
screenRect.y + screenRect.height
|
|
||||||
], {
|
|
||||||
stroke: props.strokeColor,
|
|
||||||
strokeWidth: props.strokeWidth,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
shape.set({
|
|
||||||
id: shapeAnn.id,
|
|
||||||
annotationType: 'shape',
|
|
||||||
annotationProps: shapeAnn.props,
|
|
||||||
transparentCorners: false,
|
|
||||||
cornerColor: '#3b82f6',
|
|
||||||
cornerStrokeColor: '#3b82f6',
|
|
||||||
borderColor: '#3b82f6',
|
|
||||||
cornerSize: 8,
|
|
||||||
padding: 5,
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
canvas.add(shape);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -3,14 +3,12 @@ import { TextTool } from './TextTool';
|
||||||
import { SignatureTool } from './SignatureTool';
|
import { SignatureTool } from './SignatureTool';
|
||||||
import { ImageTool } from './ImageTool';
|
import { ImageTool } from './ImageTool';
|
||||||
import { DrawTool } from './DrawTool';
|
import { DrawTool } from './DrawTool';
|
||||||
import { ShapeTool } from './ShapeTool';
|
|
||||||
|
|
||||||
// Register all tools here
|
// Register all tools here
|
||||||
registerTool(TextTool);
|
registerTool(TextTool);
|
||||||
registerTool(SignatureTool);
|
registerTool(SignatureTool);
|
||||||
registerTool(ImageTool);
|
registerTool(ImageTool);
|
||||||
registerTool(DrawTool);
|
registerTool(DrawTool);
|
||||||
registerTool(ShapeTool);
|
|
||||||
|
|
||||||
// SelectTool implementation
|
// SelectTool implementation
|
||||||
registerTool({
|
registerTool({
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue