aboutsummaryrefslogtreecommitdiff
path: root/vfs
diff options
context:
space:
mode:
Diffstat (limited to 'vfs')
-rw-r--r--vfs/example.js145
-rw-r--r--vfs/vfs.js202
2 files changed, 347 insertions, 0 deletions
diff --git a/vfs/example.js b/vfs/example.js
new file mode 100644
index 0000000..ea910f2
--- /dev/null
+++ b/vfs/example.js
@@ -0,0 +1,145 @@
+// SPDX-FileCopyrightText: 2021 M. Shulhan <ms@kilabit.info>
+// SPDX-License-Identifier: GPL-3.0-or-later
+import { WuiVfs } from "./vfs.js";
+const dummyfs = {
+ "/": {
+ name: "/",
+ path: "/",
+ is_dir: true,
+ content: "",
+ childs: [
+ {
+ name: "Dir 1",
+ path: "/Dir 1",
+ is_dir: true,
+ content: "",
+ childs: [
+ {
+ name: "File 1.1",
+ path: "/Dir 1/File 1.1",
+ is_dir: false,
+ content: "This is the content of File 1.1",
+ },
+ {
+ name: `File 1.2`,
+ path: "/Dir 1/File 1.2",
+ is_dir: false,
+ content: "This is the content of File 1.2",
+ },
+ ],
+ },
+ {
+ name: "Dir 2",
+ path: "/Dir 2",
+ is_dir: true,
+ content: "",
+ childs: [
+ {
+ name: "File 2.1",
+ path: "/Dir 2/File 2.1",
+ is_dir: false,
+ content: "This is the content of File 2.1",
+ },
+ {
+ name: "File 2.2",
+ path: "/Dir 2/File 2.2",
+ is_dir: false,
+ content: "This is the content of File 2.2",
+ },
+ ],
+ },
+ ],
+ },
+ "/Dir 1": {
+ name: "Dir 1",
+ path: "/Dir 1",
+ is_dir: true,
+ content: "",
+ childs: [
+ {
+ name: "File 1.1",
+ path: "/Dir 1/File 1.1",
+ is_dir: false,
+ content: "This is the content of File 1.1",
+ },
+ {
+ name: "File 1.2",
+ path: "/Dir 1/File 1.2",
+ is_dir: false,
+ content: "This is the content of File 1.2",
+ },
+ ],
+ },
+ "/Dir 2": {
+ name: "Dir 2",
+ path: "/Dir 2",
+ is_dir: true,
+ content: "",
+ childs: [
+ {
+ name: "File 2.1",
+ path: "/Dir 2/File 2.1",
+ is_dir: false,
+ content: "This is the content of File 2.1",
+ },
+ {
+ name: "File 2.2",
+ path: "/Dir 2/File 2.2",
+ is_dir: false,
+ content: "This is the content of File 2.2",
+ },
+ ],
+ },
+};
+async function main() {
+ const opts = {
+ id: "vfs",
+ open: open,
+ openNode: openNode,
+ };
+ const wuiVFS = new WuiVfs(opts);
+ wuiVFS.openDir("/");
+}
+async function open(path, isDir) {
+ console.log("Open:", path, isDir);
+ const res = {
+ code: 200,
+ message: "",
+ };
+ if (isDir) {
+ res.data = dummyfs[path];
+ return res;
+ }
+ res.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";
+ }
+ console.log("Open:", res);
+ return res;
+}
+async function openNode(node) {
+ return await open(node.path, node.is_dir);
+}
+main();
diff --git a/vfs/vfs.js b/vfs/vfs.js
new file mode 100644
index 0000000..440a8b5
--- /dev/null
+++ b/vfs/vfs.js
@@ -0,0 +1,202 @@
+// SPDX-FileCopyrightText: 2021 M. Shulhan <ms@kilabit.info>
+// SPDX-License-Identifier: GPL-3.0-or-later
+const CLASS_VFS_PATH = "wui_vfs_path";
+const CLASS_VFS_LIST = "wui_vfs_list";
+export class WuiVfs {
+ constructor(opts) {
+ this.opts = opts;
+ const el = document.getElementById(opts.id);
+ if (!el) {
+ console.error("WuiVfs: element id", opts.id, "not found");
+ return;
+ }
+ this.el = el;
+ this.com_path = new WuiVfsPath((path) => {
+ this.openDir(path);
+ });
+ this.el.appendChild(this.com_path.el);
+ this.com_list = new WuiVfsList((node) => {
+ this.openNode(node);
+ });
+ this.el.appendChild(this.com_list.el);
+ }
+ // filter the VFS list based on text value.
+ filter(text) {
+ this.com_list.filter(text);
+ }
+ // openNode is a handler that will be called when a node is clicked
+ // inside the WuiVfsList.
+ openNode(node) {
+ if (node.is_dir) {
+ this.openDir(node.path);
+ }
+ else {
+ this.opts.openNode(node);
+ }
+ }
+ // openDir is a handler that will be called when a path is clicked
+ // inside the WuiVfsPath.
+ async openDir(path) {
+ const res = await this.opts.open(path, true);
+ if (res.code != 200) {
+ return;
+ }
+ this.set(res.data);
+ }
+ set(node) {
+ if (node.is_dir) {
+ this.com_path.open(node);
+ this.com_list.open(node);
+ }
+ }
+}
+class WuiVfsList {
+ constructor(onClick) {
+ this.onClick = onClick;
+ this.node = null;
+ this.el = document.createElement("div");
+ this.el.classList.add(CLASS_VFS_LIST);
+ this.el.style.borderWidth = "1px";
+ this.el.style.borderStyle = "solid";
+ this.el.style.borderColor = "silver";
+ }
+ // filter re-render the list by including only the node that have name
+ // match with "text".
+ filter(text) {
+ const regexp = new RegExp(text, "i");
+ for (const elChild of this.el.children) {
+ if (regexp.test(elChild.innerHTML)) {
+ elChild.removeAttribute("hidden");
+ }
+ else {
+ elChild.setAttribute("hidden", "true");
+ }
+ }
+ }
+ open(node) {
+ this.node = node;
+ this.el.innerHTML = "";
+ if (!this.node) {
+ return;
+ }
+ if (!this.node.childs) {
+ return;
+ }
+ for (const c of this.node.childs) {
+ const el = document.createElement("div");
+ el.style.padding = "1em";
+ el.style.cursor = "pointer";
+ el.setAttribute("tabindex", "0");
+ el.innerText = c.name;
+ if (c.is_dir) {
+ el.innerText += "/";
+ el.style.backgroundColor = "cornsilk";
+ }
+ el.onclick = () => {
+ this.onClick(c);
+ };
+ el.onkeydown = (ev) => {
+ if (ev.key !== "Enter") {
+ return true;
+ }
+ this.onClick(c);
+ this.el.focus();
+ return false;
+ };
+ el.onblur = () => {
+ this.onBlur(c, el);
+ };
+ el.onmouseout = () => {
+ this.onBlur(c, el);
+ };
+ el.onfocus = () => {
+ this.onFocus(el);
+ };
+ el.onmouseover = () => {
+ this.onFocus(el);
+ };
+ this.el.appendChild(el);
+ }
+ }
+ onBlur(c, el) {
+ if (c.is_dir) {
+ el.style.backgroundColor = "cornsilk";
+ }
+ else {
+ el.style.backgroundColor = "white";
+ }
+ }
+ onFocus(el) {
+ el.style.backgroundColor = "aliceblue";
+ }
+}
+class WuiVfsPath {
+ constructor(onClick) {
+ this.el = document.createElement("div");
+ this.el.classList.add(CLASS_VFS_PATH);
+ this.el.style.borderWidth = "1px";
+ this.el.style.borderStyle = "solid";
+ this.el.style.borderColor = "silver";
+ this.el.style.overflow = "auto";
+ this.el.style.padding = "10px 10px 20px 0px";
+ this.el.style.whiteSpace = "nowrap";
+ this.onClick = onClick;
+ }
+ open(node) {
+ this.el.innerHTML = "";
+ let paths = [];
+ if (node.path == "/") {
+ paths.push(node.path);
+ }
+ else {
+ paths = node.path.split("/");
+ }
+ paths.forEach((path, x) => {
+ let fullPath = "";
+ let p = "";
+ if (x == 0) {
+ p = "/";
+ fullPath = "/";
+ }
+ else {
+ p = path;
+ fullPath = paths.slice(0, x + 1).join("/");
+ }
+ const crumb = document.createElement("span");
+ crumb.style.padding = "1em";
+ crumb.style.cursor = "pointer";
+ crumb.setAttribute("tabindex", "0");
+ crumb.innerHTML = p;
+ crumb.onclick = () => {
+ this.onClick(fullPath);
+ };
+ crumb.onkeydown = (ev) => {
+ if (ev.key !== "Enter") {
+ return true;
+ }
+ this.onClick(fullPath);
+ this.el.focus();
+ return false;
+ };
+ crumb.onmouseout = () => {
+ this.onBlur(crumb);
+ };
+ crumb.onblur = () => {
+ this.onBlur(crumb);
+ };
+ crumb.onmouseover = () => {
+ this.onFocus(crumb);
+ };
+ crumb.onfocus = () => {
+ this.onFocus(crumb);
+ };
+ this.el.appendChild(crumb);
+ });
+ }
+ onBlur(crumb) {
+ crumb.style.backgroundColor = "white";
+ }
+ onFocus(crumb) {
+ crumb.style.backgroundColor = "aliceblue";
+ }
+}