Merge pull request #3905 from FinnStutzenstein/commentfields
Motion comment section list
This commit is contained in:
commit
24abdc7bd0
@ -13,6 +13,7 @@ import { WebsocketService } from './services/websocket.service';
|
|||||||
import { AddHeaderInterceptor } from './http-interceptor';
|
import { AddHeaderInterceptor } from './http-interceptor';
|
||||||
import { DataSendService } from './services/data-send.service';
|
import { DataSendService } from './services/data-send.service';
|
||||||
import { ViewportService } from './services/viewport.service';
|
import { ViewportService } from './services/viewport.service';
|
||||||
|
import { PromptDialogComponent } from '../shared/components/prompt-dialog/prompt-dialog.component';
|
||||||
|
|
||||||
/** Global Core Module. Contains all global (singleton) services
|
/** Global Core Module. Contains all global (singleton) services
|
||||||
*
|
*
|
||||||
@ -34,7 +35,8 @@ import { ViewportService } from './services/viewport.service';
|
|||||||
useClass: AddHeaderInterceptor,
|
useClass: AddHeaderInterceptor,
|
||||||
multi: true
|
multi: true
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
entryComponents: [PromptDialogComponent]
|
||||||
})
|
})
|
||||||
export class CoreModule {
|
export class CoreModule {
|
||||||
/** make sure CoreModule is imported only by one NgModule, the AppModule */
|
/** make sure CoreModule is imported only by one NgModule, the AppModule */
|
||||||
|
17
client/src/app/core/services/prompt.service.spec.ts
Normal file
17
client/src/app/core/services/prompt.service.spec.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { PromptService } from './prompt.service';
|
||||||
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
|
||||||
|
describe('PromptService', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
providers: [PromptService]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', inject([PromptService], (service: PromptService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}));
|
||||||
|
});
|
30
client/src/app/core/services/prompt.service.ts
Normal file
30
client/src/app/core/services/prompt.service.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { OpenSlidesComponent } from 'app/openslides.component';
|
||||||
|
import { PromptDialogComponent } from '../../shared/components/prompt-dialog/prompt-dialog.component';
|
||||||
|
import { MatDialog } from '@angular/material';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A general service for prompting 'yes' or 'cancel' thinks from the user.
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class PromptService extends OpenSlidesComponent {
|
||||||
|
public constructor(private dialog: MatDialog) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the dialog. Returns true, if the user accepts.
|
||||||
|
* @param title The title to display in the dialog
|
||||||
|
* @param content The content in the dialog
|
||||||
|
*/
|
||||||
|
public async open(title: string, content: string): Promise<any> {
|
||||||
|
const dialogRef = this.dialog.open(PromptDialogComponent, {
|
||||||
|
width: '250px',
|
||||||
|
data: { title: title, content: content }
|
||||||
|
});
|
||||||
|
|
||||||
|
return dialogRef.afterClosed().toPromise();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
<h2 mat-dialog-title>{{ data.title | translate }}</h2>
|
||||||
|
<mat-dialog-content>{{ data.content | translate }}</mat-dialog-content>
|
||||||
|
<mat-dialog-actions>
|
||||||
|
<button mat-button [mat-dialog-close]="true" color="warn" translate>Yes</button>
|
||||||
|
<button mat-button [mat-dialog-close]="false" translate>Cancel</button>
|
||||||
|
</mat-dialog-actions>
|
@ -0,0 +1,26 @@
|
|||||||
|
import { async, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
// import { PromptDialogComponent } from './prompt-dialog.component';
|
||||||
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
|
||||||
|
describe('PromptDialogComponent', () => {
|
||||||
|
// let component: PromptDialogComponent;
|
||||||
|
// let fixture: ComponentFixture<PromptDialogComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// TODO: You cannot create this component in the standard way. Needs different testing.
|
||||||
|
beforeEach(() => {
|
||||||
|
/*fixture = TestBed.createComponent(PromptDialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();*/
|
||||||
|
});
|
||||||
|
|
||||||
|
/*it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});*/
|
||||||
|
});
|
@ -0,0 +1,21 @@
|
|||||||
|
import { Component, Inject } from '@angular/core';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
|
||||||
|
|
||||||
|
interface PromptDialogData {
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple prompt dialog. Takes a title and content.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'os-prompt-dialog',
|
||||||
|
templateUrl: './prompt-dialog.component.html'
|
||||||
|
})
|
||||||
|
export class PromptDialogComponent {
|
||||||
|
public constructor(
|
||||||
|
public dialogRef: MatDialogRef<PromptDialogComponent>,
|
||||||
|
@Inject(MAT_DIALOG_DATA) public data: PromptDialogData
|
||||||
|
) {}
|
||||||
|
}
|
@ -50,6 +50,7 @@ import { LegalNoticeContentComponent } from './components/legal-notice-content/l
|
|||||||
import { PrivacyPolicyContentComponent } from './components/privacy-policy-content/privacy-policy-content.component';
|
import { PrivacyPolicyContentComponent } from './components/privacy-policy-content/privacy-policy-content.component';
|
||||||
import { SearchValueSelectorComponent } from './components/search-value-selector/search-value-selector.component';
|
import { SearchValueSelectorComponent } from './components/search-value-selector/search-value-selector.component';
|
||||||
import { OpenSlidesDateAdapter } from './date-adapter';
|
import { OpenSlidesDateAdapter } from './date-adapter';
|
||||||
|
import { PromptDialogComponent } from './components/prompt-dialog/prompt-dialog.component';
|
||||||
|
|
||||||
library.add(fas);
|
library.add(fas);
|
||||||
|
|
||||||
@ -127,7 +128,8 @@ library.add(fas);
|
|||||||
HeadBarComponent,
|
HeadBarComponent,
|
||||||
SearchValueSelectorComponent,
|
SearchValueSelectorComponent,
|
||||||
LegalNoticeContentComponent,
|
LegalNoticeContentComponent,
|
||||||
PrivacyPolicyContentComponent
|
PrivacyPolicyContentComponent,
|
||||||
|
PromptDialogComponent
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
PermsDirective,
|
PermsDirective,
|
||||||
@ -136,7 +138,8 @@ library.add(fas);
|
|||||||
FooterComponent,
|
FooterComponent,
|
||||||
LegalNoticeContentComponent,
|
LegalNoticeContentComponent,
|
||||||
PrivacyPolicyContentComponent,
|
PrivacyPolicyContentComponent,
|
||||||
SearchValueSelectorComponent
|
SearchValueSelectorComponent,
|
||||||
|
PromptDialogComponent
|
||||||
],
|
],
|
||||||
providers: [{ provide: DateAdapter, useClass: OpenSlidesDateAdapter }]
|
providers: [{ provide: DateAdapter, useClass: OpenSlidesDateAdapter }]
|
||||||
})
|
})
|
||||||
|
@ -0,0 +1,108 @@
|
|||||||
|
<os-head-bar appName="Motion comment sections" [plusButton]=true (plusButtonClicked)=onPlusButton()>
|
||||||
|
</os-head-bar>
|
||||||
|
<div class="head-spacer"></div>
|
||||||
|
<mat-card *ngIf="commentSectionToCreate">
|
||||||
|
<mat-card-title translate>Create new comment section</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-hint *ngIf="!createForm.controls.name.valid">
|
||||||
|
<span translate>Required</span>
|
||||||
|
</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<os-search-value-selector ngDefaultControl [form]="createForm" [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 [form]="createForm" [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 mat-button (click)="create()" translate>Create</button>
|
||||||
|
<button mat-button (click)="commentSectionToCreate = null" translate>Abort</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
|
<mat-accordion class="os-card">
|
||||||
|
<mat-expansion-panel *ngFor="let section of this.commentSections" (opened)="openId = section.id"
|
||||||
|
(closed)="panelClosed(section)" [expanded]="openId === section.id" multiple="false">
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
<div class="header-container">
|
||||||
|
<div class="name">
|
||||||
|
{{ section.name }}
|
||||||
|
</div>
|
||||||
|
<div class="read">
|
||||||
|
<fa-icon icon="eye"></fa-icon>
|
||||||
|
{{ section.read_groups }}
|
||||||
|
<ng-container *ngIf="section.read_groups.length === 0">
|
||||||
|
–
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
<div class="write">
|
||||||
|
<fa-icon icon="pen"></fa-icon>
|
||||||
|
{{ section.write_groups }}
|
||||||
|
<ng-container *ngIf="section.write_groups.length === 0">
|
||||||
|
–
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<form [formGroup]="updateForm" *ngIf="editId === section.id" (keydown)="keyDownFunction($event, section)">
|
||||||
|
<span translate>Edit section details:</span>
|
||||||
|
<p>
|
||||||
|
<mat-form-field>
|
||||||
|
<input formControlName="name" matInput placeholder="{{'Name' | translate}}">
|
||||||
|
<mat-hint *ngIf="!updateForm.controls.name.valid">
|
||||||
|
<span translate>Required</span>
|
||||||
|
</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<os-search-value-selector ngDefaultControl [form]="updateForm" [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 [form]="updateForm" [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>
|
||||||
|
<button *ngIf="editId !== section.id" mat-button class="on-transition-fade" (click)="onEditButton(section)"
|
||||||
|
mat-icon-button>
|
||||||
|
<fa-icon icon='pen'></fa-icon>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="editId === section.id" mat-button class="on-transition-fade" (click)="editId = null"
|
||||||
|
mat-icon-button>
|
||||||
|
<fa-icon icon='times'></fa-icon>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="editId === section.id" mat-button class="on-transition-fade" (click)="onSaveButton(section)"
|
||||||
|
mat-icon-button>
|
||||||
|
<fa-icon icon='save'></fa-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-button class='on-transition-fade' (click)=onDeleteButton(section) mat-icon-button>
|
||||||
|
<fa-icon icon='trash'></fa-icon>
|
||||||
|
</button>
|
||||||
|
</mat-action-row>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
</mat-accordion>
|
@ -0,0 +1,49 @@
|
|||||||
|
.head-spacer {
|
||||||
|
width: 100%;
|
||||||
|
height: 60px;
|
||||||
|
line-height: 60px;
|
||||||
|
text-align: right;
|
||||||
|
background: white;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
grid-template-columns: 33.333% 33.333% 33.333%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
grid-row-start: 1;
|
||||||
|
grid-row-end: span 1;
|
||||||
|
grid-column-end: span 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
grid-column-start: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.read {
|
||||||
|
grid-column-start: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.write {
|
||||||
|
grid-column-start: 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
display: block;
|
||||||
|
margin-top: 12px; //distance between heading and text
|
||||||
|
margin-bottom: 3px; //distance between heading and text
|
||||||
|
font-size: 90%;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer-left {
|
||||||
|
margin-left: 40px;
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { MotionCommentSectionListComponent } from './motion-comment-section-list.component';
|
||||||
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
|
||||||
|
describe('MotionCommentSectionListComponent', () => {
|
||||||
|
let component: MotionCommentSectionListComponent;
|
||||||
|
let fixture: ComponentFixture<MotionCommentSectionListComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
declarations: [MotionCommentSectionListComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(MotionCommentSectionListComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,170 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { BaseComponent } from '../../../../base.component';
|
||||||
|
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||||
|
import { MotionCommentSection } from '../../../../shared/models/motions/motion-comment-section';
|
||||||
|
import { ViewMotionCommentSection } from '../../models/view-motion-comment-section';
|
||||||
|
import { MotionCommentSectionRepositoryService } from '../../services/motion-comment-section-repository.service';
|
||||||
|
import { PromptService } from '../../../../core/services/prompt.service';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
import { Group } from '../../../../shared/models/users/group';
|
||||||
|
import { DataStoreService } from '../../../../core/services/data-store.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List view for the categories.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'os-motion-comment-section-list',
|
||||||
|
templateUrl: './motion-comment-section-list.component.html',
|
||||||
|
styleUrls: ['./motion-comment-section-list.component.scss']
|
||||||
|
})
|
||||||
|
export class MotionCommentSectionListComponent extends BaseComponent implements OnInit {
|
||||||
|
public commentSectionToCreate: MotionCommentSection | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source of the Data
|
||||||
|
*/
|
||||||
|
public commentSections: ViewMotionCommentSection[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current focussed formgroup
|
||||||
|
*/
|
||||||
|
public updateForm: FormGroup;
|
||||||
|
|
||||||
|
public createForm: FormGroup;
|
||||||
|
|
||||||
|
public openId: number | null;
|
||||||
|
public editId: number | null;
|
||||||
|
|
||||||
|
public groups: BehaviorSubject<Array<Group>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The usual component constructor
|
||||||
|
* @param titleService
|
||||||
|
* @param translate
|
||||||
|
* @param repo
|
||||||
|
* @param formBuilder
|
||||||
|
*/
|
||||||
|
public constructor(
|
||||||
|
protected titleService: Title,
|
||||||
|
protected translate: TranslateService,
|
||||||
|
private repo: MotionCommentSectionRepositoryService,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private promptService: PromptService,
|
||||||
|
private DS: DataStoreService
|
||||||
|
) {
|
||||||
|
super(titleService, translate);
|
||||||
|
const form = {
|
||||||
|
name: ['', Validators.required],
|
||||||
|
read_groups_id: [[]],
|
||||||
|
write_groups_id: [[]]
|
||||||
|
};
|
||||||
|
this.createForm = this.formBuilder.group(form);
|
||||||
|
this.updateForm = this.formBuilder.group(form);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event on Key Down in update or create form. Do not provide the viewSection for the create form.
|
||||||
|
*/
|
||||||
|
public keyDownFunction(event: KeyboardEvent, viewSection?: ViewMotionCommentSection): void {
|
||||||
|
if (event.keyCode === 13) {
|
||||||
|
if (viewSection) {
|
||||||
|
this.onSaveButton(viewSection);
|
||||||
|
} else {
|
||||||
|
this.create();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init function.
|
||||||
|
*
|
||||||
|
* Sets the title and gets/observes categories from DataStore
|
||||||
|
*/
|
||||||
|
public ngOnInit(): void {
|
||||||
|
super.setTitle('Comment Sections');
|
||||||
|
this.groups = new BehaviorSubject(this.DS.getAll(Group));
|
||||||
|
this.DS.changeObservable.subscribe(model => {
|
||||||
|
if (model instanceof Group) {
|
||||||
|
this.groups.next(this.DS.getAll(Group));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.repo.getViewModelListObservable().subscribe(newViewSections => {
|
||||||
|
this.commentSections = newViewSections;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new Section.
|
||||||
|
*/
|
||||||
|
public onPlusButton(): void {
|
||||||
|
if (!this.commentSectionToCreate) {
|
||||||
|
this.commentSectionToCreate = new MotionCommentSection();
|
||||||
|
this.createForm.setValue({
|
||||||
|
name: '',
|
||||||
|
read_groups_id: [],
|
||||||
|
write_groups_id: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public create(): void {
|
||||||
|
if (this.createForm.valid) {
|
||||||
|
this.commentSectionToCreate.patchValues(this.createForm.value as MotionCommentSection);
|
||||||
|
this.repo.create(this.commentSectionToCreate).subscribe(resp => {
|
||||||
|
this.commentSectionToCreate = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 categories
|
||||||
|
*/
|
||||||
|
public onSaveButton(viewSection: ViewMotionCommentSection): void {
|
||||||
|
if (this.updateForm.valid) {
|
||||||
|
this.repo.update(this.updateForm.value as Partial<MotionCommentSection>, viewSection).subscribe(resp => {
|
||||||
|
this.openId = this.editId = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* is executed, when the delete button is pressed
|
||||||
|
*/
|
||||||
|
public async onDeleteButton(viewSection: ViewMotionCommentSection): Promise<any> {
|
||||||
|
const content = this.translate.instant('Delete') + ` ${viewSection.name}?`;
|
||||||
|
if (await this.promptService.open('Are you sure?', content)) {
|
||||||
|
this.repo.delete(viewSection).subscribe(resp => {
|
||||||
|
this.openId = this.editId = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is executed when a mat-extension-panel is closed
|
||||||
|
* @param viewSection the category in the panel
|
||||||
|
*/
|
||||||
|
public panelClosed(viewSection: ViewMotionCommentSection): void {
|
||||||
|
this.openId = null;
|
||||||
|
if (this.editId) {
|
||||||
|
this.onSaveButton(viewSection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -191,7 +191,7 @@
|
|||||||
<mat-option *ngFor="let state of motionCopy.nextStates" [value]="state.id">{{state}}</mat-option>
|
<mat-option *ngFor="let state of motionCopy.nextStates" [value]="state.id">{{state}}</mat-option>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
<mat-option>
|
<mat-option>
|
||||||
<fa-icon icon='exclamation-triangle'></fa-icon> <span translate>Reset State</span>
|
<fa-icon icon='exclamation-triangle'></fa-icon><span translate>Reset State</span>
|
||||||
</mat-option>
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
@ -42,6 +42,10 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
|||||||
{
|
{
|
||||||
text: 'Categories',
|
text: 'Categories',
|
||||||
action: 'toCategories'
|
action: 'toCategories'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Motion comment sections',
|
||||||
|
action: 'toMotionCommentSections'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -130,6 +134,13 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
|||||||
this.router.navigate(['./category'], { relativeTo: this.route });
|
this.router.navigate(['./category'], { relativeTo: this.route });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* navigate to 'motion/comment-section'
|
||||||
|
*/
|
||||||
|
public toMotionCommentSections(): void {
|
||||||
|
this.router.navigate(['./comment-section'], { relativeTo: this.route });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download all motions As PDF and DocX
|
* Download all motions As PDF and DocX
|
||||||
*
|
*
|
||||||
|
@ -0,0 +1,90 @@
|
|||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { BaseViewModel } from '../../base/base-view-model';
|
||||||
|
import { MotionCommentSection } from '../../../shared/models/motions/motion-comment-section';
|
||||||
|
import { Group } from '../../../shared/models/users/group';
|
||||||
|
import { BaseModel } from '../../../shared/models/base/base-model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Motion comment section class for the View
|
||||||
|
*
|
||||||
|
* Stores a motion comment section including all (implicit) references
|
||||||
|
* Provides "safe" access to variables and functions in {@link MotionCommentSection}
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
export class ViewMotionCommentSection extends BaseViewModel {
|
||||||
|
private _section: MotionCommentSection;
|
||||||
|
|
||||||
|
private _read_groups: Group[];
|
||||||
|
private _write_groups: Group[];
|
||||||
|
|
||||||
|
public edit = false;
|
||||||
|
public open = false;
|
||||||
|
|
||||||
|
public get section(): MotionCommentSection {
|
||||||
|
return this._section;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get id(): number {
|
||||||
|
return this.section ? this.section.id : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get name(): string {
|
||||||
|
return this.section ? this.section.name : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get read_groups_id(): number[] {
|
||||||
|
return this.section ? this.section.read_groups_id : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public get write_groups_id(): number[] {
|
||||||
|
return this.section ? this.section.write_groups_id : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public get read_groups(): Group[] {
|
||||||
|
return this._read_groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get write_groups(): Group[] {
|
||||||
|
return this._write_groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set name(name: string) {
|
||||||
|
this._section.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public constructor(section: MotionCommentSection, read_groups: Group[], write_groups: Group[]) {
|
||||||
|
super();
|
||||||
|
this._section = section;
|
||||||
|
this._read_groups = read_groups;
|
||||||
|
this._write_groups = write_groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTitle(translate?: TranslateService): string {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the local objects if required
|
||||||
|
* @param section
|
||||||
|
*/
|
||||||
|
public updateValues(update: BaseModel): void {
|
||||||
|
if (update instanceof MotionCommentSection) {
|
||||||
|
this._section = update as MotionCommentSection;
|
||||||
|
}
|
||||||
|
if (update instanceof Group) {
|
||||||
|
this.updateGroup(update as Group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement updating of groups
|
||||||
|
public updateGroup(group: Group): void {
|
||||||
|
console.log(this._section, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duplicate this motion into a copy of itself
|
||||||
|
*/
|
||||||
|
public copy(): ViewMotionCommentSection {
|
||||||
|
return new ViewMotionCommentSection(this._section, this._read_groups, this._write_groups);
|
||||||
|
}
|
||||||
|
}
|
@ -3,10 +3,12 @@ import { Routes, RouterModule } from '@angular/router';
|
|||||||
import { MotionListComponent } from './components/motion-list/motion-list.component';
|
import { MotionListComponent } from './components/motion-list/motion-list.component';
|
||||||
import { MotionDetailComponent } from './components/motion-detail/motion-detail.component';
|
import { MotionDetailComponent } from './components/motion-detail/motion-detail.component';
|
||||||
import { CategoryListComponent } from './components/category-list/category-list.component';
|
import { CategoryListComponent } from './components/category-list/category-list.component';
|
||||||
|
import { MotionCommentSectionListComponent } from './components/motion-comment-section-list/motion-comment-section-list.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', component: MotionListComponent },
|
{ path: '', component: MotionListComponent },
|
||||||
{ path: 'category', component: CategoryListComponent },
|
{ path: 'category', component: CategoryListComponent },
|
||||||
|
{ path: 'comment-section', component: MotionCommentSectionListComponent },
|
||||||
{ path: 'new', component: MotionDetailComponent },
|
{ path: 'new', component: MotionDetailComponent },
|
||||||
{ path: ':id', component: MotionDetailComponent }
|
{ path: ':id', component: MotionDetailComponent }
|
||||||
];
|
];
|
||||||
|
@ -6,9 +6,10 @@ import { SharedModule } from '../../shared/shared.module';
|
|||||||
import { MotionListComponent } from './components/motion-list/motion-list.component';
|
import { MotionListComponent } from './components/motion-list/motion-list.component';
|
||||||
import { MotionDetailComponent } from './components/motion-detail/motion-detail.component';
|
import { MotionDetailComponent } from './components/motion-detail/motion-detail.component';
|
||||||
import { CategoryListComponent } from './components/category-list/category-list.component';
|
import { CategoryListComponent } from './components/category-list/category-list.component';
|
||||||
|
import { MotionCommentSectionListComponent } from './components/motion-comment-section-list/motion-comment-section-list.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, MotionsRoutingModule, SharedModule],
|
imports: [CommonModule, MotionsRoutingModule, SharedModule],
|
||||||
declarations: [MotionListComponent, MotionDetailComponent, CategoryListComponent]
|
declarations: [MotionListComponent, MotionDetailComponent, CategoryListComponent, MotionCommentSectionListComponent]
|
||||||
})
|
})
|
||||||
export class MotionsModule {}
|
export class MotionsModule {}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { MotionCommentSectionRepositoryService } from './motion-comment-section-repository.service';
|
||||||
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
|
||||||
|
describe('MotionCommentSectionRepositoryService', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
providers: [MotionCommentSectionRepositoryService]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', inject(
|
||||||
|
[MotionCommentSectionRepositoryService],
|
||||||
|
(service: MotionCommentSectionRepositoryService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}
|
||||||
|
));
|
||||||
|
});
|
@ -0,0 +1,61 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { DataSendService } from '../../../core/services/data-send.service';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { DataStoreService } from '../../../core/services/data-store.service';
|
||||||
|
import { BaseRepository } from '../../base/base-repository';
|
||||||
|
import { ViewMotionCommentSection } from '../models/view-motion-comment-section';
|
||||||
|
import { MotionCommentSection } from '../../../shared/models/motions/motion-comment-section';
|
||||||
|
import { Group } from '../../../shared/models/users/group';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository Services for Categories
|
||||||
|
*
|
||||||
|
* The repository is meant to process domain objects (those found under
|
||||||
|
* shared/models), so components can display them and interact with them.
|
||||||
|
*
|
||||||
|
* Rather than manipulating models directly, the repository is meant to
|
||||||
|
* inform the {@link DataSendService} about changes which will send
|
||||||
|
* them to the Server.
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class MotionCommentSectionRepositoryService extends BaseRepository<
|
||||||
|
ViewMotionCommentSection,
|
||||||
|
MotionCommentSection
|
||||||
|
> {
|
||||||
|
/**
|
||||||
|
* Creates a CategoryRepository
|
||||||
|
* Converts existing and incoming category to ViewCategories
|
||||||
|
* Handles CRUD using an observer to the DataStore
|
||||||
|
* @param DataSend
|
||||||
|
*/
|
||||||
|
public constructor(protected DS: DataStoreService, private dataSend: DataSendService) {
|
||||||
|
super(DS, MotionCommentSection, [Group]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createViewModel(section: MotionCommentSection): ViewMotionCommentSection {
|
||||||
|
const read_groups = this.DS.getMany(Group, section.read_groups_id);
|
||||||
|
const write_groups = this.DS.getMany(Group, section.write_groups_id);
|
||||||
|
return new ViewMotionCommentSection(section, read_groups, write_groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
public create(section: MotionCommentSection): Observable<any> {
|
||||||
|
return this.dataSend.createModel(section);
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(section: Partial<MotionCommentSection>, viewSection?: ViewMotionCommentSection): Observable<any> {
|
||||||
|
let updateSection: MotionCommentSection;
|
||||||
|
if (viewSection) {
|
||||||
|
updateSection = viewSection.section;
|
||||||
|
} else {
|
||||||
|
updateSection = new MotionCommentSection();
|
||||||
|
}
|
||||||
|
updateSection.patchValues(section);
|
||||||
|
return this.dataSend.updateModel(updateSection, 'put');
|
||||||
|
}
|
||||||
|
|
||||||
|
public delete(viewSection: ViewMotionCommentSection): Observable<any> {
|
||||||
|
return this.dataSend.delete(viewSection.section);
|
||||||
|
}
|
||||||
|
}
|
@ -23,7 +23,6 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
|||||||
AppModule,
|
AppModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
|
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
TranslateModule.forRoot({
|
TranslateModule.forRoot({
|
||||||
loader: {
|
loader: {
|
||||||
|
@ -5,6 +5,7 @@ from django.utils.translation import ugettext as _
|
|||||||
|
|
||||||
from ..poll.serializers import default_votes_validator
|
from ..poll.serializers import default_votes_validator
|
||||||
from ..utils.auth import get_group_model
|
from ..utils.auth import get_group_model
|
||||||
|
from ..utils.autoupdate import inform_changed_data
|
||||||
from ..utils.rest_api import (
|
from ..utils.rest_api import (
|
||||||
CharField,
|
CharField,
|
||||||
DecimalField,
|
DecimalField,
|
||||||
@ -314,6 +315,12 @@ class MotionCommentSectionSerializer(ModelSerializer):
|
|||||||
'read_groups',
|
'read_groups',
|
||||||
'write_groups',)
|
'write_groups',)
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
""" Call inform_changed_data on creation, so the cache includes the groups. """
|
||||||
|
section = super().create(validated_data)
|
||||||
|
inform_changed_data(section)
|
||||||
|
return section
|
||||||
|
|
||||||
|
|
||||||
class MotionCommentSerializer(ModelSerializer):
|
class MotionCommentSerializer(ModelSerializer):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user