Copies over browser-fs-access src :( (#470)
This commit is contained in:
parent
32a5511b3d
commit
59e5a446c9
12 changed files with 723 additions and 353 deletions
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2020 Google LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
import supported from './supported.js'
|
||||||
|
|
||||||
|
const implementation = !supported
|
||||||
|
? import('./legacy/directory-open.js')
|
||||||
|
: import('./fs-access/directory-open.js')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For opening directories, dynamically either loads the File System Access API
|
||||||
|
* module or the legacy method.
|
||||||
|
*/
|
||||||
|
export async function directoryOpen(...args) {
|
||||||
|
return (await implementation).default(...args)
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2020 Google LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
import supported from './supported.js'
|
||||||
|
|
||||||
|
const implementation = !supported
|
||||||
|
? import('./legacy/file-open.js')
|
||||||
|
: import('./fs-access/file-open.js')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For opening files, dynamically either loads the File System Access API module
|
||||||
|
* or the legacy method.
|
||||||
|
*/
|
||||||
|
export async function fileOpen(...args) {
|
||||||
|
return (await implementation).default(...args)
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2020 Google LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
import supported from './supported.js'
|
||||||
|
|
||||||
|
const implementation = !supported
|
||||||
|
? import('./legacy/file-save.js')
|
||||||
|
: import('./fs-access/file-save.js')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For saving files, dynamically either loads the File System Access API module
|
||||||
|
* or the legacy method.
|
||||||
|
*/
|
||||||
|
export async function fileSave(...args) {
|
||||||
|
return (await implementation).default(...args)
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2020 Google LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
const getFiles = async (dirHandle, recursive, path = dirHandle.name, skipDirectory) => {
|
||||||
|
const dirs = []
|
||||||
|
const files = []
|
||||||
|
for (const entry of dirHandle.values()) {
|
||||||
|
const nestedPath = `${path}/${entry.name}`
|
||||||
|
if (entry.kind === 'file') {
|
||||||
|
files.push(
|
||||||
|
await entry.getFile().then((file) => {
|
||||||
|
file.directoryHandle = dirHandle
|
||||||
|
return Object.defineProperty(file, 'webkitRelativePath', {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
get: () => nestedPath,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} else if (
|
||||||
|
entry.kind === 'directory' &&
|
||||||
|
recursive &&
|
||||||
|
(!skipDirectory || !skipDirectory(entry))
|
||||||
|
) {
|
||||||
|
dirs.push(await getFiles(entry, recursive, nestedPath, skipDirectory))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [...(await Promise.all(dirs)).flat(), ...(await Promise.all(files))]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a directory from disk using the File System Access API.
|
||||||
|
* @type { typeof import("../../index").directoryOpen }
|
||||||
|
*/
|
||||||
|
export default async (options = {}) => {
|
||||||
|
options.recursive = options.recursive || false
|
||||||
|
const handle = await window.showDirectoryPicker({
|
||||||
|
id: options.id,
|
||||||
|
startIn: options.startIn,
|
||||||
|
})
|
||||||
|
return getFiles(handle, options.recursive, undefined, options.skipDirectory)
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2020 Google LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
const getFileWithHandle = async (handle) => {
|
||||||
|
const file = await handle.getFile();
|
||||||
|
file.handle = handle;
|
||||||
|
return file;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a file from disk using the File System Access API.
|
||||||
|
* @type { typeof import("../../index").fileOpen }
|
||||||
|
*/
|
||||||
|
export default async (options = [{}]) => {
|
||||||
|
if (!Array.isArray(options)) {
|
||||||
|
options = [options];
|
||||||
|
}
|
||||||
|
const types = [];
|
||||||
|
options.forEach((option, i) => {
|
||||||
|
types[i] = {
|
||||||
|
description: option.description || '',
|
||||||
|
accept: {},
|
||||||
|
};
|
||||||
|
if (option.mimeTypes) {
|
||||||
|
option.mimeTypes.map((mimeType) => {
|
||||||
|
types[i].accept[mimeType] = option.extensions || [];
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
types[i].accept['*/*'] = option.extensions || [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const handleOrHandles = await window.showOpenFilePicker({
|
||||||
|
id: options[0].id,
|
||||||
|
startIn: options[0].startIn,
|
||||||
|
types,
|
||||||
|
multiple: options[0].multiple || false,
|
||||||
|
excludeAcceptAllOption: options[0].excludeAcceptAllOption || false,
|
||||||
|
});
|
||||||
|
const files = await Promise.all(handleOrHandles.map(getFileWithHandle));
|
||||||
|
if (options[0].multiple) {
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
return files[0];
|
||||||
|
};
|
|
@ -0,0 +1,91 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2020 Google LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a file to disk using the File System Access API.
|
||||||
|
* @type { typeof import("../../index").fileSave }
|
||||||
|
*/
|
||||||
|
export default async (
|
||||||
|
blobOrResponse,
|
||||||
|
options = [{}],
|
||||||
|
existingHandle = null,
|
||||||
|
throwIfExistingHandleNotGood = false
|
||||||
|
) => {
|
||||||
|
if (!Array.isArray(options)) {
|
||||||
|
options = [options];
|
||||||
|
}
|
||||||
|
options[0].fileName = options[0].fileName || 'Untitled';
|
||||||
|
const types = [];
|
||||||
|
options.forEach((option, i) => {
|
||||||
|
types[i] = {
|
||||||
|
description: option.description || '',
|
||||||
|
accept: {},
|
||||||
|
};
|
||||||
|
if (option.mimeTypes) {
|
||||||
|
if (i === 0) {
|
||||||
|
if (blobOrResponse.type) {
|
||||||
|
option.mimeTypes.push(blobOrResponse.type);
|
||||||
|
} else if (
|
||||||
|
blobOrResponse.headers &&
|
||||||
|
blobOrResponse.headers.get('content-type')
|
||||||
|
) {
|
||||||
|
option.mimeTypes.push(blobOrResponse.headers.get('content-type'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
option.mimeTypes.map((mimeType) => {
|
||||||
|
types[i].accept[mimeType] = option.extensions || [];
|
||||||
|
});
|
||||||
|
} else if (blobOrResponse.type) {
|
||||||
|
types[i].accept[blobOrResponse.type] = option.extensions || [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (existingHandle) {
|
||||||
|
try {
|
||||||
|
// Check if the file still exists.
|
||||||
|
await existingHandle.getFile();
|
||||||
|
} catch (err) {
|
||||||
|
existingHandle = null;
|
||||||
|
if (throwIfExistingHandleNotGood) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handle =
|
||||||
|
existingHandle ||
|
||||||
|
(await window.showSaveFilePicker({
|
||||||
|
suggestedName: options[0].fileName,
|
||||||
|
id: options[0].id,
|
||||||
|
startIn: options[0].startIn,
|
||||||
|
types,
|
||||||
|
excludeAcceptAllOption: options[0].excludeAcceptAllOption || false,
|
||||||
|
}));
|
||||||
|
const writable = await handle.createWritable();
|
||||||
|
// Use streaming on the `Blob` if the browser supports it.
|
||||||
|
if ('stream' in blobOrResponse) {
|
||||||
|
const stream = blobOrResponse.stream();
|
||||||
|
await stream.pipeTo(writable);
|
||||||
|
return handle;
|
||||||
|
// Handle passed `ReadableStream`.
|
||||||
|
} else if ('body' in blobOrResponse) {
|
||||||
|
await blobOrResponse.body.pipeTo(writable);
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
// Default case of `Blob` passed and `Blob.stream()` not supported.
|
||||||
|
await writable.write(blob);
|
||||||
|
await writable.close();
|
||||||
|
return handle;
|
||||||
|
};
|
|
@ -1,13 +1,76 @@
|
||||||
/**
|
/**
|
||||||
* Opens file(s) from disk.
|
* Properties shared by all `options` provided to file save and open operations
|
||||||
*/
|
*/
|
||||||
export function fileOpen<M extends boolean | undefined = false>(options?: {
|
export interface CoreFileOptions {
|
||||||
/** Acceptable MIME types. [] */
|
/** Acceptable file extensions. Defaults to [""]. */
|
||||||
mimeTypes?: string[]
|
|
||||||
/** Acceptable file extensions. Defaults to "". */
|
|
||||||
extensions?: string[]
|
extensions?: string[]
|
||||||
/** Suggested file description. Defaults to "". */
|
/** Suggested file description. Defaults to "". */
|
||||||
description?: string
|
description?: string
|
||||||
|
/** Acceptable MIME types. [] */
|
||||||
|
mimeTypes?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Properties shared by the _first_ `options` object provided to file save and
|
||||||
|
* open operations (any additional options objects provided to those operations
|
||||||
|
* cannot have these properties)
|
||||||
|
*/
|
||||||
|
export interface FirstCoreFileOptions extends CoreFileOptions {
|
||||||
|
startIn?: WellKnownDirectory | FileSystemHandle
|
||||||
|
/** By specifying an ID, the user agent can remember different directories for different IDs. */
|
||||||
|
id?: string
|
||||||
|
excludeAcceptAllOption?: boolean | false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The first `options` object passed to file save operations can also specify
|
||||||
|
* a filename
|
||||||
|
*/
|
||||||
|
export interface FirstFileSaveOptions extends FirstCoreFileOptions {
|
||||||
|
/** Suggested file name. Defaults to "Untitled". */
|
||||||
|
fileName?: string
|
||||||
|
/**
|
||||||
|
* Configurable cleanup and `Promise` rejector usable with legacy API for
|
||||||
|
* determining when (and reacting if) a user cancels the operation. The
|
||||||
|
* method will be passed a reference to the internal `rejectionHandler` that
|
||||||
|
* can, e.g., be attached to/removed from the window or called after a
|
||||||
|
* timeout. The method should return a function that will be called when
|
||||||
|
* either the user chooses to open a file or the `rejectionHandler` is
|
||||||
|
* called. In the latter case, the returned function will also be passed a
|
||||||
|
* reference to the `reject` callback for the `Promise` returned by
|
||||||
|
* `fileOpen`, so that developers may reject the `Promise` when desired at
|
||||||
|
* that time.
|
||||||
|
* Example rejector:
|
||||||
|
*
|
||||||
|
* const file = await fileOpen({
|
||||||
|
* legacySetup: (rejectionHandler) => {
|
||||||
|
* const timeoutId = setTimeout(rejectionHandler, 10_000);
|
||||||
|
* return (reject) => {
|
||||||
|
* clearTimeout(timeoutId);
|
||||||
|
* if (reject) {
|
||||||
|
* reject('My error message here.');
|
||||||
|
* }
|
||||||
|
* };
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* ToDo: Remove this workaround once
|
||||||
|
* https://github.com/whatwg/html/issues/6376 is specified and supported.
|
||||||
|
*/
|
||||||
|
legacySetup?: (
|
||||||
|
resolve: (value: Blob) => void,
|
||||||
|
rejectionHandler: () => void,
|
||||||
|
anchor: HTMLAnchorElement
|
||||||
|
) => (reject?: (reason?: any) => void) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The first `options` object passed to file open operations can specify
|
||||||
|
* whether multiple files can be selected (the return type of the operation
|
||||||
|
* will be updated appropriately) and a way of handling cleanup and rejection
|
||||||
|
* for legacy open operations.
|
||||||
|
*/
|
||||||
|
export interface FirstFileOpenOptions<M extends boolean | undefined> extends FirstCoreFileOptions {
|
||||||
/** Allow multiple files to be selected. Defaults to false. */
|
/** Allow multiple files to be selected. Defaults to false. */
|
||||||
multiple?: M
|
multiple?: M
|
||||||
/**
|
/**
|
||||||
|
@ -21,29 +84,53 @@ export function fileOpen<M extends boolean | undefined = false>(options?: {
|
||||||
* reference to the `reject` callback for the `Promise` returned by
|
* reference to the `reject` callback for the `Promise` returned by
|
||||||
* `fileOpen`, so that developers may reject the `Promise` when desired at
|
* `fileOpen`, so that developers may reject the `Promise` when desired at
|
||||||
* that time.
|
* that time.
|
||||||
|
* Example rejector:
|
||||||
|
*
|
||||||
|
* const file = await fileOpen({
|
||||||
|
* legacySetup: (rejectionHandler) => {
|
||||||
|
* const timeoutId = setTimeout(rejectionHandler, 10_000);
|
||||||
|
* return (reject) => {
|
||||||
|
* clearTimeout(timeoutId);
|
||||||
|
* if (reject) {
|
||||||
|
* reject('My error message here.');
|
||||||
|
* }
|
||||||
|
* };
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*
|
||||||
* ToDo: Remove this workaround once
|
* ToDo: Remove this workaround once
|
||||||
* https://github.com/whatwg/html/issues/6376 is specified and supported.
|
* https://github.com/whatwg/html/issues/6376 is specified and supported.
|
||||||
*/
|
*/
|
||||||
setupLegacyCleanupAndRejection?: (
|
legacySetup?: (
|
||||||
rejectionHandler?: () => void
|
resolve: (value: M extends false | undefined ? FileWithHandle : FileWithHandle[]) => void,
|
||||||
) => (reject: (reason?: any) => void) => void
|
rejectionHandler: () => void,
|
||||||
}): M extends false | undefined ? Promise<FileWithHandle> : Promise<FileWithHandle[]>
|
input: HTMLInputElement
|
||||||
|
) => (reject?: (reason?: any) => void) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens file(s) from disk.
|
||||||
|
*/
|
||||||
|
export function fileOpen<M extends boolean | undefined = false>(
|
||||||
|
options?: [FirstFileOpenOptions<M>, ...CoreFileOptions[]] | FirstFileOpenOptions<M>
|
||||||
|
): M extends false | undefined ? Promise<FileWithHandle> : Promise<FileWithHandle[]>
|
||||||
|
|
||||||
|
export type WellKnownDirectory =
|
||||||
|
| 'desktop'
|
||||||
|
| 'documents'
|
||||||
|
| 'downloads'
|
||||||
|
| 'music'
|
||||||
|
| 'pictures'
|
||||||
|
| 'videos'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves a file to disk.
|
* Saves a file to disk.
|
||||||
* @returns Optional file handle to save in place.
|
* @returns Optional file handle to save in place.
|
||||||
*/
|
*/
|
||||||
export function fileSave(
|
export function fileSave(
|
||||||
/** To-be-saved blob */
|
/** To-be-saved `Blob` or `Response` */
|
||||||
blob: Blob,
|
blobOrResponse: Blob | Response,
|
||||||
options?: {
|
options?: [FirstFileSaveOptions, ...CoreFileOptions[]] | FirstFileSaveOptions,
|
||||||
/** Suggested file name. Defaults to "Untitled". */
|
|
||||||
fileName?: string
|
|
||||||
/** Suggested file extensions. Defaults to [""]. */
|
|
||||||
extensions?: string[]
|
|
||||||
/** Suggested file description. Defaults to "". */
|
|
||||||
description?: string
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* A potentially existing file handle for a file to save to. Defaults to
|
* A potentially existing file handle for a file to save to. Defaults to
|
||||||
* null.
|
* null.
|
||||||
|
@ -54,7 +141,7 @@ export function fileSave(
|
||||||
* when existingHandle is no longer good. Defaults to false.
|
* when existingHandle is no longer good. Defaults to false.
|
||||||
*/
|
*/
|
||||||
throwIfExistingHandleNotGood?: boolean | false
|
throwIfExistingHandleNotGood?: boolean | false
|
||||||
): Promise<FileSystemHandle>
|
): Promise<FileSystemHandle | null>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens a directory from disk using the File System Access API.
|
* Opens a directory from disk using the File System Access API.
|
||||||
|
@ -63,6 +150,45 @@ export function fileSave(
|
||||||
export function directoryOpen(options?: {
|
export function directoryOpen(options?: {
|
||||||
/** Whether to recursively get subdirectories. */
|
/** Whether to recursively get subdirectories. */
|
||||||
recursive: boolean
|
recursive: boolean
|
||||||
|
/** Suggested directory in which the file picker opens. */
|
||||||
|
startIn?: WellKnownDirectory | FileSystemHandle
|
||||||
|
/** By specifying an ID, the user agent can remember different directories for different IDs. */
|
||||||
|
id?: string
|
||||||
|
/** Callback to determine whether a directory should be entered, return `true` to skip. */
|
||||||
|
skipDirectory?: (fileSystemDirectoryEntry: FileSystemDirectoryEntry) => boolean
|
||||||
|
/**
|
||||||
|
* Configurable setup, cleanup and `Promise` rejector usable with legacy API
|
||||||
|
* for determining when (and reacting if) a user cancels the operation. The
|
||||||
|
* method will be passed a reference to the internal `rejectionHandler` that
|
||||||
|
* can, e.g., be attached to/removed from the window or called after a
|
||||||
|
* timeout. The method should return a function that will be called when
|
||||||
|
* either the user chooses to open a file or the `rejectionHandler` is
|
||||||
|
* called. In the latter case, the returned function will also be passed a
|
||||||
|
* reference to the `reject` callback for the `Promise` returned by
|
||||||
|
* `fileOpen`, so that developers may reject the `Promise` when desired at
|
||||||
|
* that time.
|
||||||
|
* Example rejector:
|
||||||
|
*
|
||||||
|
* const file = await directoryOpen({
|
||||||
|
* legacySetup: (rejectionHandler) => {
|
||||||
|
* const timeoutId = setTimeout(rejectionHandler, 10_000);
|
||||||
|
* return (reject) => {
|
||||||
|
* clearTimeout(timeoutId);
|
||||||
|
* if (reject) {
|
||||||
|
* reject('My error message here.');
|
||||||
|
* }
|
||||||
|
* };
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* ToDo: Remove this workaround once
|
||||||
|
* https://github.com/whatwg/html/issues/6376 is specified and supported.
|
||||||
|
*/
|
||||||
|
legacySetup?: (
|
||||||
|
resolve: (value: FileWithDirectoryHandle) => void,
|
||||||
|
rejectionHandler: () => void,
|
||||||
|
input: HTMLInputElement
|
||||||
|
) => (reject?: (reason?: any) => void) => void
|
||||||
}): Promise<FileWithDirectoryHandle[]>
|
}): Promise<FileWithDirectoryHandle[]>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,334 +1,24 @@
|
||||||
/* eslint-disable */
|
/**
|
||||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
* Copyright 2020 Google LLC
|
||||||
// @ts-nocheck
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
var w = Object.defineProperty
|
* you may not use this file except in compliance with the License.
|
||||||
var q = (e) => w(e, '__esModule', { value: !0 })
|
* You may obtain a copy of the License at
|
||||||
var u = (e, t) => () => e && (t = e((e = 0))),
|
*
|
||||||
t
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
var p = (e, t) => {
|
*
|
||||||
q(e)
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
for (var i in t) w(e, i, { get: t[i], enumerable: !0 })
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
}
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
var r = (e, t, i) =>
|
* See the License for the specific language governing permissions and
|
||||||
new Promise((d, a) => {
|
* limitations under the License.
|
||||||
var l = (s) => {
|
*/
|
||||||
try {
|
|
||||||
n(i.next(s))
|
|
||||||
} catch (o) {
|
|
||||||
a(o)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
c = (s) => {
|
|
||||||
try {
|
|
||||||
n(i.throw(s))
|
|
||||||
} catch (o) {
|
|
||||||
a(o)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
n = (s) => (s.done ? d(s.value) : Promise.resolve(s.value).then(l, c))
|
|
||||||
n((i = i.apply(e, t)).next())
|
|
||||||
})
|
|
||||||
var y = {}
|
|
||||||
p(y, { default: () => z })
|
|
||||||
var z,
|
|
||||||
v = u(() => {
|
|
||||||
z = (...t) =>
|
|
||||||
r(void 0, [...t], function* (e = {}) {
|
|
||||||
return new Promise((i, d) => {
|
|
||||||
let a = document.createElement('input')
|
|
||||||
a.type = 'file'
|
|
||||||
let l = [...(e.mimeTypes ? e.mimeTypes : []), e.extensions ? e.extensions : []].join()
|
|
||||||
;(a.multiple = e.multiple || !1), (a.accept = l || '')
|
|
||||||
let c,
|
|
||||||
n = () => c(d)
|
|
||||||
e.setupLegacyCleanupAndRejection
|
|
||||||
? (c = e.setupLegacyCleanupAndRejection(n))
|
|
||||||
: ((c = (s) => {
|
|
||||||
window.removeEventListener('pointermove', n),
|
|
||||||
window.removeEventListener('pointerdown', n),
|
|
||||||
window.removeEventListener('keydown', n),
|
|
||||||
s && s(new DOMException('The user aborted a request.', 'AbortError'))
|
|
||||||
}),
|
|
||||||
window.addEventListener('pointermove', n),
|
|
||||||
window.addEventListener('pointerdown', n),
|
|
||||||
window.addEventListener('keydown', n)),
|
|
||||||
a.addEventListener('change', () => {
|
|
||||||
c(), i(a.multiple ? Array.from(a.files) : a.files[0])
|
|
||||||
}),
|
|
||||||
a.click()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
var E = {}
|
|
||||||
p(E, { default: () => B })
|
|
||||||
var h,
|
|
||||||
B,
|
|
||||||
j = u(() => {
|
|
||||||
;(h = (e) =>
|
|
||||||
r(void 0, null, function* () {
|
|
||||||
let t = yield e.getFile()
|
|
||||||
return (t.handle = e), t
|
|
||||||
})),
|
|
||||||
(B = (...t) =>
|
|
||||||
r(void 0, [...t], function* (e = {}) {
|
|
||||||
let i = yield window.chooseFileSystemEntries({
|
|
||||||
accepts: [
|
|
||||||
{
|
|
||||||
description: e.description || '',
|
|
||||||
mimeTypes: e.mimeTypes || ['*/*'],
|
|
||||||
extensions: e.extensions || [''],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
multiple: e.multiple || !1,
|
|
||||||
})
|
|
||||||
return e.multiple ? Promise.all(i.map(h)) : h(i)
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
var g = {}
|
|
||||||
p(g, { default: () => I })
|
|
||||||
var G,
|
|
||||||
I,
|
|
||||||
x = u(() => {
|
|
||||||
;(G = (e) =>
|
|
||||||
r(void 0, null, function* () {
|
|
||||||
let t = yield e.getFile()
|
|
||||||
return (t.handle = e), t
|
|
||||||
})),
|
|
||||||
(I = (...t) =>
|
|
||||||
r(void 0, [...t], function* (e = {}) {
|
|
||||||
let i = {}
|
|
||||||
e.mimeTypes
|
|
||||||
? e.mimeTypes.map((l) => {
|
|
||||||
i[l] = e.extensions || []
|
|
||||||
})
|
|
||||||
: (i['*/*'] = e.extensions || [])
|
|
||||||
let d = yield window.showOpenFilePicker({
|
|
||||||
types: [{ description: e.description || '', accept: i }],
|
|
||||||
multiple: e.multiple || !1,
|
|
||||||
}),
|
|
||||||
a = yield Promise.all(d.map(G))
|
|
||||||
return e.multiple ? a : a[0]
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
var F = {}
|
|
||||||
p(F, { default: () => K })
|
|
||||||
var K,
|
|
||||||
k = u(() => {
|
|
||||||
K = (...t) =>
|
|
||||||
r(void 0, [...t], function* (e = {}) {
|
|
||||||
return (
|
|
||||||
(e.recursive = e.recursive || !1),
|
|
||||||
new Promise((i, d) => {
|
|
||||||
let a = document.createElement('input')
|
|
||||||
;(a.type = 'file'), (a.webkitdirectory = !0)
|
|
||||||
let l,
|
|
||||||
c = () => l(d)
|
|
||||||
e.setupLegacyCleanupAndRejection
|
|
||||||
? (l = e.setupLegacyCleanupAndRejection(c))
|
|
||||||
: ((l = (n) => {
|
|
||||||
window.removeEventListener('pointermove', c),
|
|
||||||
window.removeEventListener('pointerdown', c),
|
|
||||||
window.removeEventListener('keydown', c),
|
|
||||||
n && n(new DOMException('The user aborted a request.', 'AbortError'))
|
|
||||||
}),
|
|
||||||
window.addEventListener('pointermove', c),
|
|
||||||
window.addEventListener('pointerdown', c),
|
|
||||||
window.addEventListener('keydown', c)),
|
|
||||||
a.addEventListener('change', () => {
|
|
||||||
l()
|
|
||||||
let n = Array.from(a.files)
|
|
||||||
e.recursive || (n = n.filter((s) => s.webkitRelativePath.split('/').length === 2)),
|
|
||||||
i(n)
|
|
||||||
}),
|
|
||||||
a.click()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
var b = {}
|
|
||||||
p(b, { default: () => Q })
|
|
||||||
var P,
|
|
||||||
Q,
|
|
||||||
O = u(() => {
|
|
||||||
;(P = (d, a, ...l) =>
|
|
||||||
r(void 0, [d, a, ...l], function* (e, t, i = e.name) {
|
|
||||||
let c = [],
|
|
||||||
n = []
|
|
||||||
for (let s of e.getEntries()) {
|
|
||||||
let o = `${i}/${s.name}`
|
|
||||||
s.isFile
|
|
||||||
? n.push(
|
|
||||||
yield s.getFile().then(
|
|
||||||
(f) => (
|
|
||||||
(f.directoryHandle = e),
|
|
||||||
Object.defineProperty(f, 'webkitRelativePath', {
|
|
||||||
configurable: !0,
|
|
||||||
enumerable: !0,
|
|
||||||
get: () => o,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: s.isDirectory && t && c.push(yield P(s, t, o))
|
|
||||||
}
|
|
||||||
return [...(yield Promise.all(c)).flat(), ...(yield Promise.all(n))]
|
|
||||||
})),
|
|
||||||
(Q = (...t) =>
|
|
||||||
r(void 0, [...t], function* (e = {}) {
|
|
||||||
e.recursive = e.recursive || !1
|
|
||||||
let i = yield window.chooseFileSystemEntries({ type: 'open-directory' })
|
|
||||||
return P(i, e.recursive)
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
var T = {}
|
|
||||||
p(T, { default: () => V })
|
|
||||||
var R,
|
|
||||||
V,
|
|
||||||
S = u(() => {
|
|
||||||
;(R = (d, a, ...l) =>
|
|
||||||
r(void 0, [d, a, ...l], function* (e, t, i = e.name) {
|
|
||||||
let c = [],
|
|
||||||
n = []
|
|
||||||
for (let s of e.values()) {
|
|
||||||
let o = `${i}/${s.name}`
|
|
||||||
s.kind === 'file'
|
|
||||||
? n.push(
|
|
||||||
yield s.getFile().then(
|
|
||||||
(f) => (
|
|
||||||
(f.directoryHandle = e),
|
|
||||||
Object.defineProperty(f, 'webkitRelativePath', {
|
|
||||||
configurable: !0,
|
|
||||||
enumerable: !0,
|
|
||||||
get: () => o,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: s.kind === 'directory' && t && c.push(yield R(s, t, o))
|
|
||||||
}
|
|
||||||
return [...(yield Promise.all(c)).flat(), ...(yield Promise.all(n))]
|
|
||||||
})),
|
|
||||||
(V = (...t) =>
|
|
||||||
r(void 0, [...t], function* (e = {}) {
|
|
||||||
e.recursive = e.recursive || !1
|
|
||||||
let i = yield window.showDirectoryPicker()
|
|
||||||
return R(i, e.recursive)
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
var N = {}
|
|
||||||
p(N, { default: () => Y })
|
|
||||||
var Y,
|
|
||||||
U = u(() => {
|
|
||||||
Y = (i, ...d) =>
|
|
||||||
r(void 0, [i, ...d], function* (e, t = {}) {
|
|
||||||
let a = document.createElement('a')
|
|
||||||
;(a.download = t.fileName || 'Untitled'),
|
|
||||||
(a.href = URL.createObjectURL(e)),
|
|
||||||
a.addEventListener('click', () => {
|
|
||||||
setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1e3)
|
|
||||||
}),
|
|
||||||
a.click()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
var C = {}
|
|
||||||
p(C, { default: () => Z })
|
|
||||||
var Z,
|
|
||||||
D = u(() => {
|
|
||||||
Z = (d, ...a) =>
|
|
||||||
r(void 0, [d, ...a], function* (e, t = {}, i = null) {
|
|
||||||
;(t.fileName = t.fileName || 'Untitled'),
|
|
||||||
(i =
|
|
||||||
i ||
|
|
||||||
(yield window.chooseFileSystemEntries({
|
|
||||||
type: 'save-file',
|
|
||||||
accepts: [
|
|
||||||
{
|
|
||||||
description: t.description || '',
|
|
||||||
mimeTypes: [e.type],
|
|
||||||
extensions: t.extensions || [''],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})))
|
|
||||||
let l = yield i.createWritable()
|
|
||||||
return yield l.write(e), yield l.close(), i
|
|
||||||
})
|
|
||||||
})
|
|
||||||
var M = {}
|
|
||||||
p(M, { default: () => _ })
|
|
||||||
var _,
|
|
||||||
W = u(() => {
|
|
||||||
_ = (a, ...l) =>
|
|
||||||
r(void 0, [a, ...l], function* (e, t = {}, i = null, d = !1) {
|
|
||||||
t.fileName = t.fileName || 'Untitled'
|
|
||||||
let c = {}
|
|
||||||
if (
|
|
||||||
(t.mimeTypes
|
|
||||||
? (t.mimeTypes.push(e.type),
|
|
||||||
t.mimeTypes.map((o) => {
|
|
||||||
c[o] = t.extensions || []
|
|
||||||
}))
|
|
||||||
: (c[e.type] = t.extensions || []),
|
|
||||||
i)
|
|
||||||
)
|
|
||||||
try {
|
|
||||||
yield i.getFile()
|
|
||||||
} catch (o) {
|
|
||||||
if (((i = null), d)) throw o
|
|
||||||
}
|
|
||||||
let n =
|
|
||||||
i ||
|
|
||||||
(yield window.showSaveFilePicker({
|
|
||||||
suggestedName: t.fileName,
|
|
||||||
types: [{ description: t.description || '', accept: c }],
|
|
||||||
})),
|
|
||||||
s = yield n.createWritable()
|
|
||||||
return yield s.write(e), yield s.close(), n
|
|
||||||
})
|
|
||||||
})
|
|
||||||
p(exports, { directoryOpen: () => A, fileOpen: () => L, fileSave: () => $, supported: () => m })
|
|
||||||
var H = (() => {
|
|
||||||
if ('top' in self && self !== top)
|
|
||||||
try {
|
|
||||||
top.location + ''
|
|
||||||
} catch (e) {
|
|
||||||
return !1
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if ('chooseFileSystemEntries' in self) return 'chooseFileSystemEntries'
|
|
||||||
if ('showOpenFilePicker' in self) return 'showOpenFilePicker'
|
|
||||||
}
|
|
||||||
return !1
|
|
||||||
})(),
|
|
||||||
m = H
|
|
||||||
var J = m
|
|
||||||
? m === 'chooseFileSystemEntries'
|
|
||||||
? Promise.resolve().then(() => (j(), E))
|
|
||||||
: Promise.resolve().then(() => (x(), g))
|
|
||||||
: Promise.resolve().then(() => (v(), y))
|
|
||||||
function L(...e) {
|
|
||||||
return r(this, null, function* () {
|
|
||||||
return (yield J).default(...e)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
var X = m
|
|
||||||
? m === 'chooseFileSystemEntries'
|
|
||||||
? Promise.resolve().then(() => (O(), b))
|
|
||||||
: Promise.resolve().then(() => (S(), T))
|
|
||||||
: Promise.resolve().then(() => (k(), F))
|
|
||||||
function A(...e) {
|
|
||||||
return r(this, null, function* () {
|
|
||||||
return (yield X).default(...e)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
var ee = m
|
|
||||||
? m === 'chooseFileSystemEntries'
|
|
||||||
? Promise.resolve().then(() => (D(), C))
|
|
||||||
: Promise.resolve().then(() => (W(), M))
|
|
||||||
: Promise.resolve().then(() => (U(), N))
|
|
||||||
function $(...e) {
|
|
||||||
return r(this, null, function* () {
|
|
||||||
return (yield ee).default(...e)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @module browser-fs-access
|
||||||
|
*/
|
||||||
|
export { fileOpen } from './file-open.js'
|
||||||
|
export { directoryOpen } from './directory-open.js'
|
||||||
|
export { fileSave } from './file-save.js'
|
||||||
|
export { default as supported } from './supported.js'
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2020 Google LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a directory from disk using the legacy
|
||||||
|
* `<input type="file" webkitdirectory>` method.
|
||||||
|
* @type { typeof import("../../index").directoryOpen }
|
||||||
|
*/
|
||||||
|
export default async (options = [{}]) => {
|
||||||
|
if (!Array.isArray(options)) {
|
||||||
|
options = [options];
|
||||||
|
}
|
||||||
|
options[0].recursive = options[0].recursive || false;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'file';
|
||||||
|
input.webkitdirectory = true;
|
||||||
|
|
||||||
|
const _reject = () => cleanupListenersAndMaybeReject(reject);
|
||||||
|
const _resolve = (value) => {
|
||||||
|
if (typeof cleanupListenersAndMaybeReject === 'function') {
|
||||||
|
cleanupListenersAndMaybeReject();
|
||||||
|
}
|
||||||
|
resolve(value);
|
||||||
|
};
|
||||||
|
// ToDo: Remove this workaround once
|
||||||
|
// https://github.com/whatwg/html/issues/6376 is specified and supported.
|
||||||
|
const cleanupListenersAndMaybeReject =
|
||||||
|
options[0].legacySetup &&
|
||||||
|
options[0].legacySetup(_resolve, _reject, input);
|
||||||
|
|
||||||
|
input.addEventListener('change', () => {
|
||||||
|
let files = Array.from(input.files);
|
||||||
|
if (!options[0].recursive) {
|
||||||
|
files = files.filter((file) => {
|
||||||
|
return file.webkitRelativePath.split('/').length === 2;
|
||||||
|
});
|
||||||
|
} else if (options[0].recursive && options[0].skipDirectory) {
|
||||||
|
files = files.filter((file) => {
|
||||||
|
const directoriesName = file.webkitRelativePath.split('/');
|
||||||
|
return directoriesName.every(
|
||||||
|
(directoryName) =>
|
||||||
|
!options[0].skipDirectory({
|
||||||
|
name: directoryName,
|
||||||
|
kind: 'directory',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_resolve(files);
|
||||||
|
});
|
||||||
|
|
||||||
|
input.click();
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,54 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2020 Google LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a file from disk using the legacy `<input type="file">` method.
|
||||||
|
* @type { typeof import("../../index").fileOpen }
|
||||||
|
*/
|
||||||
|
export default async (options = [{}]) => {
|
||||||
|
if (!Array.isArray(options)) {
|
||||||
|
options = [options];
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'file';
|
||||||
|
const accept = [
|
||||||
|
...options.map((option) => option.mimeTypes || []).join(),
|
||||||
|
options.map((option) => option.extensions || []).join(),
|
||||||
|
].join();
|
||||||
|
input.multiple = options[0].multiple || false;
|
||||||
|
// Empty string allows everything.
|
||||||
|
input.accept = accept || '';
|
||||||
|
const _reject = () => cleanupListenersAndMaybeReject(reject);
|
||||||
|
const _resolve = (value) => {
|
||||||
|
if (typeof cleanupListenersAndMaybeReject === 'function') {
|
||||||
|
cleanupListenersAndMaybeReject();
|
||||||
|
}
|
||||||
|
resolve(value);
|
||||||
|
};
|
||||||
|
// ToDo: Remove this workaround once
|
||||||
|
// https://github.com/whatwg/html/issues/6376 is specified and supported.
|
||||||
|
const cleanupListenersAndMaybeReject =
|
||||||
|
options[0].legacySetup &&
|
||||||
|
options[0].legacySetup(_resolve, _reject, input);
|
||||||
|
input.addEventListener('change', () => {
|
||||||
|
_resolve(input.multiple ? Array.from(input.files) : input.files[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
input.click();
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,90 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2020 Google LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a file to disk using the legacy `<a download>` method.
|
||||||
|
* @type { typeof import("../../index").fileSave }
|
||||||
|
*/
|
||||||
|
export default async (blobOrResponse, options = {}) => {
|
||||||
|
if (Array.isArray(options)) {
|
||||||
|
options = options[0];
|
||||||
|
}
|
||||||
|
const a = document.createElement('a');
|
||||||
|
let data = blobOrResponse;
|
||||||
|
// Handle the case where input is a `ReadableStream`.
|
||||||
|
if ('body' in blobOrResponse) {
|
||||||
|
data = await streamToBlob(
|
||||||
|
blobOrResponse.body,
|
||||||
|
blobOrResponse.headers.get('content-type')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
a.download = options.fileName || 'Untitled';
|
||||||
|
a.href = URL.createObjectURL(data);
|
||||||
|
|
||||||
|
const _reject = () => cleanupListenersAndMaybeReject(reject);
|
||||||
|
const _resolve = () => {
|
||||||
|
if (typeof cleanupListenersAndMaybeReject === 'function') {
|
||||||
|
cleanupListenersAndMaybeReject();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// ToDo: Remove this workaround once
|
||||||
|
// https://github.com/whatwg/html/issues/6376 is specified and supported.
|
||||||
|
const cleanupListenersAndMaybeReject =
|
||||||
|
options.legacySetup && options.legacySetup(_resolve, _reject, a);
|
||||||
|
|
||||||
|
a.addEventListener('click', () => {
|
||||||
|
// `setTimeout()` due to
|
||||||
|
// https://github.com/LLK/scratch-gui/issues/1783#issuecomment-426286393
|
||||||
|
setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
|
||||||
|
_resolve(null);
|
||||||
|
});
|
||||||
|
a.click();
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a passed `ReadableStream` to a `Blob`.
|
||||||
|
* @param {ReadableStream} stream
|
||||||
|
* @param {string} type
|
||||||
|
* @returns {Promise<Blob>}
|
||||||
|
*/
|
||||||
|
async function streamToBlob(stream, type) {
|
||||||
|
const reader = stream.getReader();
|
||||||
|
const pumpedStream = new ReadableStream({
|
||||||
|
start(controller) {
|
||||||
|
return pump();
|
||||||
|
/**
|
||||||
|
* Recursively pumps data chunks out of the `ReadableStream`.
|
||||||
|
* @type { () => Promise<void> }
|
||||||
|
*/
|
||||||
|
async function pump() {
|
||||||
|
return reader.read().then(({ done, value }) => {
|
||||||
|
if (done) {
|
||||||
|
controller.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
controller.enqueue(value);
|
||||||
|
return pump();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = new Response(pumpedStream);
|
||||||
|
reader.releaseLock();
|
||||||
|
return new Blob([await res.blob()], { type });
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2020 Google LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the File System Access API is supported and usable in the
|
||||||
|
* current context (for example cross-origin iframes).
|
||||||
|
* @returns {boolean} Returns `true` if the File System Access API is supported and usable, else returns `false`.
|
||||||
|
*/
|
||||||
|
const supported = (() => {
|
||||||
|
// When running in an SSR environment return `false`.
|
||||||
|
if (typeof self === 'undefined') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// ToDo: Remove this check once Permissions Policy integration
|
||||||
|
// has happened, tracked in
|
||||||
|
// https://github.com/WICG/file-system-access/issues/245.
|
||||||
|
if ('top' in self && self !== top) {
|
||||||
|
try {
|
||||||
|
// This will succeed on same-origin iframes,
|
||||||
|
// but fail on cross-origin iframes.
|
||||||
|
top.location + ''
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if ('showOpenFilePicker' in self) {
|
||||||
|
return 'showOpenFilePicker'
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})()
|
||||||
|
|
||||||
|
export default supported
|
Loading…
Reference in a new issue