document.addEventListener('DOMContentLoaded', () => { // Tabs const tabUpload = document.getElementById('tab-upload'); const tabUrl = document.getElementById('tab-url'); const tabZip = document.getElementById('tab-zip'); const panelUpload = document.getElementById('panel-upload'); const panelUrl = document.getElementById('panel-url'); const panelZip = document.getElementById('panel-zip'); function showPanel(name) { [panelUpload, panelUrl, panelZip].forEach(p => p && p.classList.add('hidden')); [tabUpload, tabUrl, tabZip].forEach(t => { if (!t) return; t.classList.remove('bg-cyan-500/20', 'text-cyan-400', 'border-cyan-500/30'); t.classList.add('bg-slate-800', 'border-white/10', 'text-slate-400'); }); const activeTab = name === 'upload' ? tabUpload : name === 'url' ? tabUrl : tabZip; const activePanel = name === 'upload' ? panelUpload : name === 'url' ? panelUrl : panelZip; if (activeTab) { activeTab.classList.remove('bg-slate-800', 'border-white/10', 'text-slate-400'); activeTab.classList.add('bg-cyan-500/20', 'text-cyan-400', 'border-cyan-500/30'); } if (activePanel) activePanel.classList.remove('hidden'); } if (tabUpload) tabUpload.addEventListener('click', () => showPanel('upload')); if (tabUrl) tabUrl.addEventListener('click', () => showPanel('url')); if (tabZip) tabZip.addEventListener('click', () => showPanel('zip')); // License template selector const licenseSelect = document.getElementById('license-select'); const licenseInput = document.getElementById('license'); if (licenseSelect && licenseInput) { licenseSelect.addEventListener('change', () => { if (licenseSelect.value === 'custom') { licenseInput.classList.remove('hidden'); licenseInput.focus(); } else { licenseInput.classList.add('hidden'); licenseInput.value = licenseSelect.value; } }); } // ====== NORMAL UPLOAD ====== const dropZone = document.getElementById('drop-zone'); const fileInput = document.getElementById('file-input'); const fileNameDisplay = document.getElementById('file-name'); const uploadForm = document.getElementById('upload-form'); const submitBtn = document.getElementById('submit-btn'); const btnText = document.getElementById('btn-text'); const btnIcon = document.getElementById('btn-icon'); const tagsInput = document.getElementById('tags'); const tagsSuggestions = document.getElementById('tags-suggestions'); const partsList = document.getElementById('parts-list'); const imagesDropZone = document.getElementById('images-drop-zone'); const imagesInput = document.getElementById('images-input'); const imagesNameDisplay = document.getElementById('images-name'); let selectedFiles = []; let selectedImages = []; let allTags = []; loadExistingTags(); async function loadExistingTags() { try { allTags = await apiGet('/models/tags'); } catch (e) { console.error('Could not load tags', e); } } // Tag autocomplete if (tagsInput && tagsSuggestions) { tagsInput.addEventListener('input', () => { const val = tagsInput.value.toLowerCase(); const lastTag = val.split(',').pop().trim(); if (!lastTag || lastTag.length < 1) { tagsSuggestions.style.display = 'none'; return; } const matches = allTags.filter(t => t.name.includes(lastTag) && !val.includes(t.name)).slice(0, 5); if (matches.length === 0) { tagsSuggestions.style.display = 'none'; return; } tagsSuggestions.innerHTML = matches.map(t => `
${t.name}
` ).join(''); tagsSuggestions.style.display = 'block'; }); document.addEventListener('click', (e) => { if (!tagsInput.contains(e.target) && !tagsSuggestions.contains(e.target)) { tagsSuggestions.style.display = 'none'; } }); } window.selectTag = function(name) { if (!tagsInput) return; const parts = tagsInput.value.split(','); parts.pop(); parts.push(name); tagsInput.value = parts.join(', ') + ', '; if (tagsSuggestions) tagsSuggestions.style.display = 'none'; tagsInput.focus(); }; // 3D Files drop zone if (dropZone && fileInput) { dropZone.addEventListener('click', (e) => { if (e.target !== fileInput) fileInput.click(); }); ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, (e) => { e.preventDefault(); e.stopPropagation(); }, false); }); ['dragenter', 'dragover'].forEach(eventName => { dropZone.addEventListener(eventName, () => dropZone.classList.add('drop-active'), false); }); ['dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, () => dropZone.classList.remove('drop-active'), false); }); dropZone.addEventListener('drop', (e) => { const files = Array.from(e.dataTransfer.files).filter(f => f.name.toLowerCase().endsWith('.stl') || f.name.toLowerCase().endsWith('.3mf') ); if (files.length) handleFiles(files); }, false); fileInput.addEventListener('change', () => { if (fileInput.files.length) handleFiles(Array.from(fileInput.files)); }); } function handleFiles(files) { selectedFiles = files; renderPartsList(); const totalSize = files.reduce((sum, f) => sum + f.size, 0); if (fileNameDisplay) { fileNameDisplay.innerHTML = `${files.length} archivo(s) (${(totalSize / 1024).toFixed(1)} KB)`; } showToast(`${files.length} archivo(s) 3D seleccionado(s)`, 'success'); } function renderPartsList() { if (!partsList) return; if (selectedFiles.length === 0) { partsList.innerHTML = ''; return; } partsList.innerHTML = selectedFiles.map((f, i) => `
${i + 1}

${f.name}

${(f.size / 1024).toFixed(1)} KB

`).join(''); } // Images drop zone if (imagesDropZone && imagesInput) { imagesDropZone.addEventListener('click', (e) => { if (e.target !== imagesInput) imagesInput.click(); }); ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { imagesDropZone.addEventListener(eventName, (e) => { e.preventDefault(); e.stopPropagation(); }, false); }); ['dragenter', 'dragover'].forEach(eventName => { imagesDropZone.addEventListener(eventName, () => imagesDropZone.classList.add('drop-active'), false); }); ['dragleave', 'drop'].forEach(eventName => { imagesDropZone.addEventListener(eventName, () => imagesDropZone.classList.remove('drop-active'), false); }); imagesDropZone.addEventListener('drop', (e) => { const files = Array.from(e.dataTransfer.files).filter(f => f.name.toLowerCase().endsWith('.jpg') || f.name.toLowerCase().endsWith('.jpeg') || f.name.toLowerCase().endsWith('.png') ); if (files.length) handleImages(files); }, false); imagesInput.addEventListener('change', () => { if (imagesInput.files.length) handleImages(Array.from(imagesInput.files)); }); } function handleImages(files) { selectedImages = files; const totalSize = files.reduce((sum, f) => sum + f.size, 0); if (imagesNameDisplay) { imagesNameDisplay.innerHTML = `${files.length} imagen(es) (${(totalSize / 1024).toFixed(1)} KB)`; } showToast(`${files.length} imagen(es) seleccionada(s)`, 'success'); } if (uploadForm) { uploadForm.addEventListener('submit', async (e) => { e.preventDefault(); if (!selectedFiles.length) { showToast('Selecciona al menos un archivo 3D', 'error'); dropZone.scrollIntoView({ behavior: 'smooth', block: 'center' }); dropZone.classList.add('animate-pulse'); setTimeout(() => dropZone.classList.remove('animate-pulse'), 1000); return; } const titleEl = document.getElementById('title'); if (!titleEl || !titleEl.value.trim()) { showToast('El titulo es obligatorio', 'error'); titleEl.focus(); return; } const formData = new FormData(); selectedFiles.forEach(f => formData.append('files', f)); selectedImages.forEach(f => formData.append('images', f)); formData.append('title', titleEl.value.trim()); const descEl = document.getElementById('description'); const authorEl = document.getElementById('author'); const licenseEl = document.getElementById('license'); const tagsEl = document.getElementById('tags'); const catEl = document.getElementById('category'); if (descEl) formData.append('description', descEl.value); if (authorEl) formData.append('author', authorEl.value); if (licenseEl) formData.append('license', licenseEl.value); if (tagsEl) formData.append('tags', tagsEl.value); if (catEl) formData.append('category', catEl.value); // Collect part names const partNameInputs = document.querySelectorAll('.part-name-input'); const partNames = {}; partNameInputs.forEach(input => { const idx = input.getAttribute('data-idx'); if (input.value.trim()) { partNames[idx] = input.value.trim(); } }); if (Object.keys(partNames).length > 0) { formData.append('part_names', JSON.stringify(partNames)); } setLoading(true); try { const result = await apiPostForm('/models/', formData); showToast('Modelo subido correctamente', 'success'); window.location.href = '/model/' + result.id; } catch (err) { console.error(err); if (err.message.includes('already exists')) { showToast('Este archivo ya existe en el repositorio', 'error'); } else { showToast('Error al subir: ' + err.message, 'error'); } setLoading(false); } }); } // ====== IMPORT URL ====== const urlForm = document.getElementById('url-form'); if (urlForm) { urlForm.addEventListener('submit', async (e) => { e.preventDefault(); const urlEl = document.getElementById('url-input'); const titleEl = document.getElementById('url-title'); if (!urlEl || !urlEl.value.trim() || !titleEl || !titleEl.value.trim()) { showToast('URL y titulo son obligatorios', 'error'); return; } const formData = new FormData(); formData.append('url', urlEl.value.trim()); formData.append('title', titleEl.value.trim()); const desc = document.getElementById('url-description'); const author = document.getElementById('url-author'); const license = document.getElementById('url-license'); const tags = document.getElementById('url-tags'); const cat = document.getElementById('url-category'); if (desc) formData.append('description', desc.value); if (author) formData.append('author', author.value); if (license) formData.append('license', license.value); if (tags) formData.append('tags', tags.value); if (cat) formData.append('category', cat.value); const btn = urlForm.querySelector('button[type="submit"]'); const orig = btn.innerHTML; btn.disabled = true; btn.innerHTML = 'Importando...'; try { const result = await apiPostForm('/models/import-url', formData); showToast('Modelo importado correctamente', 'success'); window.location.href = '/model/' + result.id; } catch (err) { showToast('Error: ' + err.message, 'error'); btn.disabled = false; btn.innerHTML = orig; } }); } // ====== BULK ZIP ====== const zipDropZone = document.getElementById('zip-drop-zone'); const zipInput = document.getElementById('zip-input'); const zipNameDisplay = document.getElementById('zip-name'); const zipForm = document.getElementById('zip-form'); let selectedZip = null; if (zipDropZone && zipInput) { zipDropZone.addEventListener('click', (e) => { if (e.target !== zipInput) zipInput.click(); }); ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { zipDropZone.addEventListener(eventName, (e) => { e.preventDefault(); e.stopPropagation(); }, false); }); ['dragenter', 'dragover'].forEach(eventName => { zipDropZone.addEventListener(eventName, () => zipDropZone.classList.add('drop-active'), false); }); ['dragleave', 'drop'].forEach(eventName => { zipDropZone.addEventListener(eventName, () => zipDropZone.classList.remove('drop-active'), false); }); zipDropZone.addEventListener('drop', (e) => { const f = Array.from(e.dataTransfer.files).find(fi => fi.name.toLowerCase().endsWith('.zip')); if (f) handleZip(f); }, false); zipInput.addEventListener('change', () => { if (zipInput.files.length) handleZip(zipInput.files[0]); }); } function handleZip(file) { selectedZip = file; if (zipNameDisplay) { zipNameDisplay.innerHTML = `${file.name} (${(file.size / 1024).toFixed(1)} KB)`; } showToast('ZIP seleccionado', 'success'); } if (zipForm) { zipForm.addEventListener('submit', async (e) => { e.preventDefault(); if (!selectedZip) { showToast('Selecciona un archivo ZIP', 'error'); return; } const formData = new FormData(); formData.append('zip_file', selectedZip); const desc = document.getElementById('zip-description'); const author = document.getElementById('zip-author'); const license = document.getElementById('zip-license'); const tags = document.getElementById('zip-tags'); const cat = document.getElementById('zip-category'); if (desc) formData.append('description', desc.value); if (author) formData.append('author', author.value); if (license) formData.append('license', license.value); if (tags) formData.append('tags', tags.value); if (cat) formData.append('category', cat.value); const btn = zipForm.querySelector('button[type="submit"]'); const orig = btn.innerHTML; btn.disabled = true; btn.innerHTML = 'Procesando...'; try { const results = await apiPostForm('/models/bulk-zip', formData); showToast(`${results.length} modelo(s) importado(s)`, 'success'); setTimeout(() => window.location.href = '/', 1000); } catch (err) { showToast('Error: ' + err.message, 'error'); btn.disabled = false; btn.innerHTML = orig; } }); } function setLoading(v) { if (!submitBtn) return; submitBtn.disabled = v; if (v) { btnText.textContent = 'Subiendo...'; btnIcon.innerHTML = ''; btnIcon.classList.add('animate-spin'); } else { btnText.textContent = 'Subir Modelo'; btnIcon.innerHTML = ''; btnIcon.classList.remove('animate-spin'); } } });