Sengi-Windows-MacOS-Linux/src/app/components/stream/user-profile/user-profile.component.ts

468 lines
17 KiB
TypeScript
Raw Normal View History

2019-04-07 20:47:09 +02:00
import { Component, OnInit, Input, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core';
2019-02-12 05:41:21 +01:00
import { HttpErrorResponse } from '@angular/common/http';
2020-10-03 21:52:55 +02:00
import { faUser, faHourglassHalf, faUserCheck, faExclamationTriangle, faLink, faLock } from "@fortawesome/free-solid-svg-icons";
import { faUser as faUserRegular } from "@fortawesome/free-regular-svg-icons";
2019-02-23 05:35:12 +01:00
import { Observable, Subscription } from 'rxjs';
import { Store } from '@ngxs/store';
2019-02-12 05:41:21 +01:00
2019-06-15 20:32:08 +02:00
import { Account, Status, Relationship, Attachment } from "../../../services/models/mastodon.interfaces";
import { MastodonWrapperService } from '../../../services/mastodon-wrapper.service';
2024-03-08 06:19:39 +01:00
import { ToolsService, OpenThreadEvent, InstanceType } from '../../../services/tools.service';
2019-02-12 05:41:21 +01:00
import { NotificationService } from '../../../services/notification.service';
2019-02-23 05:35:12 +01:00
import { AccountInfo } from '../../../states/accounts.state';
2019-08-09 05:00:49 +02:00
import { StatusWrapper, OpenMediaEvent } from '../../../models/common.model';
import { EmojiConverter, EmojiTypeEnum } from '../../../tools/emoji.tools';
2019-06-15 20:32:08 +02:00
import { NavigationService } from '../../../services/navigation.service';
2020-06-14 09:07:44 +02:00
import { BrowseBase } from '../../common/browse-base';
@Component({
selector: 'app-user-profile',
templateUrl: './user-profile.component.html',
styleUrls: ['./user-profile.component.scss']
})
2020-06-14 09:07:44 +02:00
export class UserProfileComponent extends BrowseBase {
private emojiConverter = new EmojiConverter();
faUser = faUser;
faUserRegular = faUserRegular;
faHourglassHalf = faHourglassHalf;
faUserCheck = faUserCheck;
2020-03-12 02:07:57 +01:00
faExclamationTriangle = faExclamationTriangle;
faLink = faLink;
2020-10-03 21:52:55 +02:00
faLock = faLock;
2019-04-07 20:47:09 +02:00
displayedAccount: Account;
hasNote: boolean;
note: string;
2018-11-02 00:36:52 +01:00
isLoading: boolean;
loadingRelationShip = false;
2020-03-12 02:07:57 +01:00
relationShipError = false;
2019-08-06 02:11:29 +02:00
showFloatingHeader = false;
2019-08-09 05:30:51 +02:00
showFloatingStatusMenu = false;
2019-06-15 20:32:08 +02:00
2019-04-07 20:47:09 +02:00
private maxReached = false;
private maxId: string;
2018-11-01 05:44:58 +01:00
statusLoading: boolean;
2018-11-02 00:36:52 +01:00
error: string;
2019-02-23 05:35:12 +01:00
relationship: Relationship;
2018-11-02 00:36:52 +01:00
statuses: StatusWrapper[] = [];
2019-07-07 23:22:48 +02:00
pinnedStatuses: StatusWrapper[] = [];
2018-11-02 00:36:52 +01:00
2019-08-09 04:11:12 +02:00
profileSection: 'fields' | 'choices' | 'hashtags' = 'fields';
statusSection: 'status' | 'replies' | 'media' = 'status';
private lastAccountName: string;
2018-11-01 05:44:58 +01:00
2019-02-23 05:35:12 +01:00
private currentlyUsedAccount: AccountInfo;
private accounts$: Observable<AccountInfo[]>;
private accountSub: Subscription;
private deleteStatusSubscription: Subscription;
2019-08-11 05:52:56 +02:00
private refreshSubscription: Subscription;
2019-08-11 07:00:58 +02:00
private goToTopSubscription: Subscription;
2019-02-23 05:35:12 +01:00
2019-04-07 20:47:09 +02:00
@ViewChild('statusstream') public statustream: ElementRef;
2019-08-09 05:30:51 +02:00
@ViewChild('profilestatuses') public profilestatuses: ElementRef;
2019-04-07 20:47:09 +02:00
2019-08-11 05:52:56 +02:00
@Input() refreshEventEmitter: EventEmitter<any>;
2019-08-11 07:00:58 +02:00
@Input() goToTopEventEmitter: EventEmitter<any>;
2019-08-11 05:52:56 +02:00
@Input('currentAccount')
2018-11-02 00:36:52 +01:00
set currentAccount(accountName: string) {
this.load(accountName);
}
constructor(
2019-02-23 05:35:12 +01:00
private readonly store: Store,
2019-06-15 20:32:08 +02:00
private readonly navigationService: NavigationService,
private readonly notificationService: NotificationService,
private readonly mastodonService: MastodonWrapperService,
2019-02-23 05:35:12 +01:00
private readonly toolsService: ToolsService) {
2020-06-14 09:07:44 +02:00
super();
2019-02-23 05:35:12 +01:00
this.accounts$ = this.store.select(state => state.registeredaccounts.accounts);
}
ngOnInit() {
2019-08-11 07:00:58 +02:00
if (this.refreshEventEmitter) {
2019-08-11 05:52:56 +02:00
this.refreshSubscription = this.refreshEventEmitter.subscribe(() => {
this.refresh();
})
}
2019-08-11 07:00:58 +02:00
if (this.goToTopEventEmitter) {
this.goToTopSubscription = this.goToTopEventEmitter.subscribe(() => {
this.goToTop();
})
}
2019-02-23 05:35:12 +01:00
this.accountSub = this.accounts$.subscribe((accounts: AccountInfo[]) => {
2019-04-07 20:47:09 +02:00
if (this.displayedAccount) {
const userAccount = accounts.filter(x => x.isSelected)[0];
this.loadingRelationShip = true;
2020-03-12 02:07:57 +01:00
this.relationShipError = false;
2019-04-07 20:47:09 +02:00
this.toolsService.findAccount(userAccount, this.lastAccountName)
.then((account: Account) => {
2020-05-29 06:32:55 +02:00
if (!account) throw Error(`Could not find ${this.lastAccountName}`);
2020-03-12 02:28:16 +01:00
return this.getFollowStatus(userAccount, account);
})
2020-03-12 02:28:16 +01:00
.catch((err) => {
2020-03-12 02:07:57 +01:00
console.error(err);
2020-05-29 06:32:55 +02:00
this.relationShipError = true;
})
.then(() => {
this.loadingRelationShip = false;
2019-02-23 07:00:33 +01:00
});
}
2019-02-23 05:35:12 +01:00
});
this.deleteStatusSubscription = this.notificationService.deletedStatusStream.subscribe((status: StatusWrapper) => {
2019-07-07 23:22:48 +02:00
if (status) {
this.statuses = this.statuses.filter(x => {
2019-07-07 23:22:48 +02:00
return !(x.status.url.replace('https://', '').split('/')[0] === status.provider.instance && x.status.id === status.status.id);
});
}
});
2020-05-29 06:32:55 +02:00
}
2019-02-23 05:35:12 +01:00
ngOnDestroy() {
2019-08-11 07:00:58 +02:00
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) {
2018-11-02 01:22:43 +01:00
this.statuses.length = 0;
2019-07-07 23:22:48 +02:00
this.pinnedStatuses.length = 0;
2019-02-23 05:35:12 +01:00
2019-04-07 20:47:09 +02:00
this.displayedAccount = null;
this.isLoading = true;
this.showFloatingHeader = false;
2019-08-10 19:40:08 +02:00
this.isSwitchingSection = false;
2018-11-02 00:36:52 +01:00
this.lastAccountName = accountName;
this.currentlyUsedAccount = this.toolsService.getSelectedAccounts()[0];
return this.toolsService.findAccount(this.currentlyUsedAccount, this.lastAccountName)
2018-11-02 00:36:52 +01:00
.then((account: Account) => {
2019-02-23 05:35:12 +01:00
this.isLoading = false;
this.statusLoading = true;
2019-02-23 05:35:12 +01:00
2020-05-29 06:32:55 +02:00
if (!account) throw Error(`Could not find ${this.lastAccountName}`);
2020-03-12 02:28:16 +01:00
this.displayedAccount = this.fixPleromaFieldsUrl(account);
2018-11-03 04:36:35 +01:00
this.hasNote = account && account.note && account.note !== '<p></p>';
2019-06-15 20:32:08 +02:00
if (this.hasNote) {
this.note = this.emojiConverter.applyEmojis(account.emojis, account.note, EmojiTypeEnum.medium);
}
2019-02-23 05:35:12 +01:00
2019-04-07 20:47:09 +02:00
const getFollowStatusPromise = this.getFollowStatus(this.currentlyUsedAccount, this.displayedAccount);
2019-08-09 04:41:59 +02:00
const getStatusesPromise = this.getStatuses(this.currentlyUsedAccount, this.displayedAccount, false, true, null);
2019-07-07 23:22:48 +02:00
const getPinnedStatusesPromise = this.getPinnedStatuses(this.currentlyUsedAccount, this.displayedAccount);
2019-02-23 05:35:12 +01:00
2019-07-07 23:22:48 +02:00
return Promise.all([getFollowStatusPromise, getStatusesPromise, getPinnedStatusesPromise]);
2018-11-02 00:36:52 +01:00
})
2019-02-12 05:41:21 +01:00
.catch((err: HttpErrorResponse) => {
2020-03-12 02:07:57 +01:00
console.error(err);
2019-02-12 05:41:21 +01:00
})
.then(() => {
2018-11-02 00:36:52 +01:00
this.isLoading = false;
this.statusLoading = false;
});
}
private fixPleromaFieldsUrl(acc: Account): Account {
2020-05-29 06:32:55 +02:00
if (acc.fields) {
acc.fields.forEach(f => {
2020-05-29 06:32:55 +02:00
if (f.value.includes('<a href="') && !f.value.includes('target="_blank"')) {
f.value = f.value.replace('<a href="', '<a target="_blank" href="');
}
});
}
return acc;
}
2019-07-07 23:22:48 +02:00
private getPinnedStatuses(userAccount: AccountInfo, account: Account): Promise<void> {
2019-08-07 02:18:31 +02:00
return this.mastodonService.getAccountStatuses(userAccount, account.id, false, true, false, null, null, 20)
2019-07-07 23:22:48 +02:00
.then((statuses: Status[]) => {
for (const status of statuses) {
2019-08-06 00:56:45 +02:00
status.pinned = true;
2020-04-01 08:29:51 +02:00
let cwPolicy = this.toolsService.checkContentWarning(status);
const wrapper = new StatusWrapper(cwPolicy.status, userAccount, cwPolicy.applyCw, cwPolicy.hide);
2019-07-07 23:22:48 +02:00
this.pinnedStatuses.push(wrapper);
}
})
.catch(err => {
2019-09-07 23:52:07 +02:00
this.notificationService.notifyHttpError(err, userAccount);
2019-07-07 23:22:48 +02:00
});
}
2019-08-09 04:41:59 +02:00
private getStatuses(userAccount: AccountInfo, account: Account, onlyMedia: boolean, excludeReplies: boolean, maxId: string): Promise<void> {
2019-08-09 05:30:51 +02:00
this.statusLoading = true;
2019-08-09 04:41:59 +02:00
2019-08-09 06:49:23 +02:00
return this.mastodonService.getAccountStatuses(userAccount, account.id, onlyMedia, false, excludeReplies, maxId, null, 40)
2019-04-07 20:47:09 +02:00
.then((statuses: Status[]) => {
this.loadStatus(userAccount, statuses);
})
.catch(err => {
2019-09-07 23:52:07 +02:00
this.notificationService.notifyHttpError(err, userAccount);
2019-04-07 20:47:09 +02:00
})
.then(() => {
2019-02-23 05:35:12 +01:00
this.statusLoading = false;
});
}
2019-02-23 05:35:12 +01:00
private getFollowStatus(userAccount: AccountInfo, account: Account): Promise<void> {
this.loadingRelationShip = true;
2019-02-23 05:35:12 +01:00
return this.mastodonService.getRelationships(userAccount, [account])
.then((result: Relationship[]) => {
2019-02-23 05:35:12 +01:00
this.relationship = result.filter(x => x.id === account.id)[0];
})
.catch(err => {
2019-09-07 23:52:07 +02:00
this.notificationService.notifyHttpError(err, userAccount);
})
.then(() => {
this.loadingRelationShip = false;
2019-02-23 05:35:12 +01:00
});
}
2019-06-15 20:32:08 +02:00
showAvatar(avatarUrl: string): boolean {
const att: Attachment = {
id: '',
type: 'image',
remote_url: avatarUrl,
preview_url: avatarUrl,
url: avatarUrl,
meta: null,
text_url: '',
2019-07-05 23:22:49 +02:00
description: '',
pleroma: null
2019-06-15 20:32:08 +02:00
}
this.navigationService.openMedia({
selectedIndex: 0,
attachments: [att],
iframe: null
});
return false;
}
refresh(): any {
2019-08-06 02:11:29 +02:00
this.showFloatingHeader = false;
this.showFloatingStatusMenu = false;
this.load(this.lastAccountName);
}
relationshipChanged(relationship: Relationship){
this.relationship = relationship;
}
2019-08-07 02:16:10 +02:00
browseAccount(accountName: string): void {
2020-05-29 06:32:55 +02:00
if (accountName === this.toolsService.getAccountFullHandle(this.displayedAccount)) return;
2019-08-14 06:34:04 +02:00
this.browseAccountEvent.next(accountName);
2019-08-07 02:16:10 +02:00
}
openMigratedAccount(account: Account): boolean {
const handle = this.toolsService.getAccountFullHandle(account);
this.browseAccount(handle);
2019-08-06 00:42:06 +02:00
return false;
}
follow(): boolean {
2024-03-08 06:19:39 +01:00
this.loadingRelationShip = true;
2019-04-07 20:47:09 +02:00
const userAccount = this.toolsService.getSelectedAccounts()[0];
2024-03-08 06:19:39 +01:00
let foundAccountToFollow: Account;
2019-04-07 20:47:09 +02:00
this.toolsService.findAccount(userAccount, this.lastAccountName)
2019-02-23 07:00:33 +01:00
.then((account: Account) => {
2024-03-08 06:19:39 +01:00
foundAccountToFollow = account;
2019-04-07 20:47:09 +02:00
return this.mastodonService.follow(userAccount, account);
2019-02-23 07:00:33 +01:00
})
.then((relationship: Relationship) => {
2024-03-08 06:19:39 +01:00
this.relationship = relationship;
})
.then(async () => {
// Double check for pleroma users
const instanceInfo = await this.toolsService.getInstanceInfo(userAccount);
if(instanceInfo.type === InstanceType.Pleroma || instanceInfo.type === InstanceType.Akkoma){
await new Promise(resolve => setTimeout(resolve, 1000))
const relationships = await this.mastodonService.getRelationships(userAccount, [foundAccountToFollow]);
const relationship = relationships.find(x => x.id === foundAccountToFollow.id);
if(relationship){
this.relationship = relationship;
}
}
2019-02-23 07:00:33 +01:00
})
.catch((err: HttpErrorResponse) => {
2019-09-07 23:52:07 +02:00
this.notificationService.notifyHttpError(err, userAccount);
2024-03-08 06:19:39 +01:00
})
.then(() => {
this.loadingRelationShip = false;
2019-02-23 07:00:33 +01:00
});
return false;
}
unfollow(): boolean {
2024-03-08 06:19:39 +01:00
this.loadingRelationShip = true;
2019-06-15 20:32:08 +02:00
const userAccount = this.toolsService.getSelectedAccounts()[0];
2019-04-07 20:47:09 +02:00
this.toolsService.findAccount(userAccount, this.lastAccountName)
2019-02-23 07:00:33 +01:00
.then((account: Account) => {
2019-04-07 20:47:09 +02:00
return this.mastodonService.unfollow(userAccount, account);
2019-02-23 07:00:33 +01:00
})
.then((relationship: Relationship) => {
this.relationship = relationship;
})
.catch((err: HttpErrorResponse) => {
2019-09-07 23:52:07 +02:00
this.notificationService.notifyHttpError(err, userAccount);
2024-03-08 06:19:39 +01:00
})
.then(() => {
this.loadingRelationShip = false;
2019-02-23 07:00:33 +01:00
});
return false;
}
2019-04-07 20:47:09 +02:00
onScroll() {
var element = this.statustream.nativeElement as HTMLElement;
const atBottom = element.scrollHeight <= element.clientHeight + element.scrollTop + 1000;
2019-08-10 20:27:38 +02:00
if (element.scrollTop > 135) {
2019-08-06 02:11:29 +02:00
this.showFloatingHeader = true;
} else {
this.showFloatingHeader = false;
}
2020-05-29 06:32:55 +02:00
if (this.profilestatuses) {
const menuPosition = element.scrollHeight - this.profilestatuses.nativeElement.offsetHeight - 30 - 31;
if (element.scrollTop > menuPosition) {
this.showFloatingStatusMenu = true;
} else {
this.showFloatingStatusMenu = false;
}
2019-08-09 05:30:51 +02:00
}
2019-04-07 20:47:09 +02:00
if (atBottom) {
this.scrolledToBottom();
}
}
2019-06-15 20:32:08 +02:00
2019-04-07 20:47:09 +02:00
private scrolledToBottom() {
2020-05-29 06:32:55 +02:00
if (this.statusLoading || this.maxReached || !this.displayedAccount) return;
2019-04-07 20:47:09 +02:00
2019-08-09 04:41:59 +02:00
const onlyMedia = this.statusSection === 'media';
const excludeReplies = this.statusSection === 'status';
this.getStatuses(this.currentlyUsedAccount, this.displayedAccount, onlyMedia, excludeReplies, this.maxId);
2019-04-07 20:47:09 +02:00
}
2019-06-15 20:32:08 +02:00
private loadStatus(userAccount: AccountInfo, statuses: Status[]) {
2019-04-07 20:47:09 +02:00
if (statuses.length === 0) {
this.maxReached = true;
return;
}
for (const status of statuses) {
2020-04-01 08:29:51 +02:00
let cwPolicy = this.toolsService.checkContentWarning(status);
const wrapper = new StatusWrapper(cwPolicy.status, userAccount, cwPolicy.applyCw, cwPolicy.hide);
2019-04-07 20:47:09 +02:00
this.statuses.push(wrapper);
}
this.maxId = this.statuses[this.statuses.length - 1].status.id;
}
2019-07-06 07:28:30 +02:00
openAccount(): boolean {
window.open(this.displayedAccount.url, '_blank');
return false;
}
2019-08-09 04:11:12 +02:00
2019-08-09 04:41:59 +02:00
switchProfileSection(section: 'fields' | 'choices' | 'hashtags'): boolean {
2019-08-09 04:11:12 +02:00
this.profileSection = section;
return false;
}
2019-08-10 19:40:08 +02:00
isSwitchingSection: boolean;
2019-08-09 04:41:59 +02:00
switchStatusSection(section: 'status' | 'replies' | 'media'): boolean {
2019-08-11 07:00:58 +02:00
this.isSwitchingSection = true;
2019-08-10 19:40:08 +02:00
2019-08-09 04:11:12 +02:00
this.statusSection = section;
2019-08-09 04:41:59 +02:00
this.statuses.length = 0;
this.maxId = null;
2019-08-10 19:40:08 +02:00
// this.showFloatingHeader = false;
// this.showFloatingStatusMenu = false;
2019-08-09 05:30:51 +02:00
2019-08-10 19:40:08 +02:00
let promise: Promise<any>;
2019-08-09 04:41:59 +02:00
switch (section) {
case "status":
2019-08-10 19:40:08 +02:00
promise = this.getStatuses(this.currentlyUsedAccount, this.displayedAccount, false, true, this.maxId);
2019-08-09 04:41:59 +02:00
break;
case "replies":
2019-08-10 19:40:08 +02:00
promise = this.getStatuses(this.currentlyUsedAccount, this.displayedAccount, false, false, this.maxId);
2019-08-09 04:41:59 +02:00
break;
case "media":
2019-08-10 19:40:08 +02:00
promise = this.getStatuses(this.currentlyUsedAccount, this.displayedAccount, true, true, this.maxId);
2019-08-09 04:41:59 +02:00
break;
}
2019-08-10 19:40:08 +02:00
if (promise) {
promise
2019-08-10 21:00:47 +02:00
.catch(err => {
2019-08-10 19:40:08 +02:00
})
.then(() => {
this.isSwitchingSection = false;
});
} else {
this.isSwitchingSection = false;
}
2019-08-09 04:41:59 +02:00
2019-08-10 21:00:47 +02:00
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);
}
2019-08-09 04:11:12 +02:00
return false;
}
2019-08-09 05:00:49 +02:00
2019-08-09 05:30:51 +02:00
openAttachment(attachment: Attachment): boolean {
2019-08-09 05:00:49 +02:00
let openMediaEvent = new OpenMediaEvent(0, [attachment], null);
this.navigationService.openMedia(openMediaEvent);
return false;
}
@Output() browseFollowsEvent = new EventEmitter<string>();
@Output() browseFollowersEvent = new EventEmitter<string>();
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;
}
}