diff --git a/.gitignore b/.gitignore index be5020e..19b7d18 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,20 @@ -node_modules -.env +# Dependencies +node_modules/ package-lock.json -.cursor -*.excalidraw -public/dist/assets/ \ No newline at end of file + +# Build artifacts +dist/ +public/dist/ + +# Environment files +.env + +# Logs +*.log + +# Editor artifacts +.cursor/ +.claude/ + +# Development artifacts +*.excalidraw \ No newline at end of file diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index beb8e2d..3f1f822 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -5,24 +5,8 @@ import '@excalidraw/excalidraw/index.css' function App() { const [excalidrawAPI, setExcalidrawAPI] = useState(null) const [isConnected, setIsConnected] = useState(false) - const [elementCount, setElementCount] = useState(0) - const [apiPanelVisible, setApiPanelVisible] = useState(true) - const [notifications, setNotifications] = useState([]) const websocketRef = useRef(null) - // Form state - const [formData, setFormData] = useState({ - type: 'rectangle', - x: 100, - y: 100, - width: 100, - height: 100, - text: '', - backgroundColor: '#ffffff', - strokeColor: '#000000', - strokeWidth: 2 - }) - // WebSocket connection useEffect(() => { connectWebSocket() @@ -36,17 +20,11 @@ function App() { // Load existing elements when Excalidraw API becomes available useEffect(() => { if (excalidrawAPI) { - console.log('ExcalidrawAPI ready, setting up real-time sync') - - // Load existing elements immediately loadExistingElements() // Ensure WebSocket is connected for real-time updates if (!isConnected) { - console.log('ExcalidrawAPI ready but WebSocket not connected, connecting...') connectWebSocket() - } else { - console.log('Both ExcalidrawAPI and WebSocket are ready for real-time sync') } } }, [excalidrawAPI, isConnected]) @@ -57,9 +35,7 @@ function App() { const result = await response.json() if (result.success && result.elements && result.elements.length > 0) { - console.log('Loading existing elements from API:', result.elements.length) const convertedElements = convertToExcalidrawElements(result.elements) - console.log('Converted existing elements:', convertedElements) excalidrawAPI.updateScene({ elements: convertedElements }) } } catch (error) { @@ -69,24 +45,19 @@ function App() { const connectWebSocket = () => { if (websocketRef.current && websocketRef.current.readyState === WebSocket.OPEN) { - console.log('WebSocket already connected') return } const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:' const wsUrl = `${protocol}//${window.location.host}` - console.log('Connecting to WebSocket:', wsUrl) websocketRef.current = new WebSocket(wsUrl) websocketRef.current.onopen = () => { - console.log('WebSocket connected successfully') setIsConnected(true) - // Request existing elements when WebSocket connects and API is ready if (excalidrawAPI) { - console.log('WebSocket connected and ExcalidrawAPI ready - requesting refresh') - setTimeout(loadExistingElements, 100) // Small delay to ensure connection is stable + setTimeout(loadExistingElements, 100) } } @@ -100,12 +71,10 @@ function App() { } websocketRef.current.onclose = (event) => { - console.log('WebSocket disconnected:', event.code, event.reason) setIsConnected(false) // Reconnect after 3 seconds if not a clean close if (event.code !== 1000) { - console.log('Attempting to reconnect in 3 seconds...') setTimeout(connectWebSocket, 3000) } } @@ -117,62 +86,44 @@ function App() { } const handleWebSocketMessage = (data) => { - console.log('WebSocket message received:', data.type, data) - if (!excalidrawAPI) { - console.log('ExcalidrawAPI not ready, skipping message:', data.type) return } try { const currentElements = excalidrawAPI.getSceneElements() - console.log('Current elements count:', currentElements.length) switch (data.type) { case 'initial_elements': if (data.elements && data.elements.length > 0) { - console.log('Loading initial elements:', data.elements.length) const convertedElements = convertToExcalidrawElements(data.elements) - console.log('Converted initial elements:', convertedElements.length) excalidrawAPI.updateScene({ elements: convertedElements }) } break case 'element_created': - console.log('Processing element_created:', data.element) const newElement = convertToExcalidrawElements([data.element]) - console.log('Converted new element:', newElement) const updatedElementsAfterCreate = [...currentElements, ...newElement] - console.log('Total elements after create:', updatedElementsAfterCreate.length) excalidrawAPI.updateScene({ elements: updatedElementsAfterCreate }) - showNotification('Element created successfully!', 'success') break case 'element_updated': - console.log('Processing element_updated:', data.element.id) const convertedUpdatedElement = convertToExcalidrawElements([data.element])[0] const updatedElements = currentElements.map(el => el.id === data.element.id ? convertedUpdatedElement : el ) excalidrawAPI.updateScene({ elements: updatedElements }) - showNotification('Element updated successfully!', 'success') break case 'element_deleted': - console.log('Processing element_deleted:', data.elementId) const filteredElements = currentElements.filter(el => el.id !== data.elementId) excalidrawAPI.updateScene({ elements: filteredElements }) - showNotification('Element deleted successfully!', 'success') break case 'elements_batch_created': - console.log('Processing elements_batch_created:', data.elements.length) const batchElements = convertToExcalidrawElements(data.elements) - console.log('Converted batch elements:', batchElements.length) const updatedElementsAfterBatch = [...currentElements, ...batchElements] - console.log('Total elements after batch:', updatedElementsAfterBatch.length) excalidrawAPI.updateScene({ elements: updatedElementsAfterBatch }) - showNotification(`${data.elements.length} elements created!`, 'success') break default: @@ -180,344 +131,51 @@ function App() { } } catch (error) { console.error('Error processing WebSocket message:', error, data) - showNotification('Error processing real-time update', 'error') - } - } - - const showNotification = (message, type = 'success') => { - const id = Date.now() - const notification = { id, message, type } - setNotifications(prev => [...prev, notification]) - - setTimeout(() => { - setNotifications(prev => prev.filter(n => n.id !== id)) - }, 3000) - } - - const handleExcalidrawChange = (elements, appState, files) => { - setElementCount(elements.length) - } - - const handleFormSubmit = async (e) => { - e.preventDefault() - - const elementData = { - type: formData.type, - x: parseInt(formData.x), - y: parseInt(formData.y), - width: parseInt(formData.width) || undefined, - height: parseInt(formData.height) || undefined, - backgroundColor: formData.backgroundColor, - strokeColor: formData.strokeColor, - strokeWidth: parseInt(formData.strokeWidth) - } - - if (formData.text) { - elementData.text = formData.text - } - - // Remove undefined values - Object.keys(elementData).forEach(key => { - if (elementData[key] === undefined) { - delete elementData[key] - } - }) - - try { - const response = await fetch('/api/elements', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(elementData) - }) - - const result = await response.json() - - if (result.success) { - showNotification('Element created via API!', 'success') - // Reset form positions - setFormData(prev => ({ - ...prev, - x: Math.floor(Math.random() * 400) + 50, - y: Math.floor(Math.random() * 300) + 50 - })) - } else { - showNotification(`Error: ${result.error}`, 'error') - } - } catch (error) { - console.error('Error creating element:', error) - showNotification('Failed to create element', 'error') - } - } - - const createSampleElements = async () => { - const sampleElements = [ - { - type: 'rectangle', - x: 50, - y: 50, - width: 150, - height: 100, - backgroundColor: '#ffeaa7', - strokeColor: '#2d3436' - }, - { - type: 'ellipse', - x: 250, - y: 50, - width: 120, - height: 120, - backgroundColor: '#74b9ff', - strokeColor: '#0984e3' - }, - { - type: 'diamond', - x: 50, - y: 200, - width: 100, - height: 100, - backgroundColor: '#fd79a8', - strokeColor: '#e84393' - }, - { - type: 'text', - x: 250, - y: 220, - text: 'Hello from API!', - fontSize: 20, - strokeColor: '#2d3436' - } - ] - - try { - const response = await fetch('/api/elements/batch', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ elements: sampleElements }) - }) - - const result = await response.json() - - if (result.success) { - showNotification(`${result.count} sample elements created!`, 'success') - } else { - showNotification(`Error: ${result.error}`, 'error') - } - } catch (error) { - console.error('Error creating sample elements:', error) - showNotification('Failed to create sample elements', 'error') } } const clearCanvas = async () => { if (excalidrawAPI) { try { - // First, get all current elements + // Get all current elements and delete them from backend const response = await fetch('/api/elements') const result = await response.json() if (result.success && result.elements) { - // Delete all elements from backend const deletePromises = result.elements.map(element => fetch(`/api/elements/${element.id}`, { method: 'DELETE' }) ) - await Promise.all(deletePromises) - console.log('All elements deleted from backend') } // Clear the frontend canvas excalidrawAPI.updateScene({ elements: [] }) - showNotification('Canvas cleared completely!', 'success') } catch (error) { console.error('Error clearing canvas:', error) // Still clear frontend even if backend fails excalidrawAPI.updateScene({ elements: [] }) - showNotification('Canvas cleared (frontend only)', 'warning') } } } - const refreshElements = async () => { - console.log('Manual refresh requested') - if (excalidrawAPI) { - await loadExistingElements() - showNotification('Elements refreshed!', 'success') - } else { - showNotification('Canvas not ready yet', 'error') - } - } - - const forceReconnectWebSocket = () => { - console.log('Force reconnecting WebSocket') - if (websocketRef.current) { - websocketRef.current.close() - } - setTimeout(connectWebSocket, 100) - } - - const toggleApiPanel = () => { - setApiPanelVisible(!apiPanelVisible) - } - - const handleInputChange = (e) => { - const { name, value } = e.target - setFormData(prev => ({ - ...prev, - [name]: value - })) - } - return (
{/* Header */}
-

Excalidraw POC - Backend API Integration

+

Excalidraw Canvas

{isConnected ? 'Connected' : 'Disconnected'}
-
- Elements: {elementCount} -
- - - {!isConnected && ( - - )}
- {/* API Panel Toggle */} - - - {/* API Panel */} - {apiPanelVisible && ( -
-

Create Element via API

-
-
- - -
- -
-
- - -
-
- - -
-
- -
-
- - -
-
- - -
-
- -
- - -
- -
-
- - -
-
- - -
-
- -
- - -
- - -
-
- )} - {/* Canvas Container */}
setExcalidrawAPI(api)} - onChange={handleExcalidrawChange} initialData={{ elements: [], appState: { @@ -527,17 +185,6 @@ function App() { }} />
- - {/* Notifications */} - {notifications.map(notification => ( -
- {notification.message} -
- ))}
) } diff --git a/package.json b/package.json index a19326f..e5f7658 100644 --- a/package.json +++ b/package.json @@ -4,24 +4,12 @@ "description": "MCP server for Excalidraw", "main": "src/index.js", "type": "module", - "bin": { - "excalidraw-mcp": "./src/cli.js" - }, "scripts": { "start": "node src/index.js", - "dev": "nodemon src/index.js", - "server": "node src/server.js", - "server:dev": "nodemon src/server.js", - "frontend:dev": "vite", - "frontend:build": "vite build", - "frontend:preview": "vite preview", - "build": "npm run frontend:build", - "poc": "concurrently \"npm run server\" \"npm run frontend:dev\"", - "test": "jest", - "lint": "eslint src/**/*.js", - "docker:build": "docker build -t mcp/excalidraw .", - "docker:run": "docker run -p 3000:3000 mcp/excalidraw", - "prepare": "node -e \"try { require('fs').chmodSync('./src/cli.js', '755') } catch(e) { console.log(e) }\"" + "canvas": "node src/server.js", + "build": "vite build", + "dev": "concurrently \"npm run canvas\" \"vite\"", + "production": "npm run build && npm run canvas" }, "dependencies": { "@excalidraw/excalidraw": "^0.18.0", @@ -40,9 +28,6 @@ "devDependencies": { "@vitejs/plugin-react": "^4.6.0", "concurrently": "^9.2.0", - "eslint": "^8.56.0", - "jest": "^29.7.0", - "nodemon": "^3.0.2", "vite": "^6.3.5" }, "keywords": [ diff --git a/src/server.js b/src/server.js index a3d379b..706fc53 100644 --- a/src/server.js +++ b/src/server.js @@ -27,11 +27,8 @@ const wss = new WebSocketServer({ server }); app.use(cors()); app.use(express.json()); -// Serve static files from the build directory for production -// or from the old public directory for development -const staticDir = process.env.NODE_ENV === 'production' - ? path.join(__dirname, '../public/dist') - : path.join(__dirname, '../public'); +// Serve static files from the build directory +const staticDir = path.join(__dirname, '../dist'); app.use(express.static(staticDir)); // WebSocket connections @@ -345,9 +342,7 @@ app.post('/api/elements/batch', (req, res) => { // Serve the frontend app.get('/', (req, res) => { - const htmlFile = process.env.NODE_ENV === 'production' - ? path.join(__dirname, '../public/dist/index.html') - : path.join(__dirname, '../public/index.html'); + const htmlFile = path.join(__dirname, '../dist/index.html'); res.sendFile(htmlFile); }); diff --git a/vite.config.js b/vite.config.js index 14eeede..39f7d38 100644 --- a/vite.config.js +++ b/vite.config.js @@ -4,7 +4,7 @@ import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], build: { - outDir: 'public/dist', + outDir: 'dist', rollupOptions: { input: { main: './frontend/index.html',