OpenSlides/client/src/app/core/ui-services/tree.service.ts

365 lines
12 KiB
TypeScript
Raw Normal View History

2018-11-22 15:14:01 +01:00
import { Injectable } from '@angular/core';
import { Displayable } from 'app/site/base/displayable';
2018-11-22 15:14:01 +01:00
import { Identifiable } from 'app/shared/models/base/identifiable';
/**
* A basic representation of a tree node. This node does not stores any data.
2018-11-22 15:14:01 +01:00
*/
Replaces the old `angular2tree` with a custom drag&drop tree Calculates the direction of the moving. Finishes the moving of nodes in same level Adds some style Sets the padding dynamically Adds placeholder depends on the horizontal movement Set the placeholder at the correct place, so the user can see, where he will drop the moved node Finishes moving of nodes - Old parents change their option to expand. - New parents change their option to expand. - If the user moves a node between nodes with a higher level, the node will be moved to the next index with same or lower level. Fixes the visibility of moved node - If the new parent is not visible, the moved node will not be seen. If the user moves an expanded node, the new parent should expanded, too, if it's not already. Sending successfully data to the server - Sorting the items Handles moving nodes between parent and children - If the user moves a node between a parent and its children, the children will be relinked to the moved node as their new parent. Replaces the old `sorting-tree` to a new one - The new `sorted-tree` replaces the old `sorting-tree`. - The old package `angular-tree-component` was removed. - The user will only see the buttons to save or cancel his changes, if he made changes. - The buttons, that do not work currently, were removed. Adds a guard to check if the user made changes. - If the user made changes but he has not saved them, then there is a dialog that will prompt to ask for confirmation. Before cancelling the changes the user has to confirm this.
2019-02-22 12:04:36 +01:00
export interface TreeIdNode {
2018-11-22 15:14:01 +01:00
id: number;
Replaces the old `angular2tree` with a custom drag&drop tree Calculates the direction of the moving. Finishes the moving of nodes in same level Adds some style Sets the padding dynamically Adds placeholder depends on the horizontal movement Set the placeholder at the correct place, so the user can see, where he will drop the moved node Finishes moving of nodes - Old parents change their option to expand. - New parents change their option to expand. - If the user moves a node between nodes with a higher level, the node will be moved to the next index with same or lower level. Fixes the visibility of moved node - If the new parent is not visible, the moved node will not be seen. If the user moves an expanded node, the new parent should expanded, too, if it's not already. Sending successfully data to the server - Sorting the items Handles moving nodes between parent and children - If the user moves a node between a parent and its children, the children will be relinked to the moved node as their new parent. Replaces the old `sorting-tree` to a new one - The new `sorted-tree` replaces the old `sorting-tree`. - The old package `angular-tree-component` was removed. - The user will only see the buttons to save or cancel his changes, if he made changes. - The buttons, that do not work currently, were removed. Adds a guard to check if the user made changes. - If the user made changes but he has not saved them, then there is a dialog that will prompt to ask for confirmation. Before cancelling the changes the user has to confirm this.
2019-02-22 12:04:36 +01:00
children?: TreeIdNode[];
}
/**
* Extends the TreeIdNode with a name to display.
*/
export interface TreeNodeWithoutItem extends TreeIdNode {
name: string;
children?: TreeNodeWithoutItem[];
}
/**
* A representation of nodes with the item atached.
*/
Replaces the old `angular2tree` with a custom drag&drop tree Calculates the direction of the moving. Finishes the moving of nodes in same level Adds some style Sets the padding dynamically Adds placeholder depends on the horizontal movement Set the placeholder at the correct place, so the user can see, where he will drop the moved node Finishes moving of nodes - Old parents change their option to expand. - New parents change their option to expand. - If the user moves a node between nodes with a higher level, the node will be moved to the next index with same or lower level. Fixes the visibility of moved node - If the new parent is not visible, the moved node will not be seen. If the user moves an expanded node, the new parent should expanded, too, if it's not already. Sending successfully data to the server - Sorting the items Handles moving nodes between parent and children - If the user moves a node between a parent and its children, the children will be relinked to the moved node as their new parent. Replaces the old `sorting-tree` to a new one - The new `sorted-tree` replaces the old `sorting-tree`. - The old package `angular-tree-component` was removed. - The user will only see the buttons to save or cancel his changes, if he made changes. - The buttons, that do not work currently, were removed. Adds a guard to check if the user made changes. - If the user made changes but he has not saved them, then there is a dialog that will prompt to ask for confirmation. Before cancelling the changes the user has to confirm this.
2019-02-22 12:04:36 +01:00
export interface OSTreeNode<T> extends TreeNodeWithoutItem {
2018-11-22 15:14:01 +01:00
item: T;
children?: OSTreeNode<T>[];
}
Replaces the old `angular2tree` with a custom drag&drop tree Calculates the direction of the moving. Finishes the moving of nodes in same level Adds some style Sets the padding dynamically Adds placeholder depends on the horizontal movement Set the placeholder at the correct place, so the user can see, where he will drop the moved node Finishes moving of nodes - Old parents change their option to expand. - New parents change their option to expand. - If the user moves a node between nodes with a higher level, the node will be moved to the next index with same or lower level. Fixes the visibility of moved node - If the new parent is not visible, the moved node will not be seen. If the user moves an expanded node, the new parent should expanded, too, if it's not already. Sending successfully data to the server - Sorting the items Handles moving nodes between parent and children - If the user moves a node between a parent and its children, the children will be relinked to the moved node as their new parent. Replaces the old `sorting-tree` to a new one - The new `sorted-tree` replaces the old `sorting-tree`. - The old package `angular-tree-component` was removed. - The user will only see the buttons to save or cancel his changes, if he made changes. - The buttons, that do not work currently, were removed. Adds a guard to check if the user made changes. - If the user made changes but he has not saved them, then there is a dialog that will prompt to ask for confirmation. Before cancelling the changes the user has to confirm this.
2019-02-22 12:04:36 +01:00
/**
* Interface which defines the nodes for the sorting trees.
*
* Contains information like
* name: The name of the node.
* level: The level of the node. The higher, the deeper the level.
* position: The position in the array of the node.
* isExpanded: Boolean if the node is expanded.
* expandable: Boolean if the node is expandable.
* id: The id of the node.
*/
export interface FlatNode {
name: string;
level: number;
position?: number;
isExpanded?: boolean;
isSeen: boolean;
expandable: boolean;
id: number;
}
2018-11-22 15:14:01 +01:00
/**
* 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'
})
2019-02-08 17:24:32 +01:00
export class TreeService {
2018-11-22 15:14:01 +01:00
/**
* 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<T extends Identifiable & Displayable>(item: T, key: keyof T): number {
return (<any>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<T extends Identifiable & Displayable>(item: T, children?: OSTreeNode<T>[]): OSTreeNode<T> {
return {
name: item.getTitle(),
id: item.id,
item: item,
children: children
};
}
Replaces the old `angular2tree` with a custom drag&drop tree Calculates the direction of the moving. Finishes the moving of nodes in same level Adds some style Sets the padding dynamically Adds placeholder depends on the horizontal movement Set the placeholder at the correct place, so the user can see, where he will drop the moved node Finishes moving of nodes - Old parents change their option to expand. - New parents change their option to expand. - If the user moves a node between nodes with a higher level, the node will be moved to the next index with same or lower level. Fixes the visibility of moved node - If the new parent is not visible, the moved node will not be seen. If the user moves an expanded node, the new parent should expanded, too, if it's not already. Sending successfully data to the server - Sorting the items Handles moving nodes between parent and children - If the user moves a node between a parent and its children, the children will be relinked to the moved node as their new parent. Replaces the old `sorting-tree` to a new one - The new `sorted-tree` replaces the old `sorting-tree`. - The old package `angular-tree-component` was removed. - The user will only see the buttons to save or cancel his changes, if he made changes. - The buttons, that do not work currently, were removed. Adds a guard to check if the user made changes. - If the user made changes but he has not saved them, then there is a dialog that will prompt to ask for confirmation. Before cancelling the changes the user has to confirm this.
2019-02-22 12:04:36 +01:00
/**
* Function to build flat nodes from `OSTreeNode`s.
* Iterates recursively through the list of nodes.
*
* @param items
* @param weightKey
* @param parentKey
*
* @returns An array containing flat nodes.
*/
public makeFlatTree<T extends Identifiable & Displayable>(
items: T[],
weightKey: keyof T,
parentKey: keyof T
): FlatNode[] {
const tree = this.makeTree(items, weightKey, parentKey);
const flatNodes: FlatNode[] = [];
for (const node of tree) {
flatNodes.push(...this.makePartialFlatTree(node, 0, []));
}
for (let i = 0; i < flatNodes.length; ++i) {
flatNodes[i].position = i;
}
return flatNodes;
}
/**
* Function to convert a flat tree to a nested tree built from `OSTreeNodeWithOutItem`.
*
* @param nodes The array of flat nodes, which should be converted.
*
* @returns The tree with nested information.
*/
public makeTreeFromFlatTree(nodes: FlatNode[]): TreeIdNode[] {
const basicTree: TreeIdNode[] = [];
for (let i = 0; i < nodes.length; ) {
// build the next node inclusive its children
const nextNode = this.buildBranchFromFlatTree(nodes[i], nodes, 0);
// append this node to the tree
basicTree.push(nextNode.node);
// step to the next related item in the array
i += nextNode.length;
}
return basicTree;
}
2018-11-22 15:14:01 +01:00
/**
* 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<T extends Identifiable & Displayable>(
items: T[],
weightKey: keyof T,
parentIdKey: keyof T
): OSTreeNode<T>[] {
// 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<T>[] = _models => {
if (!_models) {
return;
}
const nodes: OSTreeNode<T>[] = [];
_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<T>(tree: OSTreeNode<T>[]): Iterator<T> {
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;
}
}
/**
* Removes `item` from the tree.
*
* @param tree The tree with items
* @returns The tree without items
*/
Replaces the old `angular2tree` with a custom drag&drop tree Calculates the direction of the moving. Finishes the moving of nodes in same level Adds some style Sets the padding dynamically Adds placeholder depends on the horizontal movement Set the placeholder at the correct place, so the user can see, where he will drop the moved node Finishes moving of nodes - Old parents change their option to expand. - New parents change their option to expand. - If the user moves a node between nodes with a higher level, the node will be moved to the next index with same or lower level. Fixes the visibility of moved node - If the new parent is not visible, the moved node will not be seen. If the user moves an expanded node, the new parent should expanded, too, if it's not already. Sending successfully data to the server - Sorting the items Handles moving nodes between parent and children - If the user moves a node between a parent and its children, the children will be relinked to the moved node as their new parent. Replaces the old `sorting-tree` to a new one - The new `sorted-tree` replaces the old `sorting-tree`. - The old package `angular-tree-component` was removed. - The user will only see the buttons to save or cancel his changes, if he made changes. - The buttons, that do not work currently, were removed. Adds a guard to check if the user made changes. - If the user made changes but he has not saved them, then there is a dialog that will prompt to ask for confirmation. Before cancelling the changes the user has to confirm this.
2019-02-22 12:04:36 +01:00
public stripTree<T>(tree: OSTreeNode<T>[]): TreeNodeWithoutItem[] {
return tree.map(node => {
Replaces the old `angular2tree` with a custom drag&drop tree Calculates the direction of the moving. Finishes the moving of nodes in same level Adds some style Sets the padding dynamically Adds placeholder depends on the horizontal movement Set the placeholder at the correct place, so the user can see, where he will drop the moved node Finishes moving of nodes - Old parents change their option to expand. - New parents change their option to expand. - If the user moves a node between nodes with a higher level, the node will be moved to the next index with same or lower level. Fixes the visibility of moved node - If the new parent is not visible, the moved node will not be seen. If the user moves an expanded node, the new parent should expanded, too, if it's not already. Sending successfully data to the server - Sorting the items Handles moving nodes between parent and children - If the user moves a node between a parent and its children, the children will be relinked to the moved node as their new parent. Replaces the old `sorting-tree` to a new one - The new `sorted-tree` replaces the old `sorting-tree`. - The old package `angular-tree-component` was removed. - The user will only see the buttons to save or cancel his changes, if he made changes. - The buttons, that do not work currently, were removed. Adds a guard to check if the user made changes. - If the user made changes but he has not saved them, then there is a dialog that will prompt to ask for confirmation. Before cancelling the changes the user has to confirm this.
2019-02-22 12:04:36 +01:00
const nodeWithoutItem: TreeNodeWithoutItem = {
name: node.name,
id: node.id
};
if (node.children) {
nodeWithoutItem.children = this.stripTree(node.children);
}
return nodeWithoutItem;
});
}
2018-11-22 15:14:01 +01:00
/**
* 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<T extends Identifiable & Displayable>(
items: T[],
weightKey: keyof T,
parentIdKey: keyof T
): Iterator<T> {
const tree = this.makeTree(items, weightKey, parentIdKey);
return this.traverseTree(tree);
}
2019-02-04 15:41:01 +01:00
/**
* Reduce a list of items to nodes independent from each other in a given
* branch of a tree
*
* @param branch the tree to traverse
* @param items the items to check
* @returns the selection of items that belong to different branches
*/
private getTopItemsFromBranch<T extends Identifiable & Displayable>(branch: OSTreeNode<T>, items: T[]): T[] {
const item = items.find(i => branch.item.id === i.id);
if (item) {
return [item];
} else if (!branch.children) {
return [];
} else {
return [].concat(...branch.children.map(child => this.getTopItemsFromBranch(child, items)));
}
}
/**
* Reduce a list of items to nodes independent from each other in a given tree
*
* @param tree the tree to traverse
* @param items the items to check
* @returns the selection of items that belong to different branches
*/
public getTopItemsFromTree<T extends Identifiable & Displayable>(tree: OSTreeNode<T>[], items: T[]): T[] {
let results: T[] = [];
tree.forEach(branch => {
const i = this.getTopItemsFromBranch(branch, items);
if (i.length) {
results = results.concat(i);
}
});
return results;
}
/**
* Return all items not being hierarchically dependant on the items in the input arrray
*
* @param tree
* @param items
* @returns all items that are neither in the input nor dependants of items in the input
*/
public getTreeWithoutSelection<T extends Identifiable & Displayable>(tree: OSTreeNode<T>[], items: T[]): T[] {
let result: T[] = [];
tree.forEach(branch => {
if (!items.find(i => i.id === branch.item.id)) {
result.push(branch.item);
if (branch.children) {
result = result.concat(this.getTreeWithoutSelection(branch.children, items));
}
}
});
return result;
}
Replaces the old `angular2tree` with a custom drag&drop tree Calculates the direction of the moving. Finishes the moving of nodes in same level Adds some style Sets the padding dynamically Adds placeholder depends on the horizontal movement Set the placeholder at the correct place, so the user can see, where he will drop the moved node Finishes moving of nodes - Old parents change their option to expand. - New parents change their option to expand. - If the user moves a node between nodes with a higher level, the node will be moved to the next index with same or lower level. Fixes the visibility of moved node - If the new parent is not visible, the moved node will not be seen. If the user moves an expanded node, the new parent should expanded, too, if it's not already. Sending successfully data to the server - Sorting the items Handles moving nodes between parent and children - If the user moves a node between a parent and its children, the children will be relinked to the moved node as their new parent. Replaces the old `sorting-tree` to a new one - The new `sorted-tree` replaces the old `sorting-tree`. - The old package `angular-tree-component` was removed. - The user will only see the buttons to save or cancel his changes, if he made changes. - The buttons, that do not work currently, were removed. Adds a guard to check if the user made changes. - If the user made changes but he has not saved them, then there is a dialog that will prompt to ask for confirmation. Before cancelling the changes the user has to confirm this.
2019-02-22 12:04:36 +01:00
/**
* Helper function to go recursively through the children of given node.
*
* @param item
* @param level
*
* @returns An array containing the parent node with all its children.
*/
private makePartialFlatTree<T extends Identifiable & Displayable>(
item: OSTreeNode<T>,
level: number,
parents: FlatNode[]
): FlatNode[] {
const children = item.children;
const node: FlatNode = {
id: item.id,
name: item.name,
expandable: !!children,
isExpanded: !!children,
level: level,
isSeen: true
};
const flatNodes: FlatNode[] = [node];
if (children) {
parents.push(node);
for (const child of children) {
flatNodes.push(...this.makePartialFlatTree(child, level + 1, parents));
}
}
return flatNodes;
}
/**
* Function, that returns a node containing information like id, name and children.
* Children only, if available.
*
* @param node The node which is converted.
* @param nodes The array with all nodes to convert.
* @param length The number of converted nodes related to the parent node.
*
* @returns `OSTreeNodeWithOutItem`
*/
private buildBranchFromFlatTree(
node: FlatNode,
nodes: FlatNode[],
length: number
): { node: TreeIdNode; length: number } {
const children = [];
// Begins at the position of the node in the array.
// Ends if the next node has the same or higher level than the given node.
for (let i = node.position + 1; !!nodes[i] && nodes[i].level >= node.level + 1; ++i) {
const nextNode = nodes[i];
// The next node is a child if the level is one higher than the given node.
if (nextNode.level === node.level + 1) {
// Makes the child nodes recursively.
const child = this.buildBranchFromFlatTree(nextNode, nodes, 0);
length += child.length;
children.push(child.node);
}
}
// Makes the node with child nodes.
const osNode: TreeIdNode = {
id: node.id,
children: children.length > 0 ? children : undefined
};
// Returns the built node and increase the length by one.
return { node: osNode, length: ++length };
}
2018-11-22 15:14:01 +01:00
}