Skip to content

#129 Draft - support for new fileHierarchy() function which adds a ha… #132

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
.DS_Store
node_modules/
dist/

.idea
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ const options = {
startIn: 'downloads',
// By specifying an ID, the user agent can remember different directories for different IDs.
id: 'projects',
// Set to 'read' or 'readwrite'; the browser will prompt the user accordingly.
mode: 'read',
// Callback to determine whether a directory should be entered, return `true` to skip.
skipDirectory: (entry) => entry.name[0] === '.',
};
Expand All @@ -176,6 +178,32 @@ const blobs = await directoryOpen(options);

The module also polyfills a [`webkitRelativePath`](https://developer.mozilla.org/en-US/docs/Web/API/File/webkitRelativePath) property on returned files in a consistent way, regardless of the underlying implementation.

### Opening A Directory Hierarchy

A variant of directoryOpen which is not backwards/legacy compatible, but includes
a handle referencing the directory being opened, as well as its contents.

```js
// Options are optional.
const options = {
// Set to `true` to recursively open files in all subdirectories,
// defaults to `false`.
recursive: true,
// Suggested directory in which the file picker opens. A well-known directory, or a file or directory handle.
startIn: 'downloads',
// By specifying an ID, the user agent can remember different directories for different IDs.
id: 'projects',
// Set to 'read' or 'readwrite'; the browser will prompt the user accordingly.
mode: 'read',
// Callback to determine whether a directory should be entered, return `true` to skip.
skipDirectory: (entry) => entry.name[0] === '.',
};

const dirWithContents = fileHierarchy(options);
const dir = dirWithContents.currentDir;
const contents = await dirWithContents.contents;
```

### Saving files:

```js
Expand Down
58 changes: 58 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,64 @@ export function directoryOpen(options?: {
) => (reject?: (reason?: any) => void) => void;
}): Promise<FileWithDirectoryAndFileHandle[]>;

/**
* Opens a directory from disk using the File System Access API. Includes a reference to the directory being listed
* (similar to the "." entry in UNIX file systems.) Not supported in legacy fallback mode.
* @returns Contained files.
*/
export function fileHierarchy(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;
/** By specifying a mode of `'readwrite'`, you can open a directory with write access. */
mode?: FileSystemPermissionMode;
/** Callback to determine whether a directory should be entered, return `true` to skip. */
skipDirectory?: (
entry: FileSystemDirectoryEntry | FileSystemDirectoryHandle
) => 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 fileHierarchy({
* 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: FileWithDirectoryAndFileHandle) => void,
rejectionHandler: () => void,
input: HTMLInputElement
) => (reject?: (reason?: any) => void) => void;
}): DirectoryHandleWithContents;

export interface DirectoryHandleWithContents {
currentDir: FileSystemDirectoryHandle;
contents: Promise<FileWithDirectoryAndFileHandle[]>;
}

/**
* Whether the File System Access API is supported.
*/
Expand Down
30 changes: 30 additions & 0 deletions src/file-hierarchy.mjs
Original file line number Diff line number Diff line change
@@ -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.

// there is no legacy fallback possible for this since it changes the return type
const implementation = import('./fs-access/file-hierarchy.mjs');

/**
* Variant of (and largely delegates to) directory-open. Opens a directory from disk using the File System Access API.
* Includes a reference to the directory which was opened, and therefore is not backward compatible with
* `<input type webkitdirectory>`.
*
* @type { typeof import("../index").fileHierarchy }
*/
export async function fileHierarchy(...args) {
return (await implementation).default(...args);
}
40 changes: 40 additions & 0 deletions src/fs-access/file-hierarchy.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* 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 getFiles from './directory-open.mjs';

/**
* Variant of (and largely delegates to) directory-open. Opens a directory from disk using the File System Access API.
* Includes a reference to the directory which was opened, and therefore is not backward compatible with
* `<input type webkitdirectory>`.
*
* @type { typeof import("../index").fileHierarchy }
*/
export default async (options = {}) => {
options.recursive = options.recursive || false;
options.mode = options.mode || 'read';
const handle = await window.showDirectoryPicker({
id: options.id,
startIn: options.startIn,
mode: options.mode,
});
const dirResults = getFiles(options);
return {
currentDir: handle,
contents: dirResults,
};
};
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
export { fileOpen } from './file-open.mjs';
export { directoryOpen } from './directory-open.mjs';
export { fileSave } from './file-save.mjs';
export { fileHierarchy } from './file-hierarchy.mjs';

export { default as fileOpenModern } from './fs-access/file-open.mjs';
export { default as directoryOpenModern } from './fs-access/directory-open.mjs';
Expand Down