Files
dtf-system/web/site-upload.js
Cauê Faleiros bee7f8f245
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 3m2s
Build and deploy / Secret scan and release gate (push) Successful in 7s
Build and deploy / Publish images (push) Successful in 1m0s
feat: the preview of a large file says how long is left, not a percentage
Reading a large PNG in the browser for its preview showed "gerando a
prévia… 87%". It now shows the time left, worked out from how long the
reading has taken so far: "faltam cerca de 25 s", "faltam cerca de 3 min",
"quase pronto". The bar still fills as it goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:18:21 -03:00

158 lines
7.4 KiB
JavaScript

/* Site DTF — Accepting files: the drop zone, the accept rules, first measurement.
Extracted verbatim from the single inline script in web/index.html.
Loaded as classic scripts in the order listed there: they share one global
scope and run top to bottom, exactly as the original did. */
// ── upload
// Untrusted filenames and recovered cart text must never become HTML.
function escapeHTML(value){
return String(value).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
}
function recusa(lista){
const el=$('recusa'); if(!el) return;
if(!lista.length){ el.style.display='none'; el.innerHTML=''; return; }
const exts=escapeHTML([...new Set(lista.map(f=>extDe(f.name)))].join(', '));
el.style.display='block';
el.innerHTML = ehFolha()
? '<b>'+exts+' não dá para enviar aqui.</b> A folha pronta aceita PNG, JPG, TIFF, '+
'PDF, PSD, AI e CDR.'
: '<b>'+exts+' não dá para enviar aqui.</b> Artes avulsas precisam de PNG ou JPG com '+
'fundo transparente — é o que a montagem enxerga para encaixar. '+
'Se o seu arquivo é CDR, AI ou PSD, exporte em PNG ou use <b>Arquivo por metro</b>.';
}
const Z=$('zona'), I=$('inp');
Z.addEventListener('click',()=>I.click());
Z.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){I.click();e.preventDefault();}});
['dragover','dragenter'].forEach(ev=>Z.addEventListener(ev,e=>{e.preventDefault();Z.classList.add('sobre')}));
['dragleave','drop'].forEach(ev=>Z.addEventListener(ev,e=>{
e.preventDefault(); Z.classList.remove('sobre');
if(ev==='drop') sel([...e.dataTransfer.files]);
}));
I.addEventListener('change',()=>sel([...I.files]));
function sel(fs){
if(!fs.length) return;
// Pixels cannot tell a loose artwork from a finished sheet, so the customer
// declares it by choosing the product in the selector above. The declaration is
// verified in medirFolha; the file never changes product or price on its own.
const regra = ehFolha() ? ACEITA.folha : ACEITA.avulsa;
const fora = fs.filter(f=>!regra.test(f.name));
fs = fs.filter(f=>regra.test(f.name));
recusa(fora);
const max=window.dtfUploadMaxBytes||5*1073741824;
const grandes=fs.filter(f=>f.size>max);
if(grandes.length){
const el=$('recusa');
el.style.display='block';
el.innerHTML='<b>Arquivo acima do limite de '+(max/1073741824).toFixed(0)+
' GB.</b> Divida a folha em partes menores antes de enviar.';
fs=fs.filter(f=>f.size<=max);
}
if(!fs.length) return;
if(ehFolha()){
const novas=fs.map(f=>({f, med:null, rep:1, m:0, semAnalise:null}));
folhas=folhas.concat(novas); // soma, não substitui
pintaFolha();
novas.forEach(x=>{
x.pct=5; pintaFolha();
medirFolha(x.f).then(md=>{
if(md && md.rejected){
x.measurementError=md.reason; x.pct=null; pintaFolha(); return;
}
if(md && md.dpiFolha!=null && md.dpiFolha<DPI_RECUSA) return recusaFolha(x, md);
x.med=md; if(md) x.m=md.alt/100; x.pct=55; pintaFolha();
if(md && md.grande){
// Graded at once from its size in pixels. A PNG is then read as a
// stream into a small copy: the preview, and the sheet check.
x.an={dpi:md.dpiFolha, grande:true, artes:0, residuos:0, encostadas:0,
fundoChapado:false, aproveitamento:0, alturaCm:md.alt, fonteDpi:'arquivo'};
if(!/\.png$/i.test(x.f.name)){ x.pct=null; pintaFolha(); return; }
x.pct=60; x.leitura={inicio:Date.now(), p:0}; pintaFolha();
let mostrado=0;
previaPngGrande(x.f, 600, p=>{
const pct=60+Math.floor(p*38);
x.leitura.p=p;
if(pct-mostrado>=1){ mostrado=pct; x.pct=pct; pintaProgressoFolha(x); }
}).then(cv=>{
if(cv && folhas.includes(x)){
x.previewSrc=cv.toDataURL('image/webp',0.85); x.miniSrc=miniatura(cv);
try{
const an=analisarFolha(cv, md.larg);
x.an={...an, dpi:md.dpiFolha, fonteDpi:'arquivo', grande:false};
}catch(e){}
}
}).catch(()=>{}).finally(()=>{ x.pct=null; pintaFolha(); });
}else if(md && RENDERIZA.test(x.f.name)){
carregarImagem(x.f).then(img=>{
if(img){ x.previewSrc=img.src; x.miniSrc=miniatura(img); try{ x.an=analisarFolha(img, md.larg); if(x.an) x.an.fonteDpi='arquivo'; }
catch(e){} }
x.pct=null; pintaFolha();
});
}else if(md && /\.pdf$/i.test(x.f.name) && x.f.size>GRANDE_BYTES){
x.semAnalise='PDF grande: a resolução das imagens de dentro é conferida pela equipe';
x.pct=null; pintaFolha();
}else if(md && /\.pdf$/i.test(x.f.name)){
x.pct=70; pintaFolha();
rasterizarPdf(x.f, md.larg, md.alt).then(r=>{
if(r && r.erro){
x.semAnalise = r.erro + (r.semWorker
? ' · este navegador não deixa o leitor rodar em segundo plano'
: '');
}
if(r && r.tela){
x.miniSrc=miniatura(r.tela);
try{
x.an=analisarFolha(r.tela, md.larg);
// vetor puro não tem resolução · nota máxima, e está certo
x.an.dpi = r.dpi==null ? DPI_AVISA : r.dpi;
x.an.vetor = r.dpi==null;
x.an.fonteDpi = r.dpi==null ? 'vetor' : 'imagens';
x.an.res = r.res || null;
}catch(e){}
}
x.pct=null; pintaFolha();
});
}else{ x.pct=null; pintaFolha(); }
});
});
return;
}
const novos=fs.map(f=>({f,px:0,py:0,cm:0,q:1,prop:1,giro:0,esp:false}));
artes=novos.concat(artes); // a mais recente fica no topo da fila
recemChegada=novos[0];
pintaArtes();
$('lista').scrollIntoView({behavior:'smooth',block:'nearest'});
Promise.all(novos.map(medir)).then(pintaArtes);
}
function medir(a){
// A large artwork is measured from its header and packed from a small copy
// read as a stream (previaPngGrande); the file itself is never decoded.
if(a.f.size>GRANDE_BYTES){
return dimensoesImagem(a.f).then(async d=>{
if(!d || !(d.w>0) || !(d.h>0)){ a.decodeError=true; return a; }
a.px=d.w; a.py=d.h; a.prop=d.h/d.w; a.grande=true;
// JPG and WebP cannot be read in parts: the art is placed by its
// measurements, as a full box, and the team checks it before printing.
if(!/\.png$/i.test(a.f.name)){ a.semPrevia=true; return a; }
a.lendo=0; a.leitura={inicio:Date.now(), p:0}; pintaArtes();
let mostrado=0;
const cv=await previaPngGrande(a.f, 1200, p=>{
const pct=Math.floor(p*100);
a.leitura.p=p;
if(pct-mostrado>=1){ mostrado=pct; a.lendo=pct; pintaProgressoArte(a); }
}).catch(()=>null);
a.lendo=null;
if(cv && artes.includes(a)){ a.src=cv.toDataURL('image/webp',0.9); a.miniSrc=miniatura(cv); }
else if(!cv) a.decodeError=true;
return a;
}).catch(()=>{ a.decodeError=true; return a; });
}
return carregarImagem(a.f).then(img=>{
if(img){ a.prop=img.height/img.width; a.px=img.width; a.py=img.height; a.src=img.src; a.miniSrc=miniatura(img); }
else a.decodeError=true;
return a;
});
}
// A metragem sai do arquivo, nunca do peso em bytes.
// imagem → proporção da imagem aplicada à largura do filme
// PDF → página única lida pelo PDF.js, com boxes, rotação e UserUnit
// TIFF, PSD, AI, CDR → o navegador não abre; o cliente informa o comprimento