diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Makefile | 10 | ||||
| -rw-r--r-- | tsconfig.json | 12 | ||||
| -rw-r--r-- | vfs/example.html | 139 | ||||
| -rw-r--r-- | vfs/vfs.d.ts | 54 | ||||
| -rw-r--r-- | vfs/vfs.js | 173 | ||||
| -rw-r--r-- | vfs/vfs.ts | 238 |
7 files changed, 627 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..07e6e47 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/node_modules diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..83b9780 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +OUT_JS= \ + vfs/vfs.js + +.PHONY: all +all: $(OUT_JS) + tsc --outDir . + +.PHONY: watch +watch: + tsc --outDir . --watch diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..05d094d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "declaration": true, + "forceConsistentCasingInFileNames": true, + "inlineSourceMap": true, + "skipLibCheck": true, + "strict": true, + "target": "ES3" + }, + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Recommended" +} diff --git a/vfs/example.html b/vfs/example.html new file mode 100644 index 0000000..0fb1f82 --- /dev/null +++ b/vfs/example.html @@ -0,0 +1,139 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8" /> + <title>WUI - Virtual File System (vfs)</title> + </head> + <body onload="main()"> + <div id="vfs"></div> + + <script> + var exports = {} + </script> + <script src="vfs.js"></script> + <script> + let dummyfs = { + "/": { + name: "/", + path: "/", + is_dir: true, + childs: [ + { + name: "Dir 1", + path: "/Dir 1", + is_dir: true, + childs: [ + { + name: "File 1.1", + path: "/Dir 1/File 1.1", + }, + { + name: "File 1.2", + path: "/Dir 1/File 1.2", + }, + ], + }, + { + name: "Dir 2", + path: "/Dir 2", + is_dir: true, + childs: [ + { + name: "File 2.1", + path: "/Dir 2/File 2.1", + }, + { + name: "File 2.2", + path: "/Dir 2/File 2.2", + }, + ], + }, + ], + }, + "/Dir 1": { + name: "Dir 1", + path: "/Dir 1", + is_dir: true, + childs: [ + { + name: "File 1.1", + path: "/Dir 1/File 1.1", + }, + { + name: "File 1.2", + path: "/Dir 1/File 1.2", + }, + ], + }, + "/Dir 2": { + name: "Dir 2", + path: "/Dir 2", + is_dir: true, + childs: [ + { + name: "File 2.1", + path: "/Dir 2/File 2.1", + }, + { + name: "File 2.2", + path: "/Dir 2/File 2.2", + }, + ], + }, + } + + function main() { + let opts = { + id: "vfs", + is_editable: true, + ListNodes: doListNodes, + GetNode: doGetNode, + } + + let vfs = new Vfs(opts) + } + + function doListNodes() { + let res = { + code: 200, + data: dummyfs, + } + return res + } + + function doGetNode(path) { + let res = { + code: 200, + data: { + name: "", + path: path, + content: "", + }, + } + + switch (path) { + case "/Dir 1/File 1.1": + res.data.name = "File 1.1" + res.data.content = "This is the content of " + res.data.name + break + case "/Dir 1/File 1.2": + res.data.name = "File 1.2" + res.data.content = "This is the content of " + res.data.name + break + case "/Dir 2/File 2.1": + res.data.name = "File 2.1" + res.data.content = "This is the content of " + res.data.name + break + case "/Dir 2/File 2.2": + res.data.name = "File 2.1" + res.data.content = "This is the content of " + res.data.name + break + default: + res.code = 404 + res.message = "path not found" + } + return res + } + </script> + </body> +</html> diff --git a/vfs/vfs.d.ts b/vfs/vfs.d.ts new file mode 100644 index 0000000..eadc43e --- /dev/null +++ b/vfs/vfs.d.ts @@ -0,0 +1,54 @@ +export interface IVfsNode { + name: string; + path: string; + is_dir: boolean; + mod_time_epoch?: number; + mod_time_rfc3339?: string; + size?: number; + mode?: string; + childs?: IVfsNode[]; + content?: string; +} +export interface Response { + code: number; + message?: string; + data?: IVfsNode | IPathNode; +} +export interface VfsOptions { + id: string; + is_editable: boolean; + ListNodes: () => Response; + GetNode: (path: string) => Response; + UpdateNode: (node: Node) => Response; + DeleteNode: (node: Node) => Response; +} +export declare class Vfs { + opts: VfsOptions; + private el; + private comPath; + private comList; + private pathNode; + constructor(opts: VfsOptions); + onClickNode(this: Vfs, node: VfsNode): void; + onClickPath(this: Vfs, path: string): void; + open(node: VfsNode): void; +} +declare class VfsNode implements IVfsNode { + path: string; + name: string; + mod_time_epoch: number; + mod_time_rfc3339: string; + size: number; + mode: string; + is_dir: boolean; + childs: VfsNode[]; + el: HTMLElement; + constructor(opts: IVfsNode, onClick: NodeClickHandler); + onMouseOut(t: VfsNode): void; + onMouseOver(t: VfsNode): void; +} +declare type IPathNode = { + [key: string]: IVfsNode; +}; +declare type NodeClickHandler = (node: VfsNode) => void; +export {}; diff --git a/vfs/vfs.js b/vfs/vfs.js new file mode 100644 index 0000000..1c2242c --- /dev/null +++ b/vfs/vfs.js @@ -0,0 +1,173 @@ +"use strict"; +exports.__esModule = true; +exports.Vfs = void 0; +var Vfs = /** @class */ (function () { + function Vfs(opts) { + var _this = this; + this.opts = opts; + this.el = null; + this.pathNode = {}; + this.el = document.getElementById(opts.id); + if (!this.el) { + console.log("Vfs: element id", opts.id, "not found"); + return; + } + var res = this.opts.ListNodes(); + if (res.code != 200) { + console.log("Vfs: ListNodes: ", res.message); + return; + } + var resPathNode = res.data; + for (var key in resPathNode) { + var value = resPathNode[key]; + var node = new VfsNode(value, function (node) { + _this.onClickNode(node); + }); + this.pathNode[key] = node; + } + this.el.innerHTML = ""; + this.comPath = new VfsPath(function (path) { + _this.onClickPath(path); + }); + this.el.appendChild(this.comPath.el); + this.comList = new VfsList(); + this.el.appendChild(this.comList.el); + this.open(this.pathNode["/"]); + } + Vfs.prototype.onClickNode = function (node) { + if (!node.is_dir) { + var res = this.opts.GetNode(node.path); + console.log("GetNode: ", res); + return; + } + this.comPath.open(node); + this.comList.open(node); + }; + Vfs.prototype.onClickPath = function (path) { + var node = this.pathNode[path]; + if (!node) { + console.log("Vfs: onClickPath: invalid path: ", path); + return; + } + this.open(node); + }; + Vfs.prototype.open = function (node) { + this.comPath.open(node); + this.comList.open(node); + }; + return Vfs; +}()); +exports.Vfs = Vfs; +var VfsNode = /** @class */ (function () { + function VfsNode(opts, onClick) { + var _this = this; + this.path = opts.path || ""; + this.name = opts.name || ""; + this.mod_time_epoch = opts.mod_time_epoch || 0; + this.mod_time_rfc3339 = opts.mod_time_rfc3339 || ""; + this.size = opts.size || 0; + this.mode = opts.mode || ""; + this.is_dir = opts.is_dir || false; + this.childs = []; + if (opts.childs !== undefined) { + for (var _i = 0, _a = opts.childs; _i < _a.length; _i++) { + var c = _a[_i]; + this.childs.push(new VfsNode(c, onClick)); + } + } + this.el = document.createElement("div"); + this.el.style.padding = "1em"; + this.el.style.cursor = "pointer"; + this.el.innerHTML = this.name; + this.el.onclick = function (event) { + onClick(_this); + }; + this.el.onmouseout = function (event) { + _this.onMouseOut(_this); + }; + this.el.onmouseover = function (event) { + _this.onMouseOver(_this); + }; + } + VfsNode.prototype.onMouseOut = function (t) { + t.el.style.backgroundColor = "white"; + }; + VfsNode.prototype.onMouseOver = function (t) { + t.el.style.backgroundColor = "aliceblue"; + }; + return VfsNode; +}()); +var VfsList = /** @class */ (function () { + function VfsList() { + this.el = document.createElement("div"); + this.el.style.borderWidth = "1px"; + this.el.style.borderStyle = "solid"; + this.el.style.borderColor = "black"; + } + VfsList.prototype.open = function (node) { + this.el.innerHTML = ""; + if (node.childs === undefined) { + return; + } + for (var _i = 0, _a = node.childs; _i < _a.length; _i++) { + var c = _a[_i]; + this.el.appendChild(c.el); + } + }; + return VfsList; +}()); +var VfsPath = /** @class */ (function () { + function VfsPath(onClick) { + this.el = document.createElement("div"); + this.el.style.borderWidth = "1px"; + this.el.style.borderStyle = "solid"; + this.el.style.borderColor = "black"; + this.crumbs = []; + this.onClick = onClick; + } + VfsPath.prototype.open = function (node) { + var _this = this; + this.el.innerHTML = ""; + this.crumbs = []; + var paths = []; + if (node.path == "/") { + paths.push(node.path); + } + else { + paths = node.path.split("/"); + } + var _loop_1 = function (p) { + if (p == "") { + p = "/"; + } + var crumb = document.createElement("span"); + crumb.style.display = "inline-block"; + crumb.style.padding = "1em"; + crumb.style.cursor = "pointer"; + crumb.innerHTML = p; + crumb.onclick = function (event) { + _this.onClick(p); + }; + crumb.onmouseout = function (event) { + _this.onMouseOut(crumb, event); + }; + crumb.onmouseover = function (event) { + _this.onMouseOver(crumb, event); + }; + this_1.el.appendChild(crumb); + }; + var this_1 = this; + for (var _i = 0, paths_1 = paths; _i < paths_1.length; _i++) { + var p = paths_1[_i]; + _loop_1(p); + } + }; + VfsPath.prototype.onMouseOut = function (crumb, event) { + crumb.style.backgroundColor = "white"; + }; + VfsPath.prototype.onMouseOver = function (crumb, event) { + crumb.style.backgroundColor = "aliceblue"; + }; + return VfsPath; +}()); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmZzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsidmZzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQTZCQTtJQU1DLGFBQW1CLElBQWdCO1FBQW5DLGlCQWlDQztRQWpDa0IsU0FBSSxHQUFKLElBQUksQ0FBWTtRQUwzQixPQUFFLEdBQXVCLElBQUksQ0FBQTtRQUc3QixhQUFRLEdBQWEsRUFBRSxDQUFBO1FBRzlCLElBQUksQ0FBQyxFQUFFLEdBQUcsUUFBUSxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUE7UUFDMUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLEVBQUU7WUFDYixPQUFPLENBQUMsR0FBRyxDQUFDLGlCQUFpQixFQUFFLElBQUksQ0FBQyxFQUFFLEVBQUUsV0FBVyxDQUFDLENBQUE7WUFDcEQsT0FBTTtTQUNOO1FBRUQsSUFBSSxHQUFHLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQTtRQUMvQixJQUFJLEdBQUcsQ0FBQyxJQUFJLElBQUksR0FBRyxFQUFFO1lBQ3BCLE9BQU8sQ0FBQyxHQUFHLENBQUMsa0JBQWtCLEVBQUUsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFBO1lBQzVDLE9BQU07U0FDTjtRQUVELElBQUksV0FBVyxHQUFHLEdBQUcsQ0FBQyxJQUFpQixDQUFBO1FBQ3ZDLEtBQUssSUFBTSxHQUFHLElBQUksV0FBVyxFQUFFO1lBQzlCLElBQU0sS0FBSyxHQUFHLFdBQVcsQ0FBQyxHQUFHLENBQWEsQ0FBQTtZQUMxQyxJQUFNLElBQUksR0FBRyxJQUFJLE9BQU8sQ0FBQyxLQUFLLEVBQUUsVUFBQyxJQUFhO2dCQUM3QyxLQUFJLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFBO1lBQ3ZCLENBQUMsQ0FBQyxDQUFBO1lBQ0YsSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsR0FBRyxJQUFJLENBQUE7U0FDekI7UUFFRCxJQUFJLENBQUMsRUFBRSxDQUFD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
\ No newline at end of file diff --git a/vfs/vfs.ts b/vfs/vfs.ts new file mode 100644 index 0000000..7219c3a --- /dev/null +++ b/vfs/vfs.ts @@ -0,0 +1,238 @@ +export interface IVfsNode { + name: string + path: string + is_dir: boolean + + mod_time_epoch?: number + mod_time_rfc3339?: string + size?: number + mode?: string + childs?: IVfsNode[] + content?: string +} + +export interface Response { + code: number + message?: string + data?: IVfsNode | IPathNode +} + +export interface VfsOptions { + id: string + is_editable: boolean + + ListNodes: () => Response + GetNode: (path: string) => Response + UpdateNode: (node: Node) => Response + DeleteNode: (node: Node) => Response +} + +export class Vfs { + private el: HTMLElement | null = null + private comPath!: VfsPath + private comList!: VfsList + private pathNode: PathNode = {} + + constructor(public opts: VfsOptions) { + this.el = document.getElementById(opts.id) + if (!this.el) { + console.log("Vfs: element id", opts.id, "not found") + return + } + + let res = this.opts.ListNodes() + if (res.code != 200) { + console.log("Vfs: ListNodes: ", res.message) + return + } + + let resPathNode = res.data as IPathNode + for (const key in resPathNode) { + const value = resPathNode[key] as IVfsNode + const node = new VfsNode(value, (node: VfsNode) => { + this.onClickNode(node) + }) + this.pathNode[key] = node + } + + this.el.innerHTML = "" + + this.comPath = new VfsPath((path: string) => { + this.onClickPath(path) + }) + this.el.appendChild(this.comPath.el) + + this.comList = new VfsList() + this.el.appendChild(this.comList.el) + + this.open(this.pathNode["/"]) + } + + onClickNode(this: Vfs, node: VfsNode) { + if (!node.is_dir) { + const res = this.opts.GetNode(node.path) + console.log("GetNode: ", res) + return + } + this.comPath.open(node) + this.comList.open(node) + } + + onClickPath(this: Vfs, path: string) { + const node = this.pathNode[path] + if (!node) { + console.log("Vfs: onClickPath: invalid path: ", path) + return + } + this.open(node) + } + + open(node: VfsNode) { + this.comPath.open(node) + this.comList.open(node) + } +} + +class VfsNode implements IVfsNode { + path: string + name: string + mod_time_epoch: number + mod_time_rfc3339: string + size: number + mode: string + is_dir: boolean + childs: VfsNode[] + + el: HTMLElement + + constructor(opts: IVfsNode, onClick: NodeClickHandler) { + this.path = opts.path || "" + this.name = opts.name || "" + this.mod_time_epoch = opts.mod_time_epoch || 0 + this.mod_time_rfc3339 = opts.mod_time_rfc3339 || "" + this.size = opts.size || 0 + this.mode = opts.mode || "" + this.is_dir = opts.is_dir || false + + this.childs = [] + if (opts.childs !== undefined) { + for (let c of opts.childs) { + this.childs.push(new VfsNode(c, onClick)) + } + } + + this.el = document.createElement("div") + this.el.style.padding = "1em" + this.el.style.cursor = "pointer" + this.el.innerHTML = this.name + + this.el.onclick = (event) => { + onClick(this) + } + this.el.onmouseout = (event) => { + this.onMouseOut(this) + } + this.el.onmouseover = (event) => { + this.onMouseOver(this) + } + } + + onMouseOut(t: VfsNode) { + t.el.style.backgroundColor = "white" + } + onMouseOver(t: VfsNode) { + t.el.style.backgroundColor = "aliceblue" + } +} + +class VfsList { + el: HTMLElement + + constructor() { + this.el = document.createElement("div") + this.el.style.borderWidth = "1px" + this.el.style.borderStyle = "solid" + this.el.style.borderColor = "black" + } + + open(node: VfsNode) { + this.el.innerHTML = "" + + if (node.childs === undefined) { + return + } + + for (let c of node.childs) { + this.el.appendChild(c.el) + } + } +} + +class VfsPath { + el: HTMLElement + private crumbs: string[] + private onClick: PathClickHandler + + constructor(onClick: PathClickHandler) { + this.el = document.createElement("div") + this.el.style.borderWidth = "1px" + this.el.style.borderStyle = "solid" + this.el.style.borderColor = "black" + this.crumbs = [] + this.onClick = onClick + } + + open(node: VfsNode) { + this.el.innerHTML = "" + this.crumbs = [] + let paths = [] + + if (node.path == "/") { + paths.push(node.path) + } else { + paths = node.path.split("/") + } + + for (let p of paths) { + if (p == "") { + p = "/" + } + + let crumb = document.createElement("span") + crumb.style.display = "inline-block" + crumb.style.padding = "1em" + crumb.style.cursor = "pointer" + crumb.innerHTML = p + + crumb.onclick = (event) => { + this.onClick(p) + } + crumb.onmouseout = (event) => { + this.onMouseOut(crumb, event) + } + crumb.onmouseover = (event) => { + this.onMouseOver(crumb, event) + } + + this.el.appendChild(crumb) + } + } + + onMouseOut(crumb: HTMLElement, event: MouseEvent) { + crumb.style.backgroundColor = "white" + } + onMouseOver(crumb: HTMLElement, event: MouseEvent) { + crumb.style.backgroundColor = "aliceblue" + } +} + +type IPathNode = { + [key: string]: IVfsNode +} + +type PathNode = { + [key: string]: VfsNode +} + +type NodeClickHandler = (node: VfsNode) => void +type PathClickHandler = (path: string) => void |
