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 e624c53ff3199d6849b5a70c467a9787a48459dd..6a920d6894ebbfa54966e72a5903e14ddbcfbacf 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 @@ -1,13 +1,17 @@ <div id="wholeDialog" class="drawer-container" fxLayout="column" fxLayoutGap="5px"> - <div class="cloud-configuration-form" fxLayout="column" *ngIf="!extendedView"> + <div class="cloud-configuration-form" fxLayout="column" *ngIf="!extendedView && !cleanUpView"> <h2>{{'tag-cloud-config.title' | translate}}</h2> - <div class="input-row special-settings" fxLayout="row" fxLayoutGap="5px" fxLayout.xs="column"> + <div class="input-row special-settings demo-cloud-settings" fxLayout="row" fxLayoutGap="5px" fxLayout.xs="column"> <div class="input-row" fxLayout="row" fxLayoutGap="5px" fxLayout.xs="column"> - <mat-slide-toggle fxFlex (change)="parent.demoToggle()" [checked]="parent.isDemo" - [ngModelOptions]="{standalone: true}">Demo Cloud</mat-slide-toggle> + <mat-slide-toggle fxFlex (change)="parent.dataManager.demoActive = !parent.dataManager.demoActive" + [checked]="parent.dataManager.demoActive" + [ngModelOptions]="{standalone: true}">Demo Cloud</mat-slide-toggle> </div> - <div class="button-row weight-class-button"> - <button (click)="toggleExtendedView()" mat-button class="primary">{{'tag-cloud-config.extended-btn' | translate}}</button> + </div> + <div class="input-row specialButtons special-settings" fxLayout="row" fxLayoutGap="5px" fxLayout.xs="column"> + <div class="button-row weight-class-buttons"> + <button (click)="toggleExtendedView()" mat-button class="primary" id="extendedViewButton">{{'tag-cloud-config.extended-btn' | translate}}</button> + <button (click)="toggleCleanupView()" mat-button class="primary">Tag-Cleanup</button> </div> </div> <h3>{{'tag-cloud-config.general' | translate}}</h3> @@ -98,6 +102,7 @@ </div> <h2>{{'tag-cloud-config.weight-class-settings' | translate}}</h2> <div *ngFor="let weightClass of weightClasses" class="weight-class-setting"> + <div class ="weight-class-setting-content"> <h3 class="weight-class-heading">{{'tag-cloud-config.weight-class' | translate}} {{weightClasses.indexOf(weightClass) + 1}}</h3> <div class="input-row" fxLayout="column" fxLayoutGap="5px"> <div class="input-row" fxLayout="column" fxLayoutGap="5px"> @@ -113,12 +118,51 @@ </div> </div> </div> - <div class="input-row" fxLayout="column" fxLayoutGap="5px"> - <mat-label class="label-text">{{'tag-cloud-config.weight-number' | translate}}</mat-label> - <mat-slider [value]="weightClass.maxTagNumber" min="0" max="30" step="1" + + <div class="input-row" fxLayout="column" fxLayoutGap="5px" *ngIf="weightClass.actualTagNumber > 0 && !parent.dataManager.demoActive"> + <mat-label class="label-text" >{{'tag-cloud-config.weight-number' | translate}}</mat-label> + <mat-slider [value]="weightClass.maxTagNumber" min="0" [max]="weightClass.actualTagNumber" step="1" [(ngModel)]="weightClass.maxTagNumber" [ngModelOptions]="{standalone: true}" (change)="valueChanged()" [thumbLabel]="true" matTooltip="{{'tag-cloud-config.weight-number-tooltip' | translate}}"></mat-slider> </div> + <div class="input-row" fxLayout="column" fxLayoutGap="5px"> + <mat-label class="label-text">{{'tag-cloud-config.rotate-weight' | translate}}</mat-label> + <mat-slider [value]="weightClass.rotationAngle" min="-180" max="180" step="1" + [(ngModel)]="weightClass.rotationAngle" [ngModelOptions]="{standalone: true}" (change)="valueChanged()" + [thumbLabel]="true" matTooltip="{{'tag-cloud-config.weight-number-tooltip' | translate}}"></mat-slider> + </div> + </div> + </div> + </div> + <div class="cloud-configuration-form" fxLayout="column" *ngIf="cleanUpView"> + <div class="weight-class-button button-row"> + <button (click)="toggleCleanupView()" mat-button class="primary">{{'tag-cloud-config.back-btn' | translate}}</button> </div> + <h2>{{'tag-cloud-config.cleanUpView' | translate}}</h2> + <div class="input-row special-settings automatic-spelling" fxLayout="row" fxLayoutGap="5px" fxLayout.xs="column"> + <div class="input-row" fxLayout="row" fxLayoutGap="5px" fxLayout.xs="column"> + <mat-slide-toggle matTooltip="{{'tag-cloud-config.automatic-spelling-tooltip' | translate}}" [(ngModel)]="cloudParameters.checkSpelling" [checked]="cloudParameters.checkSpelling" [ngModelOptions]="{standalone: true}" (change)="valueChanged()">{{'tag-cloud-config.automatic-spelling' | translate}}</mat-slide-toggle> + </div> + </div> + <div class="input-row special-settings automatic-spelling" fxLayout="row" fxLayoutGap="5px" fxLayout.xs="column"> + <mat-radio-group matTooltip="{{'tag-cloud-config.notation-tooltip' | translate}}" aria-label="Notation:"> {{'tag-cloud-config.notation' | translate}} + <div><mat-radio-button value="1" (change)="textStyleChanged(1)" [checked]="cloudParameters.textTransform == 1">{{'tag-cloud-config.lowerCase' | translate}}</mat-radio-button> </div> + <div><mat-radio-button value="2" (change)="textStyleChanged(2)" [checked]="cloudParameters.textTransform == 2">{{'tag-cloud-config.capitalization' | translate}}</mat-radio-button> </div> + <div><mat-radio-button value="0" (change)="textStyleChanged(0)" [checked]="cloudParameters.textTransform == 0">{{'tag-cloud-config.standard' | translate}}</mat-radio-button> </div> + </mat-radio-group> + </div> + <div class="input-row special-settings alphabetical-sorting" fxLayout="row" fxLayoutGap="5px" fxLayout.xs="column"> + <div class="input-row" fxLayout="row" fxLayoutGap="5px" fxLayout.xs="column"> + <mat-slide-toggle matTooltip="{{'tag-cloud-config.alphabetical-sorting-tooltip' | translate}}" [(ngModel)]="cloudParameters.sortAlphabetically" [checked]="cloudParameters.sortAlphabetically" [ngModelOptions]="{standalone: true}"(change)="valueChanged()">{{'tag-cloud-config.alphabetical-sorting' | translate}}</mat-slide-toggle> + </div> + </div> + <div class="input-row" fxLayout="column" fxLayoutGap="5px"> + <mat-form-field fxFlex fxLayout.xs="column"> + <mat-label>{{'tag-cloud-config.highestWeight' | translate}}</mat-label> + <input #highestWeight matTooltip="{{'tag-cloud-config.highestWeight-tooltip' | translate}}" [value]="cloudParameters.highestWeight" matInput type="number" + [(ngModel)]="cloudParameters.highestWeight" [ngModelOptions]="{standalone: true}" (change)="valueChanged()" + min="0" max="150" step="1"/> + </mat-form-field> + </div> </div> </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 53be4b33ba8bb45d14fef1af8016e198354791c5..faaf8bd4ab99c5b9349ad95b3e931b10116e778f 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 @@ -75,6 +75,7 @@ mat-divider { display: block; position: relative; right: 40px; + pointer-events: none; } ::ng-deep .mat-slide-toggle-thumb{ @@ -152,6 +153,7 @@ mat-divider { ::ng-deep.custom-color-picker{ opacity: 0; z-index: -1; + cursor: pointer; } .custom-color-picker-text{ position: absolute; @@ -167,7 +169,7 @@ mat-divider { opacity: 0.2; } -.weight-class-setting { +.weight-class-setting-content { border-top: 3px solid var(--primary); } @@ -176,12 +178,24 @@ mat-divider { color: var(--on-dialog); } -.weight-class-button { +#extendedViewButton { + margin-right: 10px; +} + +.demo-cloud-settings { + border-bottom: none; +} + +.weight-class-buttons { margin-bottom: 0; } .special-settings{ - border-top: 2px solid var(--primary); + border-top: none; border-bottom: 2px solid var(--primary); padding: 10px 0; +} + +#rotation{ + margin-top: 10px; } \ No newline at end of file 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 c4d51096b37ee8de9e1923ccb5f6cf039af0d1b3..94f2117a5e6498a1234e130d54c39cc4b0d2b192 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,7 +1,8 @@ -import { Component, Input, OnInit, SimpleChanges } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { TagCloudComponent } from '../../tag-cloud/tag-cloud.component'; -import { CloudParameters } from '../../tag-cloud/tag-cloud.interface'; +import { TagCloudMetaDataCount } from '../../tag-cloud/tag-cloud.data-manager'; +import { CloudParameters, CloudTextStyle } from '../../tag-cloud/tag-cloud.interface'; import { WeightClass } from './weight-class.interface'; @Component({ @@ -9,103 +10,170 @@ import { WeightClass } from './weight-class.interface'; templateUrl: './cloud-configuration.component.html', styleUrls: ['./cloud-configuration.component.scss'], }) -export class CloudConfigurationComponent implements OnInit{ +export class CloudConfigurationComponent implements OnInit { @Input() parent: TagCloudComponent; + CloudTextStyle: CloudTextStyle; cloudParameters: CloudParameters; defaultCloudParameters: CloudParameters; oldCloudParameters: CloudParameters; + countPerWeight: TagCloudMetaDataCount; extendedView: boolean; - weightClasses: WeightClass[]=[ - {maxTagNumber: 20, - tagColor: '#8800ff'}, - {maxTagNumber: 20, - tagColor: '#ff00ff'}, - {maxTagNumber: 17, - tagColor: '#ffea00'}, - {maxTagNumber: 15, - tagColor: '#00CC99'}, - {maxTagNumber: 12, - tagColor: '#00CC66'}, - {maxTagNumber: 10, - tagColor: '#0033FF'}, - {maxTagNumber: 8, - tagColor: '#CC0099'}, - {maxTagNumber: 7, - tagColor: '#FF3399'}, - {maxTagNumber: 6, - tagColor: '#FFFF00'}, - {maxTagNumber: 5, - tagColor: '#FF0000'}, + cleanUpView: boolean; + automaticSpelling: boolean; + lowerCase: boolean; + capitalization: boolean; + standard: boolean; + alphabeticalSorting: boolean; + rotation: number; + highestWeight: number; + weightClasses: WeightClass[] = [ + { + maxTagNumber: 20, + tagColor: '#8800ff', + actualTagNumber: 5, + rotationAngle: 0 + }, + { + maxTagNumber: 20, + tagColor: '#ff00ff', + actualTagNumber: 5, + rotationAngle: 0, + }, + { + maxTagNumber: 17, + tagColor: '#ffea00', + actualTagNumber: 5, + rotationAngle: 0 + }, + { + maxTagNumber: 15, + tagColor: '#00CC99', + actualTagNumber: 5, + rotationAngle: 0 + }, + { + maxTagNumber: 12, + tagColor: '#00CC66', + actualTagNumber: 5, + rotationAngle: 0 + }, + { + maxTagNumber: 10, + tagColor: '#0033FF', + actualTagNumber: 5, + rotationAngle: 0 + }, + { + maxTagNumber: 8, + tagColor: '#CC0099', + actualTagNumber: 5, + rotationAngle: 0 + }, + { + maxTagNumber: 7, + tagColor: '#FF3399', + actualTagNumber: 5, + rotationAngle: 0 + }, + { + maxTagNumber: 6, + tagColor: '#FFFF00', + actualTagNumber: 5, + rotationAngle: 0 + }, + { + maxTagNumber: 5, + tagColor: '#FF0000', + actualTagNumber: 5, + rotationAngle: 0 + }, ]; isTestCloud = false; - constructor(private translateService: TranslateService) {} + constructor(private translateService: TranslateService) { } ngOnInit() { this.translateService.use(localStorage.getItem('currentLang')); - this.cloudParameters = this.parent.getCurrentCloudParameters(); - this.defaultCloudParameters = this.parent.getCurrentCloudParameters(); - this.parseArrayToJsonWeightClasses(); + this.cloudParameters = this.parent.currentCloudParameters; + this.defaultCloudParameters = this.parent.currentCloudParameters; + this.parent.dataManager.getMetaData().subscribe((value)=>{ + this.countPerWeight = value.countPerWeight; + this.parseArrayToJsonWeightClasses(); + }); this.extendedView = false; + this.cleanUpView = false; + this.automaticSpelling = true; + this.lowerCase = true; + this.capitalization = false; + this.standard = false; + this.alphabeticalSorting = true; + this.rotation = 360; + this.highestWeight = 100; } - fontColorChanged(value: string){ + fontColorChanged(value: string) { this.cloudParameters.fontColor = value; this.valueChanged(); } - backgroundColorChanged(value: string){ + backgroundColorChanged(value: string) { this.cloudParameters.backgroundColor = value; this.valueChanged(); } - parseArrayToJsonWeightClasses(){ - this.cloudParameters.cloudWeightCount.forEach((element, i) => { - this.weightClasses[i].maxTagNumber = element; - }); - - this.cloudParameters.cloudWeightColor.forEach((element, i) => { - this.weightClasses[i].tagColor = element; + parseArrayToJsonWeightClasses() { + this.cloudParameters.cloudWeightSettings.forEach((element, i) => { + this.weightClasses[i].tagColor = element.color; + this.weightClasses[i].actualTagNumber = this.countPerWeight[i]; + this.weightClasses[i].rotationAngle = element.rotation; }); } - - parseJsonToArrayWeightClasses(){ + + parseJsonToArrayWeightClasses() { this.weightClasses.forEach((element, i) => { - this.cloudParameters.cloudWeightCount[i] = element.maxTagNumber; - this.cloudParameters.cloudWeightColor[i] = element.tagColor; + this.cloudParameters.cloudWeightSettings[i].maxVisibleElements = element.maxTagNumber; + this.cloudParameters.cloudWeightSettings[i].color = element.tagColor; + this.cloudParameters.cloudWeightSettings[i].rotation = element.rotationAngle; }); } - - valueChanged(){ + valueChanged() { this.parseJsonToArrayWeightClasses(); - this.parent.setCloudParameters(this.cloudParameters, false); + this.parent.setCloudParameters(this.cloudParameters, false); } - cancel(){ - this.parent.isDemo = true; - this.parent.demoToggle(); + cancel() { + this.parent.tagCloudDataManager.demoActive = false; this.parent.setCloudParameters(this.defaultCloudParameters); this.parent.configurationOpen = false; } - save(){ - this.parent.isDemo = true; - this.parent.demoToggle(); + save() { + this.parent.tagCloudDataManager.demoActive = false; this.parent.setCloudParameters(this.cloudParameters); this.parent.configurationOpen = false; } - toggleExtendedView(){ + toggleExtendedView() { + this.cleanUpView = false; this.extendedView = !this.extendedView; } + toggleCleanupView() { + this.cleanUpView = !this.cleanUpView; + this.extendedView = false; + } weightColorChanged(index: number, event: string): void { this.weightClasses[index].tagColor = event; this.valueChanged(); } + textStyleChanged(val: CloudTextStyle) { + this.cloudParameters.textTransform = val; + this.valueChanged(); + } + } diff --git a/src/app/components/shared/_dialogs/cloud-configuration/weight-class.interface.ts b/src/app/components/shared/_dialogs/cloud-configuration/weight-class.interface.ts index 771122ea4845ccb0e64b798ce5106a71dee01354..237959840512a99f0a5880fc6f98bfb51d58dcdc 100644 --- a/src/app/components/shared/_dialogs/cloud-configuration/weight-class.interface.ts +++ b/src/app/components/shared/_dialogs/cloud-configuration/weight-class.interface.ts @@ -1,4 +1,6 @@ export interface WeightClass { maxTagNumber: number; tagColor: string; -} \ No newline at end of file + actualTagNumber: number; + rotationAngle: number; +} diff --git a/src/app/components/shared/header/header.component.ts b/src/app/components/shared/header/header.component.ts index be8879e5208fc08588ae5330ec9f7afbf159f09c..c756da05480e64471a63ac955802e8fb18edf25e 100644 --- a/src/app/components/shared/header/header.component.ts +++ b/src/app/components/shared/header/header.component.ts @@ -23,7 +23,7 @@ import { MotdService } from '../../../services/http/motd.service'; import { TopicCloudFilterComponent } from '../_dialogs/topic-cloud-filter/topic-cloud-filter.component'; import { RoomService } from '../../../services/http/room.service'; import { Room } from '../../../models/room'; -import { TagCloudHeaderDataOverview } from '../tag-cloud/tag-cloud.interface'; +import { TagCloudMetaData } from '../tag-cloud/tag-cloud.data-manager'; @Component({ selector: 'app-header', @@ -43,7 +43,6 @@ export class HeaderComponent implements OnInit { commentsCountUsers = 0; commentsCountKeywords = 0; - constructor(public location: Location, private authenticationService: AuthenticationService, private notificationService: NotificationService, @@ -66,7 +65,7 @@ export class HeaderComponent implements OnInit { this.motdService.requestDialog(); }); }); - this.eventService.on<TagCloudHeaderDataOverview>('tagCloudHeaderDataOverview').subscribe(data => { + this.eventService.on<TagCloudMetaData>('tagCloudHeaderDataOverview').subscribe(data => { this.commentsCountQuestions = data.commentCount; this.commentsCountUsers = data.userCount; this.commentsCountKeywords = data.tagCount; 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 22a6bfdf975f1229946181553751bd0655e7abc3..16503d3c23d80a55096b785b95eb4c42cbaa7e14 100644 --- a/src/app/components/shared/tag-cloud/tag-cloud.component.html +++ b/src/app/components/shared/tag-cloud/tag-cloud.component.html @@ -16,7 +16,7 @@ [width]="options.width" [height]="options.height" [overflow]="options.overflow" - [delay]="options.delay" + [delay]="currentCloudParameters.delayWord" [randomizeAngle]="false" [zoomOnHover]="zoomOnHoverOptions" [realignOnResize]="false"> 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 410d9dc1ea6bba27a0a2891a736741fbf425d99a..f4a61776121d31497188f1b0dfcbdd6df9b8f2e4 100644 --- a/src/app/components/shared/tag-cloud/tag-cloud.component.ts +++ b/src/app/components/shared/tag-cloud/tag-cloud.component.ts @@ -1,14 +1,14 @@ -import { Component, OnInit, ViewChild, Input, AfterViewInit, OnDestroy } from '@angular/core'; +import { AfterViewInit, Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { CloudData, CloudOptions, Position, - ZoomOnHoverOptions, - TagCloudComponent as TCloudComponent + TagCloudComponent as TCloudComponent, + ZoomOnHoverOptions } from 'angular-tag-cloud-module'; import { CommentService } from '../../../services/http/comment.service'; -import { Result, SpacyService } from '../../../services/http/spacy.service'; +import { SpacyService } from '../../../services/http/spacy.service'; import { Comment } from '../../../models/comment'; import { LanguageService } from '../../../services/util/language.service'; import { TranslateService } from '@ngx-translate/core'; @@ -23,17 +23,17 @@ 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 { 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 { demoMap } from './demoData'; +import { TagCloudDataManager } from './tag-cloud.data-manager'; class CustomPosition implements Position { left: number; top: number; constructor(public relativeLeft: number, - public relativeTop: number) { + public relativeTop: number) { } updatePosition(width: number, height: number, text: string, style: CSSStyleDeclaration) { @@ -46,32 +46,16 @@ class CustomPosition implements Position { class TagComment implements CloudData { constructor(public color: string, - public external: boolean, - public link: string, - public position: Position, - public rotate: number, - public text: string, - public tooltip: string, - public weight: number) { + public external: boolean, + public link: string, + public position: Position, + public rotate: number, + public text: string, + public tooltip: string, + public weight: number) { } } -//CSS styles Array -type TagCloudStyleData = [ - string, // hover - string, // w1 - string, // w2 - string, // w3 - string, // w4 - string, // w5 - string, // w6 - string, // w7 - string, // w8 - string, // w9 - string, // w10 - string // background -]; - const colorRegex = /rgba?\((\d+), (\d+), (\d+)(?:, (\d(?:\.\d+)?))?\)/; const transformationScaleKiller = /scale\([^)]*\)/; const defaultColors: string[] = [ @@ -108,29 +92,20 @@ const getResolvedDefaultColors = (): string[] => { return results; }; -const setGlobalStyles = (styles: TagCloudStyleData): void => { - let customTagCloudStyles = document.getElementById('tagCloudStyles') as HTMLStyleElement; - if (!customTagCloudStyles) { - customTagCloudStyles = document.createElement('style'); - customTagCloudStyles.id = 'tagCloudStyles'; - document.head.appendChild(customTagCloudStyles); - } - const rules = customTagCloudStyles.sheet.cssRules; - for (let i = rules.length - 1; i >= 0; i--) { - customTagCloudStyles.sheet.deleteRule(i); - } - for (let i = 1; i <= 10; i++) { - customTagCloudStyles.sheet.insertRule('.spacyTagCloud > span.w' + i + ' { ' + styles[i] + ' }', rules.length); - customTagCloudStyles.sheet.insertRule('.spacyTagCloud > span.w' + i + ' > a { ' + styles[i] + ' }', rules.length); - } - customTagCloudStyles.sheet.insertRule('.spacyTagCloud > span:hover { ' + styles[0] + ' }', rules.length); - customTagCloudStyles.sheet.insertRule('.spacyTagCloud > span:hover > a { ' + styles[0] + ' }', rules.length); - customTagCloudStyles.sheet.insertRule('.spacyTagCloudContainer {' + styles[11] + '}', rules.length); -}; - const getDefaultCloudParameters = (): CloudParameters => { const resDefaultColors = getResolvedDefaultColors(); - const resWeightColors = resDefaultColors.slice(1, 11) as CloudWeightColor; + const weightSettings: CloudWeightSettings = [ + {maxVisibleElements: -1, color: resDefaultColors[1], rotation: 0}, + {maxVisibleElements: -1, color: resDefaultColors[2], rotation: 0}, + {maxVisibleElements: -1, color: resDefaultColors[3], rotation: 0}, + {maxVisibleElements: -1, color: resDefaultColors[4], rotation: 0}, + {maxVisibleElements: -1, color: resDefaultColors[5], rotation: 0}, + {maxVisibleElements: -1, color: resDefaultColors[6], rotation: 0}, + {maxVisibleElements: -1, color: resDefaultColors[7], rotation: 0}, + {maxVisibleElements: -1, color: resDefaultColors[8], rotation: 0}, + {maxVisibleElements: -1, color: resDefaultColors[9], rotation: 0}, + {maxVisibleElements: -1, color: resDefaultColors[10], rotation: 0}, + ]; return { backgroundColor: resDefaultColors[11], fontColor: resDefaultColors[0], @@ -141,8 +116,10 @@ const getDefaultCloudParameters = (): CloudParameters => { hoverDelay: 0.4, delayWord: 0, randomAngles: false, - cloudWeightColor: resWeightColors, - cloudWeightCount: [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1] + checkSpelling: true, + sortAlphabetically: true, + textTransform: CloudTextStyle.lowercase, + cloudWeightSettings: weightSettings }; }; @@ -153,11 +130,10 @@ const getDefaultCloudParameters = (): CloudParameters => { }) export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy { - @ViewChild(TCloudComponent, { static: false }) child: TCloudComponent; + @ViewChild(TCloudComponent, {static: false}) child: TCloudComponent; @Input() user: User; @Input() roomId: string; room: Room; - headerInterface = null; directSend = true; shortId: string; options: CloudOptions = { @@ -176,49 +152,70 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy { }; userRole: UserRole; data: TagComment[] = []; - sorted = false; debounceTimer = 0; lastDebounceTime = 0; configurationOpen = false; - randomizeAngle = false; isLoading = true; - dataSize: CloudWeightCount; - - //Demo Toggle - isDemo: boolean = false; - oldCloudData = []; + //Subscriptions + headerInterface = null; + themeSubscription = null; + readonly dataManager: TagCloudDataManager; + private _currentSettings: CloudParameters; constructor(private commentService: CommentService, - private spacyService: SpacyService, - private langService: LanguageService, - private translateService: TranslateService, - public dialog: MatDialog, - private notificationService: NotificationService, - public eventService: EventService, - private authenticationService: AuthenticationService, - private route: ActivatedRoute, - protected roomService: RoomService, - private themeService: ThemeService, - private wsCommentService: WsCommentServiceService) { + private spacyService: SpacyService, + private langService: LanguageService, + private translateService: TranslateService, + public dialog: MatDialog, + private notificationService: NotificationService, + public eventService: EventService, + private authenticationService: AuthenticationService, + private route: ActivatedRoute, + protected roomService: RoomService, + private themeService: ThemeService, + private wsCommentService: WsCommentServiceService) { this.roomId = localStorage.getItem('roomId'); this.langService.langEmitter.subscribe(lang => { this.translateService.use(lang); }); + this.dataManager = new TagCloudDataManager(wsCommentService, commentService); + this._currentSettings = TagCloudComponent.getCurrentCloudParameters(); + } + + 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 in Object.keys(elem)) { + if (temp[key] !== undefined) { + elem[key] = temp[key]; + } + } + } + return elem; } ngOnInit(): void { + this.updateGlobalStyles(); this.headerInterface = this.eventService.on<string>('navigate').subscribe(e => { if (e === 'createQuestion') { this.openCreateDialog(); } else if (e === 'topicCloudConfig') { this.configurationOpen = !this.configurationOpen; - this.demoToggle(); + this.dataManager.demoActive = !this.dataManager.demoActive; } else if (e === 'topicCloudAdministration') { this.dialog.open(TopicCloudAdministrationComponent, { minWidth: '50%' }); } }); + this.dataManager.getData().subscribe(_ => { + this.rebuildData(); + }); + this.dataManager.getMetaData().subscribe(data => { + this.eventService.broadcast('tagCloudHeaderDataOverview', data); + }); this.authenticationService.watchUser.subscribe(newUser => { if (newUser) { this.user = newUser; @@ -240,21 +237,13 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy { }); }); this.translateService.use(localStorage.getItem('currentLang')); - this.commentService.getFilteredComments(this.roomId).subscribe((comments: Comment[]) => { - this.analyse(comments); - }); - this.themeService.getTheme().subscribe(() => { + this.themeSubscription = this.themeService.getTheme().subscribe(() => { if (this.child) { setTimeout(() => { - this.setCloudParameters(this.getCurrentCloudParameters(), false); + this.setCloudParameters(TagCloudComponent.getCurrentCloudParameters(), false); }, 1); } }); - this.wsCommentService.getCommentStream(this.roomId).subscribe(e => { - this.commentService.getFilteredComments(this.roomId).subscribe((oldComments: Comment[]) => { - this.analyse(oldComments); - }); - }); } ngAfterViewInit() { @@ -263,132 +252,101 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy { ngOnDestroy() { document.getElementById('footer_rescale').style.display = 'block'; + this.headerInterface.unsubscribe(); + this.themeSubscription.unsubscribe(); + this.dataManager.deactivate(); } initTagCloud() { + this.dataManager.activate(this.roomId); + this.dataManager.updateDemoData(this.translateService); + this.setCloudParameters(TagCloudComponent.getCurrentCloudParameters(), false); setTimeout(() => { - this.setCloudParameters(this.getCurrentCloudParameters(), false); + this.redraw(); }); } - resetColorsToTheme() { - this.setCloudParameters(getDefaultCloudParameters()); + get tagCloudDataManager(): TagCloudDataManager { + return this.dataManager; } - getCurrentCloudParameters(): CloudParameters { - const jsonData = localStorage.getItem('tagCloudConfiguration'); - const elem: CloudParameters = jsonData != null ? JSON.parse(jsonData) : null; - return elem || getDefaultCloudParameters(); + get currentCloudParameters(): CloudParameters { + return cloneParameters(this._currentSettings); } - setCloudParameters(data: CloudParameters, save = true): void { - const arr = [data.fontColor, ...data.cloudWeightColor, data.backgroundColor]; - const fontRange = (data.fontSizeMax - data.fontSizeMin) / 10; - const styles = arr.map((e, i) => { - if (i > 10) { - return 'background-color: ' + e + ';'; - } else if (i > 0) { - return 'color: ' + e + '; font-size: ' + (data.fontSizeMin + fontRange * i).toFixed(0) + '%;'; - } else { - return 'color: ' + e + ';'; + setCloudParameters(parameters: CloudParameters, save = true): void { + parameters = cloneParameters(parameters); + const updateIntensity = this.calcUpdateIntensity(parameters); + this._currentSettings = parameters; + this.zoomOnHoverOptions.delay = parameters.hoverDelay; + this.zoomOnHoverOptions.scale = parameters.hoverScale; + this.zoomOnHoverOptions.transitionTime = parameters.hoverTime; + if (updateIntensity >= 1) { + this.updateGlobalStyles(); + } + if (updateIntensity >= 2) { + if (!this.dataManager.updateConfig(parameters)) { + this.rebuildData(); } - }); - setGlobalStyles(styles as TagCloudStyleData); - this.zoomOnHoverOptions.delay = data.hoverDelay; - this.zoomOnHoverOptions.scale = data.hoverScale; - this.zoomOnHoverOptions.transitionTime = data.hoverTime; - this.options.delay = data.delayWord; - this.randomizeAngle = data.randomAngles; - this.dataSize = data.cloudWeightCount; - this.rebuildData(); - this.updateTagCloud(); + } if (save) { - localStorage.setItem('tagCloudConfiguration', JSON.stringify(data)); + localStorage.setItem('tagCloudConfiguration', JSON.stringify(parameters)); } } + resetColorsToTheme() { + this.setCloudParameters(getDefaultCloudParameters()); + } + + onResize(event: UIEvent): any { this.updateTagCloud(); } - sortPositionsAlphabetically(sort: boolean): void { - if (!sort) { - this.sorted = false; - this.data.forEach(e => e.position = null); - return; - } - this.sorted = true; - if (!this.data.length) { - return; + rebuildData() { + const newElements = []; + const data = this.dataManager.currentData; + const countFiler = []; + for (let i = 0; i < 10; i++) { + countFiler.push(this._currentSettings.cloudWeightSettings[i].maxVisibleElements); } - this.data.sort((a, b) => a.text.localeCompare(b.text)); - const lines = Math.floor(Math.sqrt(this.data.length - 1) + 1); - const divided = Math.floor(this.data.length / lines); - let remainder = this.data.length - divided * lines; - for (let i = 0, line = 0; line < lines; line++) { - const size = divided + (--remainder >= 0 ? 1 : 0); - for (let k = 0; k < size; k++, i++) { - this.data[i].position = new CustomPosition((k + 1) / (size + 1), (line + 1) / (lines + 1)); + for (const [tag, tagData] of data) { + const amount = this.dataManager.demoActive ? 10 - tagData.adjustedWeight : 1; + for (let i = 0; i < amount; i++) { + const remaining = countFiler[tagData.adjustedWeight]; + if (remaining !== 0) { + if (remaining > 0) { + --countFiler[tagData.adjustedWeight]; + } + let rotation = this._currentSettings.cloudWeightSettings[tagData.adjustedWeight].rotation; + if (rotation === null || this._currentSettings.randomAngles) { + rotation = Math.floor(Math.random() * 30 - 15); + } + newElements.push(new TagComment(null, true, null, null, rotation, tag, 'TODO', tagData.weight)); + } } } - } - - 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>(); - res.words.filter(w => ['NE', 'NN', 'NMP', 'NNE'].indexOf(w.tag) >= 0).forEach(elem => { - 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, - this.randomizeAngle ? Math.floor(Math.random() * 30 - 15) : 0, key, - 'TODO', val)); + if (this._currentSettings.sortAlphabetically) { + const lines = Math.floor(Math.sqrt(newElements.length - 1) + 1); + const divided = Math.floor(newElements.length / lines); + let remainder = newElements.length - divided * lines; + for (let i = 0, line = 0; line < lines; line++) { + const size = divided + (--remainder >= 0 ? 1 : 0); + for (let k = 0; k < size; k++, i++) { + newElements[i].position = new CustomPosition((k + 1) / (size + 1), (line + 1) / (lines + 1)); + } } - ); - this.sortPositionsAlphabetically(this.sorted); - this.updateTagCloud(); - }); - } - - 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 + this.data = newElements; + setTimeout(() => { + this.updateTagCloud(true); + }, 2); } - updateTagCloud() { - let oldData = [].concat(this.data); - - if (this.isDemo) { - this.data = []; - demoMap.forEach((val, key) => { - this.data.push(new TagComment(null, - true, null, null, - this.randomizeAngle ? Math.floor(Math.random() * 30 - 15) : 0, key, - 'TODO', val)); - }); - } - + updateTagCloud(dataUpdated = false) { this.isLoading = true; - if (this.sorted && this.data.length) { - if (!this.child.cloudDataHtmlElements || !this.child.cloudDataHtmlElements.length) { + if (this._currentSettings.sortAlphabetically && this.data.length) { + if (dataUpdated || !this.child.cloudDataHtmlElements || !this.child.cloudDataHtmlElements.length) { this.child.reDraw(); } const width = this.child.calculatedWidth; @@ -459,6 +417,9 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy { } private redraw(): void { + if (this.child === undefined) { + return; + } this.lastDebounceTime = new Date().getTime(); this.child.reDraw(); this.isLoading = false; @@ -470,13 +431,80 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy { }); } - public demoToggle() { - if (this.isDemo) { - this.data = [].concat(this.oldCloudData); - } else { - this.oldCloudData = [].concat(this.data); + private updateGlobalStyles(): void { + let customTagCloudStyles = document.getElementById('tagCloudStyles') as HTMLStyleElement; + if (!customTagCloudStyles) { + customTagCloudStyles = document.createElement('style'); + customTagCloudStyles.id = 'tagCloudStyles'; + document.head.appendChild(customTagCloudStyles); } - this.isDemo = !this.isDemo; - this.updateTagCloud(); + const rules = customTagCloudStyles.sheet.cssRules; + for (let i = rules.length - 1; i >= 0; i--) { + customTagCloudStyles.sheet.deleteRule(i); + } + let textTransform = ''; + if (this._currentSettings.textTransform === CloudTextStyle.capitalized) { + textTransform = 'text-transform: uppercase;'; + } else if (this._currentSettings.textTransform === CloudTextStyle.lowercase) { + textTransform = 'text-transform: lowercase;'; + } + const fontRange = (this._currentSettings.fontSizeMax - this._currentSettings.fontSizeMin) / 10; + for (let i = 1; i <= 10; i++) { + customTagCloudStyles.sheet.insertRule('.spacyTagCloud > span.w' + i + + ', .spacyTagCloud > span.w' + i + ' > a { ' + + 'color: ' + this._currentSettings.cloudWeightSettings[i - 1].color + ';' + + textTransform + ' font-size: ' + + (this._currentSettings.fontSizeMin + fontRange * i).toFixed(0) + '%; }', rules.length); + } + customTagCloudStyles.sheet.insertRule('.spacyTagCloud > span:hover, .spacyTagCloud > span:hover > a { color: ' + + this._currentSettings.fontColor + '; }', rules.length); + customTagCloudStyles.sheet.insertRule('.spacyTagCloudContainer { background-color: ' + + this._currentSettings.backgroundColor + '; }', rules.length); } + + /** + * 0 = update nothing, + * 1 = update css, + * 2 = update data + */ + private calcUpdateIntensity(parameters: CloudParameters): number { + if (!this._currentSettings) { + return 2; + } + /* + hoverScale, hoverTime, hoverDelay, delayWord can be updated without refreshing + */ + const cssUpdates = ['backgroundColor', 'fontColor', 'fontSizeMin', 'fontSizeMax', 'textTransform']; + const dataUpdates = ['randomAngles', 'sortAlphabetically', 'checkSpelling']; + const cssWeightUpdates = ['color']; + const dataWeightUpdates = ['maxVisibleElements', 'rotation']; + //data updates + for (const key of dataUpdates) { + if (this._currentSettings[key] !== parameters[key]) { + return 2; + } + } + for (let i = 0; i < 10; i++) { + for (const key of dataWeightUpdates) { + if (this._currentSettings.cloudWeightSettings[i][key] !== parameters.cloudWeightSettings[i][key]) { + return 2; + } + } + } + //css updates + for (const key of cssUpdates) { + if (this._currentSettings[key] !== parameters[key]) { + return 1; + } + } + for (let i = 0; i < 10; i++) { + for (const key of cssWeightUpdates) { + if (this._currentSettings.cloudWeightSettings[i][key] !== parameters.cloudWeightSettings[i][key]) { + return 1; + } + } + } + return 0; + } + } diff --git a/src/app/components/shared/tag-cloud/tag-cloud.data-manager.ts b/src/app/components/shared/tag-cloud/tag-cloud.data-manager.ts new file mode 100644 index 0000000000000000000000000000000000000000..79ebcc754954ed89cf5588130404db354de193e0 --- /dev/null +++ b/src/app/components/shared/tag-cloud/tag-cloud.data-manager.ts @@ -0,0 +1,295 @@ +import { Comment } from '../../../models/comment'; +import { Observable, Subject } from 'rxjs'; +import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service'; +import { CommentService } from '../../../services/http/comment.service'; +import { CloudParameters } from './tag-cloud.interface'; +import { TranslateService } from '@ngx-translate/core'; + +export interface TagCloudDataTagEntry { + weight: number; + adjustedWeight: number; + cachedVoteCount: number; + comments: Comment[]; +} + +export interface TagCloudMetaData { + commentCount: number; + userCount: number; + tagCount: number; + minWeight: number; + maxWeight: number; + countPerWeight: TagCloudMetaDataCount; +} + +/** + * The key is a generated tag (out of all comments). + */ +export type TagCloudData = Map<string, TagCloudDataTagEntry>; + +export type TagCloudMetaDataCount = [ + number, // w1 + number, // w2 + number, // w3 + number, // w4 + number, // w5 + number, // w6 + number, // w7 + number, // w8 + number, // w9 + number // w10 +]; + +export enum TagCloudDataSupplyType { + fullText, + keywords, + keywordsAndFullText +} + +export enum TagCloudCalcWeightType { + byLength, + byVotes, + byLengthAndVotes +} + +export class TagCloudDataManager { + private _isDemoActive: boolean; + private _isAlphabeticallySorted: boolean; + private _dataBus: Subject<TagCloudData>; + private _metaDataBus: Subject<TagCloudMetaData>; + private _cachedData: TagCloudData; + private _wsCommentSubscription = null; + private _roomId = null; + private _supplyType = TagCloudDataSupplyType.keywordsAndFullText; + private _calcWeightType = TagCloudCalcWeightType.byLength; + private _lastFetchedData: TagCloudData = null; + private _lastFetchedComments: Comment[] = null; + private _lastMetaData: TagCloudMetaData = null; + private readonly _currentMetaData: TagCloudMetaData; + private _demoData: TagCloudData = null; + + constructor(private _wsCommentService: WsCommentServiceService, + private _commentService: CommentService) { + this._isDemoActive = false; + this._isAlphabeticallySorted = false; + this._dataBus = new Subject<TagCloudData>(); + this._currentMetaData = { + tagCount: 0, + commentCount: 0, + userCount: 0, + minWeight: 0, + maxWeight: 0, + countPerWeight: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + }; + this._metaDataBus = new Subject<TagCloudMetaData>(); + this._cachedData = null; + // Subscribe to own 'service' for caching + this._dataBus.asObservable().subscribe(data => { + this._cachedData = data; + }); + } + + activate(roomId: string): void { + if (this._wsCommentSubscription !== null) { + console.error('Tag cloud data manager was already activated!'); + return; + } + this._roomId = roomId; + this.onUpdateData(); + this._wsCommentSubscription = this._wsCommentService + .getCommentStream(this._roomId).subscribe(e => this.onUpdateData()); + } + + deactivate(): void { + if (this._wsCommentSubscription === null) { + console.error('Tag cloud data manager was already deactivated!'); + return; + } + this._wsCommentSubscription.unsubscribe(); + this._wsCommentSubscription = null; + } + + updateDemoData(translate: TranslateService): void { + translate.get('tag-cloud.demo-data-topic').subscribe(text => { + this._demoData = new Map<string, TagCloudDataTagEntry>(); + for (let i = 10; i >= 1; i--) { + this._demoData.set(text.replace('%d', '' + i), { + cachedVoteCount: 0, + comments: [], + weight: i, + adjustedWeight: i - 1 + }); + } + }); + } + + get metaData(): TagCloudMetaData { + return this._currentMetaData; + } + + get currentData(): TagCloudData { + return this._cachedData; + } + + get dataSupplyType(): TagCloudDataSupplyType { + return this._supplyType; + } + + set dataSupplyType(type: TagCloudDataSupplyType) { + if (this._supplyType !== type) { + this._supplyType = type; + this.rebuildTagData(); + } + } + + set weightCalcType(type: TagCloudCalcWeightType) { + if (type !== this._calcWeightType) { + this._calcWeightType = type; + this.rebuildTagData(); + } + } + + get weightCalcType(): TagCloudCalcWeightType { + return this._calcWeightType; + } + + get demoActive(): boolean { + return this._isDemoActive; + } + + set demoActive(active: boolean) { + if (active !== this._isDemoActive) { + this._isDemoActive = active; + if (this._isDemoActive) { + this._lastMetaData = { + ...this._currentMetaData, + countPerWeight: [...this._currentMetaData.countPerWeight] + }; + this._currentMetaData.minWeight = 1; + this._currentMetaData.maxWeight = 10; + this._currentMetaData.countPerWeight = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]; + } else if (this._lastMetaData !== null) { + for (const key of Object.keys(this._lastMetaData)) { + this._currentMetaData[key] = this._lastMetaData[key]; + } + this._lastMetaData = null; + } + this.reformatData(); + } + } + + get alphabeticallySorted(): boolean { + return this._isAlphabeticallySorted; + } + + updateConfig(parameters: CloudParameters): boolean { + if (parameters.sortAlphabetically !== this._isAlphabeticallySorted) { + this._isAlphabeticallySorted = parameters.sortAlphabetically; + this.reformatData(); + return true; + } + return false; + } + + getData(): Observable<TagCloudData> { + return this._dataBus.asObservable(); + } + + getMetaData(): Observable<TagCloudMetaData> { + return this._metaDataBus.asObservable(); + } + + reformatData(): void { + const current = this.getCurrentData(); + if (!current) { + console.error('Got no data for tag cloud!'); + return; + } + let newData: TagCloudData; + //TODO SORT + if (this._isAlphabeticallySorted) { + newData = new Map<string, TagCloudDataTagEntry>([...current] + .sort(([aTag], [bTag]) => aTag.localeCompare(bTag))); + } else { + newData = new Map<string, TagCloudDataTagEntry>([...current] + .sort(([_, aTagData], [__, bTagData]) => bTagData.weight - aTagData.weight)); + } + //TODO APPLY OTHER + this._dataBus.next(newData); + } + + private getCurrentData(): TagCloudData { + if (this._isDemoActive) { + return this._demoData; + } + return this._lastFetchedData; + } + + private onUpdateData(): void { + this._commentService.getFilteredComments(this._roomId).subscribe((comments: Comment[]) => { + this._lastFetchedComments = comments; + if (this._isDemoActive) { + this._lastMetaData.commentCount = comments.length; + } else { + this._currentMetaData.commentCount = comments.length; + } + this.rebuildTagData(); + }); + } + + private calculateWeight(tagData: TagCloudDataTagEntry): number { + switch (this._calcWeightType) { + case TagCloudCalcWeightType.byVotes: + return tagData.cachedVoteCount; + case TagCloudCalcWeightType.byLengthAndVotes: + return tagData.cachedVoteCount / 10.0 + tagData.comments.length; + default: + return tagData.comments.length; + } + } + + private rebuildTagData() { + const currentMeta = this._isDemoActive ? this._lastMetaData : this._currentMetaData; + const data: TagCloudData = new Map<string, TagCloudDataTagEntry>(); + const users = new Set<number>(); + for (const comment of this._lastFetchedComments) { + //TODO Check supply types + let keywords = comment.keywords || []; + for (const keyword of keywords) { + //TODO Check spelling + let current = data.get(keyword); + if (current === undefined) { + current = {cachedVoteCount: 0, comments: [], weight: 0, adjustedWeight: 0}; + data.set(keyword, current); + } + current.cachedVoteCount += comment.score; + current.comments.push(comment); + } + users.add(comment.userNumber); + } + let minWeight = null; + let maxWeight = null; + for (const value of data.values()) { + value.weight = this.calculateWeight(value); + minWeight = Math.min(value.weight, minWeight || value.weight); + maxWeight = Math.max(value.weight, maxWeight || value.weight); + } + //calculate weight counts and adjusted weights + const same = minWeight === maxWeight; + const span = maxWeight - minWeight; + currentMeta.countPerWeight = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (const value of data.values()) { + value.adjustedWeight = same ? 4 : Math.round((value.weight - minWeight) * 9.0 / span); + ++currentMeta.countPerWeight[value.adjustedWeight]; + } + this._lastFetchedData = data; + currentMeta.tagCount = data.size; + currentMeta.userCount = users.size; + currentMeta.minWeight = minWeight; + currentMeta.maxWeight = maxWeight; + this._metaDataBus.next(currentMeta); + if (!this._isDemoActive) { + this.reformatData(); + } + } + +} 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 b7dfb7ecfb2ae5378e981acb0cb4e6470ebbf34a..ca84784ddeec0566a7f302f303831c87aa7c5415 100644 --- a/src/app/components/shared/tag-cloud/tag-cloud.interface.ts +++ b/src/app/components/shared/tag-cloud/tag-cloud.interface.ts @@ -1,34 +1,40 @@ -export interface TagCloudHeaderDataOverview { - commentCount: number; - userCount: number; - tagCount: number; +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; } -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 CloudWeightSettings = [ + CloudWeightSetting, // w1 + CloudWeightSetting, // w2 + CloudWeightSetting, // w3 + CloudWeightSetting, // w4 + CloudWeightSetting, // w5 + CloudWeightSetting, // w6 + CloudWeightSetting, // w7 + CloudWeightSetting, // w8 + CloudWeightSetting, // w9 + CloudWeightSetting // 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 enum CloudTextStyle { + normal, + lowercase, + capitalized +} export interface CloudParameters { /** @@ -68,13 +74,38 @@ export interface CloudParameters { */ 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. + * Sorts the cloud alphabetical. + */ + sortAlphabetically: boolean; + /** + * Checks if the word is spelled correctly, if not, do not display it. */ - cloudWeightCount: CloudWeightCount; + checkSpelling: boolean; /** - * This array contains the CSS color property for each cloud weight + * Custom CSS text transform setting */ - cloudWeightColor: CloudWeightColor; + 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/http/comment.service.ts b/src/app/services/http/comment.service.ts index e664bfec77b74d00626e41124d1560c1101f1835..66ab1d529173dc3a6a5109e0bceba6970de095f3 100644 --- a/src/app/services/http/comment.service.ts +++ b/src/app/services/http/comment.service.ts @@ -109,7 +109,12 @@ export class CommentService extends BaseHttpService { externalFilters: {} }, httpOptions).pipe( map(commentList => { - return commentList.map(comment => this.parseUserNumber(comment)); + return commentList.map(comment => { + const newComment = this.parseUserNumber(comment); + // @ts-ignore + newComment.keywords = JSON.parse(newComment.keywords as string); + return newComment; + }); }), tap(_ => ''), catchError(this.handleError<Comment[]>('getComments', [])) @@ -238,4 +243,4 @@ export class CommentService extends BaseHttpService { hash = +userNumberString.substring(userNumberString.length - 4, userNumberString.length); return hash; } -} \ No newline at end of file +} diff --git a/src/assets/i18n/creator/de.json b/src/assets/i18n/creator/de.json index 348dfe6fbb8ae6317c92664965dabca116f76229..fdee800adcf2f7b2a9d15dcb3994b2974c33ad84 100644 --- a/src/assets/i18n/creator/de.json +++ b/src/assets/i18n/creator/de.json @@ -369,6 +369,22 @@ "weight-color": "Schriftfarbe", "weight-number": "max. Anzahl Schlüsselwörter", "weight-color-tooltip": "Auswahl der Schriftfarbe", - "weight-number-tooltip": "maximale Anzahl der Schlüsselwörter festlegen" + "weight-number-tooltip": "maximale Anzahl der Schlüsselwörter festlegen", + "automatic-spelling": "Automatische Rechtschreibung", + "notation": "Notation:", + "lowerCase": "Kleinschreibung", + "capitalization": "Großschreibung", + "standard": "Standard", + "alphabetical-sorting": "Alphabetische Sortierung", + "cleanUpView": "Tag-Cleanup Einstellungen", + "rotation": "Drehgrad zufälliger Einträge", + "highestWeight": "Anzahl Tags mit max. Gewichtung", + "automatic-spelling-tooltip": "automatische Überprüfung der Rechtschreibung", + "notation-tooltip": "Einstellung der Schreibweise: klein, groß, standard", + "alphabetical-sorting-tooltip": "Alphabetische Sortierung", + "rotation-tooltip": "Einige Einträge zufällig um x Grad drehen", + "highestWeight-tooltip": "x Tags mit der höchsten Gewichtung anzeigen", + "rotate-weight": "Einige Einträge dieser Gewichtsklasse zufällig um x Grad drehen", + "rotate-weight-tooltip": "Einige zufällig ausgewählte Einträge um diesen Winkel drehen" } } diff --git a/src/assets/i18n/creator/en.json b/src/assets/i18n/creator/en.json index 5eaee11f33629fea3ad5f4790b6fda2275ea6c20..94f74155d652f514048758b322e62c86835e5cad 100644 --- a/src/assets/i18n/creator/en.json +++ b/src/assets/i18n/creator/en.json @@ -370,6 +370,22 @@ "weight-color": "Font color", "weight-number": "max. number of keywords", "weight-color-tooltip": "select font-color", - "weight-number-tooltip": "select maximal number of keywords" + "weight-number-tooltip": "select maximal number of keywords", + "automatic-spelling": "Automatic spelling", + "notation": "Notation:", + "lowerCase": "Lower case", + "capitalization": "Capitalization", + "standard": "Standard", + "alphabetical-sorting": "Alphabetical sorting", + "cleanUpView": "Tag-Cleanup Settings", + "rotation": "rotation of random entries", + "highestWeight": "Number of tags with highest weight", + "automatic-spelling-tooltip": "automatic spelling check", + "notation-tooltip": "Notation-Settings: small, large, standard", + "alphabetical-sorting-tooltip": "Alphabetical sorting", + "rotation-tooltip": "rotate some entries randomly by x degrees", + "highestWeight-tooltip": "show x tags with the highest weight", + "rotate-weight": "Rotate some entries of this weight class randomly by x degrees", + "rotate-weight-tooltip": "Rotate some randomly selected entries by this angle" } } diff --git a/src/assets/i18n/participant/de.json b/src/assets/i18n/participant/de.json index f82fa30c7e299e1c96e95d9223eef07e1871b3ae..2e2cbf6e70311e9a35b63573be254bb03fbc3968 100644 --- a/src/assets/i18n/participant/de.json +++ b/src/assets/i18n/participant/de.json @@ -226,7 +226,8 @@ }, "tag-cloud": { "config": "Wolkenansicht ändern", - "administration": "Wolkenthemen editieren" + "administration": "Wolkenthemen editieren", + "demo-data-topic": "Thema %d" }, "topic-cloud-dialog": { "cancel": "Abbrechen", @@ -298,6 +299,22 @@ "weight-color": "Schriftfarbe", "weight-number": "max. Anzahl Schlüsselwörter", "weight-color-tooltip": "Auswahl der Schriftfarbe", - "weight-number-tooltip": "maximale Anzahl der Schlüsselwörter festlegen" + "weight-number-tooltip": "maximale Anzahl der Schlüsselwörter festlegen", + "automatic-spelling": "Automatische Rechtschreibung", + "notation": "Notation:", + "lowerCase": "Kleinschreibung", + "capitalization": "Großschreibung", + "standard": "Standard", + "alphabetical-sorting": "Alphabetische Sortierung", + "cleanUpView": "Tag-Cleanup Einstellungen", + "rotation": "Drehgrad zufälliger Einträge", + "highestWeight": "Anzahl Tags mit max. Gewichtung", + "automatic-spelling-tooltip": "automatische Überprüfung der Rechtschreibung", + "notation-tooltip": "Einstellung der Schreibweise: klein, groß, standard", + "alphabetical-sorting-tooltip": "Alphabetische Sortierung", + "rotation-tooltip": "Einige Einträge zufällig um x Grad drehen", + "highestWeight-tooltip": "x Tags mit der höchsten Gewichtung anzeigen", + "rotate-weight": "Einige Einträge dieser Gewichtsklasse zufällig um x Grad drehen", + "rotate-weight-tooltip": "Einige zufällig ausgewählte Einträge um diesen Winkel drehen" } } diff --git a/src/assets/i18n/participant/en.json b/src/assets/i18n/participant/en.json index 3383cfd16c303da8b9ce9eea5a9a59d194f82e89..8ec083e376d342e82786144bfd1447b57c48b152 100644 --- a/src/assets/i18n/participant/en.json +++ b/src/assets/i18n/participant/en.json @@ -231,7 +231,8 @@ }, "tag-cloud": { "config": "Modify cloud view", - "administration": "Edit cloud topics" + "administration": "Edit cloud topics", + "demo-data-topic": "Topic %d" }, "topic-cloud-dialog":{ "edit": "Edit", @@ -303,6 +304,22 @@ "weight-color": "Font color", "weight-number": "max. number of keywords", "weight-color-tooltip": "select font-color", - "wieght-number-tooltip": "select maximal number of keywords" + "wieght-number-tooltip": "select maximal number of keywords", + "automatic-spelling": "Automatic spelling", + "notation": "Notation:", + "lowerCase": "Lower case", + "capitalization": "Capitalization", + "standard": "Standard", + "alphabetical-sorting": "Alphabetical sorting", + "cleanUpView": "Tag-Cleanup Settings", + "rotation": "rotation of random entries", + "highestWeight": "Number of tags with highest weight", + "automatic-spelling-tooltip": "automatic spelling check", + "notation-tooltip": "Notation-Settings: small, large, standard", + "alphabetical-sorting-tooltip": "Alphabetical sorting", + "rotation-tooltip": "rotate some entries randomly by x degrees", + "highestWeight-tooltip": "show x tags with the highest weight", + "rotate-weight": "Rotate some entries of this weight class randomly by x degrees", + "rotate-weight-tooltip":"Rotate some randomly selected entries by this angle" } }