Dialog File Preview for Quasar

🇪🇸 Versión en español | 🇧🇷 Versão em português

npm version license Vue Quasar TypeScript Node

Quasar extension to preview files in a modal dialog in Vue 3 + Quasar apps, with an imperative composable-based API.

Table of contents

In 30 seconds

Recommended base flow:

  1. Install the extension in your Quasar app (quasar ext add).
  2. Ensure the host Pinia boot runs before this extension's boot.
  3. Import useDialogFilePreview() wherever you need to open the preview.
  4. Call void preview.show(file) — the dialog mounts globally; no need to add components to your templates.

The extension registers boots and CSS in the host project automatically after quasar ext add.

What it is and what it includes

Global modal dialog controlled by useDialogFilePreview() — not an embeddable component.

Requirements and compatibility

Item Value
Node >= 20.0.0
Quasar ^2.6.0
Vue ^3.4.18
Pinia ^2.0.11 | ^3.0.0
Package format ES modules
Recommended CLI @quasar/app-vite ^2.x or ^3.x
PDF in the package vue-pdf-embed / pdf.js included (no extra host dependency)

The host Pinia boot must run before this extension's boot.

Install in a Quasar app

Add the extension in your Quasar app:

quasar ext add @benjaminor-dev/dialog-file-preview

Remove:

quasar ext remove @benjaminor-dev/dialog-file-preview

After quasar ext add, the extension registers boots and CSS in your app automatically. No additional steps are needed for a normal npm install.

What the extension adds to the host

Resources installed or registered by the extension:

Resource npm path
Dialog boot ~@benjaminor-dev/quasar-app-extension-dialog-file-preview/boot/dialog
Styles ~@benjaminor-dev/quasar-app-extension-dialog-file-preview/main.css
Boot defaults (host) src/boot/bor/bor-dialog-file-preview-defaults.ts — created on install; Skip/Overwrite if it already exists

Install (quasar ext add / invoke): creates src/boot/bor/bor-dialog-file-preview-defaults.ts (or .js) and registers it in quasar.configboot: [] automatically. No manual editing required for a normal install. The package boot (boot/dialog) and main.css are injected by the extension on every dev/build (they do not appear as short entries in quasar.config).

Remove (quasar ext remove): removes the defaults boot and its entry in quasar.config.

Boot order (this extension): Pinia → DFP dialog boot → bor/bor-dialog-file-preview-defaults → rest. In quasar.config you will see bor/bor-dialog-file-preview-defaults; the boot/dialog boot is added by the runner at compile time.

The boot mounts the dialog in the document and connects the host Pinia store. You do not need to import DialogFilePreview in App.vue.

Global defaults (boot)

Avoid repeating showPrint / restrictInteraction on every show(). Priority: show(input, options) → boot (configureDialogFilePreviewDefaults) → built-in.

After quasar ext add, review src/boot/bor/bor-dialog-file-preview-defaults.ts — template with all boot options (// recommended: …). The boot does not support title or forcePreview — those options belong only in show(input, options).

// src/boot/bor/bor-dialog-file-preview-defaults.ts
import { configureDialogFilePreviewDefaults } from "@benjaminor-dev/quasar-app-extension-dialog-file-preview";

configureDialogFilePreviewDefaults({
  showPrint: false,
  restrictInteraction: true,
});

configureDialogFilePreview is an alias of configureDialogFilePreviewDefaults.

Language (i18n)

Built-in en, es, and pt. By default toolbar labels, tooltips, PDF controls, and fallback messages follow Quasar language ($q.lang.isoName); if Quasar has no language, fallback is en.

Locale resolution: boot locale$q.lang.isoName prefix (es*es, pt*pt) → en.

Option When to use
locale: 'es' in boot Pin a specific language (en | es | pt); ignores $q.lang changes
messages: { download: '…' } Change one string without setting locale

Quasar config example (Spanish for the whole app):

// quasar.config.ts — framework.lang
framework: {
  lang: "es",
},

Boot template (after install):

configureDialogFilePreviewDefaults({
  // locale: "es", // uncomment to pin Dialog File Preview language only
  showPrint: true,
});

Override a single string:

configureDialogFilePreviewDefaults({
  messages: { loadingFile: "Opening file…" },
});

Quick start

<script setup lang="ts">
import { useDialogFilePreview } from "@benjaminor-dev/quasar-app-extension-dialog-file-preview";

const preview = useDialogFilePreview();

function onViewPdf(file: File) {
  if (preview.canPreview(file)) {
    void preview.show(file);
  }
}
</script>

<template>
  <q-btn label="View PDF" @click="onViewPdf(selectedFile)" />
</template>

Multiple files (gallery or attachments):

void preview.show([imageFile, pdfFile, anotherImage]);

Remote URL with explicit name:

void preview.show({
  source:
    "https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf",
  name: "tracemonkey-pldi-09.pdf",
  mimeType: "application/pdf",
});

TypeScript in your IDE

The composable exposes a typed API: use autocomplete instead of guessing options.

To check whether a file can be previewed before opening the modal, canPreview() is also typed according to the source you pass.

Public API

Entry point: useDialogFilePreview().

import { useDialogFilePreview } from "@benjaminor-dev/quasar-app-extension-dialog-file-preview";

const preview = useDialogFilePreview();

Methods

Method Description
show(input, options?) Opens the dialog immediately and returns a Promise that resolves when MIME normalization/probing of sources is complete. The second argument options configures the session (toolbar, title, restrictions, forcePreview). By default, local files that exceed the size limit open in fallback mode (message + download); see forcePreview to force the embedded viewer. Meanwhile, and until the viewer emits ready state, the viewport shows "Loading file...".
hide() Closes the dialog and revokes blob: URLs created by the extension.
next() Next file (when multiple).
previous() Previous file.
downloadCurrent() Downloads the visible file (from File/Blob or link when only a URL is available).
canPreview(source) true if the MIME has a viewer in the dialog and the local file does not exceed the size limit. Synchronous evaluation (no remote probing).
canPreviewAll(sources) true if all items in the array are previewable (also synchronous).

Reactive state (read-only)

Ref Description
visible Whether the dialog is open.
current Metadata of the current item (PreviewItem) or null.
hasPrevious / hasNext Navigation in multi-file lists.
hasMultiple More than one file in the current session.

Use canPreview() to show or hide "View" buttons in your UI without opening the dialog. If the URL has no extension or known MIME, pass mimeType in the descriptor or call show() directly (remote probing happens on open, not in canPreview). If the local file exceeds the size limit, canPreview() returns false even when the MIME is valid; show() shows fallback unless you pass forcePreview: true (at your own risk).

Open behavior (show)

  1. The dialog becomes visible instantly.
  2. A single loading overlay covers the viewport (dark background, white spinner, "Loading file...") while:
    • sources are enriched (File/Blob/URL → items with MIME and display URL), and
    • the current item's viewer finishes its visible load (for PDF: first canvas render, not just document metadata).
  3. Print and download toolbar buttons appear only when the viewer emits ready state and the corresponding session option is active (showPrint / showDownload).
  4. Print is offered only for PDF, images, and text; video and audio do not show the button even when showPrint is true.
  5. When switching files in a gallery (next / previous), the loader returns until the new viewer is ready.
  6. When printing (button or Ctrl/Cmd+P), if iframe/PDF preparation takes time, the same overlay shows "Preparing print..." after ~250 ms and hides the instant the system dialog appears (it does not wait for you to confirm or cancel).

Internal preparation state is not exposed in useDialogFilePreview(); visible and the overlay UX are enough.

Session options (PreviewSessionOptions)

Optional second argument of show(). Applies to the entire dialog session (including multi-file gallery).

import type { PreviewSessionOptions } from "@benjaminor-dev/quasar-app-extension-dialog-file-preview";

void preview.show(confidentialPdf, {
  showPrint: false,
  showDownload: true,
  restrictInteraction: true,
  title: "Confidential contract",
});

void preview.show([scan1, scan2], {
  showGalleryNav: true,
  showPdfZoom: false,
});

// Force embedded viewer even when size limit is exceeded (performance / crash risk)
void preview.show(heavyLocalPdf, { forcePreview: true });
Option Default Description
showPrint true Shows the print button when the type allows it (PDF, image, text).
showDownload true Shows the download button in the toolbar.
showGalleryNav true Shows previous/next navigation when there are multiple files.
showPdfZoom true Shows "Zoom: N%" in the toolbar for PDF.
title file name Centered title in the toolbar.
restrictInteraction false Blocks copy, cut, selection, context menu, and common shortcuts in the viewer. Does not prevent screenshots or advanced browser tools.
forcePreview false Skips the size limit and opens the embedded viewer even when canPreview() is false due to size. Developer responsibility: very large files may slow the UI, freeze the tab, or close the browser. Does not change canPreview() result.

If you omit options, the table defaults are used.

Preview size limits

For File / Blob with known .size, the extension applies a cap before using the embedded viewer. The same limits apply to canPreview() and show() by default.

Viewer MIME / category Limit
Image image/* 50 MB
PDF application/pdf 100 MB
Video video/mp4, video/webm, video/ogg 150 MB
Audio audio/mpeg, audio/wav, audio/ogg, audio/webm 50 MB
Text text/plain, text/csv, application/json, application/xml, text/xml 10 MB

Exported constant: PREVIEW_MAX_BYTES_BY_KIND (bytes per viewer kind).

Behavior:

Accepted file sources

Unified type PreviewSourceInput:

Form Example
File File from <input type="file"> or new File(...)
Blob API or canvas Blob
string https://... or blob:... URL
Descriptor object See below
// File or Blob directly
void preview.show(file);
void preview.show([imageFile, pdfFile]);

// URL with explicit metadata (recommended when extension is unclear)
void preview.show({
  source: "https://example.com/document",
  name: "contract.pdf",
  mimeType: "application/pdf",
});

// Descriptor with File
void preview.show({
  source: selectedFile,
  name: "renamed-attachment.pdf",
});
Situation What to do
File / Blob Name and MIME are inferred from the object
URL without extension Pass name and mimeType in the descriptor
Remote URL on open May be probed (HEAD/partial GET) to infer MIME
canPreview() returns false (MIME) Evaluated synchronously; with explicit mimeType or after probing, show() may still open (or fallback if the type has no viewer)
canPreview() returns false (size) The File/Blob exceeds the size limit. show() opens fallback with download; use { forcePreview: true } only if you accept performance risk
External blob: URLs The extension does not revoke them; only those created from File/Blob are released

Supported file types

Category MIME / prefixes Viewer in the dialog Printable
Images image/* Image centered and contained in the viewport; click for ×2 zoom with pan; no scroll in the viewer area Yes
PDF application/pdf pdf.js (vue-pdf-embed): adaptive mode (see below); page bar with direct jump, zoom, and text/annotation layers Yes
Video video/mp4, video/webm, video/ogg <video controls> No
Audio audio/mpeg, audio/wav, audio/ogg, audio/webm <audio controls> with central icon and equalizer while playing No
Text text/plain, text/csv, application/json, application/xml, text/xml Monospaced text with scroll Yes
Other Message + download button No

If the MIME has no viewer, show() can still open the dialog in fallback mode to allow download.

Image viewer

PDF viewer (adaptive mode)

The viewer automatically chooses between two presentations based on document size:

Mode When Experience
Continuous scroll Light file (typically ≤ 25 MB, ≤ 40 pages) All pages in a column with spacing; vertical scroll; bottom bar tracks the visible page
Paginated Heavy file or many pages One page at a time (suitable for very large PDFs)

In both modes:

There is no public option to force a mode: the heuristic protects the browser from huge documents (e.g. hundreds of MB or thousands of pages).

Exported TypeScript types

From the main entrypoint:

import type {
  DialogFilePreviewApi,
  DialogFilePreviewDefaults,
  PreviewBlockReason,
  PreviewSourceInput,
  PreviewItemInput,
  PreviewItem,
  PreviewKind,
  PreviewSessionOptions,
} from "@benjaminor-dev/quasar-app-extension-dialog-file-preview";
Type Use
DialogFilePreviewApi Return type of useDialogFilePreview()
DialogFilePreviewDefaults Options accepted in configureDialogFilePreviewDefaults (boot)
PreviewBlockReason Size block reason ("size", etc.)
PreviewSourceInput First argument of show() and argument of canPreview()
PreviewSessionOptions Optional second argument of show()
PreviewItemInput Descriptor with optional metadata
PreviewItem Normalized item in reactive state (current); includes blockedReason: PreviewBlockReason | null when applicable
PreviewKind "image" | "pdf" | "video" | "audio" | "text" | "unknown"
type PreviewItemInput = {
  source: File | Blob | string;
  name?: string;
  mimeType?: string;
};

MIME utilities

Also exported to validate in your UI without opening the dialog:

import {
  isPreviewableMime,
  PREVIEWABLE_MIME_PREFIXES,
} from "@benjaminor-dev/quasar-app-extension-dialog-file-preview";

if (isPreviewableMime(file.type)) {
  // show preview button
}

Values of PREVIEWABLE_MIME_PREFIXES (prefix with / = full family; rest = exact MIME):

Entry Category
image/ Images
application/pdf PDF
video/mp4, video/webm, video/ogg Video
audio/mpeg, audio/wav, audio/ogg, audio/webm Audio
text/plain, text/csv, application/json, application/xml, text/xml Text
Extension Purpose Relationship with Dialog File Preview
@benjaminor-dev/form-builder Declarative forms with defineForm and FormBuilder InputFile and InputFileMultiple detect this extension when showPreview is not false (default true): view/download buttons in the append or management table. Not a dependency of Dialog File Preview

If you use both in the host:

quasar ext add @benjaminor-dev/form-builder
quasar ext add @benjaminor-dev/dialog-file-preview

Full stack boot order:

1. Pinia (host)
2. Form Builder — boot/store
3. bor/bor-form-builder-defaults (host)
4. Table Builder — boot/store
5. bor/bor-table-builder-defaults (host)
6. Dialog File Preview — boot/dialog
7. bor/bor-dialog-file-preview-defaults (host)
8. Dialog Loader — boot/dialog
9. bor/bor-dialog-loader-defaults (host)
10. Dialog Message — boot/dialog
11. bor/bor-dialog-message-defaults (host)

In quasar.config you will see each extension's defaults boot (e.g. bor/bor-dialog-file-preview-defaults); package boots are injected by the extension at compile time — they do not appear as short entries in your config.

Manual use from a callback or custom button (without depending on Form Builder):

<script setup lang="ts">
import { useDialogFilePreview } from "@benjaminor-dev/quasar-app-extension-dialog-file-preview";

const preview = useDialogFilePreview();

function onPreviewFile(file: File) {
  void preview.show(file);
}
</script>

The boot also exposes the API via provide (DIALOG_FILE_PREVIEW_API_KEY) for sibling library integrations — same instance as useDialogFilePreview().

Public entrypoints

Entrypoint Purpose
@benjaminor-dev/quasar-app-extension-dialog-file-preview useDialogFilePreview, configureDialogFilePreviewDefaults, i18n helpers (resolveDialogFilePreviewMessages, resolveExtensionLocale), MIME utilities (isPreviewableMime, PREVIEW_MAX_BYTES_BY_KIND, …) — IDE types (PreviewSessionOptions, DialogFilePreviewDefaults, DialogFilePreviewMessages, …)
@benjaminor-dev/quasar-app-extension-dialog-file-preview/boot/dialog Quasar boot (registered automatically by the extension)
@benjaminor-dev/quasar-app-extension-dialog-file-preview/main.css Dialog styles (registered automatically)

Troubleshooting

UI strings stay in English

Boots or styles missing from quasar.config

Under normal conditions install registers the defaults boot in boot: [] and the runner injects boot/dialog + CSS at compile time. If the defaults boot is missing (older project):

npx quasar ext invoke @benjaminor-dev/dialog-file-preview

Error: Pinia not initialized / store unavailable

useDialogFilePreview() outside setup

Call it inside setup, another composable, or <script setup>. If you need it outside the component context, ensure the extension boot has already run (app mounted).

The "View" button should not be shown

Use preview.canPreview(file) before rendering the action. Returns false if the MIME has no viewer or if the local file exceeds the size limit. isPreviewableMime() only evaluates MIME, not size.

canPreview() returns false due to size

The local file exceeds its type limit (e.g. image > 50 MB). By default show(file) opens fallback with download. To force the embedded viewer: show(file, { forcePreview: true })at your own risk (performance, frozen tab, or browser close). canPreview() will still be false.

canPreview() returns false for a URL without extension

canPreview() does not perform remote probing. If the URL has no extension or known heuristic, pass mimeType in the descriptor or call show() directly.

Very large PDF: I only see one page at a time

Expected behavior in paginated mode: files above ~25 MB, with more than 40 pages, or long documents without known size (> 15 pages) render one at a time to avoid saturating browser memory. Use the page bar to navigate.

PDF page counter does not change when scrolling

In continuous mode with high zoom (≥ 90%), wait a moment after stopping scroll; the counter tracks the page with the largest visible area. If the document entered paginated mode, viewport scroll does not apply — use the page buttons.

PDF or text from external URL does not load

Example public URL that works in tests:

preview.show({
  source:
    "https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf",
  name: "tracemonkey-pldi-09.pdf",
  mimeType: "application/pdf",
});

Download button does not appear on open

Expected at first: toolbar download is enabled only when the viewer emits ready state and showDownload is not false. While preparing sources or loading PDF/image/text you will see "Loading file..." in the viewport. If the loader does not disappear, check network/CORS errors in the console.

restrictInteraction does not block everything

The option reduces copy, selection, and context menu in the viewer, but does not replace DRM or prevent screenshots. A technically savvy user can still access the content.

Dialog opens empty briefly or takes time to show the file

show() opens the modal immediately and normalizes sources in the background. With remote URLs or multiple files, the overlay may remain for a few seconds; that alone does not indicate failure.

Image/PDF preview works locally but not in production

Verify the extension is installed/invoked in the production build and that the extension's main.css is in the generated config.

Multiple files: navigation does not appear

Pass an array to show([...]). The previous/next bar only shows when there is more than one item and showGalleryNav is not false.


MIT © Benjamín Olvera R.