Files
dtf-system/web/site-upload.js
Cauê Faleiros 64e47ee481
All checks were successful
Build and deploy / Validate source (push) Successful in 6s
Build and deploy / Integration suite on a real stack (push) Successful in 2m30s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 57s
feat: skeletons where the page waits, no flicker while a sheet is read, repeated copies in the preview
Reading a large sheet repainted the list and "Sua folha" on every progress
step, so the preview flickered; progress now moves the bar in place and
"Sua folha" shows a skeleton until the preview is ready. A sheet printed
N times is shown N times (up to six, then a count). The payment page shows
skeletons while the order loads and until the card form is ready.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 15:52:58 -03:00

149 lines
6.9 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 auto=fs.filter(f=>AUTO.test(f.name)).length;
const manual=fs.length-auto;
if(caminho==='auto' && manual){
caminho='tabela';
avisoCam('<b>Trocamos o caminho para você.</b> Você marcou conferência automática, '+
'mas '+(manual>1? manual+' arquivos vieram':'o arquivo veio')+' em formato que não '+
'abrimos. O metro vai pela tabela, '+rs(TABELA[modo])+'.');
pintaCaminhos();
}else if(caminho==='tabela' && auto){
caminho='auto';
avisoCam('<b>Boa notícia.</b> Você marcou o caminho sem conferência, mas '+
(auto>1? 'os arquivos vieram':'o arquivo veio')+' em formato que a gente confere. '+
'Vamos avaliar e o metro pode cair até '+rs(pisoEscada())+'.');
pintaCaminhos();
}
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; pintaFolha();
let mostrado=0;
previaPngGrande(x.f, 600, p=>{
const pct=60+Math.floor(p*38);
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);
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; 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){
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){
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; }
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