Merge pull request #4575 from GabrielInTheWorld/tree-sort-agenda
Implements filtering the `sorting-tree.component`
This commit is contained in:
commit
bf525cf852
@ -31,21 +31,23 @@ export interface OSTreeNode<T> extends TreeNodeWithoutItem {
|
|||||||
* Interface which defines the nodes for the sorting trees.
|
* Interface which defines the nodes for the sorting trees.
|
||||||
*
|
*
|
||||||
* Contains information like
|
* Contains information like
|
||||||
* name: The name of the node.
|
* item: The base item the node is created from.
|
||||||
* level: The level of the node. The higher, the deeper the level.
|
* level: The level of the node. The higher, the deeper the level.
|
||||||
* position: The position in the array of the node.
|
* position: The position in the array of the node.
|
||||||
* isExpanded: Boolean if the node is expanded.
|
* isExpanded: Boolean if the node is expanded.
|
||||||
* expandable: Boolean if the node is expandable.
|
* expandable: Boolean if the node is expandable.
|
||||||
* id: The id of the node.
|
* id: The id of the node.
|
||||||
|
* filtered: Optional boolean to check, if the node is filtered.
|
||||||
*/
|
*/
|
||||||
export interface FlatNode {
|
export interface FlatNode<T> {
|
||||||
name: string;
|
item: T;
|
||||||
level: number;
|
level: number;
|
||||||
position?: number;
|
position?: number;
|
||||||
isExpanded?: boolean;
|
isExpanded?: boolean;
|
||||||
isSeen: boolean;
|
isSeen: boolean;
|
||||||
expandable: boolean;
|
expandable: boolean;
|
||||||
id: number;
|
id: number;
|
||||||
|
filtered?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -98,11 +100,11 @@ export class TreeService {
|
|||||||
items: T[],
|
items: T[],
|
||||||
weightKey: keyof T,
|
weightKey: keyof T,
|
||||||
parentKey: keyof T
|
parentKey: keyof T
|
||||||
): FlatNode[] {
|
): FlatNode<T>[] {
|
||||||
const tree = this.makeTree(items, weightKey, parentKey);
|
const tree = this.makeTree(items, weightKey, parentKey);
|
||||||
const flatNodes: FlatNode[] = [];
|
const flatNodes: FlatNode<T>[] = [];
|
||||||
for (const node of tree) {
|
for (const node of tree) {
|
||||||
flatNodes.push(...this.makePartialFlatTree(node, 0, []));
|
flatNodes.push(...this.makePartialFlatTree(node, 0));
|
||||||
}
|
}
|
||||||
for (let i = 0; i < flatNodes.length; ++i) {
|
for (let i = 0; i < flatNodes.length; ++i) {
|
||||||
flatNodes[i].position = i;
|
flatNodes[i].position = i;
|
||||||
@ -117,7 +119,7 @@ export class TreeService {
|
|||||||
*
|
*
|
||||||
* @returns The tree with nested information.
|
* @returns The tree with nested information.
|
||||||
*/
|
*/
|
||||||
public makeTreeFromFlatTree(nodes: FlatNode[]): TreeIdNode[] {
|
public makeTreeFromFlatTree<T extends Identifiable & Displayable>(nodes: FlatNode<T>[]): TreeIdNode[] {
|
||||||
const basicTree: TreeIdNode[] = [];
|
const basicTree: TreeIdNode[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < nodes.length; ) {
|
for (let i = 0; i < nodes.length; ) {
|
||||||
@ -294,30 +296,29 @@ export class TreeService {
|
|||||||
/**
|
/**
|
||||||
* Helper function to go recursively through the children of given node.
|
* Helper function to go recursively through the children of given node.
|
||||||
*
|
*
|
||||||
* @param item
|
* @param item The current item from which the flat node will be created.
|
||||||
* @param level
|
* @param level The level the flat node will be.
|
||||||
|
* @param additionalTag Optional: A key of the items. If this parameter is set, the nodes will have a tag for filtering them.
|
||||||
*
|
*
|
||||||
* @returns An array containing the parent node with all its children.
|
* @returns An array containing the parent node with all its children.
|
||||||
*/
|
*/
|
||||||
private makePartialFlatTree<T extends Identifiable & Displayable>(
|
private makePartialFlatTree<T extends Identifiable & Displayable>(
|
||||||
item: OSTreeNode<T>,
|
item: OSTreeNode<T>,
|
||||||
level: number,
|
level: number
|
||||||
parents: FlatNode[]
|
): FlatNode<T>[] {
|
||||||
): FlatNode[] {
|
|
||||||
const children = item.children;
|
const children = item.children;
|
||||||
const node: FlatNode = {
|
const node: FlatNode<T> = {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
name: item.name,
|
item: item.item,
|
||||||
expandable: !!children,
|
expandable: !!children,
|
||||||
isExpanded: !!children,
|
isExpanded: !!children,
|
||||||
level: level,
|
level: level,
|
||||||
isSeen: true
|
isSeen: true
|
||||||
};
|
};
|
||||||
const flatNodes: FlatNode[] = [node];
|
const flatNodes: FlatNode<T>[] = [node];
|
||||||
if (children) {
|
if (children) {
|
||||||
parents.push(node);
|
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
flatNodes.push(...this.makePartialFlatTree(child, level + 1, parents));
|
flatNodes.push(...this.makePartialFlatTree(child, level + 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return flatNodes;
|
return flatNodes;
|
||||||
@ -333,9 +334,9 @@ export class TreeService {
|
|||||||
*
|
*
|
||||||
* @returns `OSTreeNodeWithOutItem`
|
* @returns `OSTreeNodeWithOutItem`
|
||||||
*/
|
*/
|
||||||
private buildBranchFromFlatTree(
|
private buildBranchFromFlatTree<T extends Identifiable & Displayable>(
|
||||||
node: FlatNode,
|
node: FlatNode<T>,
|
||||||
nodes: FlatNode[],
|
nodes: FlatNode<T>[],
|
||||||
length: number
|
length: number
|
||||||
): { node: TreeIdNode; length: number } {
|
): { node: TreeIdNode; length: number } {
|
||||||
const children = [];
|
const children = [];
|
||||||
|
@ -36,7 +36,9 @@
|
|||||||
chevron_right
|
chevron_right
|
||||||
</mat-icon>
|
</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
{{ node.name }}
|
<ng-container
|
||||||
|
[ngTemplateOutlet]="innerNode"
|
||||||
|
[ngTemplateOutletContext]="{item: node.item}"></ng-container>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
[style.margin-left]="placeholderLevel * 40 + 'px'"
|
[style.margin-left]="placeholderLevel * 40 + 'px'"
|
||||||
|
@ -1,43 +1,55 @@
|
|||||||
@import '../../../../assets/styles/drag.scss';
|
@import '../../../../assets/styles/drag.scss';
|
||||||
|
@import '~@angular/material/theming';
|
||||||
|
|
||||||
cdk-tree-node {
|
@mixin os-sorting-tree-style($theme) {
|
||||||
margin-bottom: 5px;
|
$background: map-get($theme, background);
|
||||||
|
|
||||||
|
cdk-tree-node {
|
||||||
|
margin: 3px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
div {
|
div {
|
||||||
|
display: inherit;
|
||||||
|
align-items: inherit;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
mat-icon {
|
mat-icon {
|
||||||
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overwrite the preview
|
// Overwrite the preview
|
||||||
.cdk-drag-preview {
|
.cdk-drag-preview {
|
||||||
box-shadow: none;
|
box-shadow: none !important;
|
||||||
background-color: unset;
|
background-color: unset !important;
|
||||||
|
border-radius: 6px !important;
|
||||||
|
|
||||||
div {
|
div {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background-color: white;
|
background-color: mat-color($background, background);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14),
|
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14),
|
||||||
0 3px 14px 2px rgba(0, 0, 0, 0.12);
|
0 3px 14px 2px rgba(0, 0, 0, 0.12);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Overwrite the placeholder
|
.mat-icon-button {
|
||||||
.cdk-drag-placeholder {
|
visibility: hidden !important;
|
||||||
opacity: 1;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite the placeholder
|
||||||
|
.cdk-drag-placeholder {
|
||||||
|
opacity: 1 !important;
|
||||||
background: #ccc;
|
background: #ccc;
|
||||||
border: dotted 3px #999;
|
border: dotted 3px #999;
|
||||||
min-height: 42px;
|
min-height: 42px;
|
||||||
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cdk-drop-list.cdk-drop-list-dragging .cdk-tree-node:not(.cdk-drag-placeholder) {
|
.cdk-drop-list.cdk-drop-list-dragging .cdk-tree-node:not(.cdk-drag-placeholder) {
|
||||||
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Component, OnInit, Input, OnDestroy, Output, EventEmitter } from '@angular/core';
|
import { Component, OnInit, Input, OnDestroy, Output, EventEmitter, ContentChild, TemplateRef } from '@angular/core';
|
||||||
|
|
||||||
import { FlatTreeControl } from '@angular/cdk/tree';
|
import { FlatTreeControl } from '@angular/cdk/tree';
|
||||||
import { ArrayDataSource } from '@angular/cdk/collections';
|
import { ArrayDataSource } from '@angular/cdk/collections';
|
||||||
import { CdkDragMove, CdkDragStart, CdkDragSortEvent } from '@angular/cdk/drag-drop';
|
import { CdkDragMove, CdkDragStart, CdkDragSortEvent } from '@angular/cdk/drag-drop';
|
||||||
|
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { Observable, Subscription } from 'rxjs';
|
||||||
import { auditTime } from 'rxjs/operators';
|
import { auditTime } from 'rxjs/operators';
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ enum Direction {
|
|||||||
* Interface which extends the `OSFlatNode`.
|
* Interface which extends the `OSFlatNode`.
|
||||||
* Containing further information like start- and next-position.
|
* Containing further information like start- and next-position.
|
||||||
*/
|
*/
|
||||||
interface ExFlatNode extends FlatNode {
|
interface ExFlatNode<T extends Identifiable & Displayable> extends FlatNode<T> {
|
||||||
startPosition: number;
|
startPosition: number;
|
||||||
nextPosition: number;
|
nextPosition: number;
|
||||||
}
|
}
|
||||||
@ -56,12 +56,12 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
/**
|
/**
|
||||||
* The data to build the tree
|
* The data to build the tree
|
||||||
*/
|
*/
|
||||||
public osTreeData: FlatNode[] = [];
|
public osTreeData: FlatNode<T>[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The tree control
|
* The tree control
|
||||||
*/
|
*/
|
||||||
public treeControl = new FlatTreeControl<FlatNode>(node => node.level, node => node.expandable);
|
public treeControl = new FlatTreeControl<FlatNode<T>>(node => node.level, node => node.expandable);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Source for the tree
|
* Source for the tree
|
||||||
@ -83,13 +83,23 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
* Node with calculated next information.
|
* Node with calculated next information.
|
||||||
* Containing information like the position, when the drag starts and where it is in the moment.
|
* Containing information like the position, when the drag starts and where it is in the moment.
|
||||||
*/
|
*/
|
||||||
public nextNode: ExFlatNode = null;
|
public nextNode: ExFlatNode<T> = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pointer for the move event
|
* Pointer for the move event
|
||||||
*/
|
*/
|
||||||
private pointer: DragEvent = null;
|
private pointer: DragEvent = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number, that holds the current visible nodes.
|
||||||
|
*/
|
||||||
|
private seenNodes: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function that will be used for filtering the nodes.
|
||||||
|
*/
|
||||||
|
private activeFilter: (node: T) => boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscription for the data store
|
* Subscription for the data store
|
||||||
*/
|
*/
|
||||||
@ -116,6 +126,8 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
/**
|
/**
|
||||||
* Setter to get all models from data store.
|
* Setter to get all models from data store.
|
||||||
* It will create or replace the existing subscription.
|
* It will create or replace the existing subscription.
|
||||||
|
*
|
||||||
|
* @param model Is the model the tree will be built of.
|
||||||
*/
|
*/
|
||||||
@Input()
|
@Input()
|
||||||
public set model(model: Observable<T[]>) {
|
public set model(model: Observable<T[]>) {
|
||||||
@ -126,12 +138,53 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
this.setSubscription();
|
this.setSubscription();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter to listen for state changes, expanded or collapsed.
|
||||||
|
*
|
||||||
|
* @param nextState Is an event emitter that emits a boolean whether the nodes should expand or not.
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
public set stateChange(nextState: EventEmitter<Boolean>) {
|
||||||
|
nextState.subscribe((state: boolean) => {
|
||||||
|
if (state) {
|
||||||
|
this.expandAll();
|
||||||
|
} else {
|
||||||
|
this.collapseAll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter to listen for filter change events.
|
||||||
|
*
|
||||||
|
* @param filter Is an event emitter that emits all active filters in an array.
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
public set filterChange(filter: EventEmitter<(node: T) => boolean>) {
|
||||||
|
filter.subscribe((value: (node: T) => boolean) => {
|
||||||
|
this.activeFilter = value;
|
||||||
|
this.checkActiveFilters();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EventEmitter to send info if changes has been made.
|
* EventEmitter to send info if changes has been made.
|
||||||
*/
|
*/
|
||||||
@Output()
|
@Output()
|
||||||
public hasChanged: EventEmitter<boolean> = new EventEmitter<boolean>();
|
public hasChanged: EventEmitter<boolean> = new EventEmitter<boolean>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EventEmitter to emit the info about the currently shown nodes.
|
||||||
|
*/
|
||||||
|
@Output()
|
||||||
|
public visibleNodes: EventEmitter<[number, number]> = new EventEmitter<[number, number]>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the template content.
|
||||||
|
*/
|
||||||
|
@ContentChild(TemplateRef)
|
||||||
|
public innerNode: TemplateRef<any>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
@ -158,7 +211,7 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
*
|
*
|
||||||
* @returns The parent node if available otherwise it returns null.
|
* @returns The parent node if available otherwise it returns null.
|
||||||
*/
|
*/
|
||||||
public getParentNode(node: FlatNode): FlatNode {
|
public getParentNode(node: FlatNode<T>): FlatNode<T> {
|
||||||
const nodeIndex = this.osTreeData.indexOf(node);
|
const nodeIndex = this.osTreeData.indexOf(node);
|
||||||
|
|
||||||
for (let i = nodeIndex - 1; i >= 0; --i) {
|
for (let i = nodeIndex - 1; i >= 0; --i) {
|
||||||
@ -178,11 +231,11 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
*
|
*
|
||||||
* @returns The node which is either the parent if not expanded or the next node.
|
* @returns The node which is either the parent if not expanded or the next node.
|
||||||
*/
|
*/
|
||||||
private getExpandedParentNode(node: FlatNode): FlatNode {
|
private getExpandedParentNode(node: FlatNode<T>): FlatNode<T> {
|
||||||
const allParents = this.getAllParents(node);
|
for (let i = node.position; i >= 0; --i) {
|
||||||
for (let i = allParents.length - 1; i >= 0; --i) {
|
const treeNode = this.osTreeData[i];
|
||||||
if (!allParents[i].isExpanded) {
|
if (treeNode.isSeen && !treeNode.filtered) {
|
||||||
return allParents[i];
|
return treeNode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return node;
|
return node;
|
||||||
@ -195,7 +248,7 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
*
|
*
|
||||||
* @returns An array containing its parent and the parents of its parent.
|
* @returns An array containing its parent and the parents of its parent.
|
||||||
*/
|
*/
|
||||||
private getAllParents(node: FlatNode): FlatNode[] {
|
private getAllParents(node: FlatNode<T>): FlatNode<T>[] {
|
||||||
return this._getAllParents(node, []);
|
return this._getAllParents(node, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +260,7 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
*
|
*
|
||||||
* @returns An array containing all parents that are in relation to the given node.
|
* @returns An array containing all parents that are in relation to the given node.
|
||||||
*/
|
*/
|
||||||
private _getAllParents(node: FlatNode, array: FlatNode[]): FlatNode[] {
|
private _getAllParents(node: FlatNode<T>, array: FlatNode<T>[]): FlatNode<T>[] {
|
||||||
const parent = this.getParentNode(node);
|
const parent = this.getParentNode(node);
|
||||||
if (parent) {
|
if (parent) {
|
||||||
array.push(parent);
|
array.push(parent);
|
||||||
@ -224,9 +277,9 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
*
|
*
|
||||||
* @returns An array that contains all the nearest children.
|
* @returns An array that contains all the nearest children.
|
||||||
*/
|
*/
|
||||||
public getChildNodes(node: FlatNode): FlatNode[] {
|
public getChildNodes(node: FlatNode<T>): FlatNode<T>[] {
|
||||||
const nodeIndex = this.osTreeData.indexOf(node);
|
const nodeIndex = this.osTreeData.indexOf(node);
|
||||||
const childNodes: FlatNode[] = [];
|
const childNodes: FlatNode<T>[] = [];
|
||||||
|
|
||||||
if (nodeIndex < this.osTreeData.length - 1) {
|
if (nodeIndex < this.osTreeData.length - 1) {
|
||||||
for (let i = nodeIndex + 1; i < this.osTreeData.length && this.osTreeData[i].level >= node.level + 1; ++i) {
|
for (let i = nodeIndex + 1; i < this.osTreeData.length && this.osTreeData[i].level >= node.level + 1; ++i) {
|
||||||
@ -239,6 +292,17 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
return childNodes;
|
return childNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to search for all nodes under the given node, that are not filtered.
|
||||||
|
*
|
||||||
|
* @param node is the parent whose visible children should be returned.
|
||||||
|
*
|
||||||
|
* @returns An array containing all nodes that are children and not filtered.
|
||||||
|
*/
|
||||||
|
private getUnfilteredChildNodes(node: FlatNode<T>): FlatNode<T>[] {
|
||||||
|
return this.getChildNodes(node).filter(child => !child.filtered);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to look for all nodes that are under the given node.
|
* Function to look for all nodes that are under the given node.
|
||||||
* This includes not only the nearest children, but also the children of the children.
|
* This includes not only the nearest children, but also the children of the children.
|
||||||
@ -247,7 +311,7 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
*
|
*
|
||||||
* @returns An array containing all the subnodes, inclusive the children of the children.
|
* @returns An array containing all the subnodes, inclusive the children of the children.
|
||||||
*/
|
*/
|
||||||
private getAllSubNodes(node: FlatNode): FlatNode[] {
|
private getAllSubNodes(node: FlatNode<T>): FlatNode<T>[] {
|
||||||
return this._getAllSubNodes(node, []);
|
return this._getAllSubNodes(node, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,7 +324,7 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
*
|
*
|
||||||
* @returns An array containing all subnodes, inclusive the children of the children.
|
* @returns An array containing all subnodes, inclusive the children of the children.
|
||||||
*/
|
*/
|
||||||
private _getAllSubNodes(node: FlatNode, array: FlatNode[]): FlatNode[] {
|
private _getAllSubNodes(node: FlatNode<T>, array: FlatNode<T>[]): FlatNode<T>[] {
|
||||||
array.push(node);
|
array.push(node);
|
||||||
for (const child of this.getChildNodes(node)) {
|
for (const child of this.getChildNodes(node)) {
|
||||||
this._getAllSubNodes(child, array);
|
this._getAllSubNodes(child, array);
|
||||||
@ -276,7 +340,7 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
*
|
*
|
||||||
* @returns The calculated position as number.
|
* @returns The calculated position as number.
|
||||||
*/
|
*/
|
||||||
private getPositionOnScreen(node: FlatNode): number {
|
private getPositionOnScreen(node: FlatNode<T>): number {
|
||||||
let currentPosition = this.osTreeData.length;
|
let currentPosition = this.osTreeData.length;
|
||||||
for (let i = this.osTreeData.length - 1; i >= 0; --i) {
|
for (let i = this.osTreeData.length - 1; i >= 0; --i) {
|
||||||
--currentPosition;
|
--currentPosition;
|
||||||
@ -297,7 +361,7 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
*
|
*
|
||||||
* @returns boolean if the node should render. Related to the state of the parent, if expanded or not.
|
* @returns boolean if the node should render. Related to the state of the parent, if expanded or not.
|
||||||
*/
|
*/
|
||||||
public shouldRender(node: FlatNode): boolean {
|
public shouldRender(node: FlatNode<T>): boolean {
|
||||||
return node.isSeen;
|
return node.isSeen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,7 +370,7 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
*
|
*
|
||||||
* @param node which is clicked.
|
* @param node which is clicked.
|
||||||
*/
|
*/
|
||||||
public handleClick(node: FlatNode): void {
|
public handleClick(node: FlatNode<T>): void {
|
||||||
node.isExpanded = !node.isExpanded;
|
node.isExpanded = !node.isExpanded;
|
||||||
if (node.isExpanded) {
|
if (node.isExpanded) {
|
||||||
for (const child of this.getChildNodes(node)) {
|
for (const child of this.getChildNodes(node)) {
|
||||||
@ -325,8 +389,8 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
*
|
*
|
||||||
* @param node is the node which should be shown again.
|
* @param node is the node which should be shown again.
|
||||||
*/
|
*/
|
||||||
private showChildren(node: FlatNode): void {
|
private showChildren(node: FlatNode<T>): void {
|
||||||
node.isSeen = true;
|
this.checkVisibility(node);
|
||||||
if (node.expandable && node.isExpanded) {
|
if (node.expandable && node.isExpanded) {
|
||||||
for (const child of this.getChildNodes(node)) {
|
for (const child of this.getChildNodes(node)) {
|
||||||
this.showChildren(child);
|
this.showChildren(child);
|
||||||
@ -338,14 +402,63 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
* Function to check the visibility of moved nodes after moving them.
|
* Function to check the visibility of moved nodes after moving them.
|
||||||
* `Warning: Side Effects`: This function works with side effects. The changed nodes won't be returned!
|
* `Warning: Side Effects`: This function works with side effects. The changed nodes won't be returned!
|
||||||
*
|
*
|
||||||
* @param nodes All affected nodes, that are either shown or not.
|
* @param node All affected nodes, that are either shown or not.
|
||||||
*/
|
*/
|
||||||
private checkVisibility(nodes: FlatNode[]): void {
|
private checkVisibility(node: FlatNode<T>): void {
|
||||||
if (this.getAllParents(nodes[0]).find(item => item.expandable && !item.isExpanded)) {
|
const shouldSee = !this.getAllParents(node).find(item => (item.expandable && !item.isExpanded) || !item.isSeen);
|
||||||
for (const child of nodes) {
|
node.isSeen = !node.filtered && shouldSee;
|
||||||
child.isSeen = false;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the next not filtered node.
|
||||||
|
* Necessary to get the next node even though it is not seen.
|
||||||
|
*
|
||||||
|
* @param node is the directly next neighbor of the moved node.
|
||||||
|
* @param direction define in which way it runs.
|
||||||
|
*
|
||||||
|
* @returns The next node with parameter `isSeen = true`.
|
||||||
|
*/
|
||||||
|
private getNextVisibleNode(node: FlatNode<T>, direction: Direction.DOWNWARDS | Direction.UPWARDS): FlatNode<T> {
|
||||||
|
if (node) {
|
||||||
|
switch (direction) {
|
||||||
|
case Direction.DOWNWARDS:
|
||||||
|
for (let i = node.position; i < this.osTreeData.length; ++i) {
|
||||||
|
if (!this.osTreeData[i].filtered) {
|
||||||
|
return this.osTreeData[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Direction.UPWARDS:
|
||||||
|
for (let i = node.position; i >= 0; --i) {
|
||||||
|
if (!this.osTreeData[i].filtered) {
|
||||||
|
return this.osTreeData[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to get the last filtered child of a node.
|
||||||
|
* This is necessary to append moved nodes next to the last place of the corresponding parent.
|
||||||
|
*
|
||||||
|
* @param node is the node where it will start.
|
||||||
|
*
|
||||||
|
* @returns The node that is an filtered child or itself if there is no filtered child.
|
||||||
|
*/
|
||||||
|
private getTheLastInvisibleNode(node: FlatNode<T>): FlatNode<T> {
|
||||||
|
let result = node;
|
||||||
|
for (let i = node.position + 1; i < this.osTreeData.length && this.osTreeData[i].level >= node.level + 1; ++i) {
|
||||||
|
if (this.osTreeData[i].filtered) {
|
||||||
|
result = this.osTreeData[i];
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -391,7 +504,7 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
*/
|
*/
|
||||||
public startsDrag(event: CdkDragStart): void {
|
public startsDrag(event: CdkDragStart): void {
|
||||||
this.removeSubscription();
|
this.removeSubscription();
|
||||||
const draggedNode = <FlatNode>event.source.data;
|
const draggedNode = <FlatNode<T>>event.source.data;
|
||||||
this.placeholderLevel = draggedNode.level;
|
this.placeholderLevel = draggedNode.level;
|
||||||
this.nextNode = {
|
this.nextNode = {
|
||||||
...draggedNode,
|
...draggedNode,
|
||||||
@ -405,12 +518,11 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
*
|
*
|
||||||
* @param node Is the dropped node.
|
* @param node Is the dropped node.
|
||||||
*/
|
*/
|
||||||
public onDrop(node: FlatNode): void {
|
public onDrop(node: FlatNode<T>): void {
|
||||||
const moving = this.getDirection();
|
|
||||||
this.pointer = null;
|
this.pointer = null;
|
||||||
|
|
||||||
this.madeChanges(true);
|
this.madeChanges(true);
|
||||||
this.moveItemToTree(node, node.position, this.nextPosition, this.placeholderLevel, moving.verticalMove);
|
this.moveItemToTree(node, node.position, this.nextPosition, this.placeholderLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -455,7 +567,13 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
this.nextPosition = nextPosition;
|
this.nextPosition = nextPosition;
|
||||||
|
|
||||||
const corrector = direction.verticalMove === Direction.DOWNWARDS ? 0 : 1;
|
const corrector = direction.verticalMove === Direction.DOWNWARDS ? 0 : 1;
|
||||||
const possibleParent = this.osTreeData[nextPosition - corrector];
|
let possibleParent = this.osTreeData[nextPosition - corrector];
|
||||||
|
for (let i = 0; possibleParent && possibleParent.filtered; ++i) {
|
||||||
|
possibleParent = this.osTreeData[nextPosition - corrector - i];
|
||||||
|
}
|
||||||
|
if (possibleParent) {
|
||||||
|
this.nextPosition = this.getTheLastInvisibleNode(possibleParent).position + corrector;
|
||||||
|
}
|
||||||
switch (direction.horizontalMove) {
|
switch (direction.horizontalMove) {
|
||||||
case Direction.LEFT:
|
case Direction.LEFT:
|
||||||
if (this.nextNode.level > 0 || this.placeholderLevel > 0) {
|
if (this.nextNode.level > 0 || this.placeholderLevel > 0) {
|
||||||
@ -506,7 +624,7 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
*/
|
*/
|
||||||
private findNextIndex(
|
private findNextIndex(
|
||||||
steps: number,
|
steps: number,
|
||||||
node: ExFlatNode,
|
node: ExFlatNode<T>,
|
||||||
verticalMove: Direction.DOWNWARDS | Direction.UPWARDS | Direction.NOWAY
|
verticalMove: Direction.DOWNWARDS | Direction.UPWARDS | Direction.NOWAY
|
||||||
): number {
|
): number {
|
||||||
let currentPosition = this.osTreeData.length;
|
let currentPosition = this.osTreeData.length;
|
||||||
@ -519,7 +637,7 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (node.name === parent.name) {
|
if (node.item.getTitle() === parent.item.getTitle()) {
|
||||||
--i;
|
--i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -553,20 +671,23 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
* @param nextLevel The next level the node should have.
|
* @param nextLevel The next level the node should have.
|
||||||
* @param verticalMove The direction of the movement in the vertical way.
|
* @param verticalMove The direction of the movement in the vertical way.
|
||||||
*/
|
*/
|
||||||
private moveItemToTree(
|
private moveItemToTree(node: FlatNode<T>, previousIndex: number, nextIndex: number, nextLevel: number): void {
|
||||||
node: FlatNode,
|
let verticalMove: string;
|
||||||
previousIndex: number,
|
if (previousIndex < nextIndex) {
|
||||||
nextIndex: number,
|
verticalMove = Direction.DOWNWARDS;
|
||||||
nextLevel: number,
|
} else if (previousIndex > nextIndex) {
|
||||||
verticalMove: Direction.UPWARDS | Direction.DOWNWARDS | Direction.NOWAY
|
verticalMove = Direction.UPWARDS;
|
||||||
): void {
|
} else {
|
||||||
|
verticalMove = Direction.NOWAY;
|
||||||
|
}
|
||||||
|
|
||||||
// Get all affected nodes.
|
// Get all affected nodes.
|
||||||
const movedNodes = this.getAllSubNodes(node);
|
const movedNodes = this.getAllSubNodes(node);
|
||||||
const corrector = verticalMove === Direction.DOWNWARDS ? 0 : 1;
|
const corrector = verticalMove === Direction.DOWNWARDS ? 0 : 1;
|
||||||
const lastChildIndex = movedNodes[movedNodes.length - 1].position;
|
const lastChildIndex = movedNodes[movedNodes.length - 1].position;
|
||||||
|
|
||||||
// Get the neighbor above and below of the new index.
|
// Get the neighbor above and below of the new index.
|
||||||
const nextNeighborAbove = this.osTreeData[nextIndex - corrector];
|
const nextNeighborAbove = this.getNextVisibleNode(this.osTreeData[nextIndex - corrector], Direction.UPWARDS);
|
||||||
const nextNeighborBelow =
|
const nextNeighborBelow =
|
||||||
verticalMove !== Direction.NOWAY
|
verticalMove !== Direction.NOWAY
|
||||||
? this.osTreeData[nextIndex - corrector + 1]
|
? this.osTreeData[nextIndex - corrector + 1]
|
||||||
@ -579,12 +700,11 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
|
|
||||||
// Check if the node was a subnode.
|
// Check if the node was a subnode.
|
||||||
if (node.level > 0) {
|
if (node.level > 0) {
|
||||||
const previousNode = this.osTreeData[previousIndex - 1];
|
// const previousNode = this.osTreeData[previousIndex - 1];
|
||||||
const isMovedLowerLevel =
|
const previousNode = this.getNextVisibleNode(this.osTreeData[previousIndex - 1], Direction.UPWARDS);
|
||||||
previousIndex === nextIndex &&
|
const onlyChild = this.getChildNodes(previousNode).length === 1;
|
||||||
nextLevel <= previousNode.level &&
|
const isMovedLowerLevel = previousIndex === nextIndex && nextLevel <= previousNode.level && onlyChild;
|
||||||
this.getChildNodes(previousNode).length === 1;
|
const isMovedAway = previousIndex !== nextIndex && onlyChild;
|
||||||
const isMovedAway = previousIndex !== nextIndex && this.getChildNodes(previousNode).length === 1;
|
|
||||||
|
|
||||||
// Check if the previous parent will have no children anymore.
|
// Check if the previous parent will have no children anymore.
|
||||||
if (isMovedAway || isMovedLowerLevel) {
|
if (isMovedAway || isMovedLowerLevel) {
|
||||||
@ -595,32 +715,32 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
|
|
||||||
// Check if the node becomes a subnode.
|
// Check if the node becomes a subnode.
|
||||||
if (nextLevel > 0) {
|
if (nextLevel > 0) {
|
||||||
|
const noChildren = this.getUnfilteredChildNodes(nextNeighborAbove).length === 0;
|
||||||
// Check if the new parent has not have any children before.
|
// Check if the new parent has not have any children before.
|
||||||
if (nextNeighborAbove.level + 1 === nextLevel && this.getChildNodes(nextNeighborAbove).length === 0) {
|
if (nextNeighborAbove.level + 1 === nextLevel && noChildren) {
|
||||||
nextNeighborAbove.expandable = true;
|
nextNeighborAbove.expandable = true;
|
||||||
nextNeighborAbove.isExpanded =
|
nextNeighborAbove.isExpanded =
|
||||||
(!!this.getParentNode(nextNeighborAbove) && this.getParentNode(nextNeighborAbove).isExpanded) ||
|
(!!this.getParentNode(nextNeighborAbove) && this.getParentNode(nextNeighborAbove).isExpanded) ||
|
||||||
this.getChildNodes(nextNeighborAbove).length === 0
|
noChildren;
|
||||||
? true
|
|
||||||
: false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the neighbor below has a higher level than the moved node.
|
// Check if the neighbor below has a higher level than the moved node.
|
||||||
if (nextNeighborBelow && nextNeighborBelow.level === nextLevel + 1) {
|
if (nextNeighborBelow && nextNeighborBelow.level === nextLevel + 1 && !nextNeighborBelow.filtered) {
|
||||||
// Check if the new neighbor above has the same level like the moved node.
|
// Check if the new neighbor above has the same level like the moved node.
|
||||||
if (nextNeighborAbove.level === nextLevel) {
|
if (nextNeighborAbove.level === nextLevel) {
|
||||||
nextNeighborAbove.expandable = false;
|
nextNeighborAbove.expandable = false;
|
||||||
nextNeighborAbove.isExpanded = false;
|
nextNeighborAbove.isExpanded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the moved node to the new parent for the subnodes.
|
|
||||||
node.expandable = true;
|
node.expandable = true;
|
||||||
node.isExpanded = true;
|
node.isExpanded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the neighbor below has a level equals to two or more higher than the moved node.
|
// Check if the neighbor below has a level equals to two or more higher than the moved node.
|
||||||
if (nextNeighborBelow && nextNeighborBelow.level >= nextLevel + 2) {
|
if (
|
||||||
|
(nextNeighborBelow && nextNeighborBelow.level >= nextLevel + 2) ||
|
||||||
|
(nextNeighborBelow.level === nextLevel + 1 && nextNeighborBelow.filtered)
|
||||||
|
) {
|
||||||
let found = false;
|
let found = false;
|
||||||
for (let i = nextIndex + 1; i < this.osTreeData.length; ++i) {
|
for (let i = nextIndex + 1; i < this.osTreeData.length; ++i) {
|
||||||
if (this.osTreeData[i].level <= nextLevel && node !== this.osTreeData[i]) {
|
if (this.osTreeData[i].level <= nextLevel && node !== this.osTreeData[i]) {
|
||||||
@ -681,7 +801,9 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check the visibility to prevent seeing nodes that are actually unseen.
|
// Check the visibility to prevent seeing nodes that are actually unseen.
|
||||||
this.checkVisibility(movedNodes);
|
for (const child of movedNodes) {
|
||||||
|
this.checkVisibility(child);
|
||||||
|
}
|
||||||
|
|
||||||
// Set a new data source.
|
// Set a new data source.
|
||||||
this.dataSource = null;
|
this.dataSource = null;
|
||||||
@ -716,6 +838,7 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
this.madeChanges(false);
|
this.madeChanges(false);
|
||||||
this.modelSubscription = this._model.pipe(auditTime(10)).subscribe(values => {
|
this.modelSubscription = this._model.pipe(auditTime(10)).subscribe(values => {
|
||||||
this.osTreeData = this.treeService.makeFlatTree(values, this.weightKey, this.parentKey);
|
this.osTreeData = this.treeService.makeFlatTree(values, this.weightKey, this.parentKey);
|
||||||
|
this.checkActiveFilters();
|
||||||
this.dataSource = new ArrayDataSource(this.osTreeData);
|
this.dataSource = new ArrayDataSource(this.osTreeData);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -729,8 +852,71 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
|||||||
this.hasChanged.emit(hasChanged);
|
this.hasChanged.emit(hasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to expand all nodes.
|
||||||
|
*/
|
||||||
|
private expandAll(): void {
|
||||||
|
for (const child of this.osTreeData) {
|
||||||
|
this.checkVisibility(child);
|
||||||
|
if (child.isSeen && child.expandable) {
|
||||||
|
child.isExpanded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to collapse all parent nodes and make their children invisible.
|
||||||
|
*/
|
||||||
|
private collapseAll(): void {
|
||||||
|
for (const child of this.osTreeData) {
|
||||||
|
child.isExpanded = false;
|
||||||
|
if (child.level > 0) {
|
||||||
|
child.isSeen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function that iterates over all top level nodes and pass them to the next function
|
||||||
|
* to decide if they will be seen or filtered and whether they will be expandable.
|
||||||
|
*/
|
||||||
|
private checkActiveFilters(): void {
|
||||||
|
this.seenNodes = 0;
|
||||||
|
for (const node of this.osTreeData.filter(item => item.level === 0)) {
|
||||||
|
this.checkChildrenToBeFiltered(node);
|
||||||
|
}
|
||||||
|
this.visibleNodes.emit([this.seenNodes, this.osTreeData.length]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to check recursively the child nodes of a given node whether they will be filtered or if they should be seen.
|
||||||
|
* The result is necessary to decide whether the parent node is expandable or not.
|
||||||
|
*
|
||||||
|
* @param node is the inspected node.
|
||||||
|
* @param parent optional: If the node has a parent, it is necessary to see if this parent will be filtered or is seen.
|
||||||
|
*
|
||||||
|
* @returns A boolean which describes if the given node will be filtered.
|
||||||
|
*/
|
||||||
|
private checkChildrenToBeFiltered(node: FlatNode<T>, parent?: FlatNode<T>): boolean {
|
||||||
|
let result = false;
|
||||||
|
const willFiltered = this.activeFilter ? this.activeFilter(node.item) || (parent && parent.filtered) : false;
|
||||||
|
node.filtered = willFiltered;
|
||||||
|
const willSeen = !willFiltered && ((parent && parent.isSeen && parent.isExpanded) || !parent);
|
||||||
|
node.isSeen = willSeen;
|
||||||
|
if (willSeen) {
|
||||||
|
this.seenNodes += 1;
|
||||||
|
}
|
||||||
|
for (const child of this.getChildNodes(node)) {
|
||||||
|
if (!this.checkChildrenToBeFiltered(child, node)) {
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.expandable = result;
|
||||||
|
return willFiltered;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to check if a node has children.
|
* Function to check if a node has children.
|
||||||
*/
|
*/
|
||||||
public hasChild = (_: number, node: FlatNode) => node.expandable;
|
public hasChild = (_: number, node: FlatNode<T>) => node.expandable;
|
||||||
}
|
}
|
||||||
|
@ -7,13 +7,59 @@
|
|||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<div class="title-slot"><h2 translate>Sort agenda</h2></div>
|
<div class="title-slot"><h2 translate>Sort agenda</h2></div>
|
||||||
</os-head-bar>
|
</os-head-bar>
|
||||||
|
<div class="custom-table-header sort-header">
|
||||||
|
<div class="button-menu left">
|
||||||
|
<button mat-button (click)="onStateChange(true)">{{ 'Expand all' | translate }}</button>
|
||||||
|
<button mat-button (click)="onStateChange(false)">{{ 'Collapse all' | translate }}</button>
|
||||||
|
</div>
|
||||||
|
<div class="current-filters" *ngIf="hasActiveFilter">
|
||||||
|
<div><span translate>Active filters</span>: </div>
|
||||||
|
<div>
|
||||||
|
<button mat-button (click)="resetFilters()">
|
||||||
|
<mat-icon inline>cancel</mat-icon>
|
||||||
|
<span>{{ 'Visibility' | translate }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="button-menu right">
|
||||||
|
<button mat-button (click)="visibilityFilter.opened ? visibilityFilter.close() : visibilityFilter.open()">Filter</button>
|
||||||
|
<mat-drawer #visibilityFilter mode="over" position="end">
|
||||||
|
<section class="sort-drawer-content">
|
||||||
|
<button mat-button (click)="visibilityFilter.toggle()">
|
||||||
|
<mat-icon>keyboard_arrow_right</mat-icon>
|
||||||
|
</button>
|
||||||
|
<span class="sort-grid">
|
||||||
|
<div class="hint">{{ 'Visibility' | translate }}</div>
|
||||||
|
<div>
|
||||||
|
<mat-checkbox *ngFor="let option of filterOptions" [(ngModel)]="option.state" (change)="onFilterChange(option.name)">
|
||||||
|
<mat-icon matTooltip="{{ option.name | translate }}">{{ getIcon(option.name) }}</mat-icon> {{ option.name | translate }}
|
||||||
|
</mat-checkbox>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</section>
|
||||||
|
</mat-drawer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<mat-card>
|
<mat-card>
|
||||||
|
<div class="current-nodes">
|
||||||
|
<span translate>You are currently seeing {{ seenNodes[0] }} of {{ seenNodes[1] }} items.</span>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
</div>
|
||||||
<os-sorting-tree
|
<os-sorting-tree
|
||||||
#osSortedTree
|
#osSortedTree
|
||||||
(hasChanged)="receiveChanges($event)"
|
(hasChanged)="receiveChanges($event)"
|
||||||
|
(visibleNodes)="onChangeAmountOfItems($event)"
|
||||||
[model]="itemsObservable"
|
[model]="itemsObservable"
|
||||||
|
[stateChange]="changeState"
|
||||||
|
[filterChange]="changeFilter"
|
||||||
parentKey="parent_id"
|
parentKey="parent_id"
|
||||||
weightKey="weight"
|
weightKey="weight"
|
||||||
></os-sorting-tree>
|
>
|
||||||
|
<ng-template #innerNode class="sorting-tree-node" let-item="item">
|
||||||
|
<span class="sort-node-title">{{ item.getTitle() }}</span>
|
||||||
|
<span class="sort-node-icon">
|
||||||
|
{{ item.verboseType | translate }} <mat-icon>{{ getIcon(item.verboseType) }}</mat-icon>
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
</os-sorting-tree>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
.sort-header {
|
||||||
|
display: block;
|
||||||
|
padding: 0 8px;
|
||||||
|
width: auto;
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.button-menu {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-drawer {
|
||||||
|
min-width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-drawer-content {
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
.mat-button {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-grid {
|
||||||
|
display: inline-grid;
|
||||||
|
grid-template-columns: auto;
|
||||||
|
grid-template-rows: 20px 30px;
|
||||||
|
margin: 8px;
|
||||||
|
vertical-align: top;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
mat-checkbox:not(:last-child) {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-filters {
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
div {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-nodes {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-node-icon {
|
||||||
|
margin: 0 12px 0 auto;
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
import { Component, ViewChild } from '@angular/core';
|
import { Component, ViewChild, EventEmitter, OnInit } from '@angular/core';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { MatSnackBar } from '@angular/material';
|
import { MatSnackBar } from '@angular/material';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable, BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
|
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
|
||||||
import { BaseViewComponent } from '../../../base/base-view';
|
import { BaseViewComponent } from '../../../base/base-view';
|
||||||
@ -11,26 +11,64 @@ import { SortingTreeComponent } from 'app/shared/components/sorting-tree/sorting
|
|||||||
import { ViewItem } from '../../models/view-item';
|
import { ViewItem } from '../../models/view-item';
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
import { CanComponentDeactivate } from 'app/shared/utils/watch-sorting-tree.guard';
|
import { CanComponentDeactivate } from 'app/shared/utils/watch-sorting-tree.guard';
|
||||||
|
import { itemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sort view for the agenda.
|
* Sort view for the agenda.
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'os-agenda-sort',
|
selector: 'os-agenda-sort',
|
||||||
templateUrl: './agenda-sort.component.html'
|
templateUrl: './agenda-sort.component.html',
|
||||||
|
styleUrls: ['./agenda-sort.component.scss']
|
||||||
})
|
})
|
||||||
export class AgendaSortComponent extends BaseViewComponent implements CanComponentDeactivate {
|
export class AgendaSortComponent extends BaseViewComponent implements CanComponentDeactivate, OnInit {
|
||||||
/**
|
/**
|
||||||
* Reference to the view child
|
* Reference to the view child
|
||||||
*/
|
*/
|
||||||
@ViewChild('osSortedTree')
|
@ViewChild('osSortedTree')
|
||||||
public osSortTree: SortingTreeComponent<ViewItem>;
|
public osSortTree: SortingTreeComponent<ViewItem>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitter to emit if the nodes should expand or collapse.
|
||||||
|
*/
|
||||||
|
public readonly changeState: EventEmitter<Boolean> = new EventEmitter<Boolean>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitter who emits the filters to the sorting tree.
|
||||||
|
*/
|
||||||
|
public readonly changeFilter: EventEmitter<(item: ViewItem) => boolean> = new EventEmitter<
|
||||||
|
(item: ViewItem) => boolean
|
||||||
|
>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These are the available options for filtering the nodes.
|
||||||
|
* Adds the property `state` to identify if the option is marked as active.
|
||||||
|
* When reset the filters, the option `state` will be set to `false`.
|
||||||
|
*/
|
||||||
|
public filterOptions = itemVisibilityChoices.map(item => {
|
||||||
|
return { ...item, state: false };
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BehaviourSubject to get informed every time the filters change.
|
||||||
|
*/
|
||||||
|
private activeFilters: BehaviorSubject<string[]> = new BehaviorSubject<string[]>([]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Boolean to check if changes has been made.
|
* Boolean to check if changes has been made.
|
||||||
*/
|
*/
|
||||||
public hasChanged = false;
|
public hasChanged = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean to check if filters are active, so they could be removed.
|
||||||
|
*/
|
||||||
|
public hasActiveFilter = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array, that holds the number of visible nodes and amount of available nodes.
|
||||||
|
*/
|
||||||
|
public seenNodes: [number, number] = [0, 0];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All agendaItems sorted by their virtual weight {@link ViewItem.agendaListWeight}
|
* All agendaItems sorted by their virtual weight {@link ViewItem.agendaListWeight}
|
||||||
*/
|
*/
|
||||||
@ -55,6 +93,24 @@ export class AgendaSortComponent extends BaseViewComponent implements CanCompone
|
|||||||
this.itemsObservable = this.agendaRepo.getViewModelListObservable();
|
this.itemsObservable = this.agendaRepo.getViewModelListObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OnInit method
|
||||||
|
*/
|
||||||
|
public ngOnInit(): void {
|
||||||
|
/**
|
||||||
|
* Passes the active filters as an array to the subject.
|
||||||
|
*/
|
||||||
|
const filter = this.activeFilters.subscribe((value: string[]) => {
|
||||||
|
this.hasActiveFilter = value.length === 0 ? false : true;
|
||||||
|
this.changeFilter.emit(
|
||||||
|
(item: ViewItem): boolean => {
|
||||||
|
return !(value.includes(item.verboseType) || value.length === 0);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
this.subscriptions.push(filter);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to save the tree by click.
|
* Function to save the tree by click.
|
||||||
*/
|
*/
|
||||||
@ -82,6 +138,54 @@ export class AgendaSortComponent extends BaseViewComponent implements CanCompone
|
|||||||
this.hasChanged = hasChanged;
|
this.hasChanged = hasChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to receive the new number of visible nodes when the filter has changed.
|
||||||
|
*
|
||||||
|
* @param nextNumberOfSeenNodes is an array with two indices:
|
||||||
|
* The first gives the number of currently shown nodes.
|
||||||
|
* The second tells how many nodes available.
|
||||||
|
*/
|
||||||
|
public onChangeAmountOfItems(nextNumberOfSeenNodes: [number, number]): void {
|
||||||
|
this.seenNodes = nextNumberOfSeenNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to emit if the nodes should be expanded or collapsed.
|
||||||
|
*
|
||||||
|
* @param nextState Is the next state, expanded or collapsed, the nodes should be.
|
||||||
|
*/
|
||||||
|
public onStateChange(nextState: boolean): void {
|
||||||
|
this.changeState.emit(nextState);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to set the active filters to null.
|
||||||
|
*/
|
||||||
|
public resetFilters(): void {
|
||||||
|
for (const option of this.filterOptions) {
|
||||||
|
option.state = false;
|
||||||
|
}
|
||||||
|
this.activeFilters.next([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to emit the active filters.
|
||||||
|
* Filters will be stored in an array to prevent duplicated options.
|
||||||
|
* Furthermore if the option is already included in this array, then it will be deleted.
|
||||||
|
* This array will be emitted.
|
||||||
|
*
|
||||||
|
* @param filter Is the filter that was activated by the user.
|
||||||
|
*/
|
||||||
|
public onFilterChange(filter: string): void {
|
||||||
|
const value = this.activeFilters.value;
|
||||||
|
if (!value.includes(filter)) {
|
||||||
|
value.push(filter);
|
||||||
|
} else {
|
||||||
|
value.splice(value.indexOf(filter), 1);
|
||||||
|
}
|
||||||
|
this.activeFilters.next(value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to open a prompt dialog,
|
* Function to open a prompt dialog,
|
||||||
* so the user will be warned if he has made changes and not saved them.
|
* so the user will be warned if he has made changes and not saved them.
|
||||||
@ -96,4 +200,22 @@ export class AgendaSortComponent extends BaseViewComponent implements CanCompone
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function, that returns an icon depending on the given tag.
|
||||||
|
*
|
||||||
|
* @param tag of which the icon will be assigned to.
|
||||||
|
*
|
||||||
|
* @returns The icon it should be.
|
||||||
|
*/
|
||||||
|
public getIcon(type: string): string {
|
||||||
|
switch (type.toLowerCase()) {
|
||||||
|
case 'public item':
|
||||||
|
return 'public';
|
||||||
|
case 'internal item':
|
||||||
|
return 'visibility';
|
||||||
|
case 'hidden item':
|
||||||
|
return 'visibility_off';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,11 @@
|
|||||||
parentKey="sort_parent_id"
|
parentKey="sort_parent_id"
|
||||||
weightKey="weight"
|
weightKey="weight"
|
||||||
(hasChanged)="receiveChanges($event)"
|
(hasChanged)="receiveChanges($event)"
|
||||||
[model]="motionsObservable"></os-sorting-tree>
|
[model]="motionsObservable">
|
||||||
|
<ng-template #innerNode let-item="item">
|
||||||
|
<span>{{ item.getTitle() }}</span>
|
||||||
|
</ng-template>
|
||||||
|
</os-sorting-tree>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
|
||||||
<mat-menu #downloadMenu="matMenu">
|
<mat-menu #downloadMenu="matMenu">
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
@import './assets/styles/global-components-style.scss';
|
@import './assets/styles/global-components-style.scss';
|
||||||
@import './app/shared/components/projector-button/projector-button.component.scss';
|
@import './app/shared/components/projector-button/projector-button.component.scss';
|
||||||
@import './app/site/agenda/components/list-of-speakers/list-of-speakers.component.scss-theme.scss';
|
@import './app/site/agenda/components/list-of-speakers/list-of-speakers.component.scss-theme.scss';
|
||||||
|
@import './app/shared/components/sorting-tree/sorting-tree.component.scss';
|
||||||
|
|
||||||
/** fonts */
|
/** fonts */
|
||||||
@import './assets/styles/fonts.scss';
|
@import './assets/styles/fonts.scss';
|
||||||
@ -23,6 +24,7 @@
|
|||||||
@include os-components-style($theme);
|
@include os-components-style($theme);
|
||||||
@include os-projector-button-style($theme);
|
@include os-projector-button-style($theme);
|
||||||
@include os-list-of-speakers-style($theme);
|
@include os-list-of-speakers-style($theme);
|
||||||
|
@include os-sorting-tree-style($theme);
|
||||||
/** More components are added here */
|
/** More components are added here */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user