From 7be3c706be2b9af00fe3bc819e2d3754fb77fb28 Mon Sep 17 00:00:00 2001 From: Ruben Bimberg <ruben.bimberg@mni.thm.de> Date: Sat, 31 Jul 2021 19:11:55 +0200 Subject: [PATCH] Update the dialog for building the topic cloud Implemented some wanted features and reorganized code internally. [Ticket: #190][Ticket: #124] --- .../cloud-configuration.component.html | 2 +- .../cloud-configuration.component.scss | 4 + .../cloud-configuration.component.ts | 14 +- .../topic-cloud-filter.component.html | 147 ++++++++++------- .../topic-cloud-filter.component.scss | 26 ++- .../topic-cloud-filter.component.ts | 41 ++++- .../comment-list/comment-list.component.ts | 2 +- .../shared/header/header.component.html | 4 +- .../shared/tag-cloud/tag-cloud.component.html | 1 + .../shared/tag-cloud/tag-cloud.component.scss | 8 + .../shared/tag-cloud/tag-cloud.component.ts | 120 ++------------ .../shared/tag-cloud/tag-cloud.interface.ts | 125 -------------- .../services/util/tag-cloud-data.service.ts | 2 +- src/app/utils/cloud-parameters.ts | 154 ++++++++++++++++++ ...ntWrapper.ts => create-comment-wrapper.ts} | 1 - src/assets/i18n/home/de.json | 24 ++- src/assets/i18n/home/en.json | 24 ++- 17 files changed, 374 insertions(+), 325 deletions(-) delete mode 100644 src/app/components/shared/tag-cloud/tag-cloud.interface.ts create mode 100644 src/app/utils/cloud-parameters.ts rename src/app/utils/{CreateCommentWrapper.ts => create-comment-wrapper.ts} (97%) diff --git a/src/app/components/shared/_dialogs/cloud-configuration/cloud-configuration.component.html b/src/app/components/shared/_dialogs/cloud-configuration/cloud-configuration.component.html index 132d5a561..67f579cc6 100644 --- a/src/app/components/shared/_dialogs/cloud-configuration/cloud-configuration.component.html +++ b/src/app/components/shared/_dialogs/cloud-configuration/cloud-configuration.component.html @@ -201,7 +201,7 @@ <div class="button-row"> <button (click)="reset()" mat-button class="reset tag-config-button" >{{'tag-cloud-config.reset-btn' | translate}}</button> </div> - <div class="button-row"> + <div class="button-row save-or-cancel"> <button (click)="cancel()" mat-button class="secondary tag-config-button">{{'tag-cloud-config.cancel-btn' | translate}}</button> <button (click)="save()" mat-button class="primary tag-config-button">{{'tag-cloud-config.save-btn' | translate}}</button> </div> diff --git a/src/app/components/shared/_dialogs/cloud-configuration/cloud-configuration.component.scss b/src/app/components/shared/_dialogs/cloud-configuration/cloud-configuration.component.scss index d2f54057e..eeabe3b58 100644 --- a/src/app/components/shared/_dialogs/cloud-configuration/cloud-configuration.component.scss +++ b/src/app/components/shared/_dialogs/cloud-configuration/cloud-configuration.component.scss @@ -249,3 +249,7 @@ mat-divider { .automatic-spelling { padding-bottom: 30px; } + +.save-or-cancel { + width: max-content; +} diff --git a/src/app/components/shared/_dialogs/cloud-configuration/cloud-configuration.component.ts b/src/app/components/shared/_dialogs/cloud-configuration/cloud-configuration.component.ts index bdd01fdb6..8139d0305 100644 --- a/src/app/components/shared/_dialogs/cloud-configuration/cloud-configuration.component.ts +++ b/src/app/components/shared/_dialogs/cloud-configuration/cloud-configuration.component.ts @@ -1,9 +1,9 @@ import { Component, Input, OnInit } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { TagCloudComponent } from '../../tag-cloud/tag-cloud.component'; -import { cloneParameters, CloudParameters, CloudTextStyle } from '../../tag-cloud/tag-cloud.interface'; import { WeightClass } from './weight-class.interface'; import { TagCloudMetaDataCount } from '../../../../services/util/tag-cloud-data.service'; +import { CloudParameters, CloudTextStyle } from '../../../../utils/cloud-parameters'; @Component({ selector: 'app-cloud-configuration', @@ -108,8 +108,8 @@ export class CloudConfigurationComponent implements OnInit { ngOnInit() { this.translateService.use(localStorage.getItem('currentLang')); - this.cloudParameters = cloneParameters(this.parent.currentCloudParameters); - this.defaultCloudParameters = cloneParameters(this.parent.currentCloudParameters); + this.cloudParameters = new CloudParameters(this.parent.currentCloudParameters); + this.defaultCloudParameters = new CloudParameters(this.parent.currentCloudParameters); this.parent.dataManager.getMetaData().subscribe((value)=>{ if (!value) { return; @@ -170,7 +170,7 @@ export class CloudConfigurationComponent implements OnInit { cancel() { this.parent.tagCloudDataManager.demoActive = false; this.parent.setCloudParameters(this.defaultCloudParameters); - this.cloudParameters = cloneParameters(this.defaultCloudParameters); + this.cloudParameters = new CloudParameters(this.defaultCloudParameters); this.parent.configurationOpen = false; this.setStep(0); this.readMaxFont(); @@ -179,7 +179,7 @@ export class CloudConfigurationComponent implements OnInit { save() { this.parent.tagCloudDataManager.demoActive = false; this.parent.setCloudParameters(this.cloudParameters); - this.defaultCloudParameters = cloneParameters(this.cloudParameters); + this.defaultCloudParameters = new CloudParameters(this.cloudParameters); this.parent.configurationOpen = false; this.setStep(0); this.readMaxFont(); @@ -220,8 +220,8 @@ export class CloudConfigurationComponent implements OnInit { reset(){ this.parent.resetColorsToTheme(); this.parent.configurationOpen = false; - this.cloudParameters = cloneParameters(this.parent.currentCloudParameters); - this.defaultCloudParameters = cloneParameters(this.parent.currentCloudParameters); + this.cloudParameters = new CloudParameters(this.parent.currentCloudParameters); + this.defaultCloudParameters = new CloudParameters(this.parent.currentCloudParameters); this.readMaxFont(); } diff --git a/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.html b/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.html index 7b1e51768..1b4bb8b0a 100644 --- a/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.html +++ b/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.html @@ -1,69 +1,100 @@ -<span *ngIf="isTopicRequirementActive"> +<h1 mat-dialog-title>{{'content.tag-cloud-title' | translate}}</h1> +<mat-dialog-content> + <p mat-dialog-content>{{'content.tag-cloud-info' | translate}}</p> + <mat-divider></mat-divider> + <h3 class="mat-h3">{{'content.tag-cloud-source-title' | translate}}</h3> + <mat-radio-group [(ngModel)]="selectedSource"> + <mat-radio-button value="fromUser"> + <span *ngIf="isMobile()">{{'content.tag-cloud-source-verified-by-user-short' | translate}}</span> + <span *ngIf="!isMobile()">{{'content.tag-cloud-source-verified-by-user' | translate}}</span> + </mat-radio-button> + <mat-radio-button value="fromSpacy"> + <span *ngIf="isMobile()">{{'content.tag-cloud-source-analyzed-short' | translate}}</span> + <span *ngIf="!isMobile()">{{'content.tag-cloud-source-analyzed' | translate}}</span> + </mat-radio-button> + <mat-radio-button value="all"> + <span>{{'content.tag-cloud-source-all' | translate}}</span> + </mat-radio-button> + </mat-radio-group> + <mat-divider></mat-divider> + <h3 class="mat-h3">{{'content.tag-cloud-questions-title' | translate}}</h3> + <span *ngIf="isTopicRequirementActive"> <mat-icon [inline]="true" style="color: var(--red) !important;">warning</mat-icon> - {{'header.overview-admin-config-enabled' | translate}} -</span> -<mat-divider></mat-divider> -<mat-radio-group [(ngModel)]="continueFilter" aria-label="Select an option"> - <mat-radio-button checked="true" value="continueWithAll"> - <div class="elementRow"> - <div class="elementText"> - {{(!isMobile() ? 'content.continue-with-all-questions' : 'content.continue-with-all-questions-short') | translate}} + {{'header.overview-admin-config-enabled' | translate}} + </span> + <mat-radio-group [(ngModel)]="continueFilter" aria-label="Select an option"> + <mat-radio-button checked="true" value="continueWithAll"> + <div class="elementRow"> + <div class="elementText" *ngIf="isMobile()">{{'content.tag-cloud-questions-all-short' | translate}}</div> + <div class="elementText" *ngIf="!isMobile()">{{'content.tag-cloud-questions-all' | translate}}</div> + <div class="elementIcons" *ngIf="allComments"> + <mat-icon [inline]="true" + matTooltip="{{'header.overview-question-tooltip' | translate}}">comment + </mat-icon> + {{allComments.comments}} + <mat-icon [inline]="true" + matTooltip="{{'header.overview-questioners-tooltip' | translate}}">person + </mat-icon> + {{allComments.users}} + <mat-icon svgIcon="hashtag" class="comment_tag-icon" + matTooltip="{{'header.overview-keywords-tooltip' | translate}}"></mat-icon> + {{allComments.keywords}} + </div> </div> - <div class="elementIcons" *ngIf="allComments"> - <mat-icon [inline]="true" - matTooltip="{{'header.overview-question-tooltip' | translate}}">comment - </mat-icon> - {{allComments.comments}} - <mat-icon [inline]="true" - matTooltip="{{'header.overview-questioners-tooltip' | translate}}">person - </mat-icon> - {{allComments.users}} - <mat-icon svgIcon="hashtag" class="comment_tag-icon" - matTooltip="{{'header.overview-keywords-tooltip' | translate}}"></mat-icon> - {{allComments.keywords}} + </mat-radio-button> + <mat-radio-button value="continueWithCurr" *ngIf="!disableCurrentFiltersOptions"> + <div class="elementRow"> + <div class="elementText">{{'content.tag-cloud-questions-current-filtered' | translate}}</div> + <div class="elementIcons" *ngIf="filteredComments"> + <mat-icon [inline]="true" + matTooltip="{{'header.overview-question-tooltip' | translate}}">comment + </mat-icon> + {{filteredComments.comments}} + <mat-icon [inline]="true" + matTooltip="{{'header.overview-questioners-tooltip' | translate}}">person + </mat-icon> + {{filteredComments.users}} + <mat-icon svgIcon="hashtag" class="comment_tag-icon" + matTooltip="{{'header.overview-keywords-tooltip' | translate}}"></mat-icon> + {{filteredComments.keywords}} + </div> </div> - </div> - </mat-radio-button> - <mat-radio-button value="continueWithCurr" *ngIf="!disableCurrentFiltersOptions"> - <div class="elementRow"> - <div class="elementText"> - {{(!isMobile() ? 'content.continue-with-current-questions' : 'content.continue-with-current-questions-short')| translate}} - </div> - <div class="elementIcons" *ngIf="filteredComments"> - <mat-icon [inline]="true" - matTooltip="{{'header.overview-question-tooltip' | translate}}">comment - </mat-icon> - {{filteredComments.comments}} - <mat-icon [inline]="true" - matTooltip="{{'header.overview-questioners-tooltip' | translate}}">person - </mat-icon> - {{filteredComments.users}} - <mat-icon svgIcon="hashtag" class="comment_tag-icon" - matTooltip="{{'header.overview-keywords-tooltip' | translate}}"></mat-icon> - {{filteredComments.keywords}} - </div> - </div> - </mat-radio-button> - <mat-radio-button value="continueWithAllFromNow"> - {{(!isMobile() ? 'content.continue-with-all-questions-from-now' : 'content.continue-with-all-questions-from-now-short') | translate}} - </mat-radio-button> -</mat-radio-group> + </mat-radio-button> + <mat-radio-button #radioButton value="continueWithAllFromNow"> + <span *ngIf="isMobile()">{{'content.tag-cloud-questions-brainstorming-short' | translate}}</span> + <span *ngIf="!isMobile()">{{'content.tag-cloud-questions-brainstorming' | translate}}</span> + </mat-radio-button> + <mat-form-field *ngIf="user && user.role > 0 && radioButton.checked" appearance="fill"> + <mat-label>{{'content.brainstorming-question' | translate}}</mat-label> + <input matInput autocomplete="off" type="text" [(ngModel)]="question"> + <button *ngIf="question" matSuffix mat-icon-button aria-label="Clear" (click)="question=''"> + <mat-icon>close</mat-icon> + </button> + </mat-form-field> + </mat-radio-group> + + <mat-divider *ngIf="user && user.role > 0 && hasNoKeywords"></mat-divider> + <mat-card *ngIf="user && user.role > 0 && hasNoKeywords" class="noKeywords"> + <p>{{'topic-cloud-filter.info-no-keywords' | translate}}</p> + <span>{{'topic-cloud-filter.label-refresh-keywords' | translate}}</span> + <button mat-flat-button + type="button" + class="mat-flat-button secondary-confirm-button" + (click)="onKeywordRefreshClick()"> + {{'topic-cloud-filter.label-refresh-keywords-start' | translate}} + </button> + </mat-card> -<mat-card *ngIf="user && user.role > 0 && hasNoKeywords" class="noKeywords"> - <p>{{'topic-cloud-filter.info-no-keywords' | translate}}</p> - <span>{{'topic-cloud-filter.label-refresh-keywords' | translate}}</span> - <button mat-button - (click)="onKeywordRefreshClick()"> - {{'topic-cloud-filter.label-refresh-keywords-start' | translate}} - </button> -</mat-card> + <mat-divider *ngIf="user && user.role > 0"></mat-divider> + <app-worker-dialog [inlined]="true" *ngIf="user && user.role > 0"></app-worker-dialog> -<mat-divider *ngIf="user && user.role > 0"></mat-divider> -<app-worker-dialog [inlined]="true" *ngIf="user && user.role > 0"></app-worker-dialog> + <mat-divider></mat-divider> + <p mat-dialog-content>{{'content.tag-cloud-description' | translate}}</p> +</mat-dialog-content> <app-dialog-action-buttons buttonsLabelSection="content" - confirmButtonLabel="continue" + confirmButtonLabel="tag-cloud-create" buttonIcon="cloud" [cancelButtonClickAction]="cancelButtonActionCallback()" diff --git a/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.scss b/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.scss index 0be1bd332..42e4150ae 100644 --- a/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.scss +++ b/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.scss @@ -5,13 +5,20 @@ mat-radio-button { mat-radio-group { display: flex; flex-direction: column; - margin: 15px 0; +} + +mat-divider { + margin: 0.75em 0; } ::ng-deep .mat-radio-label-content { width: 100%; } +::ng-deep mat-form-field > .mat-form-field-wrapper { + margin-bottom: -1.34375em; +} + .elementRow { display: flex; flex-direction: row; @@ -29,4 +36,19 @@ mat-radio-group { .comment_tag-icon { height: 18px !important; -} \ No newline at end of file +} + +.mat-dialog-content { + padding: 0; + margin: 0; +} + +.noKeywords { + background: var(--surface); + color: var(--on-surface); +} + +.secondary-confirm-button { + background-color: var(--secondary); + color: var(--on-secondary); +} diff --git a/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.ts b/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.ts index 2b521760b..6663529b0 100644 --- a/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.ts +++ b/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.ts @@ -11,11 +11,12 @@ import { RoomService } from '../../../../services/http/room.service'; import { Comment } from '../../../../models/comment'; import { CommentListData } from '../../comment-list/comment-list.component'; import { TopicCloudAdminService } from '../../../../services/util/topic-cloud-admin.service'; -import { TopicCloudAdminData } from '../topic-cloud-administration/TopicCloudAdminData'; +import { KeywordOrFulltext, TopicCloudAdminData } from '../topic-cloud-administration/TopicCloudAdminData'; import { TagCloudDataService } from '../../../../services/util/tag-cloud-data.service'; import { User } from '../../../../models/user'; import { WorkerDialogComponent } from '../worker-dialog/worker-dialog.component'; import { Room } from '../../../../models/room'; +import { CloudParameters } from '../../../../utils/cloud-parameters'; class CommentsCount { comments: number; @@ -23,6 +24,12 @@ class CommentsCount { keywords: number; } +enum KeywordsSource { + fromUser = 'fromUser', + fromSpacy = 'fromSpacy', + all = 'all' +} + @Component({ selector: 'app-topic-cloud-filter', templateUrl: './topic-cloud-filter.component.html', @@ -32,7 +39,9 @@ export class TopicCloudFilterComponent implements OnInit { @Input() target: string; @Input() user: User; + question = ''; continueFilter = 'continueWithAll'; + selectedSource; comments: Comment[]; tmpFilter: CommentFilter; allComments: CommentsCount; @@ -51,9 +60,17 @@ export class TopicCloudFilterComponent implements OnInit { private router: Router, protected roomService: RoomService, @Inject(MAT_DIALOG_DATA) public data: any, - public eventService: EventService) { + public eventService: EventService, + private topicCloudAdminService: TopicCloudAdminService) { langService.langEmitter.subscribe(lang => translationService.use(lang)); this._adminData = TopicCloudAdminService.getDefaultAdminData; + if (this._adminData.keywordORfulltext === KeywordOrFulltext.fulltext) { + this.selectedSource = KeywordsSource.fromSpacy; + } else if (this._adminData.keywordORfulltext === KeywordOrFulltext.keyword) { + this.selectedSource = KeywordsSource.fromUser; + } else { + this.selectedSource = KeywordsSource.all; + } this.isTopicRequirementActive = !TopicCloudAdminService.isTopicRequirementDisabled(this._adminData); } @@ -83,11 +100,7 @@ export class TopicCloudFilterComponent implements OnInit { } isMobile(): boolean { - if (window.matchMedia('(max-width:500px)').matches) { - return true; - } else { - return false; - } + return window.matchMedia('(max-width:500px)').matches; } onKeywordRefreshClick() { @@ -131,7 +144,21 @@ export class TopicCloudFilterComponent implements OnInit { return; } + if (this.selectedSource === KeywordsSource.fromSpacy) { + this._adminData.keywordORfulltext = KeywordOrFulltext.fulltext; + } else if (this.selectedSource === KeywordsSource.fromUser) { + this._adminData.keywordORfulltext = KeywordOrFulltext.keyword; + } else { + this._adminData.keywordORfulltext = KeywordOrFulltext.both; + } + this.topicCloudAdminService.setAdminData(this._adminData, false, this.user.role); + + const params = CloudParameters.currentParameters; + params.question = this.question; + CloudParameters.currentParameters = params; + CommentFilter.currentFilter = filter; + this.dialogRef.close(this.router.navigateByUrl(this.target)); }; } 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 dfa93b4df..fff6b1e42 100644 --- a/src/app/components/shared/comment-list/comment-list.component.ts +++ b/src/app/components/shared/comment-list/comment-list.component.ts @@ -26,7 +26,7 @@ import { Export } from '../../../models/export'; 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 { CreateCommentWrapper } from '../../../utils/create-comment-wrapper'; import { TopicCloudAdminService } from '../../../services/util/topic-cloud-admin.service'; import { RoomDataService } from '../../../services/util/room-data.service'; import { WsRoomService } from '../../../services/websockets/ws-room.service'; diff --git a/src/app/components/shared/header/header.component.html b/src/app/components/shared/header/header.component.html index bcbc279fc..16e56f230 100644 --- a/src/app/components/shared/header/header.component.html +++ b/src/app/components/shared/header/header.component.html @@ -340,8 +340,8 @@ [ngClass]="{'color-warn': room && room.questionsBlocked}" > <mat-icon class="color-warn">block</mat-icon> - <span *ngIf="!room.questionsBlocked">{{'header.block' | translate}}</span> - <span *ngIf="room.questionsBlocked">{{'header.unlock' | translate}}</span> + <span *ngIf="room && !room.questionsBlocked">{{'header.block' | translate}}</span> + <span *ngIf="room && room.questionsBlocked">{{'header.unlock' | translate}}</span> </button> <button mat-menu-item 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 c85e8b3e1..ad727ca7e 100644 --- a/src/app/components/shared/tag-cloud/tag-cloud.component.html +++ b/src/app/components/shared/tag-cloud/tag-cloud.component.html @@ -1,6 +1,7 @@ <link rel="preconnect" href="https://fonts.gstatic.com"> <link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&family=Dancing+Script&family=Indie+Flower&family=Permanent+Marker&display=swap" rel="stylesheet"> <ars-screen ars-flex-box> + <h1 *ngIf="user && user.role > 0 && question" class="tag-cloud-brainstorming-question mat-display-1">{{question}}</h1> <mat-drawer-container class="spacyTagCloudContainer"> <mat-drawer [(opened)]="configurationOpen" position="end" mode="over"> <app-cloud-configuration [parent]="this"></app-cloud-configuration> 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 e1d794690..15887f083 100644 --- a/src/app/components/shared/tag-cloud/tag-cloud.component.scss +++ b/src/app/components/shared/tag-cloud/tag-cloud.component.scss @@ -60,3 +60,11 @@ app-tag-cloud-pop-up { transition: all 0.1s ease-in-out; z-index: 2; } + +.tag-cloud-brainstorming-question { + position: fixed; + top: 80px; + left: 20px; + z-index: 2; + color: var(--tag-cloud-color, black); +} 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 122763e8f..321e584bd 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 { AfterContentInit, AfterViewInit, Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { AfterContentInit, Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { CloudData, @@ -20,14 +20,14 @@ import { ActivatedRoute, Router } from '@angular/router'; import { UserRole } from '../../../models/user-roles.enum'; 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 { WsCommentService } from '../../../services/websockets/ws-comment.service'; -import { CreateCommentWrapper } from '../../../utils/CreateCommentWrapper'; +import { CreateCommentWrapper } from '../../../utils/create-comment-wrapper'; import { TopicCloudAdminService } from '../../../services/util/topic-cloud-admin.service'; import { TagCloudPopUpComponent } from './tag-cloud-pop-up/tag-cloud-pop-up.component'; import { TagCloudDataService, TagCloudDataTagEntry } from '../../../services/util/tag-cloud-data.service'; import { WsRoomService } from '../../../services/websockets/ws-room.service'; +import { CloudParameters, CloudTextStyle } from '../../../utils/cloud-parameters'; class CustomPosition implements Position { left: number; @@ -60,96 +60,8 @@ class TagComment implements CloudData { } } -const colorRegex = /rgba?\((\d+), (\d+), (\d+)(?:, (\d(?:\.\d+)?))?\)/; const transformationScaleKiller = /scale\([^)]*\)/; const transformationRotationKiller = /rotate\(([^)]*)\)/; -type DefaultColors = [ - hover: string, - w1: string, - w2: string, - w3: string, - w4: string, - w5: string, - w6: string, - w7: string, - w8: string, - w9: string, - w10: string, - background: string -]; -const defaultColors: DefaultColors = [ - 'var(--secondary, greenyellow)', - '#c1c1c1', - '#d98e49', - '#ccca3c', - '#83e761', - '#3accd4', - '#54a1e9', - '#3a44ee', - '#9725eb', - '#e436c7', - '#ff0000', - 'var(--background, black)' -]; - -const getResolvedDefaultColors = (): string[] => { - const elem = document.createElement('p'); - elem.style.display = 'none'; - document.body.appendChild(elem); - const results = []; - for (const color of defaultColors) { - elem.style.backgroundColor = 'rgb(0, 0, 0)'; - elem.style.backgroundColor = color; - const result = window.getComputedStyle(elem).backgroundColor.match(colorRegex); - const r = parseInt(result[1], 10); - const g = parseInt(result[2], 10); - const b = parseInt(result[3], 10); - results.push(`#${((r * 256 + g) * 256 + b).toString(16).padStart(6, '0')}`); - } - elem.remove(); - return results; -}; - -const getDefaultCloudParameters = (): CloudParameters => { - const resDefaultColors = getResolvedDefaultColors(); - const minValue = window.innerWidth < window.innerHeight ? window.innerWidth : window.innerHeight; - const isMobile = minValue < 500; - const elements = isMobile ? 5 : 10; - const weightSettings: CloudWeightSettings = [ - { maxVisibleElements: elements, color: resDefaultColors[1], rotation: 0, allowManualTagNumber: true }, - { maxVisibleElements: elements, color: resDefaultColors[2], rotation: 0, allowManualTagNumber: true }, - { maxVisibleElements: elements, color: resDefaultColors[3], rotation: 0, allowManualTagNumber: true }, - { maxVisibleElements: elements, color: resDefaultColors[4], rotation: 0, allowManualTagNumber: true }, - { maxVisibleElements: elements, color: resDefaultColors[5], rotation: 0, allowManualTagNumber: true }, - { maxVisibleElements: elements, color: resDefaultColors[6], rotation: 0, allowManualTagNumber: true }, - { maxVisibleElements: elements, color: resDefaultColors[7], rotation: 0, allowManualTagNumber: true }, - { maxVisibleElements: elements, color: resDefaultColors[8], rotation: 0, allowManualTagNumber: true }, - { maxVisibleElements: elements, color: resDefaultColors[9], rotation: 0, allowManualTagNumber: true }, - { maxVisibleElements: elements, color: resDefaultColors[10], rotation: 0, allowManualTagNumber: true }, - ]; - const mapValue = (current: number, minInputValue: number, maxInputValue: number, minOut: number, maxOut: number) => { - const value = (current - minInputValue) * (maxOut - minOut) / (maxInputValue - minInputValue) + minOut; - return Math.min(maxOut, Math.max(minOut, value)); - }; - return { - fontFamily: 'Dancing Script', - fontWeight: 'normal', - fontStyle: 'normal', - fontSize: '10px', - backgroundColor: resDefaultColors[11], - fontColor: resDefaultColors[0], - fontSizeMin: mapValue(minValue, 375, 750, 125, 200), - fontSizeMax: mapValue(minValue, 375, 1500, 300, 900), - hoverScale: mapValue(minValue, 375, 1500, 1.4, 2), - hoverTime: 1, - hoverDelay: 0.4, - delayWord: 100, - randomAngles: false, - sortAlphabetically: false, - textTransform: CloudTextStyle.capitalized, - cloudWeightSettings: weightSettings - }; -}; @Component({ selector: 'app-tag-cloud', @@ -186,6 +98,7 @@ export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit { headerInterface = null; themeSubscription = null; createCommentWrapper: CreateCommentWrapper = null; + question = ''; private _currentSettings: CloudParameters; private _subscriptionCommentlist = null; private _subscriptionRoom = null; @@ -213,22 +126,13 @@ export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit { this.translateService.use(lang); }); this._currentSettings = TagCloudComponent.getCurrentCloudParameters(); + this.question = this._currentSettings.question; this._calcCanvas = document.createElement('canvas'); this._calcRenderContext = this._calcCanvas.getContext('2d'); } private static getCurrentCloudParameters(): CloudParameters { - const jsonData = localStorage.getItem('tagCloudConfiguration'); - const temp: CloudParameters = jsonData != null ? JSON.parse(jsonData) : null; - const elem = getDefaultCloudParameters(); - if (temp != null) { - for (const key of Object.keys(elem)) { - if (temp[key] !== undefined) { - elem[key] = temp[key]; - } - } - } - return elem; + return CloudParameters.currentParameters; } ngOnInit(): void { @@ -323,11 +227,11 @@ export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit { } get currentCloudParameters(): CloudParameters { - return cloneParameters(this._currentSettings); + return new CloudParameters(this._currentSettings); } setCloudParameters(parameters: CloudParameters, save = true): void { - parameters = cloneParameters(parameters); + parameters = new CloudParameters(parameters); const updateIntensity = this.calcUpdateIntensity(parameters); this._currentSettings = parameters; this.zoomOnHoverOptions.delay = parameters.hoverDelay; @@ -342,12 +246,14 @@ export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit { } } if (save) { - localStorage.setItem('tagCloudConfiguration', JSON.stringify(parameters)); + CloudParameters.currentParameters = parameters; } } resetColorsToTheme() { - this.setCloudParameters(getDefaultCloudParameters(), true); + const param = new CloudParameters(); + param.resetToDefault(); + this.setCloudParameters(param, true); } onResize(event: UIEvent): any { @@ -507,6 +413,8 @@ export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit { 'background-color: ' + this._currentSettings.backgroundColor + '; }'); customTagCloudStyles.sheet.insertRule('.spacyTagCloudContainer { ' + 'background-color: ' + this._currentSettings.backgroundColor + '; }'); + customTagCloudStyles.sheet.insertRule(':root { ' + + '--tag-cloud-color: ' + this._currentSettings.fontColor + '; }'); } /** diff --git a/src/app/components/shared/tag-cloud/tag-cloud.interface.ts b/src/app/components/shared/tag-cloud/tag-cloud.interface.ts deleted file mode 100644 index 5ede58cb1..000000000 --- a/src/app/components/shared/tag-cloud/tag-cloud.interface.ts +++ /dev/null @@ -1,125 +0,0 @@ -export interface CloudWeightSetting { - /** - * This field specifies how many elements of this weight are displayed. - * - * A number less than zero means that all elements of the weight are displayed. - */ - maxVisibleElements: number; - /** - * CSS color property value. - */ - color: string; - /** - * Rotation of each html element in degrees. - * - * null indicates random rotation. - */ - rotation: number; - allowManualTagNumber: boolean; -} - -export type CloudWeightSettings = [ - CloudWeightSetting, // w1 - CloudWeightSetting, // w2 - CloudWeightSetting, // w3 - CloudWeightSetting, // w4 - CloudWeightSetting, // w5 - CloudWeightSetting, // w6 - CloudWeightSetting, // w7 - CloudWeightSetting, // w8 - CloudWeightSetting, // w9 - CloudWeightSetting // w10 -]; - -export enum CloudTextStyle { - normal, - lowercase, - capitalized, - uppercase -} - -export interface CloudParameters { - /** - * The general font family for the tag cloud - */ - fontFamily: string; - /** - * The general font style for the tag cloud - */ - fontStyle: string; - /** - * The general font weight for the tag cloud - */ - fontWeight: string; - /** - * The general font size for the tag cloud - */ - fontSize: string; - /** - * Background color of the Tag-cloud - */ - backgroundColor: string; - /** - * Color when hovering over the elements - */ - fontColor: string; - /** - * Percentage values for the weight classes, by interpolation all classes are filled with values - */ - fontSizeMin: number; - /** - * Percentage values fot the weight classes, by interpolation all classes are filled with values - */ - fontSizeMax: number; - /** - * Describes scaling when hovering - */ - hoverScale: number; - /** - * Time for hovering in ms - */ - hoverTime: number; - /** - * Time before hover animation starts in ms - */ - hoverDelay: number; - /** - * Time for delay in ms between each word during build-up - */ - delayWord: number; - /** - * Enables random angles - */ - randomAngles: boolean; - /** - * Sorts the cloud alphabetical. - */ - sortAlphabetically: boolean; - /** - * Custom CSS text transform setting - */ - textTransform: CloudTextStyle; - /** - * Array of settings for each weight group. - */ - cloudWeightSettings: CloudWeightSettings; -} - -const clone = (elem: any): any => { - if (Array.isArray(elem)) { - const copy = new Array(elem.length); - for (let i = 0; i < elem.length; i++) { - copy[i] = clone(elem[i]); - } - return copy; - } else if (elem instanceof Object) { - const copy = {}; - for (const key of Object.keys(elem)) { - copy[key] = clone(elem[key]); - } - return copy; - } - return elem; -}; - -export const cloneParameters = (parameters: CloudParameters) => clone(parameters); diff --git a/src/app/services/util/tag-cloud-data.service.ts b/src/app/services/util/tag-cloud-data.service.ts index cc25746a6..fa0f425e0 100644 --- a/src/app/services/util/tag-cloud-data.service.ts +++ b/src/app/services/util/tag-cloud-data.service.ts @@ -4,11 +4,11 @@ import { BehaviorSubject, Observable, Subscription } from 'rxjs'; 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 { RoomDataService } from './room-data.service'; import { SpacyKeyword } from '../http/spacy.service'; import { UserRole } from '../../models/user-roles.enum'; +import { CloudParameters } from '../../utils/cloud-parameters'; export interface TagCloudDataTagEntry { weight: number; diff --git a/src/app/utils/cloud-parameters.ts b/src/app/utils/cloud-parameters.ts new file mode 100644 index 000000000..f9dcd2e9a --- /dev/null +++ b/src/app/utils/cloud-parameters.ts @@ -0,0 +1,154 @@ +export interface CloudWeightSetting { + maxVisibleElements: number; + color: string; + rotation: number; + allowManualTagNumber: boolean; +} + +export type CloudWeightSettings = [ + weight1: CloudWeightSetting, + weight2: CloudWeightSetting, + weight3: CloudWeightSetting, + weight4: CloudWeightSetting, + weight5: CloudWeightSetting, + weight6: CloudWeightSetting, + weight7: CloudWeightSetting, + weight8: CloudWeightSetting, + weight9: CloudWeightSetting, + weight10: CloudWeightSetting +]; + +export enum CloudTextStyle { + normal, + lowercase, + capitalized, + uppercase +} + +const colorRegex = /rgba?\((\d+), (\d+), (\d+)(?:, (\d(?:\.\d+)?))?\)/; + +export class CloudParameters { + + static get currentParameters(): CloudParameters { + const jsonData = localStorage.getItem('tagCloudConfiguration'); + const temp = jsonData != null ? JSON.parse(jsonData) : null; + const elem = new CloudParameters(); + elem.resetToDefault(); + if (temp != null) { + for (const key of Object.keys(elem)) { + if (temp[key] !== undefined) { + elem[key] = temp[key]; + } + } + } + return elem; + } + + static set currentParameters(parameters: CloudParameters) { + localStorage.setItem('tagCloudConfiguration', JSON.stringify(parameters)); + } + + fontFamily: string; + fontStyle: string; + fontWeight: string; + fontSize: string; + backgroundColor: string; + fontColor: string; + fontSizeMin: number; + fontSizeMax: number; + hoverScale: number; + hoverTime: number; + hoverDelay: number; + delayWord: number; + randomAngles: boolean; + sortAlphabetically: boolean; + question: string; + textTransform: CloudTextStyle; + cloudWeightSettings: CloudWeightSettings; + + constructor(obj?: any) { + if (obj) { + this.fontFamily = obj.fontFamily; + this.fontStyle = obj.fontStyle; + this.fontWeight = obj.fontWeight; + this.fontSize = obj.fontSize; + this.backgroundColor = obj.backgroundColor; + this.fontColor = obj.fontColor; + this.fontSizeMin = obj.fontSizeMin; + this.fontSizeMax = obj.fontSizeMax; + this.hoverScale = obj.hoverScale; + this.hoverTime = obj.hoverTime; + this.hoverDelay = obj.hoverDelay; + this.delayWord = obj.delayWord; + this.randomAngles = obj.randomAngles; + this.sortAlphabetically = obj.sortAlphabetically; + this.question = obj.question; + this.textTransform = obj.textTransform; + this.cloudWeightSettings = [ + { ...obj.cloudWeightSettings[0] }, + { ...obj.cloudWeightSettings[1] }, + { ...obj.cloudWeightSettings[2] }, + { ...obj.cloudWeightSettings[3] }, + { ...obj.cloudWeightSettings[4] }, + { ...obj.cloudWeightSettings[5] }, + { ...obj.cloudWeightSettings[6] }, + { ...obj.cloudWeightSettings[7] }, + { ...obj.cloudWeightSettings[8] }, + { ...obj.cloudWeightSettings[9] } + ]; + } + } + + resetToDefault() { + const p = document.createElement('p'); + p.style.display = 'none'; + document.body.appendChild(p); + const minValue = window.innerWidth < window.innerHeight ? window.innerWidth : window.innerHeight; + const isMobile = minValue < 500; + const elements = isMobile ? 5 : 10; + this.fontFamily = 'Dancing Script'; + this.fontStyle = 'normal'; + this.fontWeight = 'normal'; + this.fontSize = '10px'; + this.backgroundColor = this.resolveColor(p, 'var(--background, black)'); + this.fontColor = this.resolveColor(p, 'var(--secondary, greenyellow)'); + this.fontSizeMin = this.mapValue(minValue, 375, 750, 125, 200); + this.fontSizeMax = this.mapValue(minValue, 375, 1500, 300, 900); + this.hoverScale = this.mapValue(minValue, 375, 1500, 1.4, 2); + this.hoverTime = 1; + this.hoverDelay = 0.4; + this.delayWord = 100; + this.randomAngles = false; + this.sortAlphabetically = false; + this.question = ''; + this.textTransform = CloudTextStyle.capitalized; + this.cloudWeightSettings = [ + { maxVisibleElements: elements, color: '#c1c1c1', rotation: 0, allowManualTagNumber: true }, + { maxVisibleElements: elements, color: '#d98e49', rotation: 0, allowManualTagNumber: true }, + { maxVisibleElements: elements, color: '#ccca3c', rotation: 0, allowManualTagNumber: true }, + { maxVisibleElements: elements, color: '#83e761', rotation: 0, allowManualTagNumber: true }, + { maxVisibleElements: elements, color: '#3accd4', rotation: 0, allowManualTagNumber: true }, + { maxVisibleElements: elements, color: '#54a1e9', rotation: 0, allowManualTagNumber: true }, + { maxVisibleElements: elements, color: '#3a44ee', rotation: 0, allowManualTagNumber: true }, + { maxVisibleElements: elements, color: '#9725eb', rotation: 0, allowManualTagNumber: true }, + { maxVisibleElements: elements, color: '#e436c7', rotation: 0, allowManualTagNumber: true }, + { maxVisibleElements: elements, color: '#ff0000', rotation: 0, allowManualTagNumber: true }, + ]; + p.remove(); + } + + private resolveColor(element: HTMLParagraphElement, color: string): string { + element.style.backgroundColor = 'rgb(0, 0, 0)'; + element.style.backgroundColor = color; + const result = window.getComputedStyle(element).backgroundColor.match(colorRegex); + const r = parseInt(result[1], 10); + const g = parseInt(result[2], 10); + const b = parseInt(result[3], 10); + return `#${((r * 256 + g) * 256 + b).toString(16).padStart(6, '0')}`; + } + + private mapValue(current: number, minInput: number, maxInput: number, minOut: number, maxOut: number) { + const value = (current - minInput) * (maxOut - minOut) / (maxInput - minInput) + minOut; + return Math.min(maxOut, Math.max(minOut, value)); + } +} diff --git a/src/app/utils/CreateCommentWrapper.ts b/src/app/utils/create-comment-wrapper.ts similarity index 97% rename from src/app/utils/CreateCommentWrapper.ts rename to src/app/utils/create-comment-wrapper.ts index f7d3b3296..4ad3d4ce1 100644 --- a/src/app/utils/CreateCommentWrapper.ts +++ b/src/app/utils/create-comment-wrapper.ts @@ -6,7 +6,6 @@ import { User } from '../models/user'; import { Room } from '../models/room'; import { Comment } from '../models/comment'; import { NotificationService } from '../services/util/notification.service'; -import { UserRole } from '../models/user-roles.enum'; import { CommentService } from '../services/http/comment.service'; export class CreateCommentWrapper { diff --git a/src/assets/i18n/home/de.json b/src/assets/i18n/home/de.json index 53ff56bfc..8ca076e58 100644 --- a/src/assets/i18n/home/de.json +++ b/src/assets/i18n/home/de.json @@ -46,16 +46,26 @@ "motd-mark-read": "Gelesen" }, "content": { - "topic-cloud-content": "Weiter zur Topic-Cloud mit den aktuellen Filtern?", "cancel": "Abbrechen", "continue": "Weiter", "reset": "Zurücksetzen", - "continue-with-all-questions" : "Mit allen Fragen der Sitzung", - "continue-with-all-questions-short" : "Alle Fragen", - "continue-with-current-questions": "Mit aktueller Fragenliste", - "continue-with-current-questions-short": "Fragenliste", - "continue-with-all-questions-from-now": "Brainstorming: Wolke aus den Eingaben ab jetzt", - "continue-with-all-questions-from-now-short": "Brainstorming" + "brainstorming-question": "Brainstorming-Frage", + "tag-cloud-title": "Themenwolke", + "tag-cloud-info": "Die Wortwolke in »frag.jetzt« dient als semantischer Filter: Je größer die Schrift, desto häufiger wurde das Wort grammatikalisch in den Fragen verwendet oder als Schlüsselwort vergeben. Auch die Bewertungen der Fragen beeinflussen die Schriftgröße.", + "tag-cloud-source-title": "Wähle, welche Fragen angezeigt werden sollen:", + "tag-cloud-source-verified-by-user": "nur Fragen, die mit Stichwörtern versehen wurden", + "tag-cloud-source-verified-by-user-short": "nur Fragen mit Stichwörtern", + "tag-cloud-source-analyzed": "nur Fragen, die grammatikalisch analysiert wurden", + "tag-cloud-source-analyzed-short": "nur analysierte Fragen", + "tag-cloud-source-all": "alle Fragen", + "tag-cloud-questions-title": "Wähle, welche Fragenmenge berücksichtigt werden soll:", + "tag-cloud-questions-all": "alle Fragen der Sitzung", + "tag-cloud-questions-all-short": "alle Fragen", + "tag-cloud-questions-current-filtered": "aktuelle Fragen", + "tag-cloud-questions-brainstorming": "Brainstorming: Fragen, die ab jetzt gestellt werden", + "tag-cloud-questions-brainstorming-short": "Brainstorming", + "tag-cloud-description": "Sobald die Wolke erscheint, fahre mit der Maus über ein Wort, um die Anzahl der involvierten Fragen, Fragensteller*innen und Bewertungen zu sehen. Klicke auf das Wort, um alle zugehörigen Fragen anzuzeigen.", + "tag-cloud-create": "Wortwolke erstellen" }, "header": { "abort": "Abbrechen", diff --git a/src/assets/i18n/home/en.json b/src/assets/i18n/home/en.json index b2c4695e7..1496a7294 100644 --- a/src/assets/i18n/home/en.json +++ b/src/assets/i18n/home/en.json @@ -138,16 +138,26 @@ "invalid-char-shortid": "This key code contains invalid characters." }, "content": { - "topic-cloud-content": "Continue to the Topic Cloud with the current filters?", "cancel": "Cancel", "continue": "Continue", "reset": "Reset", - "continue-with-all-questions": "Continue with all questions of the session", - "continue-with-all-questions-short": "With all questions", - "continue-with-current-questions": "Continue with current question list", - "continue-with-current-questions-short": "With question list", - "continue-with-all-questions-from-now": "Continue with questions that will be asked from now on", - "continue-with-all-questions-from-now-short": "With questions from now on" + "brainstorming-question": "Brainstorming question", + "tag-cloud-title": "Topic cloud", + "tag-cloud-info": "The word cloud in »frag.jetzt« serves as a semantic filter: the larger the font, the more often the word was used grammatically in the questions or assigned as a keyword. The ratings of the questions also influence the font size.", + "tag-cloud-source-title": "Choose which questions to display:", + "tag-cloud-source-verified-by-user": "only questions that have been tagged with keywords", + "tag-cloud-source-verified-by-user-short": "only tagged questions", + "tag-cloud-source-analyzed": "only questions that have been analyzed grammatically", + "tag-cloud-source-analyzed-short": "only analyzed questions", + "tag-cloud-source-all": "all questions", + "tag-cloud-questions-title": "Choose which set of questions to include:", + "tag-cloud-questions-all": "all questions of the session", + "tag-cloud-questions-all-short": "all questions", + "tag-cloud-questions-current-filtered": "current questions", + "tag-cloud-questions-brainstorming": "Brain storming session: questions that will be asked from now on", + "tag-cloud-questions-brainstorming-short": "Brain storming session", + "tag-cloud-description": "Once the cloud appears, hover over a word to see the number of questions, questioners, and ratings involved. Click on the word to see all associated questions.", + "tag-cloud-create": "Create word cloud" }, "imprint": { "cancel": "Close", -- GitLab