Merge pull request #4888 from jsangmeister/dialogs-everywhere
Replaced multiple inline forms with dialogs
This commit is contained in:
commit
f25a8aefb2
1
AUTHORS
1
AUTHORS
@ -30,3 +30,4 @@ Authors of OpenSlides in chronological order of first contribution:
|
|||||||
Jochen Saalfeld <jochen.saalfeld@intevation.de>
|
Jochen Saalfeld <jochen.saalfeld@intevation.de>
|
||||||
Fadi Abbud <fmfn13@hotmail.com>
|
Fadi Abbud <fmfn13@hotmail.com>
|
||||||
Gabriel Meyer <meyergabriel@live.de>
|
Gabriel Meyer <meyergabriel@live.de>
|
||||||
|
Joshua Sangmeister <joshua.sangmeister@gmail.com>
|
||||||
|
@ -24,7 +24,8 @@
|
|||||||
"po2json-tempfix": "./node_modules/.bin/po2json -f mf src/assets/i18n/de.po /dev/stdout | sed -f sed_replacements > src/assets/i18n/de.json && ./node_modules/.bin/po2json -f mf src/assets/i18n/cs.po /dev/stdout | sed -f sed_replacements > src/assets/i18n/cs.json",
|
"po2json-tempfix": "./node_modules/.bin/po2json -f mf src/assets/i18n/de.po /dev/stdout | sed -f sed_replacements > src/assets/i18n/de.json && ./node_modules/.bin/po2json -f mf src/assets/i18n/cs.po /dev/stdout | sed -f sed_replacements > src/assets/i18n/cs.json",
|
||||||
"prettify-check": "prettier --config ./.prettierrc --list-different \"src/{app,environments}/**/*{.ts,.js,.json,.css,.scss}\"",
|
"prettify-check": "prettier --config ./.prettierrc --list-different \"src/{app,environments}/**/*{.ts,.js,.json,.css,.scss}\"",
|
||||||
"prettify-write": "prettier --config ./.prettierrc --write \"src/{app,environments}/**/*{.ts,.js,.json,.css,.scss}\"",
|
"prettify-write": "prettier --config ./.prettierrc --write \"src/{app,environments}/**/*{.ts,.js,.json,.css,.scss}\"",
|
||||||
"cleanup": "npm run lint-write; npm run prettify-write"
|
"cleanup": "npm run lint-write; npm run prettify-write",
|
||||||
|
"cleanup-win": "npm run lint-write & npm run prettify-write"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^8.0.3",
|
"@angular/animations": "^8.0.3",
|
||||||
@ -90,7 +91,7 @@
|
|||||||
"karma-jasmine-html-reporter": "^1.4.0",
|
"karma-jasmine-html-reporter": "^1.4.0",
|
||||||
"npm-license-crawler": "^0.2.1",
|
"npm-license-crawler": "^0.2.1",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "^1.18.0",
|
"prettier": "^1.18.2",
|
||||||
"protractor": "^5.4.2",
|
"protractor": "^5.4.2",
|
||||||
"resize-observer-polyfill": "^1.5.1",
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
"source-map-explorer": "^2.0.1",
|
"source-map-explorer": "^2.0.1",
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
<ng-container *osPerms="'agenda.can_manage'">
|
<ng-container *osPerms="'agenda.can_manage'">
|
||||||
<ng-container *ngIf="showForm">
|
<ng-container *ngIf="showForm">
|
||||||
<div [formGroup]="form">
|
<div [formGroup]="form">
|
||||||
<mat-checkbox formControlName="agenda_create">
|
<p>
|
||||||
<span translate>Add to agenda</span>
|
<mat-checkbox formControlName="agenda_create">
|
||||||
</mat-checkbox>
|
<span translate>Add to agenda</span>
|
||||||
|
</mat-checkbox>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-container *ngIf="!!checkbox.value">
|
<ng-container *ngIf="!!checkbox.value">
|
||||||
|
@ -3,39 +3,6 @@
|
|||||||
<div class="title-slot"><h2 translate>Motion blocks</h2></div>
|
<div class="title-slot"><h2 translate>Motion blocks</h2></div>
|
||||||
</os-head-bar>
|
</os-head-bar>
|
||||||
|
|
||||||
<!-- Creating a new motion block -->
|
|
||||||
<mat-card class="os-card" *ngIf="isCreatingNewBlock">
|
|
||||||
<mat-card-title translate>New motion block</mat-card-title>
|
|
||||||
<mat-card-content>
|
|
||||||
<form [formGroup]="createBlockForm" (keydown)="onKeyDown($event)">
|
|
||||||
<!-- Title -->
|
|
||||||
<p>
|
|
||||||
<mat-form-field>
|
|
||||||
<input formControlName="title" matInput placeholder="{{ 'Title' | translate }}" required />
|
|
||||||
<mat-error *ngIf="createBlockForm.get('title').hasError('required')" translate>
|
|
||||||
A title is required
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- Internal -->
|
|
||||||
<p>
|
|
||||||
<mat-checkbox formControlName="internal"><span translate>Internal</span></mat-checkbox>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<os-agenda-content-object-form [form]="createBlockForm"></os-agenda-content-object-form>
|
|
||||||
</form>
|
|
||||||
</mat-card-content>
|
|
||||||
|
|
||||||
<!-- Save and Cancel buttons -->
|
|
||||||
<mat-card-actions>
|
|
||||||
<button mat-button [disabled]="!createBlockForm.valid" (click)="onSaveNewButton()">
|
|
||||||
<span translate>Save</span>
|
|
||||||
</button>
|
|
||||||
<button mat-button (click)="onCancel()"><span translate>Cancel</span></button>
|
|
||||||
</mat-card-actions>
|
|
||||||
</mat-card>
|
|
||||||
|
|
||||||
<mat-card class="os-card">
|
<mat-card class="os-card">
|
||||||
<os-list-view-table
|
<os-list-view-table
|
||||||
class="block-list"
|
class="block-list"
|
||||||
@ -67,3 +34,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</os-list-view-table>
|
</os-list-view-table>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
|
||||||
|
<!-- Template for new motion block dialog -->
|
||||||
|
<ng-template #newMotionBlockDialog>
|
||||||
|
<h1 mat-dialog-title>
|
||||||
|
<span translate>New motion block</span>
|
||||||
|
</h1>
|
||||||
|
<form [formGroup]="createBlockForm" (keydown)="onKeyDown($event)">
|
||||||
|
<div mat-dialog-content>
|
||||||
|
<!-- Title -->
|
||||||
|
<mat-form-field>
|
||||||
|
<input formControlName="title" matInput placeholder="{{ 'Title' | translate }}" required />
|
||||||
|
<mat-error *ngIf="createBlockForm.get('title').hasError('required')" translate>
|
||||||
|
A title is required
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<!-- Internal -->
|
||||||
|
<p>
|
||||||
|
<mat-checkbox formControlName="internal"><span translate>Internal</span></mat-checkbox>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<os-agenda-content-object-form [form]="createBlockForm"></os-agenda-content-object-form>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div mat-dialog-actions>
|
||||||
|
<button mat-button [disabled]="!createBlockForm.valid" [mat-dialog-close]="true">
|
||||||
|
<span translate>Save</span>
|
||||||
|
</button>
|
||||||
|
<button mat-button [mat-dialog-close]="false"><span translate>Cancel</span></button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
@ -8,6 +8,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-form-field {
|
.mat-dialog-container {
|
||||||
width: 50%;
|
.mat-dialog-title {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
import { Component, OnInit, TemplateRef, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { MatDialog } from '@angular/material';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
@ -12,6 +13,7 @@ import { StorageService } from 'app/core/core-services/storage.service';
|
|||||||
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
|
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
|
||||||
import { MotionBlockRepositoryService } from 'app/core/repositories/motions/motion-block-repository.service';
|
import { MotionBlockRepositoryService } from 'app/core/repositories/motions/motion-block-repository.service';
|
||||||
import { MotionBlock } from 'app/shared/models/motions/motion-block';
|
import { MotionBlock } from 'app/shared/models/motions/motion-block';
|
||||||
|
import { infoDialogSettings } from 'app/shared/utils/dialog-settings';
|
||||||
import { ViewItem } from 'app/site/agenda/models/view-item';
|
import { ViewItem } from 'app/site/agenda/models/view-item';
|
||||||
import { BaseListViewComponent } from 'app/site/base/base-list-view';
|
import { BaseListViewComponent } from 'app/site/base/base-list-view';
|
||||||
import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
|
import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
|
||||||
@ -27,16 +29,14 @@ import { MotionBlockSortService } from 'app/site/motions/services/motion-block-s
|
|||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.None
|
||||||
})
|
})
|
||||||
export class MotionBlockListComponent extends BaseListViewComponent<ViewMotionBlock> implements OnInit {
|
export class MotionBlockListComponent extends BaseListViewComponent<ViewMotionBlock> implements OnInit {
|
||||||
|
@ViewChild('newMotionBlockDialog', { static: true })
|
||||||
|
private newMotionBlockDialog: TemplateRef<string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the create form
|
* Holds the create form
|
||||||
*/
|
*/
|
||||||
public createBlockForm: FormGroup;
|
public createBlockForm: FormGroup;
|
||||||
|
|
||||||
/**
|
|
||||||
* Flag, if the creation panel is open
|
|
||||||
*/
|
|
||||||
public isCreatingNewBlock = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the agenda items to select the parent item
|
* Holds the agenda items to select the parent item
|
||||||
*/
|
*/
|
||||||
@ -95,6 +95,7 @@ export class MotionBlockListComponent extends BaseListViewComponent<ViewMotionBl
|
|||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private itemRepo: ItemRepositoryService,
|
private itemRepo: ItemRepositoryService,
|
||||||
private operator: OperatorService,
|
private operator: OperatorService,
|
||||||
|
private dialog: MatDialog,
|
||||||
public sortService: MotionBlockSortService
|
public sortService: MotionBlockSortService
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar, storage);
|
super(titleService, translate, matSnackBar, storage);
|
||||||
@ -129,42 +130,37 @@ export class MotionBlockListComponent extends BaseListViewComponent<ViewMotionBl
|
|||||||
/**
|
/**
|
||||||
* Helper function reset the form and set the default values
|
* Helper function reset the form and set the default values
|
||||||
*/
|
*/
|
||||||
public resetForm(): void {
|
private resetForm(): void {
|
||||||
this.createBlockForm.reset();
|
this.createBlockForm.reset();
|
||||||
this.createBlockForm.get('agenda_type').setValue(this.defaultVisibility);
|
this.createBlockForm.get('agenda_type').setValue(this.defaultVisibility);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Click handler for the plus button
|
* Click handler for the plus button.
|
||||||
|
* Opens the dialog for motion block creation.
|
||||||
*/
|
*/
|
||||||
public onPlusButton(): void {
|
public onPlusButton(): void {
|
||||||
if (!this.isCreatingNewBlock) {
|
this.resetForm();
|
||||||
this.resetForm();
|
const dialogRef = this.dialog.open(this.newMotionBlockDialog, infoDialogSettings);
|
||||||
this.isCreatingNewBlock = true;
|
dialogRef.afterClosed().subscribe(res => {
|
||||||
}
|
if (res) {
|
||||||
|
this.save();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Click handler for the save button.
|
|
||||||
* Sends the block to create to the repository and resets the form.
|
* Sends the block to create to the repository and resets the form.
|
||||||
*/
|
*/
|
||||||
public async onSaveNewButton(): Promise<void> {
|
private save(): void {
|
||||||
if (this.createBlockForm.valid) {
|
if (this.createBlockForm.valid) {
|
||||||
const block = this.createBlockForm.value;
|
const block = this.createBlockForm.value;
|
||||||
if (!block.agenda_parent_id) {
|
if (!block.agenda_parent_id) {
|
||||||
delete block.agenda_parent_id;
|
delete block.agenda_parent_id;
|
||||||
}
|
}
|
||||||
|
this.repo.create(block).catch(this.raiseError);
|
||||||
try {
|
this.resetForm();
|
||||||
await this.repo.create(block);
|
|
||||||
this.resetForm();
|
|
||||||
this.isCreatingNewBlock = false;
|
|
||||||
} catch (e) {
|
|
||||||
this.raiseError(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// set a form control as "touched" to trigger potential error messages
|
|
||||||
this.createBlockForm.get('title').markAsTouched();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -175,17 +171,12 @@ export class MotionBlockListComponent extends BaseListViewComponent<ViewMotionBl
|
|||||||
*/
|
*/
|
||||||
public onKeyDown(event: KeyboardEvent): void {
|
public onKeyDown(event: KeyboardEvent): void {
|
||||||
if (event.key === 'Enter' && event.shiftKey) {
|
if (event.key === 'Enter' && event.shiftKey) {
|
||||||
this.onSaveNewButton();
|
this.save();
|
||||||
|
this.dialog.closeAll();
|
||||||
}
|
}
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
this.onCancel();
|
this.resetForm();
|
||||||
|
this.dialog.closeAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels the current form action
|
|
||||||
*/
|
|
||||||
public onCancel(): void {
|
|
||||||
this.isCreatingNewBlock = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<os-head-bar prevUrl="../.." [nav]="false" [mainButton]="true" (mainEvent)="onPlusButton()">
|
<os-head-bar prevUrl="../.." [nav]="false" [mainButton]="true" (mainEvent)="openDialog()">
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<div class="title-slot">
|
<div class="title-slot">
|
||||||
<h2 translate>Comment fields</h2>
|
<h2 translate>Comment fields</h2>
|
||||||
@ -13,53 +13,9 @@
|
|||||||
</os-head-bar>
|
</os-head-bar>
|
||||||
|
|
||||||
<div class="head-spacer"></div>
|
<div class="head-spacer"></div>
|
||||||
<mat-card *ngIf="commentSectionToCreate">
|
|
||||||
<mat-card-title translate>New comment field</mat-card-title>
|
|
||||||
<mat-card-content>
|
|
||||||
<form [formGroup]="createForm" (keydown)="keyDownFunction($event)">
|
|
||||||
<p>
|
|
||||||
<mat-form-field>
|
|
||||||
<input formControlName="name" matInput placeholder="{{ 'Name' | translate }}" required />
|
|
||||||
<mat-error *ngIf="!createForm.controls.name.valid">
|
|
||||||
<span translate>Required</span>
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<os-search-value-selector
|
|
||||||
ngDefaultControl
|
|
||||||
[formControl]="this.createForm.get('read_groups_id')"
|
|
||||||
[multiple]="true"
|
|
||||||
listname="Groups with read permissions"
|
|
||||||
[InputListValues]="this.groups"
|
|
||||||
></os-search-value-selector>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<os-search-value-selector
|
|
||||||
ngDefaultControl
|
|
||||||
[formControl]="this.createForm.get('write_groups_id')"
|
|
||||||
[multiple]="true"
|
|
||||||
listname="Groups with write permissions"
|
|
||||||
[InputListValues]="this.groups"
|
|
||||||
></os-search-value-selector>
|
|
||||||
</p>
|
|
||||||
</form>
|
|
||||||
</mat-card-content>
|
|
||||||
<mat-card-actions>
|
|
||||||
<button [disabled]="createForm.invalid" mat-button (click)="create()">
|
|
||||||
<span translate>Create</span>
|
|
||||||
</button>
|
|
||||||
<button mat-button (click)="commentSectionToCreate = null">
|
|
||||||
<span translate>Cancel</span>
|
|
||||||
</button>
|
|
||||||
</mat-card-actions>
|
|
||||||
</mat-card>
|
|
||||||
<mat-accordion class="os-card">
|
<mat-accordion class="os-card">
|
||||||
<mat-expansion-panel
|
<mat-expansion-panel
|
||||||
*ngFor="let section of this.commentSections"
|
*ngFor="let section of this.commentSections"
|
||||||
(opened)="openId = section.id"
|
|
||||||
(closed)="panelClosed(section)"
|
|
||||||
[expanded]="openId === section.id"
|
|
||||||
multiple="false"
|
multiple="false"
|
||||||
>
|
>
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
@ -87,59 +43,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</mat-panel-title>
|
</mat-panel-title>
|
||||||
</mat-expansion-panel-header>
|
</mat-expansion-panel-header>
|
||||||
<form [formGroup]="updateForm" *ngIf="editId === section.id" (keydown)="keyDownFunction($event, section)">
|
<h3 translate>Name</h3>
|
||||||
<span translate>Edit comment field:</span>
|
<div class="spacer-left">{{ section.name }}</div>
|
||||||
<p>
|
<h3 translate>Groups with read permissions</h3>
|
||||||
<mat-form-field>
|
<ul *ngFor="let group of section.read_groups">
|
||||||
<input formControlName="name" matInput placeholder="{{ 'Name' | translate }}" required />
|
<li>{{ group.getTitle() }}</li>
|
||||||
<mat-error *ngIf="!updateForm.controls.name.valid">
|
</ul>
|
||||||
<span translate>Required</span>
|
<div class="spacer-left" *ngIf="section.read_groups.length === 0" translate>No groups selected</div>
|
||||||
</mat-error>
|
<h3 translate>Groups with write permissions</h3>
|
||||||
</mat-form-field>
|
<ul *ngFor="let group of section.write_groups">
|
||||||
</p>
|
<li>{{ group.getTitle() }}</li>
|
||||||
<p>
|
</ul>
|
||||||
<os-search-value-selector
|
<div class="spacer-left" *ngIf="section.write_groups.length === 0" translate>No groups selected</div>
|
||||||
ngDefaultControl
|
|
||||||
[formControl]="this.updateForm.get('read_groups_id')"
|
|
||||||
[multiple]="true"
|
|
||||||
listname="Groups with read permissions"
|
|
||||||
[InputListValues]="this.groups"
|
|
||||||
></os-search-value-selector>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<os-search-value-selector
|
|
||||||
ngDefaultControl
|
|
||||||
[formControl]="this.updateForm.get('write_groups_id')"
|
|
||||||
[multiple]="true"
|
|
||||||
listname="Groups with write permissions"
|
|
||||||
[InputListValues]="this.groups"
|
|
||||||
></os-search-value-selector>
|
|
||||||
</p>
|
|
||||||
</form>
|
|
||||||
<ng-container *ngIf="editId !== section.id">
|
|
||||||
<h3 translate>Name</h3>
|
|
||||||
<div class="spacer-left">{{ section.name }}</div>
|
|
||||||
<h3 translate>Groups with read permissions</h3>
|
|
||||||
<ul *ngFor="let group of section.read_groups">
|
|
||||||
<li>{{ group.getTitle() }}</li>
|
|
||||||
</ul>
|
|
||||||
<div class="spacer-left" *ngIf="section.read_groups.length === 0" translate>No groups selected</div>
|
|
||||||
<h3 translate>Groups with write permissions</h3>
|
|
||||||
<ul *ngFor="let group of section.write_groups">
|
|
||||||
<li>{{ group.getTitle() }}</li>
|
|
||||||
</ul>
|
|
||||||
<div class="spacer-left" *ngIf="section.write_groups.length === 0" translate>No groups selected</div>
|
|
||||||
</ng-container>
|
|
||||||
<mat-action-row>
|
<mat-action-row>
|
||||||
<button *ngIf="editId !== section.id" mat-button (click)="onEditButton(section)" mat-icon-button>
|
<button mat-button (click)="openDialog(section)" mat-icon-button>
|
||||||
<mat-icon>edit</mat-icon>
|
<mat-icon>edit</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<button *ngIf="editId === section.id" mat-button (click)="editId = null" mat-icon-button>
|
|
||||||
<mat-icon>cancel</mat-icon>
|
|
||||||
</button>
|
|
||||||
<button *ngIf="editId === section.id" [disabled]="updateForm.invalid" mat-button (click)="onSaveButton(section)" mat-icon-button>
|
|
||||||
<mat-icon>save</mat-icon>
|
|
||||||
</button>
|
|
||||||
<button mat-button (click)="onDeleteButton(section)" mat-icon-button>
|
<button mat-button (click)="onDeleteButton(section)" mat-icon-button>
|
||||||
<mat-icon>delete</mat-icon>
|
<mat-icon>delete</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
@ -153,3 +72,49 @@
|
|||||||
<span translate>Sort</span>
|
<span translate>Sort</span>
|
||||||
</button>
|
</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
|
<!-- Template for motion comment dialog -->
|
||||||
|
<ng-template #motionCommentDialog>
|
||||||
|
<h1 mat-dialog-title>
|
||||||
|
<span *ngIf="currentComment" translate>Edit comment field</span>
|
||||||
|
<span *ngIf="!currentComment" translate>New comment field</span>
|
||||||
|
</h1>
|
||||||
|
<div class="os-form-card-mobile" mat-dialog-content>
|
||||||
|
<form [formGroup]="commentFieldForm" (keydown)="onKeyDown($event)">
|
||||||
|
<p>
|
||||||
|
<mat-form-field>
|
||||||
|
<input formControlName="name" matInput placeholder="{{ 'Name' | translate }}" required />
|
||||||
|
<mat-error *ngIf="!commentFieldForm.controls.name.valid">
|
||||||
|
<span translate>Required</span>
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<os-search-value-selector
|
||||||
|
ngDefaultControl
|
||||||
|
[formControl]="commentFieldForm.get('read_groups_id')"
|
||||||
|
[multiple]="true"
|
||||||
|
listname="Groups with read permissions"
|
||||||
|
[InputListValues]="groups"
|
||||||
|
></os-search-value-selector>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<os-search-value-selector
|
||||||
|
ngDefaultControl
|
||||||
|
[formControl]="commentFieldForm.get('write_groups_id')"
|
||||||
|
[multiple]="true"
|
||||||
|
listname="Groups with write permissions"
|
||||||
|
[InputListValues]="groups"
|
||||||
|
></os-search-value-selector>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div mat-dialog-actions>
|
||||||
|
<button [disabled]="commentFieldForm.invalid" mat-button [mat-dialog-close]="true">
|
||||||
|
<span translate>Save</span>
|
||||||
|
</button>
|
||||||
|
<button mat-button [mat-dialog-close]="false">
|
||||||
|
<span translate>Cancel</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { MatDialog } from '@angular/material';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
@ -10,6 +11,7 @@ import { MotionCommentSectionRepositoryService } from 'app/core/repositories/mot
|
|||||||
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
|
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
import { MotionCommentSection } from 'app/shared/models/motions/motion-comment-section';
|
import { MotionCommentSection } from 'app/shared/models/motions/motion-comment-section';
|
||||||
|
import { infoDialogSettings } from 'app/shared/utils/dialog-settings';
|
||||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||||
import { ViewMotionCommentSection } from 'app/site/motions/models/view-motion-comment-section';
|
import { ViewMotionCommentSection } from 'app/site/motions/models/view-motion-comment-section';
|
||||||
import { ViewGroup } from 'app/site/users/models/view-group';
|
import { ViewGroup } from 'app/site/users/models/view-group';
|
||||||
@ -23,7 +25,10 @@ import { ViewGroup } from 'app/site/users/models/view-group';
|
|||||||
styleUrls: ['./motion-comment-section-list.component.scss']
|
styleUrls: ['./motion-comment-section-list.component.scss']
|
||||||
})
|
})
|
||||||
export class MotionCommentSectionListComponent extends BaseViewComponent implements OnInit {
|
export class MotionCommentSectionListComponent extends BaseViewComponent implements OnInit {
|
||||||
public commentSectionToCreate: MotionCommentSection | null;
|
@ViewChild('motionCommentDialog', { static: true })
|
||||||
|
private motionCommentDialog: TemplateRef<string>;
|
||||||
|
|
||||||
|
public currentComment: ViewMotionCommentSection | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Source of the Data
|
* Source of the Data
|
||||||
@ -31,14 +36,9 @@ export class MotionCommentSectionListComponent extends BaseViewComponent impleme
|
|||||||
public commentSections: ViewMotionCommentSection[] = [];
|
public commentSections: ViewMotionCommentSection[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current focussed formgroup
|
* formgroup for editing and creating of comments
|
||||||
*/
|
*/
|
||||||
public updateForm: FormGroup;
|
public commentFieldForm: FormGroup;
|
||||||
|
|
||||||
public createForm: FormGroup;
|
|
||||||
|
|
||||||
public openId: number | null;
|
|
||||||
public editId: number | null;
|
|
||||||
|
|
||||||
public groups: BehaviorSubject<ViewGroup[]>;
|
public groups: BehaviorSubject<ViewGroup[]>;
|
||||||
|
|
||||||
@ -59,6 +59,7 @@ export class MotionCommentSectionListComponent extends BaseViewComponent impleme
|
|||||||
private repo: MotionCommentSectionRepositoryService,
|
private repo: MotionCommentSectionRepositoryService,
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private promptService: PromptService,
|
private promptService: PromptService,
|
||||||
|
private dialog: MatDialog,
|
||||||
private groupRepo: GroupRepositoryService
|
private groupRepo: GroupRepositoryService
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar);
|
super(titleService, translate, matSnackBar);
|
||||||
@ -68,8 +69,7 @@ export class MotionCommentSectionListComponent extends BaseViewComponent impleme
|
|||||||
read_groups_id: [[]],
|
read_groups_id: [[]],
|
||||||
write_groups_id: [[]]
|
write_groups_id: [[]]
|
||||||
};
|
};
|
||||||
this.createForm = this.formBuilder.group(form);
|
this.commentFieldForm = this.formBuilder.group(form);
|
||||||
this.updateForm = this.formBuilder.group(form);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,78 +82,54 @@ export class MotionCommentSectionListComponent extends BaseViewComponent impleme
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event on Key Down in update or create form.
|
* Event on Key Down in form.
|
||||||
*
|
*
|
||||||
* @param event the keyboard event
|
* @param event the keyboard event
|
||||||
* @param the current view in scope
|
* @param the current view in scope
|
||||||
*/
|
*/
|
||||||
public keyDownFunction(event: KeyboardEvent, viewSection?: ViewMotionCommentSection): void {
|
public onKeyDown(event: KeyboardEvent, viewSection?: ViewMotionCommentSection): void {
|
||||||
if (event.key === 'Enter' && event.shiftKey) {
|
if (event.key === 'Enter' && event.shiftKey) {
|
||||||
if (viewSection) {
|
this.save();
|
||||||
this.onSaveButton(viewSection);
|
this.dialog.closeAll();
|
||||||
} else {
|
|
||||||
this.create();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
if (viewSection) {
|
this.dialog.closeAll();
|
||||||
this.editId = null;
|
}
|
||||||
} else {
|
}
|
||||||
this.commentSectionToCreate = null;
|
|
||||||
|
/**
|
||||||
|
* Opens the create dialog.
|
||||||
|
*/
|
||||||
|
public openDialog(commentSection?: ViewMotionCommentSection): void {
|
||||||
|
this.currentComment = commentSection;
|
||||||
|
this.commentFieldForm.reset({
|
||||||
|
name: commentSection ? commentSection.name : '',
|
||||||
|
read_groups_id: commentSection ? commentSection.read_groups_id : [],
|
||||||
|
write_groups_id: commentSection ? commentSection.write_groups_id : []
|
||||||
|
});
|
||||||
|
const dialogRef = this.dialog.open(this.motionCommentDialog, infoDialogSettings);
|
||||||
|
dialogRef.afterClosed().subscribe(res => {
|
||||||
|
if (res) {
|
||||||
|
this.save();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the create form.
|
|
||||||
*/
|
|
||||||
public onPlusButton(): void {
|
|
||||||
if (!this.commentSectionToCreate) {
|
|
||||||
this.commentSectionToCreate = new MotionCommentSection();
|
|
||||||
this.createForm.setValue({
|
|
||||||
name: '',
|
|
||||||
read_groups_id: [],
|
|
||||||
write_groups_id: []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the comment section from the create form.
|
|
||||||
*/
|
|
||||||
public create(): void {
|
|
||||||
if (this.createForm.valid) {
|
|
||||||
this.commentSectionToCreate.patchValues(this.createForm.value as MotionCommentSection);
|
|
||||||
this.repo
|
|
||||||
.create(this.commentSectionToCreate)
|
|
||||||
.then(() => (this.commentSectionToCreate = null), this.raiseError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executed on edit button
|
|
||||||
* @param viewSection
|
|
||||||
*/
|
|
||||||
public onEditButton(viewSection: ViewMotionCommentSection): void {
|
|
||||||
this.editId = viewSection.id;
|
|
||||||
|
|
||||||
this.updateForm.setValue({
|
|
||||||
name: viewSection.name,
|
|
||||||
read_groups_id: viewSection.read_groups_id,
|
|
||||||
write_groups_id: viewSection.write_groups_id
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the comment section
|
* saves the current data, either updating an existing comment or creating a new one.
|
||||||
*
|
|
||||||
* @param viewSection The section to save
|
|
||||||
*/
|
*/
|
||||||
public onSaveButton(viewSection: ViewMotionCommentSection): void {
|
private save(): void {
|
||||||
if (this.updateForm.valid) {
|
if (this.commentFieldForm.valid) {
|
||||||
this.repo.update(this.updateForm.value as Partial<MotionCommentSection>, viewSection).then(() => {
|
// eiher update or create
|
||||||
this.openId = this.editId = null;
|
if (this.currentComment) {
|
||||||
}, this.raiseError);
|
this.repo
|
||||||
|
.update(this.commentFieldForm.value as Partial<MotionCommentSection>, this.currentComment)
|
||||||
|
.catch(this.raiseError);
|
||||||
|
} else {
|
||||||
|
const comment = new MotionCommentSection(this.commentFieldForm.value);
|
||||||
|
this.repo.create(comment).catch(this.raiseError);
|
||||||
|
}
|
||||||
|
this.commentFieldForm.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,18 +141,7 @@ export class MotionCommentSectionListComponent extends BaseViewComponent impleme
|
|||||||
const title = this.translate.instant('Are you sure you want to delete this comment field?');
|
const title = this.translate.instant('Are you sure you want to delete this comment field?');
|
||||||
const content = viewSection.name;
|
const content = viewSection.name;
|
||||||
if (await this.promptService.open(title, content)) {
|
if (await this.promptService.open(title, content)) {
|
||||||
this.repo.delete(viewSection).then(() => (this.openId = this.editId = null), this.raiseError);
|
this.repo.delete(viewSection).catch(this.raiseError);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is executed when a mat-extension-panel is closed
|
|
||||||
* @param viewSection the section in the panel
|
|
||||||
*/
|
|
||||||
public panelClosed(viewSection: ViewMotionCommentSection): void {
|
|
||||||
this.openId = null;
|
|
||||||
if (this.editId) {
|
|
||||||
this.onSaveButton(viewSection);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<os-head-bar prevUrl="../.." [nav]="false" [mainButton]="true" (mainEvent)="onPlusButton()">
|
<os-head-bar prevUrl="../.." [nav]="false" [mainButton]="true" (mainEvent)="openDialog()">
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<div class="title-slot">
|
<div class="title-slot">
|
||||||
<h2 translate>Statute</h2>
|
<h2 translate>Statute</h2>
|
||||||
@ -12,42 +12,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</os-head-bar>
|
</os-head-bar>
|
||||||
|
|
||||||
<mat-card *ngIf="statuteParagraphToCreate">
|
<cdk-virtual-scroll-viewport itemSize="50" [ngClass]="statuteParagraphs.length ? 'virtual-scroll-full-page' : ''">
|
||||||
<mat-card-title translate>New statute paragraph</mat-card-title>
|
|
||||||
<mat-card-content>
|
|
||||||
<form [formGroup]="createForm" (keydown)="onKeyDownCreate($event)">
|
|
||||||
<p>
|
|
||||||
<mat-form-field>
|
|
||||||
<input formControlName="title" matInput placeholder="{{ 'Title' | translate }}" required />
|
|
||||||
<mat-hint *ngIf="!createForm.controls.title.valid">
|
|
||||||
<span translate>Required</span>
|
|
||||||
</mat-hint>
|
|
||||||
</mat-form-field>
|
|
||||||
</p>
|
|
||||||
<span>
|
|
||||||
<!-- The HTML Editor -->
|
|
||||||
<h4 translate>Statute paragraph</h4>
|
|
||||||
<editor formControlName="text" [init]="tinyMceSettings"></editor>
|
|
||||||
</span>
|
|
||||||
</form>
|
|
||||||
</mat-card-content>
|
|
||||||
<mat-card-actions>
|
|
||||||
<button mat-button (click)="create()">
|
|
||||||
<span translate>Save</span>
|
|
||||||
</button>
|
|
||||||
<button mat-button (click)="onCancelCreate()">
|
|
||||||
<span translate>Cancel</span>
|
|
||||||
</button>
|
|
||||||
</mat-card-actions>
|
|
||||||
</mat-card>
|
|
||||||
|
|
||||||
<cdk-virtual-scroll-viewport itemSize="50" class="virtual-scroll-full-page">
|
|
||||||
<mat-accordion class="os-card">
|
<mat-accordion class="os-card">
|
||||||
<mat-expansion-panel
|
<mat-expansion-panel
|
||||||
*cdkVirtualFor="let statuteParagraph of statuteParagraphs"
|
*cdkVirtualFor="let statuteParagraph of statuteParagraphs"
|
||||||
(opened)="openId = statuteParagraph.id"
|
|
||||||
(closed)="panelClosed(statuteParagraph)"
|
|
||||||
[expanded]="openId === statuteParagraph.id"
|
|
||||||
multiple="false"
|
multiple="false"
|
||||||
>
|
>
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
@ -55,51 +23,17 @@
|
|||||||
{{ statuteParagraph.title }}
|
{{ statuteParagraph.title }}
|
||||||
</mat-panel-title>
|
</mat-panel-title>
|
||||||
</mat-expansion-panel-header>
|
</mat-expansion-panel-header>
|
||||||
<form [formGroup]="updateForm" *ngIf="editId === statuteParagraph.id" (keydown)="onKeyDownUpdate($event)">
|
<mat-card>
|
||||||
<span translate>Edit statute paragraph:</span>
|
<mat-card-title>{{ statuteParagraph.title }}</mat-card-title>
|
||||||
<p>
|
<mat-card-content>
|
||||||
<mat-form-field>
|
<div [innerHTML]="statuteParagraph.text"></div>
|
||||||
<input formControlName="title" matInput placeholder="{{ 'Title' | translate }}" required />
|
</mat-card-content>
|
||||||
<mat-hint *ngIf="!updateForm.controls.title.valid">
|
</mat-card>
|
||||||
<span translate>Required</span>
|
|
||||||
</mat-hint>
|
|
||||||
</mat-form-field>
|
|
||||||
</p>
|
|
||||||
<span>
|
|
||||||
<!-- The HTML Editor -->
|
|
||||||
<h4 translate>Statute paragraph</h4>
|
|
||||||
<editor formControlName="text" [init]="tinyMceSettings"></editor>
|
|
||||||
</span>
|
|
||||||
</form>
|
|
||||||
<ng-container *ngIf="editId !== statuteParagraph.id">
|
|
||||||
<mat-card>
|
|
||||||
<mat-card-title>{{ statuteParagraph.title }}</mat-card-title>
|
|
||||||
<mat-card-content>
|
|
||||||
<div [innerHTML]="statuteParagraph.text"></div>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
</ng-container>
|
|
||||||
<mat-action-row>
|
<mat-action-row>
|
||||||
<button
|
<button mat-button mat-icon-button (click)="openDialog(statuteParagraph)">
|
||||||
*ngIf="editId !== statuteParagraph.id"
|
|
||||||
mat-button
|
|
||||||
(click)="onEditButton(statuteParagraph)"
|
|
||||||
mat-icon-button
|
|
||||||
>
|
|
||||||
<mat-icon>edit</mat-icon>
|
<mat-icon>edit</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<button *ngIf="editId === statuteParagraph.id" mat-button (click)="onCancelUpdate()" mat-icon-button>
|
<button mat-button mat-icon-button (click)="onDeleteButton(statuteParagraph)">
|
||||||
<mat-icon>close</mat-icon>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
*ngIf="editId === statuteParagraph.id"
|
|
||||||
mat-button
|
|
||||||
(click)="onSaveButton(statuteParagraph)"
|
|
||||||
mat-icon-button
|
|
||||||
>
|
|
||||||
<mat-icon>save</mat-icon>
|
|
||||||
</button>
|
|
||||||
<button mat-button (click)="onDeleteButton(statuteParagraph)" mat-icon-button>
|
|
||||||
<mat-icon>delete</mat-icon>
|
<mat-icon>delete</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</mat-action-row>
|
</mat-action-row>
|
||||||
@ -123,3 +57,36 @@
|
|||||||
<span translate>Import</span><span> ...</span>
|
<span translate>Import</span><span> ...</span>
|
||||||
</button>
|
</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
|
<!-- Template for statute paragraph dialog -->
|
||||||
|
<ng-template #statuteParagraphDialog>
|
||||||
|
<h1 mat-dialog-title>
|
||||||
|
<span *ngIf="currentStatuteParagraph" translate>Edit statute paragraph</span>
|
||||||
|
<span *ngIf="!currentStatuteParagraph" translate>New statute paragraph</span>
|
||||||
|
</h1>
|
||||||
|
<div class="os-form-card-mobile" mat-dialog-content>
|
||||||
|
<form [formGroup]="statuteParagraphForm" (keydown)="onKeyDown($event)">
|
||||||
|
<p>
|
||||||
|
<mat-form-field>
|
||||||
|
<input formControlName="title" matInput placeholder="{{ 'Title' | translate }}" required />
|
||||||
|
<mat-hint *ngIf="!statuteParagraphForm.controls.title.valid">
|
||||||
|
<span translate>Required</span>
|
||||||
|
</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
</p>
|
||||||
|
<span>
|
||||||
|
<!-- The HTML Editor -->
|
||||||
|
<h4 translate>Statute paragraph</h4>
|
||||||
|
<editor formControlName="text" [init]="tinyMceSettings"></editor>
|
||||||
|
</span>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div mat-dialog-actions>
|
||||||
|
<button mat-button [mat-dialog-close]="true" [disabled]="!statuteParagraphForm.valid">
|
||||||
|
<span translate>Save</span>
|
||||||
|
</button>
|
||||||
|
<button mat-button [mat-dialog-close]="false">
|
||||||
|
<span translate>Cancel</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { MatDialog } from '@angular/material';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
@ -8,6 +9,7 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
import { StatuteParagraphRepositoryService } from 'app/core/repositories/motions/statute-paragraph-repository.service';
|
import { StatuteParagraphRepositoryService } from 'app/core/repositories/motions/statute-paragraph-repository.service';
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
import { StatuteParagraph } from 'app/shared/models/motions/statute-paragraph';
|
import { StatuteParagraph } from 'app/shared/models/motions/statute-paragraph';
|
||||||
|
import { largeDialogSettings } from 'app/shared/utils/dialog-settings';
|
||||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||||
import { ViewStatuteParagraph } from 'app/site/motions/models/view-statute-paragraph';
|
import { ViewStatuteParagraph } from 'app/site/motions/models/view-statute-paragraph';
|
||||||
import { StatuteCsvExportService } from 'app/site/motions/services/statute-csv-export.service';
|
import { StatuteCsvExportService } from 'app/site/motions/services/statute-csv-export.service';
|
||||||
@ -21,7 +23,10 @@ import { StatuteCsvExportService } from 'app/site/motions/services/statute-csv-e
|
|||||||
styleUrls: ['./statute-paragraph-list.component.scss']
|
styleUrls: ['./statute-paragraph-list.component.scss']
|
||||||
})
|
})
|
||||||
export class StatuteParagraphListComponent extends BaseViewComponent implements OnInit {
|
export class StatuteParagraphListComponent extends BaseViewComponent implements OnInit {
|
||||||
public statuteParagraphToCreate: StatuteParagraph | null;
|
@ViewChild('statuteParagraphDialog', { static: true })
|
||||||
|
private statuteParagraphDialog: TemplateRef<string>;
|
||||||
|
|
||||||
|
private currentStatuteParagraph: ViewStatuteParagraph | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Source of the Data
|
* Source of the Data
|
||||||
@ -29,14 +34,9 @@ export class StatuteParagraphListComponent extends BaseViewComponent implements
|
|||||||
public statuteParagraphs: ViewStatuteParagraph[] = [];
|
public statuteParagraphs: ViewStatuteParagraph[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current focussed formgroup
|
* Formgroup for creating and updating of statute paragraphs
|
||||||
*/
|
*/
|
||||||
public updateForm: FormGroup;
|
public statuteParagraphForm: FormGroup;
|
||||||
|
|
||||||
public createForm: FormGroup;
|
|
||||||
|
|
||||||
public openId: number | null;
|
|
||||||
public editId: number | null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The usual component constructor. Initializes the forms
|
* The usual component constructor. Initializes the forms
|
||||||
@ -56,6 +56,7 @@ export class StatuteParagraphListComponent extends BaseViewComponent implements
|
|||||||
private repo: StatuteParagraphRepositoryService,
|
private repo: StatuteParagraphRepositoryService,
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private promptService: PromptService,
|
private promptService: PromptService,
|
||||||
|
private dialog: MatDialog,
|
||||||
private csvExportService: StatuteCsvExportService
|
private csvExportService: StatuteCsvExportService
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar);
|
super(titleService, translate, matSnackBar);
|
||||||
@ -64,8 +65,7 @@ export class StatuteParagraphListComponent extends BaseViewComponent implements
|
|||||||
title: ['', Validators.required],
|
title: ['', Validators.required],
|
||||||
text: ['', Validators.required]
|
text: ['', Validators.required]
|
||||||
};
|
};
|
||||||
this.createForm = this.formBuilder.group(form);
|
this.statuteParagraphForm = this.formBuilder.group(form);
|
||||||
this.updateForm = this.formBuilder.group(form);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -81,53 +81,40 @@ export class StatuteParagraphListComponent extends BaseViewComponent implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new Section.
|
* Open the modal dialog
|
||||||
*/
|
*/
|
||||||
public onPlusButton(): void {
|
public openDialog(paragraph?: ViewStatuteParagraph): void {
|
||||||
if (!this.statuteParagraphToCreate) {
|
this.currentStatuteParagraph = paragraph;
|
||||||
this.createForm.reset();
|
this.statuteParagraphForm.reset();
|
||||||
this.createForm.setValue({
|
if (paragraph) {
|
||||||
title: '',
|
this.statuteParagraphForm.setValue({
|
||||||
text: ''
|
title: paragraph.title,
|
||||||
|
text: paragraph.text
|
||||||
});
|
});
|
||||||
this.statuteParagraphToCreate = new StatuteParagraph();
|
|
||||||
}
|
}
|
||||||
}
|
const dialogRef = this.dialog.open(this.statuteParagraphDialog, largeDialogSettings);
|
||||||
|
dialogRef.afterClosed().subscribe(res => {
|
||||||
/**
|
if (res) {
|
||||||
* Handler when clicking on create to create a new statute paragraph
|
this.save();
|
||||||
*/
|
}
|
||||||
public create(): void {
|
|
||||||
if (this.createForm.valid) {
|
|
||||||
this.statuteParagraphToCreate.patchValues(this.createForm.value as StatuteParagraph);
|
|
||||||
this.repo.create(this.statuteParagraphToCreate).then(() => {
|
|
||||||
this.statuteParagraphToCreate = null;
|
|
||||||
}, this.raiseError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executed on edit button
|
|
||||||
* @param viewStatuteParagraph
|
|
||||||
*/
|
|
||||||
public onEditButton(viewStatuteParagraph: ViewStatuteParagraph): void {
|
|
||||||
this.editId = viewStatuteParagraph.id;
|
|
||||||
|
|
||||||
this.updateForm.setValue({
|
|
||||||
title: viewStatuteParagraph.title,
|
|
||||||
text: viewStatuteParagraph.text
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the statute paragraph
|
* creates a new statute paragraph or updates the current one
|
||||||
* @param viewStatuteParagraph The statute paragraph to save
|
|
||||||
*/
|
*/
|
||||||
public onSaveButton(viewStatuteParagraph: ViewStatuteParagraph): void {
|
private save(): void {
|
||||||
if (this.updateForm.valid) {
|
if (this.statuteParagraphForm.valid) {
|
||||||
this.repo.update(this.updateForm.value as Partial<StatuteParagraph>, viewStatuteParagraph).then(() => {
|
// eiher update or create
|
||||||
this.openId = this.editId = null;
|
if (this.currentStatuteParagraph) {
|
||||||
}, this.raiseError);
|
this.repo
|
||||||
|
.update(this.statuteParagraphForm.value as Partial<StatuteParagraph>, this.currentStatuteParagraph)
|
||||||
|
.catch(this.raiseError);
|
||||||
|
} else {
|
||||||
|
const paragraph = new StatuteParagraph(this.statuteParagraphForm.value);
|
||||||
|
this.repo.create(paragraph).catch(this.raiseError);
|
||||||
|
}
|
||||||
|
this.statuteParagraphForm.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,18 +126,7 @@ export class StatuteParagraphListComponent extends BaseViewComponent implements
|
|||||||
const title = this.translate.instant('Are you sure you want to delete this statute paragraph?');
|
const title = this.translate.instant('Are you sure you want to delete this statute paragraph?');
|
||||||
const content = viewStatuteParagraph.title;
|
const content = viewStatuteParagraph.title;
|
||||||
if (await this.promptService.open(title, content)) {
|
if (await this.promptService.open(title, content)) {
|
||||||
this.repo.delete(viewStatuteParagraph).then(() => (this.openId = this.editId = null), this.raiseError);
|
this.repo.delete(viewStatuteParagraph).catch(this.raiseError);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is executed when a mat-extension-panel is closed
|
|
||||||
* @param viewStatuteParagraph the statute paragraph in the panel
|
|
||||||
*/
|
|
||||||
public panelClosed(viewStatuteParagraph: ViewStatuteParagraph): void {
|
|
||||||
this.openId = null;
|
|
||||||
if (this.editId) {
|
|
||||||
this.onSaveButton(viewStatuteParagraph);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,45 +143,16 @@ export class StatuteParagraphListComponent extends BaseViewComponent implements
|
|||||||
*
|
*
|
||||||
* @param event has the code
|
* @param event has the code
|
||||||
*/
|
*/
|
||||||
public onKeyDownCreate(event: KeyboardEvent): void {
|
public onKeyDown(event: KeyboardEvent): void {
|
||||||
if (event.key === 'Enter' && event.shiftKey) {
|
if (event.key === 'Enter' && event.shiftKey) {
|
||||||
this.create();
|
this.save();
|
||||||
|
this.dialog.closeAll();
|
||||||
}
|
}
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
this.onCancelCreate();
|
this.dialog.closeAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels the current form action
|
|
||||||
*/
|
|
||||||
public onCancelCreate(): void {
|
|
||||||
this.statuteParagraphToCreate = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* clicking Shift and Enter will save automatically
|
|
||||||
* clicking Escape will cancel the process
|
|
||||||
*
|
|
||||||
* @param event has the code
|
|
||||||
*/
|
|
||||||
public onKeyDownUpdate(event: KeyboardEvent): void {
|
|
||||||
if (event.key === 'Enter' && event.shiftKey) {
|
|
||||||
const myParagraph = this.statuteParagraphs.find(x => x.id === this.editId);
|
|
||||||
this.onSaveButton(myParagraph);
|
|
||||||
}
|
|
||||||
if (event.key === 'Escape') {
|
|
||||||
this.onCancelUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels the current form action
|
|
||||||
*/
|
|
||||||
public onCancelUpdate(): void {
|
|
||||||
this.editId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers a csv export of the statute paragraphs
|
* Triggers a csv export of the statute paragraphs
|
||||||
*/
|
*/
|
||||||
|
@ -1,37 +1,10 @@
|
|||||||
<os-head-bar
|
<os-head-bar
|
||||||
[mainButton]="true"
|
[mainButton]="true"
|
||||||
[nav]="true"
|
[nav]="true"
|
||||||
[editMode]="editTag"
|
(mainEvent)="openTagDialog()"
|
||||||
[isSaveButtonEnabled]="tagForm.valid"
|
|
||||||
(mainEvent)="setEditMode(!editTag)"
|
|
||||||
(saveEvent)="saveTag()"
|
|
||||||
[multiSelectMode]="isMultiSelect"
|
[multiSelectMode]="isMultiSelect"
|
||||||
>
|
>
|
||||||
<!-- Title -->
|
<div class="title-slot"><h2 translate>Tags</h2></div>
|
||||||
<div class="title-slot">
|
|
||||||
<h2 *ngIf="!editTag && !newTag" translate>Tags</h2>
|
|
||||||
<form *ngIf="editTag" [formGroup]="tagForm" (keydown)="keyDownFunction($event)">
|
|
||||||
<mat-form-field>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
matInput
|
|
||||||
osAutofocus
|
|
||||||
required
|
|
||||||
formControlName="name"
|
|
||||||
placeholder="{{ 'New tag name' | translate }}"
|
|
||||||
/>
|
|
||||||
<mat-error *ngIf="tagForm.invalid" translate>Required</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- remove button -->
|
|
||||||
<div class="extra-controls-slot">
|
|
||||||
<button type="button" mat-button *ngIf="!isMultiSelect && editTag && !newTag" (click)="deleteSelectedTag()">
|
|
||||||
<mat-icon>delete</mat-icon>
|
|
||||||
<span translate>Delete</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</os-head-bar>
|
</os-head-bar>
|
||||||
|
|
||||||
<os-list-view-table
|
<os-list-view-table
|
||||||
@ -42,9 +15,45 @@
|
|||||||
(dataSourceChange)="onDataSourceChange($event)"
|
(dataSourceChange)="onDataSourceChange($event)"
|
||||||
>
|
>
|
||||||
<!-- Name column -->
|
<!-- Name column -->
|
||||||
<div *pblNgridCellDef="'name'; value as name; row as tag" class="cell-slot fill clickable" (click)="selectTag(tag)">
|
<div *pblNgridCellDef="'name'; value as name" class="cell-slot fill">
|
||||||
<div>
|
<div>
|
||||||
{{ name }}
|
{{ name }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- edit column -->
|
||||||
|
<div *pblNgridCellDef="'edit'; row as tag" class="cell-slot fill">
|
||||||
|
<button mat-icon-button (click)="openTagDialog(tag)">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<!-- delete column -->
|
||||||
|
<div *pblNgridCellDef="'delete'; row as tag" class="cell-slot fill">
|
||||||
|
<button mat-icon-button color="warn" (click)="onDeleteButton(tag)">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</os-list-view-table>
|
</os-list-view-table>
|
||||||
|
|
||||||
|
<!-- Template for dialog for quick editing -->
|
||||||
|
<ng-template #tagDialog>
|
||||||
|
<h1 mat-dialog-title>
|
||||||
|
<span *ngIf="currentTag" translate>Edit tag</span>
|
||||||
|
<span *ngIf="!currentTag" translate>New tag</span>
|
||||||
|
</h1>
|
||||||
|
<div class="os-form-card-mobile" mat-dialog-content>
|
||||||
|
<form [formGroup]="tagForm" (keydown)="onKeyDown($event)">
|
||||||
|
<mat-form-field>
|
||||||
|
<input required type="text" matInput formControlName="name" placeholder="{{ 'Name' | translate }}">
|
||||||
|
</mat-form-field>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div mat-dialog-actions>
|
||||||
|
<button type="submit" mat-button [mat-dialog-close]="true" color="primary">
|
||||||
|
<span translate>Save</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" mat-button [mat-dialog-close]="false">
|
||||||
|
<span translate>Cancel</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { MatDialog } from '@angular/material';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
@ -9,11 +10,12 @@ import { PblColumnDefinition } from '@pebula/ngrid';
|
|||||||
import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service';
|
import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service';
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
import { Tag } from 'app/shared/models/core/tag';
|
import { Tag } from 'app/shared/models/core/tag';
|
||||||
|
import { infoDialogSettings } from 'app/shared/utils/dialog-settings';
|
||||||
import { BaseListViewComponent } from 'app/site/base/base-list-view';
|
import { BaseListViewComponent } from 'app/site/base/base-list-view';
|
||||||
import { ViewTag } from '../../models/view-tag';
|
import { ViewTag } from '../../models/view-tag';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listview for the complete lsit of available Tags
|
* Listview for the complete list of available Tags
|
||||||
* ### Usage:
|
* ### Usage:
|
||||||
* ```html
|
* ```html
|
||||||
* <os-tag-list></os-tag-list>
|
* <os-tag-list></os-tag-list>
|
||||||
@ -25,12 +27,17 @@ import { ViewTag } from '../../models/view-tag';
|
|||||||
styleUrls: ['./tag-list.component.scss']
|
styleUrls: ['./tag-list.component.scss']
|
||||||
})
|
})
|
||||||
export class TagListComponent extends BaseListViewComponent<ViewTag> implements OnInit {
|
export class TagListComponent extends BaseListViewComponent<ViewTag> implements OnInit {
|
||||||
public editTag = false;
|
@ViewChild('tagDialog', { static: true })
|
||||||
public newTag = false;
|
private tagDialog: TemplateRef<string>;
|
||||||
public selectedTag: ViewTag;
|
|
||||||
|
|
||||||
@ViewChild('tagForm', { static: true })
|
private tagForm: FormGroup = this.formBuilder.group({
|
||||||
public tagForm: FormGroup;
|
name: ['', [Validators.required]]
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the tag that's currently being edited, or null.
|
||||||
|
*/
|
||||||
|
public currentTag: ViewTag;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define the columns to show
|
* Define the columns to show
|
||||||
@ -39,6 +46,14 @@ export class TagListComponent extends BaseListViewComponent<ViewTag> implements
|
|||||||
{
|
{
|
||||||
prop: 'name',
|
prop: 'name',
|
||||||
width: 'auto'
|
width: 'auto'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'edit',
|
||||||
|
width: this.singleButtonWidth
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'delete',
|
||||||
|
width: this.singleButtonWidth
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -55,7 +70,9 @@ export class TagListComponent extends BaseListViewComponent<ViewTag> implements
|
|||||||
matSnackBar: MatSnackBar,
|
matSnackBar: MatSnackBar,
|
||||||
public repo: TagRepositoryService,
|
public repo: TagRepositoryService,
|
||||||
protected translate: TranslateService, // protected required for ng-translate-extract
|
protected translate: TranslateService, // protected required for ng-translate-extract
|
||||||
private promptService: PromptService
|
private promptService: PromptService,
|
||||||
|
private dialog: MatDialog,
|
||||||
|
private formBuilder: FormBuilder
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar);
|
super(titleService, translate, matSnackBar);
|
||||||
}
|
}
|
||||||
@ -66,93 +83,63 @@ export class TagListComponent extends BaseListViewComponent<ViewTag> implements
|
|||||||
*/
|
*/
|
||||||
public ngOnInit(): void {
|
public ngOnInit(): void {
|
||||||
super.setTitle('Tags');
|
super.setTitle('Tags');
|
||||||
this.tagForm = new FormGroup({ name: new FormControl('', Validators.required) });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a new or updates tag to the dataStore
|
* sets the given tag as the current and opens the tag dialog.
|
||||||
|
* @param tag the current tag, or null if a new tag is to be created
|
||||||
*/
|
*/
|
||||||
public saveTag(): void {
|
public openTagDialog(tag?: ViewTag): void {
|
||||||
if (this.editTag && this.newTag) {
|
this.currentTag = tag;
|
||||||
this.submitNewTag();
|
|
||||||
} else if (this.editTag && !this.newTag) {
|
|
||||||
this.submitEditedTag();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves a newly created tag.
|
|
||||||
*/
|
|
||||||
public submitNewTag(): void {
|
|
||||||
if (!this.tagForm.value || !this.tagForm.valid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.repo.create(this.tagForm.value).then(() => {
|
|
||||||
this.tagForm.reset();
|
|
||||||
this.cancelEditing();
|
|
||||||
}, this.raiseError);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves an edited tag.
|
|
||||||
*/
|
|
||||||
public submitEditedTag(): void {
|
|
||||||
if (!this.tagForm.value || !this.tagForm.valid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const updateData = new Tag({ name: this.tagForm.value.name });
|
|
||||||
|
|
||||||
this.repo.update(updateData, this.selectedTag).then(() => this.cancelEditing(), this.raiseError);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the selected Tag after a successful confirmation.
|
|
||||||
*/
|
|
||||||
public async deleteSelectedTag(): Promise<void> {
|
|
||||||
const title = this.translate.instant('Are you sure you want to delete this tag?');
|
|
||||||
const content = this.selectedTag.name;
|
|
||||||
if (await this.promptService.open(title, content)) {
|
|
||||||
this.repo.delete(this.selectedTag).then(() => this.cancelEditing(), this.raiseError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels the editing
|
|
||||||
*/
|
|
||||||
public cancelEditing(): void {
|
|
||||||
this.newTag = false;
|
|
||||||
this.editTag = false;
|
|
||||||
this.tagForm.reset();
|
this.tagForm.reset();
|
||||||
|
this.tagForm.get('name').setValue(this.currentTag ? this.currentTag.name : '');
|
||||||
|
const dialogRef = this.dialog.open(this.tagDialog, infoDialogSettings);
|
||||||
|
dialogRef.afterClosed().subscribe(res => {
|
||||||
|
if (res) {
|
||||||
|
this.save();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for a click on a row in the table
|
* Submit the form and create or update a tag.
|
||||||
* @param viewTag
|
|
||||||
*/
|
*/
|
||||||
public selectTag(viewTag: ViewTag): void {
|
private save(): void {
|
||||||
this.selectedTag = viewTag;
|
if (!this.tagForm.value || !this.tagForm.valid) {
|
||||||
this.setEditMode(true, false);
|
return;
|
||||||
this.tagForm.setValue({ name: this.selectedTag.name });
|
}
|
||||||
|
if (this.currentTag) {
|
||||||
|
this.repo.update(new Tag(this.tagForm.value), this.currentTag).catch(this.raiseError);
|
||||||
|
} else {
|
||||||
|
this.repo.create(this.tagForm.value).catch(this.raiseError);
|
||||||
|
}
|
||||||
|
this.tagForm.reset(); // reset here so pressing shift+enter wont save when dialog isnt open
|
||||||
}
|
}
|
||||||
|
|
||||||
public setEditMode(mode: boolean, newTag: boolean = true): void {
|
/**
|
||||||
this.editTag = mode;
|
* Deletes the given Tag after a successful confirmation.
|
||||||
this.newTag = newTag;
|
*/
|
||||||
if (!mode) {
|
public async onDeleteButton(tag: ViewTag): Promise<void> {
|
||||||
this.cancelEditing();
|
const title = this.translate.instant('Are you sure you want to delete this tag?');
|
||||||
|
const content = tag.name;
|
||||||
|
if (await this.promptService.open(title, content)) {
|
||||||
|
this.repo.delete(tag).catch(this.raiseError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles keyboard events. On enter, the editing is canceled.
|
* clicking Shift and Enter will save automatically
|
||||||
* @param event
|
* clicking Escape will cancel the process
|
||||||
|
*
|
||||||
|
* @param event has the code
|
||||||
*/
|
*/
|
||||||
public keyDownFunction(event: KeyboardEvent): void {
|
public onKeyDown(event: KeyboardEvent): void {
|
||||||
if (event.key === 'Enter' && event.shiftKey) {
|
if (event.key === 'Enter' && event.shiftKey) {
|
||||||
this.submitNewTag();
|
this.save();
|
||||||
|
this.dialog.closeAll();
|
||||||
}
|
}
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
this.cancelEditing();
|
this.dialog.closeAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user