@@ -137,7 +137,7 @@ export class RulesService implements RulesServiceInterface { | |||
responseType_ = 'text'; | |||
} | |||
return this.httpClient.post<Rule>(`${this.configuration.basePath}/rules`, | |||
return this.httpClient.post<Rule>(`${this.configuration.basePath}/rules/admin`, | |||
createRuleDto, | |||
{ | |||
responseType: <any>responseType_, | |||
@@ -196,6 +196,53 @@ export class RulesService implements RulesServiceInterface { | |||
); | |||
} | |||
/** | |||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. | |||
* @param reportProgress flag to report request and response progress. | |||
*/ | |||
public findAllAdmin(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<Array<Rule>>; | |||
public findAllAdmin(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<Array<Rule>>>; | |||
public findAllAdmin(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<Array<Rule>>>; | |||
public findAllAdmin(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> { | |||
let headers = this.defaultHeaders; | |||
let credential: string | undefined; | |||
// authentication (basic) required | |||
credential = this.configuration.lookupCredential('basic'); | |||
if (credential) { | |||
headers = headers.set('Authorization', 'Basic ' + credential); | |||
} | |||
let httpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; | |||
if (httpHeaderAcceptSelected === undefined) { | |||
// to determine the Accept header | |||
const httpHeaderAccepts: string[] = [ | |||
'application/json' | |||
]; | |||
httpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); | |||
} | |||
if (httpHeaderAcceptSelected !== undefined) { | |||
headers = headers.set('Accept', httpHeaderAcceptSelected); | |||
} | |||
let responseType_: 'text' | 'json' = 'json'; | |||
if(httpHeaderAcceptSelected && httpHeaderAcceptSelected.startsWith('text')) { | |||
responseType_ = 'text'; | |||
} | |||
return this.httpClient.get<Array<Rule>>(`${this.configuration.basePath}/rules/admin`, | |||
{ | |||
responseType: <any>responseType_, | |||
withCredentials: this.configuration.withCredentials, | |||
headers: headers, | |||
observe: observe, | |||
reportProgress: reportProgress | |||
} | |||
); | |||
} | |||
/** | |||
* @param id | |||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. | |||
@@ -286,7 +333,58 @@ export class RulesService implements RulesServiceInterface { | |||
responseType_ = 'text'; | |||
} | |||
return this.httpClient.delete<any>(`${this.configuration.basePath}/rules/${encodeURIComponent(String(id))}`, | |||
return this.httpClient.delete<any>(`${this.configuration.basePath}/rules/admin/${encodeURIComponent(String(id))}`, | |||
{ | |||
responseType: <any>responseType_, | |||
withCredentials: this.configuration.withCredentials, | |||
headers: headers, | |||
observe: observe, | |||
reportProgress: reportProgress | |||
} | |||
); | |||
} | |||
/** | |||
* @param id | |||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. | |||
* @param reportProgress flag to report request and response progress. | |||
*/ | |||
public restore(id: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined}): Observable<any>; | |||
public restore(id: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined}): Observable<HttpResponse<any>>; | |||
public restore(id: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined}): Observable<HttpEvent<any>>; | |||
public restore(id: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: undefined}): Observable<any> { | |||
if (id === null || id === undefined) { | |||
throw new Error('Required parameter id was null or undefined when calling restore.'); | |||
} | |||
let headers = this.defaultHeaders; | |||
let credential: string | undefined; | |||
// authentication (basic) required | |||
credential = this.configuration.lookupCredential('basic'); | |||
if (credential) { | |||
headers = headers.set('Authorization', 'Basic ' + credential); | |||
} | |||
let httpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; | |||
if (httpHeaderAcceptSelected === undefined) { | |||
// to determine the Accept header | |||
const httpHeaderAccepts: string[] = [ | |||
]; | |||
httpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); | |||
} | |||
if (httpHeaderAcceptSelected !== undefined) { | |||
headers = headers.set('Accept', httpHeaderAcceptSelected); | |||
} | |||
let responseType_: 'text' | 'json' = 'json'; | |||
if(httpHeaderAcceptSelected && httpHeaderAcceptSelected.startsWith('text')) { | |||
responseType_ = 'text'; | |||
} | |||
return this.httpClient.post<any>(`${this.configuration.basePath}/rules/admin/restore/${encodeURIComponent(String(id))}`, | |||
null, | |||
{ | |||
responseType: <any>responseType_, | |||
withCredentials: this.configuration.withCredentials, | |||
@@ -303,10 +401,10 @@ export class RulesService implements RulesServiceInterface { | |||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. | |||
* @param reportProgress flag to report request and response progress. | |||
*/ | |||
public update(id: string, body: object, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<object>; | |||
public update(id: string, body: object, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<object>>; | |||
public update(id: string, body: object, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<object>>; | |||
public update(id: string, body: object, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> { | |||
public update(id: string, body: object, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined}): Observable<any>; | |||
public update(id: string, body: object, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined}): Observable<HttpResponse<any>>; | |||
public update(id: string, body: object, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined}): Observable<HttpEvent<any>>; | |||
public update(id: string, body: object, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: undefined}): Observable<any> { | |||
if (id === null || id === undefined) { | |||
throw new Error('Required parameter id was null or undefined when calling update.'); | |||
} | |||
@@ -327,7 +425,6 @@ export class RulesService implements RulesServiceInterface { | |||
if (httpHeaderAcceptSelected === undefined) { | |||
// to determine the Accept header | |||
const httpHeaderAccepts: string[] = [ | |||
'application/json' | |||
]; | |||
httpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); | |||
} | |||
@@ -350,7 +447,7 @@ export class RulesService implements RulesServiceInterface { | |||
responseType_ = 'text'; | |||
} | |||
return this.httpClient.patch<object>(`${this.configuration.basePath}/rules/${encodeURIComponent(String(id))}`, | |||
return this.httpClient.patch<any>(`${this.configuration.basePath}/rules/admin/${encodeURIComponent(String(id))}`, | |||
body, | |||
{ | |||
responseType: <any>responseType_, |
@@ -38,6 +38,12 @@ export interface RulesServiceInterface { | |||
*/ | |||
findAll(extraHttpRequestParams?: any): Observable<Array<Rule>>; | |||
/** | |||
* | |||
* | |||
*/ | |||
findAllAdmin(extraHttpRequestParams?: any): Observable<Array<Rule>>; | |||
/** | |||
* | |||
* | |||
@@ -52,12 +58,19 @@ export interface RulesServiceInterface { | |||
*/ | |||
remove(id: string, extraHttpRequestParams?: any): Observable<{}>; | |||
/** | |||
* | |||
* | |||
* @param id | |||
*/ | |||
restore(id: string, extraHttpRequestParams?: any): Observable<{}>; | |||
/** | |||
* | |||
* | |||
* @param id | |||
* @param body | |||
*/ | |||
update(id: string, body: object, extraHttpRequestParams?: any): Observable<object>; | |||
update(id: string, body: object, extraHttpRequestParams?: any): Observable<{}>; | |||
} |
@@ -17,5 +17,8 @@ export interface Rule { | |||
text: string; | |||
parentRule?: Rule | null; | |||
subRule?: Array<Rule> | null; | |||
created?: string | null; | |||
updated?: string | null; | |||
deletedAt?: string | null; | |||
} | |||
@@ -4,6 +4,7 @@ import { BehaviorSubject, Subject } from "rxjs"; | |||
import { map } from "rxjs/operators"; | |||
import { CreateRuleDto, Rule, RulesService } from "../../../../../api"; | |||
import { environment } from "../../../../environments/environment"; | |||
import { RuleDataService } from "../../../service/rule-data.service"; | |||
import { RuleFlatNode } from "./rule-flat-node"; | |||
@@ -15,58 +16,45 @@ export class DynamicDatabase { | |||
get data(): Rule[] { return this.dataChange.value; } | |||
get count(): number { return this.dataChange.value.length }; | |||
protected removeData(rule: Rule) { | |||
const indexOfRule = this.dataChange.value.indexOf(rule); | |||
if (indexOfRule !== -1) { | |||
this.dataChange.value.splice(indexOfRule, 1) | |||
this.dataChange.next( | |||
this.data | |||
); | |||
} | |||
} | |||
protected addData(rule: Rule) { this.dataChange.next([...this.dataChange.value, rule]) } | |||
constructor(protected rulesService: RulesService) { | |||
constructor(protected rulesService: RulesService, protected ruleHelperService: RuleDataService) { | |||
this.rulesService.configuration.basePath = environment.apiUrl; | |||
this.rulesService.findAll() | |||
this.rulesService.findAllAdmin() | |||
.subscribe( | |||
(rules: Rule[]) => this.dataChange.next( | |||
rules | |||
.filter(this.filterRuleWithParentFromRootLayer) | |||
.sort(this.compareRulesByParagraph) | |||
.filter(ruleHelperService.filterRuleWithParentFromRootLayer) | |||
.sort(ruleHelperService.compareRulesByParagraph) | |||
) | |||
), | |||
(err) => { | |||
//TODO add toaster | |||
} | |||
} | |||
getParagraphOfNextChild = (parentRule): string => `${parentRule.paragraph}.${parentRule.subRule.length + 1}`; | |||
filterRuleWithParentFromRootLayer = (rootLayerRule: Rule) => typeof rootLayerRule.parentRule !== 'number'; | |||
compareRulesByParagraph = (ruleA: Rule, ruleB: Rule) => this.compareParahraph(ruleA.paragraph, ruleB.paragraph); | |||
compareParahraph(paragraphA: string, paragraphB: string): 0 | 1 | -1 { | |||
if (paragraphA === paragraphB) { | |||
return 0; | |||
} | |||
const paragrapASections = paragraphA.split('.'); | |||
const paragrapBSections = paragraphB.split('.'); | |||
if (+paragrapASections[0] > +paragrapBSections[0]) { | |||
return 1; | |||
} | |||
if (+paragrapASections[0] < +paragrapBSections[0]) { | |||
return -1; | |||
} | |||
if (+paragrapASections[0] === +paragrapBSections[0]) { | |||
let shiftedParagraphASections = paragrapASections; | |||
shiftedParagraphASections.shift(); | |||
let shiftedParagraphBSections = paragrapBSections; | |||
shiftedParagraphBSections.shift(); | |||
return this.compareParahraph( | |||
shiftedParagraphASections.join('.'), | |||
shiftedParagraphBSections.join('.'), | |||
) | |||
} | |||
} | |||
getParentById = (ruleId: string) => this.data.find(rule => rule.id === ruleId) | |||
insertEmptyChild(parent: Rule) { | |||
if (!parent.subRule) { | |||
parent.subRule = []; | |||
} | |||
parent.subRule.push( | |||
{ | |||
text: '', | |||
id: '0', | |||
parentRule: parent, | |||
paragraph: this.getParagraphOfNextChild(parent) | |||
paragraph: this.ruleHelperService.getParagraphOfNextChild(parent), | |||
created: 'today', | |||
deletedAt: null, | |||
updated: null, | |||
} | |||
) | |||
this.dataChange.next(this.data); | |||
@@ -78,7 +66,10 @@ export class DynamicDatabase { | |||
text: '', | |||
id: '0', | |||
parentRule: null, | |||
paragraph: `${this.count + 2}` | |||
paragraph: `${this.count + 1}`, | |||
created: 'today', | |||
deletedAt: null, | |||
updated: null, | |||
}) | |||
} | |||
insertChild(parent: Rule, subRule: Rule) { | |||
@@ -102,8 +93,8 @@ export class DynamicDatabase { | |||
paragraph: parent.paragraph, | |||
subRuleIds: [ | |||
...parent.subRule | |||
.filter(rule => rule.id === "0") | |||
.map(rule => rule.id) ?? [], | |||
.filter(rule => rule.id !== "0") | |||
.map(rule => rule.id), | |||
subRule.id, | |||
], | |||
text: parent.text | |||
@@ -142,7 +133,7 @@ export class DynamicDatabase { | |||
insertParent(newRule: Rule) { | |||
const createNewRule: CreateRuleDto = { | |||
paragraph: `${this.count + 1}`, | |||
paragraph: newRule.paragraph, | |||
text: newRule.text, | |||
subRuleIds: null, | |||
parentId: null | |||
@@ -158,13 +149,68 @@ export class DynamicDatabase { | |||
this.dataChange.next(this.data); | |||
} | |||
removeParent(parent: Rule) { | |||
parent.deletedAt = "deleted"; | |||
this.rulesService.remove(parent.id).subscribe( | |||
(deleted) => { | |||
//todo add update message | |||
}, | |||
(err) => { | |||
//TODO Push error message | |||
console.error(err); | |||
} | |||
); | |||
this.dataChange.next(this.data); | |||
} | |||
restore(rule: Rule) { | |||
rule.deletedAt = null; | |||
if (rule.subRule) { | |||
rule.subRule.forEach((subRule) => { | |||
this.restore(subRule); | |||
}) | |||
} | |||
this.rulesService.restore(rule.id).subscribe( | |||
(deleted) => { | |||
//todo add restored message | |||
}, | |||
(err) => { | |||
//TODO Push error message | |||
console.error(err); | |||
} | |||
); | |||
this.dataChange.next(this.data); | |||
} | |||
removeChild(parent: Rule, subRule: Rule) { | |||
if (parent.subRule) { | |||
const subRuleToRemove = this.findSubRuleFromParent(parent, subRule); | |||
const indexIfSubRule = parent.subRule.indexOf(subRuleToRemove); | |||
if (indexIfSubRule > -1) { | |||
parent.subRule.splice(indexIfSubRule, 1); | |||
} | |||
subRule.deletedAt = "delted"; | |||
this.rulesService.remove(subRule.id).subscribe( | |||
(deleted) => { | |||
this.update(parent.id, { | |||
paragraph: parent.paragraph, | |||
subRuleIds: [ | |||
...parent.subRule | |||
.filter(rule => rule.id !== "0") | |||
.map(rule => rule.id) | |||
], | |||
text: parent.text | |||
}).subscribe( | |||
(updated) => { | |||
//todo add update message | |||
}, | |||
(err) => { | |||
//TODO Push error message | |||
console.error(err); | |||
} | |||
); | |||
//todo add update message | |||
}, | |||
(err) => { | |||
//TODO Push error message | |||
console.error(err); | |||
} | |||
); | |||
this.dataChange.next(this.data); | |||
} | |||
} |
@@ -5,6 +5,9 @@ | |||
<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> | |||
</button> | |||
</div> | |||
</mat-tree-node> | |||
@@ -17,6 +20,9 @@ | |||
<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> | |||
@@ -27,8 +33,15 @@ | |||
</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> | |||
<mat-icon>add</mat-icon> | |||
</button> | |||
</mat-card> |
@@ -17,4 +17,8 @@ mat-label { | |||
.new-node-input { | |||
padding-left: 2vw; | |||
} | |||
.disabled-node { | |||
text-decoration: line-through; | |||
} |
@@ -60,7 +60,8 @@ export class CreateRuleComponent implements OnInit { | |||
getLevel = (node: RuleFlatNode) => node.level; | |||
isExpandable = (node: RuleFlatNode) => node.expandable; | |||
getChildren = (node: Rule): Rule[] => node.subRule; | |||
hasChild = (_: number, node: RuleFlatNode) => node.expandable; | |||
hasChild = (_: number, node: RuleFlatNode) => node.expandable && !this.isDeleted(_, node); | |||
isDeleted = (_: number, _nodeData: RuleFlatNode) => _nodeData.item.deletedAt !== null; | |||
hasNoContent = (_: number, _nodeData: RuleFlatNode) => _nodeData.item.text === ''; | |||
hasContent = (_nodeData: RuleFlatNode) => _nodeData.item.text !== ""; | |||
@@ -99,8 +100,28 @@ export class CreateRuleComponent implements OnInit { | |||
if (!!newRule.parentRule) { | |||
const parentRule = this._database.getParentById(newRule.parentRule.id) | |||
this._database.insertChild(parentRule, newRule) | |||
return; | |||
} | |||
this._database.insertParent(newRule) | |||
} | |||
async remove(nodeToRemove: RuleFlatNode) { | |||
const ruleToRemove = this.flatNodeMap.get(nodeToRemove); | |||
if (nodeToRemove.root) { | |||
this._database.removeParent(ruleToRemove); | |||
return | |||
} | |||
const parentNode = this.getParentNode(nodeToRemove); | |||
const parent = this.flatNodeMap.get(parentNode); | |||
this._database.removeChild(parent, ruleToRemove); | |||
} | |||
async restore(nodeToRestore: RuleFlatNode) { | |||
const ruleToRestore = this.flatNodeMap.get(nodeToRestore); | |||
this._database.restore(ruleToRestore); | |||
} | |||
} |
@@ -1,7 +1,7 @@ | |||
<mat-sidenav-container class="all-wrap"> | |||
<mat-sidenav-content class="page-wrap"> | |||
<mat-toolbar color="primary"> | |||
<span class="mat-title title" > | |||
<mat-sidenav-content class="page-wrap"> | |||
<mat-toolbar color="primary"> | |||
<span routerLink="" class="mat-title title"> | |||
<mat-icon mat-list-icon class="logo">bedroom_baby</mat-icon> Hof Hoppe</span | |||
> | |||
</mat-toolbar> | |||
@@ -9,4 +9,4 @@ | |||
<router-outlet></router-outlet> | |||
</main> | |||
</mat-sidenav-content> | |||
</mat-sidenav-container> | |||
</mat-sidenav-container> |
@@ -1,6 +1,7 @@ | |||
import { Component, OnInit } from '@angular/core'; | |||
import { Rule } from '../../../../api/model/rule'; | |||
import { RulesService } from '../../../../api/api/rules.service'; | |||
import { RuleDataService } from '../../service/rule-data.service'; | |||
@Component({ | |||
selector: 'app-rules', | |||
templateUrl: './rules.component.html', | |||
@@ -8,13 +9,16 @@ import { RulesService } from '../../../../api/api/rules.service'; | |||
}) | |||
export class RulesComponent implements OnInit { | |||
rules: Rule[] = [] | |||
constructor(protected rulesService: RulesService) { | |||
constructor(protected rulesService: RulesService,protected ruleDataService: RuleDataService) { | |||
rulesService.configuration.basePath = 'http://localhost:3000'; | |||
} | |||
ngOnInit(): void { | |||
this.rulesService.findAll('body').subscribe((rules: Rule[]) => { | |||
this.rules = rules; | |||
this.rules = rules | |||
.sort(this.ruleDataService.compareRulesByParagraph) | |||
}) | |||
} | |||
@@ -0,0 +1,16 @@ | |||
import { TestBed } from '@angular/core/testing'; | |||
import { RuleDataService } from './rule-data.service'; | |||
describe('RuleDataService', () => { | |||
let service: RuleDataService; | |||
beforeEach(() => { | |||
TestBed.configureTestingModule({}); | |||
service = TestBed.inject(RuleDataService); | |||
}); | |||
it('should be created', () => { | |||
expect(service).toBeTruthy(); | |||
}); | |||
}); |
@@ -0,0 +1,38 @@ | |||
import { Injectable } from '@angular/core'; | |||
import { Rule } from '../../../api'; | |||
@Injectable({ | |||
providedIn: 'root' | |||
}) | |||
export class RuleDataService { | |||
getParagraphOfNextChild = (parentRule): string => `${parentRule.paragraph}.${parentRule.subRule.length + 1}`; | |||
filterRuleWithParentFromRootLayer = (rootLayerRule: Rule) => typeof rootLayerRule.parentRule !== 'number'; | |||
compareRulesByParagraph = (ruleA: Rule, ruleB: Rule) => this.compareParahraph(ruleA.paragraph, ruleB.paragraph); | |||
compareParahraph(paragraphA: string, paragraphB: string): 0 | 1 | -1 { | |||
if (paragraphA === paragraphB) { | |||
return 0; | |||
} | |||
const paragrapASections = paragraphA.split('.'); | |||
const paragrapBSections = paragraphB.split('.'); | |||
if (+paragrapASections[0] > +paragrapBSections[0]) { | |||
return 1; | |||
} | |||
if (+paragrapASections[0] < +paragrapBSections[0]) { | |||
return -1; | |||
} | |||
if (+paragrapASections[0] === +paragrapBSections[0]) { | |||
let shiftedParagraphASections = paragrapASections; | |||
shiftedParagraphASections.shift(); | |||
let shiftedParagraphBSections = paragrapBSections; | |||
shiftedParagraphBSections.shift(); | |||
return this.compareParahraph( | |||
shiftedParagraphASections.join('.'), | |||
shiftedParagraphBSections.join('.'), | |||
) | |||
} | |||
} | |||
constructor() { } | |||
} |