diff --git a/angular.json b/angular.json index a1cf4f69c7fa2591a298a70475469dcd2efa40ca..94cb7e5259bcd28c9dd604dd9d4629300e7244f4 100644 --- a/angular.json +++ b/angular.json @@ -59,7 +59,8 @@ "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { - "browserTarget": "arsnova-angular-frontend:build" + "browserTarget": "arsnova-angular-frontend:build", + "proxyConfig": "proxy.conf.json" }, "configurations": { "production": { diff --git a/package-lock.json b/package-lock.json index 64519a45c89a47be912d9d9051c2b642d9270dbd..fc877b6e050741c3b470b580533d6e62e447ae03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1951,11 +1951,6 @@ "yargs": "8.0.2" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -2056,11 +2051,6 @@ "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", "dev": true }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", @@ -2115,14 +2105,6 @@ } } }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - }, "supports-color": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", @@ -12735,11 +12717,6 @@ "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", "dev": true }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", @@ -16271,9 +16248,9 @@ } }, "url-parse": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", - "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz", + "integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==", "dev": true, "requires": { "querystringify": "^2.1.1", diff --git a/package.json b/package.json index 58a12e29d25978cda8342dfeb1aa01be75daa816..3d014d9e91d25c354fe8add8d7b3a7864ca04232 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "license": "MIT", "scripts": { "ng": "ng", - "start": "ng serve --proxy-config proxy.conf.json", + "start": "ng serve", "build": "ng build --prod", "test": "ng test --watch=false --browsers=ChromeHeadlessCustom --source-map=false --code-coverage", "lint": "ng lint", diff --git a/proxy.conf.json b/proxy.conf.json index d77258a7a9e95751643c69901ad3180eabda246e..d9c429863b7cd7022076d74148411dac622414e8 100644 --- a/proxy.conf.json +++ b/proxy.conf.json @@ -5,7 +5,8 @@ "changeOrigin": true, "pathRewrite": { "^/spacy": "" - } + }, + "logLevel": "debug" }, "/api/ws/websocket": { "target": "ws://localhost:8080", diff --git a/src/app/components/shared/header/header.component.html b/src/app/components/shared/header/header.component.html index 0d15bee84587188d756ecdcdb890dca1bd73f59a..cd3b0f272179ce0984d9e810017fd01e3801f33d 100644 --- a/src/app/components/shared/header/header.component.html +++ b/src/app/components/shared/header/header.component.html @@ -236,7 +236,7 @@ </button> </ng-container> - + <button mat-menu-item *ngIf="user && user.role > 0 && !router.url.includes('/comment/') && !router.url.endsWith('/tagcloud')" tabindex="0" @@ -280,7 +280,7 @@ </mat-icon> <span>{{'header.back-to-questionboard' | translate}}</span> </button> - + </ng-container> </div> 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 68f445b360dafa68d47b33ab9c6b0cd5ca5ee3f7..323f62f71e65b5db7adb5d5a6152eda34caa61ed 100644 --- a/src/app/components/shared/tag-cloud/tag-cloud.component.html +++ b/src/app/components/shared/tag-cloud/tag-cloud.component.html @@ -1,10 +1,15 @@ <ars-screen ars-flex-box> - <ars-row [height]="100"> - + <ars-row [height]="65"> </ars-row> - <ars-fill ars-flex-box style="width:100%;height:100%;"> + <mat-drawer-container> + <mat-drawer [(opened)]="drawerOpen" position="start"> + Test <!-- TODO: Gruppe 4 --> + </mat-drawer> + <mat-drawer-content> + <ars-fill ars-flex-box> <angular-tag-cloud class="spacyTagCloud" + (window:resize)="onResize($event)" [data]="data" [width]="options.width" [height]="options.height" @@ -12,10 +17,9 @@ [zoomOnHover]="zoomOnHoverOptions" [realignOnResize]="true"> </angular-tag-cloud> - </ars-fill> - - <ars-row [height]="100"> - + </ars-fill> + </mat-drawer-content> + </mat-drawer-container> + <ars-row [height]="37"> </ars-row> - </ars-screen> diff --git a/src/app/components/shared/tag-cloud/tag-cloud.component.scss b/src/app/components/shared/tag-cloud/tag-cloud.component.scss index ffa2f7ef8617f5e9036fd082c63a544fb90a01f0..bc1df4b8c90998a1ebe718cb3db0137b7aeeb9b2 100644 --- a/src/app/components/shared/tag-cloud/tag-cloud.component.scss +++ b/src/app/components/shared/tag-cloud/tag-cloud.component.scss @@ -1,55 +1,18 @@ -::ng-deep .spacyTagCloud > span { - &.w10 { - color: var(--light-green, brown) !important; - font-size: 380% !important; - } - - &.w9 { - color: var(--magenta, white) !important; - font-size: 330% !important; - } - - &.w8 { - color: var(--purple, tomato) !important; - font-size: 280% !important; - } - - &.w7 { - color: var(--on-background, lightgreen) !important; - font-size: 240% !important; - } - - &.w6 { - color: var(--yellow, gray) !important; - font-size: 210% !important; - } - - &.w5 { - color: var(--primary, pink) !important; - font-size: 180% !important; - } - - &.w4 { - color: var(--red, orange) !important; - font-size: 160% !important; - } - - &.w3 { - color: var(--grey, yellow) !important; - font-size: 140% !important; - } +ars-fill { + width: calc(100% - 30px); + height: calc(100% - 30px); + margin: 15px; +} - &.w2 { - color: var(--blue, green) !important; - font-size: 120% !important; - } +mat-drawer-container { + background-color: var(--background); + height: 100%; + width: 100%; +} - &.w1 { - color: var(--moderator, lightblue) !important; - font-size: 100% !important; - } +/* TODO: Gruppe 4 */ - &:hover { - color: var(--secondary, greenyellow) !important; - } +mat-drawer { + background-color: var(--background); + color: var(--on-background); } 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 aff217c016bf2ebb845b33ceb5a891d2d4607dcc..727fa019d9b3b138e9253109c31e16afad08bb06 100644 --- a/src/app/components/shared/tag-cloud/tag-cloud.component.ts +++ b/src/app/components/shared/tag-cloud/tag-cloud.component.ts @@ -22,7 +22,26 @@ import { AuthenticationService } from '../../../services/http/authentication.ser 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'; +class CustomPosition implements Position { + left: number; + top: number; + + constructor(public relativeLeft: number, + public relativeTop: number) { + } + + updatePosition(width: number, height: number, text: string, style: CSSStyleDeclaration) { + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + context.font = style.font; + const offsetY = parseInt(style.fontSize, 10) / 2; + const offsetX = context.measureText(text).width / 2; + this.left = width * this.relativeLeft - offsetX; + this.top = height * this.relativeTop - offsetY; + } +} class TagComment implements CloudData { constructor(public color: string, @@ -36,17 +55,72 @@ class TagComment implements CloudData { } } -const weight2color = { - 1: 'blue', - 2: 'green', - 3: 'yellow', - 4: 'orange', - 5: 'pink', - 6: 'gray', - 7: 'lightgreen', - 8: 'tomato', - 9: 'white', - 10: 'brown' +//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 +]; + +const colorRegex = /rgba?\((\d+), (\d+), (\d+)(?:, (\d(?:\.\d+)?))?\)/; +const defaultColors: string[] = [ + // variable, fallback + 'var(--secondary, greenyellow)', // hover + 'var(--moderator, lightblue)', // w1 + 'var(--blue, green)', // w2 + 'var(--grey, yellow)', // w3 + 'var(--red, orange)', // w4 + 'var(--primary, pink)', // w5 + 'var(--yellow, gray)', // w6 + 'var(--on-background, lightgreen)', // w7 + 'var(--purple, tomato)', // w8 + 'var(--magenta, white)', // w9 + 'var(--light-green, brown)' // w10 +]; + +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)'; // fallback + 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 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); }; @Component({ @@ -65,9 +139,9 @@ export class TagCloudComponent implements OnInit { shortId: string; options: CloudOptions = { // if width is between 0 and 1 it will be set to the width of the upper element multiplied by the value - width: 0.97, + width: 0.99, // if height is between 0 and 1 it will be set to the height of the upper element multiplied by the value - height: 0.97, + height: 0.99, overflow: false, font: 'Georgia' // not working }; @@ -77,8 +151,9 @@ export class TagCloudComponent implements OnInit { delay: 0.4,// Zoom will take affect after 0.4 seconds color: 'red' }; - + userRole: UserRole; data: CloudData[] = []; + sorted = false; constructor(private commentService: CommentService, @@ -90,7 +165,8 @@ export class TagCloudComponent implements OnInit { public eventService: EventService, private authenticationService: AuthenticationService, private route: ActivatedRoute, - protected roomService: RoomService) { + protected roomService: RoomService, + private themeService: ThemeService) { this.roomId = localStorage.getItem('roomId'); this.langService.langEmitter.subscribe(lang => { this.translateService.use(lang); @@ -108,6 +184,7 @@ export class TagCloudComponent implements OnInit { this.user = newUser; } }); + this.userRole = this.route.snapshot.data.roles[0]; this.route.params.subscribe(params => { this.shortId = params['shortId']; this.authenticationService.guestLogin(UserRole.PARTICIPANT).subscribe(r => { @@ -126,6 +203,19 @@ export class TagCloudComponent implements OnInit { this.commentService.getAckComments(this.roomId).subscribe((comments: Comment[]) => { this.analyse(comments); }); + this.resetColorsToTheme(); + this.themeService.getTheme().subscribe(() => { + this.resetColorsToTheme(); + if (this.child) { + setTimeout(() => { + this.updateTagCloud(); + }, 1); + } + }); + } + resetColorsToTheme() { + setGlobalStyles(getResolvedDefaultColors() + .map(e => 'color: ' + e + ' !important;') as TagCloudStyleData); } analyse(comments: Comment[]) { @@ -144,10 +234,56 @@ export class TagCloudComponent implements OnInit { 'TODO', val)); } ); - this.child.reDraw(); + this.sortPositionsAlphabetically(this.sorted); + this.updateTagCloud(); }); } + drawerOpen(): boolean { + return true; + } + + 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; + } + 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)); + } + } + } + + updateTagCloud() { + if (this.sorted && this.data.length) { + if (!this.child.cloudDataHtmlElements || !this.child.cloudDataHtmlElements.length) { + this.child.reDraw(); + } + const width = this.child.calculatedWidth; + const height = this.child.calculatedHeight; + this.data.forEach((e, i) => { + (e.position as CustomPosition).updatePosition(width, height, e.text, + window.getComputedStyle(this.child.cloudDataHtmlElements[i])); + }); + } + this.child.reDraw(); + } + openCreateDialog(): void { const dialogRef = this.dialog.open(CreateCommentComponent, { width: '900px', @@ -175,11 +311,24 @@ export class TagCloudComponent implements OnInit { } send(comment: Comment): void { - this.translateService.get('comment-list.comment-sent').subscribe(msg => { - this.notificationService.show(msg); - }); - comment.ack = true; + if (this.directSend) { + this.translateService.get('comment-list.comment-sent').subscribe(msg => { + this.notificationService.show(msg); + }); + comment.ack = true; + } else { + if (this.userRole === 1 || this.userRole === 2 || this.userRole === 3) { + this.translateService.get('comment-list.comment-sent').subscribe(msg => { + this.notificationService.show(msg); + }); + comment.ack = true; + } + if (this.userRole === 0) { + this.translateService.get('comment-list.comment-sent-to-moderator').subscribe(msg => { + this.notificationService.show(msg); + }); + } + } this.commentService.addComment(comment).subscribe(); } - }