Whiteboard: Titel nachträglich umbenennbar (Doppelklick oder ✎-Button, in Liste und Topbar)
This commit is contained in:
@@ -188,6 +188,8 @@ export default function Whiteboard({ toast, mobile }) {
|
|||||||
const [current, setCurrent] = useState(null); // { id, title, role }
|
const [current, setCurrent] = useState(null); // { id, title, role }
|
||||||
const [creating, setCreating] = useState(false);
|
const [creating, setCreating] = useState(false);
|
||||||
const [newTitle, setNewTitle] = useState('');
|
const [newTitle, setNewTitle] = useState('');
|
||||||
|
const [editingId, setEditingId] = useState(null); // ID des gerade umbenannten Boards
|
||||||
|
const [editTitle, setEditTitle] = useState('');
|
||||||
|
|
||||||
// ── Canvas-State ──────────────────────────────────────────────────────────
|
// ── Canvas-State ──────────────────────────────────────────────────────────
|
||||||
const [elements, setElements] = useState([]);
|
const [elements, setElements] = useState([]);
|
||||||
@@ -224,6 +226,16 @@ export default function Whiteboard({ toast, mobile }) {
|
|||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const renameBoard = async (id, title) => {
|
||||||
|
if (!title?.trim()) return;
|
||||||
|
try {
|
||||||
|
await api(`/tools/whiteboard/${id}/title`, { method:'PATCH', body:{ title: title.trim() } });
|
||||||
|
setBoards(prev => prev.map(b => b.id === id ? { ...b, title: title.trim() } : b));
|
||||||
|
if (current?.id === id) setCurrent(prev => ({ ...prev, title: title.trim() }));
|
||||||
|
} catch(e) { toast(e.message, 'error'); }
|
||||||
|
finally { setEditingId(null); }
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => { loadBoards(); }, [loadBoards]);
|
useEffect(() => { loadBoards(); }, [loadBoards]);
|
||||||
|
|
||||||
// Canvas rendern wenn sich Elemente oder Viewport ändern
|
// Canvas rendern wenn sich Elemente oder Viewport ändern
|
||||||
@@ -560,16 +572,34 @@ export default function Whiteboard({ toast, mobile }) {
|
|||||||
|
|
||||||
{boards.map(b => (
|
{boards.map(b => (
|
||||||
<div key={b.id} style={{ ...S.card, display:'flex', alignItems:'center', gap:12, marginBottom:8, cursor:'pointer' }}
|
<div key={b.id} style={{ ...S.card, display:'flex', alignItems:'center', gap:12, marginBottom:8, cursor:'pointer' }}
|
||||||
onClick={() => openBoard(b)}>
|
onClick={() => { if (editingId !== b.id) openBoard(b); }}>
|
||||||
<div style={{ fontSize:24 }}>🖊</div>
|
<div style={{ fontSize:24 }}>🖊</div>
|
||||||
<div style={{ flex:1, minWidth:0 }}>
|
<div style={{ flex:1, minWidth:0 }}>
|
||||||
<div style={{ color:'#fff', fontFamily:'monospace', fontSize:13, fontWeight:700 }}>{b.title}</div>
|
{editingId === b.id ? (
|
||||||
|
<input autoFocus value={editTitle}
|
||||||
|
onChange={e => setEditTitle(e.target.value)}
|
||||||
|
onKeyDown={e => { if (e.key==='Enter') renameBoard(b.id, editTitle); if (e.key==='Escape') setEditingId(null); }}
|
||||||
|
onBlur={() => renameBoard(b.id, editTitle)}
|
||||||
|
onClick={e => e.stopPropagation()}
|
||||||
|
style={{ ...S.inp, fontSize:12, fontWeight:700, padding:'3px 8px', width:'100%' }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div style={{ color:'#fff', fontFamily:'monospace', fontSize:13, fontWeight:700 }}
|
||||||
|
onDoubleClick={e => { e.stopPropagation(); if (b.role==='owner'||b.role==='edit'){ setEditingId(b.id); setEditTitle(b.title); } }}>
|
||||||
|
{b.title}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div style={{ color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontSize:10, marginTop:2 }}>
|
<div style={{ color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontSize:10, marginTop:2 }}>
|
||||||
{b.role === 'owner' ? '👑 Eigenes' : b.role === 'edit' ? '✏ Bearbeiten' : '👁 Lesen'}
|
{b.role === 'owner' ? '👑 Eigenes' : b.role === 'edit' ? '✏ Bearbeiten' : '👁 Lesen'}
|
||||||
{b.owner_name && b.role !== 'owner' && ` · von ${b.owner_name}`}
|
{b.owner_name && b.role !== 'owner' && ` · von ${b.owner_name}`}
|
||||||
{' · ' + new Date(b.updated_at).toLocaleDateString('de-DE', { day:'2-digit', month:'2-digit', year:'numeric' })}
|
{' · ' + new Date(b.updated_at).toLocaleDateString('de-DE', { day:'2-digit', month:'2-digit', year:'numeric' })}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{(b.role === 'owner' || b.role === 'edit') && (
|
||||||
|
<button onClick={e => { e.stopPropagation(); setEditingId(b.id); setEditTitle(b.title); }}
|
||||||
|
title="Umbenennen"
|
||||||
|
style={{ background:'transparent', border:'none', color:'rgba(255,255,255,0.2)', cursor:'pointer', fontSize:14, padding:'4px 6px' }}>✎</button>
|
||||||
|
)}
|
||||||
{b.role === 'owner' && (
|
{b.role === 'owner' && (
|
||||||
<button onClick={e => { e.stopPropagation(); if (confirm(`"${b.title}" löschen?`))
|
<button onClick={e => { e.stopPropagation(); if (confirm(`"${b.title}" löschen?`))
|
||||||
api(`/tools/whiteboard/${b.id}`, { method:'DELETE' }).then(loadBoards).catch(err => toast(err.message, 'error'));
|
api(`/tools/whiteboard/${b.id}`, { method:'DELETE' }).then(loadBoards).catch(err => toast(err.message, 'error'));
|
||||||
@@ -609,11 +639,24 @@ export default function Whiteboard({ toast, mobile }) {
|
|||||||
background:'rgba(0,0,0,0.6)', borderBottom:'1px solid rgba(255,255,255,0.08)', flexShrink:0, flexWrap:'wrap' }}>
|
background:'rgba(0,0,0,0.6)', borderBottom:'1px solid rgba(255,255,255,0.08)', flexShrink:0, flexWrap:'wrap' }}>
|
||||||
<button onClick={closeBoard} style={{ background:'transparent', border:'none', color:'rgba(255,255,255,0.5)',
|
<button onClick={closeBoard} style={{ background:'transparent', border:'none', color:'rgba(255,255,255,0.5)',
|
||||||
cursor:'pointer', fontSize:18, padding:'2px 6px', lineHeight:1 }}>←</button>
|
cursor:'pointer', fontSize:18, padding:'2px 6px', lineHeight:1 }}>←</button>
|
||||||
<span style={{ color:'#fff', fontFamily:'monospace', fontSize:13, fontWeight:700, flex:1, minWidth:0,
|
{editingId === current?.id ? (
|
||||||
overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
|
<input autoFocus value={editTitle}
|
||||||
|
onChange={e => setEditTitle(e.target.value)}
|
||||||
|
onKeyDown={e => { if (e.key==='Enter') renameBoard(current.id, editTitle); if (e.key==='Escape') setEditingId(null); }}
|
||||||
|
onBlur={() => renameBoard(current.id, editTitle)}
|
||||||
|
style={{ ...S.inp, fontSize:13, fontWeight:700, flex:1, padding:'3px 8px' }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
onDoubleClick={() => { if (current?.role !== 'view'){ setEditingId(current.id); setEditTitle(current.title); } }}
|
||||||
|
title={current?.role !== 'view' ? 'Doppelklick zum Umbenennen' : ''}
|
||||||
|
style={{ color:'#fff', fontFamily:'monospace', fontSize:13, fontWeight:700, flex:1, minWidth:0,
|
||||||
|
overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap',
|
||||||
|
cursor: current?.role !== 'view' ? 'text' : 'default' }}>
|
||||||
{current?.title}
|
{current?.title}
|
||||||
{current?.role === 'view' && <span style={{ color:'rgba(255,255,255,0.3)', fontSize:10, marginLeft:8 }}>👁 nur lesen</span>}
|
{current?.role === 'view' && <span style={{ color:'rgba(255,255,255,0.3)', fontSize:10, marginLeft:8 }}>👁 nur lesen</span>}
|
||||||
</span>
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Aktive User */}
|
{/* Aktive User */}
|
||||||
{collab.length > 0 && (
|
{collab.length > 0 && (
|
||||||
|
|||||||
Reference in New Issue
Block a user