npm package: @siali/sorolla
Description: Sorolla components library for Siali
Author: Siali
Latest version: 1.3.0

# 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:

```bash
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/sorolla
```

## Quick Start

### Basic Usage in React (with Provider)

```tsx
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:

```tsx
"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

```typescript
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

```typescript
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

```typescript
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

```typescript
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:

```tsx
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:

```tsx
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:

```tsx
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

```tsx
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

```typescript
const defaultAttributes = {
  color: '#D3D3D3',
  opacity: 0.3,
  strokeWidth: 1,
  visible: true,
  label: "new",
};
```

### Custom Styling

```tsx
<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

```tsx
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

1. **Optimize Images**: Use WebP format and appropriate sizes
2. **Limit Annotations**: For large datasets, consider virtualization
3. **Debounce Updates**: Debounce the `onUpdate` callback for frequent changes
4. **Memoize Components**: Use `React.memo` for wrapper components

```tsx
const DebouncedAnnotator = React.memo(({ onUpdate, ...props }) => {
  const debouncedUpdate = useMemo(
    () => debounce(onUpdate, 300),
    [onUpdate]
  );
  
  return (
    <SorollaAnnotator
      {...props}
      onUpdate={debouncedUpdate}
    />
  );
});
```

## Troubleshooting

### Common Issues

1. **SSR Errors in Next.js**: Use dynamic imports with `ssr: false`
2. **Canvas Not Rendering**: Ensure container has explicit dimensions
3. **Annotations Not Updating**: Check that `onUpdate` callback is provided
4. **Performance Issues**: Reduce annotation count or optimize image size

### Debug Mode

Enable debug logging:

```tsx
// 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](https://github.com/Sialitech/siali-toolkit/issues)
- Documentation: [Full documentation](https://github.com/Sialitech/siali-toolkit#readme)

---

**Sorolla** - Making image annotation simple and powerful in React applications.