Some checks failed
Build and deploy / Validate source (push) Successful in 6s
Build and deploy / Integration suite on a real stack (push) Failing after 2m36s
Build and deploy / Secret scan and release gate (push) Successful in 8s
Build and deploy / Publish images (push) Has been skipped
Artes avulsas decoded every file whole, and the packing decoded each one again on every repaint, so a large artwork failed to load. A PNG over 150 MB is now measured from its header (pixel size, so DPI and grade stay exact) and packed from a 1200-pixel copy read as a stream, with progress on its card; every artwork's image is loaded once and reused by the packing. A large JPG or WebP asks for a PNG, and the grade card no longer calls a file unreadable while it is still being read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
167 lines
7.8 KiB
JavaScript
167 lines
7.8 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=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[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){
|
|
// 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;
|
|
if(!/\.png$/i.test(a.f.name)){ a.decodeError='grande'; return a; }
|
|
a.lendo=0; pintaArtes();
|
|
let mostrado=0;
|
|
const cv=await previaPngGrande(a.f, 1200, p=>{
|
|
const pct=Math.floor(p*100);
|
|
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); 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; }
|
|
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
|