fix text label bugs
This commit is contained in:
@@ -29,6 +29,14 @@ For the most stable experience, we recommend using the local development setup.
|
||||
- **🔄 WebSocket Updates**: Live synchronization across multiple connected clients
|
||||
- **🏗️ Production Ready**: Clean, minimal UI suitable for end users
|
||||
|
||||
## 🎥 Demo Video
|
||||
|
||||
> **See MCP Excalidraw in Action!**
|
||||
|
||||
[](https://www.youtube.com/watch?v=YOUR_VIDEO_ID)
|
||||
|
||||
*Watch how AI agents create and manipulate diagrams in real-time on the live canvas*
|
||||
|
||||
## 🏛️ Architecture Overview
|
||||
|
||||
```
|
||||
|
||||
+21
-5
@@ -2,6 +2,17 @@ import React, { useState, useEffect, useRef } from 'react'
|
||||
import { Excalidraw, convertToExcalidrawElements } from '@excalidraw/excalidraw'
|
||||
import '@excalidraw/excalidraw/index.css'
|
||||
|
||||
// Helper function to clean elements for Excalidraw
|
||||
const cleanElementForExcalidraw = (element) => {
|
||||
const {
|
||||
createdAt,
|
||||
updatedAt,
|
||||
version,
|
||||
...cleanElement
|
||||
} = element;
|
||||
return cleanElement;
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [excalidrawAPI, setExcalidrawAPI] = useState(null)
|
||||
const [isConnected, setIsConnected] = useState(false)
|
||||
@@ -35,7 +46,8 @@ function App() {
|
||||
const result = await response.json()
|
||||
|
||||
if (result.success && result.elements && result.elements.length > 0) {
|
||||
const convertedElements = convertToExcalidrawElements(result.elements)
|
||||
const cleanedElements = result.elements.map(cleanElementForExcalidraw)
|
||||
const convertedElements = convertToExcalidrawElements(cleanedElements)
|
||||
excalidrawAPI.updateScene({ elements: convertedElements })
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -96,19 +108,22 @@ function App() {
|
||||
switch (data.type) {
|
||||
case 'initial_elements':
|
||||
if (data.elements && data.elements.length > 0) {
|
||||
const convertedElements = convertToExcalidrawElements(data.elements)
|
||||
const cleanedElements = data.elements.map(cleanElementForExcalidraw)
|
||||
const convertedElements = convertToExcalidrawElements(cleanedElements)
|
||||
excalidrawAPI.updateScene({ elements: convertedElements })
|
||||
}
|
||||
break
|
||||
|
||||
case 'element_created':
|
||||
const newElement = convertToExcalidrawElements([data.element])
|
||||
const cleanedNewElement = cleanElementForExcalidraw(data.element)
|
||||
const newElement = convertToExcalidrawElements([cleanedNewElement])
|
||||
const updatedElementsAfterCreate = [...currentElements, ...newElement]
|
||||
excalidrawAPI.updateScene({ elements: updatedElementsAfterCreate })
|
||||
break
|
||||
|
||||
case 'element_updated':
|
||||
const convertedUpdatedElement = convertToExcalidrawElements([data.element])[0]
|
||||
const cleanedUpdatedElement = cleanElementForExcalidraw(data.element)
|
||||
const convertedUpdatedElement = convertToExcalidrawElements([cleanedUpdatedElement])[0]
|
||||
const updatedElements = currentElements.map(el =>
|
||||
el.id === data.element.id ? convertedUpdatedElement : el
|
||||
)
|
||||
@@ -121,7 +136,8 @@ function App() {
|
||||
break
|
||||
|
||||
case 'elements_batch_created':
|
||||
const batchElements = convertToExcalidrawElements(data.elements)
|
||||
const cleanedBatchElements = data.elements.map(cleanElementForExcalidraw)
|
||||
const batchElements = convertToExcalidrawElements(cleanedBatchElements)
|
||||
const updatedElementsAfterBatch = [...currentElements, ...batchElements]
|
||||
excalidrawAPI.updateScene({ elements: updatedElementsAfterBatch })
|
||||
break
|
||||
|
||||
+36
-17
@@ -397,6 +397,18 @@ const server = new Server(
|
||||
}
|
||||
);
|
||||
|
||||
// Helper function to convert text property to label format for Excalidraw
|
||||
function convertTextToLabel(element) {
|
||||
const { text, ...rest } = element;
|
||||
if (text) {
|
||||
return {
|
||||
...rest,
|
||||
label: { text }
|
||||
};
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
// Set up request handler for tool calls
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
try {
|
||||
@@ -417,23 +429,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
version: 1
|
||||
};
|
||||
|
||||
// Store locally (MCP server storage)
|
||||
elements.set(id, element);
|
||||
// Convert text to label format for Excalidraw
|
||||
const excalidrawElement = convertTextToLabel(element);
|
||||
|
||||
// Store the converted element locally (MCP server storage)
|
||||
elements.set(id, excalidrawElement);
|
||||
|
||||
// Sync to canvas (Express server + WebSocket broadcast)
|
||||
const canvasElement = await createElementOnCanvas(element);
|
||||
const canvasElement = await createElementOnCanvas(excalidrawElement);
|
||||
|
||||
const result = canvasElement || element;
|
||||
logger.info('Element created via MCP and synced to canvas', {
|
||||
id: result.id,
|
||||
type: result.type,
|
||||
id: excalidrawElement.id,
|
||||
type: excalidrawElement.type,
|
||||
synced: !!canvasElement
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: `Element created successfully!\n\n${JSON.stringify(result, null, 2)}\n\n${canvasElement ? '✅ Synced to canvas' : '⚠️ Canvas sync failed (element still created locally)'}`
|
||||
text: `Element created successfully!\n\n${JSON.stringify(excalidrawElement, null, 2)}\n\n${canvasElement ? '✅ Synced to canvas' : '⚠️ Canvas sync failed (element still created locally)'}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
@@ -457,22 +471,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
version: existingElement.version + 1
|
||||
};
|
||||
|
||||
// Store locally (MCP server storage)
|
||||
elements.set(id, updatedElement);
|
||||
// Convert text to label format for Excalidraw
|
||||
const excalidrawElement = convertTextToLabel(updatedElement);
|
||||
|
||||
// Store the converted element locally (MCP server storage)
|
||||
elements.set(id, excalidrawElement);
|
||||
|
||||
// Sync to canvas (Express server + WebSocket broadcast)
|
||||
const canvasElement = await updateElementOnCanvas(updatedElement);
|
||||
const canvasElement = await updateElementOnCanvas(excalidrawElement);
|
||||
|
||||
const result = canvasElement || updatedElement;
|
||||
logger.info('Element updated via MCP and synced to canvas', {
|
||||
id: result.id,
|
||||
id: excalidrawElement.id,
|
||||
synced: !!canvasElement
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: `Element updated successfully!\n\n${JSON.stringify(result, null, 2)}\n\n${canvasElement ? '✅ Synced to canvas' : '⚠️ Canvas sync failed (element still updated locally)'}`
|
||||
text: `Element updated successfully!\n\n${JSON.stringify(excalidrawElement, null, 2)}\n\n${canvasElement ? '✅ Synced to canvas' : '⚠️ Canvas sync failed (element still updated locally)'}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
@@ -668,9 +684,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
version: 1
|
||||
};
|
||||
|
||||
// Store locally (MCP server storage)
|
||||
elements.set(id, element);
|
||||
createdElements.push(element);
|
||||
// Convert text to label format for Excalidraw
|
||||
const excalidrawElement = convertTextToLabel(element);
|
||||
|
||||
// Store the converted element locally (MCP server storage)
|
||||
elements.set(id, excalidrawElement);
|
||||
createdElements.push(excalidrawElement);
|
||||
}
|
||||
|
||||
// Sync all elements to canvas at once (Express server + WebSocket broadcast)
|
||||
@@ -678,7 +697,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
|
||||
const result = {
|
||||
success: true,
|
||||
elements: canvasElements || createdElements,
|
||||
elements: createdElements,
|
||||
count: createdElements.length,
|
||||
syncedToCanvas: !!canvasElements
|
||||
};
|
||||
|
||||
@@ -30,6 +30,8 @@ app.use(express.json());
|
||||
// Serve static files from the build directory
|
||||
const staticDir = path.join(__dirname, '../dist');
|
||||
app.use(express.static(staticDir));
|
||||
// Also serve frontend assets
|
||||
app.use(express.static(path.join(__dirname, '../dist/frontend')));
|
||||
|
||||
// WebSocket connections
|
||||
const clients = new Set();
|
||||
@@ -79,6 +81,9 @@ const CreateElementSchema = z.object({
|
||||
roughness: z.number().optional(),
|
||||
opacity: z.number().optional(),
|
||||
text: z.string().optional(),
|
||||
label: z.object({
|
||||
text: z.string()
|
||||
}).optional(),
|
||||
fontSize: z.number().optional(),
|
||||
fontFamily: z.string().optional()
|
||||
});
|
||||
@@ -96,6 +101,9 @@ const UpdateElementSchema = z.object({
|
||||
roughness: z.number().optional(),
|
||||
opacity: z.number().optional(),
|
||||
text: z.string().optional(),
|
||||
label: z.object({
|
||||
text: z.string()
|
||||
}).optional(),
|
||||
fontSize: z.number().optional(),
|
||||
fontFamily: z.string().optional()
|
||||
});
|
||||
|
||||
+1
-2
@@ -7,8 +7,7 @@ export const EXCALIDRAW_ELEMENT_TYPES = {
|
||||
TEXT: 'text',
|
||||
LABEL: 'label',
|
||||
FREEDRAW: 'freedraw',
|
||||
LINE: 'line',
|
||||
ARROW_LABEL: 'arrowLabel'
|
||||
LINE: 'line'
|
||||
};
|
||||
|
||||
// In-memory storage for Excalidraw elements
|
||||
|
||||
Reference in New Issue
Block a user