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

309 lines
12 KiB
TypeScript

import { Component, Input } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { Observable, Subscription } from 'rxjs';
import { Store } from '@ngxs/store';
import { StreamElement, StreamTypeEnum } from '../../../states/streams.state';
import { AccountInfo } from '../../../states/accounts.state';
import { StreamingService, EventEnum, StatusUpdate } from '../../../services/streaming.service';
import { Status } from '../../../services/models/mastodon.interfaces';
import { MastodonWrapperService } from '../../../services/mastodon-wrapper.service';
import { NotificationService } from '../../../services/notification.service';
import { ToolsService } from '../../../services/tools.service';
import { StatusWrapper } from '../../../models/common.model';
import { TimeLineModeEnum } from '../../../states/settings.state';
import { TimelineBase } from '../../common/timeline-base';
import { SettingsService } from '../../../services/settings.service';
@Component({
selector: 'app-stream-statuses',
templateUrl: './stream-statuses.component.html',
styleUrls: ['./stream-statuses.component.scss']
})
export class StreamStatusesComponent extends TimelineBase {
protected _streamElement: StreamElement;
context: 'home' | 'notifications' | 'public' | 'thread' | 'account';
@Input()
set streamElement(streamElement: StreamElement) {
this._streamElement = streamElement;
this.hideBoosts = streamElement.hideBoosts;
this.hideBots = streamElement.hideBots;
this.hideReplies = streamElement.hideReplies;
this.load(this._streamElement);
this.setContext(this._streamElement);
}
get streamElement(): StreamElement {
return this._streamElement;
}
private goToTopSubscription: Subscription;
private streamsSubscription: Subscription;
private hideAccountSubscription: Subscription;
private deleteStatusSubscription: Subscription;
private streams$: Observable<StreamElement[]>;
constructor(
protected readonly settingsService: SettingsService,
protected readonly store: Store,
protected readonly toolsService: ToolsService,
protected readonly notificationService: NotificationService,
protected readonly streamingService: StreamingService,
protected readonly mastodonService: MastodonWrapperService) {
super(toolsService, notificationService, mastodonService);
this.streams$ = this.store.select(state => state.streamsstatemodel.streams);
}
ngOnInit() {
let settings = this.settingsService.getSettings();
this.timelineLoadingMode = settings.timelineMode;
this.goToTopSubscription = this.goToTop.subscribe(() => {
this.applyGoToTop();
});
this.streamsSubscription = this.streams$.subscribe((streams: StreamElement[]) => {
let updatedStream = streams.find(x => x.id === this.streamElement.id);
if (!updatedStream) return;
if (this.hideBoosts !== updatedStream.hideBoosts
|| this.hideBots !== updatedStream.hideBots
|| this.hideReplies !== updatedStream.hideReplies) {
this.streamElement = updatedStream;
}
});
this.hideAccountSubscription = this.notificationService.hideAccountUrlStream.subscribe((accountUrl: string) => {
if (accountUrl) {
this.statuses = this.statuses.filter(x => {
if (x.status.reblog) {
return x.status.reblog.account.url != accountUrl;
} else {
return x.status.account.url != accountUrl;
}
});
this.bufferStream = this.bufferStream.filter(x => {
if (x.reblog) {
return x.reblog.account.url != accountUrl;
} else {
return x.account.url != accountUrl;
}
});
}
});
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);
});
}
});
this.numNewItems = 0;
}
ngOnDestroy() {
if (this.goToTopSubscription) this.goToTopSubscription.unsubscribe();
if (this.streamsSubscription) this.streamsSubscription.unsubscribe();
if (this.hideAccountSubscription) this.hideAccountSubscription.unsubscribe();
if (this.deleteStatusSubscription) this.deleteStatusSubscription.unsubscribe();
}
private setContext(streamElement: StreamElement) {
switch(streamElement.type){
case StreamTypeEnum.global:
case StreamTypeEnum.local:
case StreamTypeEnum.tag:
this.context = 'public';
break;
case StreamTypeEnum.personnal:
case StreamTypeEnum.list:
this.context = 'home';
break;
case StreamTypeEnum.activity:
case StreamTypeEnum.directmessages:
this.context = 'notifications';
break;
}
}
refresh(): any {
this.load(this._streamElement);
}
protected load(streamElement: StreamElement) {
this.resetStream();
if (this.userLocked) {
const splitedUserName = streamElement.accountId.split('@');
const user = splitedUserName[0];
const instance = splitedUserName[1];
this.account = this.getRegisteredAccounts().find(x => x.username == user && x.instance == instance);
} else {
this.account = this.toolsService.getSelectedAccounts()[0];
}
this.retrieveToots();
this.launchWebsocket();
}
private resetStream() {
this.statuses.length = 0;
this.bufferStream.length = 0;
this.numNewItems = 0;
if (this.websocketStreaming) this.websocketStreaming.dispose();
}
private launchWebsocket(): void {
this.websocketStreaming = this.streamingService.getStreaming(this.account, this._streamElement);
this.websocketStreaming.statusUpdateSubjet.subscribe((update: StatusUpdate) => {
if (update) {
if (update.type === EventEnum.update) {
if (!this.statuses.find(x => x.status.id == update.status.id)) {
if ((this.streamPositionnedAtTop || this.timelineLoadingMode === TimeLineModeEnum.Continuous)
&& this.timelineLoadingMode !== TimeLineModeEnum.SlowMode) {
if (this.isFiltered(update.status)) {
return;
}
let cwPolicy = this.toolsService.checkContentWarning(update.status);
const wrapper = new StatusWrapper(cwPolicy.status, this.account, cwPolicy.applyCw, cwPolicy.hide);
this.statuses.unshift(wrapper);
} else {
this.bufferStream.push(update.status);
this.numNewItems++;
}
}
} else if (update.type === EventEnum.delete) {
this.statuses = this.statuses.filter(x => !(x.status.id === update.messageId && this.account.id === update.account.id));
this.bufferStream = this.bufferStream.filter(x => !(x.id === update.messageId && x.url.replace('https://', '').split('/')[0] === update.account.instance));
}
}
this.checkAndCleanUpStream();
});
}
protected statusProcessOnGoToTop(){
if (this.statuses.length > 2 * this.streamingService.nbStatusPerIteration) {
this.statuses.length = 2 * this.streamingService.nbStatusPerIteration;
}
}
textSelected(): void {
console.warn(`status comp: textSelected`); //TODO
}
protected scrolledToTop() {
this.streamPositionnedAtTop = true;
if (this.timelineLoadingMode !== TimeLineModeEnum.SlowMode) {
this.loadBuffer();
}
}
loadBuffer(): boolean {
if (this.bufferWasCleared) {
this.statuses.length = 0;
this.bufferWasCleared = false;
}
for (const status of this.bufferStream) {
if (this.isFiltered(status)) {
continue;
}
let cwPolicy = this.toolsService.checkContentWarning(status);
const wrapper = new StatusWrapper(cwPolicy.status, this.account, cwPolicy.applyCw, cwPolicy.hide);
this.statuses.unshift(wrapper);
}
this.bufferStream.length = 0;
this.numNewItems = 0;
return false;
}
protected getNextStatuses(): Promise<Status[]> {
const lastStatus = this.statuses[this.statuses.length - 1];
return this.mastodonService.getTimeline(this.account, this._streamElement.type, lastStatus.status.id, null, this.streamingService.nbStatusPerIteration, this._streamElement.tag, this._streamElement.listId)
.then((status: Status[]) =>{
return status.filter(x => !this.isFiltered(x));
});
}
private isFiltered(status: Status): boolean {
if (this.streamElement.hideBoosts) {
if (status.reblog) {
return true;
}
}
if (this.streamElement.hideBots) {
if (status.account.bot) {
return true;
}
}
if (this.streamElement.hideReplies) {
if (status.in_reply_to_account_id && status.account.id !== status.in_reply_to_account_id) {
return true;
}
}
return false;
}
private getRegisteredAccounts(): AccountInfo[] {
var regAccounts = <AccountInfo[]>this.store.snapshot().registeredaccounts.accounts;
return regAccounts;
}
// @ViewChildren('status') private statusEls: QueryList<ElementRef>;
focus(): boolean {
setTimeout(() => {
var element = this.statustream.nativeElement as HTMLElement;
element.focus({ preventScroll: true });
}, 0);
return false;
}
private retrieveToots(): void {
this.mastodonService.getTimeline(this.account, this._streamElement.type, null, null, this.streamingService.nbStatusPerIteration, this._streamElement.tag, this._streamElement.listId)
.then((results: Status[]) => {
this.isLoading = false;
for (const s of results) {
if (this.isFiltered(s)) {
continue;
}
let cwPolicy = this.toolsService.checkContentWarning(s);
const wrapper = new StatusWrapper(cwPolicy.status, this.account, cwPolicy.applyCw, cwPolicy.hide);
this.statuses.push(wrapper);
}
})
.catch((err: HttpErrorResponse) => {
this.notificationService.notifyHttpError(err, this.account);
});
}
private checkAndCleanUpStream(): void {
if (this.streamPositionnedAtTop && this.statuses.length > 3 * this.streamingService.nbStatusPerIteration) {
this.statuses.length = 2 * this.streamingService.nbStatusPerIteration;
this.maxReached = false;
this.lastCallReachedMax = false;
}
if (this.bufferStream.length > 3 * this.streamingService.nbStatusPerIteration) {
this.bufferWasCleared = true;
this.bufferStream.length = 2 * this.streamingService.nbStatusPerIteration;
}
}
}