Spaces:
Running
Running
| (function() { | |
| // Helper: Get query parameters from THIS script’s src URL. | |
| function getScriptQueryParam(param) { | |
| var params = new URLSearchParams(""); | |
| if (document.currentScript && document.currentScript.src.indexOf('?') !== -1) { | |
| var queryString = document.currentScript.src.split('?')[1]; | |
| params = new URLSearchParams(queryString); | |
| } else { | |
| params = new URLSearchParams(window.location.search); | |
| } | |
| return params.get(param); | |
| } | |
| // Read required URLs and optional limits from the query parameters. | |
| var gifUrl = getScriptQueryParam("gif_url"); | |
| var plyUrl = getScriptQueryParam("ply_url"); | |
| // Optional parameters for zoom and angle limits: | |
| var minZoom = parseFloat(getScriptQueryParam("minZoom")) || 1.5; | |
| var maxZoom = parseFloat(getScriptQueryParam("maxZoom")) || 5; | |
| var minAngle = parseFloat(getScriptQueryParam("minAngle")) || 0; | |
| var maxAngle = parseFloat(getScriptQueryParam("maxAngle")) || 90; | |
| // Inject CSS styles into the document head. | |
| var styleEl = document.createElement('style'); | |
| styleEl.textContent = ` | |
| /* Widget container styling */ | |
| #ply-widget-container { | |
| position: relative; | |
| width: 100%; | |
| height: 0; | |
| padding-bottom: 100%; | |
| } | |
| /* GIF Preview styling */ | |
| #gif-preview-container { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| border: 1px solid #474558; | |
| border-radius: 10px; | |
| overflow: hidden; | |
| cursor: pointer; | |
| } | |
| #gif-preview-container img { | |
| width: 100%; | |
| height: 100%; | |
| object-fit: cover; | |
| } | |
| /* Viewer Container styling */ | |
| #viewer-container { | |
| display: none; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: #ffffff; /* white background */ | |
| border: 1px solid #474558; | |
| border-radius: 10px; | |
| } | |
| /* Canvas fills the viewer container */ | |
| #canvas { | |
| width: 100%; | |
| height: 100%; | |
| display: block; | |
| } | |
| /* Progress dialog styling (as a centered div) */ | |
| #progress-dialog { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| border: none; | |
| background: rgba(255,255,255,0.9); | |
| padding: 20px; | |
| border-radius: 5px; | |
| z-index: 1000; | |
| display: none; | |
| } | |
| /* Menu (instructions) content styling */ | |
| #menu-content { | |
| display: none; | |
| position: absolute; | |
| top: 62px; | |
| right: 70px; | |
| background: #FFFEF4; | |
| border: 1px solid #474558; | |
| border-radius: 5px; | |
| padding: 10px; | |
| font-size: 15px; | |
| line-height: 1.4; | |
| color: #474558; | |
| } | |
| /* Button styling */ | |
| .widget-button { | |
| position: absolute; | |
| width: 45px; | |
| height: 45px; | |
| background-color: #FFFEF4; | |
| border: 1px solid #474558; | |
| border-radius: 50%; | |
| cursor: pointer; | |
| font-size: 14px; | |
| color: #474558; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| /* Positions: Close at top-left, fullscreen at top-right, help (instructions) below fullscreen */ | |
| #close-btn { | |
| top: 17px; | |
| left: 15px; | |
| } | |
| #fullscreen-toggle { | |
| top: 17px; | |
| right: 15px; | |
| } | |
| #help-toggle { | |
| top: 72px; | |
| right: 15px; | |
| } | |
| `; | |
| document.head.appendChild(styleEl); | |
| // Create the widget container and set its inner HTML. | |
| var widgetContainer = document.createElement('div'); | |
| widgetContainer.id = 'ply-widget-container'; | |
| widgetContainer.innerHTML = ` | |
| <!-- GIF Preview Container --> | |
| <div id="gif-preview-container"> | |
| <img id="preview-image" alt="Preview" crossorigin="anonymous"> | |
| </div> | |
| <!-- Viewer Container --> | |
| <div id="viewer-container"> | |
| <canvas id="canvas"></canvas> | |
| <div id="progress-dialog"> | |
| <progress id="progress-indicator" max="100" value="0"></progress> | |
| </div> | |
| <button id="close-btn" class="widget-button">X</button> | |
| <button id="fullscreen-toggle" class="widget-button">⇱</button> | |
| <button id="help-toggle" class="widget-button">?</button> | |
| <div id="menu-content"> | |
| - Rotate with right click<br> | |
| - Zoom in/out with middle click<br> | |
| - Translate with left click | |
| </div> | |
| </div> | |
| `; | |
| // Append widgetContainer to the current script's parent so it appears in place. | |
| document.currentScript.parentNode.appendChild(widgetContainer); | |
| // Grab element references. | |
| var gifPreview = document.getElementById('gif-preview-container'); | |
| var viewerContainer = document.getElementById('viewer-container'); | |
| var previewImage = document.getElementById('preview-image'); | |
| var closeBtn = document.getElementById('close-btn'); | |
| var fullscreenToggle = document.getElementById('fullscreen-toggle'); | |
| var helpToggle = document.getElementById('help-toggle'); | |
| var menuContent = document.getElementById('menu-content'); | |
| var canvas = document.getElementById('canvas'); | |
| var progressDialog = document.getElementById('progress-dialog'); | |
| var progressIndicator = document.getElementById('progress-indicator'); | |
| // Set the preview image if provided. | |
| if (gifUrl) { | |
| previewImage.src = gifUrl; | |
| } | |
| // --- Button Event Handlers --- | |
| // When the preview image is clicked, hide it, show the viewer, and initialize the 3D viewer. | |
| gifPreview.addEventListener('click', function() { | |
| gifPreview.style.display = 'none'; | |
| viewerContainer.style.display = 'block'; | |
| initializeViewer(); | |
| }); | |
| // Close button: hide the viewer and show the preview. | |
| closeBtn.addEventListener('click', function() { | |
| // Exit fullscreen if active. | |
| if (document.fullscreenElement === widgetContainer) { | |
| if (document.exitFullscreen) { | |
| document.exitFullscreen(); | |
| } | |
| } | |
| viewerContainer.style.display = 'none'; | |
| gifPreview.style.display = 'block'; | |
| }); | |
| // Fullscreen toggle: toggle fullscreen on the widget container. | |
| fullscreenToggle.addEventListener('click', function() { | |
| if (!document.fullscreenElement) { | |
| if (widgetContainer.requestFullscreen) { | |
| widgetContainer.requestFullscreen(); | |
| } else if (widgetContainer.webkitRequestFullscreen) { | |
| widgetContainer.webkitRequestFullscreen(); | |
| } else if (widgetContainer.mozRequestFullScreen) { | |
| widgetContainer.mozRequestFullScreen(); | |
| } else if (widgetContainer.msRequestFullscreen) { | |
| widgetContainer.msRequestFullscreen(); | |
| } | |
| } else { | |
| if (document.exitFullscreen) { | |
| document.exitFullscreen(); | |
| } | |
| } | |
| }); | |
| // Update the fullscreen button icon on fullscreen change. | |
| document.addEventListener('fullscreenchange', function() { | |
| if (document.fullscreenElement === widgetContainer) { | |
| fullscreenToggle.textContent = '⇲'; | |
| } else { | |
| fullscreenToggle.textContent = '⇱'; | |
| } | |
| }); | |
| // Help (instructions) toggle: show/hide the instructions. | |
| helpToggle.addEventListener('click', function(e) { | |
| e.stopPropagation(); | |
| menuContent.style.display = (menuContent.style.display === 'block') ? 'none' : 'block'; | |
| }); | |
| // --- Initialize the 3D PLY Viewer using PlayCanvas Model Viewer --- | |
| async function initializeViewer() { | |
| // Dynamically import the PlayCanvas Model Viewer module. | |
| const { ModelViewer } = await import("https://cdn.jsdelivr.net/npm/@playcanvas/model-viewer/dist/model-viewer.module.js"); | |
| // Display the progress dialog. | |
| progressDialog.style.display = 'block'; | |
| // Create a new instance of the ModelViewer. | |
| // The options below set a white background and disable the grid. | |
| const viewer = new ModelViewer(canvas, { | |
| backgroundColor: '#ffffff', | |
| grid: false, | |
| // Pass zoom and orbit (angle) limits; note that the property names here | |
| // assume the ModelViewer accepts these options. Adjust names as per the library docs. | |
| minZoom: minZoom, | |
| maxZoom: maxZoom, | |
| minOrbitPitch: minAngle, | |
| maxOrbitPitch: maxAngle | |
| }); | |
| // Load the PLY model from the provided URL. | |
| // Assume that the `load` method accepts a progress callback. | |
| viewer.load(plyUrl, function(progress) { | |
| progressIndicator.value = progress * 100; | |
| }).then(function() { | |
| // Hide the progress dialog once the model is loaded. | |
| progressDialog.style.display = 'none'; | |
| }).catch(function(error) { | |
| console.error("Error loading PLY file:", error); | |
| progressDialog.innerHTML = `<p style="color: red">Error loading model: ${error.message}</p>`; | |
| }); | |
| // Start the viewer's render loop. | |
| viewer.start(); | |
| } | |
| })(); | |