309 lines
12 KiB
TypeScript
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;
|
|
}
|
|
}
|
|
}
|
|
|