mirror of
https://gitlab.com/freepascal.org/fpc/pas2js.git
synced 2025-04-05 13:37:47 +02:00
* ZenFS filesystem utils and demo
This commit is contained in:
parent
3608db7e53
commit
bbd489c313
864
demo/zenfs/tree/browser.dom.js
Normal file
864
demo/zenfs/tree/browser.dom.js
Normal file
@ -0,0 +1,864 @@
|
||||
"use strict";
|
||||
var ZenFS_DOM = (() => {
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// src/index.ts
|
||||
var src_exports = {};
|
||||
__export(src_exports, {
|
||||
IndexedDB: () => IndexedDB,
|
||||
IndexedDBStore: () => IndexedDBStore,
|
||||
IndexedDBTransaction: () => IndexedDBTransaction,
|
||||
WebAccess: () => WebAccess,
|
||||
WebAccessFS: () => WebAccessFS,
|
||||
WebStorage: () => WebStorage,
|
||||
WebStorageStore: () => WebStorageStore
|
||||
});
|
||||
|
||||
// global-externals:@zenfs/core
|
||||
var core_default = ZenFS;
|
||||
var { ActionType, Async, AsyncIndexFS, AsyncTransaction, BigIntStats, BigIntStatsFs, Dir, Dirent, Errno, ErrnoError, Fetch, FetchFS, File, FileIndex, FileSystem, FileType, InMemory, InMemoryStore, IndexDirInode, IndexFS, IndexFileInode, IndexInode, Inode, LockedFS, Mutex, NoSyncFile, Overlay, OverlayFS, Port, PortFS, PortFile, PreloadFile, ReadStream, Readonly, SimpleAsyncStore, SimpleTransaction, Stats, StatsCommon, StatsFs, StoreFS, Sync, SyncIndexFS, SyncTransaction, Transaction, UnlockedOverlayFS, WriteStream, _toUnixTimestamp, access, accessSync, appendFile, appendFileSync, attachFS, checkOptions, chmod, chmodSync, chown, chownSync, close, closeSync, configure, constants, copyFile, copyFileSync, cp, cpSync, createReadStream, createWriteStream, decode, decodeDirListing, detachFS, encode, encodeDirListing, errorMessages, exists, existsSync, fchmod, fchmodSync, fchown, fchownSync, fdatasync, fdatasyncSync, flagToMode, flagToNumber, flagToString, fs, fstat, fstatSync, fsync, fsyncSync, ftruncate, ftruncateSync, futimes, futimesSync, isAppendable, isBackend, isBackendConfig, isExclusive, isReadable, isSynchronous, isTruncating, isWriteable, lchmod, lchmodSync, lchown, lchownSync, levenshtein, link, linkSync, lopenSync, lstat, lstatSync, lutimes, lutimesSync, mkdir, mkdirSync, mkdirpSync, mkdtemp, mkdtempSync, mount, mountObject, mounts, nop, normalizeMode, normalizeOptions, normalizePath, normalizeTime, open, openAsBlob, openSync, opendir, opendirSync, parseFlag, pathExistsAction, pathNotExistsAction, promises, randomIno, read, readFile, readFileSync, readSync, readdir, readdirSync, readlink, readlinkSync, readv, readvSync, realpath, realpathSync, rename, renameSync, resolveMountConfig, rm, rmSync, rmdir, rmdirSync, rootCred, rootIno, setImmediate, size_max, stat, statSync, statfs, statfsSync, symlink, symlinkSync, truncate, truncateSync, umount, unlink, unlinkSync, unwatchFile, utimes, utimesSync, watch, watchFile, write, writeFile, writeFileSync, writeSync, writev, writevSync } = ZenFS;
|
||||
|
||||
// node_modules/@zenfs/core/dist/emulation/path.js
|
||||
function normalizeString(path, allowAboveRoot) {
|
||||
let res = "";
|
||||
let lastSegmentLength = 0;
|
||||
let lastSlash = -1;
|
||||
let dots = 0;
|
||||
let char = "\0";
|
||||
for (let i = 0; i <= path.length; ++i) {
|
||||
if (i < path.length) {
|
||||
char = path[i];
|
||||
} else if (char == "/") {
|
||||
break;
|
||||
} else {
|
||||
char = "/";
|
||||
}
|
||||
if (char == "/") {
|
||||
if (lastSlash === i - 1 || dots === 1) {
|
||||
} else if (dots === 2) {
|
||||
if (res.length < 2 || lastSegmentLength !== 2 || res.at(-1) !== "." || res.at(-2) !== ".") {
|
||||
if (res.length > 2) {
|
||||
const lastSlashIndex = res.lastIndexOf("/");
|
||||
if (lastSlashIndex === -1) {
|
||||
res = "";
|
||||
lastSegmentLength = 0;
|
||||
} else {
|
||||
res = res.slice(0, lastSlashIndex);
|
||||
lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
|
||||
}
|
||||
lastSlash = i;
|
||||
dots = 0;
|
||||
continue;
|
||||
} else if (res.length !== 0) {
|
||||
res = "";
|
||||
lastSegmentLength = 0;
|
||||
lastSlash = i;
|
||||
dots = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (allowAboveRoot) {
|
||||
res += res.length > 0 ? "/.." : "..";
|
||||
lastSegmentLength = 2;
|
||||
}
|
||||
} else {
|
||||
if (res.length > 0)
|
||||
res += "/" + path.slice(lastSlash + 1, i);
|
||||
else
|
||||
res = path.slice(lastSlash + 1, i);
|
||||
lastSegmentLength = i - lastSlash - 1;
|
||||
}
|
||||
lastSlash = i;
|
||||
dots = 0;
|
||||
} else if (char === "." && dots !== -1) {
|
||||
++dots;
|
||||
} else {
|
||||
dots = -1;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
__name(normalizeString, "normalizeString");
|
||||
function normalize(path) {
|
||||
if (!path.length)
|
||||
return ".";
|
||||
const isAbsolute = path.startsWith("/");
|
||||
const trailingSeparator = path.endsWith("/");
|
||||
path = normalizeString(path, !isAbsolute);
|
||||
if (!path.length) {
|
||||
if (isAbsolute)
|
||||
return "/";
|
||||
return trailingSeparator ? "./" : ".";
|
||||
}
|
||||
if (trailingSeparator)
|
||||
path += "/";
|
||||
return isAbsolute ? `/${path}` : path;
|
||||
}
|
||||
__name(normalize, "normalize");
|
||||
function join(...parts) {
|
||||
if (!parts.length)
|
||||
return ".";
|
||||
const joined = parts.join("/");
|
||||
if (!joined?.length)
|
||||
return ".";
|
||||
return normalize(joined);
|
||||
}
|
||||
__name(join, "join");
|
||||
function dirname(path) {
|
||||
if (path.length === 0)
|
||||
return ".";
|
||||
const hasRoot = path[0] === "/";
|
||||
let end = -1;
|
||||
let matchedSlash = true;
|
||||
for (let i = path.length - 1; i >= 1; --i) {
|
||||
if (path[i] === "/") {
|
||||
if (!matchedSlash) {
|
||||
end = i;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
matchedSlash = false;
|
||||
}
|
||||
}
|
||||
if (end === -1)
|
||||
return hasRoot ? "/" : ".";
|
||||
if (hasRoot && end === 1)
|
||||
return "//";
|
||||
return path.slice(0, end);
|
||||
}
|
||||
__name(dirname, "dirname");
|
||||
function basename(path, suffix) {
|
||||
let start = 0;
|
||||
let end = -1;
|
||||
let matchedSlash = true;
|
||||
if (suffix !== void 0 && suffix.length > 0 && suffix.length <= path.length) {
|
||||
if (suffix === path)
|
||||
return "";
|
||||
let extIdx = suffix.length - 1;
|
||||
let firstNonSlashEnd = -1;
|
||||
for (let i = path.length - 1; i >= 0; --i) {
|
||||
if (path[i] === "/") {
|
||||
if (!matchedSlash) {
|
||||
start = i + 1;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (firstNonSlashEnd === -1) {
|
||||
matchedSlash = false;
|
||||
firstNonSlashEnd = i + 1;
|
||||
}
|
||||
if (extIdx >= 0) {
|
||||
if (path[i] === suffix[extIdx]) {
|
||||
if (--extIdx === -1) {
|
||||
end = i;
|
||||
}
|
||||
} else {
|
||||
extIdx = -1;
|
||||
end = firstNonSlashEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (start === end)
|
||||
end = firstNonSlashEnd;
|
||||
else if (end === -1)
|
||||
end = path.length;
|
||||
return path.slice(start, end);
|
||||
}
|
||||
for (let i = path.length - 1; i >= 0; --i) {
|
||||
if (path[i] === "/") {
|
||||
if (!matchedSlash) {
|
||||
start = i + 1;
|
||||
break;
|
||||
}
|
||||
} else if (end === -1) {
|
||||
matchedSlash = false;
|
||||
end = i + 1;
|
||||
}
|
||||
}
|
||||
if (end === -1)
|
||||
return "";
|
||||
return path.slice(start, end);
|
||||
}
|
||||
__name(basename, "basename");
|
||||
|
||||
// src/utils.ts
|
||||
function errnoForDOMException(ex) {
|
||||
switch (ex.name) {
|
||||
case "IndexSizeError":
|
||||
case "HierarchyRequestError":
|
||||
case "InvalidCharacterError":
|
||||
case "InvalidStateError":
|
||||
case "SyntaxError":
|
||||
case "NamespaceError":
|
||||
case "TypeMismatchError":
|
||||
case "ConstraintError":
|
||||
case "VersionError":
|
||||
case "URLMismatchError":
|
||||
case "InvalidNodeTypeError":
|
||||
return "EINVAL";
|
||||
case "WrongDocumentError":
|
||||
return "EXDEV";
|
||||
case "NoModificationAllowedError":
|
||||
case "InvalidModificationError":
|
||||
case "InvalidAccessError":
|
||||
case "SecurityError":
|
||||
case "NotAllowedError":
|
||||
return "EACCES";
|
||||
case "NotFoundError":
|
||||
return "ENOENT";
|
||||
case "NotSupportedError":
|
||||
return "ENOTSUP";
|
||||
case "InUseAttributeError":
|
||||
return "EBUSY";
|
||||
case "NetworkError":
|
||||
return "ENETDOWN";
|
||||
case "AbortError":
|
||||
return "EINTR";
|
||||
case "QuotaExceededError":
|
||||
return "ENOSPC";
|
||||
case "TimeoutError":
|
||||
return "ETIMEDOUT";
|
||||
case "ReadOnlyError":
|
||||
return "EROFS";
|
||||
case "DataCloneError":
|
||||
case "EncodingError":
|
||||
case "NotReadableError":
|
||||
case "DataError":
|
||||
case "TransactionInactiveError":
|
||||
case "OperationError":
|
||||
case "UnknownError":
|
||||
default:
|
||||
return "EIO";
|
||||
}
|
||||
}
|
||||
__name(errnoForDOMException, "errnoForDOMException");
|
||||
function convertException(ex, path, syscall) {
|
||||
if (ex instanceof ErrnoError) {
|
||||
return ex;
|
||||
}
|
||||
const code = ex instanceof DOMException ? Errno[errnoForDOMException(ex)] : Errno.EIO;
|
||||
const error = new ErrnoError(code, ex.message, path, syscall);
|
||||
error.stack = ex.stack;
|
||||
error.cause = ex.cause;
|
||||
return error;
|
||||
}
|
||||
__name(convertException, "convertException");
|
||||
|
||||
// src/access.ts
|
||||
var WebAccessFS = class extends Async(FileSystem) {
|
||||
_handles = /* @__PURE__ */ new Map();
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
_sync;
|
||||
constructor({ handle }) {
|
||||
super();
|
||||
this._handles.set("/", handle);
|
||||
this._sync = InMemory.create({ name: "accessfs-cache" });
|
||||
}
|
||||
metadata() {
|
||||
return {
|
||||
...super.metadata(),
|
||||
name: "WebAccess"
|
||||
};
|
||||
}
|
||||
async sync(path, data, stats) {
|
||||
const currentStats = await this.stat(path);
|
||||
if (stats.mtime !== currentStats.mtime) {
|
||||
await this.writeFile(path, data);
|
||||
}
|
||||
}
|
||||
async rename(oldPath, newPath) {
|
||||
try {
|
||||
const handle = await this.getHandle(oldPath);
|
||||
if (handle instanceof FileSystemDirectoryHandle) {
|
||||
const files = await this.readdir(oldPath);
|
||||
await this.mkdir(newPath);
|
||||
if (files.length == 0) {
|
||||
await this.unlink(oldPath);
|
||||
} else {
|
||||
for (const file of files) {
|
||||
await this.rename(join(oldPath, file), join(newPath, file));
|
||||
await this.unlink(oldPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!(handle instanceof FileSystemFileHandle)) {
|
||||
return;
|
||||
}
|
||||
const oldFile = await handle.getFile(), destFolder = await this.getHandle(dirname(newPath));
|
||||
if (!(destFolder instanceof FileSystemDirectoryHandle)) {
|
||||
return;
|
||||
}
|
||||
const newFile = await destFolder.getFileHandle(basename(newPath), { create: true });
|
||||
const writable = await newFile.createWritable();
|
||||
await writable.write(await oldFile.arrayBuffer());
|
||||
writable.close();
|
||||
await this.unlink(oldPath);
|
||||
} catch (ex) {
|
||||
throw convertException(ex, oldPath, "rename");
|
||||
}
|
||||
}
|
||||
async writeFile(fname, data) {
|
||||
const handle = await this.getHandle(dirname(fname));
|
||||
if (!(handle instanceof FileSystemDirectoryHandle)) {
|
||||
return;
|
||||
}
|
||||
const file = await handle.getFileHandle(basename(fname), { create: true });
|
||||
const writable = await file.createWritable();
|
||||
await writable.write(data);
|
||||
await writable.close();
|
||||
}
|
||||
async createFile(path, flag) {
|
||||
await this.writeFile(path, new Uint8Array());
|
||||
return this.openFile(path, flag);
|
||||
}
|
||||
async stat(path) {
|
||||
const handle = await this.getHandle(path);
|
||||
if (!handle) {
|
||||
throw ErrnoError.With("ENOENT", path, "stat");
|
||||
}
|
||||
if (handle instanceof FileSystemDirectoryHandle) {
|
||||
return new Stats({ mode: 511 | FileType.DIRECTORY, size: 4096 });
|
||||
}
|
||||
if (handle instanceof FileSystemFileHandle) {
|
||||
const { lastModified, size } = await handle.getFile();
|
||||
return new Stats({ mode: 511 | FileType.FILE, size, mtimeMs: lastModified });
|
||||
}
|
||||
throw new ErrnoError(Errno.EBADE, "Handle is not a directory or file", path, "stat");
|
||||
}
|
||||
async openFile(path, flag) {
|
||||
const handle = await this.getHandle(path);
|
||||
if (!(handle instanceof FileSystemFileHandle)) {
|
||||
throw ErrnoError.With("EISDIR", path, "openFile");
|
||||
}
|
||||
try {
|
||||
const file = await handle.getFile();
|
||||
const data = new Uint8Array(await file.arrayBuffer());
|
||||
const stats = new Stats({ mode: 511 | FileType.FILE, size: file.size, mtimeMs: file.lastModified });
|
||||
return new PreloadFile(this, path, flag, stats, data);
|
||||
} catch (ex) {
|
||||
throw convertException(ex, path, "openFile");
|
||||
}
|
||||
}
|
||||
async unlink(path) {
|
||||
const handle = await this.getHandle(dirname(path));
|
||||
if (handle instanceof FileSystemDirectoryHandle) {
|
||||
try {
|
||||
await handle.removeEntry(basename(path), { recursive: true });
|
||||
} catch (ex) {
|
||||
throw convertException(ex, path, "unlink");
|
||||
}
|
||||
}
|
||||
}
|
||||
async link(srcpath) {
|
||||
throw ErrnoError.With("ENOSYS", srcpath, "WebAccessFS.link");
|
||||
}
|
||||
async rmdir(path) {
|
||||
return this.unlink(path);
|
||||
}
|
||||
async mkdir(path) {
|
||||
const existingHandle = await this.getHandle(path);
|
||||
if (existingHandle) {
|
||||
throw ErrnoError.With("EEXIST", path, "mkdir");
|
||||
}
|
||||
const handle = await this.getHandle(dirname(path));
|
||||
if (!(handle instanceof FileSystemDirectoryHandle)) {
|
||||
throw ErrnoError.With("ENOTDIR", path, "mkdir");
|
||||
}
|
||||
await handle.getDirectoryHandle(basename(path), { create: true });
|
||||
}
|
||||
async readdir(path) {
|
||||
const handle = await this.getHandle(path);
|
||||
if (!(handle instanceof FileSystemDirectoryHandle)) {
|
||||
throw ErrnoError.With("ENOTDIR", path, "readdir");
|
||||
}
|
||||
const _keys = [];
|
||||
for await (const key of handle.keys()) {
|
||||
_keys.push(join(path, key));
|
||||
}
|
||||
return _keys;
|
||||
}
|
||||
async getHandle(path) {
|
||||
if (this._handles.has(path)) {
|
||||
return this._handles.get(path);
|
||||
}
|
||||
let walked = "/";
|
||||
for (const part of path.split("/").slice(1)) {
|
||||
const handle = this._handles.get(walked);
|
||||
if (!(handle instanceof FileSystemDirectoryHandle)) {
|
||||
throw ErrnoError.With("ENOTDIR", walked, "getHandle");
|
||||
}
|
||||
walked = join(walked, part);
|
||||
try {
|
||||
const dirHandle = await handle.getDirectoryHandle(part);
|
||||
this._handles.set(walked, dirHandle);
|
||||
} catch (_ex) {
|
||||
const ex = _ex;
|
||||
if (ex.name == "TypeMismatchError") {
|
||||
try {
|
||||
const fileHandle = await handle.getFileHandle(part);
|
||||
this._handles.set(walked, fileHandle);
|
||||
} catch (ex2) {
|
||||
convertException(ex2, walked, "getHandle");
|
||||
}
|
||||
}
|
||||
if (ex.name === "TypeError") {
|
||||
throw new ErrnoError(Errno.ENOENT, ex.message, walked, "getHandle");
|
||||
}
|
||||
convertException(ex, walked, "getHandle");
|
||||
}
|
||||
}
|
||||
return this._handles.get(path);
|
||||
}
|
||||
};
|
||||
__name(WebAccessFS, "WebAccessFS");
|
||||
var WebAccess = {
|
||||
name: "WebAccess",
|
||||
options: {
|
||||
handle: {
|
||||
type: "object",
|
||||
required: true,
|
||||
description: "The directory handle to use for the root"
|
||||
}
|
||||
},
|
||||
isAvailable() {
|
||||
return typeof FileSystemHandle == "function";
|
||||
},
|
||||
create(options) {
|
||||
return new WebAccessFS(options);
|
||||
}
|
||||
};
|
||||
|
||||
// node_modules/@zenfs/core/dist/error.js
|
||||
var Errno2;
|
||||
(function(Errno3) {
|
||||
Errno3[Errno3["EPERM"] = 1] = "EPERM";
|
||||
Errno3[Errno3["ENOENT"] = 2] = "ENOENT";
|
||||
Errno3[Errno3["EINTR"] = 4] = "EINTR";
|
||||
Errno3[Errno3["EIO"] = 5] = "EIO";
|
||||
Errno3[Errno3["ENXIO"] = 6] = "ENXIO";
|
||||
Errno3[Errno3["EBADF"] = 9] = "EBADF";
|
||||
Errno3[Errno3["EAGAIN"] = 11] = "EAGAIN";
|
||||
Errno3[Errno3["ENOMEM"] = 12] = "ENOMEM";
|
||||
Errno3[Errno3["EACCES"] = 13] = "EACCES";
|
||||
Errno3[Errno3["EFAULT"] = 14] = "EFAULT";
|
||||
Errno3[Errno3["ENOTBLK"] = 15] = "ENOTBLK";
|
||||
Errno3[Errno3["EBUSY"] = 16] = "EBUSY";
|
||||
Errno3[Errno3["EEXIST"] = 17] = "EEXIST";
|
||||
Errno3[Errno3["EXDEV"] = 18] = "EXDEV";
|
||||
Errno3[Errno3["ENODEV"] = 19] = "ENODEV";
|
||||
Errno3[Errno3["ENOTDIR"] = 20] = "ENOTDIR";
|
||||
Errno3[Errno3["EISDIR"] = 21] = "EISDIR";
|
||||
Errno3[Errno3["EINVAL"] = 22] = "EINVAL";
|
||||
Errno3[Errno3["ENFILE"] = 23] = "ENFILE";
|
||||
Errno3[Errno3["EMFILE"] = 24] = "EMFILE";
|
||||
Errno3[Errno3["ETXTBSY"] = 26] = "ETXTBSY";
|
||||
Errno3[Errno3["EFBIG"] = 27] = "EFBIG";
|
||||
Errno3[Errno3["ENOSPC"] = 28] = "ENOSPC";
|
||||
Errno3[Errno3["ESPIPE"] = 29] = "ESPIPE";
|
||||
Errno3[Errno3["EROFS"] = 30] = "EROFS";
|
||||
Errno3[Errno3["EMLINK"] = 31] = "EMLINK";
|
||||
Errno3[Errno3["EPIPE"] = 32] = "EPIPE";
|
||||
Errno3[Errno3["EDOM"] = 33] = "EDOM";
|
||||
Errno3[Errno3["ERANGE"] = 34] = "ERANGE";
|
||||
Errno3[Errno3["EDEADLK"] = 35] = "EDEADLK";
|
||||
Errno3[Errno3["ENAMETOOLONG"] = 36] = "ENAMETOOLONG";
|
||||
Errno3[Errno3["ENOLCK"] = 37] = "ENOLCK";
|
||||
Errno3[Errno3["ENOSYS"] = 38] = "ENOSYS";
|
||||
Errno3[Errno3["ENOTEMPTY"] = 39] = "ENOTEMPTY";
|
||||
Errno3[Errno3["ELOOP"] = 40] = "ELOOP";
|
||||
Errno3[Errno3["ENOMSG"] = 42] = "ENOMSG";
|
||||
Errno3[Errno3["EBADE"] = 52] = "EBADE";
|
||||
Errno3[Errno3["EBADR"] = 53] = "EBADR";
|
||||
Errno3[Errno3["EXFULL"] = 54] = "EXFULL";
|
||||
Errno3[Errno3["ENOANO"] = 55] = "ENOANO";
|
||||
Errno3[Errno3["EBADRQC"] = 56] = "EBADRQC";
|
||||
Errno3[Errno3["ENOSTR"] = 60] = "ENOSTR";
|
||||
Errno3[Errno3["ENODATA"] = 61] = "ENODATA";
|
||||
Errno3[Errno3["ETIME"] = 62] = "ETIME";
|
||||
Errno3[Errno3["ENOSR"] = 63] = "ENOSR";
|
||||
Errno3[Errno3["ENONET"] = 64] = "ENONET";
|
||||
Errno3[Errno3["EREMOTE"] = 66] = "EREMOTE";
|
||||
Errno3[Errno3["ENOLINK"] = 67] = "ENOLINK";
|
||||
Errno3[Errno3["ECOMM"] = 70] = "ECOMM";
|
||||
Errno3[Errno3["EPROTO"] = 71] = "EPROTO";
|
||||
Errno3[Errno3["EBADMSG"] = 74] = "EBADMSG";
|
||||
Errno3[Errno3["EOVERFLOW"] = 75] = "EOVERFLOW";
|
||||
Errno3[Errno3["EBADFD"] = 77] = "EBADFD";
|
||||
Errno3[Errno3["ESTRPIPE"] = 86] = "ESTRPIPE";
|
||||
Errno3[Errno3["ENOTSOCK"] = 88] = "ENOTSOCK";
|
||||
Errno3[Errno3["EDESTADDRREQ"] = 89] = "EDESTADDRREQ";
|
||||
Errno3[Errno3["EMSGSIZE"] = 90] = "EMSGSIZE";
|
||||
Errno3[Errno3["EPROTOTYPE"] = 91] = "EPROTOTYPE";
|
||||
Errno3[Errno3["ENOPROTOOPT"] = 92] = "ENOPROTOOPT";
|
||||
Errno3[Errno3["EPROTONOSUPPORT"] = 93] = "EPROTONOSUPPORT";
|
||||
Errno3[Errno3["ESOCKTNOSUPPORT"] = 94] = "ESOCKTNOSUPPORT";
|
||||
Errno3[Errno3["ENOTSUP"] = 95] = "ENOTSUP";
|
||||
Errno3[Errno3["ENETDOWN"] = 100] = "ENETDOWN";
|
||||
Errno3[Errno3["ENETUNREACH"] = 101] = "ENETUNREACH";
|
||||
Errno3[Errno3["ENETRESET"] = 102] = "ENETRESET";
|
||||
Errno3[Errno3["ETIMEDOUT"] = 110] = "ETIMEDOUT";
|
||||
Errno3[Errno3["ECONNREFUSED"] = 111] = "ECONNREFUSED";
|
||||
Errno3[Errno3["EHOSTDOWN"] = 112] = "EHOSTDOWN";
|
||||
Errno3[Errno3["EHOSTUNREACH"] = 113] = "EHOSTUNREACH";
|
||||
Errno3[Errno3["EALREADY"] = 114] = "EALREADY";
|
||||
Errno3[Errno3["EINPROGRESS"] = 115] = "EINPROGRESS";
|
||||
Errno3[Errno3["ESTALE"] = 116] = "ESTALE";
|
||||
Errno3[Errno3["EREMOTEIO"] = 121] = "EREMOTEIO";
|
||||
Errno3[Errno3["EDQUOT"] = 122] = "EDQUOT";
|
||||
})(Errno2 || (Errno2 = {}));
|
||||
var errorMessages2 = {
|
||||
[Errno2.EPERM]: "Operation not permitted",
|
||||
[Errno2.ENOENT]: "No such file or directory",
|
||||
[Errno2.EINTR]: "Interrupted system call",
|
||||
[Errno2.EIO]: "Input/output error",
|
||||
[Errno2.ENXIO]: "No such device or address",
|
||||
[Errno2.EBADF]: "Bad file descriptor",
|
||||
[Errno2.EAGAIN]: "Resource temporarily unavailable",
|
||||
[Errno2.ENOMEM]: "Cannot allocate memory",
|
||||
[Errno2.EACCES]: "Permission denied",
|
||||
[Errno2.EFAULT]: "Bad address",
|
||||
[Errno2.ENOTBLK]: "Block device required",
|
||||
[Errno2.EBUSY]: "Resource busy or locked",
|
||||
[Errno2.EEXIST]: "File exists",
|
||||
[Errno2.EXDEV]: "Invalid cross-device link",
|
||||
[Errno2.ENODEV]: "No such device",
|
||||
[Errno2.ENOTDIR]: "File is not a directory",
|
||||
[Errno2.EISDIR]: "File is a directory",
|
||||
[Errno2.EINVAL]: "Invalid argument",
|
||||
[Errno2.ENFILE]: "Too many open files in system",
|
||||
[Errno2.EMFILE]: "Too many open files",
|
||||
[Errno2.ETXTBSY]: "Text file busy",
|
||||
[Errno2.EFBIG]: "File is too big",
|
||||
[Errno2.ENOSPC]: "No space left on disk",
|
||||
[Errno2.ESPIPE]: "Illegal seek",
|
||||
[Errno2.EROFS]: "Cannot modify a read-only file system",
|
||||
[Errno2.EMLINK]: "Too many links",
|
||||
[Errno2.EPIPE]: "Broken pipe",
|
||||
[Errno2.EDOM]: "Numerical argument out of domain",
|
||||
[Errno2.ERANGE]: "Numerical result out of range",
|
||||
[Errno2.EDEADLK]: "Resource deadlock would occur",
|
||||
[Errno2.ENAMETOOLONG]: "File name too long",
|
||||
[Errno2.ENOLCK]: "No locks available",
|
||||
[Errno2.ENOSYS]: "Function not implemented",
|
||||
[Errno2.ENOTEMPTY]: "Directory is not empty",
|
||||
[Errno2.ELOOP]: "Too many levels of symbolic links",
|
||||
[Errno2.ENOMSG]: "No message of desired type",
|
||||
[Errno2.EBADE]: "Invalid exchange",
|
||||
[Errno2.EBADR]: "Invalid request descriptor",
|
||||
[Errno2.EXFULL]: "Exchange full",
|
||||
[Errno2.ENOANO]: "No anode",
|
||||
[Errno2.EBADRQC]: "Invalid request code",
|
||||
[Errno2.ENOSTR]: "Device not a stream",
|
||||
[Errno2.ENODATA]: "No data available",
|
||||
[Errno2.ETIME]: "Timer expired",
|
||||
[Errno2.ENOSR]: "Out of streams resources",
|
||||
[Errno2.ENONET]: "Machine is not on the network",
|
||||
[Errno2.EREMOTE]: "Object is remote",
|
||||
[Errno2.ENOLINK]: "Link has been severed",
|
||||
[Errno2.ECOMM]: "Communication error on send",
|
||||
[Errno2.EPROTO]: "Protocol error",
|
||||
[Errno2.EBADMSG]: "Bad message",
|
||||
[Errno2.EOVERFLOW]: "Value too large for defined data type",
|
||||
[Errno2.EBADFD]: "File descriptor in bad state",
|
||||
[Errno2.ESTRPIPE]: "Streams pipe error",
|
||||
[Errno2.ENOTSOCK]: "Socket operation on non-socket",
|
||||
[Errno2.EDESTADDRREQ]: "Destination address required",
|
||||
[Errno2.EMSGSIZE]: "Message too long",
|
||||
[Errno2.EPROTOTYPE]: "Protocol wrong type for socket",
|
||||
[Errno2.ENOPROTOOPT]: "Protocol not available",
|
||||
[Errno2.EPROTONOSUPPORT]: "Protocol not supported",
|
||||
[Errno2.ESOCKTNOSUPPORT]: "Socket type not supported",
|
||||
[Errno2.ENOTSUP]: "Operation is not supported",
|
||||
[Errno2.ENETDOWN]: "Network is down",
|
||||
[Errno2.ENETUNREACH]: "Network is unreachable",
|
||||
[Errno2.ENETRESET]: "Network dropped connection on reset",
|
||||
[Errno2.ETIMEDOUT]: "Connection timed out",
|
||||
[Errno2.ECONNREFUSED]: "Connection refused",
|
||||
[Errno2.EHOSTDOWN]: "Host is down",
|
||||
[Errno2.EHOSTUNREACH]: "No route to host",
|
||||
[Errno2.EALREADY]: "Operation already in progress",
|
||||
[Errno2.EINPROGRESS]: "Operation now in progress",
|
||||
[Errno2.ESTALE]: "Stale file handle",
|
||||
[Errno2.EREMOTEIO]: "Remote I/O error",
|
||||
[Errno2.EDQUOT]: "Disk quota exceeded"
|
||||
};
|
||||
var ErrnoError2 = class extends Error {
|
||||
static fromJSON(json) {
|
||||
const err = new ErrnoError2(json.errno, json.message, json.path, json.syscall);
|
||||
err.code = json.code;
|
||||
err.stack = json.stack;
|
||||
return err;
|
||||
}
|
||||
static With(code, path, syscall) {
|
||||
return new ErrnoError2(Errno2[code], errorMessages2[Errno2[code]], path, syscall);
|
||||
}
|
||||
/**
|
||||
* Represents a ZenFS error. Passed back to applications after a failed
|
||||
* call to the ZenFS API.
|
||||
*
|
||||
* Error codes mirror those returned by regular Unix file operations, which is
|
||||
* what Node returns.
|
||||
* @param type The type of the error.
|
||||
* @param message A descriptive error message.
|
||||
*/
|
||||
constructor(errno, message = errorMessages2[errno], path, syscall = "") {
|
||||
super(message);
|
||||
this.errno = errno;
|
||||
this.path = path;
|
||||
this.syscall = syscall;
|
||||
this.code = Errno2[errno];
|
||||
this.message = `${this.code}: ${message}${this.path ? `, '${this.path}'` : ""}`;
|
||||
}
|
||||
/**
|
||||
* @return A friendly error message.
|
||||
*/
|
||||
toString() {
|
||||
return this.message;
|
||||
}
|
||||
toJSON() {
|
||||
return {
|
||||
errno: this.errno,
|
||||
code: this.code,
|
||||
path: this.path,
|
||||
stack: this.stack,
|
||||
message: this.message,
|
||||
syscall: this.syscall
|
||||
};
|
||||
}
|
||||
/**
|
||||
* The size of the API error in buffer-form in bytes.
|
||||
*/
|
||||
bufferSize() {
|
||||
return 4 + JSON.stringify(this.toJSON()).length;
|
||||
}
|
||||
};
|
||||
__name(ErrnoError2, "ErrnoError");
|
||||
|
||||
// node_modules/@zenfs/core/dist/backends/store/store.js
|
||||
var Transaction2 = class {
|
||||
constructor() {
|
||||
this.aborted = false;
|
||||
}
|
||||
async [Symbol.asyncDispose]() {
|
||||
if (this.aborted) {
|
||||
return;
|
||||
}
|
||||
await this.commit();
|
||||
}
|
||||
[Symbol.dispose]() {
|
||||
if (this.aborted) {
|
||||
return;
|
||||
}
|
||||
this.commitSync();
|
||||
}
|
||||
};
|
||||
__name(Transaction2, "Transaction");
|
||||
var AsyncTransaction2 = class extends Transaction2 {
|
||||
getSync(ino) {
|
||||
throw ErrnoError2.With("ENOSYS", void 0, "AsyncTransaction.getSync");
|
||||
}
|
||||
setSync(ino, data) {
|
||||
throw ErrnoError2.With("ENOSYS", void 0, "AsyncTransaction.setSync");
|
||||
}
|
||||
removeSync(ino) {
|
||||
throw ErrnoError2.With("ENOSYS", void 0, "AsyncTransaction.removeSync");
|
||||
}
|
||||
commitSync() {
|
||||
throw ErrnoError2.With("ENOSYS", void 0, "AsyncTransaction.commitSync");
|
||||
}
|
||||
abortSync() {
|
||||
throw ErrnoError2.With("ENOSYS", void 0, "AsyncTransaction.abortSync");
|
||||
}
|
||||
};
|
||||
__name(AsyncTransaction2, "AsyncTransaction");
|
||||
|
||||
// src/IndexedDB.ts
|
||||
function wrap(request) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = (e) => {
|
||||
e.preventDefault();
|
||||
reject(convertException(request.error));
|
||||
};
|
||||
});
|
||||
}
|
||||
__name(wrap, "wrap");
|
||||
var IndexedDBTransaction = class extends AsyncTransaction2 {
|
||||
constructor(tx, store) {
|
||||
super();
|
||||
this.tx = tx;
|
||||
this.store = store;
|
||||
}
|
||||
get(key) {
|
||||
return wrap(this.store.get(key.toString()));
|
||||
}
|
||||
async set(key, data) {
|
||||
await wrap(this.store.put(data, key.toString()));
|
||||
}
|
||||
remove(key) {
|
||||
return wrap(this.store.delete(key.toString()));
|
||||
}
|
||||
async commit() {
|
||||
this.tx.commit();
|
||||
}
|
||||
async abort() {
|
||||
try {
|
||||
this.tx.abort();
|
||||
} catch (e) {
|
||||
throw convertException(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
__name(IndexedDBTransaction, "IndexedDBTransaction");
|
||||
async function createDB(name, indexedDB = globalThis.indexedDB) {
|
||||
const req = indexedDB.open(name);
|
||||
req.onupgradeneeded = () => {
|
||||
const db = req.result;
|
||||
if (db.objectStoreNames.contains(name)) {
|
||||
db.deleteObjectStore(name);
|
||||
}
|
||||
db.createObjectStore(name);
|
||||
};
|
||||
const result = await wrap(req);
|
||||
return result;
|
||||
}
|
||||
__name(createDB, "createDB");
|
||||
var IndexedDBStore = class {
|
||||
constructor(db) {
|
||||
this.db = db;
|
||||
}
|
||||
sync() {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
get name() {
|
||||
return IndexedDB.name + ":" + this.db.name;
|
||||
}
|
||||
clear() {
|
||||
return wrap(this.db.transaction(this.db.name, "readwrite").objectStore(this.db.name).clear());
|
||||
}
|
||||
clearSync() {
|
||||
throw ErrnoError.With("ENOSYS", void 0, "IndexedDBStore.clearSync");
|
||||
}
|
||||
transaction() {
|
||||
const tx = this.db.transaction(this.db.name, "readwrite");
|
||||
return new IndexedDBTransaction(tx, tx.objectStore(this.db.name));
|
||||
}
|
||||
};
|
||||
__name(IndexedDBStore, "IndexedDBStore");
|
||||
var IndexedDB = {
|
||||
name: "IndexedDB",
|
||||
options: {
|
||||
storeName: {
|
||||
type: "string",
|
||||
required: false,
|
||||
description: "The name of this file system. You can have multiple IndexedDB file systems operating at once, but each must have a different name."
|
||||
},
|
||||
idbFactory: {
|
||||
type: "object",
|
||||
required: false,
|
||||
description: "The IDBFactory to use. Defaults to globalThis.indexedDB."
|
||||
}
|
||||
},
|
||||
async isAvailable(idbFactory = globalThis.indexedDB) {
|
||||
try {
|
||||
if (!(idbFactory instanceof IDBFactory)) {
|
||||
return false;
|
||||
}
|
||||
const req = idbFactory.open("__zenfs_test");
|
||||
await wrap(req);
|
||||
idbFactory.deleteDatabase("__zenfs_test");
|
||||
return true;
|
||||
} catch (e) {
|
||||
idbFactory.deleteDatabase("__zenfs_test");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
async create(options) {
|
||||
const db = await createDB(options.storeName || "zenfs", options.idbFactory);
|
||||
const store = new IndexedDBStore(db);
|
||||
const fs2 = new StoreFS(store);
|
||||
return fs2;
|
||||
}
|
||||
};
|
||||
|
||||
// src/Storage.ts
|
||||
var WebStorageStore = class {
|
||||
constructor(_storage) {
|
||||
this._storage = _storage;
|
||||
}
|
||||
get name() {
|
||||
return WebStorage.name;
|
||||
}
|
||||
clear() {
|
||||
this._storage.clear();
|
||||
}
|
||||
clearSync() {
|
||||
this._storage.clear();
|
||||
}
|
||||
async sync() {
|
||||
}
|
||||
transaction() {
|
||||
return new SimpleTransaction(this);
|
||||
}
|
||||
get(key) {
|
||||
const data = this._storage.getItem(key.toString());
|
||||
if (typeof data != "string") {
|
||||
return;
|
||||
}
|
||||
return encode(data);
|
||||
}
|
||||
set(key, data) {
|
||||
try {
|
||||
this._storage.setItem(key.toString(), decode(data));
|
||||
} catch (e) {
|
||||
throw new ErrnoError(Errno.ENOSPC, "Storage is full.");
|
||||
}
|
||||
}
|
||||
delete(key) {
|
||||
try {
|
||||
this._storage.removeItem(key.toString());
|
||||
} catch (e) {
|
||||
throw new ErrnoError(Errno.EIO, "Unable to delete key " + key + ": " + e);
|
||||
}
|
||||
}
|
||||
};
|
||||
__name(WebStorageStore, "WebStorageStore");
|
||||
var WebStorage = {
|
||||
name: "WebStorage",
|
||||
options: {
|
||||
storage: {
|
||||
type: "object",
|
||||
required: false,
|
||||
description: "The Storage to use. Defaults to globalThis.localStorage."
|
||||
}
|
||||
},
|
||||
isAvailable(storage = globalThis.localStorage) {
|
||||
return storage instanceof globalThis.Storage;
|
||||
},
|
||||
create({ storage = globalThis.localStorage }) {
|
||||
return new StoreFS(new WebStorageStore(storage));
|
||||
}
|
||||
};
|
||||
return __toCommonJS(src_exports);
|
||||
})();
|
||||
//# sourceMappingURL=browser.js.map
|
14734
demo/zenfs/tree/browser.min.js
vendored
Normal file
14734
demo/zenfs/tree/browser.min.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
demo/zenfs/tree/bulma.min.css
vendored
Normal file
1
demo/zenfs/tree/bulma.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
63
demo/zenfs/tree/index.html
Normal file
63
demo/zenfs/tree/index.html
Normal file
@ -0,0 +1,63 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<title>Project1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" type="text/css" href="bulma.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="zfsstyles.css">
|
||||
<script src="browser.min.js"></script>
|
||||
<script src="browser.dom.js"></script>
|
||||
<script src="treedemo.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="box">
|
||||
<p class="title is-3">ZenFS demo</p>
|
||||
<p>This page demonstrates a utilty for the <a href="ZenFS">ZenFS Api</a>, a browser filesystem simulation.</p>
|
||||
<p>The backend is using local storage. This means that the files do not actually exist on disk, but their
|
||||
contents are saved across page loads in the browser local storage.</p>
|
||||
<p>
|
||||
You can use the utility to construct a file tree of the ZenFS filesystem. At the same time, you can
|
||||
use the helper routines to create a download link and to download the (virtual) files from browser to disk.
|
||||
</p>
|
||||
</div> <!-- .box -->
|
||||
<div class="box columns" style="min-height: 400px;">
|
||||
<div class="column">
|
||||
<div id="treeFiles" style="min-height: 300px;">
|
||||
</div>
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label is-normal">
|
||||
<label class="label">File</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field has-addons">
|
||||
<div class="control">
|
||||
<input id="edtFilename" type="text" class="input is-link" placeholder="Type filename or select file in tree">
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button" id="btnDownload">Download</button>
|
||||
</div> <!-- .control -->
|
||||
</div> <!-- .field.has-addons -->
|
||||
</div> <!-- .field-body -->
|
||||
</div> <!-- field-body -->
|
||||
<div id="divDownloads">
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<p class="title is-5">Program output</p>
|
||||
<div id="pasjsconsole"></div>
|
||||
</div>
|
||||
</div> <!-- .box .columns -->
|
||||
<footer class="footer">
|
||||
<div class="content has-text-centered">
|
||||
<p>
|
||||
Created using <a target="_blank" href="https://wiki.freepascal.org/pas2js">pas2js.</a>
|
||||
Sources: <a target="new" href="treedemo.lpr">Program</a>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
<script>
|
||||
rtl.run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
90
demo/zenfs/tree/treedemo.lpi
Normal file
90
demo/zenfs/tree/treedemo.lpi
Normal file
@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CONFIG>
|
||||
<ProjectOptions>
|
||||
<Version Value="12"/>
|
||||
<General>
|
||||
<Flags>
|
||||
<MainUnitHasCreateFormStatements Value="False"/>
|
||||
<MainUnitHasTitleStatement Value="False"/>
|
||||
<MainUnitHasScaledStatement Value="False"/>
|
||||
</Flags>
|
||||
<SessionStorage Value="InProjectDir"/>
|
||||
<Title Value="treedemo"/>
|
||||
<UseAppBundle Value="False"/>
|
||||
<ResourceType Value="res"/>
|
||||
</General>
|
||||
<CustomData Count="4">
|
||||
<Item0 Name="MaintainHTML" Value="1"/>
|
||||
<Item1 Name="Pas2JSProject" Value="1"/>
|
||||
<Item2 Name="PasJSLocation" Value="treedemo"/>
|
||||
<Item3 Name="PasJSWebBrowserProject" Value="1"/>
|
||||
</CustomData>
|
||||
<BuildModes>
|
||||
<Item Name="Default" Default="True"/>
|
||||
</BuildModes>
|
||||
<PublishOptions>
|
||||
<Version Value="2"/>
|
||||
<UseFileFilters Value="True"/>
|
||||
</PublishOptions>
|
||||
<RunParams>
|
||||
<FormatVersion Value="2"/>
|
||||
</RunParams>
|
||||
<Units>
|
||||
<Unit>
|
||||
<Filename Value="treedemo.lpr"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<UnitName Value="BrowserDom10"/>
|
||||
</Unit>
|
||||
<Unit>
|
||||
<Filename Value="index.html"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<CustomData Count="1">
|
||||
<Item0 Name="PasJSIsProjectHTMLFile" Value="1"/>
|
||||
</CustomData>
|
||||
</Unit>
|
||||
</Units>
|
||||
</ProjectOptions>
|
||||
<CompilerOptions>
|
||||
<Version Value="11"/>
|
||||
<Target FileExt=".js">
|
||||
<Filename Value="treedemo"/>
|
||||
</Target>
|
||||
<SearchPaths>
|
||||
<IncludeFiles Value="$(ProjOutDir)"/>
|
||||
<UnitOutputDirectory Value="js"/>
|
||||
</SearchPaths>
|
||||
<Parsing>
|
||||
<SyntaxOptions>
|
||||
<AllowLabel Value="False"/>
|
||||
<UseAnsiStrings Value="False"/>
|
||||
<CPPInline Value="False"/>
|
||||
</SyntaxOptions>
|
||||
</Parsing>
|
||||
<CodeGeneration>
|
||||
<TargetOS Value="browser"/>
|
||||
</CodeGeneration>
|
||||
<Linking>
|
||||
<Debugging>
|
||||
<GenerateDebugInfo Value="False"/>
|
||||
<UseLineInfoUnit Value="False"/>
|
||||
</Debugging>
|
||||
</Linking>
|
||||
<Other>
|
||||
<CustomOptions Value="-Jeutf-8 -Jirtl.js -Jc -Jminclude"/>
|
||||
<CompilerPath Value="$(pas2js)"/>
|
||||
</Other>
|
||||
</CompilerOptions>
|
||||
<Debugging>
|
||||
<Exceptions>
|
||||
<Item>
|
||||
<Name Value="EAbort"/>
|
||||
</Item>
|
||||
<Item>
|
||||
<Name Value="ECodetoolError"/>
|
||||
</Item>
|
||||
<Item>
|
||||
<Name Value="EFOpenError"/>
|
||||
</Item>
|
||||
</Exceptions>
|
||||
</Debugging>
|
||||
</CONFIG>
|
175
demo/zenfs/tree/treedemo.lpr
Normal file
175
demo/zenfs/tree/treedemo.lpr
Normal file
@ -0,0 +1,175 @@
|
||||
program BrowserDom10;
|
||||
|
||||
{$mode objfpc}
|
||||
|
||||
uses
|
||||
BrowserConsole, JS, Classes, SysUtils, Web, BrowserApp, libzenfs, libzenfsdom, wasizenfs,
|
||||
zenfsutils;
|
||||
|
||||
Type
|
||||
|
||||
{ TMyApplication }
|
||||
|
||||
TMyApplication = class(TBrowserApplication)
|
||||
Private
|
||||
BtnDownload : TJSHTMLButtonElement;
|
||||
EdtFileName : TJSHTMLInputElement;
|
||||
DivDownloads : TJSHTMLElement;
|
||||
FTreeBuilder : THTMLZenFSTree;
|
||||
procedure CreateFiles;
|
||||
procedure DoSelectFile(Sender: TObject; aFileName: String; aType: TFileType);
|
||||
procedure SetupFS; async;
|
||||
procedure DoDownload(Event : TJSEvent);
|
||||
procedure SelectFile(Sender: TObject; aFileName: String; aType : TFileType);
|
||||
Public
|
||||
constructor Create(aOwner : TComponent); override;
|
||||
procedure DoRun; override;
|
||||
end;
|
||||
|
||||
{ TMyApplication }
|
||||
|
||||
constructor TMyApplication.Create(aOwner: TComponent);
|
||||
begin
|
||||
inherited Create(aOwner);
|
||||
// Allow to load file specified in hash: index.html#mywasmfile.wasm
|
||||
BtnDownload:=TJSHTMLButtonElement(GetHTMLElement('btnDownload'));
|
||||
BtnDownload.AddEVentListener('click',@DoDownload);
|
||||
EdtFileName:=TJSHTMLInputElement(GetHTMLElement('edtFilename'));
|
||||
DivDownloads:=GetHTMLElement('divDownloads');
|
||||
FTreeBuilder:=THTMLZenFSTree.Create(Self);
|
||||
FTreeBuilder.MaxHeight:='300px';
|
||||
FTreeBuilder.ParentElementID:='treeFiles';
|
||||
FTreeBuilder.OnFileSelected:=@SelectFile;
|
||||
end;
|
||||
|
||||
procedure TMyApplication.DoRun;
|
||||
|
||||
begin
|
||||
SetupFS;
|
||||
end;
|
||||
|
||||
procedure TMyApplication.CreateFiles;
|
||||
|
||||
Procedure ForceDir(const aDir: string);
|
||||
|
||||
var
|
||||
Stat : TZenFSStats;
|
||||
|
||||
begin
|
||||
try
|
||||
Stat:=ZenFS.statSync(aDir);
|
||||
except
|
||||
Writeln('Directory "',aDir,'" does not exist, creating it.')
|
||||
end;
|
||||
if Not assigned(Stat) then
|
||||
begin
|
||||
try
|
||||
ZenFS.mkdirSync(aDir,&775)
|
||||
except
|
||||
Writeln('Failed to create directory "',aDir,'"');
|
||||
Raise;
|
||||
end;
|
||||
end
|
||||
else if Stat.isDirectory then
|
||||
Raise Exception.Create(aDir+' is not a directory');
|
||||
end;
|
||||
|
||||
Procedure ForceFile(aFile : String);
|
||||
|
||||
var
|
||||
S : String;
|
||||
I : Integer;
|
||||
|
||||
begin
|
||||
Writeln('Creating file: ',aFile);
|
||||
S:='This is the content of file "'+aFile+'". Some random numbers:';
|
||||
For I:=1 to 10+Random(90) do
|
||||
S:=S+'Line '+IntToStr(i)+': '+IntToStr(1+Random(100))+sLineBreak;
|
||||
try
|
||||
ZenFS.writeFileSync(aFile,S);
|
||||
except
|
||||
Writeln('Failed to create file: ',aFile);
|
||||
end;
|
||||
end;
|
||||
|
||||
var
|
||||
FN : Integer;
|
||||
|
||||
begin
|
||||
ForceDir('/tmp');
|
||||
ForceFile('/tmp/file1.txt');
|
||||
ForceDir('/tmp/logs');
|
||||
For FN:=2 to 5+Random(5) do
|
||||
ForceFile(Format('/tmp/file_%d.txt',[FN]));
|
||||
For FN:=1 to 5+Random(5) do
|
||||
ForceFile(Format('/tmp/logs/file_%.6d.log',[FN]));
|
||||
ForceDir('/home');
|
||||
ForceDir('/home/user');
|
||||
For FN:=1 to 5+Random(5) do
|
||||
ForceFile(Format('/home/user/diary%d.log',[FN]));
|
||||
ForceDir('/home/user2');
|
||||
For FN:=1 to 1+Random(5) do
|
||||
ForceFile(Format('/home/user2/diary%d.log',[FN]));
|
||||
end;
|
||||
|
||||
procedure TMyApplication.DoSelectFile(Sender: TObject; aFileName: String; aType: TFileType);
|
||||
|
||||
const
|
||||
filetypes : Array[TFileType] of string = ('Unknown','File','Directory','SymLink');
|
||||
|
||||
begin
|
||||
Writeln('You selected '+FileTypes[aType]+': '+aFileName);
|
||||
if aType=ftFile then
|
||||
EdtFileName.Value:=aFileName;
|
||||
end;
|
||||
|
||||
procedure TMyApplication.SetupFS;
|
||||
|
||||
var
|
||||
Stat : TZenFSStats;
|
||||
|
||||
begin
|
||||
Terminate;
|
||||
// Set up filesystem
|
||||
aWait(TJSObject,ZenFS.configure(
|
||||
New([
|
||||
'mounts', New([
|
||||
'/',DomBackends.WebStorage
|
||||
])
|
||||
])));
|
||||
// Allow to load file specified in hash: index.html#mywasmfile.wasm
|
||||
try
|
||||
Stat:=ZenFS.statSync('/tmp/file1.txt');
|
||||
|
||||
except
|
||||
Writeln('Directory structure does not exist, creating one');
|
||||
end;
|
||||
if Not assigned(Stat) then
|
||||
CreateFiles
|
||||
else
|
||||
Writeln('Directory structure already exists.');
|
||||
FTreeBuilder.ShowDir('/');
|
||||
FTreeBuilder.OnFileSelected:=@DoSelectFile;
|
||||
end;
|
||||
|
||||
procedure TMyApplication.DoDownload(Event : TJSEvent);
|
||||
var
|
||||
a : TJSHTMLAnchorElement;
|
||||
begin
|
||||
a:=CreateDownLoadFromFile(edtFileName.value,'application/octet-stream',divDownloads,'file '+edtFileName.value);
|
||||
a.click;
|
||||
end;
|
||||
|
||||
procedure TMyApplication.SelectFile(Sender: TObject; aFileName: String; aType : TFileType);
|
||||
begin
|
||||
if aType=ftFile then
|
||||
EdtFileName.Value:=aFileName;
|
||||
end;
|
||||
|
||||
var
|
||||
Application : TMyApplication;
|
||||
begin
|
||||
Application:=TMyApplication.Create(nil);
|
||||
Application.Initialize;
|
||||
Application.Run;
|
||||
end.
|
83
demo/zenfs/tree/zfsstyles.css
Normal file
83
demo/zenfs/tree/zfsstyles.css
Normal file
@ -0,0 +1,83 @@
|
||||
:root {
|
||||
--light-bg-color: rgb(237 238 242);
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Object Tree
|
||||
*
|
||||
*/
|
||||
|
||||
.zft-caption {
|
||||
padding: 1px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
background-color: rgb(237 238 242);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.zft-caption-lbl {
|
||||
flex-grow: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Object tree caption button */
|
||||
.zft-icon-btn {
|
||||
padding: 1px 10px;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
.zft-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
ul.zft-tree-nested {
|
||||
list-style-type: none;
|
||||
font-size: 10pt;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
li.zft-collapsed ul.zft-tree-nested {
|
||||
display: none
|
||||
}
|
||||
.zft-tree-item-caption {
|
||||
user-select: none;
|
||||
padding-right: 2em;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
li.zft-selected > .zft-tree-item-caption {
|
||||
background-color: blue;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.zft-tree-item-caption::before {
|
||||
color: black;
|
||||
display: inline-block;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
li.xzft-collapsed > span.zft-tree-item-caption::before {
|
||||
content: "\27A4";
|
||||
}
|
||||
|
||||
li.xzft-expanded > span.zft-tree-item-caption::before {
|
||||
content: "\2B9F";
|
||||
}
|
||||
|
||||
li.zft-collapsed::before {
|
||||
content: "\27A4";
|
||||
}
|
||||
|
||||
li.zft-expanded::before {
|
||||
content: "\2B9F";
|
||||
}
|
||||
|
||||
|
766
packages/zenfs/zenfsutils.pas
Normal file
766
packages/zenfs/zenfsutils.pas
Normal file
@ -0,0 +1,766 @@
|
||||
unit zenfsutils;
|
||||
|
||||
{$mode objfpc}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, WebOrWorker, Web, JS, LibZenFS;
|
||||
|
||||
Type
|
||||
EHTMLTreeBuilder = class(Exception);
|
||||
|
||||
TFileType = (ftUnknown,ftFile,ftDirectory,ftSymLink);
|
||||
TFileSelectedEvent = procedure(Sender : TObject; aFileName : String; aType : TFileType) of object;
|
||||
|
||||
{ TIconHTML }
|
||||
|
||||
TIconHTML = Class(TPersistent)
|
||||
private
|
||||
FOnChange: TNotifyEvent;
|
||||
FDir: String;
|
||||
FFile: String;
|
||||
FOwner : TComponent;
|
||||
FRefresh: String;
|
||||
FSymlink: String;
|
||||
procedure SetDir(AValue: String);
|
||||
procedure SetNormalFile(AValue: String);
|
||||
procedure SetRefresh(AValue: String);
|
||||
procedure SetSymlink(AValue: String);
|
||||
protected
|
||||
constructor Create(aOwner : TComponent); virtual;
|
||||
function GetOwner: TPersistent; override;
|
||||
Procedure Changed; virtual;
|
||||
property OnChange: TNotifyEvent Read FOnChange Write FOnChange;
|
||||
public
|
||||
procedure Assign(Source: TPersistent); override;
|
||||
Published
|
||||
Property Directory : String Read FDir Write SetDir;
|
||||
Property NormalFile : String Read FFile Write SetNormalFile;
|
||||
Property Refresh : String Read FRefresh Write SetRefresh;
|
||||
Property Symlink : String Read FSymlink Write SetSymlink;
|
||||
end;
|
||||
|
||||
TObjectTreeIconHTML = class(TIconHTML);
|
||||
|
||||
{ THTMLTreeBuilder }
|
||||
|
||||
THTMLTreeBuilder = class(TObject)
|
||||
private
|
||||
FIcons: TObjectTreeIconHTML;
|
||||
FOnObjectSelect: TFileSelectedEvent;
|
||||
FParentElement: TJSHTMLElement;
|
||||
FRootDir: String;
|
||||
FRootElement : TJSHTMLElement;
|
||||
FStartCollapsed: Boolean;
|
||||
function GetItemFileName(Itm: TJSHTMLElement): string;
|
||||
function GetParentDirEl(el: TJSHTMLElement): TJSHTMLELement;
|
||||
function GetPathFromEl(el: TJSHTmlElement): String;
|
||||
procedure HandleItemCollapse(Event: TJSEvent);
|
||||
procedure HandleItemSelect(Event: TJSEvent);
|
||||
procedure SetIcons(AValue: TObjectTreeIconHTML);
|
||||
procedure SetParentElement(AValue: TJSHTMLElement);
|
||||
protected
|
||||
function CreateIcons(aOwner :TComponent) : TObjectTreeIconHTML; virtual;
|
||||
Public
|
||||
constructor Create(aOwner : TComponent);
|
||||
Destructor destroy; override;
|
||||
Function AddItem(aParent : TJSHTMLElement; aCaption : String; aType : TFileType) : TJSHTMLElement;
|
||||
Function FindObjectItem(aID : Integer) : TJSHTMLElement;
|
||||
procedure Clear;
|
||||
Property ParentElement : TJSHTMLElement Read FParentElement Write SetParentElement;
|
||||
Property OnFileSelected : TFileSelectedEvent Read FOnObjectSelect Write FOnObjectSelect;
|
||||
Property StartCollapsed : Boolean Read FStartCollapsed Write FStartCollapsed;
|
||||
Property Icons : TObjectTreeIconHTML Read FIcons Write SetIcons;
|
||||
Property RootDir : String Read FRootDir;
|
||||
end;
|
||||
|
||||
|
||||
Type
|
||||
TOTOption = (otShowCaption,otStartCollapsed);
|
||||
TOTOptions = set of TOTOption;
|
||||
|
||||
{ THTMLZenFSTree }
|
||||
|
||||
THTMLZenFSTree = class(TComponent)
|
||||
private
|
||||
FBuilder: THTMLTreeBuilder;
|
||||
FCaption: String;
|
||||
FMaxHeight: String;
|
||||
FOnRefresh: TNotifyEvent;
|
||||
FOptions: TOTOptions;
|
||||
FParentElement,
|
||||
FCaptionElement : TJSHTMLElement;
|
||||
FRootDir: String;
|
||||
function GetIconHtml: TObjectTreeIconHTML;
|
||||
function GetOnObjectSelected: TFileSelectedEvent;
|
||||
function GetParentElement: TJSHTMLElement;
|
||||
function GetParentElementID: String;
|
||||
procedure HandleRefresh(aEvent: TJSEvent);
|
||||
procedure SetCaption(AValue: String);
|
||||
procedure SetIconHTML(AValue: TObjectTreeIconHTML);
|
||||
procedure SetOnObjectSelected(AValue: TFileSelectedEvent);
|
||||
procedure SetOptions(AValue: TOTOptions);
|
||||
procedure SetParentElement(AValue: TJSHTMLElement);
|
||||
procedure SetParentElementID(AValue: String);
|
||||
Protected
|
||||
function CreateBuilder: THTMLTreeBuilder; virtual;
|
||||
function BuildWrapper(aParent: TJSHTMLElement): TJSHTMLElement;
|
||||
procedure RenderCaption(aEl: TJSHTMLELement);
|
||||
Public
|
||||
Constructor Create(aOwner : TComponent); override;
|
||||
Destructor Destroy; override;
|
||||
Procedure ShowDir(aParent : TJSHTMLElement; aDir : String);
|
||||
Procedure ShowDir(aDir : String);
|
||||
Procedure Clear;
|
||||
Property ParentElement : TJSHTMLElement Read GetParentElement Write SetParentElement;
|
||||
Published
|
||||
Property ParentElementID : String Read GetParentElementID Write SetParentElementID;
|
||||
Property OnFileSelected : TFileSelectedEvent Read GetOnObjectSelected Write SetOnObjectSelected;
|
||||
Property Caption : String Read FCaption Write SetCaption;
|
||||
Property Options : TOTOptions Read FOptions Write SetOptions;
|
||||
Property OnRefresh : TNotifyEvent Read FOnRefresh Write FOnRefresh;
|
||||
Property Icons : TObjectTreeIconHTML Read GetIconHtml Write SetIconHTML;
|
||||
Property RootDir : String Read FRootDir;
|
||||
Property MaxHeight : String Read FMaxHeight Write FMaxHeight;
|
||||
end;
|
||||
|
||||
|
||||
|
||||
function base64ToBytes(str : string) : TJSuint8array;
|
||||
function bytesToBase64(bytes : TJSUInt8Array) : String;
|
||||
function base64encode(str: string) : string;
|
||||
Function CreateDataURL(aFileName,aMimeType : string) : String;
|
||||
Function CreateDownLoadFromFile(const aFileName,aMimeType : string; aParent : TJSHTMLElement; const aLinkText : String) : TJSHTMLAnchorElement;
|
||||
Function CreateDownLoadFromFile(const aFileName,aMimeType : string; aParent : TJSHTMLElement; const aLinkContent : TJSNode) : TJSHTMLAnchorElement;
|
||||
|
||||
implementation
|
||||
|
||||
// uses debug.objectinspector.html;
|
||||
|
||||
{ TIconHTML }
|
||||
|
||||
procedure TIconHTML.SetDir(AValue: String);
|
||||
begin
|
||||
if FDir=AValue then Exit;
|
||||
FDir:=AValue;
|
||||
Changed;
|
||||
end;
|
||||
|
||||
procedure TIconHTML.SetNormalFile(AValue: String);
|
||||
begin
|
||||
if FFIle=AValue then Exit;
|
||||
FFile:=AValue;
|
||||
Changed;
|
||||
end;
|
||||
|
||||
procedure TIconHTML.SetRefresh(AValue: String);
|
||||
begin
|
||||
if FRefresh=AValue then Exit;
|
||||
FRefresh:=AValue;
|
||||
Changed;
|
||||
end;
|
||||
|
||||
procedure TIconHTML.SetSymlink(AValue: String);
|
||||
begin
|
||||
if FSymlink=AValue then Exit;
|
||||
FSymlink:=AValue;
|
||||
Changed;
|
||||
end;
|
||||
|
||||
const
|
||||
DefaultDirHTML = '📁';
|
||||
DefaultFileHTML = '🗋';
|
||||
DefaultRefreshHTML = '⟳';
|
||||
|
||||
constructor TIconHTML.Create(aOwner: TComponent);
|
||||
begin
|
||||
FOwner:=aOwner;
|
||||
FDir:=DefaultDirHTML;
|
||||
FFile:=DefaultFileHTML;
|
||||
FRefresh:=DefaultRefreshHTML;
|
||||
end;
|
||||
|
||||
function TIconHTML.GetOwner: TPersistent;
|
||||
begin
|
||||
Result:=FOwner;
|
||||
end;
|
||||
|
||||
procedure TIconHTML.Changed;
|
||||
begin
|
||||
if Assigned(FOnChange) then
|
||||
FOnChange(Self);
|
||||
end;
|
||||
|
||||
procedure TIconHTML.Assign(Source: TPersistent);
|
||||
|
||||
var
|
||||
Src : TIconHTML absolute Source;
|
||||
|
||||
begin
|
||||
if Source is TIconHTML then
|
||||
begin
|
||||
FFile:=Src.FFile;
|
||||
FDir:=Src.FDir;
|
||||
end
|
||||
else
|
||||
inherited Assign(Source);
|
||||
end;
|
||||
|
||||
|
||||
procedure THTMLTreeBuilder.SetParentElement(AValue: TJSHTMLElement);
|
||||
begin
|
||||
if FParentElement=AValue then Exit;
|
||||
FParentElement:=AValue;
|
||||
FParentElement.innerHTML:='';
|
||||
FRootElement:=nil;
|
||||
end;
|
||||
|
||||
constructor THTMLTreeBuilder.Create(aOwner: TComponent);
|
||||
begin
|
||||
FIcons:=CreateIcons(aOwner);
|
||||
end;
|
||||
|
||||
destructor THTMLTreeBuilder.destroy;
|
||||
begin
|
||||
FreeAndNil(FIcons);
|
||||
inherited destroy;
|
||||
end;
|
||||
|
||||
function THTMLTreeBuilder.CreateIcons(aOwner: TComponent): TObjectTreeIconHTML;
|
||||
begin
|
||||
Result:=TObjectTreeIconHTML.Create(aOwner);
|
||||
end;
|
||||
|
||||
procedure THTMLTreeBuilder.HandleItemCollapse(Event : TJSEvent);
|
||||
|
||||
var
|
||||
El : TJSHTMLElement;
|
||||
|
||||
begin
|
||||
El:=TJSHTMLElement(event.targetElement.parentElement);
|
||||
El.classList.toggle('zft-expanded');
|
||||
El.classList.toggle('zft-collapsed');
|
||||
end;
|
||||
|
||||
function THTMLTreeBuilder.GetParentDirEl(el: TJSHTMLElement): TJSHTMLELement;
|
||||
|
||||
function IsDirEl(aItem : TJSHTMLELement) : boolean;
|
||||
begin
|
||||
Result:=SameText(aItem.tagName,'li') and aItem.ClassList.contains('zft-directory');
|
||||
end;
|
||||
|
||||
begin
|
||||
Result:=TJSHTMLElement(El.parentElement);
|
||||
While (Result<>Nil) and Not IsDirEl(Result) do
|
||||
Result:=TJSHTMLElement(Result.parentElement);
|
||||
end;
|
||||
|
||||
function THTMLTreeBuilder.GetItemFileName(Itm : TJSHTMLElement) : string;
|
||||
|
||||
var
|
||||
Cap : TJSHTMLElement;
|
||||
begin
|
||||
cap:=TJSHTMLElement(Itm.querySelector(':scope > span.zft-tree-item-caption'));
|
||||
if assigned(cap) then
|
||||
Result:=cap.innertext
|
||||
else
|
||||
Result:='';
|
||||
end;
|
||||
|
||||
function THTMLTreeBuilder.GetPathFromEl(el: TJSHTmlElement): String;
|
||||
|
||||
|
||||
var
|
||||
Dir : TJSHTMLElement;
|
||||
|
||||
begin
|
||||
Result:=GetItemFileName(el);
|
||||
Dir:=GetParentDirEl(el);
|
||||
While Dir<>Nil do
|
||||
begin
|
||||
Result:=IncludeTrailingPathDelimiter(GetItemFileName(Dir))+Result;
|
||||
Dir:=GetParentDirEl(Dir);
|
||||
end;
|
||||
Result:=ExcludeTrailingPathDelimiter(RootDir)+Result
|
||||
end;
|
||||
|
||||
procedure THTMLTreeBuilder.HandleItemSelect(Event : TJSEvent);
|
||||
|
||||
var
|
||||
El : TJSHTMLElement;
|
||||
lList : TJSNodeList;
|
||||
I : integer;
|
||||
fType:TFileType;
|
||||
|
||||
begin
|
||||
// List element
|
||||
El:=TJSHTMLElement(event.targetElement.parentElement);
|
||||
lList:=FRootElement.querySelectorAll('li.zft-selected');
|
||||
for I:=0 to lList.length-1 do
|
||||
if El<>lList.item(I) then
|
||||
TJSHtmlElement(lList.item(I)).classList.remove('zft-selected');
|
||||
El.classList.add('zft-selected');
|
||||
if Assigned(FOnObjectSelect) then
|
||||
begin
|
||||
fType:=TFileType(StrToIntDef(el.dataset['fileType'],0));
|
||||
if (fType<>ftUnknown) then
|
||||
FOnObjectSelect(Self,GetPathFromEl(el),fType);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure THTMLTreeBuilder.SetIcons(AValue: TObjectTreeIconHTML);
|
||||
begin
|
||||
if FIcons=AValue then Exit;
|
||||
FIcons.Assign(AValue);
|
||||
end;
|
||||
|
||||
|
||||
|
||||
function THTMLTreeBuilder.AddItem(aParent: TJSHTMLElement; aCaption: String; aType: TFileType): TJSHTMLElement;
|
||||
|
||||
const
|
||||
FileTypeClassNames : Array[TFileType] of string = ('','zft-file','zft-directory','zft-symlink');
|
||||
|
||||
var
|
||||
CName : String;
|
||||
Icon,Span,Item,list : TJSHTMLELement;
|
||||
|
||||
begin
|
||||
if aParent=Nil then
|
||||
begin
|
||||
if FRootElement=Nil then
|
||||
begin
|
||||
FRootElement:=TJSHTMLElement(Document.createElement('ul'));
|
||||
FRootElement.className:='zft-tree-nested';
|
||||
FParentElement.appendChild(FRootElement);
|
||||
FRootDir:=IncludeTrailingPathDelimiter(aCaption)
|
||||
end;
|
||||
aParent:=FParentElement;
|
||||
end
|
||||
else
|
||||
begin
|
||||
if Not SameText(aParent.tagName,'li') then
|
||||
Raise EHTMLTreeBuilder.CreateFmt('Invalid parent item type: %s',[aParent.tagName]);
|
||||
if Not StartCollapsed then
|
||||
begin
|
||||
aParent.ClassList.remove('zft-collapsed');
|
||||
aParent.ClassList.add('zft-expanded');
|
||||
end;
|
||||
end;
|
||||
List:=TJSHTMLELement(aParent.querySelector('ul.zft-tree-nested'));
|
||||
if List=Nil then
|
||||
begin
|
||||
List:=TJSHTMLElement(Document.createElement('ul'));
|
||||
List.className:='zft-tree-nested';
|
||||
aParent.appendChild(List);
|
||||
end;
|
||||
Item:=TJSHTMLElement(Document.createElement('li'));
|
||||
CName:='zft-tree-item '+FileTypeClassNames[aType];
|
||||
if aType=ftDirectory then
|
||||
cName:=CName+' zft-collapsed';
|
||||
Item.className:=CName;
|
||||
Item.dataset['fileType']:=IntToStr(Ord(aType));
|
||||
Icon:=TJSHTMLElement(Document.createElement('span'));
|
||||
Case aType of
|
||||
ftDirectory: Icon.InnerHTML:=Icons.Directory;
|
||||
ftFile: Icon.InnerHTML:=Icons.NormalFile;
|
||||
ftSymLink: Icon.InnerHTML:=Icons.SymLink;
|
||||
end;
|
||||
Item.appendChild(icon);
|
||||
Span:=TJSHTMLElement(Document.createElement('span'));
|
||||
Span.InnerText:=aCaption;
|
||||
Span.className:='zft-tree-item-caption' ;
|
||||
Span.addEventListener('dblclick',@HandleItemCollapse);
|
||||
Span.addEventListener('click',@HandleItemSelect);
|
||||
Item.appendChild(Span);
|
||||
List.AppendChild(Item);
|
||||
Result:=Item;
|
||||
end;
|
||||
|
||||
function THTMLTreeBuilder.FindObjectItem(aID: Integer): TJSHTMLElement;
|
||||
begin
|
||||
Result:=TJSHTMLElement(ParentElement.querySelector('li[data-object-id="'+IntToStr(aID)+'"]'));
|
||||
end;
|
||||
|
||||
procedure THTMLTreeBuilder.Clear;
|
||||
begin
|
||||
if Assigned(FParentElement) then
|
||||
FParentElement.innerHTML:='';
|
||||
FRootElement:=Nil;
|
||||
end;
|
||||
|
||||
{ THTMLZenFSTree }
|
||||
|
||||
{ THTMLZenFSTree }
|
||||
|
||||
function THTMLZenFSTree.GetParentElement: TJSHTMLElement;
|
||||
begin
|
||||
Result:=FBuilder.ParentElement;
|
||||
end;
|
||||
|
||||
|
||||
function THTMLZenFSTree.GetOnObjectSelected: TFileSelectedEvent;
|
||||
begin
|
||||
Result:=FBuilder.OnFileSelected
|
||||
end;
|
||||
|
||||
function THTMLZenFSTree.GetIconHtml: TObjectTreeIconHTML;
|
||||
begin
|
||||
Result:=FBuilder.Icons;
|
||||
end;
|
||||
|
||||
function THTMLZenFSTree.GetParentElementID: String;
|
||||
begin
|
||||
if Assigned(ParentElement) then
|
||||
Result:=ParentElement.id
|
||||
else
|
||||
Result:='';
|
||||
end;
|
||||
|
||||
procedure THTMLZenFSTree.HandleRefresh(aEvent: TJSEvent);
|
||||
var
|
||||
lRoot: String;
|
||||
|
||||
begin
|
||||
If Assigned(FOnRefresh) then
|
||||
FOnRefresh(Self)
|
||||
else
|
||||
begin
|
||||
lRoot:=RootDir;
|
||||
Clear;
|
||||
ShowDir(lRoot);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure THTMLZenFSTree.SetCaption(AValue: String);
|
||||
begin
|
||||
if FCaption=AValue then Exit;
|
||||
FCaption:=AValue;
|
||||
if Assigned(FCaption) then
|
||||
RenderCaption(FCaptionElement);
|
||||
end;
|
||||
|
||||
procedure THTMLZenFSTree.SetIconHTML(AValue: TObjectTreeIconHTML);
|
||||
begin
|
||||
FBuilder.Icons.Assign(aValue);
|
||||
end;
|
||||
|
||||
procedure THTMLZenFSTree.SetOnObjectSelected(AValue: TFileSelectedEvent);
|
||||
begin
|
||||
FBuilder.OnFileSelected:=aValue;
|
||||
end;
|
||||
|
||||
procedure THTMLZenFSTree.SetOptions(AValue: TOTOptions);
|
||||
begin
|
||||
if FOptions=AValue then Exit;
|
||||
FOptions:=AValue;
|
||||
FBuilder.StartCollapsed:=(otStartCollapsed in FOptions);
|
||||
end;
|
||||
|
||||
procedure THTMLZenFSTree.RenderCaption(aEl : TJSHTMLELement);
|
||||
|
||||
begin
|
||||
aEL.InnerText:=Caption;
|
||||
end;
|
||||
|
||||
function THTMLZenFSTree.BuildWrapper(aParent : TJSHTMLElement) : TJSHTMLElement;
|
||||
|
||||
var
|
||||
RI,SC,DW,DC,DT : TJSHTMLElement;
|
||||
|
||||
begin
|
||||
aParent.InnerHTML:='';
|
||||
DC:=TJSHTMLElement(document.createElement('div'));
|
||||
DC.className:='zft-caption';
|
||||
SC:=TJSHTMLElement(document.createElement('span'));
|
||||
DC.AppendChild(SC);
|
||||
RI:=TJSHTMLElement(document.createElement('div'));
|
||||
RI.className:='zft-icon-btn';
|
||||
RI.InnerHTML:=Icons.Refresh;
|
||||
RI.AddEventListener('click',@HandleRefresh);
|
||||
DC.AppendChild(RI);
|
||||
aParent.AppendChild(DC);
|
||||
FCaptionElement:=SC;
|
||||
if Not (otShowCaption in Options) then
|
||||
DC.classList.Add('zft-hidden');
|
||||
RenderCaption(SC);
|
||||
DT:=TJSHTMLElement(document.createElement('div'));
|
||||
DT.className:='zft-tree';
|
||||
if MaxHeight<>'' then
|
||||
begin
|
||||
DT.style.setProperty('max-height',MaxHeight);
|
||||
DT.style.setProperty('overflow','scroll');
|
||||
end;
|
||||
aParent.AppendChild(DT);
|
||||
Result:=DT;
|
||||
end;
|
||||
|
||||
procedure THTMLZenFSTree.SetParentElement(AValue: TJSHTMLElement);
|
||||
begin
|
||||
FParentElement:=aValue;
|
||||
FBuilder.ParentElement:=BuildWrapper(FParentElement);
|
||||
end;
|
||||
|
||||
procedure THTMLZenFSTree.SetParentElementID(AValue: String);
|
||||
|
||||
var
|
||||
lParent : TJSHTMlelement;
|
||||
|
||||
begin
|
||||
lParent:=TJSHTMlelement(Document.getElementById(aValue));
|
||||
if lParent=Nil then
|
||||
Raise EHTMLTreeBuilder.CreateFmt('Unknown element id: "%s"',[aValue]);
|
||||
ParentElement:=lParent;
|
||||
end;
|
||||
|
||||
function THTMLZenFSTree.CreateBuilder : THTMLTreeBuilder;
|
||||
|
||||
begin
|
||||
Result:=THTMLTreeBuilder.Create(Self);
|
||||
end;
|
||||
|
||||
constructor THTMLZenFSTree.Create(aOwner: TComponent);
|
||||
|
||||
begin
|
||||
inherited Create(aOwner);
|
||||
FBuilder:=CreateBuilder;
|
||||
FOptions:=[otShowCaption];
|
||||
FCaption:='ZenFS File Tree';
|
||||
end;
|
||||
|
||||
destructor THTMLZenFSTree.Destroy;
|
||||
begin
|
||||
FreeAndNil(FBuilder);
|
||||
Inherited;
|
||||
end;
|
||||
|
||||
procedure THTMLZenFSTree.ShowDir(aParent: TJSHTMLElement; aDir: String);
|
||||
|
||||
var
|
||||
ZenDir : TZenFSDir;
|
||||
Enum : TZenFSDirEnumerator;
|
||||
DirEnt : TZenFSDirEnt;
|
||||
El: TJSHTMLElement;
|
||||
FT : TFileType;
|
||||
|
||||
begin
|
||||
ZenDir:=ZenFS.opendirSync(aDir);
|
||||
// buggy
|
||||
TJSObject(ZenDir)['_entries']:=undefined;
|
||||
Enum:=TZenFSDirEnumerator.Create(ZenDir);
|
||||
While Enum.MoveNext do
|
||||
begin
|
||||
Dirent:=Enum.Current;
|
||||
if (Dirent.isDirectory) then
|
||||
ft:=ftDirectory
|
||||
else if Dirent.isSymbolicLink then
|
||||
ft:=ftSymLink
|
||||
else
|
||||
ft:=ftFile;
|
||||
El:=FBuilder.AddItem(aParent,Dirent.path,ft);
|
||||
if ft=ftDirectory then
|
||||
ShowDir(El,aDir+'/'+Dirent.Path);
|
||||
end;
|
||||
Enum.Free;
|
||||
end;
|
||||
|
||||
procedure THTMLZenFSTree.ShowDir(aDir: String);
|
||||
|
||||
var
|
||||
El : TJSHTMLElement;
|
||||
|
||||
begin
|
||||
FRootDir:=aDir;
|
||||
EL:=FBuilder.AddItem(Nil,aDir,ftDirectory);
|
||||
ShowDir(El,aDir);
|
||||
end;
|
||||
|
||||
procedure THTMLZenFSTree.Clear;
|
||||
begin
|
||||
FRootDir:='';
|
||||
FBuilder.Clear;
|
||||
end;
|
||||
|
||||
|
||||
Function CreateFileTree(const aStartDir : string; aParent : TJSHTMLElement) : TJSHTMLELement;
|
||||
|
||||
begin
|
||||
|
||||
end;
|
||||
|
||||
const base64abc : Array of char = (
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
||||
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
||||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
|
||||
);
|
||||
|
||||
|
||||
const base64codes : Array of byte = (
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 0, 255, 255,
|
||||
255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255,
|
||||
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
|
||||
);
|
||||
|
||||
function getBase64Code(charCode : integer) : byte;
|
||||
|
||||
begin
|
||||
if (charCode >= Length(base64codes)) then
|
||||
Raise EConvertError.Create('Unable to parse base64 string.');
|
||||
Result:=base64codes[charCode];
|
||||
if (Result=255) then
|
||||
Raise EConvertError.Create('Unable to parse base64 string.');
|
||||
end;
|
||||
|
||||
function bytesToBase64(bytes : TJSUInt8Array) : String;
|
||||
|
||||
var
|
||||
l,I : integer;
|
||||
|
||||
begin
|
||||
result:='';
|
||||
l:=bytes.length;
|
||||
i:=2;
|
||||
While I<l do
|
||||
begin
|
||||
result := result+base64abc[bytes[i - 2] shr 2];
|
||||
result := result+base64abc[((bytes[i - 2] and $03) shl 4) or (bytes[i - 1] shr 4)];
|
||||
result := result+base64abc[((bytes[i - 1] and $0F) shl 2) or (bytes[i] shr 6)];
|
||||
result := result+base64abc[bytes[i] and $3F];
|
||||
inc(I,3);
|
||||
end;
|
||||
if (i=l+1) then
|
||||
begin
|
||||
result := result+base64abc[bytes[i - 2] shr 2];
|
||||
result := result+base64abc[(bytes[i - 2] and $03) shl 4];
|
||||
result := result+'==';
|
||||
end;
|
||||
if (i = l) then
|
||||
begin
|
||||
result := result+base64abc[bytes[i - 2] shr 2];
|
||||
result := result+base64abc[((bytes[i - 2] and $03) shl 4) or (bytes[i - 1] shr 4)];
|
||||
result := result+base64abc[(bytes[i - 1] and $0F) shl 2];
|
||||
result := result+'=';
|
||||
end;
|
||||
end;
|
||||
|
||||
function base64ToBytes(str : string) : TJSuint8array;
|
||||
|
||||
var
|
||||
Buffer,Len,MissingOctets, Index,I,j : integer;
|
||||
S : TJSString;
|
||||
Res : TJSUint8Array;
|
||||
|
||||
begin
|
||||
Len:=Length(str);
|
||||
if ((len mod 4) <> 0) then
|
||||
Raise EConvertError.Create('Unable to parse base64 string');
|
||||
Index:=Pos('=',str);
|
||||
if (index=0) or (Index < Len-2) then
|
||||
Raise EConvertError.Create('Unable to parse base64 string');
|
||||
MissingOctets:=0;
|
||||
if Str[Len]='=' then
|
||||
MissingOctets:=1;
|
||||
if Str[Len-1]='=' then
|
||||
MissingOctets:=2;
|
||||
Res:=TJSUint8Array.New(3 * (Len div 4));
|
||||
i:=0;
|
||||
J:=0;
|
||||
S:=TJSString(Str);
|
||||
While I<Len do
|
||||
begin
|
||||
buffer:=(getBase64Code(S.charCodeAt(i) shl 18)) or
|
||||
(getBase64Code(S.charCodeAt(i) shl 12)) or
|
||||
(getBase64Code(S.charCodeAt(i + 2) shl 6)) or
|
||||
getBase64Code(S.charCodeAt(i + 3));
|
||||
res[j]:=buffer shr 16;
|
||||
res[j + 1]:=(buffer shr 8) and $FF;
|
||||
res[j + 2]:=buffer and $FF;
|
||||
Inc(I,4);
|
||||
Inc(J,3);
|
||||
end;
|
||||
if MissingOctets=0 then
|
||||
Result:=res
|
||||
else
|
||||
Result:=res.subarray(0,res.length-missingOctets);
|
||||
end;
|
||||
|
||||
|
||||
var
|
||||
Encoder : TJSTextEncoder;
|
||||
Decoder : TJSTextDecoder;
|
||||
|
||||
function base64encode(str: string) : string;
|
||||
begin
|
||||
Result:=bytesToBase64(encoder.encode(str));
|
||||
end;
|
||||
|
||||
function base64decode(str: string) : string;
|
||||
begin
|
||||
Result:=decoder.decode(base64ToBytes(str));
|
||||
end;
|
||||
|
||||
function uint8ArrayToDataURL(aBuffer: TJSUint8Array; aMimeType : String) : String;
|
||||
var
|
||||
b2,Base64 : String;
|
||||
|
||||
begin
|
||||
asm
|
||||
Base64=btoa(String.fromCharCode.apply(null,aBuffer));
|
||||
end;
|
||||
B2:=bytesToBase64(aBuffer);
|
||||
if Base64<>B2 then
|
||||
Writeln('Differs');
|
||||
Result:='data:'+aMimeType+';base64,' + Base64;
|
||||
end;
|
||||
|
||||
Function CreateDataURL(aFileName : string; aMimeType : String) : String;
|
||||
|
||||
var
|
||||
nRead,fd : NativeInt;
|
||||
Stat : TZenFSStats;
|
||||
aSize : NativeInt;
|
||||
V : TJSDataView;
|
||||
opts : TZenFSReadSyncOptions;
|
||||
Buf : TJSUint8Array;
|
||||
|
||||
|
||||
begin
|
||||
fd:=Zenfs.openSync(aFileName,'r');
|
||||
Stat:=ZenFS.FStatSync(fd);
|
||||
aSize:=Stat.size;
|
||||
Buf:=TJSUint8Array.New(aSize);
|
||||
V:=TJSDataView.new(Buf.buffer);
|
||||
opts:=TZenFSReadSyncOptions.new;
|
||||
opts.offset:=0;
|
||||
opts.length:=aSize;
|
||||
nRead:=ZenFS.readSync(FD,V,Opts);
|
||||
Result:=Uint8ArrayToDataURL(Buf,aMimeType);
|
||||
end;
|
||||
|
||||
Function CreateDownLoadFromFile(const aFileName,aMimeType : string; aParent : TJSHTMLElement; const aLinkText : String) : TJSHTMLAnchorElement;
|
||||
|
||||
begin
|
||||
Result:=CreateDownLoadFromFile(aFileName,aMimeType,aParent,Document.createTextNode(aLinkText));
|
||||
end;
|
||||
|
||||
Function CreateDownLoadFromFile(const aFileName,aMimeType : string; aParent : TJSHTMLElement; const aLinkContent : TJSNode) : TJSHTMLAnchorElement;
|
||||
|
||||
begin
|
||||
Result:=TJSHTMLAnchorElement(Document.createElement('a'));
|
||||
Result.href:=CreateDataURL(aFileName,aMimetype);
|
||||
Result.Download:=ExtractFileName(aFileName);
|
||||
end;
|
||||
|
||||
initialization
|
||||
Encoder:=TJSTextEncoder.New;
|
||||
Decoder:=TJSTextDecoder.New;
|
||||
end.
|
||||
|
83
packages/zenfs/zfsstyles.css
Normal file
83
packages/zenfs/zfsstyles.css
Normal file
@ -0,0 +1,83 @@
|
||||
:root {
|
||||
--light-bg-color: rgb(237 238 242);
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Object Tree
|
||||
*
|
||||
*/
|
||||
|
||||
.zft-caption {
|
||||
padding: 1px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
background-color: rgb(237 238 242);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.zft-caption-lbl {
|
||||
flex-grow: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Object tree caption button */
|
||||
.zft-icon-btn {
|
||||
padding: 1px 10px;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
.zft-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
ul.zft-tree-nested {
|
||||
list-style-type: none;
|
||||
font-size: 10pt;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
li.zft-collapsed ul.zft-tree-nested {
|
||||
display: none
|
||||
}
|
||||
.zft-tree-item-caption {
|
||||
user-select: none;
|
||||
padding-right: 2em;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
li.zft-selected > .zft-tree-item-caption {
|
||||
background-color: blue;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.zft-tree-item-caption::before {
|
||||
color: black;
|
||||
display: inline-block;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
li.xzft-collapsed > span.zft-tree-item-caption::before {
|
||||
content: "\27A4";
|
||||
}
|
||||
|
||||
li.xzft-expanded > span.zft-tree-item-caption::before {
|
||||
content: "\2B9F";
|
||||
}
|
||||
|
||||
li.zft-collapsed::before {
|
||||
content: "\27A4";
|
||||
}
|
||||
|
||||
li.zft-expanded::before {
|
||||
content: "\2B9F";
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user