diff --git a/src/app/components/creator/_dialogs/comment-settings/comment-settings.component.ts b/src/app/components/creator/_dialogs/comment-settings/comment-settings.component.ts index 179a0814d2c02ced9c1e3feba852a44d997124ea..7387d324c87175137b039fb9b8dd25dfb86e31c7 100644 --- a/src/app/components/creator/_dialogs/comment-settings/comment-settings.component.ts +++ b/src/app/components/creator/_dialogs/comment-settings/comment-settings.component.ts @@ -14,6 +14,7 @@ import { CommentBonusTokenMixin } from '../../../../models/comment-bonus-token-m import { CommentSettings } from '../../../../models/comment-settings'; import { CommentSettingsDialog } from '../../../../models/comment-settings-dialog'; import { ExportCsv } from '../../../../models/export-csv'; +import { Export } from '../../../../models/export'; @Component({ selector: 'app-comment-settings', @@ -100,19 +101,15 @@ export class CommentSettingsComponent implements OnInit { this.commentService.deleteCommentsByRoomId(this.roomId).subscribe(); } - export(delimiter: string, format: string): void { - new ExportCsv( - this.roomId, + exportCSV(): void { + const exp: Export = new Export( + this.editRoom, this.commentService, this.bonusTokenService, this.translationService, - this.notificationService, - this.editRoom - ).exportAsCsv(delimiter, format); - } - - exportCSV(): void { - this.export(';', 'csv'); + 'room-page', + this.notificationService); + exp.exportAsCsv(); } closeDialog(): void { diff --git a/src/app/components/shared/room-list/room-list.component.ts b/src/app/components/shared/room-list/room-list.component.ts index dce7a3ecd89501033b9ec0a155edc0f309e6da47..bfde2baa8a750f49219c4bd1aa8a18713c6ccbb0 100644 --- a/src/app/components/shared/room-list/room-list.component.ts +++ b/src/app/components/shared/room-list/room-list.component.ts @@ -17,6 +17,7 @@ import { RemoveFromHistoryComponent } from '../_dialogs/remove-from-history/remo import { MatTableDataSource } from '@angular/material/table'; import { ExportCsv } from '../../../models/export-csv'; import { BonusTokenService } from '../../../services/http/bonus-token.service'; +import { Export } from '../../../models/export'; @Component({ selector: 'app-room-list', @@ -171,14 +172,14 @@ export class RoomListComponent implements OnInit, OnDestroy { } exportCsv(room: Room) { - new ExportCsv( - room.id, + const exp: Export = new Export( + room, this.commentService, this.bonusTokenService, this.translateService, + 'room-list', this.notificationService, - room, - 'room-list' - ).exportAsCsv(';', 'csv', this.user); + this.user); + exp.exportAsCsv(); } } diff --git a/src/app/models/export.ts b/src/app/models/export.ts new file mode 100644 index 0000000000000000000000000000000000000000..b45e130bc63168150c845e788052daa0bb896474 --- /dev/null +++ b/src/app/models/export.ts @@ -0,0 +1,168 @@ +import { Room } from './room'; +import { CommentService } from '../services/http/comment.service'; +import { BonusTokenService } from '../services/http/bonus-token.service'; +import { CommentBonusTokenMixin } from './comment-bonus-token-mixin'; +import { BonusToken } from './bonus-token'; +import { TranslateService } from '@ngx-translate/core'; +import { User } from './user'; +import { NotificationService } from '../services/util/notification.service'; + +class ExportMapper<E> { + + private list: Map<string, string>[]; + private map: Map<string, (e: E) => any>; + + constructor() { + this.list = []; + this.map = new Map<string, (e: E) => any>(); + } + + public add(m: (f: (key: string, value: (e: E) => any) => void) => void): void { + m((key, value) => this.map.set(key, value)); + } + + public acceptAll(ar: E[]): void { + ar.forEach(elem => { + this.accept(elem); + }); + } + + public accept(e: E): void { + const row: Map<string, string> = new Map<string, string>(); + this.keys().forEach(k => { + row.set(k, this.map.get(k)(e)); + }); + this.list.push(row); + } + + public parse(delimiter: string, map?: Map<string, string>): string { + if (this.list.length <= 0) { return 'd'; } + let parse = ''; + if (!map) { + map = new Map<string, string>(); + this.keys().forEach(e => map.set(e, e)); + } + this.keys().forEach(e => parse += map.get(e) + delimiter); + this.list.forEach(e => { + parse += '\r\n'; + this.keys().forEach(k => { + parse += e.get(k) + delimiter; + }); + }); + return parse; + } + + public keys(): string[] { + return Array.from(this.map.keys()); + } + +} + +export class Export { + + private mapper: ExportMapper<CommentBonusTokenMixin>; + private bonusTokenMask: boolean; + + constructor( + private room: Room, + private commentService: CommentService, + private bonusTokenService: BonusTokenService, + private translateService: TranslateService, + private translationPath: string, + private notificationService: NotificationService, + private user?: User + ) { + this.mapper = new ExportMapper<CommentBonusTokenMixin>(); + this.bonusTokenMask = !this.user || this.user.role >= 2; + } + + public exportAsCsv() { + this.mapper.add(m => { + m('question', e => this.parseBody(e.body)); + m('timestamp', e => this.parseDate(e.timestamp)); + m('presented', e => e.read); + m('correct/wrong', e => e.correct); + m('score', e => e.score); + m('answer', e => this.parseBody(e.answer)); + m('token', e => this.checkUser(e) && e.bonusToken ? e.bonusToken : ''); + m('token-time', e => this.checkUser(e) ? this.parseDate(e.bonusTimeStamp) : ''); + }); + this.createTranslationMap(this.mapper.keys(), translationMap => { + this.getCommentBonusTokenMixin(comments => { + if (comments.length <= 0) { + this.translateService.get(this.translationPath + '.no-comments').subscribe(msg => { + this.notificationService.show(msg); + }); + } else { + this.mapper.acceptAll(comments); + const date = new Date(); + const dateString = date.toLocaleDateString(); + const data = this.mapper.parse(';', translationMap); + const fileName = this.room.name + '_' + this.room.shortId + dateString + '.csv'; + this.exportData(data, fileName); + } + }); + }); + } + + private checkUser(e: CommentBonusTokenMixin): boolean { + return this.bonusTokenMask || e.creatorId === this.user.id; + } + + private parseBody(body: string): string { + if (!body) {return ''; } + return '"' + body.replace(/[\r\n]/g, ' ').replace(/ +/g, ' ').replace(/"/g, '""') + '"'; + } + + private parseDate(date: Date): string { + if (!date) {return ''; } + const d = new Date(date); + return d.toLocaleDateString() + ' ' + d.toLocaleTimeString(); + } + + private exportData(data: string, fileName: string) { + const myBlob = new Blob([data], { type: `text/csv` }); + const link = document.createElement('a'); + link.setAttribute('download', fileName); + link.href = window.URL.createObjectURL(myBlob); + link.click(); + } + + private getCommentBonusTokenMixin(comments: (comments: CommentBonusTokenMixin[]) => void) { + this.commentService.getAckComments(this.room.id).subscribe(data => { + this.bonusTokenService.getTokensByRoomId(this.room.id).subscribe(list => { + const c = data.map(comment => { + const bt: BonusToken = list.find(e => e.userId === comment.creatorId && comment.id === e.commentId); + const commentWithToken: CommentBonusTokenMixin = <CommentBonusTokenMixin>comment; + if (bt) { + commentWithToken.bonusToken = bt.token; + commentWithToken.bonusTimeStamp = bt.timestamp; + } + return commentWithToken; + }).sort(e => e.bonusToken ? -1 : 1); + comments(c); + }); + }); + + } + + private createTranslationMap(ar: string[], map: ((m: Map<string, string>) => void)): void { + const fields = this.addTranslationPath(ar); + const tm: Map<string, string> = new Map<string, string>(); + this.translateService.get(fields).subscribe(msgs => { + Object.entries(msgs).forEach((e, i) => { + tm.set(ar[i], <string>e[1]); + }); + map(tm); + }); + } + + private addTranslationPath(ar: string[]): string[] { + const fields = []; + ar.forEach(e => { + fields.push(this.translationPath + '.' + e); + }); + return fields; + } + +}