import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges, OnDestroy, NgModule, ModuleWithProviders, } from "@angular/core"; import { Source } from './sources/source'; import { AsyncSource } from './sources/async-source'; import { SourceFactory } from './sources/source.factory'; import { AvatarService } from './avatar.service'; import { AvatarSource } from './sources/avatar-source.enum'; import { takeWhile, map } from 'rxjs/operators'; import { CommonModule } from '@angular/common'; import { AvatarConfigService } from './avatar-config.service'; import { AvatarConfig } from './avatar-config'; import { AVATAR_CONFIG } from './avatar-config.token'; /** * Universal avatar component that * generates avatar from different sources * * export * class AvatarComponent * implements {OnChanges} */ @Component({ // tslint:disable-next-line:component-selector selector: 'p-avatar', styles: [ ` :host { border-radius: '50%'; } ` ], template: `
{{ avatarText }}
` }) export class AvatarComponent implements OnChanges, OnDestroy { @Input() public round = true; @Input() public size = 50; @Input() public textSizeRatio = 3; @Input() public bgColor: string | undefined; @Input() public fgColor = '#FFF'; @Input() public borderColor: string | undefined; @Input() public style: any = {}; @Input() public cornerRadius = 0; @Input('facebookId') public facebook: string | null; @Input('twitterId') public twitter: string | null; @Input('googleId') public google: string | null; @Input('instagramId') public instagram: string | null; @Input('vkontakteId') public vkontakte: string | null; @Input('skypeId') public skype: string | null; @Input('gravatarId') public gravatar: string | null; @Input('githubId') public github: string | null; @Input('src') public custom: string | null; @Input('name') public initials: string | null; @Input('value') public value: string | null; @Input('placeholder') public placeholder: string; @Input('initialsSize') public initialsSize: number; @Output() public clickOnAvatar: EventEmitter = new EventEmitter(); public isAlive = true; public avatarSrc: string | null = null; public avatarText: string | null = null; public avatarStyle: any = {}; public hostStyle: any = {}; private currentIndex = -1; private sources: Source[] = []; constructor( public sourceFactory: SourceFactory, private avatarService: AvatarService ) {} public onAvatarClicked(): void { this.clickOnAvatar.emit(this.sources[this.currentIndex]); } /** * Detect inputs change * * param {{ [propKey: string]: SimpleChange }} changes * * memberof AvatarComponent */ public ngOnChanges(changes: SimpleChanges): void { for (const propName in changes) { if (this.avatarService.isSource(propName)) { const sourceType = AvatarSource[propName.toUpperCase()]; const currentValue = changes[propName].currentValue; if (currentValue && typeof currentValue === 'string') { this.addSource(sourceType, currentValue); } else { this.removeSource(sourceType); } } } // reinitialize the avatar component when a source property value has changed // the fallback system must be re-invoked with the new values. this.initializeAvatar(); } /** * Fetch avatar source * * param {any} event * * memberOf AvatarComponent */ public fetchAvatarSource(): void { const previousSource = this.sources[this.currentIndex]; if (previousSource) { this.avatarService.markSourceAsFailed(previousSource); } const source = this.findNextSource(); if (!source) { return; } if (this.avatarService.isTextAvatar(source.sourceType)) { this.buildTextAvatar(source); this.avatarSrc = null; } else { this.buildImageAvatar(source); } } private findNextSource(): Source | null { while (++this.currentIndex < this.sources.length) { const source = this.sources[this.currentIndex]; if (source && !this.avatarService.sourceHasFailedBefore(source)) { return source; } } return null; } public ngOnDestroy(): void { this.isAlive = false; } /** * Initialize the avatar component and its fallback system */ private initializeAvatar(): void { this.currentIndex = -1; if (this.sources.length > 0) { this.sortAvatarSources(); this.fetchAvatarSource(); this.hostStyle = { width: this.size + 'px', height: this.size + 'px' }; } } private sortAvatarSources(): void { this.sources.sort((source1, source2) => this.avatarService.compareSources(source1.sourceType, source2.sourceType) ); } private buildTextAvatar(avatarSource: Source): void { this.avatarText = avatarSource.getAvatar(this.initialsSize); this.avatarStyle = this.getInitialsStyle(avatarSource.sourceId); } private buildImageAvatar(avatarSource: Source): void { this.avatarStyle = this.getImageStyle(); if (avatarSource instanceof AsyncSource) { this.fetchAndProcessAsyncAvatar(avatarSource); } else { this.avatarSrc = avatarSource.getAvatar(this.size); } } /** * * returns initials style * * memberOf AvatarComponent */ private getInitialsStyle(avatarValue: string): void { return { textAlign: 'center', borderRadius: this.round ? '100%' : this.cornerRadius + 'px', border: this.borderColor ? '1px solid ' + this.borderColor : '', textTransform: 'uppercase', color: this.fgColor, backgroundColor: this.bgColor ? this.bgColor : this.avatarService.getRandomColor(avatarValue), font: Math.floor(this.size / this.textSizeRatio) + 'px Helvetica, Arial, sans-serif', lineHeight: this.size + 'px', ...this.style }; } /** * * returns image style * * memberOf AvatarComponent */ private getImageStyle(): void { return { maxWidth: '100%', borderRadius: this.round ? '50%' : this.cornerRadius + 'px', border: this.borderColor ? '1px solid ' + this.borderColor : '', width: this.size, height: this.size, ...this.style }; } /** * Fetch avatar image asynchronously. * * param {Source} source represents avatar source * memberof AvatarComponent */ private fetchAndProcessAsyncAvatar(source: AsyncSource): void { if (this.avatarService.sourceHasFailedBefore(source)) { return; } this.avatarService .fetchAvatar(source.getAvatar(this.size)) .pipe( takeWhile(() => this.isAlive), map(response => source.processResponse(response, this.size)), ) .subscribe( avatarSrc => (this.avatarSrc = avatarSrc), err => { this.fetchAvatarSource(); }, ); } /** * Add avatar source * * param sourceType avatar source type e.g facebook,twitter, etc. * param sourceValue source value e.g facebookId value, etc. */ private addSource(sourceType: AvatarSource, sourceValue: string): void { const source = this.sources.find(s => s.sourceType === sourceType); if (source) { source.sourceId = sourceValue; } else { this.sources.push( this.sourceFactory.newInstance(sourceType, sourceValue), ); } } /** * Remove avatar source * * param sourceType avatar source type e.g facebook,twitter, etc. */ private removeSource(sourceType: AvatarSource): void { this.sources = this.sources.filter(source => source.sourceType !== sourceType); } } @NgModule({ imports: [CommonModule], declarations: [AvatarComponent], providers: [SourceFactory, AvatarService, AvatarConfigService], exports: [AvatarComponent], }) export class AvatarModule { static forRoot( avatarConfig?: AvatarConfig ): ModuleWithProviders { return { ngModule: AvatarModule, providers: [ { provide: AVATAR_CONFIG, useValue: avatarConfig ? avatarConfig : {}, }, ], }; } }