@siali/sorolla
v1.3.0
Published
Sorolla components library for Siali
Readme
Sorolla - React Annotation Library
Sorolla is a powerful React component library for image annotation, built with Konva.js. It provides efficient canvas-based annotation tools with support for rectangles, polygons, lines, and points, optimized for both vanilla React and Next.js projects.
Features
- 🎯 Multiple Annotation Types: Rectangle, Polygon, Line, and Point annotations
- 🎨 Customizable Styling: Global and per-annotation styling options
- ⌨️ Keyboard Shortcuts: Intuitive keyboard navigation and tool switching
- 🔄 Real-time Updates: Live annotation updates with callback support
- 📱 Responsive Design: Adapts to container size changes
- 🎛️ Tool Management: Built-in tools for editing, moving, and creating annotations
- 📦 TypeScript Support: Full type safety and IntelliSense support
- 🚀 Performance Optimized: Efficient rendering with Konva.js
Installation
Install the required peer dependencies:
npm install react react-dom konva @siali/sorolla
# or
yarn add react react-dom konva @siali/sorolla
# or
pnpm add react react-dom konva @siali/sorollaQuick Start
Basic Usage in React (with Provider)
import React, { useState } from 'react';
import { SorollaAnnotator, Rectangle, AnnotationsProvider } from '@siali/sorolla';
const initialAnnotations: Rectangle[] = [
{
id: "1",
position: { x: 100, y: 100 },
width: 200,
height: 150,
type: "rectangle",
},
];
export default function MyAnnotator() {
const [annotations, setAnnotations] = useState<Rectangle[]>(initialAnnotations);
return (
<div style={{ width: '800px', height: '600px' }}>
<AnnotationsProvider>
<SorollaAnnotator
image="/path/to/your/image.jpg"
initialAnnotations={annotations}
onUpdate={setAnnotations}
/>
</AnnotationsProvider>
</div>
);
}Next.js Usage (with Provider)
For Next.js projects, use dynamic imports to avoid SSR issues:
"use client";
import dynamic from "next/dynamic";
import { Suspense } from "react";
const SorollaAnnotator = dynamic(
() => import("@siali/sorolla").then((mod) => mod.SorollaAnnotator),
{
ssr: false,
loading: () => <div>Loading annotator...</div>,
}
);
const AnnotationsProvider = dynamic(
() => import("@siali/sorolla").then((mod) => mod.AnnotationsProvider),
{
ssr: false,
loading: () => <div>Loading provider...</div>,
}
);
export default function NextJSAnnotator() {
return (
<div style={{ width: '800px', height: '600px' }}>
<Suspense fallback={<div>Loading...</div>}>
<AnnotationsProvider>
<SorollaAnnotator
image="/path/to/your/image.jpg"
initialAnnotations={[]}
onUpdate={(annotations) => console.log('Updated:', annotations)}
/>
</AnnotationsProvider>
</Suspense>
</div>
);
}API Reference
SorollaAnnotator Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| image | string | - | Required. Path or URL to the image to annotate |
| initialAnnotations | Annotation[] | [] | Initial annotations to display |
| onUpdate | (annotations: Annotation[]) => void | - | Callback fired when annotations change |
| backgroundColor | string | - | Background color for the canvas |
| fullScreen | boolean | false | Whether to use full screen mode |
| isLoading | boolean | false | Show loading state |
| style | CSSProperties | - | Custom styles for the container |
| className | string | - | CSS class name for the container |
| width | number | - | Fixed width (overrides responsive behavior) |
| height | number | - | Fixed height (overrides responsive behavior) |
Annotation Types
Rectangle Annotation
interface Rectangle {
id: string;
type: 'rectangle';
position: { x: number; y: number };
width: number;
height: number;
color?: string;
opacity?: number;
strokeWidth?: number;
visible?: boolean;
label?: string;
data?: any;
}Polygon Annotation
interface Polygon {
id: string;
type: 'polygon';
points: number[]; // [x1, y1, x2, y2, ...]
color?: string;
opacity?: number;
strokeWidth?: number;
visible?: boolean;
label?: string;
data?: any;
}Line Annotation
interface Line {
id: string;
type: 'line';
points: number[]; // [x1, y1, x2, y2, ...]
color?: string;
opacity?: number;
strokeWidth?: number;
visible?: boolean;
label?: string;
data?: any;
}Point Annotation
interface Point {
id: string;
type: 'point';
position: { x: number; y: number };
color?: string;
opacity?: number;
strokeWidth?: number;
visible?: boolean;
label?: string;
data?: any;
}Advanced Usage
Using Context Hooks
The library provides several context hooks for advanced control:
import { useCanvas, useAnnotations } from '@siali/sorolla';
function AnnotationControls() {
const {
annotations,
setAnnotations,
setAnnotationAttributes,
annotationAttributes,
tool,
setTool,
zoomIn,
zoomOut,
undo,
redo
} = useCanvas();
const { imageDimensions } = useAnnotations();
const handleUpdateStyle = () => {
setAnnotationAttributes({
color: '#ff0000',
opacity: 0.5,
strokeWidth: 3,
label: 'updated',
visible: true,
});
};
const handleDeleteAll = () => {
setAnnotations([]);
};
return (
<div className="controls">
<button onClick={() => setTool('bbox')}>Rectangle Tool</button>
<button onClick={() => setTool('polygon')}>Polygon Tool</button>
<button onClick={() => setTool('line')}>Line Tool</button>
<button onClick={() => setTool('point')}>Point Tool</button>
<button onClick={() => setTool('move')}>Move Tool</button>
<button onClick={zoomIn}>Zoom In</button>
<button onClick={zoomOut}>Zoom Out</button>
<button onClick={undo}>Undo</button>
<button onClick={redo}>Redo</button>
<button onClick={handleUpdateStyle}>Update Style</button>
<button onClick={handleDeleteAll}>Delete All</button>
<p>Current tool: {tool}</p>
<p>Annotations count: {annotations.length}</p>
<p>Real image size: {imageDimensions.realDimensions.width} x {imageDimensions.realDimensions.height}</p>
</div>
);
}Custom Annotation Attributes
Set global annotation attributes that apply to all new annotations:
const { setAnnotationAttributes } = useCanvas();
// Update global attributes
setAnnotationAttributes({
color: '#00ff00',
opacity: 0.7,
strokeWidth: 2,
label: 'custom-label',
visible: true,
});Annotation Data Handling
The onUpdate callback receives the complete annotations array whenever changes occur:
function handleAnnotationUpdate(annotations: Annotation[]) {
// Process annotations
console.log('Total annotations:', annotations.length);
// Filter by type
const rectangles = annotations.filter(ann => ann.type === 'rectangle');
const polygons = annotations.filter(ann => ann.type === 'polygon');
// Save to backend
saveAnnotations(annotations);
// Update local state
setLocalAnnotations(annotations);
}
<SorollaAnnotator
image="/image.jpg"
initialAnnotations={annotations}
onUpdate={handleAnnotationUpdate}
/>Working with Different Annotation Types
import { Rectangle, Polygon, Line, Point, Annotation } from '@siali/sorolla';
// Type-safe annotation handling
function processAnnotations(annotations: Annotation[]) {
annotations.forEach(annotation => {
switch (annotation.type) {
case 'rectangle':
const rect = annotation as Rectangle;
console.log(`Rectangle: ${rect.width}x${rect.height} at (${rect.position.x}, ${rect.position.y})`);
break;
case 'polygon':
const poly = annotation as Polygon;
console.log(`Polygon with ${poly.points.length / 2} points`);
break;
case 'line':
const line = annotation as Line;
console.log(`Line with ${line.points.length / 2} points`);
break;
case 'point':
const point = annotation as Point;
console.log(`Point at (${point.position.x}, ${point.position.y})`);
break;
}
});
}Keyboard Shortcuts
| Key | Action |
|-----|--------|
| V | Switch to Edit tool |
| B | Switch to Rectangle (Bbox) tool |
| P | Switch to Polygon tool |
| M | Switch to Move tool |
| O | Switch to Point tool |
| L | Switch to Line tool |
| Space | Temporarily switch to Move tool (hold) |
| Escape | Deselect current annotation |
| I or + | Zoom in |
| O or - | Zoom out |
| Ctrl + Mouse Wheel | Zoom |
| Mouse Wheel | Pan (when in move mode) |
Styling and Customization
Default Annotation Attributes
const defaultAttributes = {
color: '#D3D3D3',
opacity: 0.3,
strokeWidth: 1,
visible: true,
label: "new",
};Custom Styling
<SorollaAnnotator
image="/image.jpg"
style={{
border: '2px solid #ccc',
borderRadius: '8px',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
}}
className="my-annotator"
/>Error Handling
function SafeAnnotator() {
const [error, setError] = useState<string | null>(null);
const handleError = (error: Error) => {
console.error('Annotation error:', error);
setError(error.message);
};
if (error) {
return (
<div className="error">
<p>Error: {error}</p>
<button onClick={() => setError(null)}>Retry</button>
</div>
);
}
return (
<SorollaAnnotator
image="/image.jpg"
initialAnnotations={[]}
onUpdate={(annotations) => {
try {
// Process annotations safely
processAnnotations(annotations);
} catch (err) {
handleError(err as Error);
}
}}
/>
);
}Performance Tips
- Optimize Images: Use WebP format and appropriate sizes
- Limit Annotations: For large datasets, consider virtualization
- Debounce Updates: Debounce the
onUpdatecallback for frequent changes - Memoize Components: Use
React.memofor wrapper components
const DebouncedAnnotator = React.memo(({ onUpdate, ...props }) => {
const debouncedUpdate = useMemo(
() => debounce(onUpdate, 300),
[onUpdate]
);
return (
<SorollaAnnotator
{...props}
onUpdate={debouncedUpdate}
/>
);
});Troubleshooting
Common Issues
- SSR Errors in Next.js: Use dynamic imports with
ssr: false - Canvas Not Rendering: Ensure container has explicit dimensions
- Annotations Not Updating: Check that
onUpdatecallback is provided - Performance Issues: Reduce annotation count or optimize image size
Debug Mode
Enable debug logging:
// Add to your component
useEffect(() => {
console.log('Current annotations:', annotations);
}, [annotations]);Contributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
License
MIT License - see LICENSE file for details.
Support
For issues and questions:
- GitHub Issues: Create an issue
- Documentation: Full documentation
Sorolla - Making image annotation simple and powerful in React applications.
