diff --git a/examples/core-example-advanced/index.html b/examples/core-example-advanced/index.html
index 98aa82b74..9ddc4fa8a 100644
--- a/examples/core-example-advanced/index.html
+++ b/examples/core-example-advanced/index.html
@@ -2,7 +2,7 @@
-
+
tldraw - core example advanced
diff --git a/examples/core-example-advanced/public/favicon.ico b/examples/core-example-advanced/public/favicon.ico
new file mode 100644
index 000000000..ebed98e37
Binary files /dev/null and b/examples/core-example-advanced/public/favicon.ico differ
diff --git a/examples/core-example-advanced/src/index.html b/examples/core-example-advanced/src/index.html
deleted file mode 100644
index 362929e63..000000000
--- a/examples/core-example-advanced/src/index.html
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
- tldraw
-
-
-
-
-
-
-
diff --git a/examples/core-example/index.html b/examples/core-example/index.html
index 3cc9cf6fb..22786ead5 100644
--- a/examples/core-example/index.html
+++ b/examples/core-example/index.html
@@ -2,7 +2,7 @@
-
+
tldraw - core example
diff --git a/examples/core-example/public/favicon.ico b/examples/core-example/public/favicon.ico
new file mode 100644
index 000000000..ebed98e37
Binary files /dev/null and b/examples/core-example/public/favicon.ico differ
diff --git a/examples/core-example/src/index.html b/examples/core-example/src/index.html
deleted file mode 100644
index 362929e63..000000000
--- a/examples/core-example/src/index.html
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
- tldraw
-
-
-
-
-
-
-
diff --git a/examples/tldraw-example/index.html b/examples/tldraw-example/index.html
index 77350ef03..8100c26f9 100644
--- a/examples/tldraw-example/index.html
+++ b/examples/tldraw-example/index.html
@@ -2,7 +2,7 @@
-
+
tldraw - example
diff --git a/examples/tldraw-example/public/favicon.ico b/examples/tldraw-example/public/favicon.ico
new file mode 100644
index 000000000..ebed98e37
Binary files /dev/null and b/examples/tldraw-example/public/favicon.ico differ
diff --git a/packages/tldraw/package.json b/packages/tldraw/package.json
index 4e808979d..e8f6e58df 100644
--- a/packages/tldraw/package.json
+++ b/packages/tldraw/package.json
@@ -47,6 +47,7 @@
"@tldraw/core": "^1.15.0",
"@tldraw/intersect": "^1.7.1",
"@tldraw/vec": "^1.7.1",
+ "browser-fs-access": "^0.31.0",
"idb-keyval": "^6.1.0",
"lz-string": "^1.4.4",
"perfect-freehand": "^1.1.0",
diff --git a/packages/tldraw/src/state/TldrawApp.ts b/packages/tldraw/src/state/TldrawApp.ts
index d04aa7fc5..769f4bedc 100644
--- a/packages/tldraw/src/state/TldrawApp.ts
+++ b/packages/tldraw/src/state/TldrawApp.ts
@@ -222,7 +222,7 @@ export class TldrawApp extends StateManager {
editingStartTime = -1
- fileSystemHandle: FileSystemHandle | null = null
+ fileSystemHandle: FileSystemFileHandle | null = null
viewport = Utils.getBoundsFromPoints([
[0, 0],
diff --git a/packages/tldraw/src/state/data/browser-fs-access/directory-open.js b/packages/tldraw/src/state/data/browser-fs-access/directory-open.js
deleted file mode 100644
index 7114ef396..000000000
--- a/packages/tldraw/src/state/data/browser-fs-access/directory-open.js
+++ /dev/null
@@ -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)
-}
diff --git a/packages/tldraw/src/state/data/browser-fs-access/file-open.js b/packages/tldraw/src/state/data/browser-fs-access/file-open.js
deleted file mode 100644
index 81bc74450..000000000
--- a/packages/tldraw/src/state/data/browser-fs-access/file-open.js
+++ /dev/null
@@ -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)
-}
diff --git a/packages/tldraw/src/state/data/browser-fs-access/file-save.js b/packages/tldraw/src/state/data/browser-fs-access/file-save.js
deleted file mode 100644
index 5a69443ba..000000000
--- a/packages/tldraw/src/state/data/browser-fs-access/file-save.js
+++ /dev/null
@@ -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)
-}
diff --git a/packages/tldraw/src/state/data/browser-fs-access/fs-access/directory-open.js b/packages/tldraw/src/state/data/browser-fs-access/fs-access/directory-open.js
deleted file mode 100644
index 9fa2505ac..000000000
--- a/packages/tldraw/src/state/data/browser-fs-access/fs-access/directory-open.js
+++ /dev/null
@@ -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)
-}
diff --git a/packages/tldraw/src/state/data/browser-fs-access/fs-access/file-open.js b/packages/tldraw/src/state/data/browser-fs-access/fs-access/file-open.js
deleted file mode 100644
index 45d0a1328..000000000
--- a/packages/tldraw/src/state/data/browser-fs-access/fs-access/file-open.js
+++ /dev/null
@@ -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]
-}
diff --git a/packages/tldraw/src/state/data/browser-fs-access/fs-access/file-save.js b/packages/tldraw/src/state/data/browser-fs-access/fs-access/file-save.js
deleted file mode 100644
index 68fce57d6..000000000
--- a/packages/tldraw/src/state/data/browser-fs-access/fs-access/file-save.js
+++ /dev/null
@@ -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
-}
diff --git a/packages/tldraw/src/state/data/browser-fs-access/index.d.ts b/packages/tldraw/src/state/data/browser-fs-access/index.d.ts
deleted file mode 100644
index 838ce7856..000000000
--- a/packages/tldraw/src/state/data/browser-fs-access/index.d.ts
+++ /dev/null
@@ -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 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(
- options?: [FirstFileOpenOptions, ...CoreFileOptions[]] | FirstFileOpenOptions
-): M extends false | undefined ? Promise : Promise
-
-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
-
-/**
- * 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
-
-/**
- * Whether the File System Access API is supported.
- */
-export const supported: boolean
-
-export function imageToBlob(img: HTMLImageElement): Promise
-
-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
-
- queryPermission: (descriptor?: FileSystemHandlePermissionDescriptor) => Promise
- requestPermission: (descriptor?: FileSystemHandlePermissionDescriptor) => Promise
-}
diff --git a/packages/tldraw/src/state/data/browser-fs-access/index.js b/packages/tldraw/src/state/data/browser-fs-access/index.js
deleted file mode 100644
index d7a94ab34..000000000
--- a/packages/tldraw/src/state/data/browser-fs-access/index.js
+++ /dev/null
@@ -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'
diff --git a/packages/tldraw/src/state/data/browser-fs-access/legacy/directory-open.js b/packages/tldraw/src/state/data/browser-fs-access/legacy/directory-open.js
deleted file mode 100644
index 9fc001e1a..000000000
--- a/packages/tldraw/src/state/data/browser-fs-access/legacy/directory-open.js
+++ /dev/null
@@ -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
- * `` 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()
- })
-}
diff --git a/packages/tldraw/src/state/data/browser-fs-access/legacy/file-open.js b/packages/tldraw/src/state/data/browser-fs-access/legacy/file-open.js
deleted file mode 100644
index 4f8be2586..000000000
--- a/packages/tldraw/src/state/data/browser-fs-access/legacy/file-open.js
+++ /dev/null
@@ -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 `` 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()
- })
-}
diff --git a/packages/tldraw/src/state/data/browser-fs-access/legacy/file-save.js b/packages/tldraw/src/state/data/browser-fs-access/legacy/file-save.js
deleted file mode 100644
index f7f49a969..000000000
--- a/packages/tldraw/src/state/data/browser-fs-access/legacy/file-save.js
+++ /dev/null
@@ -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 `` 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}
- */
-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 }
- */
- 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 })
-}
diff --git a/packages/tldraw/src/state/data/browser-fs-access/supported.js b/packages/tldraw/src/state/data/browser-fs-access/supported.js
deleted file mode 100644
index e50802bb3..000000000
--- a/packages/tldraw/src/state/data/browser-fs-access/supported.js
+++ /dev/null
@@ -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
diff --git a/packages/tldraw/src/state/data/filesystem.ts b/packages/tldraw/src/state/data/filesystem.ts
index 0aa54d9da..e36c14eee 100644
--- a/packages/tldraw/src/state/data/filesystem.ts
+++ b/packages/tldraw/src/state/data/filesystem.ts
@@ -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 { IMAGE_EXTENSIONS, VIDEO_EXTENSIONS } from '~constants'
import type { TDDocument, TDFile } from '~types'
-import type { FileSystemHandle } from './browser-fs-access'
const options = { mode: 'readwrite' as const }
-const checkPermissions = async (handle: FileSystemHandle) => {
+const checkPermissions = async (handle: FileSystemFileHandle) => {
return (
- (await handle.queryPermission(options)) === 'granted' ||
- (await handle.requestPermission(options)) === 'granted'
+ (await (handle as unknown as FileSystemHandle).queryPermission(options)) === 'granted' ||
+ (await (handle as unknown as FileSystemHandle).requestPermission(options)) === 'granted'
)
}
@@ -19,11 +20,14 @@ export async function loadFileHandle() {
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)
}
-export async function saveToFileSystem(document: TDDocument, fileHandle: FileSystemHandle | null) {
+export async function saveToFileSystem(
+ document: TDDocument,
+ fileHandle: FileSystemFileHandle | null
+) {
// Create the saved file data
const file: TDFile = {
name: document.name || 'New Document',
@@ -46,9 +50,6 @@ export async function saveToFileSystem(document: TDDocument, fileHandle: FileSys
}
// Save to file system
- // @ts-ignore
- const browserFS = await import('./browser-fs-access')
- const fileSave = browserFS.fileSave
const newFileHandle = await fileSave(
blob,
{
@@ -66,13 +67,10 @@ export async function saveToFileSystem(document: TDDocument, fileHandle: FileSys
}
export async function openFromFileSystem(): Promise {
// Get the blob
- // @ts-ignore
- const browserFS = await import('./browser-fs-access')
- const fileOpen = browserFS.fileOpen
const blob = await fileOpen({
description: 'Tldraw File',
extensions: [`.tldr`],
@@ -106,9 +104,6 @@ export async function openFromFileSystem(): Promise
// The shape of the files stored in JSON
export interface TDFile {
name: string
- fileHandle: FileSystemHandle | null
+ fileHandle: FileSystemFileHandle | null
document: TDDocument
assets: Record
}
@@ -567,11 +567,11 @@ export interface Command {
}
export interface FileWithHandle extends File {
- handle?: FileSystemHandle
+ handle?: FileSystemFileHandle
}
export interface FileWithDirectoryHandle extends File {
- directoryHandle?: FileSystemHandle
+ directoryHandle?: FileSystemDirectoryHandle
}
// The following typings implement the relevant parts of the File System Access
diff --git a/yarn.lock b/yarn.lock
index f0cd9cd63..e48474770 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3784,6 +3784,11 @@ breakword@^1.0.5:
dependencies:
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:
version "1.0.0"
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"