UNPKG

@e280/quay

Version:

File-browser and outliner UI for the web

116 lines 4.16 kB
import { collect } from "@e280/kv"; /** * Async hierarchy of nestable items. * - items can have multiple children. * - items can only have one parent. * - items can be orphans (bring your own concept of a "root"). */ export class HyTree { records; constructor(records) { this.records = records; } async has(id) { return this.records.has(id); } async get(id) { const record = await this.records.get(id); if (record) { const [parent, children] = record; return { parent, children }; } } async gets(...ids) { const records = await this.records.gets(...ids); return records.map(r => { if (!r) return undefined; const [parent, children] = r; return { parent, children }; }); } async require(id) { const record = await this.records.require(id); const [parent, children] = record; return { parent, children }; } async requires(...ids) { const records = await this.records.requires(...ids); return records.map(([parent, children]) => ({ parent, children })); } /** establish an id as a root with no parents, but ready to accept children */ async establishRoot(root) { return this.records.set(root, [null, []]); } /** give a parent some children */ async attach(parent, ...children) { children = [...new Set(children)]; const parentItem = await this.require(parent); const siblings = new Set(parentItem.children); const childItems = await this.gets(...children); const newcomers = []; for (const [index, id] of children.entries()) { const item = childItems.at(index); // child already in tree if (item && item.parent) throw new Error(`child already has parent`); // child not in tree yet, remember to save it else newcomers.push([id, [parent, []]]); // add to siblings set siblings.add(id); } // save all new children await this.records.sets(...newcomers); // save updated parent item await this.records.set(parent, [parentItem.parent, [...siblings]]); } /** detach this id from its parent in the hierarchy */ async detach(id) { const item = await this.get(id); if (!item) return undefined; if (item.parent) { const parentItem = await this.require(item.parent); const siblings = new Set(parentItem.children); siblings.delete(id); // save the parent item with removed child await this.records.set(item.parent, [parentItem.parent, [...siblings]]); // save the child with null parent await this.records.set(id, [null, item.children]); } } /** destroy all relations associated with this id, and all its descendants */ async destroy(id) { await this.detach(id); const tree = await collect(this.crawl(id)); const ids = tree.map(({ id }) => id); await this.records.del(...ids); } /** delete absolutely everything */ async clear() { await this.records.clear(); } /** iterate over all root items */ async *roots() { for await (const [id, [parent, children]] of this.records.entries()) if (parent === null) yield { id, item: { parent, children } }; } /** iterate over this id and all its descendants */ async *crawl(id, predicate = async () => true) { const todo = [[id, []]]; const seen = new Set(); while (todo.length) { const [id, path] = todo.shift(); if (seen.has(id) || !await predicate(id, path)) continue; seen.add(id); const item = await this.require(id); yield { id, item, path }; for (const childId of item.children) todo.push([childId, [...path, id]]); } } } //# sourceMappingURL=hy-tree.js.map