Spaces:
Running
Running
Update index.html
Browse files- index.html +26 -20
index.html
CHANGED
|
@@ -69,7 +69,7 @@
|
|
| 69 |
|
| 70 |
<canvas id="bg-canvas" class="absolute top-0 left-0 w-full h-full outline-none z-10"></canvas>
|
| 71 |
|
| 72 |
-
<div id="loading-overlay" class="glass-ui absolute inset-0 z-[
|
| 73 |
<div class="spinner w-12 h-12 border-4 border-gray-600 rounded-full animate-spin"></div>
|
| 74 |
</div>
|
| 75 |
<div id="main-loader" class="absolute inset-0 bg-gray-900 flex flex-col items-center justify-center z-50 transition-opacity duration-500">
|
|
@@ -129,7 +129,7 @@
|
|
| 129 |
<input type="text" id="model-url-input" class="w-full bg-gray-900/50 p-2 rounded-md border-2 border-gray-600 focus:border-blue-500 outline-none" placeholder="Enter model URL (.glb, .gltf)">
|
| 130 |
<div class="flex justify-end gap-3 mt-2">
|
| 131 |
<button onclick="document.getElementById('upload-model-modal').style.display='none'" class="bg-gray-600 px-4 py-2 rounded-md font-semibold hover:bg-gray-700">Cancel</button>
|
| 132 |
-
<button id="load-model-url-btn" class="bg-blue-600 px-4 py-2 rounded-md font-semibold hover:bg-blue-700 min-w-[80px]">Load</button>
|
| 133 |
</div>
|
| 134 |
</div>
|
| 135 |
</div>
|
|
@@ -146,7 +146,7 @@
|
|
| 146 |
<input type="text" id="panorama-url-input" class="w-full bg-gray-900/50 p-2 rounded-md border-2 border-gray-600 focus:border-blue-500 outline-none" placeholder="Enter panorama image URL">
|
| 147 |
<div class="flex justify-end gap-3 mt-2">
|
| 148 |
<button onclick="document.getElementById('upload-panorama-modal').style.display='none'" class="bg-gray-600 px-4 py-2 rounded-md font-semibold hover:bg-gray-700">Cancel</button>
|
| 149 |
-
<button id="load-panorama-url-btn" class="bg-blue-600 px-4 py-2 rounded-md font-semibold hover:bg-blue-700 min-w-[80px]">Load</button>
|
| 150 |
</div>
|
| 151 |
</div>
|
| 152 |
</div>
|
|
@@ -162,6 +162,7 @@
|
|
| 162 |
const defaultModelFiles = [ 'gerbera.glb', 'shahed1.glb', 'shahed2.glb', 'shahed3.glb', 'supercam.glb', 'zala.glb', 'beaver.glb' ].map(name => ({ name, url: `/glb/${name}`, thumb: `/glb/thumbnails/${name.replace('.glb', '.png')}` }));
|
| 163 |
|
| 164 |
let scene, camera, renderer, controls;
|
|
|
|
| 165 |
const loadedModels = new Map();
|
| 166 |
|
| 167 |
const mainLoader = document.getElementById('main-loader');
|
|
@@ -196,7 +197,10 @@
|
|
| 196 |
await loadAndSwitchModel(defaultModelFiles[0]);
|
| 197 |
|
| 198 |
mainLoader.style.opacity = '0';
|
| 199 |
-
setTimeout(() =>
|
|
|
|
|
|
|
|
|
|
| 200 |
|
| 201 |
window.addEventListener('resize', onWindowResize);
|
| 202 |
animate();
|
|
@@ -277,7 +281,7 @@
|
|
| 277 |
const thumb = document.createElement('img');
|
| 278 |
thumb.src = modelData.thumb;
|
| 279 |
thumb.alt = `Thumbnail for ${modelData.name}`;
|
| 280 |
-
thumb.className = 'w-full h-auto aspect-square object-contain';
|
| 281 |
thumb.onerror = () => {
|
| 282 |
thumb.src = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIiB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2E1YjRjYyIgc3Ryb2tlLXdpZHRoPSIwLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0iTTUgMyBsIDE0IDAgYSBUbyAwIDAgMSAyIDIgTCAyMSAxOSBhIDIgMiAwIDAgMSAtMiAyIEwgNSAyMSBhIDIgMiAwIDAgMSAtMiAtMiBMIDMgNSBhIDIgMiAwIDAgMSAyIC0yIFoiPjwvcGF0aD48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSI0Ij48L2NpcmNsZT48L3N2Zz4=';
|
| 283 |
thumb.classList.add('bg-gray-800', 'p-4');
|
|
@@ -320,7 +324,7 @@
|
|
| 320 |
function toggleButtonLoading(button, isLoading) {
|
| 321 |
if (isLoading) {
|
| 322 |
button.disabled = true;
|
| 323 |
-
button.innerHTML = `<svg class="animate-spin h-5 w-5
|
| 324 |
} else {
|
| 325 |
button.disabled = false;
|
| 326 |
button.innerHTML = 'Load';
|
|
@@ -328,9 +332,11 @@
|
|
| 328 |
}
|
| 329 |
|
| 330 |
function setupEventListeners() {
|
| 331 |
-
// Panels
|
| 332 |
const modelPanel = document.getElementById('model-panel');
|
| 333 |
const panoramaPanel = document.getElementById('panorama-panel');
|
|
|
|
|
|
|
|
|
|
| 334 |
const closeModelPanel = () => modelPanel.classList.remove('is-open');
|
| 335 |
const closePanoramaPanel = () => panoramaPanel.classList.remove('is-open');
|
| 336 |
document.getElementById('model-panel-trigger').addEventListener('click', e => { e.stopPropagation(); modelPanel.classList.toggle('is-open'); closePanoramaPanel(); });
|
|
@@ -339,37 +345,34 @@
|
|
| 339 |
document.getElementById('close-panorama-panel').addEventListener('click', closePanoramaPanel);
|
| 340 |
document.getElementById('bg-canvas').addEventListener('click', () => { closeModelPanel(); closePanoramaPanel(); });
|
| 341 |
|
| 342 |
-
// Modals
|
| 343 |
-
const uploadModelModal = document.getElementById('upload-model-modal');
|
| 344 |
-
const uploadPanoramaModal = document.getElementById('upload-panorama-modal');
|
| 345 |
document.getElementById('add-model-btn').addEventListener('click', () => uploadModelModal.style.display = 'flex');
|
| 346 |
document.getElementById('add-panorama-btn').addEventListener('click', () => uploadPanoramaModal.style.display = 'flex');
|
| 347 |
|
| 348 |
-
// File Handling
|
| 349 |
const modelFileInput = document.getElementById('model-file-input');
|
| 350 |
const panoramaFileInput = document.getElementById('panorama-file-input');
|
| 351 |
modelFileInput.addEventListener('change', e => handleModelFile(e.target.files[0]));
|
| 352 |
panoramaFileInput.addEventListener('change', e => handlePanoramaFile(e.target.files[0]));
|
| 353 |
|
| 354 |
-
// URL Loading
|
| 355 |
const loadModelUrlBtn = document.getElementById('load-model-url-btn');
|
|
|
|
| 356 |
loadModelUrlBtn.addEventListener('click', () => {
|
| 357 |
-
const url =
|
| 358 |
if(url) {
|
| 359 |
toggleButtonLoading(loadModelUrlBtn, true);
|
| 360 |
handleModelFile(url).finally(() => toggleButtonLoading(loadModelUrlBtn, false));
|
| 361 |
}
|
| 362 |
});
|
|
|
|
| 363 |
const loadPanoramaUrlBtn = document.getElementById('load-panorama-url-btn');
|
|
|
|
| 364 |
loadPanoramaUrlBtn.addEventListener('click', () => {
|
| 365 |
-
const url =
|
| 366 |
if(url) {
|
| 367 |
toggleButtonLoading(loadPanoramaUrlBtn, true);
|
| 368 |
handlePanoramaFile(url).finally(() => toggleButtonLoading(loadPanoramaUrlBtn, false));
|
| 369 |
}
|
| 370 |
});
|
| 371 |
|
| 372 |
-
// Drag and Drop
|
| 373 |
setupDragDrop('model-drop-zone', handleModelFile, modelFileInput);
|
| 374 |
setupDragDrop('panorama-drop-zone', handlePanoramaFile, panoramaFileInput);
|
| 375 |
}
|
|
@@ -378,11 +381,12 @@
|
|
| 378 |
if(!fileOrUrl) return;
|
| 379 |
const isUrl = typeof fileOrUrl === 'string';
|
| 380 |
const url = isUrl ? fileOrUrl : URL.createObjectURL(fileOrUrl);
|
| 381 |
-
const name = isUrl ? fileOrUrl.split('/').pop() : fileOrUrl.name;
|
| 382 |
-
const modelData = { name, url, thumb: '
|
| 383 |
|
| 384 |
-
createModelCard(modelData);
|
| 385 |
document.getElementById('upload-model-modal').style.display = 'none';
|
|
|
|
|
|
|
| 386 |
await loadAndSwitchModel(modelData);
|
| 387 |
}
|
| 388 |
|
|
@@ -390,11 +394,12 @@
|
|
| 390 |
if(!fileOrUrl) return;
|
| 391 |
const isUrl = typeof fileOrUrl === 'string';
|
| 392 |
const url = isUrl ? fileOrUrl : URL.createObjectURL(fileOrUrl);
|
| 393 |
-
const name = isUrl ? fileOrUrl.split('/').pop() : fileOrUrl.name;
|
| 394 |
const panoData = { name, url, thumb: url };
|
| 395 |
|
| 396 |
-
createPanoramaCard(panoData);
|
| 397 |
document.getElementById('upload-panorama-modal').style.display = 'none';
|
|
|
|
|
|
|
| 398 |
await setPanorama(panoData);
|
| 399 |
}
|
| 400 |
|
|
@@ -413,6 +418,7 @@
|
|
| 413 |
}
|
| 414 |
|
| 415 |
function showLoadingOverlay(show) {
|
|
|
|
| 416 |
loadingOverlay.style.opacity = show ? '1' : '0';
|
| 417 |
loadingOverlay.style.pointerEvents = show ? 'auto' : 'none';
|
| 418 |
}
|
|
|
|
| 69 |
|
| 70 |
<canvas id="bg-canvas" class="absolute top-0 left-0 w-full h-full outline-none z-10"></canvas>
|
| 71 |
|
| 72 |
+
<div id="loading-overlay" class="glass-ui absolute inset-0 z-[51] flex items-center justify-center opacity-0 pointer-events-none transition-opacity duration-300">
|
| 73 |
<div class="spinner w-12 h-12 border-4 border-gray-600 rounded-full animate-spin"></div>
|
| 74 |
</div>
|
| 75 |
<div id="main-loader" class="absolute inset-0 bg-gray-900 flex flex-col items-center justify-center z-50 transition-opacity duration-500">
|
|
|
|
| 129 |
<input type="text" id="model-url-input" class="w-full bg-gray-900/50 p-2 rounded-md border-2 border-gray-600 focus:border-blue-500 outline-none" placeholder="Enter model URL (.glb, .gltf)">
|
| 130 |
<div class="flex justify-end gap-3 mt-2">
|
| 131 |
<button onclick="document.getElementById('upload-model-modal').style.display='none'" class="bg-gray-600 px-4 py-2 rounded-md font-semibold hover:bg-gray-700">Cancel</button>
|
| 132 |
+
<button id="load-model-url-btn" class="bg-blue-600 px-4 py-2 rounded-md font-semibold hover:bg-blue-700 min-w-[80px] flex justify-center items-center">Load</button>
|
| 133 |
</div>
|
| 134 |
</div>
|
| 135 |
</div>
|
|
|
|
| 146 |
<input type="text" id="panorama-url-input" class="w-full bg-gray-900/50 p-2 rounded-md border-2 border-gray-600 focus:border-blue-500 outline-none" placeholder="Enter panorama image URL">
|
| 147 |
<div class="flex justify-end gap-3 mt-2">
|
| 148 |
<button onclick="document.getElementById('upload-panorama-modal').style.display='none'" class="bg-gray-600 px-4 py-2 rounded-md font-semibold hover:bg-gray-700">Cancel</button>
|
| 149 |
+
<button id="load-panorama-url-btn" class="bg-blue-600 px-4 py-2 rounded-md font-semibold hover:bg-blue-700 min-w-[80px] flex justify-center items-center">Load</button>
|
| 150 |
</div>
|
| 151 |
</div>
|
| 152 |
</div>
|
|
|
|
| 162 |
const defaultModelFiles = [ 'gerbera.glb', 'shahed1.glb', 'shahed2.glb', 'shahed3.glb', 'supercam.glb', 'zala.glb', 'beaver.glb' ].map(name => ({ name, url: `/glb/${name}`, thumb: `/glb/thumbnails/${name.replace('.glb', '.png')}` }));
|
| 163 |
|
| 164 |
let scene, camera, renderer, controls;
|
| 165 |
+
let isInitialized = false;
|
| 166 |
const loadedModels = new Map();
|
| 167 |
|
| 168 |
const mainLoader = document.getElementById('main-loader');
|
|
|
|
| 197 |
await loadAndSwitchModel(defaultModelFiles[0]);
|
| 198 |
|
| 199 |
mainLoader.style.opacity = '0';
|
| 200 |
+
setTimeout(() => {
|
| 201 |
+
mainLoader.style.display = 'none';
|
| 202 |
+
isInitialized = true;
|
| 203 |
+
}, 500);
|
| 204 |
|
| 205 |
window.addEventListener('resize', onWindowResize);
|
| 206 |
animate();
|
|
|
|
| 281 |
const thumb = document.createElement('img');
|
| 282 |
thumb.src = modelData.thumb;
|
| 283 |
thumb.alt = `Thumbnail for ${modelData.name}`;
|
| 284 |
+
thumb.className = 'w-full h-auto aspect-square object-contain';
|
| 285 |
thumb.onerror = () => {
|
| 286 |
thumb.src = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIiB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2E1YjRjYyIgc3Ryb2tlLXdpZHRoPSIwLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0iTTUgMyBsIDE0IDAgYSBUbyAwIDAgMSAyIDIgTCAyMSAxOSBhIDIgMiAwIDAgMSAtMiAyIEwgNSAyMSBhIDIgMiAwIDAgMSAtMiAtMiBMIDMgNSBhIDIgMiAwIDAgMSAyIC0yIFoiPjwvcGF0aD48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSI0Ij48L2NpcmNsZT48L3N2Zz4=';
|
| 287 |
thumb.classList.add('bg-gray-800', 'p-4');
|
|
|
|
| 324 |
function toggleButtonLoading(button, isLoading) {
|
| 325 |
if (isLoading) {
|
| 326 |
button.disabled = true;
|
| 327 |
+
button.innerHTML = `<svg class="animate-spin h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>`;
|
| 328 |
} else {
|
| 329 |
button.disabled = false;
|
| 330 |
button.innerHTML = 'Load';
|
|
|
|
| 332 |
}
|
| 333 |
|
| 334 |
function setupEventListeners() {
|
|
|
|
| 335 |
const modelPanel = document.getElementById('model-panel');
|
| 336 |
const panoramaPanel = document.getElementById('panorama-panel');
|
| 337 |
+
const uploadModelModal = document.getElementById('upload-model-modal');
|
| 338 |
+
const uploadPanoramaModal = document.getElementById('upload-panorama-modal');
|
| 339 |
+
|
| 340 |
const closeModelPanel = () => modelPanel.classList.remove('is-open');
|
| 341 |
const closePanoramaPanel = () => panoramaPanel.classList.remove('is-open');
|
| 342 |
document.getElementById('model-panel-trigger').addEventListener('click', e => { e.stopPropagation(); modelPanel.classList.toggle('is-open'); closePanoramaPanel(); });
|
|
|
|
| 345 |
document.getElementById('close-panorama-panel').addEventListener('click', closePanoramaPanel);
|
| 346 |
document.getElementById('bg-canvas').addEventListener('click', () => { closeModelPanel(); closePanoramaPanel(); });
|
| 347 |
|
|
|
|
|
|
|
|
|
|
| 348 |
document.getElementById('add-model-btn').addEventListener('click', () => uploadModelModal.style.display = 'flex');
|
| 349 |
document.getElementById('add-panorama-btn').addEventListener('click', () => uploadPanoramaModal.style.display = 'flex');
|
| 350 |
|
|
|
|
| 351 |
const modelFileInput = document.getElementById('model-file-input');
|
| 352 |
const panoramaFileInput = document.getElementById('panorama-file-input');
|
| 353 |
modelFileInput.addEventListener('change', e => handleModelFile(e.target.files[0]));
|
| 354 |
panoramaFileInput.addEventListener('change', e => handlePanoramaFile(e.target.files[0]));
|
| 355 |
|
|
|
|
| 356 |
const loadModelUrlBtn = document.getElementById('load-model-url-btn');
|
| 357 |
+
const modelUrlInput = document.getElementById('model-url-input');
|
| 358 |
loadModelUrlBtn.addEventListener('click', () => {
|
| 359 |
+
const url = modelUrlInput.value.trim();
|
| 360 |
if(url) {
|
| 361 |
toggleButtonLoading(loadModelUrlBtn, true);
|
| 362 |
handleModelFile(url).finally(() => toggleButtonLoading(loadModelUrlBtn, false));
|
| 363 |
}
|
| 364 |
});
|
| 365 |
+
|
| 366 |
const loadPanoramaUrlBtn = document.getElementById('load-panorama-url-btn');
|
| 367 |
+
const panoramaUrlInput = document.getElementById('panorama-url-input');
|
| 368 |
loadPanoramaUrlBtn.addEventListener('click', () => {
|
| 369 |
+
const url = panoramaUrlInput.value.trim();
|
| 370 |
if(url) {
|
| 371 |
toggleButtonLoading(loadPanoramaUrlBtn, true);
|
| 372 |
handlePanoramaFile(url).finally(() => toggleButtonLoading(loadPanoramaUrlBtn, false));
|
| 373 |
}
|
| 374 |
});
|
| 375 |
|
|
|
|
| 376 |
setupDragDrop('model-drop-zone', handleModelFile, modelFileInput);
|
| 377 |
setupDragDrop('panorama-drop-zone', handlePanoramaFile, panoramaFileInput);
|
| 378 |
}
|
|
|
|
| 381 |
if(!fileOrUrl) return;
|
| 382 |
const isUrl = typeof fileOrUrl === 'string';
|
| 383 |
const url = isUrl ? fileOrUrl : URL.createObjectURL(fileOrUrl);
|
| 384 |
+
const name = isUrl ? fileOrUrl.split('/').pop().split('?')[0] : fileOrUrl.name;
|
| 385 |
+
const modelData = { name, url, thumb: `glb/thumbnails/${name.replace('.glb', '.png')}` };
|
| 386 |
|
|
|
|
| 387 |
document.getElementById('upload-model-modal').style.display = 'none';
|
| 388 |
+
document.getElementById('model-url-input').value = '';
|
| 389 |
+
createModelCard(modelData);
|
| 390 |
await loadAndSwitchModel(modelData);
|
| 391 |
}
|
| 392 |
|
|
|
|
| 394 |
if(!fileOrUrl) return;
|
| 395 |
const isUrl = typeof fileOrUrl === 'string';
|
| 396 |
const url = isUrl ? fileOrUrl : URL.createObjectURL(fileOrUrl);
|
| 397 |
+
const name = isUrl ? fileOrUrl.split('/').pop().split('?')[0] : fileOrUrl.name;
|
| 398 |
const panoData = { name, url, thumb: url };
|
| 399 |
|
|
|
|
| 400 |
document.getElementById('upload-panorama-modal').style.display = 'none';
|
| 401 |
+
document.getElementById('panorama-url-input').value = '';
|
| 402 |
+
createPanoramaCard(panoData);
|
| 403 |
await setPanorama(panoData);
|
| 404 |
}
|
| 405 |
|
|
|
|
| 418 |
}
|
| 419 |
|
| 420 |
function showLoadingOverlay(show) {
|
| 421 |
+
if (show && !isInitialized) return;
|
| 422 |
loadingOverlay.style.opacity = show ? '1' : '0';
|
| 423 |
loadingOverlay.style.pointerEvents = show ? 'auto' : 'none';
|
| 424 |
}
|