From 65e8558706f71fa09594eac1f27e84567f438a87 Mon Sep 17 00:00:00 2001 From: Mohammad Alayoub <mohammad.alayoub@mni.thm.de> Date: Tue, 22 Jun 2021 18:28:08 +0000 Subject: [PATCH] merge 'update-admin-dialog' into 'staging' --- .../room-creator-page.component.ts | 4 +- .../moderator-comment-list.component.ts | 4 +- .../room-moderator-page.component.ts | 4 +- .../room-participant-page.component.ts | 4 +- .../TopicCloudAdminData.ts | 25 +- .../topic-cloud-administration.component.ts | 196 +++++----- .../comment-answer.component.ts | 4 +- .../comment-list/comment-list.component.ts | 145 +++----- .../shared/comment/comment.component.ts | 9 +- .../shared/header/header.component.html | 9 + .../question-wall/question-wall.component.ts | 4 +- .../shared/room-page/room-page.component.ts | 4 +- .../tag-cloud-pop-up.component.html | 2 +- .../shared/tag-cloud/tag-cloud.component.ts | 10 +- src/app/services/http/spacy.service.ts | 6 +- .../services/util/room-data.service.spec.ts | 17 + src/app/services/util/room-data.service.ts | 334 ++++++++++++++++++ .../services/util/tag-cloud-data.service.ts | 99 ++---- .../util/topic-cloud-admin.service.ts | 88 ++--- .../ws-comment-service.service.spec.ts | 13 - .../websockets/ws-comment-service.service.ts | 29 -- .../websockets/ws-comment.service.spec.ts | 13 + .../services/websockets/ws-comment.service.ts | 21 ++ 23 files changed, 624 insertions(+), 420 deletions(-) create mode 100644 src/app/services/util/room-data.service.spec.ts create mode 100644 src/app/services/util/room-data.service.ts delete mode 100644 src/app/services/websockets/ws-comment-service.service.spec.ts delete mode 100644 src/app/services/websockets/ws-comment-service.service.ts create mode 100644 src/app/services/websockets/ws-comment.service.spec.ts create mode 100644 src/app/services/websockets/ws-comment.service.ts 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 8bfa542f3..094393be7 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 @@ -11,7 +11,7 @@ import { RoomEditComponent } from '../_dialogs/room-edit/room-edit.component'; import { TranslateService } from '@ngx-translate/core'; import { LanguageService } from '../../../services/util/language.service'; import { TSMap } from 'typescript-map'; -import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.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'; @@ -52,7 +52,7 @@ export class RoomCreatorPageComponent extends RoomPageComponent implements OnIni public dialog: MatDialog, private translateService: TranslateService, protected langService: LanguageService, - protected wsCommentService: WsCommentServiceService, + protected wsCommentService: WsCommentService, protected commentService: CommentService, private liveAnnouncer: LiveAnnouncer, private _r: Renderer2, diff --git a/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.ts b/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.ts index 67b350f74..fbd0782ff 100644 --- a/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.ts +++ b/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.ts @@ -5,7 +5,7 @@ import { TranslateService } from '@ngx-translate/core'; import { LanguageService } from '../../../services/util/language.service'; import { Message } from '@stomp/stompjs'; import { MatDialog } from '@angular/material/dialog'; -import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service'; +import { WsCommentService } from '../../../services/websockets/ws-comment.service'; import { User } from '../../../models/user'; import { Vote } from '../../../models/vote'; import { UserRole } from '../../../models/user-roles.enum'; @@ -73,7 +73,7 @@ export class ModeratorCommentListComponent implements OnInit, OnDestroy { private translateService: TranslateService, public dialog: MatDialog, protected langService: LanguageService, - private wsCommentService: WsCommentServiceService, + private wsCommentService: WsCommentService, protected roomService: RoomService, public eventService: EventService, private router: Router, 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 07ed7ef4a..466dc34e6 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 @@ -6,7 +6,7 @@ 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 { 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'; @@ -33,7 +33,7 @@ export class RoomModeratorPageComponent extends RoomPageComponent implements OnI protected route: ActivatedRoute, private translateService: TranslateService, protected langService: LanguageService, - protected wsCommentService: WsCommentServiceService, + protected wsCommentService: WsCommentService, protected commentService: CommentService, protected notification: NotificationService, public eventService: EventService, 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 72b93139a..2d98c3688 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 @@ -8,7 +8,7 @@ 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 { WsCommentService } from '../../../services/websockets/ws-comment.service'; import { CommentService } from '../../../services/http/comment.service'; import { AuthenticationService } from '../../../services/http/authentication.service'; import { LiveAnnouncer } from '@angular/cdk/a11y'; @@ -35,7 +35,7 @@ export class RoomParticipantPageComponent extends RoomPageComponent implements O protected route: ActivatedRoute, private translateService: TranslateService, protected langService: LanguageService, - protected wsCommentService: WsCommentServiceService, + protected wsCommentService: WsCommentService, protected commentService: CommentService, protected authenticationService: AuthenticationService, private liveAnnouncer: LiveAnnouncer, diff --git a/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts b/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts index 5d0dbabc4..56cd28585 100644 --- a/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts +++ b/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts @@ -39,20 +39,25 @@ const deLabels: Label[] = [ {tag: 'app', label: 'Apposition'}, {tag: 'da', label: 'Dativobjekt'}, {tag: 'oa', label: 'Akkusativobjekt'}, - {tag: 'nk', label: 'Noun Kernel Element'}, + {tag: 'nk', label: 'Nomen Kernelement'}, {tag: 'mo', label: 'Modifikator'}, - {tag: 'cj', label: 'Konjunktor'} + {tag: 'cj', label: 'Konjunktor'}, + {tag: 'ROOT', label: 'Satzkernelement'}, + {tag: 'par', label: 'Klammerzusatz'} ]; const enLabels: Label[] = [ - {tag: 'no', label: 'Noun'}, - {tag: 'pro', label: 'Pronoun'}, - {tag: 've', label: 'Verb'}, - {tag: 'adj', label: 'Adjective'}, - {tag: 'adv', label: 'AdverbDVERB'}, - {tag: 'pre', label: 'Preposition'}, - {tag: 'con', label: 'Conjunction'}, - {tag: 'int', label: 'Interjection'} + {tag: 'nsubj', label: 'Nominal subject'}, + {tag: 'nsubjpass', label: 'Passive nominal subject'}, + {tag: 'pobj', label: 'Object of preposition'}, + {tag: 'nummod', label: 'Numeric modifier'}, + {tag: 'compound', label: 'Compound'}, + {tag: 'dobj', label: 'Direct object'}, + {tag: 'amod', label: 'Adjectival modifier'}, + {tag: 'npadvmod', label: 'Noun phrase as adverbial modifier'}, + {tag: 'conj', label: 'Conjunct'}, + {tag: 'ROOT', label: 'Sentence kernel element'}, + {tag: 'intj', label: 'Interjection'} ]; export const spacyLabels = new Labels(deLabels, enLabels); diff --git a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.ts b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.ts index 6154f966e..5a613e270 100644 --- a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.ts +++ b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { NotificationService } from '../../../../services/util/notification.service'; import { TopicCloudConfirmDialogComponent } from '../topic-cloud-confirm-dialog/topic-cloud-confirm-dialog.component'; @@ -6,14 +6,12 @@ import { UserRole } from '../../../../models/user-roles.enum'; import { TranslateService } from '@ngx-translate/core'; import { LanguageService } from '../../../../services/util/language.service'; import { TopicCloudAdminService } from '../../../../services/util/topic-cloud-admin.service'; -import { TopicCloudAdminData, Labels, spacyLabels } from './TopicCloudAdminData'; -import { KeywordOrFulltext } from './TopicCloudAdminData'; +import { TopicCloudAdminData, Labels, spacyLabels, KeywordOrFulltext } from './TopicCloudAdminData'; import { User } from '../../../../models/user'; import { Comment } from '../../../../models/comment'; import { CommentService } from '../../../../services/http/comment.service'; -import { WsCommentServiceService } from '../../../../services/websockets/ws-comment-service.service'; import { TSMap } from 'typescript-map'; -import { Message } from '@stomp/stompjs'; +import { RoomDataService } from '../../../../services/util/room-data.service'; @Component({ @@ -30,6 +28,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { profanitywordlist: string[] = []; blacklistSubscription = undefined; profanitylistSubscription = undefined; + commentServiceSubscription = undefined; keywordOrFulltextENUM = KeywordOrFulltext; newKeyword = undefined; edit = false; @@ -68,16 +67,15 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { private langService: LanguageService, private topicCloudAdminService: TopicCloudAdminService, private commentService: CommentService, - private wsCommentServiceService: WsCommentServiceService) { - this.langService.langEmitter.subscribe(lang => { - this.translateService.use(lang); - }); - } + private roomDataService: RoomDataService) { + this.langService.langEmitter.subscribe(lang => { + this.translateService.use(lang); + }); + } ngOnInit(): void { this.isLoading = true; this.deviceType = localStorage.getItem('deviceType'); - this.wsCommentServiceService.getCommentStream(localStorage.getItem('roomId')).subscribe(message => this.receiveMessage(message)); this.blacklistSubscription = this.topicCloudAdminService.getBlacklist().subscribe(list => this.blacklist = list); this.profanitywordlist = this.topicCloudAdminService.getProfanityListFromStorage(); this.profanitylistSubscription = this.topicCloudAdminService.getCustomProfanityList().subscribe(list => { @@ -92,75 +90,10 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { this.initializeKeywords(); } - receiveMessage(message: Message){ - const msg = JSON.parse(message.body); - const payload = msg.payload; - switch (msg.type) { - case 'CommentCreated': - this.commentService.getComment(payload.id).subscribe(com => { - this.pushInKeywords(com); - }); - break; - case 'CommentPatched': - this.keywords.forEach(keyword => { - for (const comment of keyword.comments) { - if (payload.id === comment.id) { - let refreshKeywords = false; - for (const [key, value] of Object.entries(payload.changes)) { - switch (key) { - case 'score': - comment.score = value as number; - refreshKeywords = true; - break; - case 'upvotes': - comment.upvotes = value as number; - refreshKeywords = true; - break; - case 'downvotes': - comment.downvotes = value as number; - refreshKeywords = true; - break; - case 'keywordsFromSpacy': - comment.keywordsFromSpacy = JSON.parse(value as string); - refreshKeywords = true; - break; - case 'keywordsFromQuestioner': - comment.keywordsFromQuestioner = JSON.parse(value as string); - refreshKeywords = true; - break; - case 'ack': - if (!value as boolean) { - this.removeFromKeywords(payload); - refreshKeywords = true; - } - break; - case 'tag': - comment.tag = value as string; - refreshKeywords = true; - break; - } - } - if (refreshKeywords) { - this.refreshKeywords(); - } - break; - } - } - }); - break; - case 'CommentDeleted': - this.removeFromKeywords(payload); - break; - } - if (this.searchMode){ - this.searchKeyword(); - } - } - - removeFromKeywords(comment: Comment){ - for (const keyword of this.keywords){ + removeFromKeywords(comment: Comment) { + for (const keyword of this.keywords) { keyword.comments.forEach(_comment => { - if (_comment.id === comment.id){ + if (_comment.id === comment.id) { keyword.comments.splice(keyword.comments.indexOf(comment, 0), 1); } }); @@ -168,18 +101,18 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { this.refreshKeywords(); } - refreshKeywords(){ + refreshKeywords() { const tempKeywords = this.keywords; this.keywords = []; tempKeywords.forEach(keyword => { keyword.comments.forEach(comment => this.pushInKeywords(comment)); }); - if (this.searchMode){ + if (this.searchMode) { this.searchKeyword(); } } - pushInKeywords(comment: Comment){ + pushInKeywords(comment: Comment) { let keywords = comment.keywordsFromQuestioner; if (this.keywordORfulltext === KeywordOrFulltext[KeywordOrFulltext.both]) { if (!keywords || !keywords.length) { @@ -195,7 +128,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { const existingKey = this.checkIfKeywordExists(_keyword); if (existingKey) { existingKey.vote += comment.score; - if (this.checkIfCommentExists(existingKey.comments, comment.id)){ + if (this.checkIfCommentExists(existingKey.comments, comment.id)) { existingKey.comments.push(comment); } } else { @@ -210,18 +143,19 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { }); } - ngOnDestroy(){ + ngOnDestroy() { this.setAdminData(); - if(this.blacklistSubscription !== undefined){ + if (this.blacklistSubscription !== undefined) { this.blacklistSubscription.unsubscribe(); } - if(this.profanitylistSubscription !== undefined){ + if (this.profanitylistSubscription !== undefined) { this.profanitylistSubscription.unsubscribe(); } } - initializeKeywords(){ - this.commentService.getAckComments(localStorage.getItem('roomId')).subscribe(comments => { + initializeKeywords() { + const roomId = localStorage.getItem('roomId'); + this.roomDataService.getRoomData(roomId).subscribe(comments => { this.keywords = []; comments.forEach(comment => { this.pushInKeywords(comment); @@ -229,9 +163,37 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { this.sortQuestions(); this.isLoading = false; }); + this.commentServiceSubscription = this.roomDataService.receiveUpdates([ + {type: 'CommentCreated', finished: true}, + {type: 'CommentDeleted'}, + {type: 'CommentPatched', finished: true, updates: ['score']}, + {type: 'CommentPatched', finished: true, updates: ['upvotes']}, + {type: 'CommentPatched', finished: true, updates: ['downvotes']}, + {type: 'CommentPatched', finished: true, updates: ['keywordsFromSpacy']}, + {type: 'CommentPatched', finished: true, updates: ['keywordsFromQuestioner']}, + {type: 'CommentPatched', finished: true, updates: ['ack']}, + {type: 'CommentPatched', finished: true, updates: ['tag']}, + {type: 'CommentPatched', subtype: 'ack'}, + {finished: true} + ]).subscribe(update => { + if (update.type === 'CommentCreated') { + this.pushInKeywords(update.comment); + } else if (update.type === 'CommentDeleted') { + this.removeFromKeywords(update.comment); + } else if (update.type === 'CommentPatched' && update.subtype === 'ack') { + if (!update.comment.ack) { + this.removeFromKeywords(update.comment); + } + } + if (update.finished) { + if (this.searchMode) { + this.searchKeyword(); + } + } + }); } - checkIfCommentExists(comments: Comment[], id: string): boolean{ + checkIfCommentExists(comments: Comment[], id: string): boolean { return comments.filter(comment => comment.id === id).length === 0; } @@ -251,7 +213,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { } setDefaultAdminData() { - this.topicCloudAdminData = this.topicCloudAdminService.getDefaultAdminData; + this.topicCloudAdminData = TopicCloudAdminService.getDefaultAdminData; if (this.topicCloudAdminData) { this.considerVotes = this.topicCloudAdminData.considerVotes; this.profanityFilter = this.topicCloudAdminData.profanityFilter; @@ -286,7 +248,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { } checkIfThereAreQuestions() { - if (this.keywords.length === 0){ + if (this.keywords.length === 0) { this.translateService.get('topic-cloud-dialog.no-keywords-note').subscribe(msg => { this.notificationService.show(msg); }); @@ -299,11 +261,11 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { editKeyword(index: number): void { this.edit = true; setTimeout(() => { - document.getElementById('edit-input'+ index).focus(); + document.getElementById('edit-input' + index).focus(); }, 0); } - deleteKeyword(key: Keyword, message?: string): void{ + deleteKeyword(key: Keyword, message?: string): void { key.comments.forEach(comment => { const changes = new TSMap<string, any>(); let keywords = comment.keywordsFromQuestioner; @@ -315,24 +277,24 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { this.updateComment(comment, changes, message); }); - if (this.searchMode === true){ + if (this.searchMode === true) { this.searchKeyword(); } } - updateComment(updatedComment: Comment, changes: TSMap<string, any>, messageTranslate?: string){ + updateComment(updatedComment: Comment, changes: TSMap<string, any>, messageTranslate?: string) { this.commentService.patchComment(updatedComment, changes).subscribe(_ => { - if (messageTranslate){ - this.translateService.get('topic-cloud-dialog.' + messageTranslate).subscribe(msg => { - this.notificationService.show(msg); - }); - } - }, + if (messageTranslate) { + this.translateService.get('topic-cloud-dialog.' + messageTranslate).subscribe(msg => { + this.notificationService.show(msg); + }); + } + }, error => { this.translateService.get('topic-cloud-dialog.changes-gone-wrong').subscribe(msg => { this.notificationService.show(msg); }); - }); + }); } cancelEdit(): void { @@ -342,21 +304,21 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { confirmEdit(key: Keyword): void { const key2 = this.checkIfKeywordExists(this.newKeyword); - if (key2){ + if (key2) { this.openConfirmDialog('merge-message', 'merge', key, key2); } else { key.comments.forEach(comment => { const changes = new TSMap<string, any>(); let keywords = comment.keywordsFromQuestioner; - for (let i = 0; i < keywords.length; i++){ - if (keywords[i].toLowerCase() === key.keyword.toLowerCase()){ + for (let i = 0; i < keywords.length; i++) { + if (keywords[i].toLowerCase() === key.keyword.toLowerCase()) { keywords[i] = this.newKeyword.trim(); } } changes.set('keywordsFromQuestioner', JSON.stringify(keywords)); keywords = comment.keywordsFromSpacy; - for (let i = 0; i < keywords.length; i++){ - if (keywords[i].toLowerCase() === key.keyword.toLowerCase()){ + for (let i = 0; i < keywords.length; i++) { + if (keywords[i].toLowerCase() === key.keyword.toLowerCase()) { keywords[i] = this.newKeyword.trim(); } } @@ -368,13 +330,13 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { this.edit = false; this.newKeyword = undefined; this.sortQuestions(); - if (this.searchMode){ + if (this.searchMode) { this.searchKeyword(); } } openConfirmDialog(msg: string, _confirmLabel: string, keyword: Keyword, mergeTarget?: Keyword) { - const translationPart = 'topic-cloud-confirm-dialog.'+msg; + const translationPart = 'topic-cloud-confirm-dialog.' + msg; const confirmDialogRef = this.confirmDialog.open(TopicCloudConfirmDialogComponent, { data: {topic: keyword.keyword, message: translationPart, confirmLabel: _confirmLabel} }); @@ -389,7 +351,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { } searchKeyword(): void { - if (!this.searchedKeyword){ + if (!this.searchedKeyword) { this.searchMode = false; } else { this.filteredKeywords = this.keywords.filter(keyword => @@ -400,9 +362,9 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { } mergeKeywords(key1: Keyword, key2: Keyword) { - if (key1 !== undefined && key2 !== undefined){ + if (key1 !== undefined && key2 !== undefined) { key1.comments.forEach(comment => { - if (this.checkIfCommentExists(key2.comments, comment.id)){ + if (this.checkIfCommentExists(key2.comments, comment.id)) { const changes = new TSMap<string, any>(); let keywords = comment.keywordsFromQuestioner; keywords.push(key2.keyword); @@ -418,8 +380,8 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { } checkIfKeywordExists(key: string): Keyword { - for(const keyword of this.keywords){ - if(keyword.keyword.toLowerCase() === key.trim().toLowerCase()){ + for (const keyword of this.keywords) { + if (keyword.keyword.toLowerCase() === key.trim().toLowerCase()) { return keyword; } } @@ -451,11 +413,11 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { } changeProfanityFilter() { - if (this.profanityFilter){ + if (this.profanityFilter) { this.translateService.get('topic-cloud-dialog.words-will-be-overwritten').subscribe(msg => { this.notificationService.show(msg); }); - if (this.searchMode){ + if (this.searchMode) { this.searchKeyword(); } } @@ -493,7 +455,7 @@ interface Keyword { vote: number; } -export interface Data{ +export interface Data { user: User; } diff --git a/src/app/components/shared/comment-answer/comment-answer.component.ts b/src/app/components/shared/comment-answer/comment-answer.component.ts index 536bfb9d2..7b8ce1a71 100644 --- a/src/app/components/shared/comment-answer/comment-answer.component.ts +++ b/src/app/components/shared/comment-answer/comment-answer.component.ts @@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core'; 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 { WsCommentService } from '../../../services/websockets/ws-comment.service'; import { CommentService } from '../../../services/http/comment.service'; import { Comment } from '../../../models/comment'; import { User } from '../../../models/user'; @@ -30,7 +30,7 @@ export class CommentAnswerComponent implements OnInit { private notificationService: NotificationService, private translateService: TranslateService, protected langService: LanguageService, - protected wsCommentService: WsCommentServiceService, + protected wsCommentService: WsCommentService, protected commentService: CommentService, private authenticationService: AuthenticationService, public dialog: MatDialog) { } diff --git a/src/app/components/shared/comment-list/comment-list.component.ts b/src/app/components/shared/comment-list/comment-list.component.ts index 72473799d..a4b96fa6e 100644 --- a/src/app/components/shared/comment-list/comment-list.component.ts +++ b/src/app/components/shared/comment-list/comment-list.component.ts @@ -3,9 +3,7 @@ import { Comment } from '../../../models/comment'; import { CommentService } from '../../../services/http/comment.service'; import { TranslateService } from '@ngx-translate/core'; import { LanguageService } from '../../../services/util/language.service'; -import { Message } from '@stomp/stompjs'; import { MatDialog } from '@angular/material/dialog'; -import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service'; import { User } from '../../../models/user'; import { Vote } from '../../../models/vote'; import { UserRole } from '../../../models/user-roles.enum'; @@ -29,6 +27,7 @@ import { BonusTokenService } from '../../../services/http/bonus-token.service'; import { ModeratorService } from '../../../services/http/moderator.service'; import { CommentFilter, Period } from '../../../utils/filter-options'; import { CreateCommentWrapper } from '../../../utils/CreateCommentWrapper'; +import { RoomDataService } from '../../../services/util/room-data.service'; export interface CommentListData { comments: Comment[]; @@ -104,7 +103,6 @@ export class CommentListComponent implements OnInit, OnDestroy { private translateService: TranslateService, public dialog: MatDialog, protected langService: LanguageService, - private wsCommentService: WsCommentServiceService, protected roomService: RoomService, protected voteService: VoteService, private authenticationService: AuthenticationService, @@ -117,6 +115,7 @@ export class CommentListComponent implements OnInit, OnDestroy { private translationService: TranslateService, private bonusTokenService: BonusTokenService, private moderatorService: ModeratorService, + private roomDataService: RoomDataService, ) { langService.langEmitter.subscribe(lang => translateService.use(lang)); } @@ -238,13 +237,12 @@ export class CommentListComponent implements OnInit, OnDestroy { this.moderatorIds = list.map(m => m.accountId); this.moderatorIds.push(this.room.ownerId); + this.roomDataService.getRoomData(this.room.id).subscribe(comments => { + this.comments = comments; + this.getComments(); + this.eventService.broadcast('commentListCreated', null); + }); this.subscribeCommentStream(); - this.commentService.getAckComments(this.room.id) - .subscribe(comments => { - this.comments = comments; - this.getComments(); - this.eventService.broadcast('commentListCreated', null); - }); }); /** if (this.userRole === UserRole.PARTICIPANT) { @@ -341,97 +339,6 @@ export class CommentListComponent implements OnInit, OnDestroy { } } - parseIncomingMessage(message: Message) { - const msg = JSON.parse(message.body); - const payload = msg.payload; - switch (msg.type) { - case 'CommentCreated': - const c = new Comment(); - c.roomId = this.roomId; - c.body = payload.body; - c.id = payload.id; - c.timestamp = payload.timestamp; - c.tag = payload.tag; - c.creatorId = payload.creatorId; - c.userNumber = this.commentService.hashCode(c.creatorId); - this.commentService.getComment(c.id).subscribe(e => { - c.number = e.number; - c.keywordsFromQuestioner = e.keywordsFromQuestioner; - }); - - this.announceNewComment(c.body); - this.comments = this.comments.concat(c); - this.setComments(this.comments); - break; - case 'CommentPatched': - // ToDo: Use a map for comments w/ key = commentId - for (let i = 0; i < this.comments.length; i++) { - if (payload.id === this.comments[i].id) { - for (const [key, value] of Object.entries(payload.changes)) { - switch (key) { - case this.read: - this.comments[i].read = <boolean>value; - break; - case this.correct: - this.comments[i].correct = <CorrectWrong>value; - break; - case this.favorite: - this.comments[i].favorite = <boolean>value; - if (this.user.id === this.comments[i].creatorId && <boolean>value) { - this.translateService.get('comment-list.comment-got-favorited').subscribe(ret => { - this.notificationService.show(ret); - }); - } - break; - case this.bookmark: - this.comments[i].bookmark = <boolean>value; - break; - case 'score': - this.comments[i].score = <number>value; - this.getComments(); - break; - case this.ack: - const isNowAck = <boolean>value; - if (!isNowAck) { - this.comments = this.comments.filter((el) => { - return el.id !== payload.id; - }); - this.setTimePeriod(); - } - break; - case this.tag: - this.comments[i].tag = <string>value; - break; - case this.answer: - this.comments[i].answer = <string>value; - break; - } - } - } - } - break; - case 'CommentHighlighted': - // ToDo: Use a map for comments w/ key = commentId - for (let i = 0; i < this.comments.length; i++) { - if (payload.id === this.comments[i].id) { - this.comments[i].highlighted = <boolean>payload.lights; - } - } - break; - case 'CommentDeleted': - for (let i = 0; i < this.comments.length; i++) { - this.comments = this.comments.filter((el) => { - return el.id !== payload.id; - }); - } - break; - } - this.setTimePeriod(); - if (this.hideCommentsList) { - this.searchComments(); - } - } - closeDialog() { this.dialog.closeAll(); } @@ -468,7 +375,7 @@ export class CommentListComponent implements OnInit, OnDestroy { return c.userNumber === compare; case this.keyword: this.selectedKeyword = compare; - return c.keywordsFromQuestioner != null && c.keywordsFromQuestioner.length > 0 ? c.keywordsFromQuestioner.includes(compare) : false; + return c.keywordsFromQuestioner ? c.keywordsFromQuestioner.includes(compare) : false; case this.answer: return c.answer; case this.unanswered: @@ -527,6 +434,12 @@ export class CommentListComponent implements OnInit, OnDestroy { pauseCommentStream() { this.freeze = true; + this.roomDataService.getRoomData(this.roomId, true) + .subscribe(comments => { + this.comments = comments; + this.setComments(comments); + this.getComments(); + }); this.commentStream.unsubscribe(); this.translateService.get('comment-list.comment-stream-stopped').subscribe(msg => { this.notificationService.show(msg); @@ -535,7 +448,7 @@ export class CommentListComponent implements OnInit, OnDestroy { playCommentStream() { this.freeze = false; - this.commentService.getAckComments(this.roomId) + this.roomDataService.getRoomData(this.roomId) .subscribe(comments => { this.comments = comments; this.setComments(comments); @@ -548,8 +461,32 @@ export class CommentListComponent implements OnInit, OnDestroy { } subscribeCommentStream() { - this.commentStream = this.wsCommentService.getCommentStream(this.room.id).subscribe((message: Message) => { - this.parseIncomingMessage(message); + this.commentStream = this.roomDataService.receiveUpdates([ + {type: 'CommentCreated', finished: true}, + {type: 'CommentPatched', subtype: this.favorite}, + {type: 'CommentPatched', subtype: 'score'}, + {finished: true} + ]).subscribe(update => { + if (update.type === 'CommentCreated') { + this.announceNewComment(update.comment.body); + this.setComments(this.comments); + } else if (update.type === 'CommentPatched') { + if (update.subtype === 'score') { + this.getComments(); + } else if (update.subtype === this.favorite) { + if (this.user.id === update.comment.creatorId && update.comment.favorite) { + this.translateService.get('comment-list.comment-got-favorited').subscribe(ret => { + this.notificationService.show(ret); + }); + } + } + } + if (update.finished) { + this.setTimePeriod(); + if (this.hideCommentsList) { + this.searchComments(); + } + } }); } diff --git a/src/app/components/shared/comment/comment.component.ts b/src/app/components/shared/comment/comment.component.ts index 768371bf7..f3e688c7c 100644 --- a/src/app/components/shared/comment/comment.component.ts +++ b/src/app/components/shared/comment/comment.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, Output, OnInit, EventEmitter, ViewChild, AfterViewInit, Renderer2 } from '@angular/core'; +import { Component, Input, Output, OnInit, EventEmitter, ViewChild, AfterViewInit } from '@angular/core'; import { Comment } from '../../../models/comment'; import { Vote } from '../../../models/vote'; import { AuthenticationService } from '../../../services/http/authentication.service'; @@ -8,7 +8,6 @@ import { CommentService } from '../../../services/http/comment.service'; import { NotificationService } from '../../../services/util/notification.service'; import { TranslateService } from '@ngx-translate/core'; import { LanguageService } from '../../../services/util/language.service'; -import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service'; import { PresentCommentComponent } from '../_dialogs/present-comment/present-comment.component'; import { MatDialog } from '@angular/material/dialog'; import { animate, state, style, transition, trigger } from '@angular/animations'; @@ -18,7 +17,6 @@ import { UserRole } from '../../../models/user-roles.enum'; import { Rescale } from '../../../models/rescale'; import { RowComponent } from '../../../../../projects/ars/src/lib/components/layout/frame/row/row.component'; import { User } from '../../../models/user'; -import { Observable } from 'rxjs'; @Component({ selector: 'app-comment', @@ -70,8 +68,7 @@ export class CommentComponent implements OnInit, AfterViewInit { private notification: NotificationService, private translateService: TranslateService, public dialog: MatDialog, - protected langService: LanguageService, - private wsCommentService: WsCommentServiceService) { + protected langService: LanguageService) { langService.langEmitter.subscribe(lang => { translateService.use(lang); this.language = lang; @@ -227,7 +224,7 @@ export class CommentComponent implements OnInit, AfterViewInit { setBookmark(comment: Comment): void { //@ts-ignore this.commentService.toggleBookmark(comment).subscribe(c => {this.comment = c; this.comment.keywordsFromQuestioner = JSON.parse(c.keywordsFromQuestioner)}); - //@ts-ignore + //@ts-ignore } goToFullScreen(element: Element): void { diff --git a/src/app/components/shared/header/header.component.html b/src/app/components/shared/header/header.component.html index 36be0756f..a896aa9a9 100644 --- a/src/app/components/shared/header/header.component.html +++ b/src/app/components/shared/header/header.component.html @@ -163,6 +163,15 @@ <span>{{'header.tag-cloud' | translate}}</span> </button> + <button mat-menu-item + tabindex="0" + *ngIf="user && user.role > 0" + (click)="startWorkerDialog()"> + <mat-icon>update + </mat-icon> + <span>{{'header.update-spacy-keywords' | translate}}</span> + </button> + </ng-container> <ng-container *ngIf="router.url.includes('/participant/room/')"> </ng-container> diff --git a/src/app/components/shared/questionwall/question-wall/question-wall.component.ts b/src/app/components/shared/questionwall/question-wall/question-wall.component.ts index d00502ced..b0a18cbaa 100644 --- a/src/app/components/shared/questionwall/question-wall/question-wall.component.ts +++ b/src/app/components/shared/questionwall/question-wall/question-wall.component.ts @@ -3,7 +3,7 @@ import { CommentService } from '../../../../services/http/comment.service'; import { Comment } from '../../../../models/comment'; import { RoomService } from '../../../../services/http/room.service'; import { Room } from '../../../../models/room'; -import { WsCommentServiceService } from '../../../../services/websockets/ws-comment-service.service'; +import { WsCommentService } from '../../../../services/websockets/ws-comment.service'; import { QuestionWallComment } from '../QuestionWallComment'; import { ColComponent } from '../../../../../../projects/ars/src/lib/components/layout/frame/col/col.component'; import { Router } from '@angular/router'; @@ -67,7 +67,7 @@ export class QuestionWallComponent implements OnInit, AfterViewInit, OnDestroy { private router: Router, private commentService: CommentService, private roomService: RoomService, - private wsCommentService: WsCommentServiceService, + private wsCommentService: WsCommentService, private langService: LanguageService, private translateService: TranslateService ) { 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 d61ddc13f..0efacd3c4 100644 --- a/src/app/components/shared/room-page/room-page.component.ts +++ b/src/app/components/shared/room-page/room-page.component.ts @@ -4,7 +4,7 @@ import { User } from '../../../models/user'; import { RoomService } from '../../../services/http/room.service'; import { ActivatedRoute } from '@angular/router'; import { Location } from '@angular/common'; -import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service'; +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'; @@ -28,7 +28,7 @@ export class RoomPageComponent implements OnInit, OnDestroy { constructor(protected roomService: RoomService, protected route: ActivatedRoute, protected location: Location, - protected wsCommentService: WsCommentServiceService, + protected wsCommentService: WsCommentService, protected commentService: CommentService, protected eventService: EventService ) { diff --git a/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.html b/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.html index 2fb300699..c79c3e282 100644 --- a/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.html +++ b/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.html @@ -42,7 +42,7 @@ </p> </span> </div> - <div class="replacementContainer" *ngIf="checkLanguage"> + <div class="replacementContainer" *ngIf="checkLanguage && user && user.role >= 1"> <mat-form-field> <mat-label>{{'tag-cloud-popup.tag-correction-placeholder' | translate}}</mat-label> <input type="text" diff --git a/src/app/components/shared/tag-cloud/tag-cloud.component.ts b/src/app/components/shared/tag-cloud/tag-cloud.component.ts index 615d6f17d..87dac552b 100644 --- a/src/app/components/shared/tag-cloud/tag-cloud.component.ts +++ b/src/app/components/shared/tag-cloud/tag-cloud.component.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { AfterContentInit, Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { CloudData, @@ -22,7 +22,7 @@ import { RoomService } from '../../../services/http/room.service'; import { ThemeService } from '../../../../theme/theme.service'; import { cloneParameters, CloudParameters, CloudTextStyle, CloudWeightSettings } from './tag-cloud.interface'; import { TopicCloudAdministrationComponent } from '../_dialogs/topic-cloud-administration/topic-cloud-administration.component'; -import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service'; +import { WsCommentService } from '../../../services/websockets/ws-comment.service'; import { CreateCommentWrapper } from '../../../utils/CreateCommentWrapper'; import { TopicCloudAdminService } from '../../../services/util/topic-cloud-admin.service'; import { TagCloudPopUpComponent } from './tag-cloud-pop-up/tag-cloud-pop-up.component'; @@ -148,7 +148,7 @@ const getDefaultCloudParameters = (): CloudParameters => { templateUrl: './tag-cloud.component.html', styleUrls: ['./tag-cloud.component.scss'] }) -export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy { +export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit { @ViewChild(TCloudComponent, {static: false}) child: TCloudComponent; @ViewChild(TagCloudPopUpComponent) popup: TagCloudPopUpComponent; @@ -194,7 +194,7 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy { private route: ActivatedRoute, protected roomService: RoomService, private themeService: ThemeService, - private wsCommentService: WsCommentServiceService, + private wsCommentService: WsCommentService, private topicCloudAdmin: TopicCloudAdminService, private router: Router, public dataManager: TagCloudDataService) { @@ -277,7 +277,7 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy { }); } - ngAfterViewInit() { + ngAfterContentInit() { document.getElementById('footer_rescale').style.display = 'none'; this._calcFont = window.getComputedStyle(document.getElementById('tagCloudComponent')).fontFamily; this.dataManager.bindToRoom(this.roomId); diff --git a/src/app/services/http/spacy.service.ts b/src/app/services/http/spacy.service.ts index eb4f184a5..11e2b1d91 100644 --- a/src/app/services/http/spacy.service.ts +++ b/src/app/services/http/spacy.service.ts @@ -3,6 +3,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs'; import { BaseHttpService } from './base-http.service'; import { catchError, map, tap } from 'rxjs/operators'; +import { TopicCloudAdminService } from '../util/topic-cloud-admin.service'; export type Model = 'de' | 'en' | 'fr' | 'es' | 'it' | 'nl' | 'pt' | 'auto'; @@ -177,12 +178,13 @@ export class SpacyService extends BaseHttpService { getKeywords(text: string, model: Model): Observable<string[]> { const url = '/spacy'; + const wanted = TopicCloudAdminService.getDefaultAdminData.wantedLabels[model]; return this.http.post<KeywordList>(url, {text, model}, httpOptions) .pipe( tap(_ => ''), catchError(this.handleError<any>('getKeywords')), - map((result: KeywordList) => - [...new Set(result.map(e => e.type === 'entity' ? e.text.trim() : e.lemma.trim()))]) + map((elem: KeywordList) => wanted != null ? elem.filter(e => wanted.includes(e.dep)) : elem), + map((result: KeywordList) => [...new Set(result.map(e => e.lemma.trim()))]) ); } } diff --git a/src/app/services/util/room-data.service.spec.ts b/src/app/services/util/room-data.service.spec.ts new file mode 100644 index 000000000..2cf2be03c --- /dev/null +++ b/src/app/services/util/room-data.service.spec.ts @@ -0,0 +1,17 @@ +/*import { TestBed } from '@angular/core/testing'; + +import { RoomDataService } from './room-data.service'; + +describe('RoomDataService', () => { + let service: RoomDataService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(RoomDataService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); +*/ diff --git a/src/app/services/util/room-data.service.ts b/src/app/services/util/room-data.service.ts new file mode 100644 index 000000000..a88ea65f9 --- /dev/null +++ b/src/app/services/util/room-data.service.ts @@ -0,0 +1,334 @@ +import { Injectable } from '@angular/core'; +import { Observable, of, Subject, Subscription } from 'rxjs'; +import { WsCommentService } from '../websockets/ws-comment.service'; +import { Message } from '@stomp/stompjs'; +import { Comment } from '../../models/comment'; +import { CommentService } from '../http/comment.service'; +import { CorrectWrong } from '../../models/correct-wrong.enum'; + +export interface UpdateInformation { + type: 'CommentCreated' | 'CommentPatched' | 'CommentHighlighted' | 'CommentDeleted'; + subtype?: string; + comment: Comment; + finished?: boolean; + updates?: string[]; +} + +class RoomDataUpdateSubscription { + updateSubject = new Subject<UpdateInformation>(); + private readonly _filters: Partial<UpdateInformation>[]; + + constructor(filters: Partial<UpdateInformation>[]) { + this._filters = filters; + } + + onUpdate(event: UpdateInformation): void { + for (const filter of this._filters) { + if (this.ensureEqual(filter, event)) { + this.updateSubject.next(event); + break; + } + } + } + + /** + * Checks if value1 is a subset of value2 + */ + private ensureEqual(value1: any, value2: any): boolean { + if (Array.isArray(value1)) { + if (!Array.isArray(value2)) { + return false; + } + for (const key of value1) { + let same = false; + for (const otherKey of value2) { + if (this.ensureEqual(key, otherKey)) { + same = true; + break; + } + } + if (!same) { + return false; + } + } + return true; + } else if (typeof value1 === 'object') { + if (typeof value2 !== 'object') { + return false; + } + const keys = Object.keys(value1); + for (const key of keys) { + if (!this.ensureEqual(value1[key], value2[key])) { + return false; + } + } + return true; + } + return value1 === value2; + } +} + +enum UpdateType { + force, + commentStream +} + +interface FastRoomAccessObject { + [commentId: string]: Comment; +} + +@Injectable({ + providedIn: 'root' +}) +export class RoomDataService { + + private _currentSubscriptions: RoomDataUpdateSubscription[] = []; + private _currentComments: Comment[] = null; + private _commentUpdates: Subject<Comment[]> = new Subject<Comment[]>(); + private _fastCommentAccess: FastRoomAccessObject = null; + private _wsCommentServiceSubscription: Subscription = null; + private _currentRoomId: string = null; + + constructor(private wsCommentService: WsCommentService, + private commentService: CommentService) { + } + + get currentRoomData() { + return this._currentComments; + } + + receiveUpdates(updateFilter: Partial<UpdateInformation>[]): Observable<UpdateInformation> { + if (!this._currentRoomId) { + console.error('Update Subscription got not registered, room is not bound!'); + return null; + } + const subscription = new RoomDataUpdateSubscription(updateFilter); + this._currentSubscriptions.push(subscription); + return subscription.updateSubject.asObservable(); + } + + getRoomData(roomId: string, freezed: boolean = false): Observable<Comment[]> { + if (roomId && roomId === this._currentRoomId) { + return of(freezed ? [...this._currentComments] : this._currentComments); + } + const tempSubject = new Subject<Comment[]>(); + const subscription = this._commentUpdates.subscribe(comments => { + tempSubject.next(freezed ? [...comments] : comments); + subscription.unsubscribe(); + }); + this.ensureRoomBinding(roomId); + return tempSubject.asObservable(); + } + + private ensureRoomBinding(roomId: string) { + if (!roomId || roomId === this._currentRoomId) { + return; + } + this._currentSubscriptions.length = 0; + this._currentRoomId = roomId; + this._currentComments = null; + this._fastCommentAccess = {}; + if (this._wsCommentServiceSubscription) { + this._wsCommentServiceSubscription.unsubscribe(); + } + this._wsCommentServiceSubscription = this.wsCommentService.getCommentStream(roomId) + .subscribe(msg => this.onMessageReceive(msg)); + this.commentService.getAckComments(roomId).subscribe(comments => { + this._currentComments = comments; + for (const comment of comments) { + this._fastCommentAccess[comment.id] = comment; + } + this.triggerUpdate(UpdateType.force, null); + }); + } + + private triggerUpdate(type: UpdateType, additionalInformation: UpdateInformation) { + if (type === UpdateType.force) { + this._commentUpdates.next(this._currentComments); + } else if (type === UpdateType.commentStream) { + for (const subscription of this._currentSubscriptions) { + subscription.onUpdate(additionalInformation); + } + } + } + + private onMessageReceive(message: Message) { + const msg = JSON.parse(message.body); + const payload = msg.payload; + switch (msg.type) { + case 'CommentCreated': + this.onCommentCreate(payload); + break; + case 'CommentPatched': + this.onCommentPatched(payload); + break; + case 'CommentHighlighted': + this.onCommentHighlighted(payload); + break; + case 'CommentDeleted': + this.onCommentDeleted(payload); + break; + } + } + + private onCommentCreate(payload: any) { + const c = new Comment(); + c.roomId = this._currentRoomId; + c.body = payload.body; + c.id = payload.id; + c.timestamp = payload.timestamp; + c.tag = payload.tag; + c.creatorId = payload.creatorId; + c.userNumber = this.commentService.hashCode(c.creatorId); + this.triggerUpdate(UpdateType.commentStream, { + type: 'CommentCreated', + finished: false, + comment: c + }); + this.commentService.getComment(c.id).subscribe(comment => { + this._fastCommentAccess[comment.id] = comment; + this._currentComments.push(comment); + this.triggerUpdate(UpdateType.commentStream, { + type: 'CommentCreated', + finished: true, + comment + }); + }); + } + + private onCommentPatched(payload: any) { + const comment = this._fastCommentAccess[payload.id]; + if (!comment) { + console.error('comment ' + payload.id + ' was not found!'); + return; + } + const updates = []; + for (const [key, value] of Object.entries(payload.changes)) { + updates.push(key); + switch (key) { + case 'read': + comment.read = value as boolean; + this.triggerUpdate(UpdateType.commentStream, { + type: 'CommentPatched', + subtype: 'read', + comment + }); + break; + case 'correct': + comment.correct = value as CorrectWrong; + this.triggerUpdate(UpdateType.commentStream, { + type: 'CommentPatched', + subtype: 'correct', + comment + }); + break; + case 'favorite': + comment.favorite = value as boolean; + this.triggerUpdate(UpdateType.commentStream, { + type: 'CommentPatched', + subtype: 'favorite', + comment + }); + break; + case 'bookmark': + comment.bookmark = value as boolean; + this.triggerUpdate(UpdateType.commentStream, { + type: 'CommentPatched', + subtype: 'bookmark', + comment + }); + break; + case 'score': + comment.score = value as number; + this.triggerUpdate(UpdateType.commentStream, { + type: 'CommentPatched', + subtype: 'score', + comment + }); + break; + case 'upvotes': + comment.upvotes = value as number; + break; + case 'downvotes': + comment.downvotes = value as number; + break; + case 'keywordsFromSpacy': + comment.keywordsFromSpacy = JSON.parse(value as string); + break; + case 'keywordsFromQuestioner': + comment.keywordsFromQuestioner = JSON.parse(value as string); + break; + case 'ack': + const isNowAck = value as boolean; + comment.ack = isNowAck; + if (!isNowAck) { + this.removeComment(payload.id); + } + this.triggerUpdate(UpdateType.commentStream, { + type: 'CommentPatched', + subtype: 'ack', + comment + }); + break; + case 'tag': + comment.tag = value as string; + this.triggerUpdate(UpdateType.commentStream, { + type: 'CommentPatched', + subtype: 'tag', + comment + }); + break; + case 'answer': + comment.answer = value as string; + this.triggerUpdate(UpdateType.commentStream, { + type: 'CommentPatched', + subtype: 'answer', + comment + }); + break; + } + } + this.triggerUpdate(UpdateType.commentStream, { + type: 'CommentPatched', + finished: true, + updates, + comment + }); + } + + private onCommentHighlighted(payload: any) { + const comment = this._fastCommentAccess[payload.id]; + if (!comment) { + console.error('comment ' + payload.id + ' was not found!'); + return; + } + comment.highlighted = payload.lights as boolean; + this.triggerUpdate(UpdateType.commentStream, { + type: 'CommentHighlighted', + finished: true, + comment + }); + } + + private onCommentDeleted(payload: any) { + const comment = this._fastCommentAccess[payload.id]; + if (!comment) { + console.error('comment ' + payload.id + ' was not found!'); + return; + } + this.removeComment(payload.id); + this.triggerUpdate(UpdateType.commentStream, { + type: 'CommentDeleted', + finished: true, + comment + }); + } + + private removeComment(id: string) { + const index = this._currentComments.findIndex(el => el.id === id); + if (index >= 0) { + this._currentComments.splice(index, 1); + } + this._fastCommentAccess[id] = undefined; + } +} diff --git a/src/app/services/util/tag-cloud-data.service.ts b/src/app/services/util/tag-cloud-data.service.ts index 9647b619c..176e49609 100644 --- a/src/app/services/util/tag-cloud-data.service.ts +++ b/src/app/services/util/tag-cloud-data.service.ts @@ -1,14 +1,12 @@ import { Injectable } from '@angular/core'; import { TopicCloudAdminData } from '../../components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData'; import { Observable, Subject, Subscription } from 'rxjs'; -import { WsCommentServiceService } from '../websockets/ws-comment-service.service'; -import { CommentService } from '../http/comment.service'; import { TopicCloudAdminService } from './topic-cloud-admin.service'; import { CommentFilter } from '../../utils/filter-options'; import { TranslateService } from '@ngx-translate/core'; import { CloudParameters } from '../../components/shared/tag-cloud/tag-cloud.interface'; import { Comment } from '../../models/comment'; -import { Message } from '@stomp/stompjs'; +import { RoomDataService } from './room-data.service'; export interface TagCloudDataTagEntry { weight: number; @@ -70,7 +68,7 @@ export class TagCloudDataService { private _dataBus: Subject<TagCloudData>; private _metaDataBus: Subject<TagCloudMetaData>; private _cachedData: TagCloudData; - private _wsCommentSubscription = null; + private _commentSubscription = null; private _roomId = null; private _supplyType = TagCloudDataSupplyType.keywordsAndFullText; private _calcWeightType = TagCloudCalcWeightType.byLength; @@ -83,9 +81,8 @@ export class TagCloudDataService { private _subscriptionAdminData: Subscription; private _currentFilter: CommentFilter; - constructor(private _wsCommentService: WsCommentServiceService, - private _commentService: CommentService, - private _tagCloudAdmin: TopicCloudAdminService) { + constructor(private _tagCloudAdmin: TopicCloudAdminService, + private _roomDataService: RoomDataService) { this._isDemoActive = false; this._isAlphabeticallySorted = false; this._dataBus = new Subject<TagCloudData>(); @@ -108,23 +105,34 @@ export class TagCloudDataService { bindToRoom(roomId: string): void { this._currentFilter = CommentFilter.currentFilter; this._roomId = roomId; - this.onReceiveAdminData(this._tagCloudAdmin.getDefaultAdminData); + this.onReceiveAdminData(TopicCloudAdminService.getDefaultAdminData); this._subscriptionAdminData = this._tagCloudAdmin.getAdminData.subscribe(adminData => { this.onReceiveAdminData(adminData, true); }); this.fetchData(); if (!this._currentFilter.paused) { - this._wsCommentSubscription = this._wsCommentService - .getCommentStream(this._roomId).subscribe(e => this.onMessage(e)); + this._commentSubscription = this._roomDataService.receiveUpdates([ + {type: 'CommentCreated', finished: true}, + {type: 'CommentDeleted'}, + {type: 'CommentPatched', finished: true, updates: ['score']}, + {type: 'CommentPatched', finished: true, updates: ['downvotes']}, + {type: 'CommentPatched', finished: true, updates: ['upvotes']}, + {type: 'CommentPatched', finished: true, updates: ['keywordsFromSpacy']}, + {type: 'CommentPatched', finished: true, updates: ['keywordsFromQuestioner']}, + {type: 'CommentPatched', finished: true, updates: ['ack']}, + {type: 'CommentPatched', finished: true, updates: ['tag']}, + ]).subscribe(_ => { + this.rebuildTagData(); + }); } } unbindRoom(): void { this._subscriptionAdminData.unsubscribe(); - if (this._wsCommentSubscription !== null) { - this._wsCommentSubscription.unsubscribe(); - this._wsCommentSubscription = null; + if (this._commentSubscription !== null) { + this._commentSubscription.unsubscribe(); + this._commentSubscription = null; } } @@ -262,7 +270,7 @@ export class TagCloudDataService { } private fetchData(): void { - this._commentService.getAckComments(this._roomId).subscribe((comments: Comment[]) => { + this._roomDataService.getRoomData(this._roomId).subscribe((comments: Comment[]) => { this._lastFetchedComments = comments; if (this._isDemoActive) { this._lastMetaData.commentCount = comments.length; @@ -371,67 +379,4 @@ export class TagCloudDataService { this.reformatData(); } } - - private onMessage(message: Message): void { - const msg = JSON.parse(message.body); - const payload = msg.payload; - switch (msg.type) { - case 'CommentCreated': - this._commentService.getComment(payload.id).subscribe(c => { - this._lastFetchedComments.push(c); - this.rebuildTagData(); - }); - break; - case 'CommentPatched': - for (const comment of this._lastFetchedComments) { - if (payload.id === comment.id) { - let needRebuild = false; - for (const [key, value] of Object.entries(payload.changes)) { - switch (key) { - case 'score': - comment.score = value as number; - needRebuild = true; - break; - case 'upvotes': - comment.upvotes = value as number; - needRebuild = true; - break; - case 'downvotes': - comment.downvotes = value as number; - needRebuild = true; - break; - case 'keywordsFromSpacy': - comment.keywordsFromSpacy = JSON.parse(value as string); - needRebuild = true; - break; - case 'keywordsFromQuestioner': - comment.keywordsFromQuestioner = JSON.parse(value as string); - needRebuild = true; - break; - case 'ack': - const isNowAck = value as boolean; - if (!isNowAck) { - this._lastFetchedComments = this._lastFetchedComments.filter((el) => el.id !== payload.id); - } - needRebuild = true; - break; - case 'tag': - comment.tag = value as string; - needRebuild = true; - break; - } - } - if (needRebuild) { - this.rebuildTagData(); - } - break; - } - } - break; - case 'CommentDeleted': - this._lastFetchedComments = this._lastFetchedComments.filter((el) => el.id !== payload.id); - this.rebuildTagData(); - break; - } - } } diff --git a/src/app/services/util/topic-cloud-admin.service.ts b/src/app/services/util/topic-cloud-admin.service.ts index 297b61f4a..bc650ebd2 100644 --- a/src/app/services/util/topic-cloud-admin.service.ts +++ b/src/app/services/util/topic-cloud-admin.service.ts @@ -1,8 +1,11 @@ import { Injectable } from '@angular/core'; import * as BadWords from 'naughty-words'; -// eslint-disable-next-line max-len -import { TopicCloudAdminData, KeywordOrFulltext, Labels, spacyLabels } from '../../components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData'; -import { RoomService } from './../../services/http/room.service'; +import { + TopicCloudAdminData, + KeywordOrFulltext, + spacyLabels +} from '../../components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData'; +import { RoomService } from '../http/room.service'; import { Room } from '../../models/room'; import { TranslateService } from '@ngx-translate/core'; import { NotificationService } from './notification.service'; @@ -12,15 +15,16 @@ import { Observable, Subject } from 'rxjs'; providedIn: 'root', }) export class TopicCloudAdminService { + private static readonly adminKey = 'Topic-Cloud-Admin-Data'; private adminData: Subject<TopicCloudAdminData>; private blacklist: Subject<string[]>; private profanityWords = []; private customProfanityWords: Subject<string[]>; private readonly profanityKey = 'custom-Profanity-List'; - private readonly adminKey = 'Topic-Cloud-Admin-Data'; + constructor(private roomService: RoomService, - private translateService: TranslateService, - private notificationService: NotificationService) { + private translateService: TranslateService, + private notificationService: NotificationService) { this.blacklist = new Subject<string[]>(); this.adminData = new Subject<TopicCloudAdminData>(); this.customProfanityWords = new Subject<string[]>(); @@ -33,11 +37,7 @@ export class TopicCloudAdminService { .concat(BadWords['tr']); } - get getAdminData(): Observable<TopicCloudAdminData>{ - return this.adminData.asObservable(); - } - - get getDefaultAdminData(): TopicCloudAdminData { + static get getDefaultAdminData(): TopicCloudAdminData { let data = JSON.parse(localStorage.getItem(this.adminKey)); if (!data) { data = { @@ -55,14 +55,34 @@ export class TopicCloudAdminService { return data; } + static getDefaultSpacyTagsDE(): string[] { + const tags: string[] = []; + spacyLabels.de.forEach(label => { + tags.push(label.tag); + }); + return tags; + } + + static getDefaultSpacyTagsEN(): string[] { + const tags: string[] = []; + spacyLabels.en.forEach(label => { + tags.push(label.tag); + }); + return tags; + } + + get getAdminData(): Observable<TopicCloudAdminData> { + return this.adminData.asObservable(); + } + setAdminData(_adminData: TopicCloudAdminData) { - localStorage.setItem(this.adminKey, JSON.stringify(_adminData)); + localStorage.setItem(TopicCloudAdminService.adminKey, JSON.stringify(_adminData)); this.getBlacklist().subscribe(list => { _adminData.blacklist = []; - if (_adminData.blacklistIsActive){ + if (_adminData.blacklistIsActive) { _adminData.blacklist = list; } - if (_adminData.profanityFilter){ + if (_adminData.profanityFilter) { _adminData.blacklist = _adminData.blacklist.concat(this.getProfanityListFromStorage().concat(this.profanityWords)); } this.adminData.next(_adminData); @@ -77,7 +97,7 @@ export class TopicCloudAdminService { return this.blacklist.asObservable(); } - getProfanityListFromStorage(){ + getProfanityListFromStorage() { const list = localStorage.getItem(this.profanityKey); return list ? JSON.parse(list) : []; } @@ -117,7 +137,7 @@ export class TopicCloudAdminService { if (word !== undefined) { this.getRoom().subscribe(room => { const newlist = room.blacklist ? JSON.parse(room.blacklist) : []; - if (!newlist.includes(word.toLowerCase().trim())){ + if (!newlist.includes(word.toLowerCase().trim())) { newlist.push(word.toLowerCase().trim()); } this.updateBlacklist(newlist, room, 'add-successful'); @@ -128,7 +148,7 @@ export class TopicCloudAdminService { removeWordFromBlacklist(word: string) { if (word !== undefined) { this.getRoom().subscribe(room => { - if (room.blacklist && room.blacklist.length > 0){ + if (room.blacklist && room.blacklist.length > 0) { const newlist = JSON.parse(room.blacklist); newlist.splice(newlist.indexOf(word, 0), 1); this.updateBlacklist(newlist, room, 'remove-successful'); @@ -144,35 +164,19 @@ export class TopicCloudAdminService { updateRoom(updatedRoom: Room, message?: string) { this.roomService.updateRoom(updatedRoom).subscribe(_ => { - if (!message) { - message = 'changes-successful'; - } - this.translateService.get('topic-cloud.' + message).subscribe(msg => { - this.notificationService.show(msg); - this.blacklist.next(JSON.parse(updatedRoom.blacklist)); - }); - }, + if (!message) { + message = 'changes-successful'; + } + this.translateService.get('topic-cloud.' + message).subscribe(msg => { + this.notificationService.show(msg); + this.blacklist.next(JSON.parse(updatedRoom.blacklist)); + }); + }, error => { this.translateService.get('topic-cloud.changes-gone-wrong').subscribe(msg => { this.notificationService.show(msg); }); - }); - } - - getDefaultSpacyTagsDE(): string[] { - const tags: string[] = []; - spacyLabels.de.forEach(label => { - tags.push(label.tag); - }); - return tags; - } - - getDefaultSpacyTagsEN(): string[] { - const tags: string[] = []; - spacyLabels.en.forEach(label => { - tags.push(label.tag); - }); - return tags; + }); } filterProfanityWords(str: string): string { diff --git a/src/app/services/websockets/ws-comment-service.service.spec.ts b/src/app/services/websockets/ws-comment-service.service.spec.ts deleted file mode 100644 index 02c67cb49..000000000 --- a/src/app/services/websockets/ws-comment-service.service.spec.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* import { TestBed } from '@angular/core/testing'; - -import { WsCommentServiceService } from './ws-comment-service.service'; - -describe('WsCommentServiceService', () => { - beforeEach(() => TestBed.configureTestingModule({})); - - it('should be created', () => { - const service: WsCommentServiceService = TestBed.get(WsCommentServiceService); - expect(service).toBeTruthy(); - }); -}); -*/ diff --git a/src/app/services/websockets/ws-comment-service.service.ts b/src/app/services/websockets/ws-comment-service.service.ts deleted file mode 100644 index e08ea411b..000000000 --- a/src/app/services/websockets/ws-comment-service.service.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Comment } from '../../models/comment'; -import { WsConnectorService } from '../../services/websockets/ws-connector.service'; -import { CreateComment } from '../../models/messages/create-comment'; -import { PatchComment } from '../../models/messages/patch-comment'; -import { HighlightComment } from '../../models/messages/highlight-comment'; -import { TSMap } from 'typescript-map'; -import { UpVote } from '../../models/messages/up-vote'; -import { DownVote } from '../../models/messages/down-vote'; -import { ResetVote } from '../../models/messages/reset-vote'; -import { Observable } from 'rxjs'; -import { IMessage } from '@stomp/stompjs'; - - -@Injectable({ - providedIn: 'root' -}) -export class WsCommentServiceService { - - constructor(private wsConnector: WsConnectorService) { } - - getCommentStream(roomId: string): Observable<IMessage> { - return this.wsConnector.getWatcher(`/topic/${roomId}.comment.stream`); - } - - getModeratorCommentStream(roomId: string): Observable<IMessage> { - return this.wsConnector.getWatcher(`/topic/${roomId}.comment.moderator.stream`); - } -} diff --git a/src/app/services/websockets/ws-comment.service.spec.ts b/src/app/services/websockets/ws-comment.service.spec.ts new file mode 100644 index 000000000..5f3e9314b --- /dev/null +++ b/src/app/services/websockets/ws-comment.service.spec.ts @@ -0,0 +1,13 @@ +/*import { TestBed } from '@angular/core/testing'; + +import { WsCommentService } from './ws-comment.service'; + +describe('WsCommentService', () => { + beforeEach(() => TestBed.configureTestingModule({})); + + it('should be created', () => { + const service: WsCommentService = TestBed.get(WsCommentService); + expect(service).toBeTruthy(); + }); +}); +*/ diff --git a/src/app/services/websockets/ws-comment.service.ts b/src/app/services/websockets/ws-comment.service.ts new file mode 100644 index 000000000..60ccb90e7 --- /dev/null +++ b/src/app/services/websockets/ws-comment.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@angular/core'; +import { WsConnectorService } from './ws-connector.service'; +import { Observable } from 'rxjs'; +import { IMessage } from '@stomp/stompjs'; + + +@Injectable({ + providedIn: 'root' +}) +export class WsCommentService { + + constructor(private wsConnector: WsConnectorService) { } + + getCommentStream(roomId: string): Observable<IMessage> { + return this.wsConnector.getWatcher(`/topic/${roomId}.comment.stream`); + } + + getModeratorCommentStream(roomId: string): Observable<IMessage> { + return this.wsConnector.getWatcher(`/topic/${roomId}.comment.moderator.stream`); + } +} -- GitLab