FilePreview
A look at a file before opening it: an image, a PDF in an object, the start of a text file, a player, or an icon, with name, size and a download link.
Demo
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/file_preview.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/file_preview.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/file_preview.qwe
let file_preview_demo_text = "# Release notes\n\n- Components bring their own CSS\n- QrCode and Barcode, drawn in Quillo\n- ReviewList sorts and filters\n\nThanks to everyone who tried the betas.\nLine 8\nLine 9\nLine 10\nLine 11\nLine 12\nLine 13: not shown\nLine 14: not shown"view FilePreviewDemo() = <div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px"> <FilePreview id="file-preview-demo-image" name="Team offsite.jpg" src="https://picsum.photos/seed/offsite/600/400" size={3912004} meta="Sep 18"/> <FilePreview id="file-preview-demo-pdf" name="Sample.pdf" src="data:application/pdf;base64,JVBERi0xLjEKMSAwIG9iajw8L1R5cGUvQ2F0YWxvZy9QYWdlcyAyIDAgUj4+ZW5kb2JqCjIgMCBvYmo8PC9UeXBlL1BhZ2VzL0tpZHNbMyAwIFJdL0NvdW50IDE+PmVuZG9iagozIDAgb2JqPDwvVHlwZS9QYWdlL1BhcmVudCAyIDAgUi9NZWRpYUJveFswIDAgMzAwIDE0NF0+PmVuZG9iagp0cmFpbGVyPDwvUm9vdCAxIDAgUj4+Cg==" size={2483112}/> <FilePreview id="file-preview-demo-text" name="NOTES.md" text={file_preview_demo_text} size={812} href="#file_preview"/> <FilePreview id="file-preview-demo-zip" name="Brand assets.zip" size={18440230} href="#file_preview" meta="3 folders"/> <FilePreview id="file-preview-demo-audio" name="Interview.mp3" src="data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQAAAAA=" size={4200}/> <FilePreview id="file-preview-demo-plain" name="LICENSE" height={120}/> </div>Source
ui/file_preview.qwe
// FilePreview: a look at a file before it is opened: an image shown, a PDF in an// <object> (with a link when the browser cannot show it), the start of a text// file, a video or a sound with the browser's controls, or a big icon with the// file's extension for anything else; with the name, the size and a download link.//// <FilePreview name="Q3 report.pdf" src="/files/q3.pdf" size={2483112}/>// <FilePreview name="notes.md" text={notes} size={812} href="/files/notes.md"/>//// name: the file's name; its extension says what kind it is (kind: "image",// "pdf", "text", "video", "audio" or "file" to say otherwise). src: where the file// is, shown in the preview. text: the content of a text file (shown in place of// fetching it), cut after `lines` lines (12). size: in bytes, written as "2.4 MB".// href: where it downloads from (src by default; null with no src, no link).// meta: another line under the name ("Edited 2 h ago"). height: the preview's// height in px (220). id: its id. It is a <figure>, the name in its <figcaption>.import ui/theme.qwe use ui_clslet file_preview_css = css`.ui-file-preview { display: grid; grid-template-rows: auto auto; min-width: 0; margin: 0; overflow: hidden; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface); color: var(--ui-fg); font: 14px/1.4 var(--ui-font); }.ui-file-preview-view { display: grid; place-items: center; height: var(--ui-file-preview-h, 220px); overflow: hidden; background: var(--ui-subtle); }.ui-file-preview-view img { width: 100%; height: 100%; object-fit: contain; }.ui-file-preview-view object, .ui-file-preview-view video { width: 100%; height: 100%; border: 0; }.ui-file-preview-view audio { width: calc(100% - 32px); }.ui-file-preview-fallback { display: grid; place-items: center; gap: 8px; height: 100%; padding: 16px; color: var(--ui-muted); text-align: center; }.ui-file-preview-text { align-self: stretch; justify-self: stretch; margin: 0; padding: 12px 14px; overflow: hidden; background: var(--ui-surface); color: var(--ui-fg); font: 12px/1.55 var(--ui-mono); white-space: pre-wrap; overflow-wrap: anywhere; -webkit-mask-image: linear-gradient(to bottom, #000 75%, transparent); mask-image: linear-gradient(to bottom, #000 75%, transparent); }.ui-file-preview-icon { --k: var(--ui-muted); position: relative; display: grid; place-items: end center; width: 72px; height: 90px; padding-bottom: 12px; border-radius: 6px 22px 6px 6px; background: color-mix(in srgb, var(--k) 16%, var(--ui-surface)); color: var(--k); font: 800 14px/1 var(--ui-font); letter-spacing: .03em; }.ui-file-preview-icon::before { content: ""; position: absolute; top: 0; right: 0; width: 22px; height: 22px; border-radius: 0 0 0 6px; background: color-mix(in srgb, var(--k) 32%, var(--ui-surface)); }.ui-file-preview-k-image { --k: var(--ui-success); } .ui-file-preview-k-pdf { --k: var(--ui-danger); } .ui-file-preview-k-text { --k: var(--ui-info); }.ui-file-preview-k-video, .ui-file-preview-k-audio { --k: var(--ui-accent); } .ui-file-preview-k-archive { --k: var(--ui-warning); }.ui-file-preview-foot { display: flex; align-items: center; gap: 10px; min-width: 0; padding: 10px 12px; border-top: 1px solid var(--ui-border); }.ui-file-preview-info { flex: 1; display: grid; min-width: 0; }.ui-file-preview-name { overflow: hidden; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }.ui-file-preview-meta { color: var(--ui-muted); font-size: 12px; font-variant-numeric: tabular-nums; }.ui-file-preview-dl { flex: none; display: inline-grid; place-items: center; width: 32px; height: 32px; border-radius: var(--ui-radius-sm); color: var(--ui-muted); }.ui-file-preview-dl:hover { background: var(--ui-subtle); color: var(--ui-fg); }.ui-file-preview-dl:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-file-preview-dl svg { width: 18px; height: 18px; }.ui-file-preview-open { color: var(--ui-accent); }`// A size in bytes as people read it: 812 B, 14.2 KB, 3 MB (whole-number arithmetic,// so the server and the browser write the same).fn file_preview_size(n: num) -> str { if n < 1024 { return `${n} B` } let unit = n < 1024 * 1024 ? 1024 : (n < 1024 * 1024 * 1024 ? 1024 * 1024 : 1024 * 1024 * 1024) let tenths = round(n * 10 / unit) `${floor(tenths / 10)}${tenths % 10 == 0 ? "" : `.${tenths % 10}`} ${unit == 1024 ? "KB" : (unit == 1024 * 1024 ? "MB" : "GB")}`}fn file_preview_ext(name: str) -> str { let parts = split(name, ".") len(parts) > 1 ? lower(last(parts)) : ""}// What a file is, from its extension: image, pdf, text, video, audio, archive or file.fn file_preview_kind(name: str) -> str = match file_preview_ext(name) { "png" | "jpg" | "jpeg" | "gif" | "webp" | "svg" | "avif" => "image" "pdf" => "pdf" "txt" | "md" | "csv" | "json" | "log" | "qwe" | "js" | "ts" | "py" | "rs" | "css" | "html" | "sql" | "yaml" | "yml" | "toml" => "text" "mp4" | "webm" | "mov" => "video" "mp3" | "wav" | "ogg" | "m4a" | "flac" => "audio" "zip" | "gz" | "tar" | "7z" | "rar" => "archive" _ => "file" }view FilePreview(name: str, src: str? = null, kind: str? = null, text: str? = null, lines: int = 12, size: int? = null, href: str? = null, meta: str? = null, height: int = 220, id: str? = null, class: str? = null) { style global file_preview_css let k = kind ?? file_preview_kind(name) let ext = upper(slice(file_preview_ext(name), 0, 4)) let link = href ?? src let icon = <span class={`ui-file-preview-icon ui-file-preview-k-${k}`} aria-hidden="true">{ext != "" ? ext : "FILE"}</span> let shown = text != null ? join(take(split(text, "\n"), lines), "\n") : null <figure class={ui_cls(["ui-file-preview", `ui-file-preview-${k}`, class])} id={id} style={`--ui-file-preview-h: ${height}px`}> <div.ui-file-preview-view> {match k { "image" => src != null ? <img src={src} alt={name} loading="lazy"/> : icon "pdf" => src != null ? <object data={src} type="application/pdf" aria-label={name}> <div.ui-file-preview-fallback>{icon}<a.ui-file-preview-open href={src}>Open {name}</a></div> </object> : icon "video" => src != null ? <video src={src} controls={true} preload="metadata" aria-label={name}></video> : icon "audio" => src != null ? <audio src={src} controls={true} preload="metadata" aria-label={name}></audio> : icon _ => shown != null ? <pre.ui-file-preview-text aria-label={`Start of ${name}`}>{shown}</pre> : icon }} </div> <figcaption.ui-file-preview-foot> <span.ui-file-preview-info> <span.ui-file-preview-name title={name}>{name}</span> {(size != null || meta != null) && <span.ui-file-preview-meta> {join([size != null && file_preview_size(size), meta] | filter((x) => x != null && x != false), " · ")} </span>} </span> {link != null && <a.ui-file-preview-dl href={link} download={name} aria-label={`Download ${name}`} title="Download"> <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 2.5v8M4.5 7L8 10.5 11.5 7M3 13.5h10"/></svg> </a>} </figcaption> </figure>}Related
EmailPreviewAn email as a client shows it: subject, labels and star, sender with picture, to and cc, the date, the message and attachments to download.6 views · 7 downloadsBoardingPassA boarding pass with from and to, flight, date and times, gate, seat, boarding group, a barcode area and a stub.4 views · 5 downloadsIdCardAn ID badge with a lanyard slot: photo or initials, name, role, organisation, number, issued and expiry dates, an Expired stamp, and a barcode slot.5 views · 6 downloadsTicketAn event ticket with a tear line and stub: event, date, venue, section, row and seat, the holder, a code or a QR as children.6 views · 7 downloadsPdfViewerA PDF in the page with the browser's own viewer, a bar with its name, open and download links, and a fallback.6 views · 7 downloadsArticleCardA post to click into: image, category, title, excerpt, author, date and reading time; image on top or to the side.5 views · 7 downloads
Thread
Thread 0 posts
On FilePreview: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.