File helpers
Four helpers for getting a file out of the browser and back in again.
<script setup lang="ts">
import { downloadBlob, fileToBase64, getFileExtension, readFileAsText } from '@shared/utils/file'
import { Button } from '@shared/components/ui/button'
import { Input } from '@shared/components/ui/input'
import { Label } from '@shared/components/ui/label'
import { computed, ref } from 'vue'
const filename = ref('Quarterly Report.PDF')
const picked = ref('')
const preview = ref('')
const text = ref('')
const extension = computed(() => getFileExtension(filename.value) || '(none)')
const NAMES = ['Quarterly Report.PDF', '.gitignore', 'archive.tar.gz', 'v1.2/report', 'notes']
function saveCsv(): void {
const csv = 'reference,customer,total\nINV-0418,Northwind,4820\nINV-0419,Contoso,1140\n'
downloadBlob(new Blob([csv], { type: 'text/csv' }), 'invoices.csv')
}
async function onPick(event: Event): Promise<void> {
const input = event.target as HTMLInputElement
const file = input.files?.[0]
if (!file)
return
picked.value = `${file.name} · ${getFileExtension(file.name) || 'no extension'}`
preview.value = file.type.startsWith('image/') ? await fileToBase64(file) : ''
text.value = file.type.startsWith('text/') ? (await readFileAsText(file)).slice(0, 200) : ''
}
</script>
<template>
<div class="flex flex-col gap-5">
<section class="flex flex-col gap-2">
<Label for="file-name">getFileExtension</Label>
<Input id="file-name" v-model="filename" class="max-w-md" />
<p class="sa-text-sm text-muted-foreground">
→ <strong>{{ extension }}</strong>
</p>
<div class="flex flex-wrap gap-x-4 gap-y-1 sa-text-xs text-muted-foreground">
<span v-for="name in NAMES" :key="name">
<code>{{ name }}</code> → {{ getFileExtension(name) || "''" }}
</span>
</div>
</section>
<section class="flex flex-col gap-2">
<span class="sa-text-sm font-semibold">downloadBlob</span>
<div>
<Button size="sm" variant="outline" @click="saveCsv()">
Download invoices.csv
</Button>
</div>
</section>
<section class="flex flex-col gap-2">
<Label for="file-pick">Read a file</Label>
<input id="file-pick" type="file" class="sa-text-sm" @change="onPick">
<p v-if="picked" class="sa-text-sm text-muted-foreground">
{{ picked }}
</p>
<img v-if="preview" :src="preview" alt="The file you chose" class="max-h-40 w-auto rounded-lg border border-border">
<pre v-if="text" class="overflow-x-auto rounded-lg border border-border p-3 sa-text-xs">{{ text }}</pre>
</section>
</div>
</template>Import
import { downloadBlob, fileToBase64, getFileExtension, readFileAsText } from '@shared/utils/file'downloadBlob
function downloadBlob(data: Blob | string, filename: string, mimeType?: string): voiddownloadBlob(new Blob([csv], { type: 'text/csv' }), 'report.csv')
downloadBlob('hello', 'note.txt')A string is wrapped in a Blob for you, using mimeType — which defaults to text/plain;charset=utf-8.
The object URL is revoked on the next frame, not in the same tick. Same-tick revocation cancels the download in Safari and older Firefox.
fileToBase64
function fileToBase64(file: Blob): Promise<string>const preview = await fileToBase64(file)Returns a full data URL — data:image/png;base64,iVBORw0…. The prefix is kept, since that is what <img src> needs.
readFileAsText
function readFileAsText(file: Blob, encoding?: string): Promise<string>const csv = await readFileAsText(file)
const legacy = await readFileAsText(file, 'windows-1256')encoding defaults to 'utf-8'. Pass the real one for a legacy export: a Windows-1256 Arabic CSV read as UTF-8 comes back as mojibake rather than as an error, which is far harder to notice.
getFileExtension
function getFileExtension(filename: string): stringgetFileExtension('Quarterly Report.PDF') // 'pdf'Lower-cased, without the dot, and '' when there is none — so it is always safe to compare.
Three cases name.split('.').pop() gets wrong and this does not: a dotfile such as .gitignore, a double extension such as archive.tar.gz, and a dot in a directory name such as v1.2/report.
Related
useFileUpload— selection, validation and upload.useExport— CSV, Excel and PDF, which uses the same download path.- Number helpers —
formatBytesfor the size beside the name.