@ayonli/jsext
Version:
A JavaScript extension package for building strong and modern applications.
554 lines (550 loc) • 19.9 kB
JavaScript
;
var env = require('../env.js');
var runtime = require('../runtime.js');
var reader = require('../reader.js');
var async = require('../async.js');
var filetype = require('../filetype.js');
var fs = require('../fs.js');
var fs_util = require('../fs/util.js');
var object = require('../object.js');
var path = require('../path.js');
var event = require('../event.js');
var dialog_progress = require('./progress.js');
/**
* 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;
* ```
*/
async function pickFile(options = {}) {
if (typeof globalThis["showOpenFilePicker"] === "function") {
const { browserPickFile } = await Promise.resolve().then(function () { return require('./browser/file.js'); });
return await browserPickFile(options.type, {
forSave: options.forSave,
defaultName: options.defaultName,
});
}
else if (env.isDeno || env.isNodeLike) {
const { isWSL, which } = await Promise.resolve().then(function () { return require('../cli.js'); });
const _platform = runtime.platform();
if (_platform === "darwin") {
const { macPickFile } = await Promise.resolve().then(function () { return require('./terminal/file/mac.js'); });
return await macPickFile(options.title, {
type: options.type,
forSave: options === null || options === void 0 ? void 0 : options.forSave,
defaultName: options === null || options === void 0 ? void 0 : options.defaultName,
});
}
else if (_platform === "windows" || isWSL()) {
const { windowsPickFile } = await Promise.resolve().then(function () { return require('./terminal/file/windows.js'); });
return await windowsPickFile(options.title, {
type: options.type,
forSave: options === null || options === void 0 ? void 0 : options.forSave,
defaultName: options === null || options === void 0 ? void 0 : options.defaultName,
});
}
else if (_platform === "linux" || await which("zenity")) {
const { linuxPickFile } = await Promise.resolve().then(function () { return require('./terminal/file/linux.js'); });
return await linuxPickFile(options.title, {
type: options.type,
forSave: options === null || options === void 0 ? void 0 : options.forSave,
defaultName: options === null || options === void 0 ? void 0 : options.defaultName,
});
}
}
throw new Error("Unsupported platform");
}
/**
* 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[];
* ```
*/
async function pickFiles(options = {}) {
if (typeof globalThis["showOpenFilePicker"] === "function") {
const { browserPickFiles } = await Promise.resolve().then(function () { return require('./browser/file.js'); });
return await browserPickFiles(options.type);
}
else if (env.isDeno || env.isNodeLike) {
const { isWSL, which } = await Promise.resolve().then(function () { return require('../cli.js'); });
const _platform = runtime.platform();
if (_platform === "darwin") {
const { macPickFiles } = await Promise.resolve().then(function () { return require('./terminal/file/mac.js'); });
return await macPickFiles(options.title, options.type);
}
else if (_platform === "windows" || isWSL()) {
const { windowsPickFiles } = await Promise.resolve().then(function () { return require('./terminal/file/windows.js'); });
return await windowsPickFiles(options.title, options.type);
}
else if (_platform === "linux" || await which("zenity")) {
const { linuxPickFiles } = await Promise.resolve().then(function () { return require('./terminal/file/linux.js'); });
return await linuxPickFiles(options.title, options.type);
}
}
throw new Error("Unsupported platform");
}
/**
* 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;
* ```
*/
async function pickDirectory(options = {}) {
if (typeof globalThis["showDirectoryPicker"] === "function") {
const { browserPickFolder } = await Promise.resolve().then(function () { return require('./browser/file.js'); });
return await browserPickFolder();
}
else if (env.isDeno || env.isNodeLike) {
const { isWSL, which } = await Promise.resolve().then(function () { return require('../cli.js'); });
const _platform = runtime.platform();
if (_platform === "darwin") {
const { macPickFolder } = await Promise.resolve().then(function () { return require('./terminal/file/mac.js'); });
return await macPickFolder(options.title);
}
else if (_platform === "windows" || isWSL()) {
const { windowsPickFolder } = await Promise.resolve().then(function () { return require('./terminal/file/windows.js'); });
return await windowsPickFolder(options.title);
}
else if (_platform === "linux" || await which("zenity")) {
const { linuxPickFolder } = await Promise.resolve().then(function () { return require('./terminal/file/linux.js'); });
return await linuxPickFolder(options.title);
}
}
throw new Error("Unsupported platform");
}
async function openFile(options = {}) {
const { title = "", type = "", multiple = false, directory = false } = options;
if (directory) {
return await openDirectory({ title });
}
else if (multiple) {
return await openFiles({ title, type });
}
if (typeof globalThis["showOpenFilePicker"] === "function") {
const { browserPickFile } = await Promise.resolve().then(function () { return require('./browser/file.js'); });
const handle = await browserPickFile(type);
return handle ? await handle.getFile().then(fs_util.fixFileType) : null;
}
else if (env.isBrowserWindow) {
const input = document.createElement("input");
input.type = "file";
input.accept = type !== null && type !== void 0 ? type : "";
return await new Promise(resolve => {
input.onchange = () => {
var _a;
const file = (_a = input.files) === null || _a === void 0 ? void 0 : _a[0];
resolve(file ? fs_util.fixFileType(file) : null);
};
input.oncancel = () => {
resolve(null);
};
if (typeof input.showPicker === "function") {
input.showPicker();
}
else {
input.click();
}
});
}
else if (env.isDeno || env.isNodeLike) {
let filename = await pickFile({ title, type });
if (filename) {
return await fs.readFileAsFile(filename);
}
else {
return null;
}
}
else {
throw new Error("Unsupported runtime");
}
}
/**
* 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/")));
* }
* ```
*/
async function openFiles(options = {}) {
if (typeof globalThis["showOpenFilePicker"] === "function") {
const { browserPickFiles } = await Promise.resolve().then(function () { return require('./browser/file.js'); });
const handles = await browserPickFiles(options.type);
const files = [];
for (const handle of handles) {
const file = await handle.getFile();
files.push(fs_util.fixFileType(file));
}
return files;
}
else if (env.isBrowserWindow) {
const input = document.createElement("input");
input.type = "file";
input.multiple = true;
input.accept = options.type || "";
return await new Promise(resolve => {
input.onchange = () => {
const files = input.files;
resolve(files ? [...files].map(fs_util.fixFileType) : []);
};
input.oncancel = () => {
resolve([]);
};
if (typeof input.showPicker === "function") {
input.showPicker();
}
else {
input.click();
}
});
}
else if (env.isDeno || env.isNodeLike) {
const filenames = await pickFiles(options);
return await Promise.all(filenames.map(path => fs.readFileAsFile(path)));
}
else {
throw new Error("Unsupported runtime");
}
}
/**
* 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}`);
* }
* ```
*/
async function openDirectory(options = {}) {
if (typeof globalThis["showDirectoryPicker"] === "function") {
const { browserPickFolder } = await Promise.resolve().then(function () { return require('./browser/file.js'); });
const dir = await browserPickFolder();
const files = [];
if (!dir) {
return files;
}
for await (const entry of fs.readDir(dir, { recursive: true })) {
if (entry.kind === "file") {
const file = await entry.handle.getFile();
Object.defineProperty(file, "webkitRelativePath", {
configurable: true,
enumerable: true,
writable: false,
value: entry.relativePath.replace(/\\/g, "/"),
});
files.push(fs_util.fixFileType(file));
}
}
return files;
}
else if (env.isBrowserWindow) {
const input = document.createElement("input");
input.type = "file";
input.webkitdirectory = true;
return await new Promise(resolve => {
input.onchange = () => {
const files = input.files;
resolve(files ? [...files].map(fs_util.fixFileType) : []);
};
input.oncancel = () => {
resolve([]);
};
if (typeof input.showPicker === "function") {
input.showPicker();
}
else {
input.click();
}
});
}
else if (env.isDeno || env.isNodeLike) {
const dirname = await pickDirectory(options);
if (dirname) {
const files = [];
for await (const entry of fs.readDir(dirname, { recursive: true })) {
if (entry.kind === "file") {
const path$1 = path.join(dirname, entry.relativePath);
const file = await fs.readFileAsFile(path$1);
Object.defineProperty(file, "webkitRelativePath", {
configurable: true,
enumerable: true,
writable: false,
value: entry.relativePath.replace(/\\/g, "/"),
});
files.push(fs_util.fixFileType(file));
}
}
return files;
}
else {
return [];
}
}
else {
throw new Error("Unsupported runtime");
}
}
async function saveFile(file, options = {}) {
var _a;
if (env.isBrowserWindow) {
const a = document.createElement("a");
if (file instanceof ReadableStream) {
const type = options.type || "application/octet-stream";
a.href = await reader.readAsObjectURL(file, type);
a.download = options.name || "Unnamed" + (filetype.getExtensions(type)[0] || "");
}
else if (file instanceof File) {
a.href = URL.createObjectURL(file);
a.download = options.name || file.name || "Unnamed" + (filetype.getExtensions(file.type)[0] || "");
}
else if (file instanceof Blob) {
a.href = URL.createObjectURL(file);
a.download = options.name || "Unnamed" + (filetype.getExtensions(file.type)[0] || "");
}
else {
const type = options.type || "application/octet-stream";
const blob = new Blob([file], { type });
a.href = URL.createObjectURL(blob);
a.download = options.name || "Unnamed" + (filetype.getExtensions(type)[0] || "");
}
a.click();
}
else if (env.isDeno || env.isNodeLike) {
const { title } = options;
let filename;
if (typeof Blob === "function" && file instanceof Blob) {
filename = await pickFile({
title,
type: options.type || file.type,
forSave: true,
defaultName: options.name || ((_a = object.as(file, File)) === null || _a === void 0 ? void 0 : _a.name),
});
}
else {
filename = await pickFile({
title,
type: options.type,
forSave: true,
defaultName: options.name,
});
}
if (filename) {
await fs.writeFile(filename, file, object.pick(options, ["signal"]));
}
}
else {
throw new Error("Unsupported runtime");
}
}
/**
* 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");
* ```
*/
async function downloadFile(url, options = {}) {
var _a;
const src = typeof url === "object" ? url.href : url;
const name = options.name || path.basename(src);
if (env.isBrowserWindow) {
const a = document.createElement("a");
a.href = src;
a.download = name;
a.click();
return;
}
else if (!env.isDeno && !env.isNodeLike || typeof fetch !== "function") {
throw new Error("Unsupported runtime");
}
const dest = await pickFile({
title: options.title,
type: options.type,
forSave: true,
defaultName: name,
});
if (!dest) // user canceled
return;
const task = async.asyncTask();
let signal = (_a = options.signal) !== null && _a !== void 0 ? _a : null;
let result;
let updateProgress;
if (options.showProgress) {
const ctrl = new AbortController();
signal = ctrl.signal;
result = dialog_progress.default("Downloading...", async (set) => {
updateProgress = set;
return await task;
}, () => {
ctrl.abort();
throw new Error("Download canceled");
});
}
else {
result = task;
}
const res = await fetch(src, { signal });
if (!res.ok) {
throw new Error(`Failed to download: ${src}`);
}
const size = parseInt(res.headers.get("Content-Length") || "0", 10);
let stream = res.body;
if (options.onProgress || options.showProgress) {
const { onProgress } = options;
let loaded = 0;
const transform = new TransformStream({
transform(chunk, controller) {
controller.enqueue(chunk);
loaded += chunk.byteLength;
if (onProgress) {
try {
onProgress === null || onProgress === void 0 ? void 0 : onProgress(event.createProgressEvent("progress", {
lengthComputable: !!size,
loaded,
total: size !== null && size !== void 0 ? size : 0,
}));
}
catch (_a) {
// ignore
}
}
if (updateProgress && size) {
updateProgress({
percent: loaded / size,
});
}
},
});
stream = stream.pipeThrough(transform);
}
fs.writeFile(dest, stream, { signal: signal }).then(() => {
task.resolve();
}).catch(err => {
task.reject(err);
});
await result;
}
exports.downloadFile = downloadFile;
exports.openDirectory = openDirectory;
exports.openFile = openFile;
exports.openFiles = openFiles;
exports.pickDirectory = pickDirectory;
exports.pickFile = pickFile;
exports.pickFiles = pickFiles;
exports.saveFile = saveFile;
//# sourceMappingURL=file.js.map