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:
+63
-6
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user