@ayonli/jsext
Version:
A JavaScript extension package for building strong and modern applications.
388 lines (371 loc) • 11.2 kB
text/typescript
import { isBrowserWindow, isDeno, isNodeLike } from "../env.ts";
import { throwUnsupportedRuntimeError } from "../error.ts";
/**
* Options for file dialog functions, such as {@link pickFile} and
* {@link openFile}.
*/
export interface FileDialogOptions {
/**
* Customize the dialog's title. This option is ignored in the browser.
*/
title?: string | undefined;
/**
* Filter files by providing a MIME type or suffix, multiple types can be
* separated via `,`.
*/
type?: string | undefined;
}
/**
* Options for the {@link pickFile} function.
*/
export interface PickFileOptions extends FileDialogOptions {
/** Open the dialog in save mode. */
forSave?: boolean;
/** The default name of the file to save when `forSave` is set. */
defaultName?: string | undefined;
}
/**
* Opens the file picker dialog and pick a file, this function returns the
* file's path or a `FileSystemFileHandle` in the browser.
*
* NOTE: Browser support is limited to the chromium-based browsers.
*
* @example
* ```ts
* // default usage
* import { pickFile } from "@ayonli/jsext/dialog";
*
* // Node.js, Deno, Bun
* const filename = await pickFile() as string | null;
*
* // Browser (Chrome)
* const handle = await pickFile() as FileSystemFileHandle | null;
* ```
*
* @example
* ```ts
* // filter by MIME type
* import { pickFile } from "@ayonli/jsext/dialog";
*
* // Node.js, Deno, Bun
* const filename = await pickFile({ type: "image/*" }) as string | null;
*
* // Browser (Chrome)
* const handle = await pickFile({ type: "image/*" }) as FileSystemFileHandle | null;
* ```
*
* @example
* ```ts
* // pick for save
* import { pickFile } from "@ayonli/jsext/dialog";
*
* // Node.js, Deno, Bun
* const filename = await pickFile({
* forSave: true,
* defaultName: "hello.txt",
* }) as string | null;
*
* // Browser (Chrome)
* const handle = await pickFile({
* forSave: true,
* defaultName: "hello.txt",
* }) as FileSystemFileHandle | null;
* ```
*/
export async function pickFile(
options: PickFileOptions = {}
): Promise<string | FileSystemFileHandle | null> {
if (typeof (globalThis as any)["showOpenFilePicker"] === "function") {
const { pickFile } = await import("./web/file.ts");
return await pickFile(options);
} else if (isDeno || isNodeLike) {
const { pickFile } = await import("./cli/file.ts");
return await pickFile(options);
}
throwUnsupportedRuntimeError();
}
/**
* Opens the file picker dialog and pick multiple files, this function returns
* the paths or `FileSystemFileHandle` objects in the browser of the files
* selected.
*
* NOTE: Browser support is limited to the chromium-based browsers.
*
* @example
* ```ts
* // default usage
* import { pickFiles } from "@ayonli/jsext/dialog";
*
* // Node.js, Deno, Bun
* const filenames = await pickFiles() as string[];
*
* // Browser (Chrome)
* const handles = await pickFiles() as FileSystemFileHandle[];
* ```
*
* @example
* ```ts
* // filter by MIME type
* import { pickFiles } from "@ayonli/jsext/dialog";
*
* // Node.js, Deno, Bun
* const filenames = await pickFiles({ type: "image/*" }) as string[];
*
* // Browser (Chrome)
* const handles = await pickFiles({ type: "image/*" }) as FileSystemFileHandle[];
* ```
*/
export async function pickFiles(
options: FileDialogOptions = {}
): Promise<string[] | FileSystemFileHandle[]> {
if (typeof (globalThis as any)["showOpenFilePicker"] === "function") {
const { pickFiles } = await import("./web/file.ts");
return await pickFiles(options);
} else if (isDeno || isNodeLike) {
const { pickFiles } = await import("./cli/file.ts");
return await pickFiles(options);
}
throwUnsupportedRuntimeError();
}
/**
* Opens the file picker dialog and pick a directory, this function returns the
* directory's path or `FileSystemDirectoryHandle` in the browser.
*
* NOTE: Browser support is limited to the chromium-based browsers.
*
* @example
* ```ts
* import { pickDirectory } from "@ayonli/jsext/dialog";
*
* // Node.js, Deno, Bun
* const dirname = await pickDirectory() as string | null;
*
* // Browser (Chrome)
* const handle = await pickDirectory() as FileSystemDirectoryHandle | null;
* ```
*/
export async function pickDirectory(
options: Pick<FileDialogOptions, "title"> = {}
): Promise<string | FileSystemDirectoryHandle | null> {
if (typeof (globalThis as any)["showDirectoryPicker"] === "function") {
const { pickDirectory } = await import("./web/file.ts");
return await pickDirectory();
} else if (isDeno || isNodeLike) {
const { pickDirectory } = await import("./cli/file.ts");
return await pickDirectory(options);
}
throwUnsupportedRuntimeError();
}
/**
* Opens the file picker dialog and selects a file to open.
*
* @example
* ```ts
* // default usage
* import { openFile } from "@ayonli/jsext/dialog";
*
* const file = await openFile();
*
* if (file) {
* console.log(`You selected: ${file.name}`);
* }
* ```
*
* @example
* ```ts
* // filter by MIME type
* import { openFile } from "@ayonli/jsext/dialog";
*
* const file = await openFile({ type: "image/*" });
*
* if (file) {
* console.log(`You selected: ${file.name}`);
* console.assert(file.type.startsWith("image/"));
* }
* ```
*/
export async function openFile(options?: FileDialogOptions): Promise<File | null> {
if (isBrowserWindow) {
const { openFile } = await import("./web/file.ts");
return await openFile(options);
} else if (isDeno || isNodeLike) {
const { openFile } = await import("./cli/file.ts");
return await openFile(options);
} else {
throwUnsupportedRuntimeError();
}
}
/**
* Opens the file picker dialog and selects multiple files to open.
*
* @example
* ```ts
* // default usage
* import { openFiles } from "@ayonli/jsext/dialog";
*
* const files = await openFiles();
*
* if (files.length > 0) {
* console.log(`You selected: ${files.map(file => file.name).join(", ")}`);
* }
* ```
*
* @example
* ```ts
* // filter by MIME type
* import { openFiles } from "@ayonli/jsext/dialog";
*
* const files = await openFiles({ type: "image/*" });
*
* if (files.length > 0) {
* console.log(`You selected: ${files.map(file => file.name).join(", ")}`);
* console.assert(files.every(file => file.type.startsWith("image/")));
* }
* ```
*/
export async function openFiles(options: FileDialogOptions = {}): Promise<File[]> {
if (isBrowserWindow) {
const { openFiles } = await import("./web/file.ts");
return await openFiles(options);
} else if (isDeno || isNodeLike) {
const { openFiles } = await import("./cli/file.ts");
return await openFiles(options);
} else {
throwUnsupportedRuntimeError();
}
}
/**
* Opens the directory picker dialog and selects all its files to open.
*
* @example
* ```ts
* import { openDirectory } from "@ayonli/jsext/dialog";
*
* const files = await openDirectory();
*
* for (const file of files) {
* console.log(`File name: ${file.name}, path: ${file.webkitRelativePath}`);
* }
* ```
*/
export async function openDirectory(
options: Pick<FileDialogOptions, "title"> = {}
): Promise<File[]> {
if (isBrowserWindow) {
const { openDirectory } = await import("./web/file.ts");
return await openDirectory();
} else if (isDeno || isNodeLike) {
const { openDirectory } = await import("./cli/file.ts");
return await openDirectory(options);
} else {
throwUnsupportedRuntimeError();
}
}
/**
* Options for the {@link saveFile} function.
*/
export interface SaveFileOptions {
/**
* Customize the dialog's title. This option is ignored in the browser.
*/
title?: string;
/** The suggested name of the file. */
name?: string;
/** The MIME type of the file. */
type?: string;
signal?: AbortSignal;
}
/**
* Saves a file to the file system.
*
* In the CLI, this function will open a dialog to let the user choose the
* location where the file will be saved. In the browser, the file will be saved
* to the default download location, or the browser will prompt the user to
* choose a location.
*
* @example
* ```ts
* import { saveFile } from "@ayonli/jsext/dialog";
*
* const file = new File(["Hello, World!"], "hello.txt", { type: "text/plain" });
*
* await saveFile(file);
* ```
*/
export async function saveFile(file: File, options?: Pick<SaveFileOptions, "title">): Promise<void>;
/**
* @example
* ```ts
* import { saveFile } from "@ayonli/jsext/dialog";
* import bytes from "@ayonli/jsext/bytes";
*
* const data = bytes("Hello, World!");
*
* await saveFile(data, { name: "hello.txt", type: "text/plain" });
* ```
*/
export async function saveFile(
file: Blob | ArrayBuffer | ArrayBufferView | ReadableStream<Uint8Array>,
options?: SaveFileOptions
): Promise<void>;
export async function saveFile(
file: File | Blob | ArrayBuffer | ArrayBufferView | ReadableStream<Uint8Array>,
options: SaveFileOptions = {}
): Promise<void> {
if (isBrowserWindow) {
const { saveFile } = await import("./web/file.ts");
return await saveFile(file, options);
} else if (isDeno || isNodeLike) {
const { saveFile } = await import("./cli/file.ts");
return await saveFile(file, options);
} else {
throwUnsupportedRuntimeError();
}
}
/**
* Options for the {@link downloadFile} function.
*/
export interface DownloadFileOptions extends SaveFileOptions {
/**
* A callback function that will be called when the download progress
* changes.
*/
onProgress?: (event: ProgressEvent) => void;
/**
* Displays a progress bar during the download process. This option shadows
* the `signal` option if provided, as the progress bar has its own
* cancellation mechanism.
*/
showProgress?: boolean;
}
/**
* Downloads the file of the given URL to the file system.
*
* In the CLI, this function will open a dialog to let the user choose the
* location where the file will be saved. In the browser, the file will be saved
* to the default download location, or the browser will prompt the user to
* choose a location.
*
* NOTE: This function depends on the Fetch API and Web Streams API, in Node.js,
* it requires Node.js v18.0 or above.
*
* @example
* ```ts
* import { downloadFile } from "@ayonli/jsext/dialog";
*
* await downloadFile("https://ayonli.github.io/jsext/README.md");
* ```
*/
export async function downloadFile(
url: string | URL,
options: DownloadFileOptions = {}
): Promise<void> {
if (isBrowserWindow) {
const { downloadFile } = await import("./web/file.ts");
return downloadFile(url, options);
} else if (!isDeno && !isNodeLike || typeof fetch !== "function") {
throwUnsupportedRuntimeError();
}
const { downloadFile } = await import("./cli/file.ts");
return downloadFile(url, options);
}