diff --git a/src/app/components/creator/room-creator-page/room-creator-page.component.ts b/src/app/components/creator/room-creator-page/room-creator-page.component.ts index 041a2888ba2ae91f2fd7578c8ae718a78824bff3..f64d70b952792a17ea8df7eff179816cb619e002 100644 --- a/src/app/components/creator/room-creator-page/room-creator-page.component.ts +++ b/src/app/components/creator/room-creator-page/room-creator-page.component.ts @@ -1,41 +1,24 @@ -import { AfterContentInit, AfterViewInit, Component, ComponentRef, EventEmitter, OnDestroy, OnInit, Renderer2 } from '@angular/core'; +import { AfterContentInit, AfterViewInit, Component, OnDestroy, OnInit, Renderer2 } from '@angular/core'; import { RoomService } from '../../../services/http/room.service'; import { ActivatedRoute, Router } from '@angular/router'; import { RoomPageComponent } from '../../shared/room-page/room-page.component'; -import { Room } from '../../../models/room'; -import { CommentSettingsDialog } from '../../../models/comment-settings-dialog'; import { Location } from '@angular/common'; import { NotificationService } from '../../../services/util/notification.service'; import { MatDialog } from '@angular/material/dialog'; -import { RoomEditComponent } from '../_dialogs/room-edit/room-edit.component'; import { TranslateService } from '@ngx-translate/core'; import { LanguageService } from '../../../services/util/language.service'; import { WsCommentService } from '../../../services/websockets/ws-comment.service'; import { CommentService } from '../../../services/http/comment.service'; -import { ModeratorsComponent } from '../_dialogs/moderators/moderators.component'; -import { BonusTokenComponent } from '../_dialogs/bonus-token/bonus-token.component'; -import { CommentSettingsComponent } from '../_dialogs/comment-settings/comment-settings.component'; -import { TagsComponent } from '../_dialogs/tags/tags.component'; import { LiveAnnouncer } from '@angular/cdk/a11y'; import { EventService } from '../../../services/util/event.service'; import { KeyboardUtils } from '../../../utils/keyboard'; import { KeyboardKey } from '../../../utils/keyboard/keys'; import { TitleService } from '../../../services/util/title.service'; -import { DeleteCommentsComponent } from '../_dialogs/delete-comments/delete-comments.component'; -import { Export } from '../../../models/export'; import { BonusTokenService } from '../../../services/http/bonus-token.service'; -import { RoomDeleteComponent } from '../_dialogs/room-delete/room-delete.component'; -import { RoomDeleted } from '../../../models/events/room-deleted'; -import { ProfanitySettingsComponent } from '../_dialogs/profanity-settings/profanity-settings.component'; -import { RoomDescriptionSettingsComponent } from '../_dialogs/room-description-settings/room-description-settings.component'; import { AuthenticationService } from '../../../services/http/authentication.service'; -import { User } from '../../../models/user'; import { HeaderService } from '../../../services/util/header.service'; import { ArsComposeService } from '../../../../../projects/ars/src/lib/services/ars-compose.service'; -import { UserRole } from '../../../models/user-roles.enum'; -import { Palette } from '../../../../theme/Theme'; -import { ArsObserver } from '../../../../../projects/ars/src/lib/models/util/ars-observer'; -import { RoomNameSettingsComponent } from '../_dialogs/room-name-settings/room-name-settings.component'; +import { RoomEditComponent } from '../_dialogs/room-edit/room-edit.component'; @Component({ selector:'app-room-creator-page', @@ -43,26 +26,14 @@ import { RoomNameSettingsComponent } from '../_dialogs/room-name-settings/room-n styleUrls:['./room-creator-page.component.scss'] }) export class RoomCreatorPageComponent extends RoomPageComponent implements OnInit, OnDestroy, AfterContentInit, AfterViewInit{ - room: Room; - user: User; - encodedShortId: string; - updRoom: Room; - commentThreshold: number; - updCommentThreshold: number; - deviceType = localStorage.getItem('deviceType'); - viewModuleCount = 1; - moderatorCommentCounter: number; commentCounterEmitSubscription: any; - urlToCopy = `${window.location.protocol}//${window.location.hostname}/participant/room/`; - headerInterface = null; - onDestroyListener: EventEmitter<void> = new EventEmitter<void>(); constructor(protected roomService: RoomService, protected notification: NotificationService, protected route: ActivatedRoute, protected location: Location, public dialog: MatDialog, - private translateService: TranslateService, + protected translateService: TranslateService, protected langService: LanguageService, protected wsCommentService: WsCommentService, protected commentService: CommentService, @@ -70,14 +41,15 @@ export class RoomCreatorPageComponent extends RoomPageComponent implements OnIni private _r: Renderer2, public eventService: EventService, public titleService: TitleService, - private notificationService: NotificationService, - private bonusTokenService: BonusTokenService, + protected notificationService: NotificationService, + protected bonusTokenService: BonusTokenService, public router: Router, public translationService: TranslateService, public authenticationService: AuthenticationService, public headerService: HeaderService, public composeService: ArsComposeService){ - super(roomService, route, location, wsCommentService, commentService, eventService); + super(roomService, route, location, wsCommentService, commentService, eventService, headerService, composeService, + dialog, bonusTokenService, translateService, notificationService, authenticationService); this.commentCounterEmitSubscription = this.commentCounterEmit.subscribe(e => { this.titleService.attachTitle('(' + e + ')'); }); @@ -85,192 +57,13 @@ export class RoomCreatorPageComponent extends RoomPageComponent implements OnIni } ngAfterViewInit(){ - let isBuild = false; - this.authenticationService.watchUser.subscribe(user => { - this.user = user; - this.roomService.getRoomByShortId(this.encodedShortId).subscribe(e => { - this.room = e; - if (isBuild){ - return; - } - this.initNavigation(); - isBuild = true; - }); - }); - } - - initNavigation(){ - const list: ComponentRef<any>[] = this.composeService.builder(this.headerService.getHost(), e => { - e.menuItem({ - translate:this.headerService.getTranslate(), - icon:'flag', - text:'header.edit-session-description', - callback:() => this.editSessionDescription(), - condition:() => this.user.role > UserRole.PARTICIPANT - }); - e.menuItem({ - translate:this.headerService.getTranslate(), - icon:'visibility_off', - isSVGIcon:false, - text:'header.moderation-mode', - callback:() => this.showCommentsDialog(), - condition:() => this.user.role > UserRole.PARTICIPANT - }); - e.menuItem({ - translate:this.headerService.getTranslate(), - icon:'gavel', - text:'header.edit-moderator', - callback:() => this.showModeratorsDialog(), - condition:() => this.user.role > UserRole.PARTICIPANT - }); - e.menuItem({ - translate:this.headerService.getTranslate(), - icon:'comment_tag', - isSVGIcon:true, - text:'header.edit-tags', - callback:() => this.showTagsDialog(), - condition:() => this.user.role > UserRole.PARTICIPANT - }); - e.menuItem({ - translate:this.headerService.getTranslate(), - icon:'grade', - iconColor:Palette.YELLOW, - text:'header.bonustoken', - callback:() => this.showBonusTokenDialog(), - condition:() => this.user.role >= UserRole.PARTICIPANT - }); - e.menuItem({ - translate:this.headerService.getTranslate(), - icon:'file_download', - text:'header.export-questions', - callback:() => this.exportQuestions(), - condition:() => this.user.role >= UserRole.PARTICIPANT - }); - e.menuItem({ - translate:this.headerService.getTranslate(), - icon:'clear', - text:'header.profanity-filter', - callback:() => this.toggleProfanityFilter(), - condition:() => this.user.role > UserRole.PARTICIPANT - }); - e.menuItem({ - translate:this.headerService.getTranslate(), - icon:'delete_sweep', - iconColor:Palette.RED, - text:'header.delete-questions', - callback:() => this.deleteQuestions(), - condition:() => this.user.role === UserRole.CREATOR - }); - e.menuItem({ - translate:this.headerService.getTranslate(), - icon:'delete', - iconColor:Palette.RED, - isSVGIcon:false, - text:'header.delete-room', - callback:() => this.openDeleteRoomDialog(), - condition:() => this.user.role === UserRole.CREATOR - }); - e.altToggle( - { - translate:this.headerService.getTranslate(), - text:'header.block', - icon:'block', - iconColor:Palette.RED, - color:Palette.RED - }, - { - translate:this.headerService.getTranslate(), - text:'header.unlock', - icon:'block', - iconColor:Palette.RED - }, - ArsObserver.build<boolean>(e => { - e.set(this.room.questionsBlocked); - e.onChange(a => { - this.room.questionsBlocked = a.get(); - this.roomService.updateRoom(this.room).subscribe(); - if (a.get()){ - this.headerService.getTranslate().get('header.questions-blocked').subscribe(msg => { - this.headerService.getNotificationService().show(msg); - }); - } - }); - }) - , - () => this.user.role > UserRole.PARTICIPANT - ); - }); - this.onDestroyListener.subscribe(() => { - list.forEach(e => e.destroy()); - }); - } - - navigateModeratorSettings(){ - this.showCommentsDialog(); - } - - toggleProfanityFilter(){ - const dialogRef = this.dialog.open(ProfanitySettingsComponent, { - width:'400px' - }); - dialogRef.componentInstance.editRoom = this.room; - } - - editSessionName() { - const dialogRef = this.dialog.open(RoomNameSettingsComponent, { - width: '900px', - maxWidth: 'calc( 100% - 50px )', - maxHeight: 'calc( 100vh - 50px )', - autoFocus: false - }); - dialogRef.componentInstance.editRoom = this.room; - } - - editSessionDescription(){ - const dialogRef = this.dialog.open(RoomDescriptionSettingsComponent, { - width:'900px', - maxWidth:'calc( 100% - 50px )', - maxHeight:'calc( 100vh - 50px )', - autoFocus:false - }); - dialogRef.componentInstance.editRoom = this.room; - } - - exportQuestions(){ - const exp: Export = new Export( - this.room, - this.commentService, - this.bonusTokenService, - this.translateService, - 'comment-list', - this.notificationService); - exp.exportAsCsv(); - } - - deleteQuestions(){ - const dialogRef = this.dialog.open(DeleteCommentsComponent, { - width:'400px' - }); - dialogRef.componentInstance.roomId = this.room.id; - dialogRef.afterClosed() - .subscribe(result => { - if (result === 'delete'){ - this.translateService.get('room-page.comments-deleted').subscribe(msg => { - this.notificationService.show(msg); - }); - this.commentService.deleteCommentsByRoomId(this.room.id).subscribe(); - } - }); + this.tryInitNavigation(); } ngOnDestroy(){ super.ngOnDestroy(); this.commentCounterEmitSubscription.unsubscribe(); this.titleService.resetTitle(); - if (this.headerInterface){ - this.headerInterface.unsubscribe(); - } - this.onDestroyListener.emit(); } ngAfterContentInit(): void{ @@ -284,7 +77,6 @@ export class RoomCreatorPageComponent extends RoomPageComponent implements OnIni this.translateService.use(localStorage.getItem('currentLang')); this.route.params.subscribe(params => { this.initializeRoom(params['shortId']); - this.encodedShortId = params['shortId']; }); this.listenerFn = this._r.listen(document, 'keyup', (event) => { const lang: string = this.translateService.currentLang; @@ -334,168 +126,23 @@ export class RoomCreatorPageComponent extends RoomPageComponent implements OnIni } } - postRoomLoadHook(){ - if (this.moderationEnabled){ - this.viewModuleCount = this.viewModuleCount + 1; - this.commentService.countByRoomId(this.room.id, false).subscribe(commentCounter => { - this.moderatorCommentCounter = commentCounter; - }); - } - - } - - updateCommentSettings(settings: CommentSettingsDialog){ - this.room.tags = settings.tags; - - if (this.moderationEnabled && settings.directSend){ - this.viewModuleCount = this.viewModuleCount - 1; - }else if (!this.moderationEnabled && !settings.directSend){ - this.viewModuleCount = this.viewModuleCount + 1; - } - - this.moderationEnabled = !settings.directSend; - localStorage.setItem('moderationEnabled', String(this.moderationEnabled)); - } - - resetThreshold(): void{ - this.room.moderated = undefined; - this.room.threshold = undefined; - this.room.directSend = undefined; - this.room.tags = undefined; - } - - saveChanges(){ - this.roomService.updateRoom(this.updRoom) - .subscribe((room) => { - this.room = room; - this.translateService.get('room-page.changes-successful').subscribe(msg => { - this.notification.show(msg); - }); - }, - error => { - this.translateService.get('room-page.changes-gone-wrong').subscribe(msg => { - this.notification.show(msg); - }); - }); - } - - showSettingsDialog(): void{ - this.updRoom = JSON.parse(JSON.stringify(this.room)); + showSettingsDialog(): void { + const updRoom = JSON.parse(JSON.stringify(this.room)); const dialogRef = this.dialog.open(RoomEditComponent, { - width:'400px' - }); - dialogRef.componentInstance.editRoom = this.updRoom; - dialogRef.afterClosed() - .subscribe(result => { - if (result === 'abort'){ - return; - }else if (result !== 'delete'){ - this.saveChanges(); - } - }); - dialogRef.backdropClick().subscribe(res => { - dialogRef.close('abort'); - }); - } - - showCommentsDialog(): void{ - this.updRoom = JSON.parse(JSON.stringify(this.room)); - - const dialogRef = this.dialog.open(CommentSettingsComponent, { - width:'400px' + width: '400px' }); - dialogRef.componentInstance.roomId = this.room.id; - dialogRef.componentInstance.editRoom = this.updRoom; + dialogRef.componentInstance.editRoom = updRoom; dialogRef.afterClosed() - .subscribe(result => { - if (result === 'abort'){ - return; - }else{ - if (result instanceof CommentSettingsDialog){ - this.updateCommentSettings(result); - this.saveChanges(); + .subscribe(result => { + if (result === 'abort') { + return; + } else if (result !== 'delete') { + this.saveChanges(updRoom); } - } - }); + }); dialogRef.backdropClick().subscribe(res => { dialogRef.close('abort'); }); } - - showModeratorsDialog(): void{ - const dialogRef = this.dialog.open(ModeratorsComponent, { - width:'400px' - }); - dialogRef.componentInstance.roomId = this.room.id; - } - - showBonusTokenDialog(): void{ - const dialogRef = this.dialog.open(BonusTokenComponent, { - width:'400px' - }); - dialogRef.componentInstance.room = this.room; - } - - showTagsDialog(): void{ - this.updRoom = JSON.parse(JSON.stringify(this.room)); - const dialogRef = this.dialog.open(TagsComponent, { - width:'400px' - }); - let tags = []; - if (this.room.tags !== undefined){ - tags = this.room.tags; - } - dialogRef.componentInstance.tags = tags; - dialogRef.afterClosed() - .subscribe(result => { - if (!result || result === 'abort'){ - return; - }else{ - this.updRoom.tags = result; - this.saveChanges(); - } - }); - } - - openDeleteRoomDialog(): void{ - const dialogRef = this.dialog.open(RoomDeleteComponent, { - width:'400px' - }); - dialogRef.componentInstance.room = this.room; - dialogRef.afterClosed() - .subscribe(result => { - if (result === 'delete'){ - this.deleteRoom(); - } - }); - } - - deleteRoom(): void{ - this.translationService.get('room-page.deleted').subscribe(msg => { - this.notificationService.show(this.room.name + msg); - }); - this.roomService.deleteRoom(this.room.id).subscribe(result => { - const event = new RoomDeleted(this.room.id); - this.eventService.broadcast(event.type, event.payload); - this.location.back(); - }); - } - - copyShortId(): void{ - const selBox = document.createElement('textarea'); - selBox.style.position = 'fixed'; - selBox.style.left = '0'; - selBox.style.top = '0'; - selBox.style.opacity = '0'; - selBox.value = `${this.urlToCopy}${this.encodedShortId}`; - document.body.appendChild(selBox); - selBox.focus(); - selBox.select(); - document.execCommand('copy'); - document.body.removeChild(selBox); - this.translateService.get('room-page.session-id-copied').subscribe(msg => { - this.notification.show(msg, '', {duration:2000}); - }); - } } 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 index 9c8fb391b6c08a7ff9d4b3fa442028a155c458d5..0ac213176b47879d8396cd6b2c727af940173f36 100644 --- 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 @@ -8,17 +8,25 @@ <mat-card *ngIf="room"> <div fxLayout="row"> <span class="fill-remaining-space"></span> - <mat-card-header fxLayoutAlign="center"> + <mat-card-header fxLayoutAlign="center" + fxLayout="row"> <mat-card-title fxLayoutAlign="center"> <h2 class="oldtypo-h2">»{{room.name}}«</h2> + <button mat-icon-button + class="inline-icon-button" + (click)="editSessionName()" + aria-labelledby="edit_session_name"> + <mat-icon matTooltip="{{ 'session.edit-session-name' | translate}}">edit</mat-icon> + </button> </mat-card-title> <mat-card-subtitle fxLayoutAlign="center"> <span class="room-short-id"> - {{ 'room-page.session-id' | translate}}: {{ room.shortId }} - </span> + {{ 'room-page.session-id' | translate}}: {{ room.shortId }} + </span> <button id="copy" mat-icon-button - (click)="copyShortId()"> + (click)="copyShortId()" + aria-labelledby="cloud_download"> <mat-icon class="copy" matTooltip="{{ 'room-page.copy-session-id' | translate}}">share </mat-icon> @@ -37,12 +45,13 @@ fxLayoutGap="20px"> <mat-grid-list cols="{{viewModuleCount}}" rowHeight="1:1" - *ngIf="viewModuleCount > 1 && this.moderationEnabled"> + *ngIf="viewModuleCount > 1"> <mat-grid-tile> <button id="question_answer-button" mat-icon-button [disableRipple]="true" - routerLink="/moderator/room/{{ room.shortId }}/comments"> + routerLink="/moderator/room/{{ room.shortId }}/comments" + aria-labelledby="question_answer"> <mat-icon matBadge="{{commentCounter > 0 ? commentCounter : null}}" class="main-icon" [ngClass]="{'desktop' : deviceType === 'desktop'}" @@ -55,7 +64,8 @@ <button id="gavel-button" mat-icon-button [disableRipple]="true" - routerLink="/moderator/room/{{ room.shortId }}/moderator/comments"> + routerLink="/moderator/room/{{ room.shortId }}/moderator/comments" + aria-labelledby="gavel"> <mat-icon style="color: red" matBadge="{{moderatorCommentCounter > 0 ? moderatorCommentCounter : null}}" class="main-icon" [ngClass]="{'desktop' : deviceType === 'desktop'}" @@ -67,7 +77,7 @@ </mat-grid-list> <div fxLayout="row" fxLayoutAlign="center" - *ngIf="!this.moderationEnabled" + *ngIf="viewModuleCount <= 1" class="question-button-div"> <button id="question_answer-button2" mat-icon-button @@ -83,10 +93,24 @@ </div> </div> </mat-card> - + <div *ngIf="!isLoading && !room">{{ 'room-page.room-not-found' | translate }}</div> </div> <button id="live_announcer-button" tabIndex="-1" (click)="announce()" class="visually-hidden">{{ 'room-page.live-announcer' | translate }}</button> </div> + +<app-active-user *ngIf="room" + [room]="room"></app-active-user> + +<div class="visually-hidden"> + <div id="edit_session_name">{{'room-page.a11y-room-name' | translate}}</div> + <div id="cloud_download">{{'room-page.a11y-cloud_download' | translate}}</div> + <div id="question_answer">{{'room-page.a11y-question_answer' | translate}}</div> + <div id="gavel">{{'room-page.a11y-gavel' | translate}}</div> + <div id="settings">{{'room-page.a11y-settings' | translate}}</div> + <div id="edit">{{'room-page.a11y-edit' | translate}}</div> + <div id="insert_comment">{{'room-page.a11y-insert_comment' | translate}}</div> + <div id="person">{{'room-page.a11y-person' | translate}}</div> +</div> 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 index 7b6d75b82b0a441bdc89ad03251de87ef3c5563c..9cafdf112543f08e7528e586ed736da2df027498 100644 --- 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 @@ -151,3 +151,8 @@ markdown { h3 { font-size: 14px; } + +.inline-icon-button { + width: max-content; + margin: 0.25em 0.25em 0.25em 0.75em; +} 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 index d9303a6a9b6981bbb63d9d0660f0f12244a1e45a..c14a687d576465a9313bf45276661800f7894ef6 100644 --- 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 @@ -1,5 +1,4 @@ -import { Component, OnInit, Renderer2, OnDestroy, AfterContentInit } from '@angular/core'; -import { Room } from '../../../models/room'; +import { Component, OnInit, Renderer2, OnDestroy, AfterContentInit, AfterViewInit } from '@angular/core'; import { RoomPageComponent } from '../../shared/room-page/room-page.component'; import { Location } from '@angular/common'; import { RoomService } from '../../../services/http/room.service'; @@ -8,88 +7,51 @@ import { TranslateService } from '@ngx-translate/core'; import { LanguageService } from '../../../services/util/language.service'; import { WsCommentService } from '../../../services/websockets/ws-comment.service'; import { CommentService } from '../../../services/http/comment.service'; -import { Message } from '@stomp/stompjs'; import { NotificationService } from '../../../services/util/notification.service'; import { LiveAnnouncer } from '@angular/cdk/a11y'; import { EventService } from '../../../services/util/event.service'; import { KeyboardUtils } from '../../../utils/keyboard'; import { KeyboardKey } from '../../../utils/keyboard/keys'; +import { MatDialog } from '@angular/material/dialog'; +import { HeaderService } from '../../../services/util/header.service'; +import { ArsComposeService } from '../../../../../projects/ars/src/lib/services/ars-compose.service'; +import { BonusTokenService } from '../../../services/http/bonus-token.service'; +import { AuthenticationService } from '../../../services/http/authentication.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, OnDestroy, AfterContentInit { - - room: Room; - isLoading = true; - deviceType = localStorage.getItem('deviceType'); - moderatorCommentCounter: number; - viewModuleCount = 1; +export class RoomModeratorPageComponent extends RoomPageComponent implements OnInit, OnDestroy, AfterContentInit, AfterViewInit { constructor(protected location: Location, protected roomService: RoomService, protected route: ActivatedRoute, - private translateService: TranslateService, + protected translateService: TranslateService, + protected dialog: MatDialog, protected langService: LanguageService, protected wsCommentService: WsCommentService, protected commentService: CommentService, protected notification: NotificationService, + protected headerService: HeaderService, + protected composeService: ArsComposeService, + protected bonusTokenService: BonusTokenService, + protected authenticationService: AuthenticationService, public eventService: EventService, private liveAnnouncer: LiveAnnouncer, private _r: Renderer2) { - super(roomService, route, location, wsCommentService, commentService, eventService); + super(roomService, route, location, wsCommentService, commentService, eventService, headerService, composeService, + dialog, bonusTokenService, translateService, notification, authenticationService); langService.langEmitter.subscribe(lang => translateService.use(lang)); } - initializeRoom(id: string): void { - this.roomService.getRoomByShortId(id).subscribe(room => { - this.room = room; - this.isLoading = false; - this.moderationEnabled = !this.room.directSend; - if (this.moderationEnabled) { - this.viewModuleCount = this.viewModuleCount + 1; - } - this.commentService.countByRoomId(this.room.id, true).subscribe(commentCounter => { - this.commentCounter = commentCounter; - }); - if (this.moderationEnabled) { - this.commentService.countByRoomId(this.room.id, false).subscribe(commentCounter => { - this.moderatorCommentCounter = commentCounter; - }); - } - - this.commentWatch = this.wsCommentService.getCommentStream(this.room.id); - this.sub = this.commentWatch.subscribe((message: Message) => { - const msg = JSON.parse(message.body); - const payload = msg.payload; - if (msg.type === 'CommentCreated') { - this.commentCounter = this.commentCounter + 1; - } else if (msg.type === 'CommentDeleted') { - this.commentCounter = this.commentCounter - 1; - } else if (msg.type === 'CommentPatched') { - for (const [key, value] of Object.entries(payload.changes)) { - switch (key) { - case 'ack': - const isNowAck = <boolean>value; - if (isNowAck) { - this.commentCounter = this.commentCounter + 1; - this.moderatorCommentCounter = this.moderatorCommentCounter - 1; - } else { - this.commentCounter = this.commentCounter - 1; - this.moderatorCommentCounter = this.moderatorCommentCounter + 1; - } - break; - } - } - } - }); - }); + ngAfterViewInit() { + this.tryInitNavigation(); } ngAfterContentInit(): void { - setTimeout( () => { + setTimeout(() => { document.getElementById('live_announcer-button').focus(); }, 700); } @@ -128,21 +90,4 @@ export class RoomModeratorPageComponent extends RoomPageComponent implements OnI 'die Taste 8 um den aktuellen Raum-Code zu hören, die Taste 0 um auf den Zurück-Button zu gelangen, ' + 'oder die Taste 9 um diese Ansage zu wiederholen.', 'assertive'); } - - copyShortId(): void { - const selBox = document.createElement('textarea'); - selBox.style.position = 'fixed'; - selBox.style.left = '0'; - selBox.style.top = '0'; - selBox.style.opacity = '0'; - selBox.value = this.room.shortId; - document.body.appendChild(selBox); - selBox.focus(); - selBox.select(); - document.execCommand('copy'); - document.body.removeChild(selBox); - this.translateService.get('room-page.session-id-copied').subscribe(msg => { - this.notification.show(msg, '', { duration: 2000 }); - }); - } } diff --git a/src/app/components/participant/room-participant-page/room-participant-page.component.ts b/src/app/components/participant/room-participant-page/room-participant-page.component.ts index 3e4dce340b68f63eb6bfddc28500fddd8c70ffc7..0e26914824bece09c7a391a707255ac8d9a03760 100644 --- a/src/app/components/participant/room-participant-page/room-participant-page.component.ts +++ b/src/app/components/participant/room-participant-page/room-participant-page.component.ts @@ -1,6 +1,4 @@ -import { AfterContentInit, Component, OnDestroy, OnInit, Renderer2 } from '@angular/core'; -import { Room } from '../../../models/room'; -import { User } from '../../../models/user'; +import { AfterContentInit, AfterViewInit, Component, OnDestroy, OnInit, Renderer2 } from '@angular/core'; import { UserRole } from '../../../models/user-roles.enum'; import { RoomPageComponent } from '../../shared/room-page/room-page.component'; import { Location } from '@angular/common'; @@ -17,34 +15,44 @@ import { KeyboardUtils } from '../../../utils/keyboard'; import { KeyboardKey } from '../../../utils/keyboard/keys'; import { map } from 'rxjs/operators'; import { Observable, of } from 'rxjs'; +import { HeaderService } from '../../../services/util/header.service'; +import { ArsComposeService } from '../../../../../projects/ars/src/lib/services/ars-compose.service'; +import { MatDialog } from '@angular/material/dialog'; +import { BonusTokenService } from '../../../services/http/bonus-token.service'; +import { NotificationService } from '../../../services/util/notification.service'; @Component({ selector: 'app-room-participant-page', templateUrl: './room-participant-page.component.html', styleUrls: ['./room-participant-page.component.scss'] }) -export class RoomParticipantPageComponent extends RoomPageComponent implements OnInit, OnDestroy, AfterContentInit { - - room: Room; - isLoading = true; - deviceType = localStorage.getItem('deviceType'); - user: User; +export class RoomParticipantPageComponent extends RoomPageComponent implements OnInit, OnDestroy, AfterContentInit, AfterViewInit { constructor(protected location: Location, protected roomService: RoomService, protected route: ActivatedRoute, - private translateService: TranslateService, + protected translateService: TranslateService, protected langService: LanguageService, protected wsCommentService: WsCommentService, protected commentService: CommentService, protected authenticationService: AuthenticationService, private liveAnnouncer: LiveAnnouncer, + protected headerService: HeaderService, + protected composeService: ArsComposeService, + protected bonusTokenService: BonusTokenService, + protected notificationService: NotificationService, + protected dialog: MatDialog, private _r: Renderer2, public eventService: EventService) { - super(roomService, route, location, wsCommentService, commentService, eventService); + super(roomService, route, location, wsCommentService, commentService, eventService, headerService, composeService, + dialog, bonusTokenService, translateService, notificationService, authenticationService); langService.langEmitter.subscribe(lang => translateService.use(lang)); } + ngAfterViewInit() { + this.tryInitNavigation(); + } + ngAfterContentInit(): void { setTimeout(() => { document.getElementById('live_announcer-button').focus(); @@ -93,11 +101,8 @@ export class RoomParticipantPageComponent extends RoomPageComponent implements O } preRoomLoadHook(): Observable<any> { - this.authenticationService.watchUser.subscribe(user => this.user = user); if (!this.user) { - return this.authenticationService.guestLogin(UserRole.PARTICIPANT).pipe(map((user) => { - return user; - })); + return this.authenticationService.guestLogin(UserRole.PARTICIPANT).pipe(map((user) => user)); } else { return of(this.user); } diff --git a/src/app/components/shared/room-page/room-page.component.ts b/src/app/components/shared/room-page/room-page.component.ts index b9bb29d8b0b168f22db6f859113ce9acbe10d480..8f8a77b98bab4a4577959dae64c0417b8dcccd9f 100644 --- a/src/app/components/shared/room-page/room-page.component.ts +++ b/src/app/components/shared/room-page/room-page.component.ts @@ -1,14 +1,37 @@ -import { Component, OnInit, OnDestroy, EventEmitter } from '@angular/core'; +import { Component, ComponentRef, EventEmitter, OnDestroy, OnInit } from '@angular/core'; import { Room } from '../../../models/room'; -import { User } from '../../../models/user'; import { RoomService } from '../../../services/http/room.service'; import { ActivatedRoute } from '@angular/router'; import { Location } from '@angular/common'; import { WsCommentService } from '../../../services/websockets/ws-comment.service'; import { CommentService } from '../../../services/http/comment.service'; import { EventService } from '../../../services/util/event.service'; -import { Message, IMessage } from '@stomp/stompjs'; -import { Observable, Subscription, of } from 'rxjs'; +import { IMessage, Message } from '@stomp/stompjs'; +import { Observable, of, Subscription } from 'rxjs'; +import { UserRole } from '../../../models/user-roles.enum'; +import { Palette } from '../../../../theme/Theme'; +import { ArsObserver } from '../../../../../projects/ars/src/lib/models/util/ars-observer'; +import { HeaderService } from '../../../services/util/header.service'; +import { ArsComposeService } from '../../../../../projects/ars/src/lib/services/ars-compose.service'; +import { User } from '../../../models/user'; +import { RoomNameSettingsComponent } from '../../creator/_dialogs/room-name-settings/room-name-settings.component'; +import { MatDialog } from '@angular/material/dialog'; +import { RoomDescriptionSettingsComponent } from '../../creator/_dialogs/room-description-settings/room-description-settings.component'; +import { Export } from '../../../models/export'; +import { BonusTokenService } from '../../../services/http/bonus-token.service'; +import { TranslateService } from '@ngx-translate/core'; +import { NotificationService } from '../../../services/util/notification.service'; +import { DeleteCommentsComponent } from '../../creator/_dialogs/delete-comments/delete-comments.component'; +import { RoomDeleteComponent } from '../../creator/_dialogs/room-delete/room-delete.component'; +import { RoomDeleted } from '../../../models/events/room-deleted'; +import { ModeratorsComponent } from '../../creator/_dialogs/moderators/moderators.component'; +import { BonusTokenComponent } from '../../creator/_dialogs/bonus-token/bonus-token.component'; +import { CommentSettingsComponent } from '../../creator/_dialogs/comment-settings/comment-settings.component'; +import { CommentSettingsDialog } from '../../../models/comment-settings-dialog'; +import { TagsComponent } from '../../creator/_dialogs/tags/tags.component'; +import { AuthenticationService } from '../../../services/http/authentication.service'; +import { ProfanitySettingsComponent } from '../../creator/_dialogs/profanity-settings/profanity-settings.component'; +import { SyncFence } from '../../../utils/SyncFence'; @Component({ selector: 'app-room-page', @@ -17,20 +40,35 @@ import { Observable, Subscription, of } from 'rxjs'; }) export class RoomPageComponent implements OnInit, OnDestroy { room: Room = null; + user: User = null; isLoading = true; commentCounter: number; + urlToCopy = `${window.location.protocol}//${window.location.hostname}/participant/room/`; + commentCounterEmit: EventEmitter<number> = new EventEmitter<number>(); + onDestroyListener: EventEmitter<void> = new EventEmitter<void>(); + viewModuleCount = 1; + moderatorCommentCounter: number; + deviceType = localStorage.getItem('deviceType'); + userRole: UserRole; protected moderationEnabled = true; protected sub: Subscription; protected commentWatch: Observable<IMessage>; protected listenerFn: () => void; - public commentCounterEmit: EventEmitter<number> = new EventEmitter<number>(); - - constructor(protected roomService: RoomService, - protected route: ActivatedRoute, - protected location: Location, - protected wsCommentService: WsCommentService, - protected commentService: CommentService, - protected eventService: EventService + private _navigationBuild = new SyncFence(3, this.initNavigation.bind(this)); + + public constructor(protected roomService: RoomService, + protected route: ActivatedRoute, + protected location: Location, + protected wsCommentService: WsCommentService, + protected commentService: CommentService, + protected eventService: EventService, + protected headerService: HeaderService, + protected composeService: ArsComposeService, + protected dialog: MatDialog, + protected bonusTokenService: BonusTokenService, + protected translateService: TranslateService, + protected notificationService: NotificationService, + protected authenticationService: AuthenticationService ) { } @@ -46,27 +84,36 @@ export class RoomPageComponent implements OnInit, OnDestroy { if (this.sub) { this.sub.unsubscribe(); } + this.onDestroyListener.emit(); } - protected preRoomLoadHook(): Observable<any> { - return of(''); - } - - protected postRoomLoadHook() { - + tryInitNavigation() { + this._navigationBuild.resolveCondition(2); } initializeRoom(id: string): void { + this.authenticationService.watchUser.subscribe(user => { + this.user = user; + this._navigationBuild.resolveCondition(0); + }); + this.userRole = this.route.snapshot.data.roles[0]; this.preRoomLoadHook().subscribe(user => { this.roomService.getRoomByShortId(id).subscribe(room => { this.room = room; this.isLoading = false; this.moderationEnabled = !this.room.directSend; localStorage.setItem('moderationEnabled', String(this.moderationEnabled)); - this.commentService.countByRoomId(this.room.id, true) - .subscribe(commentCounter => { - this.setCommentCounter(commentCounter); + if (this.moderationEnabled) { + this.viewModuleCount = this.viewModuleCount + 1; + } + this.commentService.countByRoomId(this.room.id, true).subscribe(commentCounter => { + this.setCommentCounter(commentCounter); + }); + if (this.moderationEnabled && this.userRole > UserRole.PARTICIPANT) { + this.commentService.countByRoomId(this.room.id, false).subscribe(commentCounter => { + this.moderatorCommentCounter = commentCounter; }); + } this.commentWatch = this.wsCommentService.getCommentStream(this.room.id); this.sub = this.commentWatch.subscribe((message: Message) => { const msg = JSON.parse(message.body); @@ -75,9 +122,22 @@ export class RoomPageComponent implements OnInit, OnDestroy { this.setCommentCounter(this.commentCounter + 1); } else if (msg.type === 'CommentDeleted') { this.setCommentCounter(this.commentCounter - 1); + } else if (msg.type === 'CommentPatched' && this.userRole > UserRole.PARTICIPANT) { + const ack = payload.changes.ack; + if (ack === undefined) { + return; + } + if (ack) { + this.setCommentCounter(this.commentCounter + 1); + this.moderatorCommentCounter = this.moderatorCommentCounter - 1; + } else { + this.setCommentCounter(this.commentCounter - 1); + this.moderatorCommentCounter = this.moderatorCommentCounter + 1; + } } }); this.postRoomLoadHook(); + this._navigationBuild.resolveCondition(1); }); }); } @@ -91,4 +151,313 @@ export class RoomPageComponent implements OnInit, OnDestroy { this.roomService.deleteRoom(room.id).subscribe(); this.location.back(); } + + editSessionName() { + console.assert(this.userRole > UserRole.PARTICIPANT); + const dialogRef = this.dialog.open(RoomNameSettingsComponent, { + width: '900px', + maxWidth: 'calc( 100% - 50px )', + maxHeight: 'calc( 100vh - 50px )', + autoFocus: false + }); + dialogRef.componentInstance.editRoom = this.room; + } + + editSessionDescription() { + console.assert(this.userRole > UserRole.PARTICIPANT); + const dialogRef = this.dialog.open(RoomDescriptionSettingsComponent, { + width: '900px', + maxWidth: 'calc( 100% - 50px )', + maxHeight: 'calc( 100vh - 50px )', + autoFocus: false + }); + dialogRef.componentInstance.editRoom = this.room; + } + + exportQuestions() { + const exp: Export = new Export( + this.room, + this.commentService, + this.bonusTokenService, + this.translateService, + 'comment-list', + this.notificationService); + exp.exportAsCsv(); + } + + deleteQuestions() { + console.assert(this.userRole > UserRole.PARTICIPANT); + const dialogRef = this.dialog.open(DeleteCommentsComponent, { + width: '400px' + }); + dialogRef.componentInstance.roomId = this.room.id; + dialogRef.afterClosed() + .subscribe(result => { + if (result === 'delete') { + this.translateService.get('room-page.comments-deleted').subscribe(msg => { + this.notificationService.show(msg); + }); + this.commentService.deleteCommentsByRoomId(this.room.id).subscribe(); + } + }); + } + + openDeleteRoomDialog(): void { + console.assert(this.userRole === UserRole.CREATOR); + const dialogRef = this.dialog.open(RoomDeleteComponent, { + width: '400px' + }); + dialogRef.componentInstance.room = this.room; + dialogRef.afterClosed() + .subscribe(result => { + if (result === 'delete') { + this.deleteRoom(); + } + }); + } + + deleteRoom(): void { + console.assert(this.userRole === UserRole.CREATOR); + this.translateService.get('room-page.deleted').subscribe(msg => { + this.notificationService.show(this.room.name + msg); + }); + this.roomService.deleteRoom(this.room.id).subscribe(result => { + const event = new RoomDeleted(this.room.id); + this.eventService.broadcast(event.type, event.payload); + this.location.back(); + }); + } + + copyShortId(): void { + console.assert(this.userRole > UserRole.PARTICIPANT); + const selBox = document.createElement('textarea'); + selBox.style.position = 'fixed'; + selBox.style.left = '0'; + selBox.style.top = '0'; + selBox.style.opacity = '0'; + selBox.value = `${this.urlToCopy}${this.room.shortId}`; + document.body.appendChild(selBox); + selBox.focus(); + selBox.select(); + document.execCommand('copy'); + document.body.removeChild(selBox); + this.translateService.get('room-page.session-id-copied').subscribe(msg => { + this.notificationService.show(msg, '', { duration: 2000 }); + }); + } + + showModeratorsDialog(): void { + console.assert(this.userRole > UserRole.PARTICIPANT); + const dialogRef = this.dialog.open(ModeratorsComponent, { + width: '400px' + }); + dialogRef.componentInstance.roomId = this.room.id; + } + + showBonusTokenDialog(): void { + console.assert(this.userRole > UserRole.PARTICIPANT); + const dialogRef = this.dialog.open(BonusTokenComponent, { + width: '400px' + }); + dialogRef.componentInstance.room = this.room; + } + + showCommentsDialog(): void { + console.assert(this.userRole > UserRole.PARTICIPANT); + const updRoom = JSON.parse(JSON.stringify(this.room)); + + const dialogRef = this.dialog.open(CommentSettingsComponent, { + width: '400px' + }); + dialogRef.componentInstance.roomId = this.room.id; + dialogRef.componentInstance.editRoom = updRoom; + dialogRef.afterClosed() + .subscribe(result => { + if (result === 'abort') { + return; + } else { + if (result instanceof CommentSettingsDialog) { + this.updateCommentSettings(result); + this.saveChanges(updRoom); + } + } + }); + dialogRef.backdropClick().subscribe(res => { + dialogRef.close('abort'); + }); + } + + updateCommentSettings(settings: CommentSettingsDialog) { + this.room.tags = settings.tags; + + if (this.moderationEnabled && settings.directSend) { + this.viewModuleCount = this.viewModuleCount - 1; + } else if (!this.moderationEnabled && !settings.directSend) { + this.viewModuleCount = this.viewModuleCount + 1; + } + + this.moderationEnabled = !settings.directSend; + localStorage.setItem('moderationEnabled', String(this.moderationEnabled)); + } + + showTagsDialog(): void { + console.assert(this.userRole > UserRole.PARTICIPANT); + const updRoom = JSON.parse(JSON.stringify(this.room)); + const dialogRef = this.dialog.open(TagsComponent, { + width: '400px' + }); + let tags = []; + if (this.room.tags !== undefined) { + tags = this.room.tags; + } + dialogRef.componentInstance.tags = tags; + dialogRef.afterClosed() + .subscribe(result => { + if (!result || result === 'abort') { + return; + } else { + updRoom.tags = result; + this.saveChanges(updRoom); + } + }); + } + + toggleProfanityFilter() { + console.assert(this.userRole > UserRole.PARTICIPANT); + const dialogRef = this.dialog.open(ProfanitySettingsComponent, { + width: '400px' + }); + dialogRef.componentInstance.editRoom = this.room; + } + + protected preRoomLoadHook(): Observable<any> { + return of(''); + } + + protected postRoomLoadHook() { + } + + protected saveChanges(updRoom: Room) { + this.roomService.updateRoom(updRoom) + .subscribe((room) => { + this.room = room; + this.translateService.get('room-page.changes-successful').subscribe(msg => { + this.notificationService.show(msg); + }); + }, + error => { + this.translateService.get('room-page.changes-gone-wrong').subscribe(msg => { + this.notificationService.show(msg); + }); + }); + } + + private initNavigation() { + /* eslint-disable @typescript-eslint/no-shadow */ + const list: ComponentRef<any>[] = this.composeService.builder(this.headerService.getHost(), e => { + e.menuItem({ + translate: this.headerService.getTranslate(), + icon: 'flag', + text: 'header.edit-session-description', + callback: () => this.editSessionDescription(), + condition: () => this.userRole > UserRole.PARTICIPANT + }); + e.menuItem({ + translate: this.headerService.getTranslate(), + icon: 'visibility_off', + isSVGIcon: false, + text: 'header.moderation-mode', + callback: () => this.showCommentsDialog(), + condition: () => this.userRole > UserRole.PARTICIPANT + }); + e.menuItem({ + translate: this.headerService.getTranslate(), + icon: 'gavel', + text: 'header.edit-moderator', + callback: () => this.showModeratorsDialog(), + condition: () => this.userRole > UserRole.PARTICIPANT + }); + e.menuItem({ + translate: this.headerService.getTranslate(), + icon: 'comment_tag', + isSVGIcon: true, + text: 'header.edit-tags', + callback: () => this.showTagsDialog(), + condition: () => this.userRole > UserRole.PARTICIPANT + }); + e.menuItem({ + translate: this.headerService.getTranslate(), + icon: 'grade', + iconColor: Palette.YELLOW, + text: 'header.bonustoken', + callback: () => this.showBonusTokenDialog(), + condition: () => this.userRole > UserRole.PARTICIPANT + }); + e.menuItem({ + translate: this.headerService.getTranslate(), + icon: 'file_download', + text: 'header.export-questions', + callback: () => this.exportQuestions(), + condition: () => this.userRole >= UserRole.PARTICIPANT + }); + e.menuItem({ + translate: this.headerService.getTranslate(), + icon: 'clear', + text: 'header.profanity-filter', + callback: () => this.toggleProfanityFilter(), + condition: () => this.userRole > UserRole.PARTICIPANT + }); + e.menuItem({ + translate: this.headerService.getTranslate(), + icon: 'delete_sweep', + iconColor: Palette.RED, + text: 'header.delete-questions', + callback: () => this.deleteQuestions(), + condition: () => this.userRole > UserRole.PARTICIPANT + }); + e.menuItem({ + translate: this.headerService.getTranslate(), + icon: 'delete', + iconColor: Palette.RED, + isSVGIcon: false, + text: 'header.delete-room', + callback: () => this.openDeleteRoomDialog(), + condition: () => this.userRole === UserRole.CREATOR + }); + e.altToggle( + { + translate: this.headerService.getTranslate(), + text: 'header.block', + icon: 'block', + iconColor: Palette.RED, + color: Palette.RED + }, + { + translate: this.headerService.getTranslate(), + text: 'header.unlock', + icon: 'block', + iconColor: Palette.RED + }, + ArsObserver.build<boolean>(e => { + e.set(this.room.questionsBlocked); + e.onChange(a => { + this.room.questionsBlocked = a.get(); + this.roomService.updateRoom(this.room).subscribe(); + if (a.get()) { + this.headerService.getTranslate().get('header.questions-blocked').subscribe(msg => { + this.headerService.getNotificationService().show(msg); + }); + } + }); + }) + , + () => this.userRole > UserRole.PARTICIPANT + ); + }); + this.onDestroyListener.subscribe(() => { + list.forEach(e => e.destroy()); + }); + /* eslint-enable @typescript-eslint/no-shadow */ + } + }