| import { MatCommonModule } from '@angular/material/core'; | import { MatCommonModule } from '@angular/material/core'; | ||||
| import { MatInputModule } from '@angular/material/input'; | import { MatInputModule } from '@angular/material/input'; | ||||
| import { DynamicDatabase } from './create-rule/class/dynamic-database'; | import { DynamicDatabase } from './create-rule/class/dynamic-database'; | ||||
| import {MatSnackBarModule} from '@angular/material/snack-bar'; | |||||
| import {MatBottomSheetModule} from '@angular/material/bottom-sheet'; | |||||
| import { OptionsShetComponent } from './options-shet/options-shet.component'; | |||||
| @NgModule({ | @NgModule({ | ||||
| declarations: [ | declarations: [ | ||||
| CreateRuleComponent | |||||
| CreateRuleComponent, | |||||
| OptionsShetComponent | |||||
| ], | ], | ||||
| imports: [ | imports: [ | ||||
| MatBottomSheetModule, | |||||
| MatSnackBarModule, | |||||
| CommonModule, | CommonModule, | ||||
| MatCardModule, | MatCardModule, | ||||
| MatFormFieldModule, | MatFormFieldModule, |
| import { Injectable } from "@angular/core" | |||||
| import { RippleRef } from "@angular/material/core"; | |||||
| import { Injectable } from "@angular/core"; | |||||
| import { BehaviorSubject, Subject } from "rxjs"; | import { BehaviorSubject, Subject } from "rxjs"; | ||||
| import { map } from "rxjs/operators"; | |||||
| import { CreateRuleDto, Rule, RulesService } from "../../../../../api"; | import { CreateRuleDto, Rule, RulesService } from "../../../../../api"; | ||||
| import { environment } from "../../../../environments/environment"; | import { environment } from "../../../../environments/environment"; | ||||
| import { RuleDataService } from "../../../service/rule-data.service"; | import { RuleDataService } from "../../../service/rule-data.service"; | ||||
| import { RuleFlatNode } from "./rule-flat-node"; | |||||
| import { EventMessage } from "./event-message"; | |||||
| @Injectable({ providedIn: 'root' }) | @Injectable({ providedIn: 'root' }) | ||||
| dataChange = new BehaviorSubject<Rule[]>([]); | dataChange = new BehaviorSubject<Rule[]>([]); | ||||
| childAdded = new Subject<Rule>(); | childAdded = new Subject<Rule>(); | ||||
| onEvent = new Subject<EventMessage>() | |||||
| get data(): Rule[] { return this.dataChange.value; } | get data(): Rule[] { return this.dataChange.value; } | ||||
| get count(): number { return this.dataChange.value.length }; | get count(): number { return this.dataChange.value.length }; | ||||
| ) | ) | ||||
| ), | ), | ||||
| (err) => { | (err) => { | ||||
| //TODO add toaster | |||||
| this.onEvent.next( | |||||
| { | |||||
| event: 'Error', | |||||
| error: err, | |||||
| message: `Fehler beim abrufen der Regeln` | |||||
| } | |||||
| ); | |||||
| } | } | ||||
| } | } | ||||
| getById = (ruleId: string) => { | |||||
| const parentNode = this.getParentById(ruleId); | |||||
| if (parentNode) { | |||||
| return parentNode; | |||||
| } | |||||
| for (const rootRule of this.data) { | |||||
| const found = this.findChildById(rootRule, ruleId); | |||||
| if (found) { | |||||
| return found; | |||||
| } | |||||
| } | |||||
| return null; | |||||
| } | |||||
| findChildById = (parent: Rule, ruleId: string) => { | |||||
| if (!parent.subRule) { | |||||
| return null; | |||||
| } | |||||
| return parent.subRule.find(( | |||||
| rule => rule.id === ruleId | |||||
| )) | |||||
| } | |||||
| getParentById = (ruleId: string) => this.data.find(rule => rule.id === ruleId) | getParentById = (ruleId: string) => this.data.find(rule => rule.id === ruleId) | ||||
| insertEmptyChild(parent: Rule) { | insertEmptyChild(parent: Rule) { | ||||
| ; | |||||
| if (!parent.subRule) { | if (!parent.subRule) { | ||||
| parent.subRule = []; | parent.subRule = []; | ||||
| } | } | ||||
| { | { | ||||
| text: '', | text: '', | ||||
| id: '0', | id: '0', | ||||
| parentRule: parent, | |||||
| parentRule: { | |||||
| id: parent.id | |||||
| }, | |||||
| paragraph: this.ruleHelperService.getParagraphOfNextChild(parent), | paragraph: this.ruleHelperService.getParagraphOfNextChild(parent), | ||||
| created: 'today', | created: 'today', | ||||
| deletedAt: null, | deletedAt: null, | ||||
| updated: null, | updated: null, | ||||
| } | |||||
| } as Rule | |||||
| ) | ) | ||||
| this.dataChange.next(this.data); | this.dataChange.next(this.data); | ||||
| this.childAdded.next(parent); | this.childAdded.next(parent); | ||||
| } | } | ||||
| this.rulesService.create(createSubRule).subscribe( | this.rulesService.create(createSubRule).subscribe( | ||||
| newSubRule => { | newSubRule => { | ||||
| subRule.id = newSubRule.id; | |||||
| this.dataChange.next(this.data); | |||||
| if (typeof newSubRule.id !== 'undefined' && typeof newSubRule.parentRule?.id !== 'undefined') { | if (typeof newSubRule.id !== 'undefined' && typeof newSubRule.parentRule?.id !== 'undefined') { | ||||
| const updateParentRule: CreateRuleDto = { | const updateParentRule: CreateRuleDto = { | ||||
| paragraph: parent.paragraph, | paragraph: parent.paragraph, | ||||
| //TODO Push updated message | //TODO Push updated message | ||||
| this.update(parent.id, { | this.update(parent.id, { | ||||
| paragraph: parent.paragraph, | paragraph: parent.paragraph, | ||||
| subRuleIds: [ | |||||
| subRule: [ | |||||
| ...parent.subRule | ...parent.subRule | ||||
| .filter(rule => rule.id !== "0") | |||||
| .map(rule => rule.id), | |||||
| subRule.id, | |||||
| .filter(rule => rule.id !== "0"), | |||||
| subRule, | |||||
| ], | ], | ||||
| text: parent.text | text: parent.text | ||||
| }).subscribe( | |||||
| (updated) => { | |||||
| //todo add update message | |||||
| }, | |||||
| (err) => { | |||||
| //TODO Push error message | |||||
| console.error(err); | |||||
| } | |||||
| ) | |||||
| }); | |||||
| }, | }, | ||||
| (err) => { | (err) => { | ||||
| //TODO Push error message | |||||
| console.error(err); | |||||
| this.onEvent.next( | |||||
| { | |||||
| event: 'Error', | |||||
| error: err, | |||||
| message: `Fehler beim aktualisieren von Regel ${parent.paragraph}` | |||||
| } | |||||
| ); | |||||
| } | } | ||||
| ) | ) | ||||
| return; | return; | ||||
| } | } | ||||
| //TODO Push error message | |||||
| } | } | ||||
| ) | ) | ||||
| if (!parent.subRule) { | if (!parent.subRule) { | ||||
| this.childAdded.next(parent); | this.childAdded.next(parent); | ||||
| } | } | ||||
| update(id: string, changedData: Partial<CreateRuleDto>) { | |||||
| return this.rulesService.update(id, changedData); | |||||
| reload() { | |||||
| this.dataChange.next(this.data); | |||||
| } | |||||
| update(id: string, changedData: Partial<Rule>) { | |||||
| let ruleToUpdate = this.getById(id); | |||||
| const updateDto = { | |||||
| paragraph: !!changedData.paragraph ? changedData.paragraph : ruleToUpdate.paragraph, | |||||
| text: !!changedData.text ? changedData.text : ruleToUpdate.text, | |||||
| parentId: !!changedData.parentRule ? changedData.parentRule.id : null, | |||||
| subRuleIds: [ | |||||
| ...!!changedData.subRule ? | |||||
| changedData.subRule | |||||
| .map(rule => rule.id) | |||||
| : [] | |||||
| ] | |||||
| } as CreateRuleDto | |||||
| this.rulesService.update(id, updateDto).subscribe( | |||||
| () => { | |||||
| this.onEvent.next( | |||||
| { | |||||
| event: 'Update', | |||||
| message: `Regel ${ruleToUpdate.paragraph} wurde erfolgreich aktualisiert` | |||||
| } | |||||
| ); | |||||
| }, | |||||
| (err) => { | |||||
| this.onEvent.next( | |||||
| { | |||||
| event: 'Error', | |||||
| error: err, | |||||
| message: `Fehler beim aktualisieren der Regel ${ruleToUpdate.paragraph}` | |||||
| } | |||||
| ); | |||||
| } | |||||
| ) | |||||
| this.dataChange.next(this.data); | |||||
| } | } | ||||
| insertParent(newRule: Rule) { | insertParent(newRule: Rule) { | ||||
| parentId: null | parentId: null | ||||
| } | } | ||||
| this.rulesService.create(createNewRule).subscribe( | this.rulesService.create(createNewRule).subscribe( | ||||
| () => { | |||||
| //TODO created message | |||||
| (created) => { | |||||
| newRule.id = created.id; | |||||
| this.dataChange.next(this.data); | |||||
| this.onEvent.next( | |||||
| { | |||||
| event: 'Create', | |||||
| message: `Regel ${createNewRule.paragraph} wurde erfolgreich erstellt` | |||||
| } | |||||
| ); | |||||
| }, | }, | ||||
| (err) => { | (err) => { | ||||
| //TODO Error message | |||||
| this.onEvent.next( | |||||
| { | |||||
| event: 'Error', | |||||
| error: err, | |||||
| message: `Fehler beim anlegen der Regel ${createNewRule.paragraph}` | |||||
| } | |||||
| ); | |||||
| } | } | ||||
| ) | ) | ||||
| this.dataChange.next(this.data); | this.dataChange.next(this.data); | ||||
| parent.deletedAt = "deleted"; | parent.deletedAt = "deleted"; | ||||
| this.rulesService.remove(parent.id).subscribe( | this.rulesService.remove(parent.id).subscribe( | ||||
| (deleted) => { | (deleted) => { | ||||
| //todo add update message | |||||
| this.onEvent.next( | |||||
| { | |||||
| event: 'Restore', | |||||
| message: `Regel ${parent.paragraph} wurde erfolgreich deaktiviert` | |||||
| } | |||||
| ); | |||||
| }, | }, | ||||
| (err) => { | (err) => { | ||||
| //TODO Push error message | |||||
| console.error(err); | |||||
| this.onEvent.next( | |||||
| { | |||||
| event: 'Error', | |||||
| error: err, | |||||
| message: `Fehler beim aktivieren von Regel ${parent.paragraph}` | |||||
| } | |||||
| ); | |||||
| } | } | ||||
| ); | ); | ||||
| this.dataChange.next(this.data); | this.dataChange.next(this.data); | ||||
| } | } | ||||
| this.rulesService.restore(rule.id).subscribe( | this.rulesService.restore(rule.id).subscribe( | ||||
| (deleted) => { | (deleted) => { | ||||
| //todo add restored message | |||||
| this.onEvent.next( | |||||
| { | |||||
| event: 'Restore', | |||||
| message: `Regel ${rule.paragraph} wurde erfolgreich reaktiviert` | |||||
| } | |||||
| ); | |||||
| }, | }, | ||||
| (err) => { | (err) => { | ||||
| //TODO Push error message | |||||
| console.error(err); | |||||
| this.onEvent.next( | |||||
| { | |||||
| event: 'Error', | |||||
| error: err, | |||||
| message: `Fehler beim aktivieren der Regel ${rule.paragraph}` | |||||
| } | |||||
| ); | |||||
| } | } | ||||
| ); | ); | ||||
| this.dataChange.next(this.data); | this.dataChange.next(this.data); | ||||
| (deleted) => { | (deleted) => { | ||||
| this.update(parent.id, { | this.update(parent.id, { | ||||
| paragraph: parent.paragraph, | paragraph: parent.paragraph, | ||||
| subRuleIds: [ | |||||
| subRule: [ | |||||
| ...parent.subRule | ...parent.subRule | ||||
| .filter(rule => rule.id !== "0") | .filter(rule => rule.id !== "0") | ||||
| .map(rule => rule.id) | |||||
| ], | ], | ||||
| text: parent.text | text: parent.text | ||||
| }).subscribe( | |||||
| (updated) => { | |||||
| //todo add update message | |||||
| }, | |||||
| (err) => { | |||||
| //TODO Push error message | |||||
| console.error(err); | |||||
| }); | |||||
| this.onEvent.next( | |||||
| { | |||||
| event: 'Remove', | |||||
| message: `Regel ${subRule.paragraph} wurde erfolgreich deaktiviert` | |||||
| } | } | ||||
| ); | ); | ||||
| //todo add update message | |||||
| }, | }, | ||||
| (err) => { | (err) => { | ||||
| //TODO Push error message | |||||
| console.error(err); | |||||
| this.onEvent.next( | |||||
| { | |||||
| event: 'Error', | |||||
| error: err, | |||||
| message: `Fehler beim deaktivieren von Regel ${subRule.paragraph}` | |||||
| } | |||||
| ); | |||||
| } | } | ||||
| ); | ); | ||||
| this.dataChange.next(this.data); | this.dataChange.next(this.data); |
| export class EventMessage { event: 'Error' | 'Create' | 'Update' | 'Restore' | 'Remove'; message: string; error?: Error } |
| import { Rule } from "../../../../../api"; | import { Rule } from "../../../../../api"; | ||||
| export class RuleFlatNode { | export class RuleFlatNode { | ||||
| constructor(public item: Rule, public level = 1, public expandable = false, | |||||
| public isLoading = false,public root = true) { } | |||||
| constructor( | |||||
| public item: Rule, | |||||
| public level = 1, | |||||
| public expandable = false, | |||||
| public isLoading = false, | |||||
| public root = true, | |||||
| public editMode = false, | |||||
| ) { } | |||||
| } | } | ||||
| <mat-card class="wood-card"> | <mat-card class="wood-card"> | ||||
| <mat-tree [dataSource]="dataSource" [treeControl]="treeControl"> | |||||
| <mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding> | |||||
| <div> | |||||
| <button mat-icon-button disabled></button> §{{ node.item.paragraph }} {{ node.item.text }} | |||||
| <button *ngIf="node.root" mat-icon-button (click)="addEmptyRule(node)"> | |||||
| <mat-icon>add</mat-icon> | |||||
| </button> | |||||
| <button mat-icon-button (click)="remove(node)"> | |||||
| <mat-icon>remove</mat-icon> | |||||
| <mat-tree [dataSource]="dataSource" [treeControl]="treeControl"> | |||||
| <mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding> | |||||
| <div> | |||||
| <button mat-icon-button disabled></button> | |||||
| <span *ngIf="!node.editMode"> | |||||
| §{{ node.item.paragraph }} {{ node.item.text }} | |||||
| </span> | |||||
| <span *ngIf="node.editMode"> | |||||
| §{{ node.item.paragraph }} | |||||
| <mat-form-field class="new-node-input"> | |||||
| <mat-label>Updated Rule</mat-label> | |||||
| <input [formControl]="text" [value]="node.item.text" matInput #itemValue placeholder="text" /> | |||||
| </mat-form-field> | |||||
| <button [disabled]="itemValue.value === ''" mat-button (click)="updateNode(node, itemValue.value)">Update</button> | |||||
| </span> | |||||
| <button mat-icon-button (click)="openOptions(node)"> | |||||
| <mat-icon>more_vert</mat-icon> | |||||
| </button> | </button> | ||||
| </div> | |||||
| </mat-tree-node> | |||||
| <mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding> | |||||
| <button mat-icon-button matTreeNodeToggle [attr.aria-label]="'Toggle ' + node.text"> | |||||
| <mat-icon class="mat-icon-rtl-mirror"> | |||||
| {{ treeControl.isExpanded(node) ? "expand_more" : "chevron_right" }} | |||||
| </mat-icon> | |||||
| </button> §{{ node.item.paragraph }} {{ node.item.text }} | |||||
| <button *ngIf="node.root" mat-icon-button (click)="addEmptyRule(node)"> | |||||
| <mat-icon>add</mat-icon> | |||||
| </button> | |||||
| <button mat-icon-button (click)="remove(node)"> | |||||
| <mat-icon>remove</mat-icon> | |||||
| </button> | |||||
| </mat-tree-node> | |||||
| <mat-tree-node *matTreeNodeDef="let node; when: hasNoContent" matTreeNodePadding> | |||||
| <button mat-icon-button disabled></button> | |||||
| <span>§{{ node.item.paragraph }}</span> | |||||
| <mat-form-field class="new-node-input"> | |||||
| <mat-label>New Rule</mat-label> | |||||
| <input [formControl]="text" matInput #itemValue placeholder="text" /> | |||||
| </mat-form-field> | |||||
| <button mat-button (click)="addNode(node, itemValue.value)">Save</button> | |||||
| </mat-tree-node> | |||||
| <mat-tree-node class="disabled-node" *matTreeNodeDef="let node; when: isDeleted" matTreeNodePadding> | |||||
| <button mat-icon-button (click)="restore(node)"> | |||||
| <mat-icon>restore</mat-icon> | |||||
| </button> | |||||
| <span>§{{ node.item.paragraph }} {{node.item.text}}</span> | |||||
| </mat-tree-node> | |||||
| </mat-tree> | |||||
| <button mat-icon-button (click)="addEmptyRule()"> | |||||
| <mat-icon>add</mat-icon> | |||||
| </div> | |||||
| </mat-tree-node> | |||||
| <mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding> | |||||
| <button mat-icon-button matTreeNodeToggle [attr.aria-label]="'Toggle ' + node.text"> | |||||
| <mat-icon class="mat-icon-rtl-mirror"> | |||||
| {{ treeControl.isExpanded(node) ? "expand_more" : "chevron_right" }} | |||||
| </mat-icon> | |||||
| </button> | |||||
| <span *ngIf="!node.editMode"> | |||||
| §{{ node.item.paragraph }} {{ node.item.text }} | |||||
| </span> | |||||
| <span *ngIf="node.editMode"> | |||||
| §{{ node.item.paragraph }} | |||||
| <mat-form-field class="new-node-input"> | |||||
| <mat-label>Updated Rule</mat-label> | |||||
| <input [formControl]="text" [value]="node.item.text" matInput #itemValue placeholder="text" /> | |||||
| </mat-form-field> | |||||
| <button [disabled]="itemValue.value === ''" mat-button (click)="updateNode(node, itemValue.value)">Update</button> | |||||
| </span> | |||||
| <button mat-icon-button (click)="openOptions(node)"> | |||||
| <mat-icon>more_vert</mat-icon> | |||||
| </button> | |||||
| </mat-tree-node> | |||||
| <mat-tree-node *matTreeNodeDef="let node; when: hasNoContent" matTreeNodePadding> | |||||
| <button mat-icon-button disabled></button> | |||||
| <span>§{{ node.item.paragraph }}</span> | |||||
| <mat-form-field class="new-node-input"> | |||||
| <mat-label>New Rule</mat-label> | |||||
| <input [formControl]="text" matInput #itemValue placeholder="text" /> | |||||
| </mat-form-field> | |||||
| <button [disabled]="itemValue.value === ''" mat-button (click)="addNode(node, itemValue.value)">Save</button> | |||||
| </mat-tree-node> | |||||
| <mat-tree-node class="disabled-node" *matTreeNodeDef="let node; when: isDeleted" matTreeNodePadding> | |||||
| <button mat-icon-button (click)="restore(node)"> | |||||
| <mat-icon>restore</mat-icon> | |||||
| </button> | |||||
| <span>§{{ node.item.paragraph }} {{node.item.text}}</span> | |||||
| <button mat-icon-button (click)="openOptions(node)"> | |||||
| <mat-icon>more_vert</mat-icon> | |||||
| </button> | |||||
| </mat-tree-node> | |||||
| </mat-tree> | |||||
| <button mat-icon-button (click)="addEmptyRule()"> | |||||
| <mat-icon>add</mat-icon> | |||||
| </button> | </button> | ||||
| </mat-card> | </mat-card> |
| import { FlatTreeControl } from '@angular/cdk/tree'; | import { FlatTreeControl } from '@angular/cdk/tree'; | ||||
| import { Component, OnInit } from '@angular/core'; | |||||
| import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; | |||||
| import { FormControl } from '@angular/forms'; | import { FormControl } from '@angular/forms'; | ||||
| import { MatBottomSheet, MatBottomSheetConfig } from '@angular/material/bottom-sheet'; | |||||
| import { MatSnackBar } from '@angular/material/snack-bar'; | |||||
| import { MatTreeFlatDataSource, MatTreeFlattener } from '@angular/material/tree'; | import { MatTreeFlatDataSource, MatTreeFlattener } from '@angular/material/tree'; | ||||
| import { Subject } from 'rxjs'; | |||||
| import { Rule } from '../../../../api/model/rule'; | import { Rule } from '../../../../api/model/rule'; | ||||
| import { EventDialogComponent } from '../../event-dialog/event-dialog.component'; | |||||
| import { OptionsShetComponent } from '../options-shet/options-shet.component'; | |||||
| import { DynamicDatabase } from './class/dynamic-database'; | import { DynamicDatabase } from './class/dynamic-database'; | ||||
| import { EventMessage } from './class/event-message'; | |||||
| import { RuleFlatNode } from './class/rule-flat-node'; | import { RuleFlatNode } from './class/rule-flat-node'; | ||||
| flatNodeMap = new Map<RuleFlatNode, Rule>(); | flatNodeMap = new Map<RuleFlatNode, Rule>(); | ||||
| nestedNodeMap = new Map<Rule, RuleFlatNode>(); | nestedNodeMap = new Map<Rule, RuleFlatNode>(); | ||||
| text = new FormControl(''); | text = new FormControl(''); | ||||
| @ViewChild(TemplateRef) template: TemplateRef<any>; | |||||
| private _transformer = (rule: Rule, level: number) => { | private _transformer = (rule: Rule, level: number) => { | ||||
| ; | |||||
| const existingNode = this.nestedNodeMap.get(rule); | const existingNode = this.nestedNodeMap.get(rule); | ||||
| const flatNode = existingNode && existingNode.item.id === rule.id | const flatNode = existingNode && existingNode.item.id === rule.id | ||||
| ? existingNode | ? existingNode | ||||
| dataSource: MatTreeFlatDataSource<Rule, RuleFlatNode>; | dataSource: MatTreeFlatDataSource<Rule, RuleFlatNode>; | ||||
| _database: DynamicDatabase; | _database: DynamicDatabase; | ||||
| constructor(database: DynamicDatabase) { | |||||
| constructor(database: DynamicDatabase, private _snackBar: MatSnackBar, readonly bottomSheet: MatBottomSheet) { | |||||
| this._database = database; | this._database = database; | ||||
| this.treeControl = new FlatTreeControl<RuleFlatNode>(this.getLevel, this.isExpandable); | this.treeControl = new FlatTreeControl<RuleFlatNode>(this.getLevel, this.isExpandable); | ||||
| this.treeFlattener = new MatTreeFlattener<Rule, RuleFlatNode, RuleFlatNode>( | this.treeFlattener = new MatTreeFlattener<Rule, RuleFlatNode, RuleFlatNode>( | ||||
| this._database.dataChange.subscribe(data => { | this._database.dataChange.subscribe(data => { | ||||
| this.dataSource.data = data; | this.dataSource.data = data; | ||||
| }); | }); | ||||
| this._database.onEvent.subscribe( | |||||
| (event) => { | |||||
| if (event.error) { | |||||
| console.error(event.error); | |||||
| } | |||||
| this.openSnackBar(event) | |||||
| } | |||||
| ) | |||||
| this._database.childAdded.subscribe(parent => this.treeControl.expand(this.nestedNodeMap.get(parent))) | this._database.childAdded.subscribe(parent => this.treeControl.expand(this.nestedNodeMap.get(parent))) | ||||
| } | } | ||||
| ngOnInit(): void { | ngOnInit(): void { | ||||
| } | } | ||||
| openOptions(node: RuleFlatNode) { | |||||
| const onRemove = new Subject<RuleFlatNode>(); | |||||
| const onAdd = new Subject<RuleFlatNode>(); | |||||
| const onEdit = new Subject<RuleFlatNode>(); | |||||
| this.bottomSheet.open(OptionsShetComponent, { | |||||
| data: { | |||||
| remove: onRemove, | |||||
| add: onAdd, | |||||
| edit: onEdit, | |||||
| node: node | |||||
| } | |||||
| }); | |||||
| onRemove.subscribe( | |||||
| (node) => { | |||||
| this.remove(node); | |||||
| this.bottomSheet.dismiss(); | |||||
| } | |||||
| ) | |||||
| onAdd.subscribe( | |||||
| (node) => { | |||||
| this.addEmptyRule(node); | |||||
| this.bottomSheet.dismiss(); | |||||
| } | |||||
| ) | |||||
| onEdit.subscribe( | |||||
| (node) => { | |||||
| this.openEdit(node); | |||||
| this.bottomSheet.dismiss(); | |||||
| } | |||||
| ) | |||||
| } | |||||
| openSnackBar(event: EventMessage) { | |||||
| this._snackBar.openFromComponent(EventDialogComponent, { | |||||
| data: event, | |||||
| duration: 1000, | |||||
| }); | |||||
| } | |||||
| getLevel = (node: RuleFlatNode) => node.level; | getLevel = (node: RuleFlatNode) => node.level; | ||||
| isExpandable = (node: RuleFlatNode) => node.expandable; | isExpandable = (node: RuleFlatNode) => node.expandable; | ||||
| getChildren = (node: Rule): Rule[] => node.subRule; | getChildren = (node: Rule): Rule[] => node.subRule; | ||||
| hasChild = (_: number, node: RuleFlatNode) => node.expandable && !this.isDeleted(_, node); | |||||
| hasChild = (_: number, node: RuleFlatNode) => node.expandable && !this.isDeleted(_, node) && !this.inEditMode(_, node); | |||||
| inEditMode = (_: number, node: RuleFlatNode) => node.editMode; | |||||
| isDeleted = (_: number, _nodeData: RuleFlatNode) => _nodeData.item.deletedAt !== null; | isDeleted = (_: number, _nodeData: RuleFlatNode) => _nodeData.item.deletedAt !== null; | ||||
| hasNoContent = (_: number, _nodeData: RuleFlatNode) => _nodeData.item.text === ''; | hasNoContent = (_: number, _nodeData: RuleFlatNode) => _nodeData.item.text === ''; | ||||
| hasContent = (_nodeData: RuleFlatNode) => _nodeData.item.text !== ""; | hasContent = (_nodeData: RuleFlatNode) => _nodeData.item.text !== ""; | ||||
| } | } | ||||
| async addNode(newNode: RuleFlatNode, text: string) { | async addNode(newNode: RuleFlatNode, text: string) { | ||||
| ; | |||||
| const newRule = this.flatNodeMap.get(newNode); | const newRule = this.flatNodeMap.get(newNode); | ||||
| newRule.text = text; | newRule.text = text; | ||||
| if (!!newRule.parentRule) { | if (!!newRule.parentRule) { | ||||
| ; | |||||
| const parentRule = this._database.getParentById(newRule.parentRule.id) | const parentRule = this._database.getParentById(newRule.parentRule.id) | ||||
| this._database.insertChild(parentRule, newRule) | this._database.insertChild(parentRule, newRule) | ||||
| return; | return; | ||||
| this._database.removeChild(parent, ruleToRemove); | this._database.removeChild(parent, ruleToRemove); | ||||
| } | } | ||||
| async openEdit(nodeToEdit: RuleFlatNode) { | |||||
| nodeToEdit.editMode = true; | |||||
| this._database.reload; | |||||
| } | |||||
| async restore(nodeToRestore: RuleFlatNode) { | async restore(nodeToRestore: RuleFlatNode) { | ||||
| const ruleToRestore = this.flatNodeMap.get(nodeToRestore); | const ruleToRestore = this.flatNodeMap.get(nodeToRestore); | ||||
| this._database.restore(ruleToRestore); | this._database.restore(ruleToRestore); | ||||
| } | } | ||||
| async updateNode(updatedNode: RuleFlatNode, updatedText: string) { | |||||
| const parentNode = this.getParentNode(updatedNode); | |||||
| if (parentNode) { | |||||
| updatedNode.item.parentRule = { | |||||
| id: parentNode.item.id, | |||||
| text: parentNode.item.text, | |||||
| paragraph: parentNode.item.text | |||||
| } | |||||
| } | |||||
| const updatedRule = this.flatNodeMap.get(updatedNode); | |||||
| updatedRule.text = updatedText; | |||||
| this._database.update(updatedRule.id, updatedRule) | |||||
| } | |||||
| } | } |
| <span> | |||||
| <button *ngIf="config.node.root" mat-icon-button (click)="config.add.next(config.node)"> | |||||
| <mat-icon>add</mat-icon> | |||||
| </button> | |||||
| <button mat-icon-button (click)="config.remove.next(config.node)"> | |||||
| <mat-icon>remove</mat-icon> | |||||
| </button> | |||||
| <button mat-icon-button (click)="config.edit.next(config.node)"> | |||||
| <mat-icon>edit</mat-icon> | |||||
| </button> | |||||
| </span> |
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | |||||
| import { OptionsShetComponent } from './options-shet.component'; | |||||
| describe('OptionsShetComponent', () => { | |||||
| let component: OptionsShetComponent; | |||||
| let fixture: ComponentFixture<OptionsShetComponent>; | |||||
| beforeEach(async () => { | |||||
| await TestBed.configureTestingModule({ | |||||
| declarations: [ OptionsShetComponent ] | |||||
| }) | |||||
| .compileComponents(); | |||||
| }); | |||||
| beforeEach(() => { | |||||
| fixture = TestBed.createComponent(OptionsShetComponent); | |||||
| component = fixture.componentInstance; | |||||
| fixture.detectChanges(); | |||||
| }); | |||||
| it('should create', () => { | |||||
| expect(component).toBeTruthy(); | |||||
| }); | |||||
| }); |
| import { Component, Inject, OnInit } from '@angular/core'; | |||||
| import { MAT_BOTTOM_SHEET_DATA } from '@angular/material/bottom-sheet'; | |||||
| import { Subject } from 'rxjs'; | |||||
| import { RuleFlatNode } from '../create-rule/class/rule-flat-node'; | |||||
| @Component({ | |||||
| selector: 'app-options-shet', | |||||
| templateUrl: './options-shet.component.html', | |||||
| styleUrls: ['./options-shet.component.scss'] | |||||
| }) | |||||
| export class OptionsShetComponent implements OnInit { | |||||
| constructor(@Inject(MAT_BOTTOM_SHEET_DATA) public config: { | |||||
| add: Subject<RuleFlatNode>, | |||||
| remove: Subject<RuleFlatNode>, | |||||
| edit: Subject<RuleFlatNode>, | |||||
| node: RuleFlatNode | |||||
| }) { } | |||||
| ngOnInit(): void { | |||||
| } | |||||
| } |
| import { HTTP_INTERCEPTORS } from '@angular/common/http'; | import { HTTP_INTERCEPTORS } from '@angular/common/http'; | ||||
| import { AuthInterceptor } from './auth/interceptor/auth.interceptor'; | import { AuthInterceptor } from './auth/interceptor/auth.interceptor'; | ||||
| import { ErrorInterceptor } from './auth/interceptor/error.interceptor'; | import { ErrorInterceptor } from './auth/interceptor/error.interceptor'; | ||||
| import { EventDialogComponent } from './event-dialog/event-dialog.component'; | |||||
| @NgModule({ | @NgModule({ | ||||
| declarations: [ | declarations: [ | ||||
| DashboardComponent, | DashboardComponent, | ||||
| GridComponent, | GridComponent, | ||||
| MainComponent, | MainComponent, | ||||
| EventDialogComponent, | |||||
| ], | ], | ||||
| imports: [ | imports: [ | ||||
| BrowserModule, | BrowserModule, |
| // auto logout if 401 response returned from api | // auto logout if 401 response returned from api | ||||
| this.authenticationService.logout(); | this.authenticationService.logout(); | ||||
| } | } | ||||
| const error = err.error.message || err.statusText; | |||||
| ; | |||||
| const error = err.error?.message || err.statusText; | |||||
| return throwError(error); | return throwError(error); | ||||
| })) | })) | ||||
| } | } |
| <span [ngClass]="{primary: event.event !== 'Error', warn: event.event === 'Error'}"> | |||||
| {{event.message}} | |||||
| </span> |
| @import '~@angular/material/prebuilt-themes/deeppurple-amber.css'; | |||||
| .primary {} | |||||
| .warn { | |||||
| background-color: red; | |||||
| } |
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | |||||
| import { EventDialogComponent } from './event-dialog.component'; | |||||
| describe('EventDialogComponent', () => { | |||||
| let component: EventDialogComponent; | |||||
| let fixture: ComponentFixture<EventDialogComponent>; | |||||
| beforeEach(async () => { | |||||
| await TestBed.configureTestingModule({ | |||||
| declarations: [ EventDialogComponent ] | |||||
| }) | |||||
| .compileComponents(); | |||||
| }); | |||||
| beforeEach(() => { | |||||
| fixture = TestBed.createComponent(EventDialogComponent); | |||||
| component = fixture.componentInstance; | |||||
| fixture.detectChanges(); | |||||
| }); | |||||
| it('should create', () => { | |||||
| expect(component).toBeTruthy(); | |||||
| }); | |||||
| }); |
| import { Component, Inject, Input, OnInit } from '@angular/core'; | |||||
| import { MAT_SNACK_BAR_DATA } from '@angular/material/snack-bar'; | |||||
| import { EventMessage } from '../admin/create-rule/class/event-message'; | |||||
| @Component({ | |||||
| selector: 'app-event-dialog', | |||||
| templateUrl: './event-dialog.component.html', | |||||
| styleUrls: ['./event-dialog.component.scss'] | |||||
| }) | |||||
| export class EventDialogComponent implements OnInit { | |||||
| constructor(@Inject(MAT_SNACK_BAR_DATA) public event: EventMessage) { } | |||||
| ngOnInit(): void { | |||||
| } | |||||
| } |
| <mat-toolbar> | <mat-toolbar> | ||||
| <span > | |||||
| <span> | |||||
| <a href="https://ziermach.de/legal">legal</a> | <a href="https://ziermach.de/legal">legal</a> | ||||
| </span | |||||
| > | |||||
| </mat-toolbar> | |||||
| </span> | |||||
| <span [routerLink]="['/admin/create']"> | |||||
| <mat-icon>add</mat-icon> | |||||
| </span> | |||||
| </mat-toolbar> |
| .mat-toolbar { | .mat-toolbar { | ||||
| height: 3%; | |||||
| height: 3%; | |||||
| background: transparent; | |||||
| color: white; | |||||
| font-size: small; | |||||
| } | } | ||||
| a { | |||||
| color: white | |||||
| } |
| import { CommonModule } from '@angular/common'; | import { CommonModule } from '@angular/common'; | ||||
| import { FooterComponent } from './footer.component'; | import { FooterComponent } from './footer.component'; | ||||
| import { MatToolbarModule } from '@angular/material/toolbar'; | import { MatToolbarModule } from '@angular/material/toolbar'; | ||||
| import { MatIconModule } from '@angular/material/icon'; | |||||
| import { RouterModule } from '@angular/router'; | |||||
| ], | ], | ||||
| imports: [ | imports: [ | ||||
| CommonModule, | CommonModule, | ||||
| MatToolbarModule | |||||
| MatToolbarModule, | |||||
| MatIconModule, | |||||
| RouterModule, | |||||
| ], | ], | ||||
| exports: [ | exports: [ | ||||
| FooterComponent | FooterComponent |
| <mat-sidenav-content class="page-wrap"> | <mat-sidenav-content class="page-wrap"> | ||||
| <mat-toolbar color="primary"> | <mat-toolbar color="primary"> | ||||
| <span routerLink="" class="mat-title title"> | <span routerLink="" class="mat-title title"> | ||||
| <mat-icon mat-list-icon class="logo">bedroom_baby</mat-icon> Hof Hoppe</span | |||||
| <mat-icon mat-list-icon class="logo">bedroom_baby</mat-icon> Hof Hoppe</span | |||||
| > | > | ||||
| </mat-toolbar> | |||||
| <main class="content"> | |||||
| <router-outlet></router-outlet> | |||||
| </main> | |||||
| </mat-sidenav-content> | |||||
| </mat-toolbar> | |||||
| <main class="content"> | |||||
| <router-outlet></router-outlet> | |||||
| </main> | |||||
| <app-footer></app-footer> | |||||
| </mat-sidenav-content> | |||||
| </mat-sidenav-container> | </mat-sidenav-container> |
| <mat-card class="dashboard-card wood-card"> | <mat-card class="dashboard-card wood-card"> | ||||
| <mat-card-header> | |||||
| <mat-card-title class="title mat-title"> | |||||
| <mat-icon mat-list-icon class="logo">gavel</mat-icon> Regeln | |||||
| </mat-card-title> | |||||
| </mat-card-header> | |||||
| <mat-card-content class="dashboard-card-content"> | |||||
| <app-rule-list [rules]="rules"></app-rule-list> | |||||
| </mat-card-content> | |||||
| </mat-card> | |||||
| <mat-card-header> | |||||
| <mat-card-title class="title mat-title"> | |||||
| <mat-icon mat-list-icon class="logo">gavel</mat-icon> Regeln | |||||
| </mat-card-title> | |||||
| </mat-card-header> | |||||
| <mat-card-content class="dashboard-card-content"> | |||||
| <app-rule-list [rules]="rules"></app-rule-list> | |||||
| </mat-card-content> | |||||
| </mat-card> |
| import { Rule } from '../../../../api/model/rule'; | import { Rule } from '../../../../api/model/rule'; | ||||
| import { RulesService } from '../../../../api/api/rules.service'; | import { RulesService } from '../../../../api/api/rules.service'; | ||||
| import { RuleDataService } from '../../service/rule-data.service'; | import { RuleDataService } from '../../service/rule-data.service'; | ||||
| import { Router } from '@angular/router'; | |||||
| @Component({ | @Component({ | ||||
| selector: 'app-rules', | selector: 'app-rules', | ||||
| templateUrl: './rules.component.html', | templateUrl: './rules.component.html', | ||||
| }) | }) | ||||
| export class RulesComponent implements OnInit { | export class RulesComponent implements OnInit { | ||||
| rules: Rule[] = [] | rules: Rule[] = [] | ||||
| constructor(protected rulesService: RulesService,protected ruleDataService: RuleDataService) { | |||||
| constructor( | |||||
| protected rulesService: RulesService, | |||||
| protected ruleDataService: RuleDataService, | |||||
| protected router: Router, | |||||
| ) { | |||||
| rulesService.configuration.basePath = 'http://localhost:3000'; | rulesService.configuration.basePath = 'http://localhost:3000'; | ||||
| } | } | ||||
| }) | }) | ||||
| } | } | ||||
| openAdminArea() { | |||||
| this.router.navigate(['/admin/create']); | |||||
| } | |||||
| } | } |