Another attempt at making use of the browser-fs-access
npm package directly (#894)
* Fight with TypeScript * Make TypeScript happy(?) * Apply suggestions from code review Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com> * Update yarn.lock * Fix favicons Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com> Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
This commit is contained in:
parent
bba572852f
commit
c8badf8072
26 changed files with 25 additions and 844 deletions
|
@ -2,7 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="shortcut icon" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>tldraw - core example advanced</title>
|
<title>tldraw - core example advanced</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
BIN
examples/core-example-advanced/public/favicon.ico
Normal file
BIN
examples/core-example-advanced/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -1,14 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="stylesheet" href="index.css" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<title>tldraw</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
||||||
<script type="module" src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -2,7 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="shortcut icon" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>tldraw - core example</title>
|
<title>tldraw - core example</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
BIN
examples/core-example/public/favicon.ico
Normal file
BIN
examples/core-example/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -1,14 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="stylesheet" href="index.css" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<title>tldraw</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
||||||
<script type="module" src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -2,7 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="shortcut icon" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>tldraw - example</title>
|
<title>tldraw - example</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
BIN
examples/tldraw-example/public/favicon.ico
Normal file
BIN
examples/tldraw-example/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -47,6 +47,7 @@
|
||||||
"@tldraw/core": "^1.15.0",
|
"@tldraw/core": "^1.15.0",
|
||||||
"@tldraw/intersect": "^1.7.1",
|
"@tldraw/intersect": "^1.7.1",
|
||||||
"@tldraw/vec": "^1.7.1",
|
"@tldraw/vec": "^1.7.1",
|
||||||
|
"browser-fs-access": "^0.31.0",
|
||||||
"idb-keyval": "^6.1.0",
|
"idb-keyval": "^6.1.0",
|
||||||
"lz-string": "^1.4.4",
|
"lz-string": "^1.4.4",
|
||||||
"perfect-freehand": "^1.1.0",
|
"perfect-freehand": "^1.1.0",
|
||||||
|
|
|
@ -222,7 +222,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
|
|
||||||
editingStartTime = -1
|
editingStartTime = -1
|
||||||
|
|
||||||
fileSystemHandle: FileSystemHandle | null = null
|
fileSystemHandle: FileSystemFileHandle | null = null
|
||||||
|
|
||||||
viewport = Utils.getBoundsFromPoints([
|
viewport = Utils.getBoundsFromPoints([
|
||||||
[0, 0],
|
[0, 0],
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
/**
|
|
||||||
* 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)
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
/**
|
|
||||||
* 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)
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
/**
|
|
||||||
* 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)
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
/**
|
|
||||||
* 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)
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
/**
|
|
||||||
* 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]
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
/**
|
|
||||||
* 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,225 +0,0 @@
|
||||||
/**
|
|
||||||
* Properties shared by all `options` provided to file save and open operations
|
|
||||||
*/
|
|
||||||
export interface CoreFileOptions {
|
|
||||||
/** Acceptable file extensions. Defaults to [""]. */
|
|
||||||
extensions?: string[]
|
|
||||||
/** Suggested file description. Defaults to "". */
|
|
||||||
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. */
|
|
||||||
multiple?: M
|
|
||||||
/**
|
|
||||||
* 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: M extends false | undefined ? FileWithHandle : FileWithHandle[]) => void,
|
|
||||||
rejectionHandler: () => void,
|
|
||||||
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.
|
|
||||||
* @returns Optional file handle to save in place.
|
|
||||||
*/
|
|
||||||
export function fileSave(
|
|
||||||
/** To-be-saved `Blob` or `Response` */
|
|
||||||
blobOrResponse: Blob | Response,
|
|
||||||
options?: [FirstFileSaveOptions, ...CoreFileOptions[]] | FirstFileSaveOptions,
|
|
||||||
/**
|
|
||||||
* A potentially existing file handle for a file to save to. Defaults to
|
|
||||||
* null.
|
|
||||||
*/
|
|
||||||
existingHandle?: FileSystemHandle | null,
|
|
||||||
/**
|
|
||||||
* Determines whether to throw (rather than open a new file save dialog)
|
|
||||||
* when existingHandle is no longer good. Defaults to false.
|
|
||||||
*/
|
|
||||||
throwIfExistingHandleNotGood?: boolean | false
|
|
||||||
): Promise<FileSystemHandle | null>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens a directory from disk using the File System Access API.
|
|
||||||
* @returns Contained files.
|
|
||||||
*/
|
|
||||||
export function directoryOpen(options?: {
|
|
||||||
/** Whether to recursively get subdirectories. */
|
|
||||||
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[]>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the File System Access API is supported.
|
|
||||||
*/
|
|
||||||
export const supported: boolean
|
|
||||||
|
|
||||||
export function imageToBlob(img: HTMLImageElement): Promise<Blob>
|
|
||||||
|
|
||||||
export interface FileWithHandle extends File {
|
|
||||||
handle?: FileSystemHandle
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FileWithDirectoryHandle extends File {
|
|
||||||
directoryHandle?: FileSystemHandle
|
|
||||||
}
|
|
||||||
|
|
||||||
// The following typings implement the relevant parts of the File System Access
|
|
||||||
// API. This can be removed once the specification reaches the Candidate phase
|
|
||||||
// and is implemented as part of microsoft/TSJS-lib-generator.
|
|
||||||
|
|
||||||
export interface FileSystemHandlePermissionDescriptor {
|
|
||||||
mode?: 'read' | 'readwrite'
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FileSystemHandle {
|
|
||||||
readonly kind: 'file' | 'directory'
|
|
||||||
readonly name: string
|
|
||||||
|
|
||||||
isSameEntry: (other: FileSystemHandle) => Promise<boolean>
|
|
||||||
|
|
||||||
queryPermission: (descriptor?: FileSystemHandlePermissionDescriptor) => Promise<PermissionState>
|
|
||||||
requestPermission: (descriptor?: FileSystemHandlePermissionDescriptor) => Promise<PermissionState>
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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'
|
|
|
@ -1,69 +0,0 @@
|
||||||
/**
|
|
||||||
* 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()
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
/**
|
|
||||||
* 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()
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
/**
|
|
||||||
* 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 })
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
/**
|
|
||||||
* 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
|
|
|
@ -1,14 +1,15 @@
|
||||||
|
import { fileOpen, fileSave } from 'browser-fs-access'
|
||||||
|
import type { FileSystemHandle } from 'browser-fs-access'
|
||||||
import { get as getFromIdb, set as setToIdb } from 'idb-keyval'
|
import { get as getFromIdb, set as setToIdb } from 'idb-keyval'
|
||||||
import { IMAGE_EXTENSIONS, VIDEO_EXTENSIONS } from '~constants'
|
import { IMAGE_EXTENSIONS, VIDEO_EXTENSIONS } from '~constants'
|
||||||
import type { TDDocument, TDFile } from '~types'
|
import type { TDDocument, TDFile } from '~types'
|
||||||
import type { FileSystemHandle } from './browser-fs-access'
|
|
||||||
|
|
||||||
const options = { mode: 'readwrite' as const }
|
const options = { mode: 'readwrite' as const }
|
||||||
|
|
||||||
const checkPermissions = async (handle: FileSystemHandle) => {
|
const checkPermissions = async (handle: FileSystemFileHandle) => {
|
||||||
return (
|
return (
|
||||||
(await handle.queryPermission(options)) === 'granted' ||
|
(await (handle as unknown as FileSystemHandle).queryPermission(options)) === 'granted' ||
|
||||||
(await handle.requestPermission(options)) === 'granted'
|
(await (handle as unknown as FileSystemHandle).requestPermission(options)) === 'granted'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,11 +20,14 @@ export async function loadFileHandle() {
|
||||||
return fileHandle
|
return fileHandle
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveFileHandle(fileHandle: FileSystemHandle | null) {
|
export async function saveFileHandle(fileHandle: FileSystemFileHandle | null) {
|
||||||
return setToIdb(`Tldraw_file_handle_${window.location.origin}`, fileHandle)
|
return setToIdb(`Tldraw_file_handle_${window.location.origin}`, fileHandle)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveToFileSystem(document: TDDocument, fileHandle: FileSystemHandle | null) {
|
export async function saveToFileSystem(
|
||||||
|
document: TDDocument,
|
||||||
|
fileHandle: FileSystemFileHandle | null
|
||||||
|
) {
|
||||||
// Create the saved file data
|
// Create the saved file data
|
||||||
const file: TDFile = {
|
const file: TDFile = {
|
||||||
name: document.name || 'New Document',
|
name: document.name || 'New Document',
|
||||||
|
@ -46,9 +50,6 @@ export async function saveToFileSystem(document: TDDocument, fileHandle: FileSys
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save to file system
|
// Save to file system
|
||||||
// @ts-ignore
|
|
||||||
const browserFS = await import('./browser-fs-access')
|
|
||||||
const fileSave = browserFS.fileSave
|
|
||||||
const newFileHandle = await fileSave(
|
const newFileHandle = await fileSave(
|
||||||
blob,
|
blob,
|
||||||
{
|
{
|
||||||
|
@ -66,13 +67,10 @@ export async function saveToFileSystem(document: TDDocument, fileHandle: FileSys
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openFromFileSystem(): Promise<null | {
|
export async function openFromFileSystem(): Promise<null | {
|
||||||
fileHandle: FileSystemHandle | null
|
fileHandle: FileSystemFileHandle | null
|
||||||
document: TDDocument
|
document: TDDocument
|
||||||
}> {
|
}> {
|
||||||
// Get the blob
|
// Get the blob
|
||||||
// @ts-ignore
|
|
||||||
const browserFS = await import('./browser-fs-access')
|
|
||||||
const fileOpen = browserFS.fileOpen
|
|
||||||
const blob = await fileOpen({
|
const blob = await fileOpen({
|
||||||
description: 'Tldraw File',
|
description: 'Tldraw File',
|
||||||
extensions: [`.tldr`],
|
extensions: [`.tldr`],
|
||||||
|
@ -106,9 +104,6 @@ export async function openFromFileSystem(): Promise<null | {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openAssetsFromFileSystem() {
|
export async function openAssetsFromFileSystem() {
|
||||||
// @ts-ignore
|
|
||||||
const browserFS = await import('./browser-fs-access')
|
|
||||||
const fileOpen = browserFS.fileOpen
|
|
||||||
return fileOpen({
|
return fileOpen({
|
||||||
description: 'Image or Video',
|
description: 'Image or Video',
|
||||||
extensions: [...IMAGE_EXTENSIONS, ...VIDEO_EXTENSIONS],
|
extensions: [...IMAGE_EXTENSIONS, ...VIDEO_EXTENSIONS],
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export * from './migrate'
|
export * from './migrate'
|
||||||
export * from './filesystem'
|
export * from './filesystem'
|
||||||
export * from './browser-fs-access'
|
export * from 'browser-fs-access'
|
||||||
|
|
|
@ -129,7 +129,7 @@ export type TldrawCommand = Command<TDSnapshot>
|
||||||
// The shape of the files stored in JSON
|
// The shape of the files stored in JSON
|
||||||
export interface TDFile {
|
export interface TDFile {
|
||||||
name: string
|
name: string
|
||||||
fileHandle: FileSystemHandle | null
|
fileHandle: FileSystemFileHandle | null
|
||||||
document: TDDocument
|
document: TDDocument
|
||||||
assets: Record<string, unknown>
|
assets: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
@ -567,11 +567,11 @@ export interface Command<T extends { [key: string]: any }> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileWithHandle extends File {
|
export interface FileWithHandle extends File {
|
||||||
handle?: FileSystemHandle
|
handle?: FileSystemFileHandle
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileWithDirectoryHandle extends File {
|
export interface FileWithDirectoryHandle extends File {
|
||||||
directoryHandle?: FileSystemHandle
|
directoryHandle?: FileSystemDirectoryHandle
|
||||||
}
|
}
|
||||||
|
|
||||||
// The following typings implement the relevant parts of the File System Access
|
// The following typings implement the relevant parts of the File System Access
|
||||||
|
|
|
@ -3784,6 +3784,11 @@ breakword@^1.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
wcwidth "^1.0.1"
|
wcwidth "^1.0.1"
|
||||||
|
|
||||||
|
browser-fs-access@^0.31.0:
|
||||||
|
version "0.31.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/browser-fs-access/-/browser-fs-access-0.31.0.tgz#b2ac17322b81f3c89eaddcfc871b5fdbac174734"
|
||||||
|
integrity sha512-OndIM5LkmHfx01D3YuVvPpW3fochBeXFS3yhDzcD/Yk7S3lx5RZGltnf7xwbr5V0B8XuN2becZmtceOQFZgQPw==
|
||||||
|
|
||||||
browser-process-hrtime@^1.0.0:
|
browser-process-hrtime@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
|
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
|
||||||
|
|
Loading…
Reference in a new issue