CoolFace
Apppublic

Jhonathan-jsa/schemaflow-visual-postgresql-maestro

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
database-designer.html513 linesDownload Raw Back to root
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