import { LoaderType } from '../types/model-viewer'; /** * Automatically detects model loader type based on URL, MIME type, or File/Blob. */ export function detectLoaderType( source: string | File | Blob, mimeType?: string ): LoaderType { // 1️⃣ Detekce podle přípony (nejčastější případ) const getExt = (s: string) => s.toLowerCase().split('.').pop() || ''; if (typeof source === 'string') { const ext = getExt(source); if (ext === 'stl') return 'stl'; if (ext === 'obj') return 'obj'; if (ext === 'gltf') return 'gltf'; if (ext === 'glb') return 'glb'; } // 2️⃣ Pokud je to File nebo Blob, zkusíme jméno souboru if (source instanceof File || source instanceof Blob) { const name = (source as File).name?.toLowerCase?.() || ''; const ext = getExt(name); if (ext === 'stl') return 'stl'; if (ext === 'obj') return 'obj'; if (ext === 'gltf') return 'gltf'; if (ext === 'glb') return 'glb'; } // 3️⃣ Fallback podle MIME typu const type = (mimeType || '').toLowerCase(); if (type.includes('stl')) return 'stl'; if (type.includes('obj')) return 'obj'; if (type.includes('gltf')) return 'gltf'; if (type.includes('glb')) return 'glb'; throw new Error('Cannot detect loader type from input'); } /** * Adds the CSS styles for a spinner overlay to the page. * * @returns {void} */ function addSpinnerStyle() { const style = document.createElement('style'); style.textContent = ` .press3d-spinner { box-sizing: border-box; border: 2px solid #f3f3f3; border-top: 2px solid #333; border-radius: 50%; width: 50px; height: 50px; animation: spin 1s linear infinite; box-shadow: 0 0 0 0.3px rgba(204,204,204,0.7); } .press3d-spinner-text { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; font-size: 10px; color: #333; font-family: sans-serif; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `; document.head.appendChild(style); } /** * Creates a spinner overlay inside the given element. * * @param {HTMLElement} elem The element that should contain the spinner. */ export function createSpinnerInElement(elem: HTMLElement) { // --- Overlay spinner --- const overlay = document.createElement('div'); overlay.style.position = 'absolute'; overlay.style.top = '0'; overlay.style.left = '0'; overlay.style.width = '100%'; overlay.style.height = '100%'; overlay.style.display = 'flex'; overlay.style.alignItems = 'center'; overlay.style.justifyContent = 'center'; overlay.style.background = 'transparent'; overlay.innerHTML = `
0%
`; elem.style.position = 'relative'; elem.appendChild(overlay); const textElem = overlay.querySelector('.press3d-spinner-text'); addSpinnerStyle(); return { show: () => { overlay.style.display = 'flex'; }, hide: () => { overlay.style.display = 'none'; }, setProgress: (progress: number) => { if (textElem) { textElem.textContent = `${Math.round(progress * 100)}%`; } }, destroy: () => { if (overlay.parentNode) { overlay.parentNode.removeChild(overlay); } } }; } /** * Adds the CSS styles for a progress bar overlay to the page. * * @returns {void} */ export function addProgressBarStyle() { const style = document.createElement('style'); style.textContent = ` .press3d-progress-container { width: 60%; max-width: 200px; height: 2px; background: rgba(0,0,0,0.05); border-radius: 2px; overflow: hidden; box-shadow: 0 0 0 0.3px rgba(204,204,204,0.7); } .press3d-progress-bar { width: 0%; height: 100%; background: var(--wp--preset--color--primary, #aaa); transition: width 0.2s ease; } .press3d-progress-text { margin-bottom: 8px; font-size: 12px; color: #333; font-family: sans-serif; } `; document.head.appendChild(style); } /** * Creates a progress bar overlay inside the given element. * * @param {HTMLElement} elem The element that should contain the progress bar. * @returns {Object} An object with a show and hide method. */ export function createProgressBarInElement(elem: HTMLElement) { const overlay = document.createElement('div'); overlay.style.position = 'absolute'; overlay.style.top = '0'; overlay.style.left = '0'; overlay.style.width = '100%'; overlay.style.height = '100%'; overlay.style.display = 'flex'; overlay.style.flexDirection = 'column'; overlay.style.alignItems = 'center'; overlay.style.justifyContent = 'center'; overlay.style.background = 'transparent'; overlay.innerHTML = `
0%
`; elem.style.position = 'relative'; elem.appendChild(overlay); const progressBar = overlay.querySelector('.press3d-progress-bar'); const textElem = overlay.querySelector('.press3d-progress-text'); if (!progressBar) { throw new Error('Could not find progress bar element'); } addProgressBarStyle(); return { show: () => { progressBar.style.width = '0%'; if (textElem) textElem.textContent = '0%'; overlay.style.display = 'flex'; }, hide: () => { overlay.style.display = 'none'; }, setProgress: (progress: number) => { const percent = Math.round(progress * 100); progressBar.style.width = `${percent}%`; if (textElem) { textElem.textContent = `${percent}%`; } }, destroy: () => { if (overlay.parentNode) { overlay.parentNode.removeChild(overlay); } } }; } /** * Adds the CSS styles for a 3D cube loader overlay to the page. * * @returns {void} */ export function addCubeStyle() { const style = document.createElement('style'); style.textContent = ` .press3d-cube-loader { position: relative; width: 40px; height: 40px; transform-style: preserve-3d; animation: press3d-cube-spin 2s infinite linear; } .press3d-cube-face { position: absolute; width: 40px; height: 40px; background: rgba(0, 0, 0, 0.02); border: 1px solid #888; box-sizing: border-box; box-shadow: 0 0 0 0.3px rgba(255,255,255,0.7); } .press3d-cube-face:nth-child(1) { transform: rotateY(0deg) translateZ(20px); } .press3d-cube-face:nth-child(2) { transform: rotateY(90deg) translateZ(20px); } .press3d-cube-face:nth-child(3) { transform: rotateY(180deg) translateZ(20px); } .press3d-cube-face:nth-child(4) { transform: rotateY(-90deg) translateZ(20px); } .press3d-cube-face:nth-child(5) { transform: rotateX(90deg) translateZ(20px); } .press3d-cube-face:nth-child(6) { transform: rotateX(-90deg) translateZ(20px); } @keyframes press3d-cube-spin { 0% { transform: rotateX(0deg) rotateY(0deg); } 100% { transform: rotateX(360deg) rotateY(360deg); } } `; document.head.appendChild(style); } /** * Creates a 3D cube loader overlay inside the given element. * * @param {HTMLElement} elem The element that should contain the cube loader. * @returns {Object} An object with a show and hide method. */ export function createCubeInElement(elem: HTMLElement) { const overlay = document.createElement('div'); overlay.style.position = 'absolute'; overlay.style.top = '0'; overlay.style.left = '0'; overlay.style.width = '100%'; overlay.style.height = '100%'; overlay.style.display = 'flex'; overlay.style.alignItems = 'center'; overlay.style.justifyContent = 'center'; overlay.style.background = 'transparent'; overlay.style.perspective = '1000px'; // Necessary for 3D effect overlay.innerHTML = `
`; elem.style.position = 'relative'; elem.appendChild(overlay); addCubeStyle(); return { show: () => { overlay.style.display = 'flex'; }, hide: () => { overlay.style.display = 'none'; }, destroy: () => { if (overlay.parentNode) { overlay.parentNode.removeChild(overlay); } } }; }