forked from vitest-dev/vitest
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(useFileSystemAccess): new function (vitest-dev#1243)
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
- Loading branch information
1 parent
2a582d4
commit 28066cb
Showing
4 changed files
with
304 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
<script setup lang="ts"> | ||
import { stringify } from '@vueuse/docs-utils' | ||
import type { Ref } from 'vue' | ||
import { reactive, ref } from 'vue' | ||
import { useFileSystemAccess } from '.' | ||
const dataType = ref('Text') as Ref<'Text' | 'ArrayBuffer' | 'Blob'> | ||
const res = useFileSystemAccess({ | ||
dataType, | ||
types: [{ | ||
description: 'text', | ||
accept: { | ||
'text/plain': ['.txt', '.html'], | ||
}, | ||
}], | ||
excludeAcceptAllOption: true, | ||
}) | ||
const content = res.data | ||
const str = stringify(reactive({ | ||
isSupported: res.isSupported, | ||
file: res.file, | ||
fileName: res.fileName, | ||
fileMIME: res.fileMIME, | ||
fileSize: res.fileSize, | ||
fileLastModified: res.fileLastModified, | ||
})) | ||
async function onSave() { | ||
await res.save() | ||
} | ||
</script> | ||
|
||
<template> | ||
<div> | ||
<div flex="~ gap-1" items-center> | ||
<button @click="res.open()"> | ||
Open | ||
</button> | ||
<button @click="res.create()"> | ||
New file | ||
</button> | ||
<button :disabled="!res.file.value" @click="onSave"> | ||
Save | ||
</button> | ||
<button :disabled="!res.file.value" @click="res.saveAs()"> | ||
Save as | ||
</button> | ||
|
||
<div ml5> | ||
<div text-xs op50> | ||
DataType | ||
</div> | ||
<select v-model="dataType" class="outline-none w-30 px2 py1 text-sm" border="~ main rounded"> | ||
<option value="Text"> | ||
Text | ||
</option> | ||
<option value="ArrayBuffer"> | ||
ArrayBuffer | ||
</option> | ||
<option value="Blob"> | ||
Blob | ||
</option> | ||
</select> | ||
</div> | ||
</div> | ||
|
||
<pre class="code-block" lang="yaml">{{ str }}</pre> | ||
|
||
<div v-if="content"> | ||
Content | ||
<textarea | ||
v-if="typeof content === 'string'" | ||
v-model="content" rows="20" cols="40" w-full | ||
/> | ||
<span v-else>{{ content }}</span> | ||
</div> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
--- | ||
category: Browser | ||
--- | ||
|
||
# useFileSystemAccess | ||
|
||
Create and read and write local files with [FileSystemAccessAPI](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API) | ||
|
||
## Usage | ||
|
||
```ts | ||
import { useFileSystemAccess } from '@vueuse/core' | ||
|
||
const { isSupported, data, file, fileName, fileMIME, fileSize, fileLastModified, create, open, save, saveAs, updateData } = useFileSystemAccess() | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
import type { Ref } from 'vue-demi' | ||
import { computed, ref, unref, watch } from 'vue-demi' | ||
import type { Awaitable, MaybeRef } from '@vueuse/shared' | ||
import type { ConfigurableWindow } from '../_configurable' | ||
import { defaultWindow } from '../_configurable' | ||
|
||
/** | ||
* window.showOpenFilePicker parameters | ||
* @see https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker#parameters | ||
*/ | ||
export interface FileSystemAccessShowOpenFileOptions { | ||
multiple?: boolean | ||
types?: Array<{ | ||
description?: string | ||
accept: Record<string, string[]> | ||
}> | ||
excludeAcceptAllOption?: boolean | ||
} | ||
|
||
/** | ||
* window.showSaveFilePicker parameters | ||
* @see https://developer.mozilla.org/en-US/docs/Web/API/window/showSaveFilePicker#parameters | ||
*/ | ||
export interface FileSystemAccessShowSaveFileOptions { | ||
suggestedName?: string | ||
types?: Array<{ | ||
description?: string | ||
accept: Record<string, string[]> | ||
}> | ||
excludeAcceptAllOption?: boolean | ||
} | ||
|
||
/** | ||
* FileHandle | ||
* @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileHandle | ||
*/ | ||
export interface FileSystemFileHandle { | ||
getFile: () => Promise<File> | ||
createWritable: () => FileSystemWritableFileStream | ||
} | ||
|
||
/** | ||
* @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream | ||
*/ | ||
interface FileSystemWritableFileStream extends WritableStream { | ||
/** | ||
* @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream/write | ||
*/ | ||
write: FileSystemWritableFileStreamWrite | ||
/** | ||
* @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream/seek | ||
*/ | ||
seek: (position: number) => Promise<void> | ||
/** | ||
* @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream/truncate | ||
*/ | ||
truncate: (size: number) => Promise<void> | ||
} | ||
|
||
/** | ||
* FileStream.write | ||
* @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream/write | ||
*/ | ||
interface FileSystemWritableFileStreamWrite { | ||
(data: string | BufferSource | Blob): Promise<void> | ||
(options: { type: 'write'; position: number; data: string | BufferSource | Blob }): Promise<void> | ||
(options: { type: 'seek'; position: number }): Promise<void> | ||
(options: { type: 'truncate'; size: number }): Promise<void> | ||
} | ||
|
||
/** | ||
* FileStream.write | ||
* @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream/write | ||
*/ | ||
export type FileSystemAccessWindow = Window & { | ||
showSaveFilePicker: (options: FileSystemAccessShowSaveFileOptions) => Promise<FileSystemFileHandle> | ||
showOpenFilePicker: (options: FileSystemAccessShowOpenFileOptions) => Promise<FileSystemFileHandle[]> | ||
} | ||
|
||
export type UseFileSystemAccessCommonOptions = Pick<FileSystemAccessShowOpenFileOptions, 'types' | 'excludeAcceptAllOption'> | ||
export type UseFileSystemAccessShowSaveFileOptions = Pick<FileSystemAccessShowSaveFileOptions, 'suggestedName'> | ||
|
||
export type UseFileSystemAccessOptions = ConfigurableWindow & UseFileSystemAccessCommonOptions & { | ||
/** | ||
* file data type | ||
*/ | ||
dataType?: MaybeRef<'Text' | 'ArrayBuffer' | 'Blob'> | ||
} | ||
|
||
/** | ||
* Create and read and write local files. | ||
* @see https://vueuse.org/useElementByPoint | ||
* @param options | ||
*/ | ||
export function useFileSystemAccess(options: UseFileSystemAccessOptions & { dataType: 'Text' }): UseFileSystemAccessReturn<string> | ||
export function useFileSystemAccess(options: UseFileSystemAccessOptions & { dataType: 'ArrayBuffer' }): UseFileSystemAccessReturn<ArrayBuffer> | ||
export function useFileSystemAccess(options: UseFileSystemAccessOptions & { dataType: 'Blob' }): UseFileSystemAccessReturn<Blob> | ||
export function useFileSystemAccess(options: UseFileSystemAccessOptions): UseFileSystemAccessReturn<string | ArrayBuffer | Blob> | ||
export function useFileSystemAccess(options: UseFileSystemAccessOptions = {}): UseFileSystemAccessReturn<string | ArrayBuffer | Blob> { | ||
const { | ||
window: _window = defaultWindow, | ||
dataType = 'Text', | ||
} = unref(options) | ||
const window = _window as FileSystemAccessWindow | ||
const isSupported = Boolean(window && 'showSaveFilePicker' in window && 'showOpenFilePicker' in window) | ||
|
||
const fileHandle = ref<FileSystemFileHandle>() | ||
const data = ref<string | ArrayBuffer | Blob>() | ||
|
||
const file = ref<File>() | ||
const fileName = computed(() => file.value?.name ?? '') | ||
const fileMIME = computed(() => file.value?.type ?? '') | ||
const fileSize = computed(() => file.value?.size ?? 0) | ||
const fileLastModified = computed(() => file.value?.lastModified ?? 0) | ||
|
||
async function open(_options: UseFileSystemAccessCommonOptions = {}) { | ||
if (!isSupported) | ||
return | ||
const [handle] = await window.showOpenFilePicker({ ...unref(options), ..._options }) | ||
fileHandle.value = handle | ||
await updateFile() | ||
await updateData() | ||
} | ||
|
||
async function create(_options: UseFileSystemAccessShowSaveFileOptions = {}) { | ||
if (!isSupported) | ||
return | ||
fileHandle.value = await (window as FileSystemAccessWindow).showSaveFilePicker({ ...unref(options), ..._options }) | ||
data.value = undefined | ||
await updateFile() | ||
await updateData() | ||
} | ||
|
||
async function save(_options: UseFileSystemAccessShowSaveFileOptions = {}) { | ||
if (!isSupported) | ||
return | ||
|
||
if (!fileHandle.value) | ||
// save as | ||
return saveAs(_options) | ||
|
||
if (data.value) { | ||
const writableStream = await fileHandle.value.createWritable() | ||
await writableStream.write(data.value) | ||
await writableStream.close() | ||
} | ||
await updateFile() | ||
} | ||
|
||
async function saveAs(_options: UseFileSystemAccessShowSaveFileOptions = {}) { | ||
if (!isSupported) | ||
return | ||
|
||
fileHandle.value = await (window as FileSystemAccessWindow).showSaveFilePicker({ ...unref(options), ..._options }) | ||
|
||
if (data.value) { | ||
const writableStream = await fileHandle.value.createWritable() | ||
await writableStream.write(data.value) | ||
await writableStream.close() | ||
} | ||
|
||
await updateFile() | ||
} | ||
|
||
async function updateFile() { | ||
file.value = await fileHandle.value?.getFile() | ||
} | ||
|
||
async function updateData() { | ||
if (unref(dataType) === 'Text') | ||
data.value = await file.value?.text() | ||
if (unref(dataType) === 'ArrayBuffer') | ||
data.value = await file.value?.arrayBuffer() | ||
if (unref(dataType) === 'Blob') | ||
data.value = file.value | ||
} | ||
|
||
watch(() => unref(dataType), updateData) | ||
|
||
return { | ||
isSupported, | ||
data, | ||
file, | ||
fileName, | ||
fileMIME, | ||
fileSize, | ||
fileLastModified, | ||
open, | ||
create, | ||
save, | ||
saveAs, | ||
updateData, | ||
} | ||
} | ||
|
||
export interface UseFileSystemAccessReturn<T = string> { | ||
isSupported: boolean | ||
data: Ref<T | undefined> | ||
file: Ref<File | undefined> | ||
fileName: Ref<string> | ||
fileMIME: Ref<string> | ||
fileSize: Ref<number> | ||
fileLastModified: Ref<number> | ||
open: (_options?: UseFileSystemAccessCommonOptions) => Awaitable<void> | ||
create: (_options?: UseFileSystemAccessShowSaveFileOptions) => Awaitable<void> | ||
save: (_options?: UseFileSystemAccessShowSaveFileOptions) => Awaitable<void> | ||
saveAs: (_options?: UseFileSystemAccessShowSaveFileOptions) => Awaitable<void> | ||
updateData: () => Awaitable<void> | ||
} |