diff --git a/src/server/public/assets/js/item_lab.js b/src/server/public/assets/js/item_lab.js new file mode 100644 index 0000000..be100df --- /dev/null +++ b/src/server/public/assets/js/item_lab.js @@ -0,0 +1,85 @@ +(function(){ + const $ = id => document.getElementById(id); + const guildId = (() => { try{ return (document.getElementById('itemsRoot') && document.getElementById('itemsRoot').dataset && document.getElementById('itemsRoot').dataset.guildId) || ''; }catch(e){ return ''; } })(); + + // form elements + const form = $('itemLabForm'); + const labKey = $('labKey'); + const labName = $('labName'); + const labCategory = $('labCategory'); + const labIcon = $('labIcon'); + const labDescription = $('labDescription'); + const labTags = $('labTags'); + const labProps = $('labProps'); + const labPreview = $('labPreview'); + const labReset = $('labReset'); + + function resetForm(){ labKey.value=''; labName.value=''; labCategory.value=''; labIcon.value=''; labDescription.value=''; labTags.value=''; labProps.value='{}'; renderPreview(); } + function renderPreview(){ + const payload = { + key: labKey.value.trim(), + name: labName.value.trim(), + category: labCategory.value.trim(), + icon: labIcon.value.trim(), + description: labDescription.value.trim(), + tags: labTags.value.split(',').map(s=>s.trim()).filter(Boolean), + props: (()=>{ try{ return JSON.parse(labProps.value||'{}'); }catch(e){ return labProps.value||"{}"; } })() + }; + labPreview.textContent = JSON.stringify(payload, null, 2); + // update 3D preview (simple animate color based on key length) + if(window.lab3D && typeof window.lab3D.update === 'function') window.lab3D.update(payload); + } + + if(form){ + form.addEventListener('input', renderPreview); + form.addEventListener('submit', async (ev)=>{ + ev.preventDefault(); + const payload = { + key: labKey.value.trim(), + name: labName.value.trim(), + category: labCategory.value.trim(), + icon: labIcon.value.trim(), + description: labDescription.value.trim(), + tags: labTags.value.split(',').map(s=>s.trim()).filter(Boolean), + props: (()=>{ try{ return JSON.parse(labProps.value||'{}'); }catch(e){ alert('JSON inválido en Props'); throw e; } })() + }; + try{ + if(!guildId){ alert('No guildId disponible'); return; } + const res = await fetch('/api/dashboard/' + encodeURIComponent(guildId) + '/items', { method:'POST', headers:{ 'Content-Type':'application/json' }, body: JSON.stringify(payload) }); + if(!res.ok){ alert('Error al guardar: HTTP ' + res.status); return; } + alert('Item guardado'); + resetForm(); + }catch(e){ alert('Error guardando: ' + (e && e.message)); } + }); + labReset.addEventListener('click', resetForm); + } + + // minimal three.js preview + function init3D(){ + try{ + const container = $('lab3d'); + const scene = new THREE.Scene(); + const camera = new THREE.PerspectiveCamera(45, container.clientWidth / container.clientHeight, 0.1, 1000); + camera.position.z = 5; + const renderer = new THREE.WebGLRenderer({ antialias:true }); + renderer.setSize(container.clientWidth, container.clientHeight); + container.appendChild(renderer.domElement); + const geometry = new THREE.BoxGeometry(1.5,1.5,1.5); + const material = new THREE.MeshStandardMaterial({ color: 0x336699 }); + const cube = new THREE.Mesh(geometry, material); + scene.add(cube); + const light = new THREE.DirectionalLight(0xffffff, 1); + light.position.set(5,5,5); scene.add(light); + + function animate(){ requestAnimationFrame(animate); cube.rotation.x += 0.01; cube.rotation.y += 0.01; renderer.render(scene, camera); } + animate(); + + window.lab3D = { + update(payload){ try{ const k = (payload && payload.key) ? payload.key.length : 1; const c = Math.max(0.2, Math.min(1, (k%10)/10 + 0.2)); material.color.setRGB(c*0.2, c*0.5, c*0.8); }catch(e){} } + }; + window.addEventListener('resize', ()=>{ renderer.setSize(container.clientWidth, container.clientHeight); camera.aspect = container.clientWidth / container.clientHeight; camera.updateProjectionMatrix(); }); + }catch(e){ console.warn('3D init failed', e); } + } + + resetForm(); renderPreview(); init3D(); +})(); diff --git a/src/server/public/assets/js/three.min.js b/src/server/public/assets/js/three.min.js new file mode 100644 index 0000000..3714b5f --- /dev/null +++ b/src/server/public/assets/js/three.min.js @@ -0,0 +1,8 @@ +/* three.js placeholder: using a tiny minimal build for preview. In production prefer to vendor a proper three.js build. */ +// Very small shim mapping to real three if available in node_modules; here we assume the app will have /assets/js/three.min.js replaced with official build. +// For now include CDN loader fallback +if(typeof THREE === 'undefined'){ + (function(){ + var s=document.createElement('script'); s.src='https://unpkg.com/three@0.153.0/build/three.min.js'; s.onload=function(){ console.log('three loaded'); }; document.head.appendChild(s); + })(); +} diff --git a/src/server/public/views/item_lab.ejs b/src/server/public/views/item_lab.ejs new file mode 100644 index 0000000..954acd8 --- /dev/null +++ b/src/server/public/views/item_lab.ejs @@ -0,0 +1,61 @@ +<%- include('partials/head') %> +
+

Item Lab

+
+
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+

Preview 3D

+
+
+ +
+

Preview JSON

+

+      
+
+
+
+ + +<%- include('partials/foot') %> diff --git a/src/server/server.ts b/src/server/server.ts index 91a5420..6313aa4 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -929,6 +929,7 @@ export const server = createServer( const guildsJson = guildsRes.ok ? await guildsRes.json() : []; // Filter guilds where user is owner or has ADMINISTRATOR bit + const ADMIN_BIT = 0x8; const adminGuilds = ( Array.isArray(guildsJson) ? guildsJson : [] @@ -1918,6 +1919,7 @@ export const server = createServer( if (parts.length >= 2) { const guildId = parts[1]; const page = parts[2] || "overview"; + const fragment = url.searchParams.get("fragment") || url.searchParams.get("ajax"); // find a nicer display name for selected guild