diff options
Diffstat (limited to 'vfs/vfs.ts')
| -rw-r--r-- | vfs/vfs.ts | 238 |
1 files changed, 238 insertions, 0 deletions
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 |
