Add OCRImportPanel component and ocr-integration utilities to import OCR-analyzed data from the grid detection service into the worksheet editor. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
331 lines
11 KiB
TypeScript
331 lines
11 KiB
TypeScript
'use client'
|
|
|
|
import { useRef } from 'react'
|
|
import { useTheme } from '@/lib/ThemeContext'
|
|
import { useLanguage } from '@/lib/LanguageContext'
|
|
import { useWorksheet } from '@/lib/worksheet-editor/WorksheetContext'
|
|
import type { EditorTool } from '@/app/worksheet-editor/types'
|
|
|
|
interface EditorToolbarProps {
|
|
onOpenAIGenerator: () => void
|
|
onOpenDocumentImporter: () => void
|
|
onOpenCleanupPanel?: () => void
|
|
onOpenOCRImport?: () => void
|
|
className?: string
|
|
}
|
|
|
|
interface ToolButtonProps {
|
|
tool: EditorTool
|
|
icon: React.ReactNode
|
|
label: string
|
|
isActive: boolean
|
|
onClick: () => void
|
|
isDark: boolean
|
|
}
|
|
|
|
function ToolButton({ tool, icon, label, isActive, onClick, isDark }: ToolButtonProps) {
|
|
return (
|
|
<button
|
|
onClick={onClick}
|
|
title={label}
|
|
className={`w-12 h-12 flex items-center justify-center rounded-xl transition-all ${
|
|
isActive
|
|
? isDark
|
|
? 'bg-purple-500/30 text-purple-300 shadow-lg'
|
|
: 'bg-purple-100 text-purple-700 shadow-lg'
|
|
: isDark
|
|
? 'text-white/70 hover:bg-white/10 hover:text-white'
|
|
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900'
|
|
}`}
|
|
>
|
|
{icon}
|
|
</button>
|
|
)
|
|
}
|
|
|
|
export function EditorToolbar({ onOpenAIGenerator, onOpenDocumentImporter, onOpenCleanupPanel, onOpenOCRImport, className = '' }: EditorToolbarProps) {
|
|
const { isDark } = useTheme()
|
|
const { t } = useLanguage()
|
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
|
|
const {
|
|
activeTool,
|
|
setActiveTool,
|
|
canvas,
|
|
canUndo,
|
|
canRedo,
|
|
undo,
|
|
redo,
|
|
} = useWorksheet()
|
|
|
|
const handleToolClick = (tool: EditorTool) => {
|
|
setActiveTool(tool)
|
|
}
|
|
|
|
const handleImageUpload = () => {
|
|
fileInputRef.current?.click()
|
|
}
|
|
|
|
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0]
|
|
if (!file || !canvas) return
|
|
|
|
const reader = new FileReader()
|
|
reader.onload = (event) => {
|
|
const url = event.target?.result as string
|
|
if ((canvas as any).addImage) {
|
|
(canvas as any).addImage(url)
|
|
}
|
|
}
|
|
reader.readAsDataURL(file)
|
|
|
|
// Reset input
|
|
e.target.value = ''
|
|
}
|
|
|
|
// Glassmorphism styles
|
|
const toolbarStyle = isDark
|
|
? 'backdrop-blur-xl bg-white/10 border border-white/20'
|
|
: 'backdrop-blur-xl bg-white/70 border border-black/10 shadow-xl'
|
|
|
|
const dividerStyle = isDark
|
|
? 'border-white/20'
|
|
: 'border-slate-200'
|
|
|
|
return (
|
|
<div className={`flex flex-col gap-2 p-2 rounded-2xl ${toolbarStyle} ${className}`}>
|
|
{/* Selection Tool */}
|
|
<ToolButton
|
|
tool="select"
|
|
isActive={activeTool === 'select'}
|
|
onClick={() => handleToolClick('select')}
|
|
isDark={isDark}
|
|
label="Auswählen"
|
|
icon={
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5M7.188 2.239l.777 2.897M5.136 7.965l-2.898-.777M13.95 4.05l-2.122 2.122m-5.657 5.656l-2.12 2.122" />
|
|
</svg>
|
|
}
|
|
/>
|
|
|
|
<div className={`border-t ${dividerStyle}`} />
|
|
|
|
{/* Text Tool */}
|
|
<ToolButton
|
|
tool="text"
|
|
isActive={activeTool === 'text'}
|
|
onClick={() => handleToolClick('text')}
|
|
isDark={isDark}
|
|
label="Text"
|
|
icon={
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
|
</svg>
|
|
}
|
|
/>
|
|
|
|
<div className={`border-t ${dividerStyle}`} />
|
|
|
|
{/* Shape Tools */}
|
|
<ToolButton
|
|
tool="rectangle"
|
|
isActive={activeTool === 'rectangle'}
|
|
onClick={() => handleToolClick('rectangle')}
|
|
isDark={isDark}
|
|
label="Rechteck"
|
|
icon={
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 6h16M4 18h16M4 6v12M20 6v12" />
|
|
</svg>
|
|
}
|
|
/>
|
|
|
|
<ToolButton
|
|
tool="circle"
|
|
isActive={activeTool === 'circle'}
|
|
onClick={() => handleToolClick('circle')}
|
|
isDark={isDark}
|
|
label="Kreis"
|
|
icon={
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<circle cx="12" cy="12" r="9" strokeWidth={1.5} />
|
|
</svg>
|
|
}
|
|
/>
|
|
|
|
<ToolButton
|
|
tool="line"
|
|
isActive={activeTool === 'line'}
|
|
onClick={() => handleToolClick('line')}
|
|
isDark={isDark}
|
|
label="Linie"
|
|
icon={
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 20L20 4" />
|
|
</svg>
|
|
}
|
|
/>
|
|
|
|
<ToolButton
|
|
tool="arrow"
|
|
isActive={activeTool === 'arrow'}
|
|
onClick={() => handleToolClick('arrow')}
|
|
isDark={isDark}
|
|
label="Pfeil"
|
|
icon={
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M17 8l4 4m0 0l-4 4m4-4H3" />
|
|
</svg>
|
|
}
|
|
/>
|
|
|
|
<div className={`border-t ${dividerStyle}`} />
|
|
|
|
{/* Image Tools */}
|
|
<ToolButton
|
|
tool="image"
|
|
isActive={activeTool === 'image'}
|
|
onClick={handleImageUpload}
|
|
isDark={isDark}
|
|
label="Bild hochladen"
|
|
icon={
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
|
</svg>
|
|
}
|
|
/>
|
|
|
|
<input
|
|
ref={fileInputRef}
|
|
type="file"
|
|
accept="image/*"
|
|
onChange={handleFileChange}
|
|
className="hidden"
|
|
/>
|
|
|
|
{/* AI Image Generator */}
|
|
<ToolButton
|
|
tool="ai-image"
|
|
isActive={activeTool === 'ai-image'}
|
|
onClick={onOpenAIGenerator}
|
|
isDark={isDark}
|
|
label="KI-Bild generieren"
|
|
icon={
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z" />
|
|
</svg>
|
|
}
|
|
/>
|
|
|
|
<div className={`border-t ${dividerStyle}`} />
|
|
|
|
{/* Document Import */}
|
|
<button
|
|
onClick={onOpenDocumentImporter}
|
|
title="Dokument importieren"
|
|
className={`w-12 h-12 flex items-center justify-center rounded-xl transition-all ${
|
|
isDark
|
|
? 'text-blue-300 hover:bg-blue-500/20 hover:text-blue-200'
|
|
: 'text-blue-600 hover:bg-blue-100 hover:text-blue-700'
|
|
}`}
|
|
>
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 3v6a1 1 0 001 1h6" />
|
|
</svg>
|
|
</button>
|
|
|
|
{/* Cleanup Panel - Handwriting Removal */}
|
|
{onOpenCleanupPanel && (
|
|
<button
|
|
onClick={onOpenCleanupPanel}
|
|
title="Arbeitsblatt bereinigen (Handschrift entfernen)"
|
|
className={`w-12 h-12 flex items-center justify-center rounded-xl transition-all ${
|
|
isDark
|
|
? 'text-orange-300 hover:bg-orange-500/20 hover:text-orange-200'
|
|
: 'text-orange-600 hover:bg-orange-100 hover:text-orange-700'
|
|
}`}
|
|
>
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" />
|
|
</svg>
|
|
</button>
|
|
)}
|
|
|
|
{/* OCR Import */}
|
|
{onOpenOCRImport && (
|
|
<button
|
|
onClick={onOpenOCRImport}
|
|
title="OCR Daten importieren (aus Grid Analyse)"
|
|
className={`w-12 h-12 flex items-center justify-center rounded-xl transition-all ${
|
|
isDark
|
|
? 'text-green-300 hover:bg-green-500/20 hover:text-green-200'
|
|
: 'text-green-600 hover:bg-green-100 hover:text-green-700'
|
|
}`}
|
|
>
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
|
</svg>
|
|
</button>
|
|
)}
|
|
|
|
<div className={`border-t ${dividerStyle}`} />
|
|
|
|
{/* Table Tool */}
|
|
<ToolButton
|
|
tool="table"
|
|
isActive={activeTool === 'table'}
|
|
onClick={() => handleToolClick('table')}
|
|
isDark={isDark}
|
|
label="Tabelle"
|
|
icon={
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
|
</svg>
|
|
}
|
|
/>
|
|
|
|
<div className={`border-t ${dividerStyle} mt-auto`} />
|
|
|
|
{/* Undo/Redo */}
|
|
<button
|
|
onClick={undo}
|
|
disabled={!canUndo}
|
|
title="Rückgängig (Ctrl+Z)"
|
|
className={`w-12 h-12 flex items-center justify-center rounded-xl transition-all ${
|
|
canUndo
|
|
? isDark
|
|
? 'text-white/70 hover:bg-white/10 hover:text-white'
|
|
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900'
|
|
: isDark
|
|
? 'text-white/30 cursor-not-allowed'
|
|
: 'text-slate-300 cursor-not-allowed'
|
|
}`}
|
|
>
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" />
|
|
</svg>
|
|
</button>
|
|
|
|
<button
|
|
onClick={redo}
|
|
disabled={!canRedo}
|
|
title="Wiederholen (Ctrl+Y)"
|
|
className={`w-12 h-12 flex items-center justify-center rounded-xl transition-all ${
|
|
canRedo
|
|
? isDark
|
|
? 'text-white/70 hover:bg-white/10 hover:text-white'
|
|
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900'
|
|
: isDark
|
|
? 'text-white/30 cursor-not-allowed'
|
|
: 'text-slate-300 cursor-not-allowed'
|
|
}`}
|
|
>
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M21 10h-10a8 8 0 00-8 8v2M21 10l-6 6m6-6l-6-6" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
)
|
|
}
|