UploadProgress
One file's upload: name, bytes sent of its size, speed, time left, a bar, and Cancel; done, paused and error states.
Demo
holiday-in-lisbon.mp4
It uploads by itself; Cancel pauses it.
quarterly-report-final-v3-really-final.pdf
logo.svg
scan-0042.tiff
archive.zip
Get it
It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.
$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/upload_progress.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/upload_progress.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/upload_progress.qwe
component UploadProgressDemoLive() { state sent = 0 state status = "uploading" let size = 84000000 if status == "uploading" && sent < size { every(400, () => { sent = min([size, sent + 6000000]) if sent >= size { status = "done" } }) } <div.demo-stack> <UploadProgress id="upload-progress-demo-live" name="holiday-in-lisbon.mp4" size={size} sent={sent} speed={status == "uploading" ? 15000000 : null} status={status} on_cancel={() => { status = "paused" }}/> <div.demo-row> <Button id="upload-progress-demo-restart" size="sm" variant="outline" on_click={() => { sent = 0; status = "uploading" }}>Start again</Button> <span.demo-note>It uploads by itself; Cancel pauses it.</span> </div> </div>}view UploadProgressDemo() = <div.demo-stack> <UploadProgressDemoLive/> <UploadProgress id="upload-progress-demo-half" name="quarterly-report-final-v3-really-final.pdf" size={4200000} sent={1554000} speed={620000}/> <UploadProgress id="upload-progress-demo-done" name="logo.svg" size={18400} sent={18400} status="done"/> <UploadProgress id="upload-progress-demo-error" name="scan-0042.tiff" size={9000000} sent={1200000} status="error" error="The connection dropped"/> <UploadProgress name="archive.zip" size={2500000000} sent={1000000000} status="paused"/> </div>Source
ui/upload_progress.qwe
// UploadProgress: one file on its way up: its name, how much of its size has// gone, the speed, the time left, and a button to cancel. Give it new numbers as// the upload goes (from an XMLHttpRequest's progress events, or a server fn// that reports) and it redraws.//// <UploadProgress name="holiday.mp4" size={84000000} sent={31000000} speed={2400000} on_cancel={() => { stop(id) }}/>// <UploadProgress name="report.pdf" size={420000} sent={420000} status="done"/>// <UploadProgress name="scan.tiff" size={9000000} sent={1200000} status="error" error="The connection dropped" on_retry={retry}/>//// name: the file's name. size: its bytes; sent: the bytes gone so far. speed:// bytes per second, when known; the time left is worked out from it.// status: "uploading" (default), "done", "error" or "paused". error: what went// wrong, shown under the name. on_cancel(): inside one of your components, what// the Cancel button does (shown while uploading or paused; none without it).// on_retry(): a Retry button after an error. cancel, retry: the buttons' words.// It is a progressbar named after the file; screen readers hear "37%".import ui/theme.qwe use ui_clslet upload_progress_css = css`.ui-upprog { display: grid; grid-template-columns: auto 1fr auto; gap: 4px 12px; align-items: center; padding: 10px 12px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-surface); font: 13px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-upprog-icon { grid-row: 1 / 3; display: grid; place-items: center; width: 36px; height: 36px; border-radius: var(--ui-radius-sm); background: var(--ui-subtle); color: var(--ui-muted); }.ui-upprog-icon svg { width: 18px; height: 18px; }.ui-upprog-done .ui-upprog-icon { background: var(--ui-success-soft); color: var(--ui-success); }.ui-upprog-error .ui-upprog-icon { background: var(--ui-danger-soft); color: var(--ui-danger); }.ui-upprog-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }.ui-upprog-btn { grid-row: 1 / 3; height: 30px; padding: 0 10px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-surface); color: var(--ui-fg); font: 500 12px/1 var(--ui-font); cursor: pointer; }.ui-upprog-btn:hover { border-color: var(--ui-strong); background: var(--ui-subtle); }.ui-upprog-btn:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-upprog-pct { grid-row: 1 / 3; font-variant-numeric: tabular-nums; color: var(--ui-muted); }.ui-upprog-body { display: grid; gap: 5px; min-width: 0; }.ui-upprog-track { height: 6px; overflow: hidden; border-radius: 999px; background: var(--ui-subtle); }.ui-upprog-fill { height: 100%; border-radius: inherit; background: var(--ui-accent); transition: width .25s ease; }.ui-upprog-done .ui-upprog-fill { background: var(--ui-success); }.ui-upprog-error .ui-upprog-fill { background: var(--ui-danger); }.ui-upprog-paused .ui-upprog-fill { background: var(--ui-strong); }.ui-upprog-meta { color: var(--ui-muted); font-size: 12px; font-variant-numeric: tabular-nums; }.ui-upprog-error .ui-upprog-meta { color: var(--ui-danger); }@media (prefers-reduced-motion: reduce) { .ui-upprog-fill { transition: none; } }`// Bytes as text: "840 B", "2.1 MB".fn upload_progress_size(n: num) -> str { if n < 1024 { return `${round(n)} B` } let unit = n < 1048576 ? 1024 : (n < 1073741824 ? 1048576 : 1073741824) let tenths = round(n * 10 / unit) `${floor(tenths / 10)}${tenths % 10 == 0 ? "" : `.${tenths % 10}`} ${unit == 1024 ? "KB" : (unit == 1048576 ? "MB" : "GB")}`}// Seconds as "5 s", "2 min", "1 h 20 min".fn upload_progress_time(s: num) -> str = s < 60 ? `${ceil(s)} s` : (s < 3600 ? `${ceil(s / 60)} min` : `${floor(s / 3600)} h ${ceil(s % 3600 / 60)} min`)view UploadProgress(name: str, size: num, sent: num = 0, speed: num? = null, status: str = "uploading", error: str? = null, on_cancel = null, on_retry = null, cancel: str = "Cancel", retry: str = "Retry", id: str? = null, class: str? = null) { style global upload_progress_css let part = size > 0 ? (sent > size ? size : (sent < 0 ? 0 : sent)) : 0 let pct = status == "done" ? 100 : (size > 0 ? floor(part * 100 / size) : 0) let left = speed != null && speed > 0 && status == "uploading" ? (size - part) / speed : null let meta = match status { "done" => `${upload_progress_size(size)} · Done` "error" => error ?? "Upload failed" "paused" => `${upload_progress_size(part)} of ${upload_progress_size(size)} · Paused` _ => join([`${upload_progress_size(part)} of ${upload_progress_size(size)}`, speed != null && speed > 0 && `${upload_progress_size(speed)}/s`, left != null && `${upload_progress_time(left)} left`] | filter((p) => p != null && p != false), " · ") } let icon = match status { "done" => <path d="M5 12.5l4 4 10-10"/> "error" => <path d="M12 8v5M12 16.5h.01M5 19h14L12 5z"/> _ => <path d="M12 17V5M6 11l6-6 6 6M5 20h14"/> } <div class={ui_cls(["ui-upprog", `ui-upprog-${status}`, class])} id={id}> <span.ui-upprog-icon><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">{icon}</svg></span> <div.ui-upprog-body> <span.ui-upprog-name title={name}>{name}</span> <div.ui-upprog-track role="progressbar" aria-label={name} aria-valuemin="0" aria-valuemax="100" aria-valuenow={pct} aria-valuetext={`${pct}%`}> <div.ui-upprog-fill style={`width: ${pct}%`}></div> </div> <span.ui-upprog-meta role={status == "error" ? "alert" : null}>{meta}</span> </div> {status == "error" && on_retry != null ? <button.ui-upprog-btn type="button" @click={() => { on_retry() }}>{retry}</button> : ((status == "uploading" || status == "paused") && on_cancel != null ? <button.ui-upprog-btn type="button" aria-label={`${cancel} ${name}`} @click={() => { on_cancel() }}>{cancel}</button> : <span.ui-upprog-pct aria-hidden="true">{pct}%</span>)} </div>}Related
FileUploadDrop or choose files: kinds, sizes and thumbnails listed, limits checked per file, removable, with upload progress.5 views · 6 downloadsMeterA measurement within a range, coloured green, amber or red by whether it is where it should be, as segments or one bar.4 views · 7 downloadsProgressHow far along something is, as a bar or a ring, with its percentage, or indeterminate.5 views · 6 downloadsProgressRingCircular progress in four sizes, with its percentage or a value of your own in the middle, or turning while not known.6 views · 7 downloadsAvatarUploadA round picture with Change and Remove, showing the picture picked before it is sent, on a real file input.6 views · 6 downloadsFileDropDrop files or click to choose them: a real file input that a form posts, listing names and sizes.5 views · 7 downloads
Thread
Thread 0 posts
On UploadProgress: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.