Gang Sheet Builder
×
(function(){const canvas=document.getElementById('gs-canvas');const ctx=canvas.getContext('2d');let W=canvas.width,H=canvas.height;const gridSize=50;const state={items:[],selectedIndex:-1,dragging:!1,dragOffset:{x:0,y:0},resizing:!1,rotate:!1};const upload=document.getElementById('gs-upload');const clearBtn=document.getElementById('gs-clear');const exportBtn=document.getElementById('gs-export');const sendBtn=document.getElementById('gs-send');const gridToggle=document.getElementById('gs-grid');const snapToggle=document.getElementById('gs-snap');
function draw(){ctx.clearRect(0,0,W,H);ctx.fillStyle='#fff';ctx.fillRect(0,0,W,H);
if(gridToggle.checked){ctx.strokeStyle='#eee';ctx.lineWidth=1;for(let x=0;x<=W;x+=gridSize){ctx.beginPath();ctx.moveTo(x+0.5,0);ctx.lineTo(x+0.5,H);ctx.stroke()} for(let y=0;y<=H;y+=gridSize){ctx.beginPath();ctx.moveTo(0,y+0.5);ctx.lineTo(W,y+0.5);ctx.stroke()}} state.items.forEach((it,idx)=>{ctx.save();ctx.translate(it.x+it.w/2,it.y+it.h/2);ctx.rotate(it.rot*Math.PI/180);ctx.globalAlpha=it.opacity??1;ctx.drawImage(it.img,-it.w/2,-it.h/2,it.w,it.h);ctx.restore();
if(idx===state.selectedIndex){ctx.save();ctx.strokeStyle='#06f';ctx.lineWidth=2;ctx.translate(it.x+it.w/2,it.y+it.h/2);ctx.rotate(it.rot*Math.PI/180);ctx.strokeRect(-it.w/2,-it.h/2,it.w,it.h);ctx.restore()}})}
function addImage(img){let maxW=W*0.25;let scale=Math.min(1,maxW/img.width);const w=Math.round(img.width*scale);const h=Math.round(img.height*scale);state.items.push({img,x:10+state.items.length*10,y:10+state.items.length*10,w,h,rot:0});state.selectedIndex=state.items.length-1;draw()}
upload.addEventListener('change',e=>{const files=Array.from(e.target.files);files.forEach(f=>{const reader=new FileReader();reader.onload=function(ev){const im=new Image();im.onload=()=>addImage(im);im.src=ev.target.result};reader.readAsDataURL(f)});upload.value=''});
clearBtn.addEventListener('click',()=>{state.items=[];state.selectedIndex=-1;draw()});
document.getElementById('gs-resize').addEventListener('click',()=>{const w=parseInt(document.getElementById('gs-width').value)||1600;const h=parseInt(document.getElementById('gs-height').value)||1200;canvas.width=w;canvas.height=h;W=w;H=h;draw()});
function mouseToCanvas(ev){const rect=canvas.getBoundingClientRect();const x=(ev.clientX-rect.left)*(canvas.width/rect.width);const y=(ev.clientY-rect.top)*(canvas.height/rect.height);return{x,y}}
canvas.addEventListener('mousedown',ev=>{const pos=mouseToCanvas(ev);for(let i=state.items.length-1;i>=0;i--){const it=state.items[i];const cx=it.x+it.w/2,cy=it.y+it.h/2;const dx=pos.x-cx,dy=pos.y-cy;const angle=-it.rot*Math.PI/180;const rx=dx*Math.cos(angle)-dy*Math.sin(angle);const ry=dx*Math.sin(angle)+dy*Math.cos(angle);if(rx>=-it.w/2&&rx<=it.w/2&&ry>=-it.h/2&&ry<=it.h/2){state.selectedIndex=i;state.dragging=!0;state.dragOffset.x=pos.x-it.x;state.dragOffset.y=pos.y-it.y;draw();return}} state.selectedIndex=-1;draw()});window.addEventListener('mousemove',ev=>{if(!state.dragging)return;const pos=mouseToCanvas(ev);const it=state.items[state.selectedIndex];if(!it)return;let nx=pos.x-state.dragOffset.x;let ny=pos.y-state.dragOffset.y;
if(snapToggle.checked){nx=Math.round(nx/gridSize)*gridSize;ny=Math.round(ny/gridSize)*gridSize} it.x=nx;it.y=ny;draw()});
window.addEventListener('mouseup',ev=>{state.dragging=!1});
window.addEventListener('keydown',ev=>{const it=state.items[state.selectedIndex];if(!it)return;if(ev.key==='Delete'||ev.key==='Backspace'){state.items.splice(state.selectedIndex,1);state.selectedIndex=-1;draw()} if(ev.key==='+'||ev.key==='='){it.w*=1.05;it.h*=1.05;draw()} if(ev.key==='-'){it.w*=0.95;it.h*=0.95;draw()} if(ev.key==='r'){it.rot=(it.rot+15)%360;draw()} if(ev.key==='R'){it.rot=(it.rot-15)%360;draw()}});
exportBtn.addEventListener('click',()=>{const data=canvas.toDataURL('image/png');const w=window.open();w.document.write('
sendBtn.addEventListener('click',()=>{const data=canvas.toDataURL('image/png');const endpoint='/wp-json/gangsheet/v1/upload';fetch(endpoint,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({image:data})}).then(r=>r.json()).then(j=>alert('Server response: '+(j.message||JSON.stringify(j)))).catch(e=>alert('Send failed: '+e))});
draw()})()