241 lines
8.2 KiB
HTML
241 lines
8.2 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Excalidraw Editor</title>
|
|
<style>
|
|
* { margin: 0; padding: 0; }
|
|
html, body { width: 100%; height: 100%; overflow: hidden; }
|
|
#root { width: 100%; height: 100%; }
|
|
.toolbar {
|
|
position: fixed;
|
|
top: 10px;
|
|
left: 10px;
|
|
z-index: 1000;
|
|
display: flex;
|
|
gap: 8px;
|
|
background: rgba(255,255,255,0.95);
|
|
padding: 8px 12px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
}
|
|
.toolbar button, .toolbar a {
|
|
padding: 6px 14px;
|
|
border: none;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
background: #6c5ce7;
|
|
color: white;
|
|
text-decoration: none;
|
|
display: inline-block;
|
|
}
|
|
.toolbar button:hover, .toolbar a:hover { background: #5b4cdb; }
|
|
.toolbar .secondary { background: #ddd; color: #333; }
|
|
.toolbar .secondary:hover { background: #ccc; }
|
|
.toolbar .title {
|
|
font-weight: 600;
|
|
padding: 6px 0;
|
|
color: #333;
|
|
}
|
|
.status {
|
|
position: fixed;
|
|
bottom: 10px;
|
|
right: 10px;
|
|
padding: 6px 12px;
|
|
background: rgba(0,0,0,0.7);
|
|
color: white;
|
|
border-radius: 4px;
|
|
font-size: 12px;
|
|
z-index: 1000;
|
|
opacity: 0;
|
|
transition: opacity 0.3s;
|
|
}
|
|
.status.show { opacity: 1; }
|
|
.loading {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 100%;
|
|
font-size: 1.2rem;
|
|
color: #666;
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
}
|
|
.error { color: #e74c3c; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="toolbar">
|
|
<a href="/" class="secondary">Back to Library</a>
|
|
<span class="title" id="title">Loading...</span>
|
|
<button onclick="saveDrawing()">Save</button>
|
|
</div>
|
|
<div id="root">
|
|
<div class="loading">
|
|
<div>Loading Excalidraw...</div>
|
|
<div id="load-status" style="font-size: 0.9rem; color: #888;"></div>
|
|
</div>
|
|
</div>
|
|
<div id="status" class="status">Saved</div>
|
|
|
|
<script>
|
|
// Get drawing ID from URL path: /draw/{id}
|
|
var pathParts = window.location.pathname.split('/');
|
|
var drawingId = pathParts[pathParts.length - 1] || pathParts[pathParts.length - 2];
|
|
|
|
if (!drawingId) {
|
|
document.getElementById('root').innerHTML = '<div class="loading error">No drawing ID specified</div>';
|
|
throw new Error('No drawing ID');
|
|
}
|
|
|
|
document.getElementById('title').textContent = drawingId;
|
|
document.title = drawingId + ' - Excalidraw';
|
|
|
|
var excalidrawAPI = null;
|
|
var autoSaveTimeout = null;
|
|
|
|
function updateLoadStatus(msg) {
|
|
var el = document.getElementById('load-status');
|
|
if (el) el.textContent = msg;
|
|
console.log('[Excalidraw]', msg);
|
|
}
|
|
|
|
function showStatus(msg) {
|
|
var el = document.getElementById('status');
|
|
el.textContent = msg;
|
|
el.classList.add('show');
|
|
setTimeout(function() { el.classList.remove('show'); }, 2000);
|
|
}
|
|
|
|
async function loadDrawing() {
|
|
try {
|
|
var resp = await fetch('/api/drawings/' + drawingId);
|
|
if (resp.ok) {
|
|
return await resp.json();
|
|
}
|
|
} catch (e) {
|
|
console.log('No existing drawing, starting fresh');
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async function saveDrawing() {
|
|
if (!excalidrawAPI) {
|
|
console.log('Cannot save: excalidrawAPI not ready');
|
|
return;
|
|
}
|
|
|
|
var elements = excalidrawAPI.getSceneElements();
|
|
var appState = excalidrawAPI.getAppState();
|
|
|
|
var data = {
|
|
type: "excalidraw",
|
|
version: 2,
|
|
source: "excalidraw-library",
|
|
elements: elements,
|
|
appState: {
|
|
viewBackgroundColor: appState.viewBackgroundColor,
|
|
gridSize: appState.gridSize
|
|
}
|
|
};
|
|
|
|
try {
|
|
await fetch('/api/drawings/' + drawingId, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data)
|
|
});
|
|
showStatus('Saved');
|
|
} catch (e) {
|
|
showStatus('Save failed!');
|
|
console.error('Save error:', e);
|
|
}
|
|
}
|
|
|
|
function onChange() {
|
|
clearTimeout(autoSaveTimeout);
|
|
autoSaveTimeout = setTimeout(saveDrawing, 2000);
|
|
}
|
|
|
|
// Load scripts dynamically
|
|
function loadScript(src) {
|
|
return new Promise(function(resolve, reject) {
|
|
var script = document.createElement('script');
|
|
script.src = src;
|
|
script.crossOrigin = 'anonymous';
|
|
script.onload = resolve;
|
|
script.onerror = function() { reject(new Error('Failed to load: ' + src)); };
|
|
document.head.appendChild(script);
|
|
});
|
|
}
|
|
|
|
async function init() {
|
|
try {
|
|
updateLoadStatus('Loading React...');
|
|
await loadScript('https://unpkg.com/react@18.2.0/umd/react.production.min.js');
|
|
|
|
updateLoadStatus('Loading ReactDOM...');
|
|
await loadScript('https://unpkg.com/react-dom@18.2.0/umd/react-dom.production.min.js');
|
|
|
|
updateLoadStatus('Loading Excalidraw...');
|
|
await loadScript('https://unpkg.com/@excalidraw/excalidraw@0.17.6/dist/excalidraw.production.min.js');
|
|
|
|
updateLoadStatus('Initializing...');
|
|
|
|
// Verify libraries loaded
|
|
if (!window.React) throw new Error('React not loaded');
|
|
if (!window.ReactDOM) throw new Error('ReactDOM not loaded');
|
|
if (!window.ExcalidrawLib) throw new Error('ExcalidrawLib not loaded');
|
|
|
|
console.log('React version:', React.version);
|
|
console.log('ExcalidrawLib:', Object.keys(ExcalidrawLib));
|
|
|
|
updateLoadStatus('Loading drawing data...');
|
|
var initialData = await loadDrawing();
|
|
|
|
updateLoadStatus('Rendering Excalidraw...');
|
|
|
|
// Create Excalidraw component
|
|
function App() {
|
|
return React.createElement(ExcalidrawLib.Excalidraw, {
|
|
initialData: initialData ? {
|
|
elements: initialData.elements || [],
|
|
appState: initialData.appState || {}
|
|
} : undefined,
|
|
excalidrawAPI: function(api) {
|
|
excalidrawAPI = api;
|
|
console.log('Excalidraw API ready');
|
|
},
|
|
onChange: onChange
|
|
});
|
|
}
|
|
|
|
var root = ReactDOM.createRoot(document.getElementById('root'));
|
|
root.render(React.createElement(App));
|
|
|
|
console.log('Excalidraw rendered successfully');
|
|
|
|
} catch (e) {
|
|
console.error('Init error:', e);
|
|
document.getElementById('root').innerHTML =
|
|
'<div class="loading error">' +
|
|
'<div>Failed to load Excalidraw</div>' +
|
|
'<div style="font-size:0.9rem">' + e.message + '</div>' +
|
|
'</div>';
|
|
}
|
|
}
|
|
|
|
// Keyboard shortcut: Ctrl+S to save
|
|
document.addEventListener('keydown', function(e) {
|
|
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
|
e.preventDefault();
|
|
saveDrawing();
|
|
}
|
|
});
|
|
|
|
init();
|
|
</script>
|
|
</body>
|
|
</html>
|