tracker/static/usecase-export.js
// Export the complete diagram in world coordinates, independent of viewport/zoom.
(() => {
const NS='http://www.w3.org/2000/svg';
function init(){
const root=document.getElementById('uc-editor');if(!root||root.dataset.exportReady)return;root.dataset.exportReady='1';
const $=id=>root.querySelector('#uc-'+id),dialog=$('export-dialog');let busy=false;
const paint=['fill','fill-opacity','stroke','stroke-width','stroke-opacity','stroke-dasharray','stroke-linecap','stroke-linejoin','opacity','font-family','font-size','font-weight','font-style','letter-spacing','word-spacing','text-anchor','dominant-baseline','paint-order','white-space'];
function snapshot(){
const svg=$('svg'),content=svg.querySelector('.uc-graph');if(!content||!content.children.length)throw new Error('เพิ่ม shape ก่อนส่งออก PNG');
const box=content.getBBox(),padding=32,width=Math.max(1,Math.ceil(box.width+padding*2)),height=Math.max(1,Math.ceil(box.height+padding*2));
const copy=svg.cloneNode(true),sourceNodes=[svg,...svg.querySelectorAll('*')],targetNodes=[copy,...copy.querySelectorAll('*')];
for(let i=0;i<sourceNodes.length;i++){
const from=sourceNodes[i],to=targetNodes[i],computed=getComputedStyle(from);to.removeAttribute('tabindex');to.removeAttribute('role');to.removeAttribute('aria-label');
for(const property of paint){const value=computed.getPropertyValue(property);if(value)to.style.setProperty(property,value);}
// Remove selection outlines from the exported document.
if(from.classList.contains('uc-shape')){const type=from.closest('[data-node]').dataset.type,key=type==='start'?'--good':type==='end'?'--bad':type==='decision'?'--violet':'--muted';to.style.stroke=getComputedStyle(root).getPropertyValue(key).trim();to.style.strokeWidth='1.5';}
if(from.classList.contains('uc-edge-line')){to.style.stroke=getComputedStyle(root).getPropertyValue('--muted').trim();to.style.strokeWidth='2';}
}
copy.querySelectorAll('.uc-grid,.uc-port,.uc-wire-preview,title').forEach(el=>el.remove());
copy.querySelectorAll('.is-selected').forEach(el=>el.classList.remove('is-selected'));
copy.setAttribute('xmlns',NS);copy.setAttribute('width',width);copy.setAttribute('height',height);copy.setAttribute('viewBox',`${box.x-padding} ${box.y-padding} ${width} ${height}`);
return {xml:new XMLSerializer().serializeToString(copy),width,height};
}
$('export-png').addEventListener('click',()=>{
$('export-png').closest('details').open=false;$('export-status').dataset.error='false';$('export-status').textContent='PNG · Full diagram · ไม่รวมกริดและจุดลาก';dialog.showModal();
});
const close=()=>{if(!busy)dialog.close()};$('export-close').onclick=close;$('export-cancel').onclick=close;dialog.addEventListener('cancel',e=>{if(busy)e.preventDefault()});
$('export-form').addEventListener('submit',async e=>{
e.preventDefault();if(busy)return;busy=true;$('export-download').disabled=true;$('export-close').disabled=true;$('export-cancel').disabled=true;$('export-status').dataset.error='false';$('export-status').textContent='กำลังสร้าง PNG…';let imageURL;
try{
await document.fonts.ready;const {xml,width,height}=snapshot();
// Keep a full diagram export within browser canvas memory/dimension limits.
const scale=Math.min(2,16384/width,16384/height,Math.sqrt(16000000/(width*height)));
const canvas=document.createElement('canvas');canvas.width=Math.max(1,Math.floor(width*scale));canvas.height=Math.max(1,Math.floor(height*scale));
const ctx=canvas.getContext('2d');if(!ctx)throw new Error('ไม่สามารถสร้างภาพ PNG ได้');
if($('export-form').elements.background.value==='white'){ctx.fillStyle='#ffffff';ctx.fillRect(0,0,canvas.width,canvas.height);}
imageURL=URL.createObjectURL(new Blob([xml],{type:'image/svg+xml;charset=utf-8'}));
const img=new Image();await new Promise((resolve,reject)=>{img.onload=resolve;img.onerror=()=>reject(new Error('ไม่สามารถอ่านภาพ diagram ได้'));img.src=imageURL});ctx.drawImage(img,0,0,canvas.width,canvas.height);
const blob=await new Promise(resolve=>canvas.toBlob(resolve,'image/png'));if(!blob)throw new Error('ไม่สามารถสร้างไฟล์ PNG ได้');
const href=URL.createObjectURL(blob),link=document.createElement('a');link.href=href;link.download=root.dataset.uid+'-workflow-'+$('export-form').elements.background.value+'.png';document.body.append(link);link.click();link.remove();setTimeout(()=>URL.revokeObjectURL(href),60000);
$('export-status').textContent='ส่งออก PNG แล้ว · '+canvas.width+' × '+canvas.height+' px';
}catch(err){$('export-status').dataset.error='true';$('export-status').textContent=err.message||'ส่งออกไม่สำเร็จ กรุณาลองใหม่';}
finally{if(imageURL)URL.revokeObjectURL(imageURL);busy=false;$('export-download').disabled=false;$('export-close').disabled=false;$('export-cancel').disabled=false;}
});
}
init();document.addEventListener('tracker:updated',init);
})();