From d0b46d8abdc9c04d433da4daae608fd00feb1a78 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=20K=C3=A4sler?= <tom.kaesler@mni.thm.de>
Date: Wed, 24 Jul 2019 15:14:05 +0200
Subject: [PATCH] Implement new moderators module

Add moderation icon to moderators room page component in preparation for blacklist/whitelist
Implement delete function
---
 src/app/app-routing.module.ts                 |   4 +
 .../moderator/moderator-routing.module.ts     |  24 ++++
 .../components/moderator/moderator.module.ts  |  35 ++++++
 .../room-moderator-page.component.html        |  53 +++++++++
 .../room-moderator-page.component.scss        | 105 ++++++++++++++++++
 .../room-moderator-page.component.ts          |  42 +++++++
 .../participant/participant-routing.module.ts |   6 -
 .../shared/comment/comment.component.html     |   5 +
 .../shared/header/header.component.html       |   6 +-
 .../shared/room-list/room-list.component.ts   |   8 +-
 .../services/http/authentication.service.ts   |   6 +
 src/assets/i18n/moderator/de.json             |  36 ++++++
 src/assets/i18n/moderator/en.json             |  36 ++++++
 13 files changed, 354 insertions(+), 12 deletions(-)
 create mode 100644 src/app/components/moderator/moderator-routing.module.ts
 create mode 100644 src/app/components/moderator/moderator.module.ts
 create mode 100644 src/app/components/moderator/room-moderator-page/room-moderator-page.component.html
 create mode 100644 src/app/components/moderator/room-moderator-page/room-moderator-page.component.scss
 create mode 100644 src/app/components/moderator/room-moderator-page/room-moderator-page.component.ts
 create mode 100644 src/assets/i18n/moderator/de.json
 create mode 100644 src/assets/i18n/moderator/en.json

diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index 889a9ee26..fb9afda9c 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -26,6 +26,10 @@ const routes: Routes = [
     path: 'participant',
     loadChildren: './components/participant/participant.module#ParticipantModule'
   },
+  {
+    path: 'moderator',
+    loadChildren: './components/moderator/moderator.module#ModeratorModule'
+  },
   {
     path: '**',
     component: PageNotFoundComponent
diff --git a/src/app/components/moderator/moderator-routing.module.ts b/src/app/components/moderator/moderator-routing.module.ts
new file mode 100644
index 000000000..21150ea03
--- /dev/null
+++ b/src/app/components/moderator/moderator-routing.module.ts
@@ -0,0 +1,24 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+import { AuthenticationGuard } from '../../guards/authentication.guard';
+import { RoomModeratorPageComponent } from './room-moderator-page/room-moderator-page.component';
+import { CommentPageComponent } from '../shared/comment-page/comment-page.component';
+
+const routes: Routes = [
+  {
+    path: 'room/:roomId',
+    component: RoomModeratorPageComponent,
+    canActivate: [AuthenticationGuard],
+  },
+  {
+    path: 'room/:roomId/comments',
+    component: CommentPageComponent,
+    canActivate: [AuthenticationGuard],
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule]
+})
+export class ModeratorRoutingModule { }
diff --git a/src/app/components/moderator/moderator.module.ts b/src/app/components/moderator/moderator.module.ts
new file mode 100644
index 000000000..4181ca584
--- /dev/null
+++ b/src/app/components/moderator/moderator.module.ts
@@ -0,0 +1,35 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { ModeratorRoutingModule } from './moderator-routing.module';
+import { RoomModeratorPageComponent } from './room-moderator-page/room-moderator-page.component';
+import { EssentialsModule } from '../essentials/essentials.module';
+import { SharedModule } from '../shared/shared.module';
+import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
+import { HttpClient } from '@angular/common/http';
+import { TranslateHttpLoader } from '@ngx-translate/http-loader';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    ModeratorRoutingModule,
+    EssentialsModule,
+    SharedModule,
+    TranslateModule.forChild({
+      loader: {
+        provide: TranslateLoader,
+        useFactory: (HttpLoaderFactory),
+        deps: [HttpClient]
+      },
+      isolate: true
+    })
+  ],
+  declarations: [
+    RoomModeratorPageComponent
+  ]
+})
+export class ModeratorModule {
+}
+
+export function HttpLoaderFactory(http: HttpClient) {
+  return new TranslateHttpLoader(http, '../../assets/i18n/moderator/', '.json');
+}
diff --git a/src/app/components/moderator/room-moderator-page/room-moderator-page.component.html b/src/app/components/moderator/room-moderator-page/room-moderator-page.component.html
new file mode 100644
index 000000000..beb79d0bb
--- /dev/null
+++ b/src/app/components/moderator/room-moderator-page/room-moderator-page.component.html
@@ -0,0 +1,53 @@
+<div fxLayout="column" fxLayoutAlign="center" fxLayoutGap="20px" fxFill>
+    <div fxLayout="row" fxLayoutAlign="center">
+      <mat-progress-spinner *ngIf="isLoading" mode="indeterminate"></mat-progress-spinner>
+      <mat-card *ngIf="room">
+        <div fxLayout="row">
+          <span class="fill-remaining-space"></span>
+          <mat-card-header fxLayoutAlign="center">
+            <mat-card-title fxLayoutAlign="center">
+              <h2>{{ room.name }}</h2>
+            </mat-card-title>
+            <mat-card-subtitle fxLayoutAlign="center">
+              <h3>
+                {{ 'room-page.session-id' | translate}}: {{ room.shortId.slice(0, 4) }} {{  room.shortId.slice(4, 8) }}
+              </h3>
+            </mat-card-subtitle>
+          </mat-card-header>
+          <span class="fill-remaining-space"></span>
+        </div>
+        <mat-divider></mat-divider>
+        <mat-card-content *ngIf="room.description" fxLayoutAlign="center">
+          <h4>{{room.description.trim()}}</h4>
+        </mat-card-content>
+        <mat-grid-list cols="2" rowHeight="2:1">
+            <mat-grid-tile>
+              <button mat-icon-button routerLink="/moderator/room/{{ room.shortId }}/comments">
+                <mat-icon matBadge="{{commentCounter}}" matBadgeColor="primary"
+                          [ngClass]="{'desktop' : deviceType === 'desktop'}">question_answer
+                </mat-icon>
+                <h3>{{ 'room-page.public-stream' | translate}}</h3>         <!-- *ngIf="deviceType === 'desktop'" -->
+              </button>
+            </mat-grid-tile>
+            <mat-grid-tile>
+              <button mat-icon-button routerLink="/moderator/room/{{ room.shortId }}/comments">
+                <mat-icon matBadge="{{commentCounter}}" matBadgeColor="primary"
+                          [ngClass]="{'desktop' : deviceType === 'desktop'}">gavel
+                </mat-icon>
+                <h3>{{ 'room-page.moderating-stream' | translate}}</h3>         <!-- *ngIf="deviceType === 'desktop'" -->
+              </button>
+            </mat-grid-tile>
+          <!--  <mat-grid-tile>
+              <button mat-icon-button routerLink="/participant/room/{{ room.shortId }}/feedback-barometer">
+                <mat-icon [ngClass]="{'desktop' : deviceType === 'desktop'}">thumbs_up_down</mat-icon>
+                <h3 *ngIf="deviceType === 'desktop'">{{ 'room-page.give-feedback' | translate}}</h3>
+              </button>
+            </mat-grid-tile> -->
+        </mat-grid-list>
+  
+        <!-- <app-content-groups *ngIf="room && room.contentGroups" [contentGroups]="room.contentGroups"></app-content-groups> -->
+      </mat-card>
+  
+    </div>
+  </div>
+  
\ No newline at end of file
diff --git a/src/app/components/moderator/room-moderator-page/room-moderator-page.component.scss b/src/app/components/moderator/room-moderator-page/room-moderator-page.component.scss
new file mode 100644
index 000000000..c321c4e16
--- /dev/null
+++ b/src/app/components/moderator/room-moderator-page/room-moderator-page.component.scss
@@ -0,0 +1,105 @@
+@import '../../../../styles';
+
+mat-card {
+  width: 100%;
+  max-width: 800px;
+  min-height: 350px;
+  background-color: var(--surface)!important;
+}
+
+mat-card-content>:first-child {
+  margin-top: 5%;
+}
+
+.mat-icon-button {
+  width: 60%; // 100%
+  height: 75%;
+  margin-bottom: 3%;
+  color: var(--primary)!important;
+}
+
+/*mat-icon {
+  font-size: 60px;
+  height: 60px;
+  width: 60px;
+  line-height: 100%!important;
+}*/
+
+mat-icon {
+  font-size: 80px;
+  height: 80px;
+  width: 80px;
+  line-height: 100%!important;
+}
+
+.desktop {
+  font-size: 70px;
+  height: 70px;
+  width: 70px;
+}
+
+button {
+  width: 30%;
+  transition: all 0.3s;
+
+  &:hover {
+    transform: scale(1.2)
+  }
+}
+
+h2 {
+  font-size: large;
+  color: var(--on-surface);
+}
+
+h3 {
+  font-size: larger;
+  color: var(--on-surface)!important;
+  margin-top: 15px;
+  margin-bottom: 10px;
+}
+
+h4 {
+  font-size: 15px;
+  color: var(--on-surface)!important;
+  padding: 0 1% 0 1%;
+}
+
+mat-card-header {
+  min-height: 80px!important;
+  height: 12%!important;
+}
+
+mat-card-title {
+  height: 40%;
+}
+
+mat-card-subtitle {
+  height: 20%;
+}
+
+mat-expansion-panel {
+  background-color: var(--surface)!important;
+  min-width: 200px;
+  hyphens: auto;
+}
+
+mat-grid-list {
+  margin-bottom: 20px !important;
+}
+
+mat-grid-tile {
+  height: 100%!important;
+  padding: 2%!important;
+}
+
+#settings {
+  width: 10%;
+  max-width: 40px;
+}
+
+#settings-icon {
+  font-size: 35px;
+  height: 35px;
+  width: 35px;
+}
diff --git a/src/app/components/moderator/room-moderator-page/room-moderator-page.component.ts b/src/app/components/moderator/room-moderator-page/room-moderator-page.component.ts
new file mode 100644
index 000000000..0361c3189
--- /dev/null
+++ b/src/app/components/moderator/room-moderator-page/room-moderator-page.component.ts
@@ -0,0 +1,42 @@
+import { Component, OnInit } from '@angular/core';
+import { Room } from '../../../models/room';
+import { RoomPageComponent } from '../../shared/room-page/room-page.component';
+import { Location } from '@angular/common';
+import { RoomService } from '../../../services/http/room.service';
+import { ActivatedRoute } from '@angular/router';
+import { TranslateService } from '@ngx-translate/core';
+import { LanguageService } from '../../../services/util/language.service';
+import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service';
+import { CommentService } from '../../../services/http/comment.service';
+
+@Component({
+  selector: 'app-room-moderator-page',
+  templateUrl: './room-moderator-page.component.html',
+  styleUrls: ['./room-moderator-page.component.scss']
+})
+export class RoomModeratorPageComponent extends RoomPageComponent implements OnInit {
+
+  room: Room;
+  isLoading = true;
+  deviceType = localStorage.getItem('deviceType');
+
+
+  constructor(protected location: Location,
+              protected roomService: RoomService,
+              protected route: ActivatedRoute,
+              private translateService: TranslateService,
+              protected langService: LanguageService,
+              protected wsCommentService: WsCommentServiceService,
+              protected commentService: CommentService) {
+    super(roomService, route, location, wsCommentService, commentService);
+    langService.langEmitter.subscribe(lang => translateService.use(lang));
+  }
+
+  ngOnInit() {
+    window.scroll(0, 0);
+    this.route.params.subscribe(params => {
+      this.initializeRoom(params['roomId']);
+    });
+    this.translateService.use(localStorage.getItem('currentLang'));
+  }
+}
diff --git a/src/app/components/participant/participant-routing.module.ts b/src/app/components/participant/participant-routing.module.ts
index 9c7b4ce2d..95f9263d0 100644
--- a/src/app/components/participant/participant-routing.module.ts
+++ b/src/app/components/participant/participant-routing.module.ts
@@ -13,37 +13,31 @@ const routes: Routes = [
   {
     path: 'room/:roomId',
     component: RoomParticipantPageComponent,
-    canActivate: [AuthenticationGuard],
     data: { roles: [UserRole.PARTICIPANT] }
   },
   {
     path: 'room/:roomId/statistics',
     component: StatisticsPageComponent,
-    canActivate: [AuthenticationGuard],
     data: { roles: [UserRole.PARTICIPANT] }
   },
   {
     path: 'room/:roomId/statistics/:contentId',
     component: StatisticComponent,
-    canActivate: [AuthenticationGuard],
     data: { roles: [UserRole.PARTICIPANT] }
   },
   {
     path: 'room/:roomId/comments',
     component: CommentPageComponent,
-    canActivate: [AuthenticationGuard],
     data: { roles: [UserRole.PARTICIPANT] }
   },
   {
     path: 'room/:roomId/feedback-barometer',
     component: FeedbackBarometerPageComponent,
-    canActivate: [AuthenticationGuard],
     data: { roles: [UserRole.PARTICIPANT] }
   },
   {
     path: 'room/:roomId/:contentGroup',
     component: ParticipantContentCarouselPageComponent,
-    canActivate: [AuthenticationGuard],
     data: { roles: [UserRole.PARTICIPANT] }
   }
 ];
diff --git a/src/app/components/shared/comment/comment.component.html b/src/app/components/shared/comment/comment.component.html
index c553b23ce..172a759e3 100644
--- a/src/app/components/shared/comment/comment.component.html
+++ b/src/app/components/shared/comment/comment.component.html
@@ -26,6 +26,11 @@
                   matTooltip="{{ 'comment-page.mark-read' | translate }}">speaker_notes
         </mat-icon>
       </button>
+      <button mat-icon-button *ngIf="!isStudent" [disabled]="isStudent" (click)="delete(comment)">
+        <mat-icon [ngClass]="{'read-icon': comment.read, 'not-marked' : !comment.read}"
+                  matTooltip="{{ 'comment-page.delete' | translate }}">delete
+        </mat-icon>
+      </button>
     </div>
     <div fxLayout="row">
       <div class="body click" (click)="openPresentDialog(comment)">{{comment.body.trim()}}</div>
diff --git a/src/app/components/shared/header/header.component.html b/src/app/components/shared/header/header.component.html
index 109f4dddd..a59db1e27 100644
--- a/src/app/components/shared/header/header.component.html
+++ b/src/app/components/shared/header/header.component.html
@@ -73,14 +73,10 @@
     </button>
 
     <mat-menu #userMenu="matMenu" [overlapTrigger]="false">
-      <button mat-menu-item *ngIf="user && user.role === 1" routerLink="/creator">
+      <button mat-menu-item *ngIf="user" routerLink="/user">
         <mat-icon class="sessions">work</mat-icon>
         <span>{{'header.my-sessions' | translate}}</span>
       </button>
-      <button mat-menu-item *ngIf="user && user.role === 0" routerLink="/participant">
-        <mat-icon class="sessions">attach_file</mat-icon>
-        <span>{{'header.visited-sessions' | translate}}</span>
-      </button>
       <button mat-menu-item (click)="logout()">
         <mat-icon color="warn">exit_to_app</mat-icon>
         <span>{{ 'header.logout' | translate }}</span>
diff --git a/src/app/components/shared/room-list/room-list.component.ts b/src/app/components/shared/room-list/room-list.component.ts
index 329a3ef22..f04f99a06 100644
--- a/src/app/components/shared/room-list/room-list.component.ts
+++ b/src/app/components/shared/room-list/room-list.component.ts
@@ -54,6 +54,7 @@ export class RoomListComponent implements OnInit {
       if (isOwner) {
         roomWithRole.role = UserRole.CREATOR;
       } else {
+        // TODO: acknowledge the other role option too
         roomWithRole.role = UserRole.PARTICIPANT;
         this.moderatorService.get(room.id).subscribe((moderators: Moderator[]) => {
           for (const m of moderators) {
@@ -69,7 +70,12 @@ export class RoomListComponent implements OnInit {
   }
 
   setCurrentRoom(shortId: string) {
-    localStorage.setItem('shortId', shortId);
+    for (const r of this.roomsWithRole) {
+      if (r.shortId === shortId) {
+        this.authenticationService.assignRole(r.role);
+        localStorage.setItem('shortId', shortId);
+      }
+    }
   }
 
   roleToString(role: UserRole): string {
diff --git a/src/app/services/http/authentication.service.ts b/src/app/services/http/authentication.service.ts
index 64127a9ae..cb5eccd37 100644
--- a/src/app/services/http/authentication.service.ts
+++ b/src/app/services/http/authentication.service.ts
@@ -105,6 +105,12 @@ export class AuthenticationService {
     return this.user.getValue() !== undefined;
   }
 
+  assignRole(role: UserRole): void {
+    const u = this.user.getValue();
+    u.role = role;
+    this.setUser(u);
+  }
+
   getRole(): UserRole {
     return this.isLoggedIn() ? this.user.getValue().role : undefined;
   }
diff --git a/src/assets/i18n/moderator/de.json b/src/assets/i18n/moderator/de.json
new file mode 100644
index 000000000..5de058b30
--- /dev/null
+++ b/src/assets/i18n/moderator/de.json
@@ -0,0 +1,36 @@
+{
+  "room-page": {
+    "session-id": "Session-ID",
+    "public-stream": "Öffentliche Kommentare",
+    "moderating-stream": "Moderation"
+  },
+  "comment-list": {
+    "search": "Suchen",
+    "filter-comments": "Fragen filtern",
+    "sort-comments": "Fragen sortieren",
+    "add-comment": "Stell deine Frage!",
+    "correct": "Bejaht",
+    "favorite": "Hervorgehoben",
+    "read": "Beantwortet",
+    "unread": "Nicht beantwortet",
+    "vote-desc": "Absteigende Votes",
+    "vote-asc": "Aufsteigende Votes",
+    "time": "Zeit"
+  },
+  "comment-page": {
+    "enter-title": "Titel",
+    "enter-comment": "Deine Frage",
+    "send": "Senden",
+    "abort": "Abbrechen",
+    "error-comment": "Bitte gib deine Frage ein.",
+    "error-title": "Bitte gib einen Titel ein.",
+    "error-both-fields": "Bitte fülle alle Felder aus.",
+    "no-comments": "Noch keine Fragen",
+    "mark-correct": "Dozent/in hat die Frage bejaht.",
+    "mark-favorite": "Dozent/in hält die Frage für besonders interessant.",
+    "mark-read": "Dozent/in hat die Frage beantwortet.",
+    "vote-up": "Hochvoten",
+    "vote-down": "Runtervoten",
+    "delete": "Löschen"
+  }
+}
diff --git a/src/assets/i18n/moderator/en.json b/src/assets/i18n/moderator/en.json
new file mode 100644
index 000000000..95b3ad265
--- /dev/null
+++ b/src/assets/i18n/moderator/en.json
@@ -0,0 +1,36 @@
+{
+  "room-page": {
+    "session-id": "Session-ID",
+    "public-stream": "Public Comments",
+    "moderating-stream": "Moderation"
+  },
+  "comment-list": {
+    "search": "Search",
+    "filter-comments": "Filter questions",
+    "sort-comments": "Sort questions",
+    "add-comment": "Ask a question!",
+    "correct": "Marked as correct by the professor",
+    "favorite": "Professor's favorites",
+    "read": "Discussed by the professor",
+    "unread": "Not yet discussed",
+    "vote-desc": "Descending votes",
+    "vote-asc": "Ascending votes",
+    "time": "Time"
+  },
+  "comment-page": {
+    "enter-title": "Title",
+    "enter-comment": "Your question",
+    "send": "Send",
+    "abort": "Cancel",
+    "error-title": "Please enter a title.",
+    "error-comment": "Please enter a question.",
+    "error-both-fields": "Please fill in all fields.",
+    "no-comments": "No questions yet",
+    "mark-correct": "Marked as correct by the professor",
+    "mark-favorite": "Professor's favorite",
+    "mark-read": "Already discussed by the professor",
+    "vote-up": "Vote up",
+    "vote-down": "Vote down",
+    "delete": "Delete"
+  }
+}
-- 
GitLab