ความคืบหน้างาน ไฟล์ต้นฉบับ

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);
})();