neyka/nodebase-explorer
0
1<!DOCTYPE html>2<html lang="en">3<head>4 <meta charset="UTF-8">5 <meta name="viewport" content="width=device-width, initial-scale=1.0">6 <title>Node Database</title>7 <script src="https://cdn.tailwindcss.com"></script>8 <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>9 <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>10 <style>11 body {12 overflow: hidden;13 background: #0f172a;14 color: #e2e8f0;15 }16 #bg {17 position: fixed;18 top: 0;19 left: 0;20 z-index: -1;21 opacity: 0.4;22 }23 .node {24 width: 200px;25 border-radius: 8px;26 padding: 12px;27 position: absolute;28 cursor: move;29 box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);30 transition: box-shadow 0.3s ease;31 user-select: none;32 }33 .node:hover {34 box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);35 }36 .node-db {37 background: #1e40af;38 border: 2px solid #3b82f6;39 }40 .node-column {41 background: #4338ca;42 border: 2px solid #6366f1;43 }44 .node-data {45 background: #6b21a8;46 border: 2px solid #9333ea;47 }48 .connector {49 width: 12px;50 height: 12px;51 border-radius: 50%;52 position: absolute;53 cursor: pointer;54 z-index: 10;55 }56 .input-connector {57 background: #10b981;58 left: -6px;59 top: 50%;60 transform: translateY(-50%);61 }62 .output-connector {63 background: #f59e0b;64 right: -6px;65 top: 50%;66 transform: translateY(-50%);67 }68 .connection {69 position: absolute;70 background: rgba(255, 255, 255, 0.3);71 height: 2px;72 transform-origin: 0 0;73 z-index: 5;74 }75 .controls {76 position: fixed;77 bottom: 20px;78 left: 50%;79 transform: translateX(-50%);80 display: flex;81 gap: 10px;82 }83 .controls button {84 background: rgba(30, 41, 59, 0.8);85 border: 1px solid #334155;86 color: #e2e8f0;87 padding: 8px 16px;88 border-radius: 6px;89 cursor: pointer;90 display: flex;91 align-items: center;92 gap: 6px;93 }94 .controls button:hover {95 background: rgba(51, 65, 85, 0.8);96 }97 </style>98</head>99<body>100 <canvas id="bg"></canvas>101 <div id="workspace"></div>102 <div class="controls">103 <button id="create-db"><i data-feather="database"></i> Database</button>104 <button id="create-column"><i data-feather="columns"></i> Column</button>105 <button id="create-data"><i data-feather="file-text"></i> Data</button>106 <button id="clear-all"><i data-feather="trash-2"></i> Clear</button>107 </div>108 109 <script>110 // Mountain parallax shader111 const bgCanvas = document.getElementById('bg');112 const renderer = new THREE.WebGLRenderer({ 113 canvas: bgCanvas,114 alpha: true 115 });116 renderer.setSize(window.innerWidth, window.innerHeight);117 118 const scene = new THREE.Scene();119 const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);120 camera.position.z = 15;121 122 // Create mountains123 const geometry = new THREE.PlaneGeometry(30, 30, 32, 32);124 const material = new THREE.MeshBasicMaterial({ 125 color: 0x1e3a8a,126 wireframe: true,127 transparent: true,128 opacity: 0.7129 });130 131 const mountains = [];132 for (let i = 0; i < 5; i++) {133 const mountain = new THREE.Mesh(geometry, material);134 mountain.position.z = -i * 5;135 mountain.scale.set(1 + i * 0.2, 1 + i * 0.2, 1);136 scene.add(mountain);137 mountains.push(mountain);138 }139 140 function animate() {141 requestAnimationFrame(animate);142 143 mountains.forEach((mountain, i) => {144 mountain.rotation.z += 0.0002 * (i + 1);145 mountain.rotation.x += 0.0001 * (i + 1);146 });147 148 renderer.render(scene, camera);149 }150 animate();151 152 window.addEventListener('resize', () => {153 camera.aspect = window.innerWidth / window.innerHeight;154 camera.updateProjectionMatrix();155 renderer.setSize(window.innerWidth, window.innerHeight);156 });157 158 // Node system159 const workspace = document.getElementById('workspace');160 let nodes = [];161 let connections = [];162 let draggingNode = null;163 let startPos = { x: 0, y: 0 };164 let selectedConnector = null;165 166 class Node {167 constructor(type, x, y) {168 this.id = Date.now() + Math.floor(Math.random() * 1000);169 this.type = type;170 this.x = x;171 this.y = y;172 this.element = null;173 this.inputConnector = null;174 this.outputConnector = null;175 this.create();176 nodes.push(this);177 }178 179 create() {180 this.element = document.createElement('div');181 this.element.className = `node node-${this.type}`;182 this.element.style.left = `${this.x}px`;183 this.element.style.top = `${this.y}px`;184 this.element.dataset.id = this.id;185 186 let title = '';187 let showInput = true;188 let showOutput = true;189 190 switch (this.type) {191 case 'db':192 title = 'Database';193 showInput = false;194 break;195 case 'column':196 title = 'Column';197 break;198 case 'data':199 title = 'Data';200 showOutput = false;201 break;202 }203 204 this.element.innerHTML = `205 <div class="font-bold mb-2">${title}</div>206 <input type="text" placeholder="Name" class="w-full bg-transparent border-b border-gray-500 focus:border-blue-400 outline-none">207 `;208 209 if (showInput) {210 this.inputConnector = document.createElement('div');211 this.inputConnector.className = 'connector input-connector';212 this.inputConnector.dataset.nodeId = this.id;213 this.inputConnector.dataset.type = 'input';214 this.element.appendChild(this.inputConnector);215 }216 217 if (showOutput) {218 this.outputConnector = document.createElement('div');219 this.outputConnector.className = 'connector output-connector';220 this.outputConnector.dataset.nodeId = this.id;221 this.outputConnector.dataset.type = 'output';222 this.element.appendChild(this.outputConnector);223 }224 225 workspace.appendChild(this.element);226 227 // Add drag functionality228 this.element.addEventListener('mousedown', (e) => {229 if (e.target === this.inputConnector || e.target === this.outputConnector) return;230 231 draggingNode = this;232 startPos = {233 x: e.clientX - this.x,234 y: e.clientY - this.y235 };236 });237 }238 239 updatePosition(x, y) {240 this.x = x;241 this.y = y;242 this.element.style.left = `${x}px`;243 this.element.style.top = `${y}px`;244 245 // Update connections246 this.updateConnections();247 }248 249 updateConnections() {250 connections.forEach(conn => {251 if (conn.fromNode === this.id || conn.toNode === this.id) {252 conn.update();253 }254 });255 }256 257 remove() {258 // Remove connected connections259 connections = connections.filter(conn => {260 if (conn.fromNode === this.id || conn.toNode === this.id) {261 conn.element.remove();262 return false;263 }264 return true;265 });266 267 this.element.remove();268 nodes = nodes.filter(n => n.id !== this.id);269 }270 }271 272 class Connection {273 constructor(fromNode, toNode) {274 this.fromNode = fromNode;275 this.toNode = toNode;276 this.element = document.createElement('div');277 this.element.className = 'connection';278 workspace.appendChild(this.element);279 this.update();280 connections.push(this);281 }282 283 update() {284 const fromNode = nodes.find(n => n.id === this.fromNode);285 const toNode = nodes.find(n => n.id === this.toNode);286 287 if (!fromNode || !toNode) {288 this.element.remove();289 connections = connections.filter(c => c !== this);290 return;291 }292 293 const fromRect = fromNode.outputConnector.getBoundingClientRect();294 const toRect = toNode.inputConnector.getBoundingClientRect();295 296 const length = Math.sqrt(297 Math.pow(toRect.left - fromRect.left, 2) + 298 Math.pow(toRect.top - fromRect.top, 2)299 );300 301 const angle = Math.atan2(302 toRect.top - fromRect.top,303 toRect.left - fromRect.left304 );305 306 this.element.style.width = `${length}px`;307 this.element.style.left = `${fromRect.left - workspace.getBoundingClientRect().left + fromRect.width}px`;308 this.element.style.top = `${fromRect.top - workspace.getBoundingClientRect().top + fromRect.height / 2}px`;309 this.element.style.transform = `rotate(${angle}rad)`;310 }311 }312 313 // Event listeners314 document.addEventListener('mousemove', (e) => {315 if (draggingNode) {316 draggingNode.updatePosition(317 e.clientX - startPos.x,318 e.clientY - startPos.y319 );320 }321 });322 323 document.addEventListener('mouseup', () => {324 draggingNode = null;325 });326 327 workspace.addEventListener('click', (e) => {328 if (e.target.classList.contains('connector')) {329 if (!selectedConnector) {330 selectedConnector = e.target;331 e.target.style.transform = 'translateY(-50%) scale(1.5)';332 } else {333 const fromType = selectedConnector.dataset.type;334 const toType = e.target.dataset.type;335 const fromNode = selectedConnector.dataset.nodeId;336 const toNode = e.target.dataset.nodeId;337 338 if (fromNode !== toNode && fromType !== toType) {339 // Check if connection already exists340 const exists = connections.some(conn => 341 (conn.fromNode == fromNode && conn.toNode == toNode) ||342 (conn.fromNode == toNode && conn.toNode == fromNode)343 );344 345 if (!exists) {346 new Connection(347 fromType === 'output' ? fromNode : toNode,348 fromType === 'output' ? toNode : fromNode349 );350 }351 }352 353 selectedConnector.style.transform = 'translateY(-50%)';354 selectedConnector = null;355 }356 } else if (selectedConnector) {357 selectedConnector.style.transform = 'translateY(-50%)';358 selectedConnector = null;359 }360 });361 362 // Control buttons363 document.getElementById('create-db').addEventListener('click', () => {364 new Node('db', 100, 100);365 });366 367 document.getElementById('create-column').addEventListener('click', () => {368 new Node('column', 300, 100);369 });370 371 document.getElementById('create-data').addEventListener('click', () => {372 new Node('data', 500, 100);373 });374 375 document.getElementById('clear-all').addEventListener('click', () => {376 nodes.forEach(node => node.remove());377 nodes = [];378 connections = [];379 });380 381 // Initialize feather icons382 feather.replace();383 </script>384</body>385</html>386```