feat(item_lab): agregar formulario y funcionalidad para gestión de items con vista previa 3D
This commit is contained in:
85
src/server/public/assets/js/item_lab.js
Normal file
85
src/server/public/assets/js/item_lab.js
Normal file
@@ -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();
|
||||||
|
})();
|
||||||
8
src/server/public/assets/js/three.min.js
vendored
Normal file
8
src/server/public/assets/js/three.min.js
vendored
Normal file
@@ -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);
|
||||||
|
})();
|
||||||
|
}
|
||||||
61
src/server/public/views/item_lab.ejs
Normal file
61
src/server/public/views/item_lab.ejs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<%- include('partials/head') %>
|
||||||
|
<div id="item-lab-root" class="p-6">
|
||||||
|
<h1 class="text-xl font-bold mb-4">Item Lab</h1>
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<form id="itemLabForm" class="space-y-4 bg-[#071a2a] p-4 rounded">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<div class="flex-1">
|
||||||
|
<label>Key</label>
|
||||||
|
<input id="labKey" class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<label>Name</label>
|
||||||
|
<input id="labName" class="w-full" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<div class="flex-1">
|
||||||
|
<label>Category</label>
|
||||||
|
<input id="labCategory" class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<label>Icon</label>
|
||||||
|
<input id="labIcon" class="w-full" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Description</label>
|
||||||
|
<textarea id="labDescription" class="w-full h-20"></textarea>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Tags (comma)</label>
|
||||||
|
<input id="labTags" class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Props (JSON)</label>
|
||||||
|
<textarea id="labProps" class="w-full h-32 font-mono"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2 justify-end">
|
||||||
|
<button type="button" id="labReset" class="btn">Reset</button>
|
||||||
|
<button type="submit" id="labSave" class="btn btn-primary">Guardar</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="mb-4 bg-[#071a2a] p-4 rounded">
|
||||||
|
<h2 class="font-semibold mb-2">Preview 3D</h2>
|
||||||
|
<div id="lab3d" style="width:100%;height:300px;background:#001827;border-radius:6px;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-[#071a2a] p-4 rounded">
|
||||||
|
<h2 class="font-semibold mb-2">Preview JSON</h2>
|
||||||
|
<pre id="labPreview" class="text-sm max-h-60 overflow-auto p-2"></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="/assets/js/three.min.js"></script>
|
||||||
|
<script src="/assets/js/item_lab.js"></script>
|
||||||
|
<%- include('partials/foot') %>
|
||||||
@@ -929,6 +929,7 @@ export const server = createServer(
|
|||||||
const guildsJson = guildsRes.ok ? await guildsRes.json() : [];
|
const guildsJson = guildsRes.ok ? await guildsRes.json() : [];
|
||||||
|
|
||||||
// Filter guilds where user is owner or has ADMINISTRATOR bit
|
// Filter guilds where user is owner or has ADMINISTRATOR bit
|
||||||
|
|
||||||
const ADMIN_BIT = 0x8;
|
const ADMIN_BIT = 0x8;
|
||||||
const adminGuilds = (
|
const adminGuilds = (
|
||||||
Array.isArray(guildsJson) ? guildsJson : []
|
Array.isArray(guildsJson) ? guildsJson : []
|
||||||
@@ -1918,6 +1919,7 @@ export const server = createServer(
|
|||||||
if (parts.length >= 2) {
|
if (parts.length >= 2) {
|
||||||
const guildId = parts[1];
|
const guildId = parts[1];
|
||||||
const page = parts[2] || "overview";
|
const page = parts[2] || "overview";
|
||||||
|
|
||||||
const fragment =
|
const fragment =
|
||||||
url.searchParams.get("fragment") || url.searchParams.get("ajax");
|
url.searchParams.get("fragment") || url.searchParams.get("ajax");
|
||||||
// find a nicer display name for selected guild
|
// find a nicer display name for selected guild
|
||||||
|
|||||||
Reference in New Issue
Block a user