feat: show upload progress with a bar, time left and a header badge
All checks were successful
Build and deploy / Validate source (push) Successful in 9s
Build and deploy / Integration suite on a real stack (push) Successful in 2m27s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 45s
All checks were successful
Build and deploy / Validate source (push) Successful in 9s
Build and deploy / Integration suite on a real stack (push) Successful in 2m27s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 45s
Uploads of several GB ran behind one grey line in the cart. The order summary now has a progress bar with the percentage, size and an estimate of the time left from the recent speed; the header shows "enviando 45%" on every page while it runs; the payment button says what it is waiting for; and leaving the page mid-upload asks first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -116,24 +116,57 @@
|
|||||||
const arquivosDoCarrinho=()=>[...pedido,...(busy&&itemAtual?[itemAtual]:[])].flatMap(it=>it.localFiles||[]);
|
const arquivosDoCarrinho=()=>[...pedido,...(busy&&itemAtual?[itemAtual]:[])].flatMap(it=>it.localFiles||[]);
|
||||||
const gb=n=>(n/1073741824).toLocaleString('pt-BR',{maximumFractionDigits:1})+' GB';
|
const gb=n=>(n/1073741824).toLocaleString('pt-BR',{maximumFractionDigits:1})+' GB';
|
||||||
const mb=n=>n>=1073741824 ? gb(n) : Math.round(n/1048576)+' MB';
|
const mb=n=>n>=1073741824 ? gb(n) : Math.round(n/1048576)+' MB';
|
||||||
function textoEnvio() {
|
// Where the uploads stand, and roughly how long is left from the recent speed.
|
||||||
const files=arquivosDoCarrinho(); if(!files.length) return '';
|
const amostras=[];
|
||||||
|
function estadoEnvio() {
|
||||||
|
const files=arquivosDoCarrinho(); if(!files.length) return null;
|
||||||
let total=0, sent=0, pendentes=0;
|
let total=0, sent=0, pendentes=0;
|
||||||
for (const f of files) {
|
for (const f of files) {
|
||||||
const e=envios.get(chaveArquivo(f));
|
const e=envios.get(chaveArquivo(f));
|
||||||
total+=f.size; sent+=e ? Math.min(e.sent,f.size) : 0;
|
total+=f.size; sent+=e ? Math.min(e.sent,f.size) : 0;
|
||||||
if (!e || !e.done) pendentes++;
|
if (!e || !e.done) pendentes++;
|
||||||
}
|
}
|
||||||
if (!pendentes) return 'Arquivos enviados.';
|
const agora=Date.now();
|
||||||
if (sent>=total) return 'Arquivos enviados · verificando a segurança…';
|
amostras.push([agora,sent]); while(amostras.length>2 && agora-amostras[0][0]>15000) amostras.shift();
|
||||||
return 'Enviando seus arquivos: '+Math.floor(sent/total*100)+'% ('+mb(sent)+' de '+mb(total)+')';
|
const [t0,s0]=amostras[0], taxa=agora>t0 ? (sent-s0)/((agora-t0)/1000) : 0;
|
||||||
|
const fase=!pendentes ? 'pronto' : sent>=total ? 'verificando' : 'enviando';
|
||||||
|
return {total, sent, fase, pct:Math.floor(sent/total*100),
|
||||||
|
restante: fase==='enviando' && taxa>0 ? (total-sent)/taxa : null};
|
||||||
|
}
|
||||||
|
const tempo=s=>s<60 ? 'menos de 1 min' : 'cerca de '+Math.ceil(s/60)+' min';
|
||||||
|
function textoEnvio(e) {
|
||||||
|
if (!e) return '';
|
||||||
|
if (e.fase==='pronto') return 'Arquivos enviados ✓';
|
||||||
|
if (e.fase==='verificando') return 'Arquivos enviados · verificando a segurança…';
|
||||||
|
return 'Enviando seus arquivos: '+e.pct+'% · '+mb(e.sent)+' de '+mb(e.total)+
|
||||||
|
(e.restante!=null ? ' · '+tempo(e.restante) : '');
|
||||||
}
|
}
|
||||||
function pintaEnvio() {
|
function pintaEnvio() {
|
||||||
|
const e=estadoEnvio(), texto=textoEnvio(e);
|
||||||
const el=document.getElementById('envioArq');
|
const el=document.getElementById('envioArq');
|
||||||
const texto=textoEnvio();
|
if (el) {
|
||||||
if (el) el.textContent=texto;
|
el.replaceChildren();
|
||||||
if (busy && texto) message(texto);
|
if (e && e.fase!=='pronto') {
|
||||||
|
const barra=document.createElement('div'); barra.className='envioBarra';
|
||||||
|
const i=document.createElement('i'); i.style.width=(e.fase==='verificando'?100:e.pct)+'%'; barra.append(i);
|
||||||
|
el.append(barra);
|
||||||
|
}
|
||||||
|
if (texto) el.append(document.createTextNode(texto));
|
||||||
|
el.classList.toggle('ok', e?.fase==='pronto');
|
||||||
|
}
|
||||||
|
// Visible on every page while it runs.
|
||||||
|
const topo=document.getElementById('cartEnvio');
|
||||||
|
if (topo) topo.textContent = !e || e.fase==='pronto' ? '' : e.fase==='verificando' ? '· verificando' : '· enviando '+e.pct+'%';
|
||||||
|
if (busy && texto) {
|
||||||
|
message(texto);
|
||||||
|
$('bPagar').textContent = e && e.fase!=='pronto' ? (e.fase==='verificando' ? 'Verificando os arquivos…' : 'Enviando arquivos… '+e.pct+'%') : $('bPagar').textContent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// Leaving the page pauses an upload; it resumes, but the customer is warned.
|
||||||
|
window.addEventListener('beforeunload',event=>{
|
||||||
|
const e=estadoEnvio();
|
||||||
|
if (e && e.fase==='enviando') { event.preventDefault(); event.returnValue=''; }
|
||||||
|
});
|
||||||
// Only what is in the cart: the item on the product page may still change.
|
// Only what is in the cart: the item on the product page may still change.
|
||||||
window.addEventListener('dtf-cart-changed',()=>{ arquivosDoCarrinho().forEach(f=>{ enviar(f).catch(()=>{}); }); pintaEnvio(); });
|
window.addEventListener('dtf-cart-changed',()=>{ arquivosDoCarrinho().forEach(f=>{ enviar(f).catch(()=>{}); }); pintaEnvio(); });
|
||||||
async function upload(file) {
|
async function upload(file) {
|
||||||
|
|||||||
@@ -765,7 +765,7 @@ footer a:hover{color:var(--laranja2)}
|
|||||||
</div>
|
</div>
|
||||||
<div class="acts">
|
<div class="acts">
|
||||||
<a href="/portal.html">Minha conta</a>
|
<a href="/portal.html">Minha conta</a>
|
||||||
<a href="/carrinho" id="cartLink">Carrinho (0)</a>
|
<a href="/carrinho" id="cartLink">Carrinho (0)</a><span class="cartEnvio" id="cartEnvio" aria-live="polite"></span>
|
||||||
</div>
|
</div>
|
||||||
</div></header>
|
</div></header>
|
||||||
|
|
||||||
|
|||||||
@@ -229,7 +229,12 @@ section+section{border-top:0}
|
|||||||
/* The payment pages: paying on the left, the order summary on the right */
|
/* The payment pages: paying on the left, the order summary on the right */
|
||||||
html:is([data-rota="pagamento"],[data-rota="pix"]) .checkout{max-width:1040px;margin-top:8px}
|
html:is([data-rota="pagamento"],[data-rota="pix"]) .checkout{max-width:1040px;margin-top:8px}
|
||||||
html:is([data-rota="pagamento"],[data-rota="pix"]) .pagGrid{display:grid;grid-template-columns:minmax(0,1fr) 340px;gap:32px;align-items:start}
|
html:is([data-rota="pagamento"],[data-rota="pix"]) .pagGrid{display:grid;grid-template-columns:minmax(0,1fr) 340px;gap:32px;align-items:start}
|
||||||
.envioArq{font-size:13px;color:var(--texto2);margin-top:12px;line-height:1.5}
|
.envioArq{font-size:13px;color:var(--texto2);margin-top:14px;line-height:1.5}
|
||||||
|
.envioArq.ok{color:var(--verde)}
|
||||||
|
.envioBarra{height:6px;border-radius:999px;background:#EDEDED;overflow:hidden;margin-bottom:6px}
|
||||||
|
.envioBarra i{display:block;height:100%;background:var(--laranja);border-radius:999px;transition:width .4s}
|
||||||
|
.cartEnvio{font-size:13px;font-weight:700;color:var(--laranja-escuro);margin-left:6px}
|
||||||
|
.cartEnvio:empty{display:none}
|
||||||
.envioArq:empty{display:none}
|
.envioArq:empty{display:none}
|
||||||
.dicaEnd{font-size:13px;color:var(--texto2);margin-top:12px}
|
.dicaEnd{font-size:13px;color:var(--texto2);margin-top:12px}
|
||||||
.dicaEnd:empty{display:none}
|
.dicaEnd:empty{display:none}
|
||||||
|
|||||||
Reference in New Issue
Block a user