diff --git a/src/app/app.module.ts b/src/app/app.module.ts index cdb2b87b8f12804181c2ad3fe95b801f42ef7726..48b63d1647be0271bed79002ec739c9e80b48a87 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -13,6 +13,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { PageNotFoundPageComponent } from './components/pages/page-not-found-page/page-not-found-page.component'; import { FlexLayoutModule } from '@angular/flex-layout'; import { + MAT_DIALOG_DATA, MatAutocompleteModule, MatButtonModule, MatButtonToggleModule, @@ -21,6 +22,7 @@ import { MatChipsModule, MatDatepickerModule, MatDialogModule, + MatDialogRef, MatDividerModule, MatExpansionModule, MatFormFieldModule, @@ -78,6 +80,10 @@ import { ContentTextParticipantComponent } from './components/fragments/content- import { ContentTextCreatorComponent } from './components/fragments/content-text-creator/content-text-creator.component'; import { AuthenticationInterceptor } from './interceptors/authentication.interceptor'; import { HeaderComponent } from './components/fragments/header/header.component'; +import { ContentLikertCreatorComponent } from './components/fragments/content-likert-creator/content-likert-creator.component'; +import { ContentYesNoCreatorComponent } from './components/fragments/content-yes-no-creator/content-yes-no-creator.component'; +import { AnswerEditComponent } from './components/dialogs/answer-edit/answer-edit.component'; +import { ContentDeleteComponent } from './components/dialogs/content-delete/content-delete.component'; @NgModule({ declarations: [ @@ -110,14 +116,24 @@ import { HeaderComponent } from './components/fragments/header/header.component' ContentTextParticipantComponent, StatisticsComponent, ContentTextCreatorComponent, - HeaderComponent + HeaderComponent, + ContentLikertCreatorComponent, + ContentYesNoCreatorComponent, + AnswerEditComponent, + ContentDeleteComponent ], entryComponents: [ RegisterComponent, PasswordResetComponent, RoomCreateComponent, RoomDeleteComponent, - RoomEditComponent + RoomEditComponent, + AnswerEditComponent, + ContentChoiceCreatorComponent, + ContentLikertCreatorComponent, + ContentTextCreatorComponent, + ContentYesNoCreatorComponent, + ContentDeleteComponent ], imports: [ AppRoutingModule, @@ -172,7 +188,18 @@ import { HeaderComponent } from './components/fragments/header/header.component' RoomService, CommentService, ContentService, - ContentAnswerService + ContentAnswerService, + { + provide: MatDialogRef, + useValue: { + close: (dialogResult: any) => { + } + } + }, + { + provide: MAT_DIALOG_DATA, + useValue: [] + } ], bootstrap: [AppComponent] }) diff --git a/src/app/components/dialogs/answer-edit/answer-edit.component.html b/src/app/components/dialogs/answer-edit/answer-edit.component.html new file mode 100644 index 0000000000000000000000000000000000000000..797df3faf590ab249296c1cc70d5c175db3d6918 --- /dev/null +++ b/src/app/components/dialogs/answer-edit/answer-edit.component.html @@ -0,0 +1,19 @@ +<div *ngIf="answer"> + <mat-form-field class="input-block"> + <input [(ngModel)]="answer.answerOption.label" #roomName matInput placeholder="Answer label" name="answer-label"/> + </mat-form-field> + <mat-form-field class="input-block"> + <textarea [(ngModel)]="answer.answerOption.points" #roomDescription matInput matTextareaAutosize + matAutosizeMinRows="2" matAutosizeMaxRows="5" placeholder="Points" name="points"> + </textarea> + </mat-form-field> + <mat-checkbox [(ngModel)]="answer.correct" color="primary">Answer is {{answer.correct}}.</mat-checkbox> + <div fxLayout="row" fxLayoutAlign="center" fxLayoutGap="10px"> + <button (click)="dialogRef.close()" mat-button color="primary"> + Leave + </button> + <button (click)="dialogRef.close('edit')" mat-raised-button color="primary"> + Update + </button> + </div> +</div> diff --git a/src/app/components/dialogs/answer-edit/answer-edit.component.scss b/src/app/components/dialogs/answer-edit/answer-edit.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/app/components/dialogs/answer-edit/answer-edit.component.spec.ts b/src/app/components/dialogs/answer-edit/answer-edit.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..7515572bf0f33e5404fbce5978c7ef745aa236fa --- /dev/null +++ b/src/app/components/dialogs/answer-edit/answer-edit.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AnswerEditComponent } from './answer-edit.component'; + +describe('AnswerEditComponent', () => { + let component: AnswerEditComponent; + let fixture: ComponentFixture<AnswerEditComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AnswerEditComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AnswerEditComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/dialogs/answer-edit/answer-edit.component.ts b/src/app/components/dialogs/answer-edit/answer-edit.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..c3627bd627dee4e3343a9dbd9361a67fc79a5e0f --- /dev/null +++ b/src/app/components/dialogs/answer-edit/answer-edit.component.ts @@ -0,0 +1,23 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { ContentChoiceCreatorComponent, DisplayAnswer } from '../../fragments/content-choice-creator/content-choice-creator.component'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; + +@Component({ + selector: 'app-answer-edit', + templateUrl: './answer-edit.component.html', + styleUrls: ['./answer-edit.component.scss'] +}) +export class AnswerEditComponent implements OnInit { + answer: DisplayAnswer; + + constructor(public dialogRef: MatDialogRef<ContentChoiceCreatorComponent>, + @Inject(MAT_DIALOG_DATA) public data: any) { + } + + ngOnInit() { + } + + onNoClick(): void { + this.dialogRef.close(); + } +} diff --git a/src/app/components/dialogs/content-delete/content-delete.component.html b/src/app/components/dialogs/content-delete/content-delete.component.html new file mode 100644 index 0000000000000000000000000000000000000000..cf6fc8b3e690f27abe4864c42fbc4a8bad71aa1e --- /dev/null +++ b/src/app/components/dialogs/content-delete/content-delete.component.html @@ -0,0 +1,10 @@ +<h3>Are you sure?</h3> +<p>Do you really want to delete content <strong>{{content.subject}}</strong>? This action can not be undone.</p> +<div fxLayout="row" fxLayoutAlign="center" fxLayoutGap="10px"> + <button mat-raised-button color="warn" (click)="closeDialog('delete')"> + Delete content + </button> + <button mat-raised-button color="primary" (click)="closeDialog('abort')"> + Abort + </button> +</div> diff --git a/src/app/components/dialogs/content-delete/content-delete.component.scss b/src/app/components/dialogs/content-delete/content-delete.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/app/components/dialogs/content-delete/content-delete.component.spec.ts b/src/app/components/dialogs/content-delete/content-delete.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..0093e4f28d14c7b1d56a029584f7777f021df670 --- /dev/null +++ b/src/app/components/dialogs/content-delete/content-delete.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ContentDeleteComponent } from './content-delete.component'; + +describe('ContentDeleteComponent', () => { + let component: ContentDeleteComponent; + let fixture: ComponentFixture<ContentDeleteComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ContentDeleteComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ContentDeleteComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/dialogs/content-delete/content-delete.component.ts b/src/app/components/dialogs/content-delete/content-delete.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..a0abe7bd228e4df8c0d742dcf22fa03c39708dfe --- /dev/null +++ b/src/app/components/dialogs/content-delete/content-delete.component.ts @@ -0,0 +1,34 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { Router } from '@angular/router'; +import { NotificationService } from '../../../services/util/notification.service'; +import { ContentType } from '../../../models/content-type.enum'; +import { Content } from '../../../models/content'; + +@Component({ + selector: 'app-content-delete', + templateUrl: './content-delete.component.html', + styleUrls: ['./content-delete.component.scss'] +}) +export class ContentDeleteComponent implements OnInit { + ContentType: typeof ContentType = ContentType; + format: ContentType; + content: Content; + + constructor(private router: Router, + private notification: NotificationService, + public dialogRef: MatDialogRef<any>, + @Inject(MAT_DIALOG_DATA) public data: any) { + } + + onNoClick(): void { + this.dialogRef.close('abort'); + } + + closeDialog(action: string) { + this.dialogRef.close(action); + } + + ngOnInit() { + } +} diff --git a/src/app/components/fragments/content-choice-creator/content-choice-creator.component.html b/src/app/components/fragments/content-choice-creator/content-choice-creator.component.html index 6f3cee20c23a9be85ca7835d0e23e33c0fe5cc9b..a61d5163bf2a15186737c3dc31895d6da65ccd78 100644 --- a/src/app/components/fragments/content-choice-creator/content-choice-creator.component.html +++ b/src/app/components/fragments/content-choice-creator/content-choice-creator.component.html @@ -1,52 +1,91 @@ <form (ngSubmit)="submitContent()"> - <mat-form-field class="input-block"> - <input matInput #subject [(ngModel)]="content.subject" placeholder="Subject" name="subject"> - </mat-form-field> - <mat-form-field class="input-block"> - <textarea matInput #body [(ngModel)]="content.body" placeholder="Body" name="body"></textarea> - </mat-form-field> - <mat-divider></mat-divider> + <section class="choice-seformn" fxLayout="row" fxLayoutAlign="center" fxLayoutGap="20px"> + <mat-checkbox (click)="singleChoice = true; multipleChoice = false;" + color="primary" [(ngModel)]="singleChoice" name="singleChoice">Single choice + </mat-checkbox> + <mat-checkbox (click)="multipleChoice = true; singleChoice = false;" + color="primary" [(ngModel)]="multipleChoice" name="multiChoice">Multiple choice + </mat-checkbox> + </section> + <div *ngIf="singleChoice || multipleChoice"> + <mat-form-field class="input-block"> + <input matInput #subject [(ngModel)]="content.subject" placeholder="Subject" name="subject"> + </mat-form-field> + <mat-form-field class="input-block"> + <textarea matInput #body [(ngModel)]="content.body" placeholder="Body" name="body"></textarea> + </mat-form-field> + <mat-divider></mat-divider> - <mat-table #table [dataSource]="displayAnswers"> + <mat-table #table [dataSource]="displayAnswers"> - <ng-container matColumnDef="label"> - <mat-header-cell *matHeaderCellDef>Answer</mat-header-cell> - <mat-cell *matCellDef="let answer"> - <!-- ToDo: Check ngModel --> - <mat-checkbox color="primary" [(ngModel)]="answer.correct" [checked]="answer.correct" - name="{{ answer.answerOption.label }}">{{ answer.answerOption.label }} is {{ answer.correct }} - </mat-checkbox> - </mat-cell> - </ng-container> + <ng-container matColumnDef="label"> + <mat-header-cell *matHeaderCellDef>Answer</mat-header-cell> + <mat-cell *matCellDef="let answer"> + <!-- ToDo: Check ngModel --> + <mat-checkbox color="primary" (click)="switchValue(answer.answerOption.label)" [(ngModel)]="answer.correct" + [checked]="answer.correct" + name="{{ answer.answerOption.label }}">{{ answer.answerOption.label }} + </mat-checkbox> + </mat-cell> + </ng-container> + + <ng-container matColumnDef="points"> + <mat-header-cell *matHeaderCellDef>Points</mat-header-cell> + <mat-cell *matCellDef="let answer">{{ answer.answerOption.points }}</mat-cell> + </ng-container> - <ng-container matColumnDef="points"> - <mat-header-cell *matHeaderCellDef>Points</mat-header-cell> - <mat-cell *matCellDef="let answer">{{ answer.answerOption.points }}</mat-cell> - </ng-container> + <ng-container matColumnDef="actions"> + <mat-header-cell *matHeaderCellDef>Actions</mat-header-cell> + <mat-cell *matCellDef="let answer"> + <button mat-icon-button + (click)="openAnswerModificationDialog($event, answer.answerOption.label, answer.answerOption.points, answer.correct)" + color="primary" matTooltip="Edit answer"> + <mat-icon>edit</mat-icon> + </button> + <button mat-icon-button color="primary" (click)="deleteAnswer($event, answer.answerOption.label)" + matTooltip="Delete answer"> + <mat-icon>delete</mat-icon> + </button> + </mat-cell> + </ng-container> - <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> - <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row> - </mat-table> + <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> + <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row> + </mat-table> - <mat-divider></mat-divider> + <mat-divider></mat-divider> - <div fxLayout="row" fxLayoutGap="15px"> - <div fxLayout="column" fxLayoutAlign="center"> - <mat-checkbox #answerIsCorrect [(ngModel)]="newAnswerOptionChecked" [checked]="newAnswerOptionChecked" color="primary" name="ischecked">Is correct</mat-checkbox> + <div fxLayout="row" fxLayoutGap="15px"> + <div fxLayout="column" fxLayoutAlign="center"> + <mat-checkbox #answerIsCorrect [(ngModel)]="newAnswerOptionChecked" [checked]="newAnswerOptionChecked" + color="primary" name="ischecked">Is correct + </mat-checkbox> + </div> + <mat-form-field class="input-block"> + <input matInput #answerLabel [(ngModel)]="newAnswerOptionLabel" placeholder="Answer" name="answer"> + </mat-form-field> + <mat-form-field class="input-block"> + <input matInput #answerPoints [(ngModel)]="newAnswerOptionPoints" placeholder="Points" name="points"> + </mat-form-field> + <div fxLayout="column" fxLayoutAlign="center"> + <button mat-button type="button" + (click)="addAnswer($event); answerIsCorrect.checked = false; answerLabel.value = ''; answerPoints.value = ''"> + Add Answer + </button> + </div> </div> - <mat-form-field class="input-block"> - <input matInput #answerLabel [(ngModel)]="newAnswerOptionLabel" placeholder="Answer" name="answer"> - </mat-form-field> - <mat-form-field class="input-block"> - <input matInput #answerPoints [(ngModel)]="newAnswerOptionPoints" placeholder="Points" name="points"> - </mat-form-field> - <div fxLayout="column" fxLayoutAlign="center"> - <button mat-button type="button" - (click)="addAnswer(); answerIsCorrect.checked = false; answerLabel.value = ''; answerPoints.value = ''"> - Add Answer + <div *ngIf="!editDialogMode"> + <button mat-raised-button type="submit" color="primary">Submit</button> + <button mat-raised-button (click)="reset($event)" color="primary">Reset</button> + <button mat-raised-button *ngIf="lastDeletedDisplayAnswer" (click)="recoverDeletedAnswer($event)" color="primary"> + Undo + deletion </button> </div> + <div *ngIf="editDialogMode"> + <button mat-raised-button (click)="editDialogClose($event,'edit')" color="primary">Update</button> + <button mat-raised-button (click)="editDialogClose($event,'abort')" color="primary">Abort</button> + <button mat-raised-button (click)="openDeletionContentDialog($event)" color="warn">Delete</button> + </div> </div> - - <button mat-raised-button type="submit" color="primary">Submit</button> </form> diff --git a/src/app/components/fragments/content-choice-creator/content-choice-creator.component.scss b/src/app/components/fragments/content-choice-creator/content-choice-creator.component.scss index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..687e7d96c8ae5f81676b91d911abedb0790ac2f9 100644 --- a/src/app/components/fragments/content-choice-creator/content-choice-creator.component.scss +++ b/src/app/components/fragments/content-choice-creator/content-choice-creator.component.scss @@ -0,0 +1,3 @@ +form > button { + margin: 20px 0; +} diff --git a/src/app/components/fragments/content-choice-creator/content-choice-creator.component.ts b/src/app/components/fragments/content-choice-creator/content-choice-creator.component.ts index 48b8a0b08b0c0eaa16cd013e7aa549800352ef39..0b81440c4ea1f444a03aee7e9da1a258c3a27395 100644 --- a/src/app/components/fragments/content-choice-creator/content-choice-creator.component.ts +++ b/src/app/components/fragments/content-choice-creator/content-choice-creator.component.ts @@ -1,7 +1,14 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Inject, OnInit } from '@angular/core'; import { AnswerOption } from '../../../models/answer-option'; import { ContentChoice } from '../../../models/content-choice'; import { ContentService } from '../../../services/http/content.service'; +import { NotificationService } from '../../../services/util/notification.service'; +import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material'; +import { AnswerEditComponent } from '../../dialogs/answer-edit/answer-edit.component'; +import { ContentType } from '../../../models/content-type.enum'; +import { ActivatedRoute } from '@angular/router'; +import { ContentListComponent } from '../content-list/content-list.component'; +import { ContentDeleteComponent } from '../../dialogs/content-delete/content-delete.component'; export class DisplayAnswer { answerOption: AnswerOption; @@ -19,7 +26,8 @@ export class DisplayAnswer { styleUrls: ['./content-choice-creator.component.scss'] }) export class ContentChoiceCreatorComponent implements OnInit { - + singleChoice: boolean; + multipleChoice: boolean; content: ContentChoice = new ContentChoice('0', '1', '', @@ -28,20 +36,36 @@ export class ContentChoiceCreatorComponent implements OnInit { 1, [], [], - true); + true, + ContentType.CHOICE); - displayedColumns = ['label', 'points']; + displayedColumns = ['label', 'points', 'actions']; displayAnswers: DisplayAnswer[] = []; + lastDeletedDisplayAnswer: DisplayAnswer; newAnswerOptionChecked = false; newAnswerOptionLabel = ''; newAnswerOptionPoints = ''; - constructor(private contentService: ContentService) { + editDisplayAnswer: DisplayAnswer; + originalDisplayAnswer: DisplayAnswer; + + editDialogMode = false; + changesAllowed = false; + + constructor(private contentService: ContentService, + private notificationService: NotificationService, + private route: ActivatedRoute, + public dialog: MatDialog, + public dialogRef: MatDialogRef<ContentListComponent>, + @Inject(MAT_DIALOG_DATA) public data: any) { } ngOnInit() { + this.route.params.subscribe(params => { + this.content.roomId = params['roomId']; + }); this.fillCorrectAnswers(); } @@ -52,16 +76,39 @@ export class ContentChoiceCreatorComponent implements OnInit { } } - submitContent() { - if (this.content.contentId === '0') { - this.contentService.addContent(this.content).subscribe(); - } else { - // ToDo: Implement function in service - // this.contentService.updateContent(this.content).subscribe(); + findAnswerIndexByLabel(label: string): number { + let index = -1; + for (let i = 0; i < this.content.options.length; i++) { + if (this.content.options[i].label.valueOf() === label.valueOf()) { + index = i; + break; + } } + return index; } - addAnswer() { + addAnswer($event) { + $event.preventDefault(); + if (this.newAnswerOptionLabel === '') { + this.notificationService.show('No empty answers allowed.'); + this.newAnswerOptionChecked = false; + this.newAnswerOptionLabel = ''; + this.newAnswerOptionPoints = ''; + return; + } + if (this.singleChoice && this.content.correctOptionIndexes.length > 0 && this.newAnswerOptionChecked) { + this.notificationService.show('In single choice mode is only 1 true answer allowed.'); + this.newAnswerOptionChecked = false; + this.newAnswerOptionLabel = ''; + this.newAnswerOptionPoints = ''; + return; + } + for (let i = 0; i < this.content.options.length; i++) { + if (this.content.options[i].label.valueOf() === this.newAnswerOptionLabel.valueOf()) { + this.notificationService.show('Same answer label is not allowed.'); + return; + } + } this.content.options.push(new AnswerOption(this.newAnswerOptionLabel, this.newAnswerOptionPoints)); if (this.newAnswerOptionChecked) { this.content.correctOptionIndexes.push(this.content.options.length - 1); @@ -70,6 +117,182 @@ export class ContentChoiceCreatorComponent implements OnInit { this.newAnswerOptionLabel = ''; this.newAnswerOptionPoints = ''; this.fillCorrectAnswers(); - this.submitContent(); + } + + openAnswerModificationDialog($event, label: string, points: string, correct: boolean) { + $event.preventDefault(); + const index = this.findAnswerIndexByLabel(label); + this.editDisplayAnswer = new DisplayAnswer(new AnswerOption(label, points), correct); + this.originalDisplayAnswer = new DisplayAnswer(new AnswerOption(label, points), correct); + const dialogRef = this.dialog.open(AnswerEditComponent, { + width: '400px' + }); + dialogRef.componentInstance.answer = this.editDisplayAnswer; + dialogRef.afterClosed() + .subscribe(result => { + if (result === 'edit') { + this.saveChanges(index, this.editDisplayAnswer, true); + } + }); + } + + saveChanges(index: number, answer: DisplayAnswer, matDialogOutput: boolean) { + this.content.options[index].label = answer.answerOption.label; + this.content.options[index].points = answer.answerOption.points; + const indexInCorrectOptionIndexes = this.content.correctOptionIndexes.indexOf(index); + if (indexInCorrectOptionIndexes === -1 && answer.correct) { + if (this.singleChoice) { + this.content.correctOptionIndexes = [index]; + this.fillCorrectAnswers(); + return; + } + this.content.correctOptionIndexes.push(index); + } + if (indexInCorrectOptionIndexes !== -1 && !answer.correct) { + this.content.correctOptionIndexes.splice(indexInCorrectOptionIndexes, 1); + } + this.fillCorrectAnswers(); + if (matDialogOutput) { + this.notificationService.show('Update changes.'); + } + } + + deleteAnswer($event, label: string) { + $event.preventDefault(); + const index = this.findAnswerIndexByLabel(label); + this.lastDeletedDisplayAnswer = new DisplayAnswer(this.content.options[index], false); + this.content.options.splice(index, 1); + for (let j = 0; j < this.content.correctOptionIndexes.length; j++) { + if (this.content.correctOptionIndexes[j] === index) { + this.lastDeletedDisplayAnswer.correct = true; + this.content.correctOptionIndexes.splice(j, 1); + } + if (this.content.correctOptionIndexes[j] > index) { // [j] > i + this.content.correctOptionIndexes[j] = this.content.correctOptionIndexes[j] - 1; + } + } + this.fillCorrectAnswers(); + this.notificationService.show('Answer "' + this.lastDeletedDisplayAnswer.answerOption.label + '" successfully deleted.'); + } + + recoverDeletedAnswer($event) { + $event.preventDefault(); + let msgAddon = 'Answer "' + this.lastDeletedDisplayAnswer.answerOption.label + '" successfully recovered.'; + if (this.lastDeletedDisplayAnswer === null) { + this.notificationService.show('Nothing to recover.'); + } + for (let i = 0; i < this.content.options.length; i++) { + if (this.content.options[i].label.valueOf() === this.lastDeletedDisplayAnswer.answerOption.label.valueOf()) { + this.notificationService.show('Same answer label is not allowed.'); + return; + } + } + this.content.options.push(this.lastDeletedDisplayAnswer.answerOption); + if (this.lastDeletedDisplayAnswer.correct) { + if (this.singleChoice && this.content.correctOptionIndexes.length > 0) { + msgAddon = 'In single mode is only 1 true answer allowed. Recovered item is set to false.'; + } else { + this.content.correctOptionIndexes.push(this.content.options.length - 1); + } + } + this.notificationService.show(msgAddon); + this.lastDeletedDisplayAnswer = null; + this.fillCorrectAnswers(); + } + + switchValue(label: string) { + const index = this.findAnswerIndexByLabel(label); + this.editDisplayAnswer = new DisplayAnswer( + new AnswerOption( + this.displayAnswers[index].answerOption.label, + this.displayAnswers[index].answerOption.points), + !this.displayAnswers[index].correct); + this.saveChanges(index, this.editDisplayAnswer, false); + } + + reset($event) { + $event.preventDefault(); + this.content.subject = ''; + this.content.body = ''; + this.content.options = []; + this.content.correctOptionIndexes = []; + this.fillCorrectAnswers(); + this.notificationService.show('Reset all inputs to default.'); + } + + resetAfterSubmit() { + this.content.subject = ''; + this.content.body = ''; + this.content.options = []; + this.content.correctOptionIndexes = []; + this.fillCorrectAnswers(); + this.notificationService.show('Content submitted. Ready for creation of new content.'); + } + + submitContent() { + if (this.content.body.valueOf() === '' || this.content.body.valueOf() === '') { + this.notificationService.show('No empty fields allowed. Please check subject and body.'); + return; + } + if (this.content.options.length === 0) { + this.notificationService.show('Choice content needs answers. Please add some answers.'); + return; + } + if (this.singleChoice && this.content.correctOptionIndexes.length !== 1) { + this.notificationService.show('In single choice mode you have to select 1 true answer.'); + return; + } + if (this.multipleChoice && this.content.correctOptionIndexes.length < 1) { + this.notificationService.show('In multiple choice mode you have to select at least 1 true answer.'); + return; + } + if (this.singleChoice) { + this.content.multiple = false; + this.content.format = ContentType.BINARY; + } + if (this.multipleChoice) { + this.content.multiple = true; + this.content.format = ContentType.CHOICE; + } + if (this.editDialogMode) { + this.changesAllowed = true; + return; + } + // ToDo: Check api call + // this.contentService.addContent(this.content); + // For Testing: + // console.log(this.content); + this.resetAfterSubmit(); + } + + editDialogClose($event, action: string) { + $event.preventDefault(); + if (action.valueOf() === 'edit') { + this.submitContent(); + } + if (action.valueOf() === 'abort') { + this.dialogRef.close(action); + } + if (this.changesAllowed) { + this.dialogRef.close(action); + } + } + + onNoClick(): void { + this.dialogRef.close(); + } + + openDeletionContentDialog($event): void { + $event.preventDefault(); + const dialogRef = this.dialog.open(ContentDeleteComponent, { + width: '400px' + }); + dialogRef.componentInstance.content = this.content; + dialogRef.afterClosed() + .subscribe(result => { + if (result === 'delete') { + this.dialogRef.close(result); + } + }); } } diff --git a/src/app/components/fragments/content-choice-participant/content-choice-participant.component.html b/src/app/components/fragments/content-choice-participant/content-choice-participant.component.html index b731b9397f64528f72a58473ea31e5af0fd77e0c..c6f2a01e4e2da3e0fdf16aa0a8e57f385d23168b 100644 --- a/src/app/components/fragments/content-choice-participant/content-choice-participant.component.html +++ b/src/app/components/fragments/content-choice-participant/content-choice-participant.component.html @@ -2,10 +2,28 @@ <h1 class="mat-headline">{{ content.subject }}</h1> <p>{{ content.body }}</p> <mat-divider></mat-divider> - <mat-list> + <mat-list *ngIf="content.multiple"> <mat-list-item *ngFor="let answer of checkedAnswers"> - <mat-checkbox color="primary" [(ngModel)]="answer.checked" name="answer">{{ answer.answerOption.label }}</mat-checkbox> + <mat-checkbox color="primary" [(ngModel)]="answer.checked" name="answer"> + {{ answer.answerOption.label }} + </mat-checkbox> </mat-list-item> </mat-list> - <button mat-raised-button color="primary">Send answer</button> + <mat-radio-group *ngIf="!content.multiple" [(ngModel)]="selectedSingleAnswer" name="Radio"> + <mat-list> + <mat-list-item *ngFor="let answer of checkedAnswers"> + <mat-radio-button color="primary" [value]="answer.answerOption.label" name="answer"> + {{ answer.answerOption.label }} + </mat-radio-button> + </mat-list-item> + </mat-list> + </mat-radio-group> + <mat-divider></mat-divider> + <div fxLayout fxLayoutGap="5px" fxAlign="row"> + <button mat-raised-button type="submit" color="primary">Submit</button> + <button mat-raised-button (click)="abstain($event)" color="primary">Abstain</button> + <mat-chip-list *ngIf="isAnswerSent"> + <mat-chip color="primary" selected="true">Answer sent</mat-chip> + </mat-chip-list> + </div> </form> diff --git a/src/app/components/fragments/content-choice-participant/content-choice-participant.component.scss b/src/app/components/fragments/content-choice-participant/content-choice-participant.component.scss index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..615eaa08f34372a38036c3138ca8ba7e5b3bc5fc 100644 --- a/src/app/components/fragments/content-choice-participant/content-choice-participant.component.scss +++ b/src/app/components/fragments/content-choice-participant/content-choice-participant.component.scss @@ -0,0 +1,11 @@ +form > div > button { + margin: 20px 0; +} + +form > div > mat-chip-list { + margin: 20px 0; +} + +form > h1 { + margin:20px 0; +} diff --git a/src/app/components/fragments/content-choice-participant/content-choice-participant.component.ts b/src/app/components/fragments/content-choice-participant/content-choice-participant.component.ts index b7890316fe41031743a7c8d9841cbe32e8af235c..4d48783545349c942905c8c35ccb539886859281 100644 --- a/src/app/components/fragments/content-choice-participant/content-choice-participant.component.ts +++ b/src/app/components/fragments/content-choice-participant/content-choice-participant.component.ts @@ -1,7 +1,10 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { ContentChoice } from '../../../models/content-choice'; import { AnswerOption } from '../../../models/answer-option'; import { ContentAnswerService } from '../../../services/http/content-answer.service'; +import { NotificationService } from '../../../services/util/notification.service'; +import { AnswerChoice } from '../../../models/answer-choice'; +import { ContentType } from '../../../models/content-type.enum'; class CheckedAnswer { answerOption: AnswerOption; @@ -19,7 +22,12 @@ class CheckedAnswer { styleUrls: ['./content-choice-participant.component.scss'] }) export class ContentChoiceParticipantComponent implements OnInit { - content: ContentChoice = new ContentChoice('2', + @Input() content: ContentChoice; + ContentType: typeof ContentType = ContentType; + + selectedSingleAnswer: string; + + dummyContent: ContentChoice = new ContentChoice('2', '1', '1', 'Choice Content 1', @@ -32,10 +40,13 @@ export class ContentChoiceParticipantComponent implements OnInit { new AnswerOption('Option 4', '30') ], [2, 3, 4], - true); + false, + ContentType.BINARY); checkedAnswers: CheckedAnswer[] = []; + isAnswerSent = false; - constructor(private answerService: ContentAnswerService) { + constructor(private answerService: ContentAnswerService, + private notificationService: NotificationService) { } ngOnInit() { @@ -49,13 +60,50 @@ export class ContentChoiceParticipantComponent implements OnInit { } submitAnswer(): void { - const selectedAnswers: number[] = []; - for (let i = 0; i < this.checkedAnswers.length; i++) { - if (this.checkedAnswers[i].checked) { - selectedAnswers.push(i); + let selectedAnswers: number[] = []; + if (this.content.multiple) { + for (let i = 0; i < this.checkedAnswers.length; i++) { + if (this.checkedAnswers[i].checked) { + selectedAnswers.push(i); + } } + } else { + for (let i = 0; i < this.checkedAnswers.length; i++) { + if (this.checkedAnswers[i].answerOption.label === this.selectedSingleAnswer) { + selectedAnswers = [i]; + break; + } + } + } + + if (!this.content.multiple && selectedAnswers.length !== 1) { + this.notificationService.show('In single choice mode is only 1 selection allowed'); + this.isAnswerSent = false; + return; + } + if (this.content.multiple && selectedAnswers.length === 0) { + this.notificationService.show('In multiple choice mode is at least 1 selection needed'); + this.isAnswerSent = false; + return; } + this.isAnswerSent = true; // ToDo: Implement function in service - // this.answerService.addChoiceAnswer(selectedAnswers); + /* + this.answerService.addAnswerChoice({ + id: '0', + revision: '0', + contentId: this.content.contentId, + round: this.content.round, + selectedChoiceIndexes: selectedAnswers, + } as AnswerChoice).subscribe(result => { + // TODO: Set isAnswerSent + }); + */ + } + + abstain($event) { + $event.preventDefault(); + console.log('abstain'); + // ToDo: Send emtpy answer to backend } } diff --git a/src/app/components/fragments/content-likert-creator/content-likert-creator.component.html b/src/app/components/fragments/content-likert-creator/content-likert-creator.component.html new file mode 100644 index 0000000000000000000000000000000000000000..29748e8b1b64b7ae6584c647360f7322f45a22fd --- /dev/null +++ b/src/app/components/fragments/content-likert-creator/content-likert-creator.component.html @@ -0,0 +1,31 @@ +<form (ngSubmit)="submitContent()"> + <mat-form-field class="input-block"> + <input matInput #subject [(ngModel)]="content.subject" placeholder="Subject" name="subject"> + </mat-form-field> + <mat-form-field class="input-block"> + <textarea matInput #body [(ngModel)]="content.body" placeholder="Body" name="body"></textarea> + </mat-form-field> + <mat-divider></mat-divider> + + <mat-table #table [dataSource]="displayAnswers"> + + <ng-container matColumnDef="label"> + <mat-header-cell *matHeaderCellDef>Answer</mat-header-cell> + <mat-cell *matCellDef="let answer"> + <!-- ToDo: Check ngModel --> + {{ answer.answerOption.label }} + </mat-cell> + </ng-container> + + <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> + <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row> + </mat-table> + <div *ngIf="!editDialogMode"> + <button mat-raised-button type="submit" color="primary">Submit</button> + </div> + <div *ngIf="editDialogMode"> + <button mat-raised-button (click)="editDialogClose($event,'edit')" color="primary">Update</button> + <button mat-raised-button (click)="editDialogClose($event,'abort')" color="primary">Abort</button> + <button mat-raised-button (click)="openDeletionContentDialog($event)" color="warn">Delete</button> + </div> +</form> diff --git a/src/app/components/fragments/content-likert-creator/content-likert-creator.component.scss b/src/app/components/fragments/content-likert-creator/content-likert-creator.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..687e7d96c8ae5f81676b91d911abedb0790ac2f9 --- /dev/null +++ b/src/app/components/fragments/content-likert-creator/content-likert-creator.component.scss @@ -0,0 +1,3 @@ +form > button { + margin: 20px 0; +} diff --git a/src/app/components/fragments/content-likert-creator/content-likert-creator.component.spec.ts b/src/app/components/fragments/content-likert-creator/content-likert-creator.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..9dccca82016b0d5324e65fb9cd77a5a0da2ae4b2 --- /dev/null +++ b/src/app/components/fragments/content-likert-creator/content-likert-creator.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ContentLikertCreatorComponent } from './content-likert-creator.component'; + +describe('ContentLikertCreatorComponent', () => { + let component: ContentLikertCreatorComponent; + let fixture: ComponentFixture<ContentLikertCreatorComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ContentLikertCreatorComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ContentLikertCreatorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/fragments/content-likert-creator/content-likert-creator.component.ts b/src/app/components/fragments/content-likert-creator/content-likert-creator.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..24c5a125cd81d64fc180fe15a34d8d52919eef71 --- /dev/null +++ b/src/app/components/fragments/content-likert-creator/content-likert-creator.component.ts @@ -0,0 +1,114 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { DisplayAnswer } from '../content-choice-creator/content-choice-creator.component'; +import { ContentChoice } from '../../../models/content-choice'; +import { AnswerOption } from '../../../models/answer-option'; +import { ContentType } from '../../../models/content-type.enum'; +import { ContentService } from '../../../services/http/content.service'; +import { NotificationService } from '../../../services/util/notification.service'; +import { ActivatedRoute } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material'; +import { ContentListComponent } from '../content-list/content-list.component'; +import { ContentDeleteComponent } from '../../dialogs/content-delete/content-delete.component'; + +@Component({ + selector: 'app-content-likert-creator', + templateUrl: './content-likert-creator.component.html', + styleUrls: ['./content-likert-creator.component.scss'] +}) +export class ContentLikertCreatorComponent implements OnInit { + likertScale = [ + 'Strongly agree', + 'Agree', + 'Neither agree nor disagree', + 'Disagree', + 'Strongly disagree' + ]; + + content: ContentChoice = new ContentChoice('0', + '1', + '', + '', + '', + 1, + [], + [], + false, + ContentType.SCALE); + + displayedColumns = ['label']; + + displayAnswers: DisplayAnswer[] = []; + newAnswerOptionPoints = '0'; + + editDialogMode = false; + + constructor(private contentService: ContentService, + private notificationService: NotificationService, + private route: ActivatedRoute, + public dialog: MatDialog, + public dialogRef: MatDialogRef<ContentListComponent>, + @Inject(MAT_DIALOG_DATA) public data: any) { + } + + fillCorrectAnswers() { + this.displayAnswers = []; + for (let i = 0; i < this.content.options.length; i++) { + this.content.correctOptionIndexes.push(i); + this.displayAnswers.push(new DisplayAnswer(this.content.options[i], this.content.correctOptionIndexes.includes(i))); + } + } + + ngOnInit() { + this.route.params.subscribe(params => { + this.content.roomId = params['roomId']; + }); + for (let i = 0; i < this.likertScale.length; i++) { + this.content.options.push(new AnswerOption(this.likertScale[i], this.newAnswerOptionPoints)); + } + this.fillCorrectAnswers(); + } + + resetAfterSubmit() { + this.content.subject = ''; + this.content.body = ''; + this.content.correctOptionIndexes = []; + this.fillCorrectAnswers(); + this.notificationService.show('Content submitted. Ready for creation of new content.'); + } + + submitContent(): void { + if (this.content.body.valueOf() === '' || this.content.body.valueOf() === '') { + this.notificationService.show('No empty fields allowed. Please check subject and body.'); + return; + } + this.notificationService.show('Content sumbitted.'); + // ToDo: Check api call + // this.contentService.addContent(this.content); + // For Testing: + // console.log(this.content); + this.resetAfterSubmit(); + } + + editDialogClose($event, action: string) { + $event.preventDefault(); + this.dialogRef.close(action); + } + + onNoClick(): void { + this.dialogRef.close(); + } + + openDeletionContentDialog($event): void { + $event.preventDefault(); + const dialogRef = this.dialog.open(ContentDeleteComponent, { + width: '400px' + }); + dialogRef.componentInstance.content = this.content; + dialogRef.afterClosed() + .subscribe(result => { + if (result === 'delete') { + this.dialogRef.close(result); + } + }); + } +} diff --git a/src/app/components/fragments/content-list/content-list.component.html b/src/app/components/fragments/content-list/content-list.component.html index fd966e2ad255f8af5c2cb07e0415cd9ea36a7aa7..756295061ad1c9f6378274d66d5efdad82d291d0 100644 --- a/src/app/components/fragments/content-list/content-list.component.html +++ b/src/app/components/fragments/content-list/content-list.component.html @@ -1,7 +1,7 @@ <mat-list> <mat-list-item *ngFor="let content of contents"> - <button mat-button routerLink="content/{{content.contentId}}"> - Content {{content.contentId}}: {{content.subject}} + <button mat-button (click)="editContent(content.subject)"> + {{content.subject}} </button> </mat-list-item> </mat-list> diff --git a/src/app/components/fragments/content-list/content-list.component.ts b/src/app/components/fragments/content-list/content-list.component.ts index b209b36a5e5a447c43d3af38f38891282e1f0013..aacb8c1ba1994b9bfea4cc21cc434f60768506cc 100644 --- a/src/app/components/fragments/content-list/content-list.component.ts +++ b/src/app/components/fragments/content-list/content-list.component.ts @@ -2,6 +2,15 @@ import { Component, OnInit } from '@angular/core'; import { ContentService } from '../../../services/http/content.service'; import { Content } from '../../../models/content'; import { ActivatedRoute } from '@angular/router'; +import { ContentChoice } from '../../../models/content-choice'; +import { ContentText } from '../../../models/content-text'; +import { AnswerOption } from '../../../models/answer-option'; +import { ContentType } from '../../../models/content-type.enum'; +import { MatDialog } from '@angular/material'; +import { ContentChoiceCreatorComponent } from '../content-choice-creator/content-choice-creator.component'; +import { ContentLikertCreatorComponent } from '../content-likert-creator/content-likert-creator.component'; +import { ContentTextCreatorComponent } from '../content-text-creator/content-text-creator.component'; +import { NotificationService } from '../../../services/util/notification.service'; @Component({ selector: 'app-content-list', @@ -9,15 +18,64 @@ import { ActivatedRoute } from '@angular/router'; styleUrls: ['./content-list.component.scss'] }) export class ContentListComponent implements OnInit { - contents: Content[]; + // contents: Content[]; + contents = [ + new ContentChoice('0', + '1', + 'roomId1', + 'MultipleChoice Subject', + 'MultipleChoice Body', + 1, + [new AnswerOption('yes', ''), new AnswerOption('no', '')], + [0], + true, + ContentType.CHOICE), + new ContentChoice('0', + '1', + 'roomId2', + 'SingleChoice Subject', + 'SingleChoice Body', + 1, + [new AnswerOption('may', ''), new AnswerOption('not', '')], + [1], + false, + ContentType.BINARY), + new ContentText('1', + '1', + 'roomId3', + 'TextContent Subject', + 'TextContent Body', + 1), + new ContentChoice('0', + '1', + 'roomId4', + 'LikertContent Subject', + 'LikertContent Body', + 1, + [ + new AnswerOption('Strongly agree', '0'), + new AnswerOption('Agree', '0'), + new AnswerOption('Neither agree nor disagree', '0'), + new AnswerOption('Disagree', '0'), + new AnswerOption('Strongly disagree', '0')], + [], + false, + ContentType.SCALE) + ]; + + contentBackup: Content; + + ContentType: typeof ContentType = ContentType; constructor(private contentService: ContentService, - private route: ActivatedRoute) { + private route: ActivatedRoute, + private notificationService: NotificationService, + public dialog: MatDialog) { } ngOnInit() { this.route.params.subscribe(params => { - this.getContents(params['roomId']); +// this.getContents(params['roomId']); }); } @@ -27,4 +85,156 @@ export class ContentListComponent implements OnInit { this.contents = contents; }); } + + findIndexOfSubject(subject: string): number { + let index = -1; + for (let i = 0; i < this.contents.length; i++) { + if (this.contents[i].subject.valueOf() === subject.valueOf()) { + index = i; + break; + } + } + return index; + } + + createChoiceContentBackup(content: ContentChoice) { + const answerOptions: Array<AnswerOption> = new Array<AnswerOption> (); + const correctAnswers: number[] = []; + + for (let i = 0; i < content.options.length; i++) { + answerOptions.push(content.options[i]); + } + + for (let i = 0; i < content.correctOptionIndexes.length; i++) { + correctAnswers.push(content.correctOptionIndexes[i]); + } + + this.contentBackup = new ContentChoice( + content.contentId, + content.revision, + content.roomId, + content.subject, + content.body, + content.round, + answerOptions, + correctAnswers, + content.multiple, + content.format + ); + } + + createTextContentBackup(content: ContentText) { + this.contentBackup = new ContentText( + content.contentId, + content.revision, + content.roomId, + content.subject, + content.body, + content.round + ); + } + + editContent(subject: string) { + const index = this.findIndexOfSubject(subject); + const format = this.contents[index].format; + + if (format === this.ContentType.TEXT) { + this.createTextContentBackup(this.contents[index] as ContentText); + } else { + this.createChoiceContentBackup(this.contents[index] as ContentChoice); + } + + switch (format) { + case this.ContentType.CHOICE: + this.editChoiceContentDialog(index, this.contents[index] as ContentChoice); + break; + case this.ContentType.BINARY: + this.editBinaryContentDialog(index, this.contents[index] as ContentChoice); + break; + case this.ContentType.SCALE: + this.editLikertContentDialog(index, this.contents[index] as ContentChoice); + break; + case this.ContentType.TEXT: + this.editTextContentDialog(index, this.contents[index] as ContentText); + break; + default: + return; + } + } + + editChoiceContentDialog(index: number, content: ContentChoice) { + const dialogRef = this.dialog.open(ContentChoiceCreatorComponent, { + width: '800px' + }); + if (content.multiple) { + dialogRef.componentInstance.singleChoice = false; + dialogRef.componentInstance.multipleChoice = true; + } else { + dialogRef.componentInstance.multipleChoice = false; + dialogRef.componentInstance.singleChoice = true; + } + dialogRef.componentInstance.editDialogMode = true; + dialogRef.componentInstance.content = content; + dialogRef.afterClosed() + .subscribe(result => { + this.updateContentChanges(index, result); + }); + } + + editBinaryContentDialog(index: number, content: ContentChoice) { + const dialogRef = this.dialog.open(ContentChoiceCreatorComponent, { + width: '800px' + }); + dialogRef.componentInstance.editDialogMode = true; + dialogRef.componentInstance.multipleChoice = false; + dialogRef.componentInstance.singleChoice = true; + dialogRef.componentInstance.content = content; + dialogRef.afterClosed() + .subscribe(result => { + this.updateContentChanges(index, result); + }); + } + + editLikertContentDialog(index: number, content: ContentChoice) { + const dialogRef = this.dialog.open(ContentLikertCreatorComponent, { + width: '800px' + }); + dialogRef.componentInstance.editDialogMode = true; + dialogRef.componentInstance.content = content; + dialogRef.afterClosed() + .subscribe(result => { + this.updateContentChanges(index, result); + }); + } + + editTextContentDialog(index: number, content: ContentText) { + const dialogRef = this.dialog.open(ContentTextCreatorComponent, { + width: '800px' + }); + dialogRef.componentInstance.editDialogMode = true; + dialogRef.componentInstance.content = content; + dialogRef.afterClosed() + .subscribe(result => { + this.updateContentChanges(index, result); + }); + } + + updateContentChanges(index: number, action: string) { + if (!action) { + this.contents[index] = this.contentBackup; + } else { + if (action.valueOf() === 'delete') { + this.notificationService.show('Content "' + this.contents[index].subject + '" deleted.'); + this.contentService.deleteContent(this.contents[index].contentId); + this.contents.splice(index, 1); + } + if (action.valueOf() === 'edit') { + this.notificationService.show('Content "' + this.contents[index].subject + '" updated.'); + this.contentService.updateContent(this.contents[index]); + } + if (action.valueOf() === 'abort') { + this.contents[index] = this.contentBackup; + } + } + } } diff --git a/src/app/components/fragments/content-text-creator/content-text-creator.component.html b/src/app/components/fragments/content-text-creator/content-text-creator.component.html index c1e54b5fc10ef64770e4a33c14e6d698a323607c..a21dd08201a5677fcd3a61d6058ad441fbc2bd80 100644 --- a/src/app/components/fragments/content-text-creator/content-text-creator.component.html +++ b/src/app/components/fragments/content-text-creator/content-text-creator.component.html @@ -5,5 +5,12 @@ <mat-form-field class="input-block"> <textarea matInput #body [(ngModel)]="content.body" placeholder="Body" name="body"></textarea> </mat-form-field> - <button mat-raised-button type="submit" color="primary">Submit</button> + <div *ngIf="!editDialogMode"> + <button mat-raised-button type="submit" color="primary">Submit</button> + </div> + <div *ngIf="editDialogMode"> + <button mat-raised-button (click)="editDialogClose($event,'edit')" color="primary">Update</button> + <button mat-raised-button (click)="editDialogClose($event,'abort')" color="primary">Abort</button> + <button mat-raised-button (click)="openDeletionContentDialog($event)" color="warn">Delete</button> + </div> </form> diff --git a/src/app/components/fragments/content-text-creator/content-text-creator.component.scss b/src/app/components/fragments/content-text-creator/content-text-creator.component.scss index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..687e7d96c8ae5f81676b91d911abedb0790ac2f9 100644 --- a/src/app/components/fragments/content-text-creator/content-text-creator.component.scss +++ b/src/app/components/fragments/content-text-creator/content-text-creator.component.scss @@ -0,0 +1,3 @@ +form > button { + margin: 20px 0; +} diff --git a/src/app/components/fragments/content-text-creator/content-text-creator.component.ts b/src/app/components/fragments/content-text-creator/content-text-creator.component.ts index c7edf502e65a8c4ac30e6275101df075c1f8df41..5608961eb1bc0c1afb448fc5263bb0dd41fef967 100644 --- a/src/app/components/fragments/content-text-creator/content-text-creator.component.ts +++ b/src/app/components/fragments/content-text-creator/content-text-creator.component.ts @@ -1,6 +1,11 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Inject, OnInit } from '@angular/core'; import { ContentText } from '../../../models/content-text'; import { ContentService } from '../../../services/http/content.service'; +import { ActivatedRoute } from '@angular/router'; +import { NotificationService } from '../../../services/util/notification.service'; +import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material'; +import { ContentListComponent } from '../content-list/content-list.component'; +import { ContentDeleteComponent } from '../../dialogs/content-delete/content-delete.component'; @Component({ selector: 'app-content-text-creator', @@ -16,18 +21,61 @@ export class ContentTextCreatorComponent implements OnInit { '', 1); - constructor(private contentService: ContentService) { + editDialogMode = false; + + constructor(private contentService: ContentService, + private notificationService: NotificationService, + private route: ActivatedRoute, + public dialog: MatDialog, + public dialogRef: MatDialogRef<ContentListComponent>, + @Inject(MAT_DIALOG_DATA) public data: any) { } ngOnInit() { + this.route.params.subscribe(params => { + this.content.roomId = params['roomId']; + }); + } + + resetAfterSubmit() { + this.content.subject = ''; + this.content.body = ''; + this.notificationService.show('Content submitted. Ready for creation of new content.'); } submitContent() { - if (this.content.contentId === '1') { - this.contentService.addContent(this.content).subscribe(); - } else { - // ToDo: Implement function in service - // this.contentService.updateContent(this.content).subscribe(); + if (this.content.body.valueOf() === '' || this.content.body.valueOf() === '') { + this.notificationService.show('No empty fields allowed. Please check subject and body.'); + return; } + this.notificationService.show('Content submitted.'); + // ToDo: Check api call + // this.contentService.addContent(this.content); + // For Testing: + // console.log(this.content); + this.resetAfterSubmit(); + } + + editDialogClose($event, action: string) { + $event.preventDefault(); + this.dialogRef.close(action); + } + + onNoClick(): void { + this.dialogRef.close(); + } + + openDeletionContentDialog($event): void { + $event.preventDefault(); + const dialogRef = this.dialog.open(ContentDeleteComponent, { + width: '400px' + }); + dialogRef.componentInstance.content = this.content; + dialogRef.afterClosed() + .subscribe(result => { + if (result === 'delete') { + this.dialogRef.close(result); + } + }); } } diff --git a/src/app/components/fragments/content-text-participant/content-text-participant.component.html b/src/app/components/fragments/content-text-participant/content-text-participant.component.html index 6d3f5a502be39d56e8cbd89c1ed63c2beb776e9c..ca53d5933fb9633d531ba5a3419204292c527012 100644 --- a/src/app/components/fragments/content-text-participant/content-text-participant.component.html +++ b/src/app/components/fragments/content-text-participant/content-text-participant.component.html @@ -1,9 +1,15 @@ -<form (ngSubmit)="submitAnswer(answer.value)"> +<form (ngSubmit)="submitAnswer()"> <h1 class="mat-headline">{{ content.subject }}</h1> <p>{{ content.body }}</p> <mat-divider></mat-divider> <mat-form-field class="input-block"> - <textarea matInput #answer placeholder="Your answer"></textarea> + <textarea matInput [(ngModel)]="textAnswer" name="answer" #answer placeholder="Your answer"></textarea> </mat-form-field> - <button mat-raised-button type="submit" color="primary">Submit</button> + <div fxLayout fxLayoutGap="5px" fxAlign="row"> + <button mat-raised-button type="submit" color="primary">Submit</button> + <button mat-raised-button (click)="abstain($event)" color="primary">Abstain</button> + <mat-chip-list *ngIf="isAnswerSent"> + <mat-chip color="primary" selected="true">Answer sent</mat-chip> + </mat-chip-list> + </div> </form> diff --git a/src/app/components/fragments/content-text-participant/content-text-participant.component.scss b/src/app/components/fragments/content-text-participant/content-text-participant.component.scss index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..615eaa08f34372a38036c3138ca8ba7e5b3bc5fc 100644 --- a/src/app/components/fragments/content-text-participant/content-text-participant.component.scss +++ b/src/app/components/fragments/content-text-participant/content-text-participant.component.scss @@ -0,0 +1,11 @@ +form > div > button { + margin: 20px 0; +} + +form > div > mat-chip-list { + margin: 20px 0; +} + +form > h1 { + margin:20px 0; +} diff --git a/src/app/components/fragments/content-text-participant/content-text-participant.component.ts b/src/app/components/fragments/content-text-participant/content-text-participant.component.ts index fb2b11c90a954686b2c76d79effb23efacd80d7a..c87bf8e50c308fbaed27fb535a9d3f7b988f0143 100644 --- a/src/app/components/fragments/content-text-participant/content-text-participant.component.ts +++ b/src/app/components/fragments/content-text-participant/content-text-participant.component.ts @@ -1,7 +1,8 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { ContentText } from '../../../models/content-text'; import { ContentAnswerService } from '../../../services/http/content-answer.service'; import { AnswerText } from '../../../models/answer-text'; +import { NotificationService } from '../../../services/util/notification.service'; @Component({ selector: 'app-content-text-participant', @@ -9,29 +10,51 @@ import { AnswerText } from '../../../models/answer-text'; styleUrls: ['./content-text-participant.component.scss'] }) export class ContentTextParticipantComponent implements OnInit { - content: ContentText = new ContentText('1', + @Input() content: ContentText; + + dummyContent: ContentText = new ContentText('1', '1', '1', 'Text Content 1', 'This is the body of Text Content 1', 1); - constructor(private answerService: ContentAnswerService) { + textAnswer = ''; + isAnswerSent = false; + + constructor(private answerService: ContentAnswerService, + private notificationService: NotificationService) { } ngOnInit() { } - submitAnswer(answer: string) { +// submitAnswer(answer: string) { + submitAnswer() { + if (this.textAnswer.trim().valueOf() === '') { + this.notificationService.show('No empty answer allowed.'); + this.textAnswer = ''; + return; + } + this.isAnswerSent = true; + /* + // ToDo: Check correct api call this.answerService.addAnswerText({ id: '0', - revision: this.content.revision, + revision: '0', contentId: this.content.contentId, round: this.content.round, subject: this.content.subject, - body: answer, + body: this.textAnswer, read: 'false', creationTimestamp: new Date() - } as AnswerText).subscribe(); + } as AnswerText).subscribe(result => { + // TODO: Set isAnswerSent + }); */ + } + + abstain($event) { + $event.preventDefault(); + console.log('abstain'); } } diff --git a/src/app/components/fragments/content-yes-no-creator/content-yes-no-creator.component.html b/src/app/components/fragments/content-yes-no-creator/content-yes-no-creator.component.html new file mode 100644 index 0000000000000000000000000000000000000000..789031a51f210163b31b909b8c4ab370429b2a3c --- /dev/null +++ b/src/app/components/fragments/content-yes-no-creator/content-yes-no-creator.component.html @@ -0,0 +1,34 @@ +<form (ngSubmit)="submitContent()"> + <mat-form-field class="input-block"> + <input matInput #subject [(ngModel)]="content.subject" placeholder="Subject" name="subject"> + </mat-form-field> + <mat-form-field class="input-block"> + <textarea matInput #body [(ngModel)]="content.body" placeholder="Body" name="body"></textarea> + </mat-form-field> + <mat-divider></mat-divider> + + <mat-table #table [dataSource]="displayAnswers"> + + <ng-container matColumnDef="label"> + <mat-header-cell *matHeaderCellDef>Answer</mat-header-cell> + <mat-cell *matCellDef="let answer"> + <!-- ToDo: Check ngModel --> + <mat-checkbox (click)="setCorrect($event ,answer.answerOption.label)" color="primary" [(ngModel)]="answer.correct" [checked]="answer.correct" + name="{{ answer.answerOption.label }}">{{ answer.answerOption.label }} + </mat-checkbox> + </mat-cell> + </ng-container> + + <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> + <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row> + </mat-table> + <mat-divider></mat-divider> + <div *ngIf="!editDialogMode"> + <button mat-raised-button type="submit" color="primary">Submit</button> + </div> + <div *ngIf="editDialogMode"> + <button mat-raised-button (click)="editDialogClose($event,'edit')" color="primary">Update</button> + <button mat-raised-button (click)="editDialogClose($event,'abort')" color="primary">Abort</button> + <button mat-raised-button (click)="openDeletionContentDialog($event)" color="warn">Delete</button> + </div> +</form> diff --git a/src/app/components/fragments/content-yes-no-creator/content-yes-no-creator.component.scss b/src/app/components/fragments/content-yes-no-creator/content-yes-no-creator.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..687e7d96c8ae5f81676b91d911abedb0790ac2f9 --- /dev/null +++ b/src/app/components/fragments/content-yes-no-creator/content-yes-no-creator.component.scss @@ -0,0 +1,3 @@ +form > button { + margin: 20px 0; +} diff --git a/src/app/components/fragments/content-yes-no-creator/content-yes-no-creator.component.spec.ts b/src/app/components/fragments/content-yes-no-creator/content-yes-no-creator.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..80d64d567dcbfb19cb9d5d2cdc65de2a48735c80 --- /dev/null +++ b/src/app/components/fragments/content-yes-no-creator/content-yes-no-creator.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ContentYesNoCreatorComponent } from './content-yes-no-creator.component'; + +describe('ContentYesNoCreatorComponent', () => { + let component: ContentYesNoCreatorComponent; + let fixture: ComponentFixture<ContentYesNoCreatorComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ContentYesNoCreatorComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ContentYesNoCreatorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/fragments/content-yes-no-creator/content-yes-no-creator.component.ts b/src/app/components/fragments/content-yes-no-creator/content-yes-no-creator.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..75857efdc9b6306065bb2208c25a34f6513695ca --- /dev/null +++ b/src/app/components/fragments/content-yes-no-creator/content-yes-no-creator.component.ts @@ -0,0 +1,129 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { ContentChoice } from '../../../models/content-choice'; +import { DisplayAnswer } from '../content-choice-creator/content-choice-creator.component'; +import { AnswerOption } from '../../../models/answer-option'; +import { NotificationService } from '../../../services/util/notification.service'; +import { ContentType } from '../../../models/content-type.enum'; +import { ContentService } from '../../../services/http/content.service'; +import { ActivatedRoute } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material'; +import { ContentListComponent } from '../content-list/content-list.component'; +import { ContentDeleteComponent } from '../../dialogs/content-delete/content-delete.component'; + +@Component({ + selector: 'app-content-yes-no-creator', + templateUrl: './content-yes-no-creator.component.html', + styleUrls: ['./content-yes-no-creator.component.scss'] +}) +export class ContentYesNoCreatorComponent implements OnInit { + answerLabels = [ + 'yes', + 'no' + ]; + content: ContentChoice = new ContentChoice('0', + '1', + '', + '', + '', + 1, + [], + [], + false, + ContentType.BINARY + ); + + displayedColumns = ['label']; + + displayAnswers: DisplayAnswer[] = []; + newAnswerOptionPoints = ''; + + editDialogMode = false; + + constructor(private contentService: ContentService, + private route: ActivatedRoute, + private notificationService: NotificationService, + public dialog: MatDialog, + public dialogRef: MatDialogRef<ContentListComponent>, + @Inject(MAT_DIALOG_DATA) public data: any) { + } + + ngOnInit() { + this.route.params.subscribe(params => { + this.content.roomId = params['roomId']; + }); + for (let i = 0; i < this.answerLabels.length; i++) { + this.content.options.push(new AnswerOption(this.answerLabels[i], this.newAnswerOptionPoints)); + } + this.fillCorrectAnswers(); + } + + fillCorrectAnswers() { + this.displayAnswers = []; + for (let i = 0; i < this.content.options.length; i++) { + this.displayAnswers.push(new DisplayAnswer(this.content.options[i], this.content.correctOptionIndexes.includes(i))); + } + } + + setCorrect($event, label: string) { + $event.preventDefault(); + if (label === 'yes') { + this.content.correctOptionIndexes = [0]; + } + if (label === 'no') { + this.content.correctOptionIndexes = [1]; + } + this.fillCorrectAnswers(); + } + + checkAllowedContent(): boolean { + return (this.content.correctOptionIndexes.length === 1); + } + + resetAfterSubmit() { + this.content.subject = ''; + this.content.body = ''; + this.content.correctOptionIndexes = []; + this.fillCorrectAnswers(); + this.notificationService.show('Content submitted. Ready for creation of new content.'); + } + + submitContent(): void { + if (this.content.body.valueOf() === '' || this.content.body.valueOf() === '') { + this.notificationService.show('No empty fields allowed. Please check subject and body.'); + return; + } + if (!this.checkAllowedContent()) { + this.notificationService.show('Select 1 true answer.'); + return; + } + this.notificationService.show('Content sumbitted.'); + // ToDo: Check api call + // this.contentService.addContent(this.content); + // For Testing: + // console.log(this.content); + this.resetAfterSubmit(); + } + + editDialogClose($event, action: string) { + $event.preventDefault(); + this.dialogRef.close(action); + } + + onNoClick(): void { + this.dialogRef.close(); + } + + openDeletionContentDialog($event): void { + $event.preventDefault(); + const dialogRef = this.dialog.open(ContentDeleteComponent, { + width: '400px' + }); + dialogRef.componentInstance.content = this.content; + dialogRef.afterClosed() + .subscribe(result => { + if (result === 'delete') { + this.dialogRef.close(result); + } + }); + } +} diff --git a/src/app/components/pages/content-carousel-page/content-carousel-page.component.html b/src/app/components/pages/content-carousel-page/content-carousel-page.component.html index c256a384463275428b63acc6dacdfbc639fe2f09..563abf8fe34028d34b5d3a5f00704f59c9bc69bf 100644 --- a/src/app/components/pages/content-carousel-page/content-carousel-page.component.html +++ b/src/app/components/pages/content-carousel-page/content-carousel-page.component.html @@ -1,21 +1,16 @@ <div fxLayout="column" fxLayoutAlign="start" fxLayoutGap="20px" fxFill> <div fxLayout="row" fxLayoutAlign="center"> <mat-tab-group> - <!-- - <mat-tab *ngFor="let content of contents"> - <app-content-choice-participant *ngIf="content.format === ContentType.CHOICE"></app-content-choice-participant> - <app-content-text-participant *ngIf="content.format === ContentType.TEXT"></app-content-text-participant> - </mat-tab> - --> - <mat-tab label="Text"> - <div class="tab-container"> - <app-content-text-participant></app-content-text-participant> - </div> - </mat-tab> - <mat-tab label="Choice"> - <div class="tab-container"> - <app-content-choice-participant></app-content-choice-participant> - </div> + + <mat-tab *ngFor="let content of contents" label="{{content.subject}}"> + <app-content-choice-participant [content]="content" + *ngIf="content.format === ContentType.CHOICE"></app-content-choice-participant> + <app-content-choice-participant [content]="content" + *ngIf="content.format === ContentType.SCALE"></app-content-choice-participant> + <app-content-choice-participant [content]="content" + *ngIf="content.format === ContentType.BINARY"></app-content-choice-participant> + <app-content-text-participant [content]="content" + *ngIf="content.format === ContentType.TEXT"></app-content-text-participant> </mat-tab> </mat-tab-group> </div> diff --git a/src/app/components/pages/content-carousel-page/content-carousel-page.component.ts b/src/app/components/pages/content-carousel-page/content-carousel-page.component.ts index 3fa5301e9db8a3d256983dd60fe78da30c43f07f..c5a4637829638ea60522a8bde767c39f11534f9a 100644 --- a/src/app/components/pages/content-carousel-page/content-carousel-page.component.ts +++ b/src/app/components/pages/content-carousel-page/content-carousel-page.component.ts @@ -1,6 +1,10 @@ import { Component, OnInit } from '@angular/core'; -import { Content } from '../../../models/content'; import { ContentType } from '../../../models/content-type.enum'; +import { AnswerOption } from '../../../models/answer-option'; +import { ContentChoice } from '../../../models/content-choice'; +import { ContentText } from '../../../models/content-text'; +import { ContentService } from '../../../services/http/content.service'; +import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-content-carousel-page', @@ -8,13 +12,63 @@ import { ContentType } from '../../../models/content-type.enum'; styleUrls: ['./content-carousel-page.component.scss'] }) export class ContentCarouselPageComponent implements OnInit { - ContentType: ContentType; + ContentType: typeof ContentType = ContentType; - contents: Content[]; +// contents: Content[]; + contents = [ + new ContentChoice('0', + '1', + 'roomId1', + 'MultipleChoice Subject', + 'MultipleChoice Body', + 1, + [new AnswerOption('yes', ''), new AnswerOption('no', '')], + [], + true, + ContentType.CHOICE), + new ContentChoice('0', + '1', + 'roomId2', + 'SingleChoice Subject', + 'SingleChoice Body', + 1, + [new AnswerOption('may', ''), new AnswerOption('not', '')], + [], + false, + ContentType.BINARY), + new ContentText('1', + '1', + 'roomId3', + 'TextContent Subject', + 'TextContent Body', + 1), + new ContentChoice('0', + '1', + 'roomId4', + 'LikertContent Subject', + 'LikertContent Body', + 1, + [ + new AnswerOption('Strongly agree', '0'), + new AnswerOption('Agree', '0'), + new AnswerOption('Neither agree nor disagree', '0'), + new AnswerOption('Disagree', '0'), + new AnswerOption('Strongly disagree', '0')], + [], + false, + ContentType.SCALE) + ]; - constructor() { + constructor(private contentService: ContentService, + private route: ActivatedRoute) { } ngOnInit() { + this.route.params.subscribe(params => { + // ToDo: Check api call + /* this.contentService.getContents(params['roomId']).subscribe(result => { + this.contents = result; + }); */ + }); } } diff --git a/src/app/components/pages/content-create-page/content-create-page.component.html b/src/app/components/pages/content-create-page/content-create-page.component.html index a6cf4afcde44f4fd2df514da2269471ccff1c470..ff136f4272c48bbaf8d27cb0c910ce76bb1dd071 100644 --- a/src/app/components/pages/content-create-page/content-create-page.component.html +++ b/src/app/components/pages/content-create-page/content-create-page.component.html @@ -6,11 +6,21 @@ <app-content-text-creator></app-content-text-creator> </div> </mat-tab> - <mat-tab label="Choice"> + <mat-tab label="Single / Multiple Choice"> <div class="tab-container"> <app-content-choice-creator></app-content-choice-creator> </div> </mat-tab> + <mat-tab label="Likert"> + <div class="tab-container"> + <app-content-likert-creator></app-content-likert-creator> + </div> + </mat-tab> + <mat-tab label="Yes / No"> + <div class="tab-container"> + <app-content-yes-no-creator></app-content-yes-no-creator> + </div> + </mat-tab> </mat-tab-group> </div> </div> diff --git a/src/app/models/content-choice.ts b/src/app/models/content-choice.ts index 25cb9927d2c6e058c1b0fa94b7eebd66d5f372a5..6bca40dbb5dce9934821ad755d9a94d0e3d5e60f 100644 --- a/src/app/models/content-choice.ts +++ b/src/app/models/content-choice.ts @@ -15,14 +15,15 @@ export class ContentChoice extends Content { round: number, options: AnswerOption[], correctOptionIndexes: number[], - multiple: boolean) { + multiple: boolean, + format: ContentType) { super(contentId, revision, roomId, subject, body, round, - ContentType.CHOICE, + format, new Map()); this.options = options; this.correctOptionIndexes = correctOptionIndexes; diff --git a/src/app/services/http/content-answer.service.ts b/src/app/services/http/content-answer.service.ts index 67f55778b4ccdf0ebb41efa1ec5706b2b8e58285..1d024e95a0fe08720df4fd8b8f99f37bd33fcc23 100644 --- a/src/app/services/http/content-answer.service.ts +++ b/src/app/services/http/content-answer.service.ts @@ -4,6 +4,8 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import { catchError, tap } from 'rxjs/operators'; import { BaseHttpService } from './base-http.service'; +import { AnswerChoice } from '../../models/answer-choice'; + const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) }; @@ -37,6 +39,14 @@ export class ContentAnswerService extends BaseHttpService { ); } + addAnswerChoice(answerChoice: AnswerChoice): Observable<AnswerChoice> { + // Dummy method copied from addAnswerText. + // Todo: Implement correct method with api + return this.http.post<AnswerChoice>(this.textAnswerUrl, answerChoice, httpOptions).pipe( + catchError(this.handleError<AnswerChoice>('addAnswerText')) + ); + } + getAnswerText(id: string): Observable<AnswerText> { const url = `${this.textAnswerUrl}/${id}`; return this.http.get<AnswerText>(url).pipe( diff --git a/src/app/services/http/content.service.ts b/src/app/services/http/content.service.ts index f41febbc1cf34ecb7aaba57ea8db37ea1dc90b7c..2e29b4985e3c40b79afdea27189eac9f5a9eb01a 100644 --- a/src/app/services/http/content.service.ts +++ b/src/app/services/http/content.service.ts @@ -4,6 +4,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import { catchError, tap } from 'rxjs/operators'; import { BaseHttpService } from './base-http.service'; + const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) }; @@ -35,4 +36,14 @@ export class ContentService extends BaseHttpService { catchError(this.handleError<Content>(`getContent id=${contentId}`)) ); } + + updateContent(content: Content) { + // ToDo: implement service, api call + console.log('Content updated.'); + } + + deleteContent(contentId: string) { + // ToDo: implement service, api call + console.log('Content deleted.'); + } }