Jhonathan-jsa/schemaflow-visual-postgresql-maestro
0
1```html2<!DOCTYPE html>3<html lang="en">4<head>5 <meta charset="UTF-8">6 <meta name="viewport" content="width=device-width, initial-scale=1.0">7 <title>Database Designer</title>8 <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">9 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">10 <style>11 #designer-area {12 background-image: radial-gradient(#e5e7eb 1px, transparent 1px);13 background-size: 20px 20px;14 min-height: 80vh;15 position: relative;16 overflow: auto;17 }18 .table-card {19 width: 250px;20 position: absolute;21 background: white;22 border-radius: 5px;23 box-shadow: 0 2px 5px rgba(0,0,0,0.1);24 cursor: move;25 }26 .table-header {27 background: #0d6efd;28 color: white;29 padding: 8px 12px;30 border-radius: 5px 5px 0 0;31 }32 .table-column {33 padding: 5px 12px;34 border-bottom: 1px solid #eee;35 font-size: 14px;36 }37 .relationship {38 position: absolute;39 height: 2px;40 background: #0d6efd;41 z-index: -1;42 }43 .relationship::after {44 content: '';45 position: absolute;46 right: 0;47 top: -4px;48 width: 0;49 height: 0;50 border-left: 8px solid #0d6efd;51 border-top: 5px solid transparent;52 border-bottom: 5px solid transparent;53 }54 .toolbox-item {55 padding: 8px;56 margin-bottom: 5px;57 background: #f8f9fa;58 border-radius: 4px;59 cursor: pointer;60 }61 .toolbox-item:hover {62 background: #e9ecef;63 }64 </style>65</head>66<body>67 <nav class="navbar navbar-expand-lg navbar-dark bg-primary">68 <div class="container-fluid">69 <a class="navbar-brand" href="#">70 <i class="bi bi-database"></i> Database Designer71 </a>72 <div class="d-flex">73 <button class="btn btn-light me-2" id="export-btn">74 <i class="bi bi-download"></i> Export SQL75 </button>76 <button class="btn btn-light" id="clear-btn">77 <i class="bi bi-trash"></i> Clear78 </button>79 </div>80 </div>81 </nav>82 83 <div class="container-fluid mt-3">84 <div class="row">85 <div class="col-md-2">86 <div class="card">87 <div class="card-header">88 <i class="bi bi-tools"></i> Toolbox89 </div>90 <div class="card-body">91 <div class="toolbox-item" draggable="true" data-type="table">92 <i class="bi bi-table"></i> Table93 </div>94 <div class="toolbox-item" draggable="true" data-type="relationship">95 <i class="bi bi-arrow-left-right"></i> Relationship96 </div>97 </div>98 </div>99 </div>100 <div class="col-md-8">101 <div class="card">102 <div class="card-header">103 <i class="bi bi-pencil-square"></i> Designer104 </div>105 <div class="card-body p-0">106 <div id="designer-area"></div>107 </div>108 </div>109 </div>110 <div class="col-md-2">111 <div class="card">112 <div class="card-header">113 <i class="bi bi-gear"></i> Properties114 </div>115 <div class="card-body" id="properties-panel">116 <p class="text-muted">Select an element to edit its properties</p>117 </div>118 </div>119 </div>120 </div>121 </div>122 123 <div class="modal fade" id="tableModal" tabindex="-1">124 <div class="modal-dialog">125 <div class="modal-content">126 <div class="modal-header">127 <h5 class="modal-title">Table Properties</h5>128 <button type="button" class="btn-close" data-bs-dismiss="modal"></button>129 </div>130 <div class="modal-body">131 <div class="mb-3">132 <label class="form-label">Table Name</label>133 <input type="text" class="form-control" id="table-name">134 </div>135 <div class="mb-3">136 <label class="form-label">Columns</label>137 <div id="columns-container"></div>138 <button class="btn btn-sm btn-primary mt-2" id="add-column-btn">139 <i class="bi bi-plus"></i> Add Column140 </button>141 </div>142 </div>143 <div class="modal-footer">144 <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>145 <button type="button" class="btn btn-primary" id="save-table-btn">Save</button>146 </div>147 </div>148 </div>149 </div>150 151 <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>152 <script>153 document.addEventListener('DOMContentLoaded', function() {154 const designerArea = document.getElementById('designer-area');155 let selectedTable = null;156 let tables = [];157 let relationships = [];158 let isCreatingRelationship = false;159 let relationshipStart = null;160 161 // Initialize drag from toolbox162 document.querySelectorAll('.toolbox-item').forEach(item => {163 item.addEventListener('dragstart', function(e) {164 e.dataTransfer.setData('type', this.getAttribute('data-type'));165 });166 });167 168 // Handle drop on designer area169 designerArea.addEventListener('dragover', function(e) {170 e.preventDefault();171 });172 173 designerArea.addEventListener('drop', function(e) {174 e.preventDefault();175 const type = e.dataTransfer.getData('type');176 const rect = designerArea.getBoundingClientRect();177 const x = e.clientX - rect.left;178 const y = e.clientY - rect.top;179 180 if (type === 'table') {181 createTable(x, y);182 } else if (type === 'relationship') {183 startCreatingRelationship(x, y);184 }185 });186 187 // Create a new table188 function createTable(x, y) {189 const tableId = 'table-' + Date.now();190 const table = {191 id: tableId,192 name: 'new_table',193 x: x,194 y: y,195 columns: [196 { name: 'id', type: 'INT', pk: true }197 ]198 };199 200 tables.push(table);201 renderTable(table);202 showTableModal(table);203 }204 205 // Render a table on the designer206 function renderTable(table) {207 let tableElement = document.getElementById(table.id);208 209 if (!tableElement) {210 tableElement = document.createElement('div');211 tableElement.id = table.id;212 tableElement.className = 'table-card';213 tableElement.style.left = table.x + 'px';214 tableElement.style.top = table.y + 'px';215 designerArea.appendChild(tableElement);216 }217 218 tableElement.innerHTML = `219 <div class="table-header d-flex justify-content-between">220 <span>${table.name}</span>221 <div>222 <i class="bi bi-pencil edit-btn" data-id="${table.id}"></i>223 <i class="bi bi-x delete-btn" data-id="${table.id}"></i>224 </div>225 </div>226 ${table.columns.map(col => `227 <div class="table-column d-flex justify-content-between">228 <span>${col.name}</span>229 <span class="text-muted">${col.type}${col.pk ? ' <i class="bi bi-key"></i>' : ''}</span>230 </div>231 `).join('')}232 `;233 234 // Make table draggable235 tableElement.addEventListener('mousedown', function(e) {236 if (e.target.classList.contains('edit-btn')) {237 e.preventDefault();238 const tableId = e.target.getAttribute('data-id');239 showTableModal(tables.find(t => t.id === tableId));240 return;241 }242 243 if (e.target.classList.contains('delete-btn')) {244 e.preventDefault();245 const tableId = e.target.getAttribute('data-id');246 deleteTable(tableId);247 return;248 }249 250 if (isCreatingRelationship) {251 relationshipStart = table.id;252 return;253 }254 255 const startX = e.clientX;256 const startY = e.clientY;257 const startLeft = parseInt(tableElement.style.left);258 const startTop = parseInt(tableElement.style.top);259 260 function moveHandler(e) {261 const dx = e.clientX - startX;262 const dy = e.clientY - startY;263 tableElement.style.left = (startLeft + dx) + 'px';264 tableElement.style.top = (startTop + dy) + 'px';265 }266 267 function upHandler() {268 document.removeEventListener('mousemove', moveHandler);269 document.removeEventListener('mouseup', upHandler);270 271 // Update table position272 const table = tables.find(t => t.id === table.id);273 if (table) {274 table.x = parseInt(tableElement.style.left);275 table.y = parseInt(tableElement.style.top);276 }277 278 updateRelationships();279 }280 281 document.addEventListener('mousemove', moveHandler);282 document.addEventListener('mouseup', upHandler);283 });284 }285 286 // Show table properties modal287 function showTableModal(table) {288 selectedTable = table;289 document.getElementById('table-name').value = table.name;290 291 const columnsContainer = document.getElementById('columns-container');292 columnsContainer.innerHTML = '';293 294 table.columns.forEach((col, index) => {295 const colElement = document.createElement('div');296 colElement.className = 'row mb-2';297 colElement.innerHTML = `298 <div class="col-5">299 <input type="text" class="form-control form-control-sm col-name" value="${col.name}">300 </div>301 <div class="col-5">302 <select class="form-select form-select-sm col-type">303 <option ${col.type === 'INT' ? 'selected' : ''}>INT</option>304 <option ${col.type === 'VARCHAR(255)' ? 'selected' : ''}>VARCHAR(255)</option>305 <option ${col.type === 'TEXT' ? 'selected' : ''}>TEXT</option>306 <option ${col.type === 'BOOLEAN' ? 'selected' : ''}>BOOLEAN</option>307 <option ${col.type === 'DATE' ? 'selected' : ''}>DATE</option>308 <option ${col.type === 'TIMESTAMP' ? 'selected' : ''}>TIMESTAMP</option>309 </select>310 </div>311 <div class="col-2">312 <input type="checkbox" class="form-check-input col-pk" ${col.pk ? 'checked' : ''}>313 </div>314 `;315 columnsContainer.appendChild(colElement);316 });317 318 const modal = new bootstrap.Modal(document.getElementById('tableModal'));319 modal.show();320 }321 322 // Save table properties323 document.getElementById('save-table-btn').addEventListener('click', function() {324 if (!selectedTable) return;325 326 selectedTable.name = document.getElementById('table-name').value;327 328 const newColumns = [];329 document.querySelectorAll('#columns-container .row').forEach(row => {330 newColumns.push({331 name: row.querySelector('.col-name').value,332 type: row.querySelector('.col-type').value,333 pk: row.querySelector('.col-pk').checked334 });335 });336 337 selectedTable.columns = newColumns;338 renderTable(selectedTable);339 340 const modal = bootstrap.Modal.getInstance(document.getElementById('tableModal'));341 modal.hide();342 });343 344 // Add new column345 document.getElementById('add-column-btn').addEventListener('click', function() {346 const columnsContainer = document.getElementById('columns-container');347 const colElement = document.createElement('div');348 colElement.className = 'row mb-2';349 colElement.innerHTML = `350 <div class="col-5">351 <input type="text" class="form-control form-control-sm col-name" value="new_column">352 </div>353 <div class="col-5">354 <select class="form-select form-select-sm col-type">355 <option>INT</option>356 <option>VARCHAR(255)</option>357 <option>TEXT</option>358 <option>BOOLEAN</option>359 <option>DATE</option>360 <option selected>TIMESTAMP</option>361 </select>362 </div>363 <div class="col-2">364 <input type="checkbox" class="form-check-input col-pk">365 </div>366 `;367 columnsContainer.appendChild(colElement);368 });369 370 // Delete a table371 function deleteTable(tableId) {372 tables = tables.filter(t => t.id !== tableId);373 document.getElementById(tableId)?.remove();374 375 // Remove related relationships376 relationships = relationships.filter(r => r.from !== tableId && r.to !== tableId);377 updateRelationships();378 }379 380 // Start creating a relationship381 function startCreatingRelationship(x, y) {382 isCreatingRelationship = true;383 relationshipStart = null;384 385 function moveHandler(e) {386 if (!relationshipStart) return;387 388 const rect = designerArea.getBoundingClientRect();389 const endX = e.clientX - rect.left;390 const endY = e.clientY - rect.top;391 392 // Find closest table393 let closestTable = null;394 let minDist = Infinity;395 396 tables.forEach(table => {397 const tableElement = document.getElementById(table.id);398 const tableRect = tableElement.getBoundingClientRect();399 const tableCenterX = tableRect.left + tableRect.width/2 - rect.left;400 const tableCenterY = tableRect.top + tableRect.height/2 - rect.top;401 402 const dist = Math.sqrt(403 Math.pow(endX - tableCenterX, 2) + 404 Math.pow(endY - tableCenterY, 2)405 );406 407 if (dist < minDist && dist < 50) {408 minDist = dist;409 closestTable = table.id;410 }411 });412 413 if (closestTable && closestTable !== relationshipStart) {414 createRelationship(relationshipStart, closestTable);415 isCreatingRelationship = false;416 relationshipStart = null;417 document.removeEventListener('mousemove', moveHandler);418 document.removeEventListener('mouseup', upHandler);419 }420 }421 422 function upHandler() {423 isCreatingRelationship = false;424 relationshipStart = null;425 document.removeEventListener('mousemove', moveHandler);426 document.removeEventListener('mouseup', upHandler);427 }428 429 document.addEventListener('mousemove', moveHandler);430 document.addEventListener('mouseup', upHandler);431 }432 433 // Create a relationship between two tables434 function createRelationship(fromTableId, toTableId) {435 const relationshipId = 'rel-' + Date.now();436 const relationship = {437 id: relationshipId,438 from: fromTableId,439 to: toTableId,440 type: 'ONE_TO_MANY'441 };442 443 relationships.push(relationship);444 updateRelationships();445 }446 447 // Update all relationships on the designer448 function updateRelationships() {449 // Remove existing relationship elements450 document.querySelectorAll('.relationship').forEach(el => el.remove());451 452 relationships.forEach(rel => {453 const fromTable = document.getElementById(rel.from);454 const toTable = document.getElementById(rel.to);455 456 if (!fromTable || !toTable) return;457 458 const fromRect = fromTable.getBoundingClientRect();459 const toRect = toTable.getBoundingClientRect();460 const designerRect = designerArea.getBoundingClientRect();461 462 const fromX = fromRect.left + fromRect.width - designerRect.left;463 const fromY = fromRect.top + fromRect.height/2 - designerRect.top;464 const toX = toRect.left - designerRect.left;465 const toY = toRect.top + toRect.height/2 - designerRect.top;466 467 const length = Math.sqrt(Math.pow(toX - fromX, 2) + Math.pow(toY - fromY, 2));468 const angle = Math.atan2(toY - fromY, toX - fromX) * 180 / Math.PI;469 470 const relationship = document.createElement('div');471 relationship.className = 'relationship';472 relationship.style.width = length + 'px';473 relationship.style.left = fromX + 'px';474 relationship.style.top = fromY + 'px';475 relationship.style.transform = `rotate(${angle}deg)`;476 relationship.style.transformOrigin = '0 0';477 478 designerArea.appendChild(relationship);479 });480 }481 482 // Export to SQL483 document.getElementById('export-btn').addEventListener('click', function() {484 let sql = '';485 486 tables.forEach(table => {487 sql += `CREATE TABLE ${table.name} (\n`;488 489 table.columns.forEach((col, index) => {490 sql += ` ${col.name} ${col.type}`;491 if (col.pk) sql += ' PRIMARY KEY';492 if (index < table.columns.length - 1) sql += ',';493 sql += '\n';494 });495 496 sql += ');\n\n';497 });498 499 // Create a download link500 const blob = new Blob([sql], { type: 'text/plain' });501 const url = URL.createObjectURL(blob);502 const a = document.createElement('a');503 a.href = url;504 a.download = 'database_schema.sql';505 document.body.appendChild(a);506 a.click();507 document.body.removeChild(a);508 URL.revokeObjectURL(url);509 });510 511 // Clear designer512 document.getElementById('clear-btn').addEventListener('click', function() {513 if