|
2 | 2 | <html lang="en"> |
3 | 3 | <head> |
4 | 4 | <script>fetch('https://api.countapi.xyz/hit/rmkr-dev.github.io/draw');</script> |
5 | | - |
6 | 5 | <meta charset="UTF-8"> |
7 | 6 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
8 | 7 | <title>Architect Draw | RMKR Dev</title> |
9 | 8 |
|
10 | | - <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath fill='%23FFC300' d='M448 96c0-35.3-28.7-64-64-64H128C92.7 32 64 60.7 64 96v320c0 35.3 28.7 64 64 64h256c35.3 0 64-28.7 64-64V96zM128 80h256c8.8 0 16 7.2 16 16v24H112V96c0-8.8 7.2-16 16-16zm256 352H128c-8.8 0-16-7.2-16-16v-24h288v24c0 8.8-7.2 16-16 16z'/%3E%3C/svg%3E"> |
| 9 | + <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> |
| 10 | + |
11 | 11 | <script src="https://cdn.tailwindcss.com"></script> |
12 | | - <link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&family=Fira+Code:wght@300..700&display=swap" rel="stylesheet"> |
| 12 | + <link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet"> |
13 | 13 | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" /> |
14 | 14 |
|
15 | | - <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script> |
16 | | - |
17 | 15 | <style> |
18 | 16 | :root { |
19 | 17 | --color-bg: #0C1524; --color-card: #18233C; |
20 | 18 | --color-accent: #FFC300; --color-text: #F0F4F8; |
21 | 19 | --color-muted: #A0AEC0; --color-line: #2D3748; |
22 | 20 | } |
23 | | - body { font-family: 'Inter', sans-serif; background-color: var(--color-bg); color: var(--color-text); } |
24 | | - .wrap { max-width: 1200px; margin: 0 auto; padding: 2rem; } |
| 21 | + body { font-family: 'Inter', sans-serif; background-color: var(--color-bg); color: var(--color-text); overflow: hidden; } |
| 22 | + .wrap { max-width: 1400px; margin: 0 auto; padding: 2rem; height: 100vh; display: flex; flex-direction: column; } |
25 | 23 |
|
26 | | - textarea { |
27 | | - font-family: 'Fira Code', monospace; |
28 | | - background: #080E1A !important; |
29 | | - color: #4ade80 !important; /* Code Green */ |
30 | | - resize: none; |
31 | | - outline: none; |
32 | | - border: 1px solid var(--color-line); |
| 24 | + /* Whiteboard Canvas */ |
| 25 | + #whiteboard { |
| 26 | + background-color: #F0F4F8; /* White/Light Gray background for drawing */ |
| 27 | + border-radius: 12px; |
| 28 | + cursor: crosshair; |
| 29 | + box-shadow: 0 10px 30px rgba(0,0,0,0.5); |
| 30 | + flex-grow: 1; |
| 31 | + width: 100%; |
33 | 32 | } |
34 | | - |
35 | | - #diagram-output { |
36 | | - background: rgba(255,255,255,0.02); |
37 | | - border: 1px dashed var(--color-line); |
| 33 | + |
| 34 | + /* Tool Palette */ |
| 35 | + .tool-palette { |
| 36 | + background: var(--color-card); |
| 37 | + border: 1px solid var(--color-line); |
38 | 38 | border-radius: 12px; |
39 | | - min-height: 500px; |
| 39 | + padding: 10px; |
40 | 40 | display: flex; |
41 | | - justify-content: center; |
| 41 | + gap: 10px; |
| 42 | + margin-bottom: 20px; |
42 | 43 | align-items: center; |
43 | | - overflow: auto; |
44 | 44 | } |
45 | 45 |
|
46 | | - /* Customizing Mermaid SVG Colors */ |
47 | | - .mermaid svg { max-width: 100% !important; height: auto !important; } |
| 46 | + .tool-btn { |
| 47 | + width: 40px; height: 40px; |
| 48 | + display: flex; align-items: center; justify-content: center; |
| 49 | + border-radius: 8px; border: 1px solid transparent; |
| 50 | + color: var(--color-muted); |
| 51 | + transition: all 0.2s; |
| 52 | + } |
| 53 | + .tool-btn:hover { background: rgba(255,195,0,0.1); color: var(--color-accent); } |
| 54 | + .tool-btn.active { background: var(--color-accent); color: var(--color-bg); font-weight: bold; border-color: var(--color-accent); } |
| 55 | + |
| 56 | + .color-dot { |
| 57 | + width: 20px; height: 20px; border-radius: 50%; |
| 58 | + cursor: pointer; border: 2px solid transparent; |
| 59 | + transition: all 0.2s; |
| 60 | + } |
| 61 | + .color-dot:hover { transform: scale(1.1); } |
| 62 | + .color-dot.active { border-color: #fff; box-shadow: 0 0 5px rgba(255,255,255,0.5); } |
| 63 | + |
| 64 | + .btn-gold { background: var(--color-accent); color: var(--color-bg); padding: 8px 16px; border-radius: 8px; font-weight: bold; font-size: 0.8rem; } |
48 | 65 | </style> |
49 | 66 | </head> |
50 | 67 | <body> |
51 | 68 |
|
52 | 69 | <div class="wrap"> |
53 | | - <header class="flex justify-between items-center mb-8 border-b border-[--color-line] pb-4"> |
| 70 | + <header class="flex justify-between items-center mb-6 border-b border-[--color-line] pb-4"> |
54 | 71 | <div> |
55 | | - <h1 class="text-2xl font-bold italic tracking-tighter">ARCHITECT<span class="text-[--color-accent] not-italic">DRAW</span></h1> |
56 | | - <p class="text-xs text-[--color-muted] font-bold uppercase tracking-widest mt-1">Diagrams as Code</p> |
57 | | - </div> |
58 | | - <div class="flex gap-4"> |
59 | | - <button onclick="downloadSVG()" class="text-xs font-bold text-[--color-accent] hover:underline uppercase">Export SVG</button> |
60 | | - <a href="../" class="text-sm font-bold text-[--color-accent] hover:underline"><i class="fa-solid fa-house mr-1"></i> HUB</a> |
| 72 | + <h1 class="text-2xl font-bold tracking-tighteritalic">ARCHITECT<span class="text-[--color-accent] not-italic">DRAW</span></h1> |
| 73 | + <p class="text-xs text-[--color-muted] font-bold uppercase tracking-widest mt-1">Client-side Whiteboard</p> |
61 | 74 | </div> |
| 75 | + <a href="../" class="bg-[--color-card] p-3 rounded-xl border border-[--color-line] hover:border-[--color-accent] transition"> |
| 76 | + <i class="fa-solid fa-house-user"></i> |
| 77 | + </a> |
62 | 78 | </header> |
63 | 79 |
|
64 | | - <div class="grid grid-cols-1 lg:grid-cols-12 gap-6 h-[75vh]"> |
65 | | - <div class="lg:col-span-4 flex flex-col gap-4"> |
66 | | - <div class="bg-[--color-card] p-4 rounded-xl border border-[--color-line] flex-grow flex flex-col"> |
67 | | - <label class="text-[10px] font-black text-[--color-muted] uppercase mb-2">Mermaid Script</label> |
68 | | - <textarea id="editor" class="w-full flex-grow p-4 rounded-lg text-xs leading-relaxed" spellcheck="false">graph TD |
69 | | - A[Client] -->|Request| B(API Gateway) |
70 | | - B --> C{Authorizer} |
71 | | - C -->|Allow| D[Service A] |
72 | | - C -->|Deny| E[403 Forbidden] |
73 | | - D --> F[(Database)]</textarea> |
74 | | - <p class="text-[9px] text-gray-500 mt-3 uppercase font-mono italic">Live rendering enabled</p> |
75 | | - </div> |
| 80 | + <div class="tool-palette flex flex-wrap"> |
| 81 | + <div class="flex gap-1 border-r border-[--color-line] pr-4"> |
| 82 | + <button onclick="setTool('pencil')" id="btn-pencil" class="tool-btn active"><i class="fa-solid fa-pencil"></i></button> |
| 83 | + <button onclick="setTool('rect')" id="btn-rect" class="tool-btn"><i class="fa-regular fa-square"></i></button> |
| 84 | + <button onclick="setTool('circle')" id="btn-circle" class="tool-btn"><i class="fa-regular fa-circle"></i></button> |
| 85 | + <button onclick="setTool('line')" id="btn-line" class="tool-btn"><i class="fa-solid fa-grip-lines"></i></button> |
| 86 | + </div> |
| 87 | + |
| 88 | + <div class="flex gap-2 border-r border-[--color-line] pr-4 ml-2"> |
| 89 | + <div onclick="setColor('#0C1524')" style="background:#0C1524" class="color-dot active"></div> |
| 90 | + <div onclick="setColor('#ef4444')" style="background:#ef4444" class="color-dot"></div> |
| 91 | + <div onclick="setColor('#22c55e')" style="background:#22c55e" class="color-dot"></div> |
| 92 | + <div onclick="setColor('#3b82f6')" style="background:#3b82f6" class="color-dot"></div> |
| 93 | + <div onclick="setColor('#FFC300')" style="background:#FFC300" class="color-dot"></div> |
76 | 94 | </div> |
77 | 95 |
|
78 | | - <div class="lg:col-span-8 bg-[--color-card] rounded-xl border border-[--color-line] p-6 flex flex-col overflow-hidden"> |
79 | | - <div class="flex justify-between items-center mb-4"> |
80 | | - <span class="text-[10px] font-black text-[--color-muted] uppercase">Live Preview</span> |
81 | | - <div class="flex gap-2"> |
82 | | - <button onclick="updateDiagram('graph TD\n A-->B')" class="text-[9px] bg-[--color-line] px-2 py-1 rounded text-white">Flow</button> |
83 | | - <button onclick="updateDiagram('sequenceDiagram\n Alice->>Bob: Hello')" class="text-[9px] bg-[--color-line] px-2 py-1 rounded text-white">Sequence</button> |
84 | | - </div> |
85 | | - </div> |
86 | | - <div id="diagram-output" class="mermaid"> |
87 | | - </div> |
| 96 | + <button onclick="clearCanvas()" class="text-xs font-bold text-red-400/50 hover:text-red-400 ml-2 uppercase">Clear</button> |
| 97 | + |
| 98 | + <div class="ml-auto flex gap-3"> |
| 99 | + <button onclick="exportJPG()" class="btn-gold uppercase">Export JPG</button> |
| 100 | + <button onclick="exportPDF()" class="btn-gold uppercase">Export PDF</button> |
88 | 101 | </div> |
89 | 102 | </div> |
| 103 | + |
| 104 | + <canvas id="whiteboard"></canvas> |
90 | 105 | </div> |
91 | 106 |
|
92 | 107 | <script> |
93 | | - // Initialize Mermaid with Dark Theme |
94 | | - mermaid.initialize({ |
95 | | - startOnLoad: false, |
96 | | - theme: 'dark', |
97 | | - themeVariables: { |
98 | | - primaryColor: '#FFC300', |
99 | | - primaryTextColor: '#0C1524', |
100 | | - primaryBorderColor: '#FFC300', |
101 | | - lineColor: '#A0AEC0', |
102 | | - secondaryColor: '#18233C', |
103 | | - tertiaryColor: '#0C1524' |
| 108 | + const canvas = document.getElementById('whiteboard'); |
| 109 | + const ctx = canvas.getContext('2d'); |
| 110 | + |
| 111 | + // Set canvas dimensions based on container |
| 112 | + function resizeCanvas() { |
| 113 | + // Create an offline buffer to save the state |
| 114 | + const buffer = document.createElement('canvas'); |
| 115 | + buffer.width = canvas.width; |
| 116 | + buffer.height = canvas.height; |
| 117 | + buffer.getContext('2d').drawImage(canvas, 0, 0); |
| 118 | + |
| 119 | + canvas.width = canvas.parentElement.offsetWidth - 32; // Include padding |
| 120 | + canvas.height = canvas.parentElement.offsetHeight - 200; // Offset for headers |
| 121 | + |
| 122 | + // Restore state |
| 123 | + ctx.drawImage(buffer, 0, 0); |
| 124 | + } |
| 125 | + |
| 126 | + // Whiteboard State |
| 127 | + let drawing = false; |
| 128 | + let currentTool = 'pencil'; |
| 129 | + let currentColor = '#0C1524'; |
| 130 | + let startX, startY; |
| 131 | + let savedCanvasImage; |
| 132 | + |
| 133 | + function setTool(tool) { |
| 134 | + currentTool = tool; |
| 135 | + document.querySelectorAll('.tool-btn').forEach(b => b.classList.remove('active')); |
| 136 | + document.getElementById('btn-'+tool).classList.add('active'); |
| 137 | + } |
| 138 | + |
| 139 | + function setColor(color) { |
| 140 | + currentColor = color; |
| 141 | + document.querySelectorAll('.color-dot').forEach(b => b.classList.remove('active')); |
| 142 | + // Find the color dot by its style background and add active class |
| 143 | + event.target.classList.add('active'); |
| 144 | + } |
| 145 | + |
| 146 | + function clearCanvas() { |
| 147 | + ctx.clearRect(0, 0, canvas.width, canvas.height); |
| 148 | + } |
| 149 | + |
| 150 | + // Core Drawing Logic |
| 151 | + function startDrawing(e) { |
| 152 | + drawing = true; |
| 153 | + startX = e.offsetX; |
| 154 | + startY = e.offsetY; |
| 155 | + |
| 156 | + if (currentTool === 'pencil') { |
| 157 | + ctx.beginPath(); |
| 158 | + ctx.moveTo(startX, startY); |
| 159 | + } else { |
| 160 | + // Save the state for shape preview |
| 161 | + savedCanvasImage = ctx.getImageData(0, 0, canvas.width, canvas.height); |
104 | 162 | } |
105 | | - }); |
106 | | - |
107 | | - const editor = document.getElementById('editor'); |
108 | | - const output = document.getElementById('diagram-output'); |
109 | | - |
110 | | - async function render() { |
111 | | - const code = editor.value; |
112 | | - try { |
113 | | - output.innerHTML = ''; // Clear old |
114 | | - const { svg } = await mermaid.render('mermaid-svg', code); |
115 | | - output.innerHTML = svg; |
116 | | - } catch (err) { |
117 | | - // Silently wait for user to finish typing valid Mermaid syntax |
| 163 | + } |
| 164 | + |
| 165 | + function stopDrawing() { |
| 166 | + drawing = false; |
| 167 | + ctx.beginPath(); // Reset path for pencil |
| 168 | + } |
| 169 | + |
| 170 | + function draw(e) { |
| 171 | + if (!drawing) return; |
| 172 | + const x = e.offsetX; |
| 173 | + const y = e.offsetY; |
| 174 | + |
| 175 | + ctx.lineWidth = currentTool === 'pencil' ? 3 : 2; |
| 176 | + ctx.lineCap = 'round'; |
| 177 | + ctx.strokeStyle = currentColor; |
| 178 | + |
| 179 | + if (currentTool === 'pencil') { |
| 180 | + ctx.lineTo(x, y); |
| 181 | + ctx.stroke(); |
| 182 | + } else { |
| 183 | + // Shape Preview Logic |
| 184 | + ctx.putImageData(savedCanvasImage, 0, 0); // Restore original state |
| 185 | + ctx.beginPath(); |
| 186 | + |
| 187 | + if (currentTool === 'rect') { |
| 188 | + ctx.rect(startX, startY, x - startX, y - startY); |
| 189 | + } else if (currentTool === 'circle') { |
| 190 | + const radius = Math.sqrt(Math.pow(x - startX, 2) + Math.pow(y - startY, 2)); |
| 191 | + ctx.arc(startX, startY, radius, 0, 2 * Math.PI); |
| 192 | + } else if (currentTool === 'line') { |
| 193 | + ctx.moveTo(startX, startY); |
| 194 | + ctx.lineTo(x, y); |
| 195 | + } |
| 196 | + ctx.stroke(); |
118 | 197 | } |
119 | 198 | } |
120 | 199 |
|
121 | | - function updateDiagram(template) { |
122 | | - editor.value = template; |
123 | | - render(); |
| 200 | + // Export Logic |
| 201 | + function exportJPG() { |
| 202 | + // Create a temporary canvas to add the solid background |
| 203 | + const exportCanvas = document.createElement('canvas'); |
| 204 | + exportCanvas.width = canvas.width; |
| 205 | + exportCanvas.height = canvas.height; |
| 206 | + const eCtx = exportCanvas.getContext('2d'); |
| 207 | + |
| 208 | + // Add solid background (JPG doesn't support transparency) |
| 209 | + eCtx.fillStyle = "#F0F4F8"; |
| 210 | + eCtx.fillRect(0, 0, canvas.width, canvas.height); |
| 211 | + eCtx.drawImage(canvas, 0, 0); |
| 212 | + |
| 213 | + const link = document.createElement('a'); |
| 214 | + link.download = 'whiteboard-sketch.jpg'; |
| 215 | + link.href = exportCanvas.toDataURL('image/jpeg', 0.9); |
| 216 | + link.click(); |
124 | 217 | } |
125 | 218 |
|
126 | | - function downloadSVG() { |
127 | | - const svgData = document.getElementById('mermaid-svg').outerHTML; |
128 | | - const svgBlob = new Blob([svgData], {type:"image/svg+xml;charset=utf-8"}); |
129 | | - const svgUrl = URL.createObjectURL(svgBlob); |
130 | | - const downloadLink = document.createElement("a"); |
131 | | - downloadLink.href = svgUrl; |
132 | | - downloadLink.download = "architecture-diagram.svg"; |
133 | | - document.body.appendChild(downloadLink); |
134 | | - downloadLink.click(); |
135 | | - document.body.removeChild(downloadLink); |
| 219 | + function exportPDF() { |
| 220 | + const { jsPDF } = window.jspdf; |
| 221 | + const doc = new jsPDF('l', 'px', [canvas.width, canvas.height]); |
| 222 | + |
| 223 | + // Jspdf works best with base64 images |
| 224 | + const imgData = canvas.toDataURL('image/png'); |
| 225 | + doc.addImage(imgData, 'PNG', 0, 0, canvas.width, canvas.height); |
| 226 | + doc.save('whiteboard-sketch.pdf'); |
136 | 227 | } |
137 | 228 |
|
138 | | - // Debounced listener |
139 | | - let timeout = null; |
140 | | - editor.addEventListener('input', () => { |
141 | | - clearTimeout(timeout); |
142 | | - timeout = setTimeout(render, 500); |
143 | | - }); |
| 229 | + // Event Listeners |
| 230 | + canvas.addEventListener('mousedown', startDrawing); |
| 231 | + canvas.addEventListener('mousemove', draw); |
| 232 | + canvas.addEventListener('mouseup', stopDrawing); |
| 233 | + canvas.addEventListener('mouseout', stopDrawing); |
| 234 | + |
| 235 | + window.addEventListener('load', resizeCanvas); |
| 236 | + window.addEventListener('resize', resizeCanvas); |
144 | 237 |
|
145 | | - // Initial load |
146 | | - window.onload = render; |
147 | 238 | </script> |
148 | 239 | </body> |
149 | 240 | </html> |
0 commit comments