UNPKG

@platform/state

Version:

A small, simple, strongly typed, [rx/observable] state-machine.

184 lines (183 loc) 6.4 kB
import { toNodeId } from '../common'; import { TreeIdentity as Identity } from '../TreeIdentity'; export class TreeQuery { constructor(args) { this.walkDown = (visit) => { let stopped = false; const walk = (args) => { if (!args.node || stopped) { return; } const { id, key, namespace } = Identity.parse(args.node.id); if (this.namespace && namespace !== this.namespace) { return; } let skipChildren = false; visit({ id, key, namespace, node: args.node, parent: args.parent, index: args.index, level: args.depth, stop: () => (stopped = true), skip: () => (skipChildren = true), }); if (stopped) { return; } let index = -1; if (!skipChildren) { for (const child of TreeQuery.children(args.node, undefined, { assign: false })) { index++; walk({ node: child, parent: args.node, index, depth: args.depth + 1, }); } } }; return walk({ node: this.root, depth: 0, index: -1 }); }; this.walkUp = (startAt, visit) => { let level = -1; const inner = (startAt, visit) => { const current = this.findById(toNodeId(startAt)); level++; if (current) { let stop = false; const parentNode = this.parent(current); const { id, key, namespace } = Identity.parse(current.id); const args = { id, key, namespace, node: current, parent: parentNode, get index() { const id = current ? current.id : ''; return !parentNode ? -1 : (parentNode.children || []).findIndex((node) => node.id === id); }, level, stop: () => (stop = true), }; visit(args); if (!stop && parentNode) { inner(args.parent, visit); } } }; return inner(startAt, visit); }; this.find = (match) => { let result; this.walkDown((e) => { if (match(e) === true) { result = e.node; e.stop(); } }); return result ? result : undefined; }; this.findById = (id) => { if (!id) { return undefined; } else { const target = Identity.parse(typeof id === 'string' ? id : id.id); return this.find((e) => { if (!target.namespace && e.key === target.key) { return true; } else { return e.key === target.key && e.namespace === target.namespace; } }); } }; this.parent = (node) => { if (!node) { return undefined; } if (typeof node !== 'object') { const id = node; node = this.findById(id); if (!node) { throw new Error(`Cannot find parent of '${id}' because that child node was not found.`); } } let result; const target = node; this.walkDown((e) => { if (TreeQuery.hasChild(e.node, target)) { result = e.node; e.stop(); } }); return result; }; this.ancestor = (node, match) => { let result; this.walkUp(node, (e) => { if (match(e)) { result = e.node; e.stop(); } }); return result; }; this.depth = (node) => { let depth = -1; if (!node || !this.root) { return depth; } else { const id = toNodeId(node); this.walkDown((e) => { if (e.node.id === id) { depth = e.level; e.stop(); } }); return depth; } }; this.exists = (input) => { const node = typeof input === 'function' ? this.find(input) : this.findById(input); return Boolean(node); }; this.root = args.root; this.namespace = (args.namespace || '').trim(); } static create(args) { const input = args; const isQueryArgs = typeof input.root === 'object'; const root = (isQueryArgs ? input.root : args); const namespace = isQueryArgs ? input.namespace || '' : ''; return new TreeQuery({ root, namespace }); } static children(of, fn, options = {}) { options = (typeof fn === 'object' ? fn : options) || {}; const children = (!of ? [] : of.children || []); if (options.assign !== false && of && !of.children) { of.children = children; } if (typeof fn === 'function') { fn(children); } return children; } static hasChild(parent, child) { const nodes = TreeQuery.children(parent); const id = toNodeId(child); return nodes.some((node) => node.id === id); } static childAt(index, parent) { return TreeQuery.children(parent)[index]; } }