diff --git a/src/app/components/common/timeline-base.ts b/src/app/components/common/timeline-base.ts new file mode 100644 index 00000000..6443b3b4 --- /dev/null +++ b/src/app/components/common/timeline-base.ts @@ -0,0 +1,104 @@ +import { OnInit, Input, OnDestroy, Output, EventEmitter } from '@angular/core'; +import { HttpErrorResponse } from '@angular/common/http'; +import { Store } from '@ngxs/store'; +import { Observable } from 'rxjs'; + +import { MastodonWrapperService } from '../../services/mastodon-wrapper.service'; +import { AccountInfo } from '../../states/accounts.state'; +import { StreamingService, StreamingWrapper } from '../../services/streaming.service'; +import { NotificationService } from '../../services/notification.service'; +import { ToolsService, OpenThreadEvent } from '../../services/tools.service'; +import { StatusWrapper } from '../../models/common.model'; +import { Status } from '../../services/models/mastodon.interfaces'; +import { StreamElement } from '../../states/streams.state'; +import { TimeLineModeEnum } from '../../states/settings.state'; + +export abstract class TimelineBase implements OnInit, OnDestroy { + isLoading = true; + protected maxReached = false; + isThread = false; + displayError: string; + hasContentWarnings = false; + + timelineLoadingMode: TimeLineModeEnum = TimeLineModeEnum.OnTop; + + + protected account: AccountInfo; + protected websocketStreaming: StreamingWrapper; + + statuses: StatusWrapper[] = []; + bufferStream: Status[] = []; + protected bufferWasCleared: boolean; + streamPositionnedAtTop: boolean = true; + protected isProcessingInfiniteScroll: boolean; + + protected hideBoosts: boolean; + protected hideReplies: boolean; + protected hideBots: boolean; + + @Output() browseAccountEvent = new EventEmitter(); + @Output() browseHashtagEvent = new EventEmitter(); + @Output() browseThreadEvent = new EventEmitter(); + + @Input() goToTop: Observable; + + @Input() userLocked = true; + + constructor( + // protected readonly store: Store, + protected readonly toolsService: ToolsService, + protected readonly notificationService: NotificationService, + // protected readonly streamingService: StreamingService, + protected readonly mastodonService: MastodonWrapperService) { + + // this.streams$ = this.store.select(state => state.streamsstatemodel.streams); + } + + abstract ngOnInit(); + abstract ngOnDestroy(); + // protected abstract load(streamElement: StreamElement); + + protected scrolledToBottom() { + if (this.isLoading || this.maxReached) return; + + this.isLoading = true; + this.isProcessingInfiniteScroll = true; + + this.getNextStatuses() + .then((status: Status[]) => { + if (!status || status.length === 0 || this.maxReached) { + this.maxReached = true; + return; + } + + for (const s of status) { + 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); + }) + .then(() => { + this.isLoading = false; + this.isProcessingInfiniteScroll = false; + }); + } + + protected abstract getNextStatuses(): Promise; + + + + browseAccount(accountName: string): void { + this.browseAccountEvent.next(accountName); + } + + browseHashtag(hashtag: string): void { + this.browseHashtagEvent.next(hashtag); + } + + browseThread(openThreadEvent: OpenThreadEvent): void { + this.browseThreadEvent.next(openThreadEvent); + } +} \ No newline at end of file diff --git a/src/app/components/floating-column/manage-account/bookmarks/bookmarks.component.ts b/src/app/components/floating-column/manage-account/bookmarks/bookmarks.component.ts index 0b148cb7..7540151e 100644 --- a/src/app/components/floating-column/manage-account/bookmarks/bookmarks.component.ts +++ b/src/app/components/floating-column/manage-account/bookmarks/bookmarks.component.ts @@ -8,50 +8,58 @@ import { MastodonWrapperService } from '../../../../services/mastodon-wrapper.se import { Status } from '../../../../services/models/mastodon.interfaces'; import { NotificationService } from '../../../../services/notification.service'; import { TimeLineModeEnum } from '../../../../states/settings.state'; +import { TimelineBase } from '../../../../components/common/timeline-base'; @Component({ selector: 'app-bookmarks', templateUrl: '../../../stream/stream-statuses/stream-statuses.component.html', styleUrls: ['../../../stream/stream-statuses/stream-statuses.component.scss', './bookmarks.component.scss'] }) -export class BookmarksComponent implements OnInit { - statuses: StatusWrapper[] = []; - displayError: string; - isLoading = true; - isThread = false; - hasContentWarnings = false; +export class BookmarksComponent extends TimelineBase { + // statuses: StatusWrapper[] = []; + // displayError: string; + // isLoading = true; + // isThread = false; + // hasContentWarnings = false; - bufferStream: Status[] = []; //html compatibility only - streamPositionnedAtTop: boolean = true; //html compatibility only - timelineLoadingMode: TimeLineModeEnum = TimeLineModeEnum.OnTop; //html compatibility only + // bufferStream: Status[] = []; //html compatibility only + // streamPositionnedAtTop: boolean = true; //html compatibility only + // timelineLoadingMode: TimeLineModeEnum = TimeLineModeEnum.OnTop; //html compatibility only - @Output() browseAccountEvent = new EventEmitter(); - @Output() browseHashtagEvent = new EventEmitter(); - @Output() browseThreadEvent = new EventEmitter(); + // @Output() browseAccountEvent = new EventEmitter(); + // @Output() browseHashtagEvent = new EventEmitter(); + // @Output() browseThreadEvent = new EventEmitter(); - private maxReached = false; + // private maxReached = false; private maxId: string; - private _account: AccountWrapper; + private _accountWrapper: AccountWrapper; @Input('account') - set account(acc: AccountWrapper) { - this._account = acc; + set accountWrapper(acc: AccountWrapper) { + this._accountWrapper = acc; + this.account = acc.info; this.getBookmarks(); } - get account(): AccountWrapper { - return this._account; + get accountWrapper(): AccountWrapper { + return this._accountWrapper; } @ViewChild('statusstream') public statustream: ElementRef; constructor( - private readonly toolsService: ToolsService, - private readonly notificationService: NotificationService, - private readonly mastodonService: MastodonWrapperService) { } + protected readonly toolsService: ToolsService, + protected readonly notificationService: NotificationService, + protected readonly mastodonService: MastodonWrapperService) { + + super(toolsService, notificationService, mastodonService); + } ngOnInit() { } + ngOnDestroy() { + } + private reset() { this.isLoading = true; this.statuses.length = 0; @@ -62,17 +70,17 @@ export class BookmarksComponent implements OnInit { private getBookmarks() { this.reset(); - this.mastodonService.getBookmarks(this.account.info) + this.mastodonService.getBookmarks(this.account) .then((result: BookmarkResult) => { this.maxId = result.max_id; for (const s of result.bookmarked) { let cwPolicy = this.toolsService.checkContentWarning(s); - const wrapper = new StatusWrapper(cwPolicy.status, this.account.info, cwPolicy.applyCw, cwPolicy.hide); + const wrapper = new StatusWrapper(cwPolicy.status, this.account, cwPolicy.applyCw, cwPolicy.hide); this.statuses.push(wrapper); } }) .catch(err => { - this.notificationService.notifyHttpError(err, this.account.info); + this.notificationService.notifyHttpError(err, this.account); }) .then(() => { this.isLoading = false; @@ -89,45 +97,59 @@ export class BookmarksComponent implements OnInit { } - private scrolledToBottom() { - if (this.isLoading || this.maxReached) return; + // private scrolledToBottom() { + // if (this.isLoading || this.maxReached) return; - this.isLoading = true; - this.mastodonService.getBookmarks(this.account.info, this.maxId) + // this.isLoading = true; + // this.mastodonService.getBookmarks(this.account, this.maxId) + // .then((result: BookmarkResult) => { + // const statuses = result.bookmarked; + // if (statuses.length === 0 || !this.maxId) { + // this.maxReached = true; + // return; + // } + + // this.maxId = result.max_id; + // for (const s of statuses) { + // let cwPolicy = this.toolsService.checkContentWarning(s); + // const wrapper = new StatusWrapper(cwPolicy.status, this.account, cwPolicy.applyCw, cwPolicy.hide); + // this.statuses.push(wrapper); + // } + // }) + // .catch(err => { + // this.notificationService.notifyHttpError(err, this.account); + // }) + // .then(() => { + // this.isLoading = false; + // }); + // } + + protected getNextStatuses(): Promise { + return this.mastodonService.getBookmarks(this.account, this.maxId) .then((result: BookmarkResult) => { const statuses = result.bookmarked; - if (statuses.length === 0 || !this.maxId) { + this.maxId = result.max_id; + + if(!this.maxId){ this.maxReached = true; - return; } - this.maxId = result.max_id; - for (const s of statuses) { - let cwPolicy = this.toolsService.checkContentWarning(s); - const wrapper = new StatusWrapper(cwPolicy.status, this.account.info, cwPolicy.applyCw, cwPolicy.hide); - this.statuses.push(wrapper); - } - }) - .catch(err => { - this.notificationService.notifyHttpError(err, this.account.info); - }) - .then(() => { - this.isLoading = false; + return statuses; }); } - browseAccount(accountName: string): void { - this.browseAccountEvent.next(accountName); - } + // browseAccount(accountName: string): void { + // this.browseAccountEvent.next(accountName); + // } - browseHashtag(hashtag: string): void { - this.browseHashtagEvent.next(hashtag); - } + // browseHashtag(hashtag: string): void { + // this.browseHashtagEvent.next(hashtag); + // } + + // browseThread(openThreadEvent: OpenThreadEvent): void { + // this.browseThreadEvent.next(openThreadEvent); + // } - browseThread(openThreadEvent: OpenThreadEvent): void { - this.browseThreadEvent.next(openThreadEvent); - } - applyGoToTop(): boolean { const stream = this.statustream.nativeElement as HTMLElement; setTimeout(() => { diff --git a/src/app/components/stream/stream-statuses/stream-statuses.component.ts b/src/app/components/stream/stream-statuses/stream-statuses.component.ts index 435f2b1d..89eedd3a 100644 --- a/src/app/components/stream/stream-statuses/stream-statuses.component.ts +++ b/src/app/components/stream/stream-statuses/stream-statuses.component.ts @@ -12,38 +12,60 @@ import { NotificationService } from '../../../services/notification.service'; import { OpenThreadEvent, ToolsService } from '../../../services/tools.service'; import { StatusWrapper } from '../../../models/common.model'; import { TimeLineModeEnum } from '../../../states/settings.state'; +import { TimelineBase } from '../../common/timeline-base'; @Component({ selector: 'app-stream-statuses', templateUrl: './stream-statuses.component.html', styleUrls: ['./stream-statuses.component.scss'] }) -export class StreamStatusesComponent implements OnInit, OnDestroy { - isLoading = true; - private lastInfinityFetchReturnedNothing = false; - isThread = false; - displayError: string; - hasContentWarnings = false; +// export class StreamStatusesComponent implements OnInit, OnDestroy { +export class StreamStatusesComponent extends TimelineBase { + // isLoading = true; + // private lastInfinityFetchReturnedNothing = false; + // isThread = false; + // displayError: string; + // hasContentWarnings = false; - timelineLoadingMode: TimeLineModeEnum; + // timelineLoadingMode: TimeLineModeEnum; - private _streamElement: StreamElement; - private account: AccountInfo; - private websocketStreaming: StreamingWrapper; + // private _streamElement: StreamElement; + // private account: AccountInfo; + // private websocketStreaming: StreamingWrapper; - statuses: StatusWrapper[] = []; - bufferStream: Status[] = []; - private bufferWasCleared: boolean; - streamPositionnedAtTop: boolean = true; - private isProcessingInfiniteScroll: boolean; + // statuses: StatusWrapper[] = []; + // bufferStream: Status[] = []; + // private bufferWasCleared: boolean; + // streamPositionnedAtTop: boolean = true; + // private isProcessingInfiniteScroll: boolean; - private hideBoosts: boolean; - private hideReplies: boolean; - private hideBots: boolean; + // private hideBoosts: boolean; + // private hideReplies: boolean; + // private hideBots: boolean; - @Output() browseAccountEvent = new EventEmitter(); - @Output() browseHashtagEvent = new EventEmitter(); - @Output() browseThreadEvent = new EventEmitter(); + // @Output() browseAccountEvent = new EventEmitter(); + // @Output() browseHashtagEvent = new EventEmitter(); + // @Output() browseThreadEvent = new EventEmitter(); + + // @Input() + // set streamElement(streamElement: StreamElement) { + // this._streamElement = streamElement; + + // this.hideBoosts = streamElement.hideBoosts; + // this.hideBots = streamElement.hideBots; + // this.hideReplies = streamElement.hideReplies; + + // this.load(this._streamElement); + // } + // get streamElement(): StreamElement { + // return this._streamElement; + // } + + // @Input() goToTop: Observable; + + // @Input() userLocked = true; + + protected _streamElement: StreamElement; @Input() set streamElement(streamElement: StreamElement) { @@ -59,10 +81,6 @@ export class StreamStatusesComponent implements OnInit, OnDestroy { return this._streamElement; } - @Input() goToTop: Observable; - - @Input() userLocked = true; - private goToTopSubscription: Subscription; private streamsSubscription: Subscription; private hideAccountSubscription: Subscription; @@ -70,12 +88,13 @@ export class StreamStatusesComponent implements OnInit, OnDestroy { private streams$: Observable; constructor( - private readonly store: Store, - private readonly toolsService: ToolsService, - private readonly notificationService: NotificationService, - private readonly streamingService: StreamingService, - private readonly mastodonService: MastodonWrapperService) { + 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); } @@ -138,7 +157,7 @@ export class StreamStatusesComponent implements OnInit, OnDestroy { this.load(this._streamElement); } - private load(streamElement: StreamElement) { + protected load(streamElement: StreamElement) { this.resetStream(); if (this.userLocked) { @@ -221,17 +240,17 @@ export class StreamStatusesComponent implements OnInit, OnDestroy { } } - browseAccount(accountName: string): void { - this.browseAccountEvent.next(accountName); - } + // browseAccount(accountName: string): void { + // this.browseAccountEvent.next(accountName); + // } - browseHashtag(hashtag: string): void { - this.browseHashtagEvent.next(hashtag); - } + // browseHashtag(hashtag: string): void { + // this.browseHashtagEvent.next(hashtag); + // } - browseThread(openThreadEvent: OpenThreadEvent): void { - this.browseThreadEvent.next(openThreadEvent); - } + // browseThread(openThreadEvent: OpenThreadEvent): void { + // this.browseThreadEvent.next(openThreadEvent); + // } textSelected(): void { console.warn(`status comp: textSelected`); //TODO @@ -265,37 +284,65 @@ export class StreamStatusesComponent implements OnInit, OnDestroy { return false; } - private scrolledToBottom() { - if (this.isLoading || this.lastInfinityFetchReturnedNothing) return; + // private scrolledToBottom() { + // if (this.isLoading || this.lastInfinityFetchReturnedNothing) return; - this.isLoading = true; - this.isProcessingInfiniteScroll = true; + // this.isLoading = true; + // this.isProcessingInfiniteScroll = true; + // const lastStatus = this.statuses[this.statuses.length - 1]; + // this.mastodonService.getTimeline(this.account, this._streamElement.type, lastStatus.status.id, null, this.streamingService.nbStatusPerIteration, this._streamElement.tag, this._streamElement.listId) + // .then((status: Status[]) => { + // for (const s of status) { + // 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); + // } + + // if (!status || status.length === 0) { + // this.lastInfinityFetchReturnedNothing = true; + // } + // }) + // .catch((err: HttpErrorResponse) => { + // this.notificationService.notifyHttpError(err, this.account); + // }) + // .then(() => { + // this.isLoading = false; + // this.isProcessingInfiniteScroll = false; + // }); + // } + + protected getNextStatuses(): Promise { const lastStatus = this.statuses[this.statuses.length - 1]; - this.mastodonService.getTimeline(this.account, this._streamElement.type, lastStatus.status.id, null, this.streamingService.nbStatusPerIteration, this._streamElement.tag, this._streamElement.listId) - .then((status: Status[]) => { - for (const s of status) { - 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); - } - - if (!status || status.length === 0) { - this.lastInfinityFetchReturnedNothing = true; - } - }) - .catch((err: HttpErrorResponse) => { - this.notificationService.notifyHttpError(err, this.account); - }) - .then(() => { - this.isLoading = false; - this.isProcessingInfiniteScroll = false; + 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 = this.store.snapshot().registeredaccounts.accounts; @@ -306,7 +353,7 @@ export class StreamStatusesComponent implements OnInit, OnDestroy { focus(): boolean { setTimeout(() => { var element = this.statustream.nativeElement as HTMLElement; - element.focus({preventScroll:true}); + element.focus({ preventScroll: true }); }, 0); return false; } @@ -333,7 +380,7 @@ export class StreamStatusesComponent implements OnInit, OnDestroy { private checkAndCleanUpStream(): void { if (this.streamPositionnedAtTop && this.statuses.length > 3 * this.streamingService.nbStatusPerIteration) { this.statuses.length = 2 * this.streamingService.nbStatusPerIteration; - this.lastInfinityFetchReturnedNothing = false; + this.maxReached = false; } if (this.bufferStream.length > 3 * this.streamingService.nbStatusPerIteration) { @@ -342,23 +389,23 @@ export class StreamStatusesComponent implements OnInit, OnDestroy { } } - 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 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; + // } }