import { Injectable } from '@angular/core'; import { OpenSlidesComponent } from 'app/openslides.component'; import { Displayable } from 'app/shared/models/base/displayable'; import { Identifiable } from 'app/shared/models/base/identifiable'; /** * A representation of nodes in our tree. Saves the displayed name, the id, the element and children to build a full tree. */ export interface OSTreeNode { name: string; id: number; item: T; children?: OSTreeNode[]; } /** * This services handles all operations belonging to trees. It can build trees of plain lists (giving the weight * and parentId property) and traverse the trees in pre-order. */ @Injectable({ providedIn: 'root' }) export class TreeService extends OpenSlidesComponent { /** * Yes, a constructor. */ public constructor() { super(); } /** * Returns the weight casted to a number from a given model. * * @param item The model to get the weight from. * @param key * @returns the weight of the model */ private getAttributeAsNumber(item: T, key: keyof T): number { return (item[key]) as number; } /** * Build our representation of a tree node given the model and optional children * to append to this node. * * @param item The model to create a node of. * @param children Optional children to append to this node. * @returns The created node. */ private buildTreeNode(item: T, children?: OSTreeNode[]): OSTreeNode { return { name: item.getTitle(), id: item.id, item: item, children: children }; } /** * Builds a tree from the given items on the relations between items with weight and parentId * * @param items All items to traverse * @param weightKey The key giving access to the weight property * @param parentIdKey The key giving access to the parentId property * @returns An iterator for all items in the right order. */ public makeTree( items: T[], weightKey: keyof T, parentIdKey: keyof T ): OSTreeNode[] { // Sort items after their weight items.sort((a, b) => this.getAttributeAsNumber(a, weightKey) - this.getAttributeAsNumber(b, weightKey)); // Build a dict with all children (dict-value) to a specific // item id (dict-key). const children: { [parendId: number]: T[] } = {}; items.forEach(model => { if (model[parentIdKey]) { const parentId = this.getAttributeAsNumber(model, parentIdKey); if (children[parentId]) { children[parentId].push(model); } else { children[parentId] = [model]; } } }); // Recursive function that generates a nested list with all // items with there children const getChildren: (_models?: T[]) => OSTreeNode[] = _models => { if (!_models) { return; } const nodes: OSTreeNode[] = []; _models.forEach(_model => { nodes.push(this.buildTreeNode(_model, getChildren(children[_model.id]))); }); return nodes; }; // Generates the list of root items (with no parents) const parentItems = items.filter(model => !this.getAttributeAsNumber(model, parentIdKey)); return getChildren(parentItems); } /** * Traverses the given tree in pre order. * * @param tree The tree to traverse * @returns An iterator for all items in the right order. */ public *traverseTree(tree: OSTreeNode[]): Iterator { const nodesToVisit = tree.reverse(); while (nodesToVisit.length > 0) { const node = nodesToVisit.pop(); if (node.children) { node.children.reverse().forEach(n => { nodesToVisit.push(n); }); } yield node.item; } } /** * Traverses items in pre-order givem (implicit) by the weight and parentId. * * Just builds the tree with `makeTree` and get the iterator from `traverseTree`. * * @param items All items to traverse * @param weightKey The key giving access to the weight property * @param parentIdKey The key giving access to the parentId property * @returns An iterator for all items in the right order. */ public traverseItems( items: T[], weightKey: keyof T, parentIdKey: keyof T ): Iterator { const tree = this.makeTree(items, weightKey, parentIdKey); return this.traverseTree(tree); } }