import { Component, OnInit, Input, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core'; import { HttpErrorResponse } from '@angular/common/http'; import { faUser, faHourglassHalf, faUserCheck, faExclamationTriangle, faLink, faLock } from "@fortawesome/free-solid-svg-icons"; import { faUser as faUserRegular } from "@fortawesome/free-regular-svg-icons"; import { Observable, Subscription } from 'rxjs'; import { Store } from '@ngxs/store'; import { Account, Status, Relationship, Attachment } from "../../../services/models/mastodon.interfaces"; import { MastodonWrapperService } from '../../../services/mastodon-wrapper.service'; import { ToolsService, OpenThreadEvent, InstanceType } from '../../../services/tools.service'; import { NotificationService } from '../../../services/notification.service'; import { AccountInfo } from '../../../states/accounts.state'; import { StatusWrapper, OpenMediaEvent } from '../../../models/common.model'; import { EmojiConverter, EmojiTypeEnum } from '../../../tools/emoji.tools'; import { NavigationService } from '../../../services/navigation.service'; import { BrowseBase } from '../../common/browse-base'; @Component({ selector: 'app-user-profile', templateUrl: './user-profile.component.html', styleUrls: ['./user-profile.component.scss'] }) export class UserProfileComponent extends BrowseBase { private emojiConverter = new EmojiConverter(); faUser = faUser; faUserRegular = faUserRegular; faHourglassHalf = faHourglassHalf; faUserCheck = faUserCheck; faExclamationTriangle = faExclamationTriangle; faLink = faLink; faLock = faLock; displayedAccount: Account; hasNote: boolean; note: string; isLoading: boolean; loadingRelationShip = false; relationShipError = false; showFloatingHeader = false; showFloatingStatusMenu = false; private maxReached = false; private maxId: string; statusLoading: boolean; error: string; relationship: Relationship; statuses: StatusWrapper[] = []; pinnedStatuses: StatusWrapper[] = []; profileSection: 'fields' | 'choices' | 'hashtags' = 'fields'; statusSection: 'status' | 'replies' | 'media' = 'status'; private lastAccountName: string; private currentlyUsedAccount: AccountInfo; private accounts$: Observable; private accountSub: Subscription; private deleteStatusSubscription: Subscription; private refreshSubscription: Subscription; private goToTopSubscription: Subscription; @ViewChild('statusstream') public statustream: ElementRef; @ViewChild('profilestatuses') public profilestatuses: ElementRef; @Input() refreshEventEmitter: EventEmitter; @Input() goToTopEventEmitter: EventEmitter; @Input('currentAccount') set currentAccount(accountName: string) { this.load(accountName); } constructor( private readonly store: Store, private readonly navigationService: NavigationService, private readonly notificationService: NotificationService, private readonly mastodonService: MastodonWrapperService, private readonly toolsService: ToolsService) { super(); this.accounts$ = this.store.select(state => state.registeredaccounts.accounts); } ngOnInit() { if (this.refreshEventEmitter) { this.refreshSubscription = this.refreshEventEmitter.subscribe(() => { this.refresh(); }) } if (this.goToTopEventEmitter) { this.goToTopSubscription = this.goToTopEventEmitter.subscribe(() => { this.goToTop(); }) } this.accountSub = this.accounts$.subscribe((accounts: AccountInfo[]) => { if (this.displayedAccount) { const userAccount = accounts.filter(x => x.isSelected)[0]; this.loadingRelationShip = true; this.relationShipError = false; this.toolsService.findAccount(userAccount, this.lastAccountName) .then((account: Account) => { if (!account) throw Error(`Could not find ${this.lastAccountName}`); return this.getFollowStatus(userAccount, account); }) .catch((err) => { console.error(err); this.relationShipError = true; }) .then(() => { this.loadingRelationShip = false; }); } }); this.deleteStatusSubscription = this.notificationService.deletedStatusStream.subscribe((status: StatusWrapper) => { if (status) { this.statuses = this.statuses.filter(x => { return !(x.status.url.replace('https://', '').split('/')[0] === status.provider.instance && x.status.id === status.status.id); }); } }); } ngOnDestroy() { if (this.accountSub) this.accountSub.unsubscribe(); if (this.deleteStatusSubscription) this.deleteStatusSubscription.unsubscribe(); if (this.refreshSubscription) this.refreshSubscription.unsubscribe(); if (this.goToTopSubscription) this.goToTopSubscription.unsubscribe(); } goToTop(): any { const stream = this.statustream.nativeElement as HTMLElement; setTimeout(() => { stream.scrollTo({ top: 0, behavior: 'smooth' }); }, 0); } private load(accountName: string) { this.statuses.length = 0; this.pinnedStatuses.length = 0; this.displayedAccount = null; this.isLoading = true; this.showFloatingHeader = false; this.isSwitchingSection = false; this.lastAccountName = accountName; this.currentlyUsedAccount = this.toolsService.getSelectedAccounts()[0]; return this.toolsService.findAccount(this.currentlyUsedAccount, this.lastAccountName) .then((account: Account) => { this.isLoading = false; this.statusLoading = true; if (!account) throw Error(`Could not find ${this.lastAccountName}`); this.displayedAccount = this.fixPleromaFieldsUrl(account); this.hasNote = account && account.note && account.note !== '

'; if (this.hasNote) { this.note = this.emojiConverter.applyEmojis(account.emojis, account.note, EmojiTypeEnum.medium); } const getFollowStatusPromise = this.getFollowStatus(this.currentlyUsedAccount, this.displayedAccount); const getStatusesPromise = this.getStatuses(this.currentlyUsedAccount, this.displayedAccount, false, true, null); const getPinnedStatusesPromise = this.getPinnedStatuses(this.currentlyUsedAccount, this.displayedAccount); return Promise.all([getFollowStatusPromise, getStatusesPromise, getPinnedStatusesPromise]); }) .catch((err: HttpErrorResponse) => { console.error(err); }) .then(() => { this.isLoading = false; this.statusLoading = false; }); } private fixPleromaFieldsUrl(acc: Account): Account { if (acc.fields) { acc.fields.forEach(f => { if (f.value.includes(' { }) .then(() => { this.isSwitchingSection = false; }); } else { this.isSwitchingSection = false; } if (this.showFloatingStatusMenu) { setTimeout(() => { var element = this.statustream.nativeElement as HTMLElement; const menuPosition = element.scrollHeight - this.profilestatuses.nativeElement.offsetHeight - 30 - 29; element.scrollTop = menuPosition; }, 0); } return false; } openAttachment(attachment: Attachment): boolean { let openMediaEvent = new OpenMediaEvent(0, [attachment], null); this.navigationService.openMedia(openMediaEvent); return false; } @Output() browseFollowsEvent = new EventEmitter(); @Output() browseFollowersEvent = new EventEmitter(); browseFollows(): boolean { let accountName = this.toolsService.getAccountFullHandle(this.displayedAccount); this.browseFollowsEvent.next(accountName); return false; } browseFollowers(): boolean { let accountName = this.toolsService.getAccountFullHandle(this.displayedAccount); this.browseFollowersEvent.next(accountName); return false; } }