Fix infinite scroll on big screens

This commit is contained in:
Chocobozzz 2019-08-02 14:49:25 +02:00
parent dd570a34ff
commit ad453580b2
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
28 changed files with 135 additions and 49 deletions

View File

@ -1,4 +1,4 @@
<div class="row" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()">
<div class="row" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()">
<div class="col-xl-6 col-md-12">
<div i18n class="subtitle">Followers</div>

View File

@ -4,6 +4,7 @@ import { ComponentPagination, hasMoreItems } from '@app/shared/rest/component-pa
import { Notifier } from '@app/core'
import { RestService } from '@app/shared'
import { SortMeta } from 'primeng/api'
import { Subject } from 'rxjs'
@Component({
selector: 'my-about-follows',
@ -17,13 +18,13 @@ export class AboutFollowsComponent implements OnInit {
followersPagination: ComponentPagination = {
currentPage: 1,
itemsPerPage: 40,
itemsPerPage: 20,
totalItems: null
}
followingsPagination: ComponentPagination = {
currentPage: 1,
itemsPerPage: 40,
itemsPerPage: 20,
totalItems: null
}
@ -32,6 +33,8 @@ export class AboutFollowsComponent implements OnInit {
order: -1
}
onDataSubject = new Subject<any[]>()
constructor (
private restService: RestService,
private notifier: Notifier,
@ -78,6 +81,8 @@ export class AboutFollowsComponent implements OnInit {
this.followers = this.followers.concat(newFollowers)
this.followersPagination.totalItems = resultList.total
this.onDataSubject.next(newFollowers)
},
err => this.notifier.error(err.message)
@ -94,6 +99,8 @@ export class AboutFollowsComponent implements OnInit {
this.followings = this.followings.concat(newFollowings)
this.followingsPagination.totalItems = resultList.total
this.onDataSubject.next(newFollowings)
},
err => this.notifier.error(err.message)

View File

@ -2,7 +2,7 @@
<div class="no-results" i18n *ngIf="channelPagination.totalItems === 0">This account does not have channels.</div>
<div class="channels" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true">
<div class="channels" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" [dataObservable]="onChannelDataSubject.asObservable()">
<div class="section channel" *ngFor="let videoChannel of videoChannels">
<div class="section-title">
<a [routerLink]="getVideoChannelLink(videoChannel)" i18n-title title="See this video channel">

View File

@ -4,7 +4,7 @@ import { Account } from '@app/shared/account/account.model'
import { AccountService } from '@app/shared/account/account.service'
import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
import { concatMap, map, switchMap, tap } from 'rxjs/operators'
import { from, Subscription } from 'rxjs'
import { from, Subject, Subscription } from 'rxjs'
import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
import { Video } from '@app/shared/video/video.model'
import { AuthService } from '@app/core'
@ -33,6 +33,8 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy {
}
videosSort: VideoSortField = '-publishedAt'
onChannelDataSubject = new Subject<any>()
private accountSub: Subscription
constructor (
@ -75,6 +77,8 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy {
this.videoChannels.push(videoChannel)
this.videos[videoChannel.id] = videos
this.onChannelDataSubject.next([ videoChannel ])
})
}

View File

@ -6,7 +6,7 @@
{{ getNoResultMessage() }}
</div>
<div class="plugins" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true">
<div class="plugins" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" [dataObservable]="onDataSubject.asObservable()">
<div class="card plugin" *ngFor="let plugin of plugins">
<div class="card-body">
<div class="first-row">

View File

@ -8,6 +8,7 @@ import { PeerTubePlugin } from '@shared/models/plugins/peertube-plugin.model'
import { ActivatedRoute, Router } from '@angular/router'
import { compareSemVer } from '@shared/core-utils/miscs/miscs'
import { PluginService } from '@app/core/plugins/plugin.service'
import { Subject } from 'rxjs'
@Component({
selector: 'my-plugin-list-installed',
@ -33,6 +34,8 @@ export class PluginListInstalledComponent implements OnInit {
PluginType = PluginType
onDataSubject = new Subject<any[]>()
constructor (
private i18n: I18n,
private pluginService: PluginService,
@ -67,6 +70,8 @@ export class PluginListInstalledComponent implements OnInit {
res => {
this.plugins = this.plugins.concat(res.data)
this.pagination.totalItems = res.total
this.onDataSubject.next(res.data)
},
err => this.notifier.error(err.message)

View File

@ -29,7 +29,7 @@
No results.
</div>
<div class="plugins" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true">
<div class="plugins" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" [dataObservable]="onDataSubject.asObservable()">
<div class="card plugin" *ngFor="let plugin of plugins">
<div class="card-body">
<div class="first-row">

View File

@ -36,6 +36,8 @@ export class PluginSearchComponent implements OnInit {
installing: { [name: string]: boolean } = {}
pluginInstalled = false
onDataSubject = new Subject<any[]>()
private searchSubject = new Subject<string>()
constructor (
@ -90,6 +92,8 @@ export class PluginSearchComponent implements OnInit {
this.plugins = this.plugins.concat(res.data)
this.pagination.totalItems = res.total
this.onDataSubject.next(res.data)
},
err => {

View File

@ -13,7 +13,7 @@
<div class="no-history" i18n *ngIf="pagination.totalItems === 0">You don't have videos history yet.</div>
<div myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" class="videos">
<div myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" [dataObservable]="onDataSubject.asObservable()" class="videos">
<div class="video" *ngFor="let video of videos">
<my-video-miniature
[video]="video" [displayAsRow]="true"

View File

@ -1,6 +1,6 @@
<div class="no-results" i18n *ngIf="pagination.totalItems === 0">You don't have any subscriptions yet.</div>
<div class="video-channels" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()">
<div class="video-channels" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()">
<div *ngFor="let videoChannel of videoChannels" class="video-channel">
<a [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]">
<img [src]="videoChannel.avatarUrl" alt="Avatar" />

View File

@ -3,6 +3,7 @@ import { Notifier } from '@app/core'
import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
import { UserSubscriptionService } from '@app/shared/user-subscription'
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
import { Subject } from 'rxjs'
@Component({
selector: 'my-account-subscriptions',
@ -18,6 +19,8 @@ export class MyAccountSubscriptionsComponent implements OnInit {
totalItems: null
}
onDataSubject = new Subject<any[]>()
constructor (
private userSubscriptionService: UserSubscriptionService,
private notifier: Notifier
@ -33,6 +36,8 @@ export class MyAccountSubscriptionsComponent implements OnInit {
res => {
this.videoChannels = this.videoChannels.concat(res.data)
this.pagination.totalItems = res.total
this.onDataSubject.next(res.data)
},
error => this.notifier.error(error.message)

View File

@ -12,7 +12,7 @@
<div
class="videos" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()"
cdkDropList (cdkDropListDropped)="drop($event)"
cdkDropList (cdkDropListDropped)="drop($event)" [dataObservable]="onDataSubject.asObservable()"
>
<div class="video" *ngFor="let playlistElement of playlistElements; trackBy: trackByFn" cdkDrag>
<my-video-playlist-element-miniature

View File

@ -7,7 +7,7 @@
margin-left: -15px;
margin-top: -$sub-menu-margin-bottom;
padding: $sub-menu-margin-bottom 0;
padding: $sub-menu-margin-bottom 0 -15px 0;
display: flex;
justify-content: center;

View File

@ -3,7 +3,7 @@ import { Notifier, ServerService } from '@app/core'
import { AuthService } from '../../core/auth'
import { ConfirmService } from '../../core/confirm'
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
import { Subscription } from 'rxjs'
import { Subject, Subscription } from 'rxjs'
import { ActivatedRoute } from '@angular/router'
import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
@ -22,10 +22,12 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro
pagination: ComponentPagination = {
currentPage: 1,
itemsPerPage: 30,
itemsPerPage: 10,
totalItems: null
}
onDataSubject = new Subject<any[]>()
private videoPlaylistId: string | number
private paramsSub: Subscription
@ -102,6 +104,8 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro
.subscribe(({ total, data }) => {
this.playlistElements = this.playlistElements.concat(data)
this.pagination.totalItems = total
this.onDataSubject.next(data)
})
}

View File

@ -5,7 +5,7 @@
</a>
</div>
<div class="video-playlists" myInfiniteScroller (nearOfBottom)="onNearOfBottom()">
<div class="video-playlists" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()">
<div *ngFor="let playlist of videoPlaylists" class="video-playlist">
<div class="miniature-wrapper">
<my-video-playlist-miniature [playlist]="playlist" [toManage]="true" [displayChannel]="true" [displayDescription]="true" [displayPrivacy]="true"

View File

@ -9,6 +9,7 @@ import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
import { VideoPlaylistType } from '@shared/models'
import { Subject } from 'rxjs'
@Component({
selector: 'my-account-video-playlists',
@ -20,10 +21,12 @@ export class MyAccountVideoPlaylistsComponent implements OnInit {
pagination: ComponentPagination = {
currentPage: 1,
itemsPerPage: 10,
itemsPerPage: 5,
totalItems: null
}
onDataSubject = new Subject<any[]>()
private user: User
constructor (
@ -78,11 +81,15 @@ export class MyAccountVideoPlaylistsComponent implements OnInit {
}
private loadVideoPlaylists () {
const playlistsObservable = this.videoPlaylistService.listAccountPlaylists(this.user.account, this.pagination, '-updatedAt')
this.authService.userInformationLoaded
.pipe(flatMap(() => this.videoPlaylistService.listAccountPlaylists(this.user.account, '-updatedAt')))
.pipe(flatMap(() => playlistsObservable))
.subscribe(res => {
this.videoPlaylists = this.videoPlaylists.concat(res.data)
this.pagination.totalItems = res.total
this.onDataSubject.next(res.data)
})
}
}

View File

@ -4,7 +4,7 @@
<div i18n class="no-results" *ngIf="pagination.totalItems === 0">This channel does not have playlists.</div>
<div class="video-playlist" myInfiniteScroller (nearOfBottom)="onNearOfBottom()">
<div class="video-playlist" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()">
<div *ngFor="let playlist of videoPlaylists">
<my-video-playlist-miniature [playlist]="playlist" [toManage]="false"></my-video-playlist-miniature>
</div>

View File

@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'
import { ConfirmService } from '../../core/confirm'
import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
import { Subscription } from 'rxjs'
import { Subject, Subscription } from 'rxjs'
import { Notifier } from '@app/core'
import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
import { ComponentPagination, hasMoreItems } from '@app/shared/rest/component-pagination.model'
@ -22,6 +22,8 @@ export class VideoChannelPlaylistsComponent implements OnInit, OnDestroy {
totalItems: null
}
onDataSubject = new Subject<any[]>()
private videoChannelSub: Subscription
private videoChannel: VideoChannel
@ -53,10 +55,12 @@ export class VideoChannelPlaylistsComponent implements OnInit, OnDestroy {
}
private loadVideoPlaylists () {
this.videoPlaylistService.listChannelPlaylists(this.videoChannel)
this.videoPlaylistService.listChannelPlaylists(this.videoChannel, this.pagination)
.subscribe(res => {
this.videoPlaylists = this.videoPlaylists.concat(res.data)
this.pagination.totalItems = res.total
this.onDataSubject.next(res.data)
})
}
}

View File

@ -1,6 +1,6 @@
<div *ngIf="componentPagination.totalItems === 0" class="no-notification" i18n>You don't have notifications.</div>
<div class="notifications" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()">
<div class="notifications" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()">
<div *ngFor="let notification of notifications" class="notification" [ngClass]="{ unread: !notification.read }" (click)="markAsRead(notification)">
<ng-container [ngSwitch]="notification.type">

View File

@ -4,6 +4,7 @@ import { UserNotificationType } from '../../../../../shared'
import { ComponentPagination, hasMoreItems } from '@app/shared/rest/component-pagination.model'
import { Notifier } from '@app/core'
import { UserNotification } from '@app/shared/users/user-notification.model'
import { Subject } from 'rxjs'
@Component({
selector: 'my-user-notifications',
@ -24,6 +25,8 @@ export class UserNotificationsComponent implements OnInit {
componentPagination: ComponentPagination
onDataSubject = new Subject<any[]>()
constructor (
private userNotificationService: UserNotificationService,
private notifier: Notifier
@ -47,6 +50,8 @@ export class UserNotificationsComponent implements OnInit {
this.componentPagination.totalItems = result.total
this.notificationsLoaded.emit()
this.onDataSubject.next(result.data)
},
err => this.notifier.error(err.message)

View File

@ -83,7 +83,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
load () {
forkJoin([
this.videoPlaylistService.listAccountPlaylists(this.user.account, '-updatedAt'),
this.videoPlaylistService.listAccountPlaylists(this.user.account, undefined,'-updatedAt'),
this.videoPlaylistService.doesVideoExistInPlaylist(this.video.id)
])
.subscribe(

View File

@ -45,21 +45,28 @@ export class VideoPlaylistService {
)
}
listChannelPlaylists (videoChannel: VideoChannel): Observable<ResultList<VideoPlaylist>> {
listChannelPlaylists (videoChannel: VideoChannel, componentPagination: ComponentPagination): Observable<ResultList<VideoPlaylist>> {
const url = VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannel.nameWithHost + '/video-playlists'
const pagination = this.restService.componentPaginationToRestPagination(componentPagination)
return this.authHttp.get<ResultList<VideoPlaylist>>(url)
let params = new HttpParams()
params = this.restService.addRestGetParams(params, pagination)
return this.authHttp.get<ResultList<VideoPlaylist>>(url, { params })
.pipe(
switchMap(res => this.extractPlaylists(res)),
catchError(err => this.restExtractor.handleError(err))
)
}
listAccountPlaylists (account: Account, sort: string): Observable<ResultList<VideoPlaylist>> {
listAccountPlaylists (account: Account, componentPagination: ComponentPagination, sort: string): Observable<ResultList<VideoPlaylist>> {
const url = AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/video-playlists'
const pagination = componentPagination
? this.restService.componentPaginationToRestPagination(componentPagination)
: undefined
let params = new HttpParams()
params = this.restService.addRestGetParams(params, undefined, sort)
params = this.restService.addRestGetParams(params, pagination, sort)
return this.authHttp.get<ResultList<VideoPlaylist>>(url, { params })
.pipe(

View File

@ -19,7 +19,7 @@
<div class="no-results" i18n *ngIf="pagination.totalItems === 0">No results.</div>
<div
myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true"
myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" [dataObservable]="onDataSubject.asObservable()"
class="videos"
>
<ng-container *ngFor="let video of videos; trackBy: videoById;">

View File

@ -1,7 +1,7 @@
import { debounceTime, first, tap } from 'rxjs/operators'
import { OnDestroy, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { fromEvent, Observable, of, Subscription } from 'rxjs'
import { fromEvent, Observable, of, Subject, Subscription } from 'rxjs'
import { AuthService } from '../../core/auth'
import { ComponentPagination } from '../rest/component-pagination.model'
import { VideoSortField } from './sort-field.type'
@ -59,6 +59,8 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
blacklistInfo: false
}
onDataSubject = new Subject<any[]>()
protected abstract notifier: Notifier
protected abstract authService: AuthService
protected abstract route: ActivatedRoute
@ -147,6 +149,8 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
if (this.groupByDate) this.buildGroupedDateLabels()
this.onMoreVideos()
this.onDataSubject.next(data)
},
error => this.notifier.error(error.message)

View File

@ -1,14 +1,15 @@
import { distinct, distinctUntilChanged, filter, map, share, startWith, throttleTime } from 'rxjs/operators'
import { Directive, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'
import { fromEvent, Subscription } from 'rxjs'
import { distinctUntilChanged, filter, map, share, startWith, tap, throttleTime } from 'rxjs/operators'
import { AfterContentChecked, Directive, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'
import { fromEvent, Observable, Subscription } from 'rxjs'
@Directive({
selector: '[myInfiniteScroller]'
})
export class InfiniteScrollerDirective implements OnInit, OnDestroy {
export class InfiniteScrollerDirective implements OnInit, OnDestroy, AfterContentChecked {
@Input() percentLimit = 70
@Input() autoInit = false
@Input() onItself = false
@Input() dataObservable: Observable<any[]>
@Output() nearOfBottom = new EventEmitter<void>()
@ -17,10 +18,22 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy {
private scrollDownSub: Subscription
private container: HTMLElement
private checkScroll = false
constructor (private el: ElementRef) {
this.decimalLimit = this.percentLimit / 100
}
ngAfterContentChecked () {
if (this.checkScroll) {
this.checkScroll = false
console.log('Checking if the initial state has a scroll.')
if (this.hasScroll() === false) this.nearOfBottom.emit()
}
}
ngOnInit () {
if (this.autoInit === true) return this.initialize()
}
@ -30,14 +43,15 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy {
}
initialize () {
if (this.onItself) {
this.container = this.el.nativeElement
}
this.container = this.onItself
? this.el.nativeElement
: document.documentElement
// Emit the last value
const throttleOptions = { leading: true, trailing: true }
const scrollObservable = fromEvent(this.container || window, 'scroll')
const scrollableElement = this.onItself ? this.container : window
const scrollObservable = fromEvent(scrollableElement, 'scroll')
.pipe(
startWith(null as string), // FIXME: typings
throttleTime(200, undefined, throttleOptions),
@ -49,23 +63,34 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy {
// Scroll Down
this.scrollDownSub = scrollObservable
.pipe(
// Check we scroll down
filter(({ current }) => {
const res = this.lastCurrentBottom < current
this.lastCurrentBottom = current
return res
}),
filter(({ current, maximumScroll }) => maximumScroll <= 0 || (current / maximumScroll) > this.decimalLimit)
filter(({ current }) => this.isScrollingDown(current)),
filter(({ current, maximumScroll }) => (current / maximumScroll) > this.decimalLimit)
)
.subscribe(() => this.nearOfBottom.emit())
if (this.dataObservable) {
this.dataObservable
.pipe(filter(d => d.length !== 0))
.subscribe(() => this.checkScroll = true)
}
}
private getScrollInfo () {
if (this.container) {
return { current: this.container.scrollTop, maximumScroll: this.container.scrollHeight }
}
return { current: this.container.scrollTop, maximumScroll: this.getMaximumScroll() }
}
return { current: window.scrollY, maximumScroll: document.body.clientHeight - window.innerHeight }
private getMaximumScroll () {
return this.container.scrollHeight - window.innerHeight
}
private hasScroll () {
return this.getMaximumScroll() > 0
}
private isScrollingDown (current: number) {
const result = this.lastCurrentBottom < current
this.lastCurrentBottom = current
return result
}
}

View File

@ -1,6 +1,6 @@
<div class="no-results" i18n *ngIf="pagination.totalItems === 0">No results.</div>
<div myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" class="videos">
<div myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()" class="videos">
<div class="video" *ngFor="let video of videos; let i = index; trackBy: videoById">
<div class="checkbox-container">

View File

@ -21,6 +21,7 @@
myInfiniteScroller
[autoInit]="true"
(nearOfBottom)="onNearOfBottom()"
[dataObservable]="onDataSubject.asObservable()"
>
<div #commentHighlightBlock id="highlighted-comment">
<my-video-comment

View File

@ -1,7 +1,7 @@
import { Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { ConfirmService, Notifier } from '@app/core'
import { Subscription } from 'rxjs'
import { Subject, Subscription } from 'rxjs'
import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model'
import { AuthService } from '../../../core/auth'
import { ComponentPagination, hasMoreItems } from '../../../shared/rest/component-pagination.model'
@ -38,6 +38,8 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
syndicationItems: Syndication[] = []
onDataSubject = new Subject<any[]>()
private sub: Subscription
constructor (
@ -124,6 +126,8 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
res => {
this.comments = this.comments.concat(res.data)
this.componentPagination.totalItems = res.total
this.onDataSubject.next(res.data)
},
err => this.notifier.error(err.message)