added dialogs for creating and editing tags, statute paragraphs, motion comments and motion blocks

This commit is contained in:
jsangmeister 2019-07-31 12:04:15 +02:00 committed by Joshua Sangmeister
parent 32bec58333
commit 1bd93f0e98
10 changed files with 348 additions and 488 deletions

View File

@ -90,7 +90,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",

View File

@ -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,36 @@
</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>{{ "New motion block" | translate }}</span>
</h1>
<form [formGroup]="createBlockForm" (keydown)="onKeyDown($event)">
<div class="os-form-card-mobile" mat-dialog-content>
<!-- 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>
</div>
</form>
<div mat-dialog-actions>
<button mat-button [disabled]="!createBlockForm.valid" (click)="onSaveNewButton()">
<span translate>Save</span>
</button>
<button mat-button (click)="onCancel()"><span translate>Cancel</span></button>
</div>
</ng-template>

View File

@ -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, MatDialogRef } 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';
@ -27,6 +28,11 @@ 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>;
private dialogRef: MatDialogRef<string, any>;
/** /**
* Holds the create form * Holds the create form
*/ */
@ -95,6 +101,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);
@ -127,21 +134,29 @@ 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 as well as closing the modal dialog
*/ */
public resetForm(): void { public resetForm(): void {
this.createBlockForm.reset(); this.createBlockForm.reset();
this.createBlockForm.get('agenda_type').setValue(this.defaultVisibility); this.createBlockForm.get('agenda_type').setValue(this.defaultVisibility);
if (this.dialogRef) {
this.dialogRef.close();
}
this.dialogRef = null;
} }
/** /**
* 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(); this.dialogRef = this.dialog.open(this.newMotionBlockDialog, {
this.isCreatingNewBlock = true; width: '400px',
} maxWidth: '90vw',
maxHeight: '90vh',
disableClose: true
});
} }
/** /**
@ -158,7 +173,6 @@ export class MotionBlockListComponent extends BaseListViewComponent<ViewMotionBl
try { try {
await this.repo.create(block); await this.repo.create(block);
this.resetForm(); this.resetForm();
this.isCreatingNewBlock = false;
} catch (e) { } catch (e) {
this.raiseError(e); this.raiseError(e);
} }
@ -186,6 +200,6 @@ export class MotionBlockListComponent extends BaseListViewComponent<ViewMotionBl
* Cancels the current form action * Cancels the current form action
*/ */
public onCancel(): void { public onCancel(): void {
this.isCreatingNewBlock = false; this.resetForm();
} }
} }

View File

@ -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,52 +13,11 @@
</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" (opened)="openId = section.id"
(closed)="panelClosed(section)" (closed)="openId = null"
[expanded]="openId === section.id" [expanded]="openId === section.id"
multiple="false" multiple="false"
> >
@ -87,59 +46,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 +75,48 @@
<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>{{ (currentComment ? "Edit comment field" : "New comment field") | translate }}</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 (click)="save()">
<span translate>Save</span>
</button>
<button mat-button (click)="cancel()">
<span translate>Cancel</span>
</button>
</div>
</ng-template>

View File

@ -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, MatDialogRef } 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';
@ -23,7 +24,12 @@ 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;
public dialogRef: MatDialogRef<string, any>;
/** /**
* Source of the Data * Source of the Data
@ -31,14 +37,11 @@ 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 openId: number | null;
public editId: number | null;
public groups: BehaviorSubject<ViewGroup[]>; public groups: BehaviorSubject<ViewGroup[]>;
@ -59,6 +62,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 +72,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,81 +85,62 @@ 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);
} else {
this.create();
}
} }
if (event.key === 'Escape') { if (event.key === 'Escape') {
if (viewSection) { this.cancel();
this.editId = null;
} else {
this.commentSectionToCreate = null;
}
} }
} }
/** /**
* Opens the create form. * Opens the create dialog.
*/ */
public onPlusButton(): void { public openDialog(c?: ViewMotionCommentSection): void {
if (!this.commentSectionToCreate) { this.currentComment = c;
this.commentSectionToCreate = new MotionCommentSection(); this.commentFieldForm.reset({
this.createForm.setValue({ name: c ? c.name : '',
name: '', read_groups_id: c ? c.read_groups_id : [],
read_groups_id: [], write_groups_id: c ? c.write_groups_id : []
write_groups_id: [] });
}); this.dialogRef = this.dialog.open(this.motionCommentDialog, {
} width: '500px',
} maxWidth: '90vw',
maxHeight: '90vh',
/** disableClose: true
* 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 { public 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)
.then(() => this.dialogRef.close(), this.raiseError);
} else {
const c = new MotionCommentSection(this.commentFieldForm.value);
this.repo.create(c).then(() => this.dialogRef.close(), this.raiseError);
}
} }
} }
/**
* close the dialog
*/
public cancel(): void {
this.dialogRef.close();
}
/** /**
* is executed, when the delete button is pressed * is executed, when the delete button is pressed
* @param viewSection The section to delete * @param viewSection The section to delete
@ -165,18 +149,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);
} }
} }
} }

View File

@ -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,41 +12,12 @@
</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" (opened)="openId = statuteParagraph.id"
(closed)="panelClosed(statuteParagraph)" (closed)="openId = null"
[expanded]="openId === statuteParagraph.id" [expanded]="openId === statuteParagraph.id"
multiple="false" multiple="false"
> >
@ -55,51 +26,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 +60,35 @@
<span translate>Import</span><span>&nbsp;...</span> <span translate>Import</span><span>&nbsp;...</span>
</button> </button>
</mat-menu> </mat-menu>
<!-- Template for statute paragraph dialog -->
<ng-template #statuteParagraphDialog>
<h1 mat-dialog-title>
<span>{{ (currentStatuteParagraph ? "Edit statute paragraph" : "New statute paragraph") | translate }}</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 (click)="save()" [disabled]="!statuteParagraphForm.valid">
<span translate>Save</span>
</button>
<button mat-button (click)="cancel()">
<span translate>Cancel</span>
</button>
</div>
</ng-template>

View File

@ -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, MatDialogRef } 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';
@ -21,7 +22,12 @@ 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 dialogRef: MatDialogRef<string, any>;
private currentStatuteParagraph: ViewStatuteParagraph | null;
/** /**
* Source of the Data * Source of the Data
@ -29,14 +35,11 @@ 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 openId: number | null;
public editId: number | null;
/** /**
* The usual component constructor. Initializes the forms * The usual component constructor. Initializes the forms
@ -56,6 +59,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 +68,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 +84,39 @@ export class StatuteParagraphListComponent extends BaseViewComponent implements
} }
/** /**
* Add a new Section. * Open the modal dialog
*/ */
public onPlusButton(): void { public openDialog(p?: ViewStatuteParagraph): void {
if (!this.statuteParagraphToCreate) { this.currentStatuteParagraph = p;
this.createForm.reset(); this.statuteParagraphForm.reset();
this.createForm.setValue({ if (p) {
title: '', this.statuteParagraphForm.setValue({
text: '' title: p.title,
text: p.text
}); });
this.statuteParagraphToCreate = new StatuteParagraph();
} }
} this.dialogRef = this.dialog.open(this.statuteParagraphDialog, {
width: '1000px',
/** maxWidth: '95vw',
* Handler when clicking on create to create a new statute paragraph maxHeight: '90vh',
*/ disableClose: true
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 { public 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)
.then(() => this.dialogRef.close(), this.raiseError);
} else {
const p = new StatuteParagraph(this.statuteParagraphForm.value);
this.repo.create(p).then(() => this.dialogRef.close(), this.raiseError);
}
} }
} }
@ -139,18 +128,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,43 +145,20 @@ 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();
} }
if (event.key === 'Escape') { if (event.key === 'Escape') {
this.onCancelCreate(); this.cancel();
} }
} }
/** /**
* Cancels the current form action * Closes the dialog
*/ */
public onCancelCreate(): void { public cancel(): void {
this.statuteParagraphToCreate = null; this.dialogRef.close();
}
/**
* 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;
} }
/** /**

View File

@ -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,44 @@
(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>{{ (currentTag ? 'Edit tag' : 'New tag') | translate }}</span>
</h1>
<form [formGroup]="tagForm" (ngSubmit)="onSubmit()">
<div class="os-form-card-mobile" mat-dialog-content>
<mat-form-field>
<input required type="text" matInput formControlName="name" placeholder="{{ 'Name' | translate }}">
</mat-form-field>
</div>
<div mat-dialog-actions>
<button type="submit" mat-button color="primary">
<span translate>Save</span>
</button>
<button type="button" mat-button [mat-dialog-close]="null">
<span translate>Cancel</span>
</button>
</div>
</form>
</ng-template>

View File

@ -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, MatDialogRef } 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';
@ -13,7 +14,7 @@ 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 +26,19 @@ 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]]
});
private dialogRef: MatDialogRef<string, any>;
/**
* 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 +47,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 +71,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 +84,48 @@ 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 : '');
this.dialogRef = this.dialog.open(this.tagDialog, {
width: '400px',
maxWidth: '90vw',
maxHeight: '90vh',
disableClose: true
});
} }
/** /**
* 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 { public onSubmit(): 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
public setEditMode(mode: boolean, newTag: boolean = true): void { .update(new Tag(this.tagForm.value), this.currentTag)
this.editTag = mode; .then(() => this.dialogRef.close(), this.raiseError);
this.newTag = newTag; } else {
if (!mode) { this.repo.create(this.tagForm.value).then(() => this.dialogRef.close(), this.raiseError);
this.cancelEditing();
} }
} }
/** /**
* Handles keyboard events. On enter, the editing is canceled. * Deletes the given Tag after a successful confirmation.
* @param event
*/ */
public keyDownFunction(event: KeyboardEvent): void { public async onDeleteButton(tag: ViewTag): Promise<void> {
if (event.key === 'Enter' && event.shiftKey) { const title = this.translate.instant('Are you sure you want to delete this tag?');
this.submitNewTag(); const content = tag.name;
} if (await this.promptService.open(title, content)) {
if (event.key === 'Escape') { this.repo.delete(tag).catch(this.raiseError);
this.cancelEditing();
} }
} }
} }

View File

@ -186,6 +186,7 @@ class ProjectorViewSet(ModelViewSet):
If `reset_scroll` is True, the scoll of the projector will reset. If `reset_scroll` is True, the scoll of the projector will reset.
""" """
projector = self.get_object() projector = self.get_object()
projector.scroll = 0
elements = request.data.get("elements") elements = request.data.get("elements")
preview = request.data.get("preview") preview = request.data.get("preview")
history_element = request.data.get("append_to_history") history_element = request.data.get("append_to_history")