diff --git a/angular/src/components/avatar.component.ts b/angular/src/components/avatar.component.ts new file mode 100644 index 0000000000..1760c521f2 --- /dev/null +++ b/angular/src/components/avatar.component.ts @@ -0,0 +1,138 @@ +import { + Component, + Input, + OnChanges, + OnInit, +} from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; + +import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; + +import { Utils } from 'jslib-common/misc/utils'; + +@Component({ + selector: 'app-avatar', + template: '', +}) +export class AvatarComponent implements OnChanges, OnInit { + @Input() data: string; + @Input() email: string; + @Input() size = 45; + @Input() charCount = 2; + @Input() textColor = '#ffffff'; + @Input() fontSize = 20; + @Input() fontWeight = 300; + @Input() dynamic = false; + @Input() circle = false; + + src: string; + + constructor(public sanitizer: DomSanitizer, private cryptoFunctionService: CryptoFunctionService, + private stateService: StateService) { } + + ngOnInit() { + if (!this.dynamic) { + this.generate(); + } + } + + ngOnChanges() { + if (this.dynamic) { + this.generate(); + } + } + + private async generate() { + const enableGravatars = await this.stateService.get('enableGravatars'); + if (enableGravatars && this.email != null) { + const hashBytes = await this.cryptoFunctionService.hash(this.email.toLowerCase().trim(), 'md5'); + const hash = Utils.fromBufferToHex(hashBytes).toLowerCase(); + this.src = 'https://www.gravatar.com/avatar/' + hash + '?s=' + this.size + '&r=pg&d=retro'; + } else { + let chars: string = null; + const upperData = this.data.toUpperCase(); + + if (this.charCount > 1) { + chars = this.getFirstLetters(upperData, this.charCount); + } + if (chars == null) { + chars = this.unicodeSafeSubstring(upperData, this.charCount); + } + + // If the chars contain an emoji, only show it. + if (chars.match(Utils.regexpEmojiPresentation)) { + chars = chars.match(Utils.regexpEmojiPresentation)[0]; + } + + const charObj = this.getCharText(chars); + const color = this.stringToColor(upperData); + const svg = this.getSvg(this.size, color); + svg.appendChild(charObj); + const html = window.document.createElement('div').appendChild(svg).outerHTML; + const svgHtml = window.btoa(unescape(encodeURIComponent(html))); + this.src = 'data:image/svg+xml;base64,' + svgHtml; + } + } + + private stringToColor(str: string): string { + let hash = 0; + for (let i = 0; i < str.length; i++) { + // tslint:disable-next-line + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + let color = '#'; + for (let i = 0; i < 3; i++) { + // tslint:disable-next-line + const value = (hash >> (i * 8)) & 0xFF; + color += ('00' + value.toString(16)).substr(-2); + } + return color; + } + + private getFirstLetters(data: string, count: number): string { + const parts = data.split(' '); + if (parts.length > 1) { + let text = ''; + for (let i = 0; i < count; i++) { + text += this.unicodeSafeSubstring(parts[i], 1); + } + return text; + } + return null; + } + + private getSvg(size: number, color: string): HTMLElement { + const svgTag = window.document.createElement('svg'); + svgTag.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); + svgTag.setAttribute('pointer-events', 'none'); + svgTag.setAttribute('width', size.toString()); + svgTag.setAttribute('height', size.toString()); + svgTag.style.backgroundColor = color; + svgTag.style.width = size + 'px'; + svgTag.style.height = size + 'px'; + return svgTag; + } + + private getCharText(character: string): HTMLElement { + const textTag = window.document.createElement('text'); + textTag.setAttribute('text-anchor', 'middle'); + textTag.setAttribute('y', '50%'); + textTag.setAttribute('x', '50%'); + textTag.setAttribute('dy', '0.35em'); + textTag.setAttribute('pointer-events', 'auto'); + textTag.setAttribute('fill', this.textColor); + textTag.setAttribute('font-family', '"Open Sans","Helvetica Neue",Helvetica,Arial,' + + 'sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"'); + textTag.textContent = character; + textTag.style.fontWeight = this.fontWeight.toString(); + textTag.style.fontSize = this.fontSize + 'px'; + return textTag; + } + + private unicodeSafeSubstring(str: string, count: number) { + const characters = str.match(/./ug); + return characters != null ? characters.slice(0, count).join('') : ''; + } +}