creation of a dedicated controller for loading the statuses
This commit is contained in:
parent
f33f2e37bb
commit
27d3507922
@ -41,6 +41,7 @@ import { HashtagComponent } from './components/stream/hashtag/hashtag.component'
|
||||
import { StreamOverlayComponent } from './components/stream/stream-overlay/stream-overlay.component';
|
||||
import { DatabindedTextComponent } from './components/stream/status/databinded-text/databinded-text.component';
|
||||
import { TimeAgoPipe } from './pipes/time-ago.pipe';
|
||||
import { StreamStatusesComponent } from './components/stream/stream-statuses/stream-statuses.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: "", redirectTo: "home", pathMatch: "full" },
|
||||
@ -74,7 +75,8 @@ const routes: Routes = [
|
||||
HashtagComponent,
|
||||
StreamOverlayComponent,
|
||||
DatabindedTextComponent,
|
||||
TimeAgoPipe
|
||||
TimeAgoPipe,
|
||||
StreamStatusesComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
@ -16,7 +16,6 @@ export class FloatingColumnComponent implements OnInit {
|
||||
|
||||
openPanel: string;
|
||||
|
||||
|
||||
constructor(private readonly navigationService: NavigationService) { }
|
||||
|
||||
ngOnInit() {
|
||||
|
@ -0,0 +1,6 @@
|
||||
<div class="stream-toots flexcroll" #statusstream (scroll)="onScroll()">
|
||||
<!-- data-simplebar -->
|
||||
<div class="stream-toots__status" *ngFor="let statusWrapper of statuses">
|
||||
<app-status [statusWrapper]="statusWrapper" (browseAccount)="accountSelected($event)" (browseHashtag)="hashtagSelected($event)"></app-status>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,13 @@
|
||||
@import "variables";
|
||||
@import "commons";
|
||||
|
||||
.stream-toots {
|
||||
height: calc(100%);
|
||||
width: calc(100%);
|
||||
|
||||
overflow: auto;
|
||||
&__status:not(:last-child) {
|
||||
border: solid #06070b;
|
||||
border-width: 0 0 1px 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { StreamStatusesComponent } from './stream-statuses.component';
|
||||
|
||||
describe('StreamStatusesComponent', () => {
|
||||
let component: StreamStatusesComponent;
|
||||
let fixture: ComponentFixture<StreamStatusesComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ StreamStatusesComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(StreamStatusesComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,198 @@
|
||||
import { Component, OnInit, Input, ViewChild, ElementRef, OnDestroy, EventEmitter, Output } from '@angular/core';
|
||||
import { Store } from '@ngxs/store';
|
||||
|
||||
import { StreamElement } from '../../../states/streams.state';
|
||||
import { AccountInfo } from '../../../states/accounts.state';
|
||||
import { StreamingService, EventEnum, StreamingWrapper, StatusUpdate } from '../../../services/streaming.service';
|
||||
import { Status } from '../../../services/models/mastodon.interfaces';
|
||||
import { MastodonService } from '../../../services/mastodon.service';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { StatusWrapper } from '../stream.component';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-stream-statuses',
|
||||
templateUrl: './stream-statuses.component.html',
|
||||
styleUrls: ['./stream-statuses.component.scss']
|
||||
})
|
||||
export class StreamStatusesComponent implements OnInit, OnDestroy {
|
||||
private _streamElement: StreamElement;
|
||||
private account: AccountInfo;
|
||||
private websocketStreaming: StreamingWrapper;
|
||||
|
||||
statuses: StatusWrapper[] = [];
|
||||
private bufferStream: Status[] = [];
|
||||
private bufferWasCleared: boolean;
|
||||
|
||||
@Output() browseAccount = new EventEmitter<string>();
|
||||
@Output() browseHashtag = new EventEmitter<string>();
|
||||
@Output() browseThread = new EventEmitter<string>();
|
||||
|
||||
@Input()
|
||||
set streamElement(streamElement: StreamElement) {
|
||||
this._streamElement = streamElement;
|
||||
|
||||
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);
|
||||
|
||||
this.retrieveToots();
|
||||
this.launchWebsocket();
|
||||
}
|
||||
get streamElement(): StreamElement {
|
||||
return this._streamElement;
|
||||
}
|
||||
|
||||
@Input() goToTop: Observable<void>;
|
||||
|
||||
private goToTopSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
private readonly store: Store,
|
||||
private readonly streamingService: StreamingService,
|
||||
private readonly mastodonService: MastodonService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.goToTopSubscription = this.goToTop.subscribe(() => {
|
||||
this.applyGoToTop();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(){
|
||||
if( this.goToTopSubscription) this.goToTopSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
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) {
|
||||
const wrapper = new StatusWrapper(update.status, this.account);
|
||||
this.statuses.unshift(wrapper);
|
||||
} else {
|
||||
this.bufferStream.push(update.status);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.checkAndCleanUpStream();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ViewChild('statusstream') public statustream: ElementRef;
|
||||
private applyGoToTop(): boolean {
|
||||
this.loadBuffer();
|
||||
if (this.statuses.length > 2 * this.streamingService.nbStatusPerIteration) {
|
||||
this.statuses.length = 2 * this.streamingService.nbStatusPerIteration;
|
||||
}
|
||||
const stream = this.statustream.nativeElement as HTMLElement;
|
||||
stream.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
private streamPositionnedAtTop: boolean = true;
|
||||
private isProcessingInfiniteScroll: boolean;
|
||||
|
||||
onScroll() {
|
||||
var element = this.statustream.nativeElement as HTMLElement;
|
||||
const atBottom = element.scrollHeight <= element.clientHeight + element.scrollTop + 1000;
|
||||
const atTop = element.scrollTop === 0;
|
||||
|
||||
this.streamPositionnedAtTop = false;
|
||||
if (atBottom && !this.isProcessingInfiniteScroll) {
|
||||
this.scrolledToBottom();
|
||||
} else if (atTop) {
|
||||
this.scrolledToTop();
|
||||
}
|
||||
}
|
||||
|
||||
accountSelected(accountName: string): void {
|
||||
console.warn(`status comp: accountSelected ${accountName}`);
|
||||
this.browseAccount.next(accountName);
|
||||
}
|
||||
|
||||
hashtagSelected(hashtag: string): void {
|
||||
console.warn(`status comp: hashtagSelected ${hashtag}`);
|
||||
this.browseHashtag.next(hashtag);
|
||||
}
|
||||
|
||||
textSelected(): void {
|
||||
console.warn(`status comp: textSelected`);
|
||||
}
|
||||
|
||||
private scrolledToTop() {
|
||||
this.streamPositionnedAtTop = true;
|
||||
|
||||
this.loadBuffer();
|
||||
}
|
||||
|
||||
private loadBuffer(){
|
||||
if(this.bufferWasCleared) {
|
||||
this.statuses.length = 0;
|
||||
this.bufferWasCleared = false;
|
||||
}
|
||||
|
||||
for (const status of this.bufferStream) {
|
||||
const wrapper = new StatusWrapper(status, this.account);
|
||||
this.statuses.unshift(wrapper);
|
||||
}
|
||||
|
||||
this.bufferStream.length = 0;
|
||||
}
|
||||
|
||||
private scrolledToBottom() {
|
||||
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.list)
|
||||
.then((status: Status[]) => {
|
||||
for (const s of status) {
|
||||
const wrapper = new StatusWrapper(s, this.account);
|
||||
this.statuses.push(wrapper);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
})
|
||||
.then(() => {
|
||||
this.isProcessingInfiniteScroll = false;
|
||||
});
|
||||
}
|
||||
|
||||
private getRegisteredAccounts(): AccountInfo[] {
|
||||
var regAccounts = <AccountInfo[]>this.store.snapshot().registeredaccounts.accounts;
|
||||
return regAccounts;
|
||||
}
|
||||
|
||||
|
||||
private retrieveToots(): void {
|
||||
this.mastodonService.getTimeline(this.account, this._streamElement.type, null, null, this.streamingService.nbStatusPerIteration, this._streamElement.tag, this._streamElement.list)
|
||||
.then((results: Status[]) => {
|
||||
for (const s of results) {
|
||||
const wrapper = new StatusWrapper(s, this.account);
|
||||
this.statuses.push(wrapper);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private checkAndCleanUpStream(): void {
|
||||
if (this.streamPositionnedAtTop && this.statuses.length > 3 * this.streamingService.nbStatusPerIteration) {
|
||||
this.statuses.length = 2 * this.streamingService.nbStatusPerIteration;
|
||||
}
|
||||
|
||||
if (this.bufferStream.length > 3 * this.streamingService.nbStatusPerIteration) {
|
||||
this.bufferWasCleared = true;
|
||||
this.bufferStream.length = 2 * this.streamingService.nbStatusPerIteration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,10 +10,10 @@
|
||||
<h1>{{ streamElement.name.toUpperCase() }}</h1>
|
||||
</a>
|
||||
</div>
|
||||
<div class="stream-toots flexcroll" #statusstream (scroll)="onScroll()">
|
||||
<!-- data-simplebar -->
|
||||
<app-stream-statuses class="stream-statuses" [streamElement]="streamElement" [goToTop]="goToTopSubject.asObservable()" (browseAccount)="browseAccount($event)" (browseHashtag)="browseHashtag($event)"></app-stream-statuses>
|
||||
<!-- <div class="stream-toots flexcroll" #statusstream (scroll)="onScroll()">
|
||||
<div class="stream-toots__status" *ngFor="let statusWrapper of statuses">
|
||||
<app-status [statusWrapper]="statusWrapper" (browseAccount)="browseAccount($event)" (browseHashtag)="browseHashtag($event)"></app-status>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
@ -22,16 +22,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
.stream-toots {
|
||||
.stream-statuses {
|
||||
display: block;
|
||||
height: calc(100% - 30px);
|
||||
width: 320px;
|
||||
overflow: auto;
|
||||
&__status:not(:last-child) {
|
||||
border: solid #06070b;
|
||||
border-width: 0 0 1px 0;
|
||||
}
|
||||
}
|
||||
|
||||
// .stream-toots {
|
||||
// height: calc(100% - 30px);
|
||||
// width: 320px;
|
||||
// overflow: auto;
|
||||
// &__status:not(:last-child) {
|
||||
// border: solid #06070b;
|
||||
// border-width: 0 0 1px 0;
|
||||
// }
|
||||
// }
|
||||
|
||||
.stream-overlay {
|
||||
position: absolute;
|
||||
|
||||
|
@ -1,11 +1,9 @@
|
||||
import { Component, OnInit, Input, ElementRef, ViewChild, HostListener } from "@angular/core";
|
||||
import { AccountWrapper } from "../../models/account.models";
|
||||
import { StreamElement, StreamTypeEnum } from "../../states/streams.state";
|
||||
import { StreamingService, StreamingWrapper, EventEnum, StatusUpdate } from "../../services/streaming.service";
|
||||
import { Store } from "@ngxs/store";
|
||||
import { AccountInfo } from "../../states/accounts.state";
|
||||
import { Subject } from "rxjs";
|
||||
|
||||
import { StreamElement } from "../../states/streams.state";
|
||||
import { Status } from "../../services/models/mastodon.interfaces";
|
||||
import { MastodonService } from "../../services/mastodon.service";
|
||||
import { AccountInfo } from "../../states/accounts.state";
|
||||
|
||||
@Component({
|
||||
selector: "app-stream",
|
||||
@ -13,54 +11,34 @@ import { MastodonService } from "../../services/mastodon.service";
|
||||
styleUrls: ["./stream.component.scss"]
|
||||
})
|
||||
export class StreamComponent implements OnInit {
|
||||
private _streamElement: StreamElement;
|
||||
private account: AccountInfo;
|
||||
private websocketStreaming: StreamingWrapper;
|
||||
|
||||
statuses: StatusWrapper[] = [];
|
||||
private bufferStream: Status[] = [];
|
||||
private bufferWasCleared: boolean;
|
||||
|
||||
overlayActive: boolean;
|
||||
overlayAccountToBrowse: string;
|
||||
overlayHashtagToBrowse: string;
|
||||
|
||||
@Input()
|
||||
set streamElement(streamElement: StreamElement) {
|
||||
this._streamElement = streamElement;
|
||||
private goToTopSubject: Subject<void> = new Subject<void>();
|
||||
|
||||
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);
|
||||
@Input() streamElement: StreamElement;
|
||||
|
||||
this.retrieveToots();
|
||||
this.launchWebsocket();
|
||||
}
|
||||
|
||||
get streamElement(): StreamElement {
|
||||
return this._streamElement;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly store: Store,
|
||||
private readonly streamingService: StreamingService,
|
||||
private readonly mastodonService: MastodonService) {
|
||||
}
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
goToTop(): boolean {
|
||||
this.goToTopSubject.next();
|
||||
return false;
|
||||
}
|
||||
|
||||
browseAccount(account: string): void {
|
||||
this.overlayAccountToBrowse = account;
|
||||
this.overlayHashtagToBrowse = null;
|
||||
this.overlayActive = true;
|
||||
this.overlayActive = true;
|
||||
}
|
||||
|
||||
browseHashtag(hashtag: string): void {
|
||||
this.overlayAccountToBrowse = null;
|
||||
this.overlayHashtagToBrowse = hashtag;
|
||||
this.overlayActive = true;
|
||||
this.overlayActive = true;
|
||||
}
|
||||
|
||||
browseThread(thread: string): void {
|
||||
@ -72,126 +50,11 @@ export class StreamComponent implements OnInit {
|
||||
this.overlayAccountToBrowse = null;
|
||||
this.overlayActive = false;
|
||||
}
|
||||
|
||||
@ViewChild('statusstream') public statustream: ElementRef;
|
||||
goToTop(): boolean {
|
||||
this.loadBuffer();
|
||||
if (this.statuses.length > 2 * this.streamingService.nbStatusPerIteration) {
|
||||
this.statuses.length = 2 * this.streamingService.nbStatusPerIteration;
|
||||
}
|
||||
const stream = this.statustream.nativeElement as HTMLElement;
|
||||
stream.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
private streamPositionnedAtTop: boolean = true;
|
||||
private isProcessingInfiniteScroll: boolean;
|
||||
|
||||
onScroll() {
|
||||
var element = this.statustream.nativeElement as HTMLElement;
|
||||
const atBottom = element.scrollHeight <= element.clientHeight + element.scrollTop + 1000;
|
||||
const atTop = element.scrollTop === 0;
|
||||
|
||||
this.streamPositionnedAtTop = false;
|
||||
if (atBottom && !this.isProcessingInfiniteScroll) {
|
||||
this.scrolledToBottom();
|
||||
} else if (atTop) {
|
||||
this.scrolledToTop();
|
||||
}
|
||||
}
|
||||
|
||||
private scrolledToTop() {
|
||||
this.streamPositionnedAtTop = true;
|
||||
|
||||
this.loadBuffer();
|
||||
}
|
||||
|
||||
private loadBuffer(){
|
||||
if(this.bufferWasCleared) {
|
||||
this.statuses.length = 0;
|
||||
this.bufferWasCleared = false;
|
||||
}
|
||||
|
||||
for (const status of this.bufferStream) {
|
||||
const wrapper = new StatusWrapper(status, this.account);
|
||||
this.statuses.unshift(wrapper);
|
||||
}
|
||||
|
||||
this.bufferStream.length = 0;
|
||||
}
|
||||
|
||||
private scrolledToBottom() {
|
||||
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.list)
|
||||
.then((status: Status[]) => {
|
||||
for (const s of status) {
|
||||
const wrapper = new StatusWrapper(s, this.account);
|
||||
this.statuses.push(wrapper);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
})
|
||||
.then(() => {
|
||||
this.isProcessingInfiniteScroll = false;
|
||||
});
|
||||
}
|
||||
|
||||
private getRegisteredAccounts(): AccountInfo[] {
|
||||
var regAccounts = <AccountInfo[]>this.store.snapshot().registeredaccounts.accounts;
|
||||
return regAccounts;
|
||||
}
|
||||
|
||||
private retrieveToots(): void {
|
||||
this.mastodonService.getTimeline(this.account, this._streamElement.type, null, null, this.streamingService.nbStatusPerIteration, this._streamElement.tag, this._streamElement.list)
|
||||
.then((results: Status[]) => {
|
||||
for (const s of results) {
|
||||
const wrapper = new StatusWrapper(s, this.account);
|
||||
this.statuses.push(wrapper);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
const wrapper = new StatusWrapper(update.status, this.account);
|
||||
this.statuses.unshift(wrapper);
|
||||
} else {
|
||||
this.bufferStream.push(update.status);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.checkAndCleanUpStream();
|
||||
});
|
||||
}
|
||||
|
||||
private checkAndCleanUpStream(): void {
|
||||
if (this.streamPositionnedAtTop && this.statuses.length > 3 * this.streamingService.nbStatusPerIteration) {
|
||||
this.statuses.length = 2 * this.streamingService.nbStatusPerIteration;
|
||||
}
|
||||
|
||||
if (this.bufferStream.length > 3 * this.streamingService.nbStatusPerIteration) {
|
||||
this.bufferWasCleared = true;
|
||||
this.bufferStream.length = 2 * this.streamingService.nbStatusPerIteration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class StatusWrapper {
|
||||
constructor(
|
||||
public status: Status,
|
||||
public provider: AccountInfo
|
||||
) {}
|
||||
) { }
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user