Dialog File Preview para Quasar

🇬🇧 English version | 🇪🇸 Versión en español

npm version license Vue Quasar TypeScript Node

Extensão Quasar para pré-visualizar arquivos em um diálogo modal em apps Vue 3 + Quasar, com API imperativa baseada em composable.

Índice

Em 30 segundos

Fluxo base recomendado:

  1. Instalar a extensão na sua app Quasar (quasar ext add).
  2. Garantir que o boot de Pinia do host rode antes do boot desta extensão.
  3. Importar useDialogFilePreview() onde precisar abrir a pré-visualização.
  4. Chamar void preview.show(arquivo) — o diálogo é montado globalmente; não é necessário adicionar componentes aos seus templates.

A extensão registra boots e CSS no projeto host automaticamente após quasar ext add.

O que é e o que inclui

Diálogo modal global controlado por useDialogFilePreview() — não é um componente embutível.

Requisitos e compatibilidade

Item Valor
Node >= 20.0.0
Quasar ^2.6.0
Vue ^3.4.18
Pinia ^2.0.11 | ^3.0.0
Formato do pacote ES modules
CLI recomendado @quasar/app-vite ^2.x ou ^3.x
PDF no pacote vue-pdf-embed / pdf.js incluídos (sem dependência extra no host)

O boot de Pinia do host deve executar antes do boot desta extensão.

Instalação na app Quasar

Adicionar a extensão na sua app Quasar:

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

Remover:

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

Após quasar ext add, a extensão registra boots e CSS na sua app automaticamente. Não são necessários passos adicionais em uma instalação normal via npm.

O que a extensão adiciona ao host

Recursos que a extensão instala ou registra:

Recurso Caminho npm
Boot do diálogo ~@benjaminor-dev/quasar-app-extension-dialog-file-preview/boot/dialog
Estilos ~@benjaminor-dev/quasar-app-extension-dialog-file-preview/main.css
Defaults boot (host) src/boot/bor/bor-dialog-file-preview-defaults.ts — criado no install; Skip/Overwrite se já existir

Install (quasar ext add / invoke): cria src/boot/bor/bor-dialog-file-preview-defaults.ts (ou .js) e o registra em quasar.configboot: [] automaticamente. Não é necessário editá-lo manualmente em uma instalação normal. O boot do pacote (boot/dialog) e main.css são injetados pela extensão em cada dev/build (não aparecem como entradas curtas em quasar.config).

Remove (quasar ext remove): remove o defaults boot e sua entrada em quasar.config.

Ordem de boots (esta extensão): Pinia → boot dialog DFP → bor/bor-dialog-file-preview-defaults → resto. Em quasar.config você verá bor/bor-dialog-file-preview-defaults; o boot boot/dialog é adicionado pelo runner ao compilar.

O boot monta o diálogo no documento e conecta a store Pinia do host. Não é necessário importar DialogFilePreview em App.vue.

Defaults globais (boot)

Evite repetir showPrint / restrictInteraction em cada show(). Prioridade: show(input, options) → boot (configureDialogFilePreviewDefaults) → built-in.

Após quasar ext add, revise src/boot/bor/bor-dialog-file-preview-defaults.ts — template com todas as opções boot (// recomendado: …). O boot não admite title nem forcePreview — essas opções vão apenas em 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 é alias de configureDialogFilePreviewDefaults.

Idioma (i18n)

Integrado en, es e pt. Por padrão, rótulos da toolbar, tooltips, controles PDF e mensagens fallback seguem o idioma do Quasar ($q.lang.isoName); se o Quasar não tiver idioma, fallback en.

Resolução de locale: boot locale → prefixo de $q.lang.isoName (es*es, pt*pt) → en.

Opção Quando usar
locale: 'es' no boot Fixar idioma (en | es | pt); ignora mudanças de $q.lang
messages: { download: '…' } Alterar um texto sem fixar locale

Exemplo no quasar.config (português para todo o app):

framework: {
  lang: "pt-BR",
},

Template boot (após install):

configureDialogFilePreviewDefaults({
  // locale: "pt", // descomente para fixar só o idioma do Dialog File Preview
  showPrint: true,
});

Override de um único string:

configureDialogFilePreviewDefaults({
  messages: { loadingFile: "Abrindo arquivo…" },
});

Uso rápido

<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="Ver PDF" @click="onViewPdf(selectedFile)" />
</template>

Vários arquivos (galeria ou anexos):

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

URL remota com nome explícito:

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 no seu IDE

O composable expõe uma API tipada: use autocompletar em vez de adivinhar opções.

Para verificar se um arquivo pode ser pré-visualizado antes de abrir o modal, canPreview() também está tipado conforme a fonte que você passar.

API pública

Ponto de entrada: useDialogFilePreview().

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

const preview = useDialogFilePreview();

Métodos

Método Descrição
show(input, options?) Abre o diálogo imediatamente e retorna uma Promise que resolve quando termina a normalização/sondagem MIME das fontes. O segundo argumento options configura a sessão (toolbar, título, restrições, forcePreview). Por padrão, arquivos locais que excedem o limite de tamanho abrem em modo fallback (mensagem + download); veja forcePreview para forçar o visualizador embutido. Enquanto isso, e até o visualizador emitir estado pronto, o viewport mostra «Carregando arquivo...».
hide() Fecha o diálogo e libera URLs blob: criadas pela extensão.
next() Próximo arquivo (se houver vários).
previous() Arquivo anterior.
downloadCurrent() Faz download do arquivo visível (de File/Blob ou link se houver apenas URL).
canPreview(source) true se o MIME tem visualizador no diálogo e o arquivo local não excede o limite de tamanho. Avaliação síncrona (sem sondagem remota).
canPreviewAll(sources) true se todos os itens do array são pré-visualizáveis (também síncrono).

Estado reativo (somente leitura)

Ref Descrição
visible Se o diálogo está aberto.
current Metadados do item atual (PreviewItem) ou null.
hasPrevious / hasNext Navegação em listas multi-arquivo.
hasMultiple Há mais de um arquivo na sessão atual.

Use canPreview() para mostrar ou ocultar botões «Ver» na sua UI sem abrir o diálogo. Se a URL não tem extensão nem MIME conhecido, passe mimeType no descriptor ou chame show() diretamente (a sondagem remota ocorre ao abrir, não em canPreview). Se o arquivo local excede o limite de tamanho, canPreview() retorna false mesmo que o MIME seja válido; show() mostra fallback salvo que você passe forcePreview: true (por sua conta e risco).

Comportamento ao abrir (show)

  1. O diálogo fica visível instantaneamente.
  2. Um único overlay de carregamento cobre o viewport (fundo escuro, spinner branco, «Carregando arquivo...») enquanto:
    • as fontes são enriquecidas (File/Blob/URL → itens com MIME e URL de visualização), e
    • o visualizador do item atual termina seu carregamento visível (em PDF: primeiro render do canvas, não apenas metadados do documento).
  3. Os botões imprimir e download na toolbar aparecem apenas quando o visualizador emite estado pronto (ready) e a opção de sessão correspondente está ativa (showPrint / showDownload).
  4. Imprimir só é oferecido para PDF, imagens e texto; vídeo e áudio não mostram o botão mesmo que showPrint seja true.
  5. Ao mudar de arquivo em uma galeria (next / previous), o loader volta até o novo visualizador estar pronto.
  6. Ao imprimir (botão ou Ctrl/Cmd+P), se a preparação do iframe/PDF demorar, o mesmo overlay mostra «Preparando impressão...» após ~250 ms e some no instante em que aparece o diálogo do sistema (não espera você confirmar ou cancelar).

O estado interno de preparação não é exposto em useDialogFilePreview(); basta visible e a UX do overlay.

Opções de sessão (PreviewSessionOptions)

Segundo argumento opcional de show(). Aplica-se a toda a sessão do diálogo (incluindo galeria multi-arquivo).

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

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

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

// Forçar visualizador embutido mesmo excedendo o limite de tamanho (risco de desempenho / crash)
void preview.show(heavyLocalPdf, { forcePreview: true });
Opção Default Descrição
showPrint true Mostra o botão imprimir quando o tipo permite (PDF, imagem, texto).
showDownload true Mostra o botão download na toolbar.
showGalleryNav true Mostra navegação anterior/próximo se houver vários arquivos.
showPdfZoom true Mostra «Zoom: N%» na toolbar para PDF.
title nome do arquivo Título centralizado na toolbar.
restrictInteraction false Bloqueia copiar, cortar, seleção, menu contextual e atalhos comuns no visualizador. Não evita capturas de tela nem ferramentas avançadas do navegador.
forcePreview false Ignora o limite de tamanho e abre o visualizador embutido mesmo que canPreview() seja false por tamanho. Responsabilidade do desenvolvedor: arquivos muito grandes podem deixar a UI lenta, congelar a aba ou fechar o navegador. Não altera o resultado de canPreview().

Se omitir options, são usados os valores padrão da tabela.

Limites de tamanho para pré-visualização

Para File / Blob com .size conhecido, a extensão aplica um teto antes de usar o visualizador embutido. Os mesmos limites usam canPreview() e show() por padrão.

Visualizador MIME / categoria Limite
Imagem image/* 50 MB
PDF application/pdf 100 MB
Vídeo video/mp4, video/webm, video/ogg 150 MB
Áudio audio/mpeg, audio/wav, audio/ogg, audio/webm 50 MB
Texto text/plain, text/csv, application/json, application/xml, text/xml 10 MB

Constante exportada: PREVIEW_MAX_BYTES_BY_KIND (bytes por tipo de visualizador).

Comportamento:

Fontes de arquivo aceitas

Tipo unificado PreviewSourceInput:

Forma Exemplo
File Arquivo de <input type="file"> ou new File(...)
Blob Blob de API ou canvas
string URL https://... ou blob:...
Objeto descriptor Veja abaixo
// File ou Blob direto
void preview.show(file);
void preview.show([imageFile, pdfFile]);

// URL com metadados explícitos (recomendado sem extensão clara)
void preview.show({
  source: "https://exemplo.com/documento",
  name: "contrato.pdf",
  mimeType: "application/pdf",
});

// Descriptor com File
void preview.show({
  source: selectedFile,
  name: "anexo-renomeado.pdf",
});
Situação O que fazer
File / Blob Nome e MIME são inferidos do objeto
URL sem extensão Passe name e mimeType no descriptor
URL remota ao abrir Pode ser sondada (HEAD/GET parcial) para inferir MIME
canPreview() retorna false (MIME) Avalia de forma síncrona; com mimeType explícito ou após sondagem, show() pode abrir igual (ou fallback se o tipo não tem visualizador)
canPreview() retorna false (tamanho) O File/Blob excede o limite de tamanho. show() abre fallback com download; use { forcePreview: true } apenas se assumir o risco de desempenho
URLs blob: externas A extensão não as revoga; apenas libera as criadas a partir de File/Blob

Tipos de arquivo suportados

Categoria MIME / prefixos Visualizador no diálogo Imprimível
Imagens image/* Imagem centralizada e contida no viewport; clique para zoom ×2 com pan; sem scroll na área do visualizador Sim
PDF application/pdf pdf.js (vue-pdf-embed): modo adaptativo (veja abaixo); barra de página com salto direto, zoom e camadas de texto/anotações Sim
Vídeo video/mp4, video/webm, video/ogg <video controls> Não
Áudio audio/mpeg, audio/wav, audio/ogg, audio/webm <audio controls> com ícone central e equalizador ao reproduzir Não
Texto text/plain, text/csv, application/json, application/xml, text/xml Texto monoespaçado com scroll Sim
Outros Mensagem + botão download Não

Se o MIME não tem visualizador, show() ainda pode abrir o diálogo em modo fallback para permitir o download.

Visualizador de imagens

Visualizador de PDF (modo adaptativo)

O visualizador escolhe automaticamente entre duas apresentações conforme o tamanho do documento:

Modo Quando Experiência
Scroll contínuo Arquivo leve (tipicamente ≤ 25 MB, ≤ 40 páginas) Todas as páginas em coluna com espaçamento; scroll vertical; a barra inferior acompanha a página visível
Paginado Arquivo pesado ou muitas páginas Uma página por vez (adequado para PDFs muito grandes)

Em ambos os modos:

Não há opção pública para forçar um modo: a heurística protege o navegador contra documentos enormes (ex.: centenas de MB ou milhares de páginas).

Tipos TypeScript exportados

Do entrypoint principal:

import type {
  DialogFilePreviewApi,
  DialogFilePreviewDefaults,
  PreviewBlockReason,
  PreviewSourceInput,
  PreviewItemInput,
  PreviewItem,
  PreviewKind,
  PreviewSessionOptions,
} from "@benjaminor-dev/quasar-app-extension-dialog-file-preview";
Tipo Uso
DialogFilePreviewApi Tipo de retorno de useDialogFilePreview()
DialogFilePreviewDefaults Opções admitidas em configureDialogFilePreviewDefaults (boot)
PreviewBlockReason Motivo de bloqueio por tamanho ("size", etc.)
PreviewSourceInput Primeiro argumento de show() e argumento de canPreview()
PreviewSessionOptions Segundo argumento opcional de show()
PreviewItemInput Descriptor com metadados opcionais
PreviewItem Item normalizado do estado reativo (current); inclui blockedReason: PreviewBlockReason | null quando aplicável
PreviewKind "image" | "pdf" | "video" | "audio" | "text" | "unknown"
type PreviewItemInput = {
  source: File | Blob | string;
  name?: string;
  mimeType?: string;
};

Utilitários MIME

Também exportados para validar na sua UI sem abrir o diálogo:

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

if (isPreviewableMime(file.type)) {
  // mostrar botão de pré-visualização
}

Valores de PREVIEWABLE_MIME_PREFIXES (prefixo com / = família completa; resto = MIME exato):

Entrada Categoria
image/ Imagens
application/pdf PDF
video/mp4, video/webm, video/ogg Vídeo
audio/mpeg, audio/wav, audio/ogg, audio/webm Áudio
text/plain, text/csv, application/json, application/xml, text/xml Texto

Extensões opcionais relacionadas

Extensão Propósito Relação com Dialog File Preview
@benjaminor-dev/form-builder Formulários declarativos com defineForm e FormBuilder InputFile e InputFileMultiple detectam esta extensão quando showPreview não é false (padrão true): botões ver/download no append ou na tabela de gestão. Não é dependência de Dialog File Preview

Se usar ambas no host:

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

Ordem de boots (stack completo):

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)

Em quasar.config você verá o defaults boot de cada extensão (ex.: bor/bor-dialog-file-preview-defaults); os boots do pacote são injetados pela extensão ao compilar — não aparecem como entradas curtas na sua config.

Uso manual a partir de um callback ou botão custom (sem depender de 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>

O boot também expõe a API via provide (DIALOG_FILE_PREVIEW_API_KEY) para integrações de bibliotecas irmãs — mesma instância que useDialogFilePreview().

Entrypoints públicos

Entrypoint Propósito
@benjaminor-dev/quasar-app-extension-dialog-file-preview useDialogFilePreview, configureDialogFilePreviewDefaults, i18n (resolveDialogFilePreviewMessages, resolveExtensionLocale), utilitários MIME — tipos no IDE (PreviewSessionOptions, DialogFilePreviewDefaults, DialogFilePreviewMessages, …)
@benjaminor-dev/quasar-app-extension-dialog-file-preview/boot/dialog Boot Quasar (registrado automaticamente pela extensão)
@benjaminor-dev/quasar-app-extension-dialog-file-preview/main.css Estilos do diálogo (registrados automaticamente)

Troubleshooting

Os textos da UI continuam em inglês

Boots ou estilos não aparecem em quasar.config

Em condições normais o install registra o defaults boot em boot: [] e o runner injeta boot/dialog + CSS ao compilar. Se faltar o defaults boot (projeto antigo):

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

Erro: Pinia não inicializada / store indisponível

useDialogFilePreview() fora de setup

Chame-o dentro de setup, outro composable ou <script setup>. Se precisar usá-lo fora do contexto de componente, garanta que o boot da extensão já foi executado (app montada).

O botão «Ver» não deveria ser mostrado

Use preview.canPreview(file) antes de renderizar a ação. Retorna false se o MIME não tem visualizador ou se o arquivo local excede o limite de tamanho. isPreviewableMime() só avalia MIME, não tamanho.

canPreview() retorna false por tamanho

O arquivo local excede o limite do seu tipo (ex.: imagem > 50 MB). Por padrão show(file) abre fallback com download. Para forçar o visualizador embutido: show(file, { forcePreview: true })por sua conta e risco (desempenho, aba congelada ou fechamento do navegador). canPreview() continuará false.

canPreview() retorna false em uma URL sem extensão

canPreview() não faz sondagem remota. Se a URL não tem extensão nem heurística conhecida, passe mimeType no descriptor ou chame show() diretamente.

PDF muito grande: só vejo uma página por vez

É o comportamento esperado no modo paginado: arquivos acima de ~25 MB, com mais de 40 páginas, ou documentos longos sem tamanho conhecido (> 15 páginas) são renderizados um por vez para não saturar a memória do navegador. Use a barra de páginas para navegar.

O contador de página do PDF não muda ao rolar

No modo contínuo com zoom alto (≥ 90%), aguarde um instante ao parar o scroll; o contador acompanha a página com maior área visível. Se o documento entrou em modo paginado, o scroll do viewport não se aplica — use os botões de página.

PDF ou texto de URL externa não carrega

Exemplo de URL pública que funciona em testes:

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

O botão de download não aparece ao abrir

É esperado no início: o download na toolbar só é habilitado quando o visualizador emite estado pronto (ready) e showDownload não é false. Enquanto prepara fontes ou carrega PDF/imagem/texto você verá «Carregando arquivo...» no viewport. Se o loader não desaparecer, revise erros de rede/CORS no console.

Não aparece o botão imprimir

O loader de impressão demora ou não aparece

restrictInteraction não bloqueia tudo

A opção reduz cópia, seleção e menu contextual no visualizador, mas não substitui DRM nem impede capturas de tela. Um usuário com conhecimentos técnicos ainda pode acessar o conteúdo.

O diálogo abre vazio por um instante ou demora a mostrar o arquivo

show() abre o modal imediatamente e normaliza as fontes em segundo plano. Com URLs remotas ou vários arquivos, o overlay pode permanecer alguns segundos; isso por si só não indica falha.

A pré-visualização de imagem/PDF funciona em local mas não em produção

Verifique que a extensão está instalada/invocada no build de produção e que main.css da extensão está na config gerada.

Vários arquivos: não aparece navegação

Passe um array para show([...]). A barra anterior/próximo só é mostrada quando há mais de um item e showGalleryNav não é false.


MIT © Benjamín Olvera R.