fix: MEDIUM issues #31-#36 — inline text editor, image/paste persist, input validation on all routes, useEffect deps, online count fix

This commit is contained in:
A0 Orchestrator
2026-06-30 14:03:22 +02:00
parent 172f933456
commit ee664750e6
15 changed files with 566 additions and 16 deletions
+63 -6
View File
@@ -19,6 +19,7 @@ import MobileDrawers from './components/MobileDrawers';
import BackgroundImport from './components/BackgroundImport';
import HistoryPanel from './components/HistoryPanel';
import SettingsModal from './components/SettingsModal';
import InlineTextEditor from './components/InlineTextEditor';
import { BackgroundService, type BackgroundConfig } from './services/backgroundService';
import { HistoryManager, type CADStateSnapshot, type HistoryEntry } from './history';
import { getCommandRegistry } from './services/commandRegistry';
@@ -140,8 +141,8 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
pluginRegistry.initDefaults();
}, []);
// Status bar
const onlineCount = collab.cursors.length + 1;
// Status bar only count self as online when actually connected
const onlineCount = collab.status === 'connected' ? collab.cursors.length : 0;
// Mobile drawers
const [mobileLeftOpen, setMobileLeftOpen] = useState(false);
@@ -520,13 +521,37 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
setToolState(state);
}, []);
// Inline text editor state
const [editingTextElement, setEditingTextElement] = useState<CADElement | null>(null);
const handleTextEdit = useCallback((el: CADElement) => {
const text = window.prompt('Text eingeben:', '');
if (text !== null && text.trim() !== '') {
setEditingTextElement(el);
}, []);
const handleTextEditorSubmit = useCallback((text: string) => {
if (editingTextElement && text.trim() !== '') {
const updatedEl = { ...editingTextElement, properties: { ...editingTextElement.properties, text } };
setElements((prev) => prev.map((e) =>
e.id === el.id ? { ...e, properties: { ...e.properties, text } } : e
e.id === editingTextElement.id ? updatedEl : e
));
// Push to Yjs CRDT for real-time sync
collab.setElement(updatedEl);
// Update in backend
if (token) {
setSavedStatus('Speichert…');
updateElement(token, updatedEl.id, updatedEl).then(() => {
setSavedStatus('gespeichert');
}).catch((err) => {
console.error('Failed to save text edit:', err);
setSavedStatus('Fehler beim Speichern');
});
}
}
setEditingTextElement(null);
}, [editingTextElement, collab, token]);
const handleTextEditorCancel = useCallback(() => {
setEditingTextElement(null);
}, []);
const handleCommandTrigger = useCallback((msg: string) => {
@@ -816,6 +841,19 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
}));
setElements((prev) => [...prev, ...pasted]);
setCommandHistory((prev) => [...prev, { prefix: '·', text: `${pasted.length} Element(e) eingefügt`, type: 'info' }]);
// Push pasted elements to Yjs CRDT and backend
for (const el of pasted) {
collab.setElement(el);
}
if (drawingId && token) {
setSavedStatus('Speichert…');
Promise.all(pasted.map(el => createElementTyped(token, drawingId, el))).then(() => {
setSavedStatus('gespeichert');
}).catch((err) => {
console.error('Failed to save pasted elements:', err);
setSavedStatus('Fehler beim Speichern');
});
}
} else {
setCommandHistory((prev) => [...prev, { prefix: '·', text: 'Zwischenablage leer', type: 'info' }]);
}
@@ -847,6 +885,18 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
};
setElements((prev) => [...prev, imgEl]);
setCommandHistory((prev) => [...prev, { prefix: '·', text: 'Bild eingefügt', type: 'info' }]);
// Push image element to Yjs CRDT for real-time sync
collab.setElement(imgEl);
// Save to backend
if (drawingId && token) {
setSavedStatus('Speichert…');
createElementTyped(token, drawingId, imgEl).then(() => {
setSavedStatus('gespeichert');
}).catch((err) => {
console.error('Failed to save image element:', err);
setSavedStatus('Fehler beim Speichern');
});
}
};
reader.readAsDataURL(input.files[0]);
}
@@ -892,7 +942,7 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
if (action === 'ki') { setActiveRightPanel('ki'); setCommandHistory((prev) => [...prev, { prefix: '·', text: 'KI Copilot geöffnet', type: 'info' }]); return; }
if (action === 'ki-draw') { setActiveRightPanel('ki'); setCommandHistory((prev) => [...prev, { prefix: '·', text: 'KI Zeichnen: Beschreibung eingeben', type: 'info' }]); return; }
if (action === 'ki-analyze') { setActiveRightPanel('ki'); setCommandHistory((prev) => [...prev, { prefix: '·', text: 'KI Analyse gestartet', type: 'info' }]); return; }
}, [handleUndo, handleRedo, handleImport, handleExport, onNavigateBack, drawingId, token, elements, activeLayerId, gridEnabled]);
}, [handleUndo, handleRedo, handleImport, handleExport, onNavigateBack, drawingId, token, elements, activeLayerId, gridEnabled, collab]);
const handleToolChange = useCallback((tool: string) => {
setActiveTool(tool);
@@ -1461,6 +1511,13 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
</div>
</div>
)}
{editingTextElement && (
<InlineTextEditor
initialText={editingTextElement.properties?.text ?? ''}
onSubmit={handleTextEditorSubmit}
onCancel={handleTextEditorCancel}
/>
)}
</div>
);
};