diff --git a/src/app/text-answer.ts b/src/app/answer-text.ts similarity index 73% rename from src/app/text-answer.ts rename to src/app/answer-text.ts index a4232a41a593e2e32c04d2e9972ff17bafb69e84..b2014e2d8dcf7bcc12a50cdb48563dcef2766ff9 100644 --- a/src/app/text-answer.ts +++ b/src/app/answer-text.ts @@ -1,7 +1,7 @@ -export class TextAnswer { +export class AnswerText { id: string; revision: string; - contendId: string; + contentId: string; round: number; subject: string; body: string; diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 43f6b8ba07102c4c1b788b6c63a11e4f7748c5c2..1364c4b066fb22ad795087d85b0652a6f4a4ea2f 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -10,6 +10,9 @@ import { UserRole } from './user-roles.enum'; import { ParticipantRoomComponent } from './participant-room/participant-room.component'; import { CreatorRoomComponent } from './creator-room/creator-room.component'; import { CommentListComponent } from './comment-list/comment-list.component'; +import { ContentListComponent } from './content-list/content-list.component'; +import { ContentCreationComponent } from './content-creation/content-creation.component'; +import { ContentDetailComponent } from './content-detail/content-detail.component'; const routes: Routes = [ { path: '', redirectTo: '/home', pathMatch: 'full' }, @@ -42,6 +45,24 @@ const routes: Routes = [ canActivate: [AuthenticationGuard], data: { roles: [UserRole.CREATOR] } }, + { + path: 'creator/room/:roomId/content-creation', + component: ContentCreationComponent, + canActivate: [AuthenticationGuard], + data: { roles: [UserRole.CREATOR] } + }, + { + path: 'creator/room/:roomId/content-list', + component: ContentListComponent, + canActivate: [AuthenticationGuard], + data: { roles: [UserRole.CREATOR] } + }, + { + path: 'creator/room/:roomId/:id', + component: ContentDetailComponent, + canActivate: [AuthenticationGuard], + data: { roles: [UserRole.CREATOR] } + }, { path: 'participant/room/:roomId/create-comment', component: CreateCommentComponent, canActivate: [AuthenticationGuard], diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 50d647593f9dd73bf9a6d71b49ad9ced8349c3b6..a31d35189dbef5ab02ab88b2641862a720b904f6 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -67,6 +67,11 @@ import { ParticipantHomeScreenComponent } from './participant-home-screen/partic import { ParticipantRoomComponent } from './participant-room/participant-room.component'; import { DataStoreService } from './data-store.service'; import { CreatorRoomComponent } from './creator-room/creator-room.component'; +import { ContentDetailComponent } from './content-detail/content-detail.component'; +import { ContentListComponent } from './content-list/content-list.component'; +import { ContentService } from './content.service'; +import { ContentAnswersListComponent } from './content-answers-list/content-answers-list.component'; +import { ContentAnswerService } from './content-answer.service'; @NgModule({ declarations: [ @@ -89,7 +94,10 @@ import { CreatorRoomComponent } from './creator-room/creator-room.component'; CommentListComponent, ContentAnswersComponent, ParticipantRoomComponent, - CreatorRoomComponent + CreatorRoomComponent, + ContentDetailComponent, + ContentListComponent, + ContentAnswersListComponent ], entryComponents: [ RegisterComponent, @@ -145,7 +153,9 @@ import { CreatorRoomComponent } from './creator-room/creator-room.component'; AuthenticationGuard, DataStoreService, RoomService, - CommentService + CommentService, + ContentService, + ContentAnswerService ], bootstrap: [AppComponent] }) diff --git a/src/app/content-answer.service.spec.ts b/src/app/content-answer.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..74b3356516c1746f7bdc1478bf1de87c0d7e35b8 --- /dev/null +++ b/src/app/content-answer.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { ContentAnswerService } from './content-answer.service'; + +describe('ContentAnswerService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ContentAnswerService] + }); + }); + + it('should be created', inject([ContentAnswerService], (service: ContentAnswerService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/content-answer.service.ts b/src/app/content-answer.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..e414d1d2ac4bcfa1eec16cea94ad9624f84f5eb6 --- /dev/null +++ b/src/app/content-answer.service.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@angular/core'; +import { AnswerText } from './answer-text'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Observable } from 'rxjs/Observable'; +import { catchError, tap } from 'rxjs/operators'; +import { ErrorHandlingService } from './error-handling.service'; +const httpOptions = { + headers: new HttpHeaders({ 'Content-Type': 'application/json' }) +}; + +@Injectable() +export class ContentAnswerService extends ErrorHandlingService { + private answerUrl = 'api/answerTexts'; + + constructor(private http: HttpClient) { + super(); + } + + getAnswerTexts(): Observable<AnswerText[]> { + return this.http.get<AnswerText[]>(this.answerUrl).pipe( + catchError(this.handleError('getAnswerTexts', [])) + ); + } + + addAnswerText(answerText: AnswerText): Observable<AnswerText> { + return this.http.post<AnswerText>(this.answerUrl, answerText, httpOptions).pipe( + catchError(this.handleError<AnswerText>('addAnswerText')) + ); + } + + getAnswerText(id: string): Observable<AnswerText> { + const url = `${this.answerUrl}/${id}`; + return this.http.get<AnswerText>(url).pipe( + catchError(this.handleError<AnswerText>(`getAnswerText id=${id}`)) + ); + } +} diff --git a/src/app/content-answers-list/content-answers-list.component.html b/src/app/content-answers-list/content-answers-list.component.html new file mode 100644 index 0000000000000000000000000000000000000000..d1205e828c48078f9500b29a95674ca9d06dd6e0 --- /dev/null +++ b/src/app/content-answers-list/content-answers-list.component.html @@ -0,0 +1,8 @@ +<mat-list> + <h3 mat-subheader>Answers</h3> + <mat-list-item *ngFor="let textAnswer of textAnswers"> + <button mat-button routerLink="{{textAnswer.id}}"> + {{textAnswer.body}} + </button> + </mat-list-item> +</mat-list> \ No newline at end of file diff --git a/src/app/content-answers-list/content-answers-list.component.scss b/src/app/content-answers-list/content-answers-list.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/app/content-answers-list/content-answers-list.component.spec.ts b/src/app/content-answers-list/content-answers-list.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..feaa00d040d470accd1ed236a996c8e3551266bd --- /dev/null +++ b/src/app/content-answers-list/content-answers-list.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ContentAnswersListComponent } from './content-answers-list.component'; + +describe('ContentAnswersListComponent', () => { + let component: ContentAnswersListComponent; + let fixture: ComponentFixture<ContentAnswersListComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ContentAnswersListComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ContentAnswersListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/content-answers-list/content-answers-list.component.ts b/src/app/content-answers-list/content-answers-list.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..c9524b968505a465e2b0b841cf3f7dfe580a031f --- /dev/null +++ b/src/app/content-answers-list/content-answers-list.component.ts @@ -0,0 +1,25 @@ +import { Component, OnInit } from '@angular/core'; +import { ContentAnswerService } from '../content-answer.service'; +import { AnswerText } from '../answer-text'; + +@Component({ + selector: 'app-content-answers-list', + templateUrl: './content-answers-list.component.html', + styleUrls: ['./content-answers-list.component.scss'] +}) +export class ContentAnswersListComponent implements OnInit { + textAnswers: AnswerText[]; + + constructor(private contentAnswerService: ContentAnswerService) { } + + ngOnInit() { + this.getAnswerTexts(); + } + + getAnswerTexts(): void { + this.contentAnswerService.getAnswerTexts(). + subscribe(textAnswers => { + this.textAnswers = textAnswers; + }); + } +} diff --git a/src/app/content-creation/content-creation.component.html b/src/app/content-creation/content-creation.component.html index 01347286e16cb3d0510cac053783ed6af165efdd..44da99144a7ceb9f746cec1f13e77dffd80ff027 100644 --- a/src/app/content-creation/content-creation.component.html +++ b/src/app/content-creation/content-creation.component.html @@ -28,9 +28,10 @@ </mat-card> <button mat-raised-button="Preview">Preview</button> -<!-- TODO: Save answers to array in a class. Show answers in a matlist --> + <!-- TODO: Save answers to array in a class. Show answers in a matlist --> <div> <mat-slide-toggle>Allow absention</mat-slide-toggle> <mat-slide-toggle>Show hints</mat-slide-toggle> </div> + <!-- Let this view open in a mat dialog --> diff --git a/src/app/content-creation/content-creation.component.scss b/src/app/content-creation/content-creation.component.scss index b8fb3a62658b7b3f63345c8950c8cb8057964750..ed600034848f739d3824f93fe847dbe25f91693d 100644 --- a/src/app/content-creation/content-creation.component.scss +++ b/src/app/content-creation/content-creation.component.scss @@ -1,4 +1,4 @@ .content-card { max-width: 400px; - margin-bottom: 5%; + margin: 10px; } diff --git a/src/app/content-detail/content-detail.component.html b/src/app/content-detail/content-detail.component.html new file mode 100644 index 0000000000000000000000000000000000000000..404cab88379b788d3324fb00252102317dca68d7 --- /dev/null +++ b/src/app/content-detail/content-detail.component.html @@ -0,0 +1,34 @@ +<div fxLayout="column" fxLayoutAlign="start" fxLayoutGap="20px" fxFill> + <div *ngIf="content" fxLayout="row" fxLayoutAlign="center"> + <mat-card> + <mat-card-header> + <mat-card-title> + <h3 class="subheading-2">{{content.subject}}</h3> + </mat-card-title> + <mat-card-subtitle>ID: {{ content.id }} + <br> Round: {{ content.round }}</mat-card-subtitle> + </mat-card-header> + <mat-divider></mat-divider> + <mat-card-content> + <p> + {{ content.body }} + </p> + <mat-divider></mat-divider> + <div> + <app-content-answers-list></app-content-answers-list> + </div> + </mat-card-content> + <mat-divider></mat-divider> + <!-- answes list here --> + <mat-card-actions> + <button mat-button color="primary" matTooltip="Create new content" routerLink="/creator/room/{{content.id}}/content-creation"> + Create answer + </button> + <button mat-button color="warn" matTooltip="Delete selected answer"> + <!-- on click, add a 'x' on the right end of each answer to make it deletable --> + Delete answer + </button> + </mat-card-actions> + </mat-card> + </div> +</div> diff --git a/src/app/content-detail/content-detail.component.scss b/src/app/content-detail/content-detail.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..78348d75ebc905c70ce1c8c0e6b0596305491911 --- /dev/null +++ b/src/app/content-detail/content-detail.component.scss @@ -0,0 +1,8 @@ +mat-card { + max-width: 800px; +} + +mat-card-content > :first-child { + margin-top: 16px; +} + \ No newline at end of file diff --git a/src/app/content-detail/content-detail.component.spec.ts b/src/app/content-detail/content-detail.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..00c63bd7d7ddfeacfa0920a868069c46f44186f8 --- /dev/null +++ b/src/app/content-detail/content-detail.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ContentDetailComponent } from './content-detail.component'; + +describe('ContentDetailComponent', () => { + let component: ContentDetailComponent; + let fixture: ComponentFixture<ContentDetailComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ContentDetailComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ContentDetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/content-detail/content-detail.component.ts b/src/app/content-detail/content-detail.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..6787bcc6d6e8794926fd2a8c29de65e214f418d2 --- /dev/null +++ b/src/app/content-detail/content-detail.component.ts @@ -0,0 +1,29 @@ +import { Component, OnInit } from '@angular/core'; +import { Content } from '../content'; +import { ContentService } from '../content.service'; +import { ActivatedRoute } from '@angular/router'; + +@Component({ + selector: 'app-content-detail', + templateUrl: './content-detail.component.html', + styleUrls: ['./content-detail.component.scss'] +}) +export class ContentDetailComponent implements OnInit { + content: Content = null; + + constructor( + private contentService: ContentService, + private route: ActivatedRoute + ) {} + + ngOnInit() { + this.route.params.subscribe(params => { + this.getContent(params['id']); + }); + } + + getContent(id: string): void { + this.contentService.getContent(id) + .subscribe(content => this.content = content); + } +} diff --git a/src/app/content-list/content-list.component.html b/src/app/content-list/content-list.component.html new file mode 100644 index 0000000000000000000000000000000000000000..4bca2af658745f293e2c9df890fdd22b381b1d4f --- /dev/null +++ b/src/app/content-list/content-list.component.html @@ -0,0 +1,7 @@ +<mat-list> + <mat-list-item *ngFor="let content of contents"> + <button mat-button routerLink="{{content.id}}"> + Content {{content.id}}: {{content.subject}} + </button> + </mat-list-item> +</mat-list> diff --git a/src/app/content-list/content-list.component.scss b/src/app/content-list/content-list.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/app/content-list/content-list.component.spec.ts b/src/app/content-list/content-list.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..b8d2e87b6ef5ee8443e45c30749ba5211b0fc32e --- /dev/null +++ b/src/app/content-list/content-list.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ContentListComponent } from './content-list.component'; + +describe('ContentListComponent', () => { + let component: ContentListComponent; + let fixture: ComponentFixture<ContentListComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ContentListComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ContentListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/content-list/content-list.component.ts b/src/app/content-list/content-list.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..4fddce67c735ec8276ab9128b08ef09b26bcc076 --- /dev/null +++ b/src/app/content-list/content-list.component.ts @@ -0,0 +1,25 @@ +import { Component, OnInit } from '@angular/core'; +import { ContentService } from '../content.service'; +import { Content } from '../content'; + +@Component({ + selector: 'app-content-list', + templateUrl: './content-list.component.html', + styleUrls: ['./content-list.component.scss'] +}) +export class ContentListComponent implements OnInit { + contents: Content[]; + + constructor(private contentService: ContentService) { } + + ngOnInit() { + this.getContents(); + } + + getContents(): void { + this.contentService.getContents() + .subscribe(contents => { + this.contents = contents; + }); + } +} diff --git a/src/app/content.service.spec.ts b/src/app/content.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..4ba03d0804f87f8ff930553201fd0718ddd050b2 --- /dev/null +++ b/src/app/content.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { ContentService } from './content.service'; + +describe('ContentService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ContentService] + }); + }); + + it('should be created', inject([ContentService], (service: ContentService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/content.service.ts b/src/app/content.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..4907191b21e8d13fc9119ced2d0c2f3b3d1ed45e --- /dev/null +++ b/src/app/content.service.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@angular/core'; +import { Content } from './content'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Observable } from 'rxjs/Observable'; +import { catchError, tap } from 'rxjs/operators'; +import { ErrorHandlingService } from './error-handling.service'; +const httpOptions = { + headers: new HttpHeaders({ 'Content-Type': 'application/json' }) +}; + +@Injectable() +export class ContentService extends ErrorHandlingService { + private contentUrl = 'api/contents'; + + constructor(private http: HttpClient) { + super(); + } + + getContents(): Observable<Content[]> { + return this.http.get<Content[]>(this.contentUrl).pipe( + catchError(this.handleError('getContents', [])) + ); + } + + addContent(content: Content): Observable<Content> { + return this.http.post<Content>(this.contentUrl, content, httpOptions).pipe( + catchError(this.handleError<Content>('addContent')) + ); + } + + getContent(id: string): Observable<Content> { + const url = `${this.contentUrl}/${id}`; + return this.http.get<Content>(url).pipe( + catchError(this.handleError<Content>(`getContent id=${id}`)) + ); + } +} diff --git a/src/app/content.ts b/src/app/content.ts index 4b75ceb17fad81abac090104d3a8ee36ca935f0d..f8069e8117c8840f9fe5a0ac8b7fc9363f5b3b9e 100644 --- a/src/app/content.ts +++ b/src/app/content.ts @@ -1,4 +1,4 @@ -enum Format { +export enum Format { CHOICE, BINARY, SCALE, @@ -17,4 +17,3 @@ export class Content { format: Format; formatAttributes: Map<string, string>; } - diff --git a/src/app/creator-room/creator-room.component.html b/src/app/creator-room/creator-room.component.html index 9c82c86740615d3b1ee9023cb986ac950b36cfc6..0cc2f6c50e540198f048ec96935cf7b3daab4ed6 100644 --- a/src/app/creator-room/creator-room.component.html +++ b/src/app/creator-room/creator-room.component.html @@ -17,14 +17,15 @@ {{ room.description }} </p> </mat-card-content> + <mat-divider></mat-divider> + + <app-content-list></app-content-list> + <mat-divider></mat-divider> <mat-card-actions> - <button mat-button color="primary" matTooltip="Create new content"> + <button mat-button color="primary" matTooltip="Create new content" routerLink="/creator/room/{{room.id}}/content-creation"> Create content </button> - <button mat-button color="primary" matTooltip="See contents"> - Contents - </button> <button mat-button color="primary" matTooltip="See room comments" routerLink="/creator/room/{{room.id}}/comments"> Comments diff --git a/src/app/in-memory-data.service.ts b/src/app/in-memory-data.service.ts index c91bb3c246498bd39476c7dfdd2d1d4ff1944382..49a107eacb6d9164fb6db63960061a076ba4a6d1 100644 --- a/src/app/in-memory-data.service.ts +++ b/src/app/in-memory-data.service.ts @@ -1,4 +1,5 @@ import { InMemoryDbService } from 'angular-in-memory-web-api'; +import { Format } from './content'; export class InMemoryDataService implements InMemoryDbService { /** @@ -80,6 +81,60 @@ export class InMemoryDataService implements InMemoryDbService { creationTimestamp: new Date(Date.now()), } ]; - return { rooms, comments }; + + const contents = [ + { + id: '1', + revision: '1', + roomId: '1', + subject: 'Textaufgabe 1', + body: 'testcontent alpha beta', + round: 1, + format: Format.TEXT + }, + { + id: '2', + revision: '2', + roomId: '3', + subject: 'Textaufgabe 2', + body: 'Ein Mann kauft 20 Melonen. Eine Melone wiegt jeweils 5kg. Berechnen Sie das Gesamtgewicht.', + round: 5, + format: Format.TEXT + } + ]; + + const answerTexts = [ + { + id: '1', + revision: '1', + contendId: '1', + round: '1', + subject: 'Textaufgabe 1', + body: 'gamma, delta', + read: 'yes', + creationTimestamp: Date, + }, + { + id: '1', + revision: '1', + contendId: '1', + round: '1', + subject: 'Textaufgabe 1', + body: 'epsilon, phi', + read: 'yes', + creationTimestamp: Date, + }, + { + id: '2', + revision: '2', + contendId: '2', + round: '3', + subject: 'Textaufgabe 2', + body: 'Der Turm ist 20m hoch', + read: 'yes', + creationTimestamp: Date, + } + ]; + return { rooms, comments, contents, answerTexts }; } }