feat: give Artes avulsas the file card of Folha já montada
All checks were successful
Build and deploy / Validate source (push) Successful in 10s
Build and deploy / Integration suite on a real stack (push) Successful in 2m49s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 54s

Each artwork gets the same card as a finished sheet: "Suas artes" with the
count of artworks and copies, a thumbnail, the size in cm with pixels,
format and weight, a DPI chip and how many fit per row, the resolution or
width hint, then width in cm, a copies stepper, rotate and mirror, and the
usual sizes. Packing, pricing and the hints are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Cauê Faleiros
2026-09-29 17:49:22 -03:00
parent a02711ef2b
commit 1b57313496
3 changed files with 66 additions and 33 deletions

View File

@@ -521,7 +521,7 @@ const n1=v=>v.toFixed(1).replace('.',',');
// Moves one artwork's progress in place, without repainting the list.
function pintaProgressoArte(a){
const el=$('lista').querySelector('.art[data-arte="'+artes.indexOf(a)+'"]');
const el=$('lista').querySelector('[data-arte="'+artes.indexOf(a)+'"]');
const barra=el && el.querySelector('.prog i'), texto=el && el.querySelector('.progT');
if(!barra){ pintaArtes(); return; }
barra.style.width=a.lendo+'%'; if(texto) texto.textContent='arquivo grande · gerando a prévia… '+a.lendo+'%';
@@ -529,26 +529,30 @@ function pintaProgressoArte(a){
function pintaArtes(){
invalidaItemAtual();
$('zona').classList.toggle('compacta', artes.length>0);
$('lista').innerHTML=artes.map((a,i)=>{
let barra='', dica='', calc='<span>informe a largura para ver a qualidade</span>';
// The same card as a finished sheet: thumbnail, size, a DPI chip, then the
// width (with the usual sizes), copies, rotation and mirroring.
const copiasTot=artes.reduce((t,a)=>t+(a.q||1),0);
$('lista').innerHTML=(artes.length? '<div class="listaCab"><h4>Suas artes</h4><span>'+
artes.length+' arte'+(artes.length>1?'s':'')+' · '+copiasTot+' cópia'+(copiasTot>1?'s':'')+'</span></div>' : '')+
artes.map((a,i)=>{
let barra='', dica='', chips='', aviso='', medida='<b class="pede">Informe a largura</b>';
if(a.lendo!=null){
barra='<div class="prog"><i style="width:'+a.lendo+'%"></i></div>';
calc='<span class="progT">arquivo grande · gerando a prévia… '+a.lendo+'%</span>';
barra='<div class="prog"><i style="width:'+a.lendo+'%"></i></div>'+
'<span class="progT">arquivo grande · gerando a prévia… '+a.lendo+'%</span>';
}else if(a.decodeError==='grande'){
calc='<span class="er">Arquivo grande em JPG ou WebP: exporte em PNG para montar a folha</span>';
aviso='<p class="avisoF"><b>Arquivo grande em JPG ou WebP.</b> Exporte em PNG para montar a folha.</p>';
}else if(a.decodeError){
calc='<span class="er">Não conseguimos ler esta imagem; exporte novamente em PNG ou JPG</span>';
aviso='<p class="avisoF"><b>Não conseguimos ler esta imagem.</b> Exporte novamente em PNG ou JPG.</p>';
}else if(a.cm>larguraFilme()){
calc='<span class="er">Largura maior que o filme de '+n1(larguraFilme())+' cm</span>';
aviso='<p class="avisoF"><b>Largura maior que o filme.</b> O filme aqui tem '+n1(larguraFilme())+' cm.</p>';
}else if(a.cm>0 && a.src){
const dpi=Math.round(dpiDe(a)), n=Math.max(6,Math.min(100,Math.round(dpi/300*100)));
barra='<div class="qb2"><i class="'+cls(n)+'" style="width:'+n+'%"></i></div>';
const fila=cabemNaLinha(a.cm);
calc='<span class="'+cls(n)+'"><b>'+n+'%</b> · '+dpi+' DPI</span><span>'+
a.q+' × '+n1(a.cm)+' × '+n1(a.cm*propDe(a))+' cm · '+fila+' por fileira</span>';
const dpi=Math.round(dpiDe(a));
medida='<b>'+n1(a.cm)+' × '+n1(a.cm*propDe(a))+' cm</b>';
chips='<ul class="chips"><li class="chip '+(dpi>=DPI_AVISA?'ok':dpi>=DPI_RECUSA?'av':'er')+'">'+dpi+' DPI</li>'+
'<li class="chip nt">'+cabemNaLinha(a.cm)+' por fileira</li></ul>';
const sug=sugestao(a);
// One hint per row, on the row it is about: resolution first, since it
// changes the price; otherwise a width that saves sheet.
// One hint per artwork: resolution first, since it changes the price;
// otherwise a width that saves sheet.
const maxCm=Math.floor(a.px/DPI_AVISA*2.54*10)/10;
dica = dpi<DPI_AVISA && maxCm>0 && maxCm<a.cm
? '<div class="dica av">Pequena para '+n1(a.cm)+' cm. Em '+DPI_AVISA+' DPI ela sai com até '+
@@ -558,23 +562,38 @@ function pintaArtes(){
n1(sug.cm)+' cm.<button data-usar="'+i+'" data-cm="'+sug.alvo+'">Usar '+n1(sug.alvo)+' cm</button></div>'
: '';
}
return '<div class="art'+(a===recemChegada?' nova':'')+'" data-arte="'+i+'"><div class="l1"><b>'+escapeHTML(a.f.name)+'</b>'+
'<span style="color:var(--fraco);font-size:10.5px">'+
(a.px>0?a.px+' × '+a.py+' px':'a ler')+'</span>'+
'<button data-rm="'+i+'">×</button></div><div class="cps">'+
'<div><label>largura na peça</label><input type="number" min="1" max="'+larguraFilme()+
'" placeholder="cm" data-cm="'+i+'" value="'+(a.cm||'')+'"></div>'+
'<div><label>quantas vezes</label><input type="number" min="1" placeholder="1" data-q="'+i+
'" value="'+a.q+'"></div></div>'+
'<div class="atalhos">'+
(TAMANHOS[larguraFilme()]||[]).map(t=>'<button class="tm'+
(Math.abs(a.cm-t)<0.05?' on':'')+'" data-tam="'+i+'" data-t="'+t+'">'+
n1(t)+' cm</button>').join('')+
'<span class="sep"></span>'+
'<button class="gr'+(a.giro?' on':'')+'" data-giro="'+i+'" title="Girar 90°">⟳</button>'+
'<button class="gr'+(a.esp?' on':'')+'" data-esp="'+i+'" title="Espelhar">⇋</button>'+
'</div>'+barra+'<div class="calc">'+calc+'</div>'+dica+'</div>';
const mini = a.miniSrc ? '<img src="'+escapeHTML(a.miniSrc)+'" alt="">' : '<span>'+escapeHTML(extDe(a.f.name))+'</span>';
const sub = a.cm>0 && a.src ? (a.q>1? a.q+' × ' : '')+'<b>'+n1(a.cm)+' × '+n1(a.cm*propDe(a))+' cm</b>' : '';
return '<div class="rep art2'+(a===recemChegada?' nova':'')+'" data-arte="'+i+'">'+
'<div class="repTopo"><div class="miniF">'+mini+'</div><div class="repInfo">'+
'<div class="l1"><b>'+escapeHTML(a.f.name)+'</b>'+(a===recemChegada?'<em class="novaT">nova</em>':'')+
'<button data-rm="'+i+'" aria-label="Remover '+escapeHTML(a.f.name)+'">×</button></div>'+
'<div class="repMed">'+medida+'<span>'+(a.px>0? a.px+' × '+a.py+' px · ' : '')+
escapeHTML(extDe(a.f.name))+' · '+fmt(a.f.size)+'</span></div>'+
barra+chips+aviso+dica+
'</div></div>'+
'<div class="cps">'+
'<div class="campo"><label for="cm'+i+'">Largura</label>'+
'<span class="un"><input type="number" id="cm'+i+'" min="1" max="'+larguraFilme()+'" step="0.1" '+
'placeholder="0" data-cm="'+i+'" value="'+(a.cm||'')+'"><i>cm</i></span></div>'+
'<div class="campo"><label for="q'+i+'">Cópias</label>'+
'<span class="passo"><button type="button" data-passoa="-1" data-i="'+i+'" aria-label="Menos uma cópia">−</button>'+
'<input type="number" id="q'+i+'" min="1" max="200" data-q="'+i+'" value="'+a.q+'">'+
'<button type="button" data-passoa="1" data-i="'+i+'" aria-label="Mais uma cópia">+</button></span></div>'+
'<span class="giros">'+
'<button class="gr'+(a.giro?' on':'')+'" data-giro="'+i+'" title="Girar 90°" aria-label="Girar 90°">⟳</button>'+
'<button class="gr'+(a.esp?' on':'')+'" data-esp="'+i+'" title="Espelhar" aria-label="Espelhar">⇋</button></span>'+
(sub? '<div class="conta">'+sub+'</div>' : '')+
'<div class="atalhos">'+(TAMANHOS[larguraFilme()]||[]).map(t=>'<button class="tm'+
(Math.abs(a.cm-t)<0.05?' on':'')+'" data-tam="'+i+'" data-t="'+t+'">'+n1(t)+' cm</button>').join('')+'</div>'+
'</div>'+
'</div>';
}).join('');
$('lista').querySelectorAll('[data-passoa]').forEach(b=>b.addEventListener('click',()=>{
const el=$('q'+b.dataset.i);
el.value=Math.max(1,Math.min(200,(+el.value||1)+(+b.dataset.passoa)));
el.dispatchEvent(new Event('change'));
}));
$('lista').querySelectorAll('[data-rm]').forEach(b=>b.addEventListener('click',()=>{artes.splice(+b.dataset.rm,1);pintaArtes();}));
$('lista').querySelectorAll('[data-cm]').forEach(i=>i.addEventListener('change',()=>{artes[+i.dataset.cm].cm=+i.value||0;pintaArtes();}));
$('lista').querySelectorAll('[data-q]').forEach(i=>i.addEventListener('change',()=>{artes[+i.dataset.q].q=Math.max(1,Math.min(200,Math.floor(+i.value||1)));pintaArtes();}));

View File

@@ -136,12 +136,13 @@ function medir(a){
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;
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; }
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;
});

View File

@@ -436,3 +436,16 @@ html:is([data-rota="pagamento"],[data-rota="pix"]) .pagGrid{display:grid;grid-te
.skelLinha{height:14px;margin:8px 0}
.skelLinha.curta{width:40%}
.skelBloco{height:160px;margin-top:8px}
/* Artes avulsas: the file card of a finished sheet, plus width, rotation and sizes */
.rep.nova{box-shadow:0 0 0 2px rgba(255,168,26,.45)}
.novaT{margin-left:8px;padding:2px 7px;border-radius:999px;background:#FFF1DB;color:#8A4B00;font-size:11px;font-style:normal;font-weight:800;text-transform:uppercase;letter-spacing:.04em}
.repMed .pede{font-size:16px;color:var(--texto2)}
.art2 .giros{display:inline-flex;gap:6px}
.art2 .giros .gr{width:44px;height:44px;border:1px solid #D6D6D6;border-radius:10px;background:#fff;font-size:16px;cursor:pointer}
.art2 .giros .gr.on{border-color:var(--laranja);background:var(--aviso-fundo)}
.art2 .atalhos{flex-basis:100%;display:flex;flex-wrap:wrap;gap:8px;margin:0}
.art2 .atalhos .tm{height:34px;padding:0 12px;border:1px solid #D6D6D6;border-radius:8px;background:#fff;font-size:13px;font-weight:600;cursor:pointer}
.art2 .atalhos .tm.on{border-color:var(--verde);background:var(--verde-fundo);color:var(--verde)}
.art2 .dica{margin-top:4px}
.rep .l1 .novaT{margin-right:auto}