feat: skeletons where the page waits, no flicker while a sheet is read, repeated copies in the preview
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
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
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>
This commit is contained in:
@@ -86,6 +86,11 @@
|
|||||||
rows.push(linha('Total', rs(approved.total_cents/100), 'tot'));
|
rows.push(linha('Total', rs(approved.total_cents/100), 'tot'));
|
||||||
resumo.replaceChildren(node('h4','Resumo do pedido'), ...rows);
|
resumo.replaceChildren(node('h4','Resumo do pedido'), ...rows);
|
||||||
}
|
}
|
||||||
|
function esqueletoPagamento() {
|
||||||
|
const linhas=['skel skelLinha','skel skelLinha','skel skelLinha curta'].map(c=>node('div',null,c));
|
||||||
|
resumo.hidden=false; resumo.replaceChildren(node('h4','Resumo do pedido'),...linhas);
|
||||||
|
actions.replaceChildren(node('div',null,'skel skelLinha curta'),node('div',null,'skel skelBloco'));
|
||||||
|
}
|
||||||
function semPedido() {
|
function semPedido() {
|
||||||
pintaResumo(null);
|
pintaResumo(null);
|
||||||
actions.replaceChildren();
|
actions.replaceChildren();
|
||||||
@@ -259,6 +264,7 @@
|
|||||||
if (!naPagina()) { if (!go || !draftId) return; }
|
if (!naPagina()) { if (!go || !draftId) return; }
|
||||||
else if (!draftId) { semPedido(); return; }
|
else if (!draftId) { semPedido(); return; }
|
||||||
const version=++refreshVersion, shownId=draftId;
|
const version=++refreshVersion, shownId=draftId;
|
||||||
|
if (naPagina() && !actions.children.length) esqueletoPagamento();
|
||||||
try {
|
try {
|
||||||
await ready;
|
await ready;
|
||||||
const quote=await api('/quotes/'+shownId);
|
const quote=await api('/quotes/'+shownId);
|
||||||
@@ -474,9 +480,10 @@
|
|||||||
async function showCard(approved, into, version, tipo) {
|
async function showCard(approved, into, version, tipo) {
|
||||||
const holder=node('div');
|
const holder=node('div');
|
||||||
holder.id='cardPaymentBrick';
|
holder.id='cardPaymentBrick';
|
||||||
into.append(holder);
|
const espera=node('div',null,'skel skelBloco');
|
||||||
|
into.append(espera,holder);
|
||||||
try { await loadMercadoPago(); }
|
try { await loadMercadoPago(); }
|
||||||
catch(error) { message(error.message); return; }
|
catch(error) { espera.remove(); message(error.message); return; }
|
||||||
const session=await ready;
|
const session=await ready;
|
||||||
// The customer may have switched to PIX, or the page re-rendered, meanwhile.
|
// The customer may have switched to PIX, or the page re-rendered, meanwhile.
|
||||||
if (!holder.isConnected || version!==refreshVersion) return;
|
if (!holder.isConnected || version!==refreshVersion) return;
|
||||||
@@ -493,7 +500,7 @@
|
|||||||
visual:{hideFormTitle:true,
|
visual:{hideFormTitle:true,
|
||||||
style:{customVariables:{baseColor:'#FFA81A',buttonTextColor:'#03060B'}}}},
|
style:{customVariables:{baseColor:'#FFA81A',buttonTextColor:'#03060B'}}}},
|
||||||
callbacks:{
|
callbacks:{
|
||||||
onReady:()=>{},
|
onReady:()=>{ espera.remove(); },
|
||||||
onError:error=>{ console.error(error); message('Erro no formulário do cartão. Confira os dados e tente de novo.'); },
|
onError:error=>{ console.error(error); message('Erro no formulário do cartão. Confira os dados e tente de novo.'); },
|
||||||
onSubmit:async data=>{
|
onSubmit:async data=>{
|
||||||
if (!cartOk()) { await refresh(); throw new Error('cart changed'); }
|
if (!cartOk()) { await refresh(); throw new Error('cart changed'); }
|
||||||
|
|||||||
@@ -277,16 +277,32 @@ function previaAoVivo(){
|
|||||||
const tot=folhaTotalM();
|
const tot=folhaTotalM();
|
||||||
$('vMt').textContent = tot>0 ? fmtM(cobrar(tot))+' m' : '—';
|
$('vMt').textContent = tot>0 ? fmtM(cobrar(tot))+' m' : '—';
|
||||||
const medidas=folhas.filter(x=>x.med);
|
const medidas=folhas.filter(x=>x.med);
|
||||||
const previews=folhas.filter(x=>x.previewSrc);
|
// Sheets still being read show a placeholder; a sheet printed N times is
|
||||||
|
// shown N times (up to REPETE_MAX, then a count).
|
||||||
|
const REPETE_MAX=6;
|
||||||
|
const previews=folhas.filter(x=>x.previewSrc || (x.pct!=null && x.med && x.med.grande));
|
||||||
if(previews.length){
|
if(previews.length){
|
||||||
const painel=document.createElement('div'); painel.className='folhaPrevias';
|
const painel=document.createElement('div'); painel.className='folhaPrevias';
|
||||||
previews.forEach((x,index)=>{
|
previews.forEach((x,index)=>{
|
||||||
const figura=document.createElement('figure'); figura.className='folhaPrevia';
|
const figura=document.createElement('figure'); figura.className='folhaPrevia';
|
||||||
const imagem=new Image(); imagem.src=x.previewSrc; imagem.alt=x.f.name;
|
|
||||||
const legenda=document.createElement('figcaption');
|
const legenda=document.createElement('figcaption');
|
||||||
|
const vezes=Math.max(1,x.rep||1);
|
||||||
legenda.textContent=(previews.length>1 ? (index+1)+'. ' : '')+x.f.name+
|
legenda.textContent=(previews.length>1 ? (index+1)+'. ' : '')+x.f.name+
|
||||||
(x.med ? ' · '+n1(x.med.larg)+' × '+n1(x.med.alt)+' cm' : '');
|
(x.med ? ' · '+n1(x.med.larg)+' × '+n1(x.med.alt)+' cm' : '')+(vezes>1 ? ' · '+vezes+' cópias' : '');
|
||||||
figura.append(imagem,legenda); painel.append(figura);
|
if(!x.previewSrc){
|
||||||
|
const esqueleto=document.createElement('div'); esqueleto.className='skel folhaSkel';
|
||||||
|
esqueleto.style.aspectRatio=x.med ? x.med.larg+' / '+Math.min(x.med.alt,x.med.larg*4) : '1 / 2';
|
||||||
|
esqueleto.append(Object.assign(document.createElement('span'),{textContent:'Gerando a prévia da folha…'}));
|
||||||
|
figura.append(esqueleto,legenda); painel.append(figura); return;
|
||||||
|
}
|
||||||
|
const pilha=document.createElement('div'); pilha.className='folhaPilha';
|
||||||
|
for(let k=0;k<Math.min(vezes,REPETE_MAX);k++){
|
||||||
|
const imagem=new Image(); imagem.src=x.previewSrc; imagem.alt=k? '' : x.f.name;
|
||||||
|
imagem.style.maxHeight=Math.floor(520/Math.min(vezes,REPETE_MAX))+'px'; pilha.append(imagem);
|
||||||
|
}
|
||||||
|
if(vezes>REPETE_MAX) pilha.append(Object.assign(document.createElement('div'),
|
||||||
|
{className:'folhaMais', textContent:'+ '+(vezes-REPETE_MAX)+' cópia'+(vezes-REPETE_MAX>1?'s':'')}));
|
||||||
|
figura.append(pilha,legenda); painel.append(figura);
|
||||||
});
|
});
|
||||||
$('vArea').replaceChildren(painel);
|
$('vArea').replaceChildren(painel);
|
||||||
}else{
|
}else{
|
||||||
|
|||||||
@@ -352,6 +352,18 @@ function medirFolha(file){
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function textoProgresso(x){
|
||||||
|
if(x.med && x.med.grande) return 'gerando a prévia e conferindo a arte… '+x.pct+'%';
|
||||||
|
return x.pct<50?'lendo o arquivo…':x.pct<70?'medindo a folha…':'conferindo a arte…';
|
||||||
|
}
|
||||||
|
// Moves one sheet's progress bar in place: repainting the whole list and the
|
||||||
|
// preview on every step made "Sua folha" flicker while a large file was read.
|
||||||
|
function pintaProgressoFolha(x){
|
||||||
|
const el=$('lista').querySelector('.rep[data-folha="'+folhas.indexOf(x)+'"]');
|
||||||
|
const barra=el && el.querySelector('.prog i'), texto=el && el.querySelector('.progT');
|
||||||
|
if(!barra){ pintaFolha(); return; }
|
||||||
|
barra.style.width=x.pct+'%'; if(texto) texto.textContent=textoProgresso(x);
|
||||||
|
}
|
||||||
function pintaFolha(){
|
function pintaFolha(){
|
||||||
invalidaItemAtual();
|
invalidaItemAtual();
|
||||||
const L=larguraFilme();
|
const L=larguraFilme();
|
||||||
@@ -415,14 +427,13 @@ function pintaFolha(){
|
|||||||
}
|
}
|
||||||
const barra = x.pct!=null
|
const barra = x.pct!=null
|
||||||
? '<div class="prog"><i style="width:'+x.pct+'%"></i></div>'+
|
? '<div class="prog"><i style="width:'+x.pct+'%"></i></div>'+
|
||||||
'<span class="progT">'+(x.pct<50?'lendo o arquivo…':
|
'<span class="progT">'+textoProgresso(x)+'</span>'
|
||||||
x.pct<70?'medindo a folha…':'conferindo a arte…')+'</span>'
|
|
||||||
: x.semAnalise
|
: x.semAnalise
|
||||||
? '<div class="progF"><b>Não conseguimos conferir esta folha.</b> '+escapeHTML(x.semAnalise)+
|
? '<div class="progF"><b>Não conseguimos conferir esta folha.</b> '+escapeHTML(x.semAnalise)+
|
||||||
'. Seguimos com o preço de tabela — se quiser a nota e o desconto, exporte a mesma '+
|
'. Seguimos com o preço de tabela — se quiser a nota e o desconto, exporte a mesma '+
|
||||||
'folha em <b>PNG</b>, que a conferência é bem mais leve.</div>'
|
'folha em <b>PNG</b>, que a conferência é bem mais leve.</div>'
|
||||||
: '';
|
: '';
|
||||||
return '<div class="rep"><div class="l1"><b>'+
|
return '<div class="rep" data-folha="'+i+'"><div class="l1"><b>'+
|
||||||
(folhas.length>1? (i+1)+'. ':'')+escapeHTML(x.f.name)+'</b>'+
|
(folhas.length>1? (i+1)+'. ':'')+escapeHTML(x.f.name)+'</b>'+
|
||||||
'<span style="color:var(--fraco);font-size:10.5px">'+fmt(x.f.size)+'</span>'+
|
'<span style="color:var(--fraco);font-size:10.5px">'+fmt(x.f.size)+'</span>'+
|
||||||
'<button data-rmf="'+i+'">×</button></div>'+
|
'<button data-rmf="'+i+'">×</button></div>'+
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ function sel(fs){
|
|||||||
let mostrado=0;
|
let mostrado=0;
|
||||||
previaPngGrande(x.f, 600, p=>{
|
previaPngGrande(x.f, 600, p=>{
|
||||||
const pct=60+Math.floor(p*38);
|
const pct=60+Math.floor(p*38);
|
||||||
if(pct-mostrado>=3){ mostrado=pct; x.pct=pct; pintaFolha(); }
|
if(pct-mostrado>=1){ mostrado=pct; x.pct=pct; pintaProgressoFolha(x); }
|
||||||
}).then(cv=>{
|
}).then(cv=>{
|
||||||
if(cv && folhas.includes(x)){
|
if(cv && folhas.includes(x)){
|
||||||
x.previewSrc=cv.toDataURL('image/webp',0.85);
|
x.previewSrc=cv.toDataURL('image/webp',0.85);
|
||||||
|
|||||||
@@ -318,3 +318,16 @@ html:is([data-rota="pagamento"],[data-rota="pix"]) .pagGrid{display:grid;grid-te
|
|||||||
@media (max-width:480px){
|
@media (max-width:480px){
|
||||||
.etapas2 .w{grid-template-columns:1fr}
|
.etapas2 .w{grid-template-columns:1fr}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Skeleton placeholders where the page really waits */
|
||||||
|
.skel{position:relative;border-radius:8px;background:linear-gradient(90deg,#EEEFF1 25%,#F7F7F8 37%,#EEEFF1 63%);background-size:400% 100%;animation:skel 1.4s ease infinite}
|
||||||
|
@keyframes skel{0%{background-position:100% 50%}100%{background-position:0 50%}}
|
||||||
|
@media (prefers-reduced-motion:reduce){.skel{animation:none}}
|
||||||
|
.folhaSkel{display:flex;align-items:center;justify-content:center;max-height:520px;min-height:180px;width:100%}
|
||||||
|
.folhaSkel span{font-size:12.5px;color:var(--fraco);background:#fffc;padding:4px 10px;border-radius:999px}
|
||||||
|
#vArea .folhaPilha{display:flex;flex-direction:column;gap:4px;max-height:520px;overflow:auto;background:#fff}
|
||||||
|
#vArea .folhaPilha img{width:100%;height:auto;display:block}
|
||||||
|
.folhaMais{font-size:12px;color:var(--texto2);text-align:center;padding:8px}
|
||||||
|
.skelLinha{height:14px;margin:8px 0}
|
||||||
|
.skelLinha.curta{width:40%}
|
||||||
|
.skelBloco{height:160px;margin-top:8px}
|
||||||
|
|||||||
Reference in New Issue
Block a user