From ffea270fbcd8e4fa0979a238278689fba2889876 Mon Sep 17 00:00:00 2001 From: Leopoldadmin Date: Mon, 22 Jun 2026 20:59:35 +0000 Subject: [PATCH] feat: enhance BlockLibrary with create, delete, search and category features --- frontend/src/components/BlockLibrary.jsx | 280 +++++++++++++++++++++-- 1 file changed, 258 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/BlockLibrary.jsx b/frontend/src/components/BlockLibrary.jsx index 617bbb8..d7dc031 100644 --- a/frontend/src/components/BlockLibrary.jsx +++ b/frontend/src/components/BlockLibrary.jsx @@ -1,32 +1,112 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import api from '../services/api'; -const BlockLibrary = ({ canvasRef }) => { +const BlockLibrary = ({ canvasRef, yjsDoc }) => { const [blocks, setBlocks] = useState([]); const [categories, setCategories] = useState([]); const [activeCategory, setActiveCategory] = useState('Alle'); const [loading, setLoading] = useState(true); - + const [selectedElements, setSelectedElements] = useState([]); + const [newBlockName, setNewBlockName] = useState(''); + const [newBlockCategory, setNewBlockCategory] = useState(''); + const [showCreateDialog, setShowCreateDialog] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); + + // Load blocks on component mount useEffect(() => { - api.get('/blocks') - .then(res => { - const data = res.data || []; - setBlocks(data); - const cats = ['Alle', ...new Set(data.map(b => b.category || 'Allgemein'))]; - setCategories(cats); - setLoading(false); - }) - .catch(() => setLoading(false)); + loadBlocks(); }, []); - - const filteredBlocks = activeCategory === 'Alle' - ? blocks - : blocks.filter(b => (b.category || 'Allgemein') === activeCategory); - + + const loadBlocks = async () => { + try { + const res = await api.get('/blocks'); + const data = res.data || []; + setBlocks(data); + + // Extract unique categories + const cats = ['Alle', ...new Set(data.map(b => b.category || 'Allgemein'))]; + setCategories(cats); + } catch (error) { + console.error('Failed to load blocks:', error); + } finally { + setLoading(false); + } + }; + + // Filter blocks by category and search query + const filteredBlocks = blocks.filter(block => { + const matchesCategory = activeCategory === 'Alle' || (block.category || 'Allgemein') === activeCategory; + const matchesSearch = block.name.toLowerCase().includes(searchQuery.toLowerCase()) || + (block.category || '').toLowerCase().includes(searchQuery.toLowerCase()); + return matchesCategory && matchesSearch; + }); + + // Handle creating a new block from selected elements + const handleCreateBlock = async () => { + if (!newBlockName.trim() || selectedElements.length === 0) return; + + try { + const response = await api.post('/blocks', { + name: newBlockName, + category: newBlockCategory || 'Allgemein', + elements: selectedElements + }); + + // Add to Yjs document + if (yjsDoc) { + const blockId = response.data.id; + yjsDoc.blocks.set(blockId, response.data); + } + + // Refresh blocks list + loadBlocks(); + + // Reset form + setNewBlockName(''); + setNewBlockCategory(''); + setShowCreateDialog(false); + setSelectedElements([]); + } catch (error) { + console.error('Failed to create block:', error); + alert('Fehler beim Erstellen des Blocks'); + } + }; + + // Handle deleting a block + const handleDeleteBlock = async (blockId, blockName) => { + if (!window.confirm(`Sind Sie sicher, dass Sie den Block "${blockName}" löschen möchten?`)) { + return; + } + + try { + await api.delete(`/blocks/${blockId}`); + + // Remove from Yjs document + if (yjsDoc) { + yjsDoc.blocks.delete(blockId); + } + + // Refresh blocks list + loadBlocks(); + } catch (error) { + console.error('Failed to delete block:', error); + alert('Fehler beim Löschen des Blocks'); + } + }; + + // Handle inserting a block instance on canvas + const handleInsertBlock = (block) => { + if (canvasRef.current) { + canvasRef.current.addSvgObject(block.svg_data); + } + }; + + // Handle drag start for block insertion const handleDragStart = (e, block) => { e.dataTransfer.setData('text/plain', block.svg_data); }; - + + // Handle drop on canvas const handleDrop = (e) => { e.preventDefault(); const svgData = e.dataTransfer.getData('text/plain'); @@ -34,19 +114,36 @@ const BlockLibrary = ({ canvasRef }) => { canvasRef.current.addSvgObject(svgData); } }; - + const handleDragOver = (e) => { e.preventDefault(); }; - + if (loading) return
Lade...
; - + return (
+ {/* Search bar */} +
+ setSearchQuery(e.target.value)} + style={{ + width: '100%', + padding: '4px 8px', + fontSize: 12, + border: '1px solid #ddd', + borderRadius: 4 + }} + /> +
+ {/* Category filter */}
{categories.map(cat => ( @@ -67,6 +164,26 @@ const BlockLibrary = ({ canvasRef }) => { ))}
+ + {/* Create block button */} +
+ +
+ {/* Block grid */}
{filteredBlocks.map(block => ( @@ -74,6 +191,7 @@ const BlockLibrary = ({ canvasRef }) => { key={block.id} draggable onDragStart={(e) => handleDragStart(e, block)} + onClick={() => handleInsertBlock(block)} style={{ border: '1px solid #ddd', borderRadius: 4, @@ -81,6 +199,7 @@ const BlockLibrary = ({ canvasRef }) => { background: 'white', cursor: 'grab', textAlign: 'center', + position: 'relative', }} title={block.name} > @@ -91,11 +210,128 @@ const BlockLibrary = ({ canvasRef }) => {
{block.name}
+
))}
+ {filteredBlocks.length === 0 && ( -

Keine Blöcke in dieser Kategorie.

+

Keine Blöcke gefunden.

+ )} + + {/* Create Block Dialog */} + {showCreateDialog && ( +
+
+

Neuen Block erstellen

+
+ + setNewBlockName(e.target.value)} + style={{ + width: '100%', + padding: '6px', + fontSize: 12, + border: '1px solid #ddd', + borderRadius: 4, + }} + /> +
+
+ + setNewBlockCategory(e.target.value)} + placeholder="Allgemein" + style={{ + width: '100%', + padding: '6px', + fontSize: 12, + border: '1px solid #ddd', + borderRadius: 4, + }} + /> +
+
+ + +
+
+
)} );