diff --git a/src/app/components/shared/header/header.component.html b/src/app/components/shared/header/header.component.html index 757110d6f6fa5342988a9933d2821b58f6f01a88..2a0abab45d924c8dbe0323a938a5c98eb8eeebd6 100644 --- a/src/app/components/shared/header/header.component.html +++ b/src/app/components/shared/header/header.component.html @@ -11,11 +11,46 @@ <span class="fill-remaining-space"></span> <h2 class="oldtypo-h2" - *ngIf="router.url.includes('comments') && deviceType === 'desktop'" + *ngIf="router.url.includes('comments') && deviceType === 'desktop'" fxLayoutAlign="center center"> {{cTime}} </h2> + <ars-style-btn-material + [ngClass]="{'noOfQuestions': deviceType === 'desktop'}" + *ngIf="router.url.includes('tagcloud')" + ars-flex-box> + <ars-col ars-btn-wrp [xp]="16" [extra]="true"> + <button ars-btn + class="pseudo-button" + matRipple + matTooltip="{{'header.overview-question-tooltip' | translate}}"> + <i>comment</i> + <p style="margin-left: 0.25em;">{{commentsCountQuestions}}</p> + </button> + <button ars-btn + class="pseudo-button" + matRipple + matTooltip="{{'header.overview-questioners-tooltip' | translate}}"> + <i>person</i> + <p style="margin-left: 0.25em;">{{commentsCountUsers}}</p> + </button> + + <button ars-btn + class="pseudo-button" + matRipple + matTooltip="{{'header.overview-keywords-tooltip' | translate}}"> + <mat-icon svgIcon="comment_tag" + class="oldtypo-h2 comment_tag-icon"></mat-icon> + <p style="margin-left: 0.25em;">{{commentsCountKeywords}}</p> + </button> + </ars-col> + </ars-style-btn-material> + + + + + <!-- <span *ngIf="router.url.includes('comments') && !router.url.includes('moderator/comments') && deviceType === 'desktop'" class="fill-remaining-space"></span> diff --git a/src/app/components/shared/header/header.component.scss b/src/app/components/shared/header/header.component.scss index 2cbe46f2d37731df916225704471332a85e17dec..1d715447119c3af13ca55c2338211e10e75597db 100644 --- a/src/app/components/shared/header/header.component.scss +++ b/src/app/components/shared/header/header.component.scss @@ -29,6 +29,27 @@ mat-toolbar-row { font-weight: bold; } +.noOfQuestions { + position: absolute !important; + right: 100px !important; + padding: 10px !important; + margin: 0 !important; +} + +ars-style-btn-material button { + &:hover, &:focus { + border: none; + } + + > * { + color: var(--on-surface); + } +} + +.comment_tag-icon { + height: 18px !important; +} + .sessions { color: var(--primary); } diff --git a/src/app/components/shared/header/header.component.ts b/src/app/components/shared/header/header.component.ts index 2378610f89b6628c30f7823ca4f10c8f55bc5340..00abbe32e89cd8277f576ff3deefbd170e1d0f08 100644 --- a/src/app/components/shared/header/header.component.ts +++ b/src/app/components/shared/header/header.component.ts @@ -6,7 +6,7 @@ import { User } from '../../../models/user'; import { UserRole } from '../../../models/user-roles.enum'; import { Location } from '@angular/common'; import { TranslateService } from '@ngx-translate/core'; -import {MatDialog, MatDialogRef} from '@angular/material/dialog'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { LoginComponent } from '../login/login.component'; import { DeleteAccountComponent } from '../_dialogs/delete-account/delete-account.component'; import { UserService } from '../../../services/http/user.service'; @@ -20,9 +20,8 @@ import { RemindOfTokensComponent } from '../../participant/_dialogs/remind-of-to import { QrCodeDialogComponent } from '../_dialogs/qr-code-dialog/qr-code-dialog.component'; import { BonusTokenService } from '../../../services/http/bonus-token.service'; import { MotdService } from '../../../services/http/motd.service'; -import { RoomService } from '../../../services/http/room.service'; -//import {CloudConfigurationComponent} from "../_dialogs/cloud-configuration/cloud-configuration.component"; import { TopicCloudFilterComponent } from '../_dialogs/topic-cloud-filter/topic-cloud-filter.component'; +import { TagCloudHeaderDataOverview } from '../tag-cloud/tag-cloud.interface'; @Component({ selector: 'app-header', @@ -37,6 +36,9 @@ export class HeaderComponent implements OnInit { isSafari = 'false'; moderationEnabled: boolean; motdState = false; + commentsCountQuestions = 0; + commentsCountUsers = 0; + commentsCountKeywords = 0; constructor(public location: Location, private authenticationService: AuthenticationService, @@ -49,7 +51,7 @@ export class HeaderComponent implements OnInit { private bonusTokenService: BonusTokenService, private _r: Renderer2, private motdService: MotdService, - private confirmDialog: MatDialog + private confirmDialog: MatDialog, ) { } @@ -59,6 +61,11 @@ export class HeaderComponent implements OnInit { this.motdService.requestDialog(); }); }); + this.eventService.on<TagCloudHeaderDataOverview>('tagCloudHeaderDataOverview').subscribe(data => { + this.commentsCountQuestions = data.commentCount; + this.commentsCountUsers = data.userCount; + this.commentsCountKeywords = data.tagCount; + }); if (localStorage.getItem('loggedin') !== null && localStorage.getItem('loggedin') === 'true') { this.authenticationService.refreshLogin(); } @@ -148,7 +155,7 @@ export class HeaderComponent implements OnInit { logout() { // ToDo: Fix this madness. if (this.user.authProvider === 'ARSNOVA_GUEST') { - this.bonusTokenService.getTokensByUserId(this.user.id).subscribe( list => { + this.bonusTokenService.getTokensByUserId(this.user.id).subscribe(list => { if (list && list.length > 0) { const dialogRef = this.dialog.open(RemindOfTokensComponent, { width: '600px' diff --git a/src/app/components/shared/tag-cloud/tag-cloud.component.html b/src/app/components/shared/tag-cloud/tag-cloud.component.html index 16b972cd20d071719f62bcf3de098167f4c343f8..22a6bfdf975f1229946181553751bd0655e7abc3 100644 --- a/src/app/components/shared/tag-cloud/tag-cloud.component.html +++ b/src/app/components/shared/tag-cloud/tag-cloud.component.html @@ -1,12 +1,13 @@ <ars-screen ars-flex-box> - <ars-row [height]="65"> - </ars-row> <mat-drawer-container class="spacyTagCloudContainer"> <mat-drawer [(opened)]="configurationOpen" position="end" mode="push"> <app-cloud-configuration [parent]="this"></app-cloud-configuration> </mat-drawer> <mat-drawer-content> <ars-fill ars-flex-box> + <div [ngClass]="{'hidden': !isLoading}" fxLayout="row" fxLayoutAlign="center center" fxFill> + <mat-progress-spinner *ngIf="isLoading" mode="indeterminate"></mat-progress-spinner> + </div> <angular-tag-cloud class="spacyTagCloud" (window:resize)="onResize($event)" @@ -23,6 +24,4 @@ </ars-fill> </mat-drawer-content> </mat-drawer-container> - <ars-row [height]="37"> - </ars-row> </ars-screen> diff --git a/src/app/components/shared/tag-cloud/tag-cloud.component.scss b/src/app/components/shared/tag-cloud/tag-cloud.component.scss index 711b33256f6068560f5a4178ce8c58135622cd5f..adfc5f5d2534942cfe624e704b87e16e83ba393d 100644 --- a/src/app/components/shared/tag-cloud/tag-cloud.component.scss +++ b/src/app/components/shared/tag-cloud/tag-cloud.component.scss @@ -13,8 +13,21 @@ ars-fill { } mat-drawer-container { - height: 100%; + height: calc(100% - 67px); width: 100%; position: fixed; + margin-top: 67px; +} + +mat-drawer-content { + overflow: hidden; + height: 100%; } +.hidden { + display: none !important; +} + +::ng-deep mat-progress-spinner circle { + stroke: var(--on-background) !important; +} 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 224904e5c93c3590171627f8caca6353d20d7bc2..7465af5823637b74e26ce8cef5e5fa182adb25f1 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 {Component, OnInit, ViewChild, Input} from '@angular/core'; +import { Component, OnInit, ViewChild, Input, AfterViewInit, OnDestroy } from '@angular/core'; import { CloudData, @@ -7,24 +7,25 @@ import { ZoomOnHoverOptions, TagCloudComponent as TCloudComponent } from 'angular-tag-cloud-module'; -import {CommentService} from '../../../services/http/comment.service'; -import {Result, SpacyService} from '../../../services/http/spacy.service'; -import {Comment} from '../../../models/comment'; -import {LanguageService} from '../../../services/util/language.service'; -import {TranslateService} from '@ngx-translate/core'; -import {CreateCommentComponent} from '../_dialogs/create-comment/create-comment.component'; -import {MatDialog} from '@angular/material/dialog'; -import {User} from '../../../models/user'; -import {Room} from '../../../models/room'; -import {NotificationService} from '../../../services/util/notification.service'; -import {EventService} from '../../../services/util/event.service'; -import {AuthenticationService} from '../../../services/http/authentication.service'; -import {ActivatedRoute} from '@angular/router'; -import {UserRole} from '../../../models/user-roles.enum'; -import {RoomService} from '../../../services/http/room.service'; -import {ThemeService} from '../../../../theme/theme.service'; -import {CloudParameters} from './tag-cloud.interface'; +import { CommentService } from '../../../services/http/comment.service'; +import { Result, SpacyService } from '../../../services/http/spacy.service'; +import { Comment } from '../../../models/comment'; +import { LanguageService } from '../../../services/util/language.service'; +import { TranslateService } from '@ngx-translate/core'; +import { CreateCommentComponent } from '../_dialogs/create-comment/create-comment.component'; +import { MatDialog } from '@angular/material/dialog'; +import { User } from '../../../models/user'; +import { Room } from '../../../models/room'; +import { NotificationService } from '../../../services/util/notification.service'; +import { EventService } from '../../../services/util/event.service'; +import { AuthenticationService } from '../../../services/http/authentication.service'; +import { ActivatedRoute } from '@angular/router'; +import { UserRole } from '../../../models/user-roles.enum'; +import { RoomService } from '../../../services/http/room.service'; +import { ThemeService } from '../../../../theme/theme.service'; +import { CloudParameters, CloudWeightColor, CloudWeightCount, TagCloudHeaderDataOverview } from './tag-cloud.interface'; import { TopicCloudAdministrationComponent } from '../_dialogs/topic-cloud-administration/topic-cloud-administration.component'; +import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service'; class CustomPosition implements Position { left: number; @@ -71,6 +72,7 @@ type TagCloudStyleData = [ ]; const colorRegex = /rgba?\((\d+), (\d+), (\d+)(?:, (\d(?:\.\d+)?))?\)/; +const transformationScaleKiller = /scale\([^)]*\)/; const defaultColors: string[] = [ // variable, fallback 'var(--secondary, greenyellow)', // hover @@ -127,6 +129,7 @@ const setGlobalStyles = (styles: TagCloudStyleData): void => { const getDefaultCloudParameters = (): CloudParameters => { const resDefaultColors = getResolvedDefaultColors(); + const resWeightColors = resDefaultColors.slice(1, 11) as CloudWeightColor; return { backgroundColor: resDefaultColors[11], fontColor: resDefaultColors[0], @@ -136,8 +139,10 @@ const getDefaultCloudParameters = (): CloudParameters => { hoverTime: 0.6, hoverDelay: 0.4, delayWord: 0, - randomAngles: false - } + randomAngles: false, + cloudWeightColor: resWeightColors, + cloudWeightCount: [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1] + }; }; @Component({ @@ -145,7 +150,7 @@ const getDefaultCloudParameters = (): CloudParameters => { templateUrl: './tag-cloud.component.html', styleUrls: ['./tag-cloud.component.scss'] }) -export class TagCloudComponent implements OnInit { +export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy { @ViewChild(TCloudComponent, {static: false}) child: TCloudComponent; @Input() user: User; @@ -156,9 +161,9 @@ export class TagCloudComponent implements OnInit { shortId: string; options: CloudOptions = { // if width is between 0 and 1 it will be set to the width of the upper element multiplied by the value - width: 0.99, + width: 1, // if height is between 0 and 1 it will be set to the height of the upper element multiplied by the value - height: 0.99, + height: 1, overflow: false, font: 'Georgia', // not working delay: 0 @@ -169,12 +174,14 @@ export class TagCloudComponent implements OnInit { delay: 0.4 // Zoom will take affect after 0.4 seconds }; userRole: UserRole; - data: CloudData[] = []; + data: TagComment[] = []; sorted = false; debounceTimer = 0; lastDebounceTime = 0; configurationOpen = false; randomizeAngle = false; + isLoading = true; + dataSize: CloudWeightCount; constructor(private commentService: CommentService, private spacyService: SpacyService, @@ -186,7 +193,8 @@ export class TagCloudComponent implements OnInit { private authenticationService: AuthenticationService, private route: ActivatedRoute, protected roomService: RoomService, - private themeService: ThemeService) { + private themeService: ThemeService, + private wsCommentService: WsCommentServiceService) { this.roomId = localStorage.getItem('roomId'); this.langService.langEmitter.subscribe(lang => { this.translateService.use(lang); @@ -200,10 +208,10 @@ export class TagCloudComponent implements OnInit { } else if (e === 'topicCloudConfig') { this.configurationOpen = !this.configurationOpen; } else if (e === 'topicCloudAdministration') { - this.dialog.open(TopicCloudAdministrationComponent, { - minWidth: '50%' - }); - } + this.dialog.open(TopicCloudAdministrationComponent, { + minWidth: '50%' + }); + } }); this.authenticationService.watchUser.subscribe(newUser => { if (newUser) { @@ -233,10 +241,22 @@ export class TagCloudComponent implements OnInit { if (this.child) { setTimeout(() => { this.setCloudParameters(this.getCurrentCloudParameters(), false); - this.updateTagCloud(); }, 1); } }); + this.wsCommentService.getCommentStream(this.roomId).subscribe(e => { + this.commentService.getFilteredComments(this.roomId).subscribe((oldComments: Comment[]) => { + this.analyse(oldComments); + }); + }); + } + + ngAfterViewInit() { + document.getElementById('footer_rescale').style.display = 'none'; + } + + ngOnDestroy() { + document.getElementById('footer_rescale').style.display = 'block'; } initTagCloud() { @@ -256,9 +276,7 @@ export class TagCloudComponent implements OnInit { } setCloudParameters(data: CloudParameters, save = true): void { - const arr = getResolvedDefaultColors(); - arr[0] = data.fontColor; - arr[11] = data.backgroundColor; + const arr = [data.fontColor, ...data.cloudWeightColor, data.backgroundColor]; const fontRange = (data.fontSizeMax - data.fontSizeMin) / 10; const styles = arr.map((e, i) => { if (i > 10) { @@ -275,11 +293,8 @@ export class TagCloudComponent implements OnInit { this.zoomOnHoverOptions.transitionTime = data.hoverTime; this.options.delay = data.delayWord; this.randomizeAngle = data.randomAngles; - if (this.randomizeAngle) { - this.data.forEach(e => e.rotate = Math.floor(Math.random() * 30 - 15)); - } else { - this.data.forEach(e => e.rotate = 0); - } + this.dataSize = data.cloudWeightCount; + this.rebuildData(); this.updateTagCloud(); if (save) { localStorage.setItem('tagCloudConfiguration', JSON.stringify(data)); @@ -314,6 +329,10 @@ export class TagCloudComponent implements OnInit { analyse(comments: Comment[]) { const commentsConcatenated = comments.map(c => c.body).join(' '); + const userSet = new Set<number>(); + comments.forEach(comment => { + userSet.add(comment.userNumber); + }); this.spacyService.analyse(commentsConcatenated, 'de').subscribe((res: Result) => { const map = new Map<string, number>(); @@ -321,6 +340,12 @@ export class TagCloudComponent implements OnInit { const count = (map.get(elem.text) || 0) + 1; map.set(elem.text, count); }); + this.eventService.broadcast('tagCloudHeaderDataOverview', { + commentCount: comments.length, + userCount: userSet.size, + tagCount: map.size + } as TagCloudHeaderDataOverview); + this.data.length = 0; map.forEach((val, key) => { this.data.push(new TagComment(null, true, null, null, @@ -333,7 +358,17 @@ export class TagCloudComponent implements OnInit { }); } + rebuildData() { + if (this.randomizeAngle) { + this.data.forEach(e => e.rotate = Math.floor(Math.random() * 30 - 15)); + } else { + this.data.forEach(e => e.rotate = 0); + } + //TODO Sort using votes and keys + } + updateTagCloud() { + this.isLoading = true; if (this.sorted && this.data.length) { if (!this.child.cloudDataHtmlElements || !this.child.cloudDataHtmlElements.length) { this.child.reDraw(); @@ -349,13 +384,11 @@ export class TagCloudComponent implements OnInit { const current = new Date().getTime(); const diff = current - this.lastDebounceTime; if (diff >= debounceTime) { - this.lastDebounceTime = current; - this.child.reDraw(); + this.redraw(); } else { clearTimeout(this.debounceTimer); this.debounceTimer = setTimeout(() => { - this.lastDebounceTime = new Date().getTime(); - this.child.reDraw(); + this.redraw(); }, debounceTime - diff); } } @@ -407,8 +440,16 @@ export class TagCloudComponent implements OnInit { this.commentService.addComment(comment).subscribe(); } - openCloudConfiguration() { - this.configurationOpen = true; + private redraw(): void { + this.lastDebounceTime = new Date().getTime(); + this.child.reDraw(); + this.isLoading = false; + // This should fix the hover bug (scale was not turned off sometimes) + this.child.cloudDataHtmlElements.forEach(elem => { + elem.addEventListener('mouseleave', () => { + elem.style.transform = elem.style.transform.replace(transformationScaleKiller, '').trim(); + }); + }); } } diff --git a/src/app/components/shared/tag-cloud/tag-cloud.interface.ts b/src/app/components/shared/tag-cloud/tag-cloud.interface.ts index b3faae6f98bc4f485fc2ed4bbaeaefdbc71be061..b7dfb7ecfb2ae5378e981acb0cb4e6470ebbf34a 100644 --- a/src/app/components/shared/tag-cloud/tag-cloud.interface.ts +++ b/src/app/components/shared/tag-cloud/tag-cloud.interface.ts @@ -1,3 +1,35 @@ +export interface TagCloudHeaderDataOverview { + commentCount: number; + userCount: number; + tagCount: number; +} + +export type CloudWeightCount = [ + number, // w1 + number, // w2 + number, // w3 + number, // w4 + number, // w5 + number, // w6 + number, // w7 + number, // w8 + number, // w9 + number // w10 +]; + +export type CloudWeightColor = [ + string, // w1 + string, // w2 + string, // w3 + string, // w4 + string, // w5 + string, // w6 + string, // w7 + string, // w8 + string, // w9 + string // w10 +]; + export interface CloudParameters { /** * Background color of the Tag-cloud @@ -35,4 +67,14 @@ export interface CloudParameters { * Enables random angles */ randomAngles: boolean; + /** + * The count of cloud weights is used to limit the size of the displayed weighted elements. + * + * A number less than zero means that all elements of the weight are displayed. + */ + cloudWeightCount: CloudWeightCount; + /** + * This array contains the CSS color property for each cloud weight + */ + cloudWeightColor: CloudWeightColor; } diff --git a/src/assets/i18n/home/de.json b/src/assets/i18n/home/de.json index 2b479ee8c3314b36a78739f99b38354e51e91b2f..3b531446fa9200db5c40c15aa2d24a362b5310ad 100644 --- a/src/assets/i18n/home/de.json +++ b/src/assets/i18n/home/de.json @@ -95,7 +95,10 @@ "fullscreen": "Text skalieren", "motd": "News", "tag-cloud-config": "Wolkenansicht ändern", - "tag-cloud-administration": "Wolkenthemen editieren" + "tag-cloud-administration": "Wolkenthemen editieren", + "overview-question-tooltip": "Anzahl gestellter Fragen", + "overview-questioners-tooltip": "Anzahl Fragensteller*innen", + "overview-keywords-tooltip": "Anzahl Schlüsselwörter" }, "help": { "cancel": "Schließen", diff --git a/src/assets/i18n/home/en.json b/src/assets/i18n/home/en.json index 3dda55a3730c7f2a357d3ad41fa2cd9dcf125d60..7acc8a7550065feebb9f96c5ab35de311f398355 100644 --- a/src/assets/i18n/home/en.json +++ b/src/assets/i18n/home/en.json @@ -87,7 +87,10 @@ "fullscreen": "Text scaling", "motd": "News", "tag-cloud-config": "Modify cloud view", - "tag-cloud-administration": "Edit cloud topics" + "tag-cloud-administration": "Edit cloud topics", + "overview-question-tooltip": "Number of questions", + "overview-questioners-tooltip": "Number of questioners", + "overview-keywords-tooltip": "Number of Keywords" }, "help": { "cancel": "Close", @@ -118,7 +121,7 @@ "custom-shortid-placeholder": "a-z, A-Z, 0-9, - _ . ~ no blanks", "invalid-shortid": "This key code is not available.", "invalid-char-shortid": "This key code contains invalid characters." - }, + }, "content": { "topic-cloud-content": "Continue to the Topic Cloud with the current filters?", "cancel": "Cancel",