← Commit history

Merge PR #147: Frame the active silkscreen group directly in recorded replays

John Lauer ·c41f64cd59 ·20d ago ·parent 19e55ba
1 file changed +1−1
tools/silkscreen-dashboard/index.html+1−1
@@ -49,7 +49,7 @@ function manualCamera(){cancelAnimationFrame(cameraAnimation);$('#follow').check canvas.addEventListener('wheel',e=>{e.preventDefault();if(!view||!e.deltaY)return;manualCamera();const r=canvas.getBoundingClientRect(),delta=e.deltaY*(e.deltaMode===1?16:e.deltaMode===2?r.height:1),requested=Math.exp(Math.max(-1,Math.min(1,delta*.0015))),span=Math.max(view[2]-view[0],view[3]-view[1]),f=Math.max(.5,Math.min(1000,span*requested))/span,p=boardPoint(e.clientX,e.clientY);view=view.map((v,i)=>p[i%2]+(v-p[i%2])*f);draw()},{passive:false}); canvas.onpointerdown=e=>{if(!view)return;manualCamera();drag={x:e.clientX,y:e.clientY,v:[...view]};canvas.setPointerCapture(e.pointerId)};canvas.onpointermove=e=>{if(!drag)return;const a=boardPoint(drag.x,drag.y,drag.v),b=boardPoint(e.clientX,e.clientY,drag.v),dx=b[0]-a[0],dy=b[1]-a[1];view=[drag.v[0]-dx,drag.v[1]-dy,drag.v[2]-dx,drag.v[3]-dy];draw()};canvas.onpointerup=canvas.onpointercancel=()=>drag=null; -const captureCanvas=document.createElement('canvas');captureCanvas.width=1920;captureCanvas.height=1080;const captureCtx=captureCanvas.getContext('2d');function paintCapture(){paint(captureCtx,1920,1080,view);captureCtx.fillStyle='#0d1117e8';captureCtx.fillRect(0,0,1920,50);captureCtx.fillStyle='#e6edf3';captureCtx.font='22px sans-serif';captureCtx.fillStyle=state.layer==='B.SilkS'?'#79b8ff':'#ffd479';captureCtx.fillText('Adom AI Flow · '+(state.layer==='F.SilkS'?'Top, viewed from above':'Bottom, viewed from below')+' silkscreen · '+$('#textsize').textContent,20,32)}+const captureCanvas=document.createElement('canvas');captureCanvas.width=1920;captureCanvas.height=1080;const captureCtx=captureCanvas.getContext('2d');function captureExtent(){if(state.summary||!state.playing)return bounds;const e=history[state.cursor],children=e?.candidate?.children||[];const boxes=children.map(c=>c.box).filter(Boolean);for(const child of children){if(child.reference)for(const o of data.obstacles)if(o.reference===child.reference&&o.layer===state.layer&&o.box)boxes.push(o.box)}if(!boxes.length)return bounds;const b=[Math.min(...boxes.map(b=>b[0])),Math.min(...boxes.map(b=>b[1])),Math.max(...boxes.map(b=>b[2])),Math.max(...boxes.map(b=>b[3]))],cx=(b[0]+b[2])/2,cy=(b[1]+b[3])/2,w=Math.max(18,b[2]-b[0]+6),h=Math.max(10,b[3]-b[1]+6);return [cx-w/2,cy-h/2,cx+w/2,cy+h/2]}function paintCapture(){paint(captureCtx,1920,1080,captureExtent());captureCtx.fillStyle='#0d1117e8';captureCtx.fillRect(0,0,1920,50);captureCtx.fillStyle='#e6edf3';captureCtx.font='22px sans-serif';captureCtx.fillStyle=state.layer==='B.SilkS'?'#79b8ff':'#ffd479';captureCtx.fillText('Adom AI Flow · '+(state.layer==='F.SilkS'?'Top, viewed from above':'Bottom, viewed from below')+' silkscreen · '+$('#textsize').textContent,20,32)} recordReplay=async function(summary=false){if(recording)return;const latest=history.map((e,i)=>e.kind==='start'?i:-1).filter(i=>i>=0).pop()||0;await api('control',{mode:'replay',playing:false,summary,cursor:latest-1,view:'2d'});$('#follow').checked=!summary;frame(bounds);recording=true;const chunks=[],rec=new MediaRecorder(captureCanvas.captureStream(30),{mimeType:'video/webm'});rec.ondataavailable=e=>chunks.push(e.data);rec.onstop=()=>{recording=false;const blob=new Blob(chunks,{type:'video/webm'}),a=document.createElement('a');window.silkscreen.recordingBlob=blob;const reader=new FileReader();reader.onload=()=>api('recording',{kind:summary?'overview':'walkthrough',video:reader.result.split(',')[1]}).catch(e=>$('#error').textContent=e.message);reader.readAsDataURL(blob);a.href=URL.createObjectURL(blob);a.download='silkscreen-replay.webm';a.click();$('#record').textContent='Record replay';draw()};paintCapture();rec.start();$('#record').textContent='Recording replay…';await api('control',{playing:true});const timer=setInterval(()=>{draw();paintCapture();if(!state.playing&&state.cursor>=history.length-1){clearInterval(timer);frame(bounds);const hold=setInterval(paintCapture,33);setTimeout(()=>{clearInterval(hold);paintCapture();rec.stop()},1200)}},100)};$('#record').onclick=()=>recordReplay(false);$('#short').onclick=()=>recordReplay(true); let infoTimer;const brand=$('.brand'),info=$('#appinfo');brand.onmouseenter=()=>{clearTimeout(infoTimer);infoTimer=setTimeout(()=>info.hidden=false,600)};brand.onmouseleave=()=>{clearTimeout(infoTimer);infoTimer=setTimeout(()=>info.hidden=true,220)};brand.onfocus=()=>info.hidden=false;document.addEventListener('keydown',e=>{if(e.key==='Escape'){if($('#settings').open)$('#settings').close();info.hidden=true}}); new ResizeObserver(draw).observe($('#stage'));draw();}catch(e){$('#error').textContent=e.stack}