feat(client/infinite-scroll): SEO friendly

closes #6332
This commit is contained in:
kontrollanten 2024-09-12 13:12:51 +02:00
parent 52aba7a1cb
commit a91fd0cb35
38 changed files with 504 additions and 259 deletions

View File

@ -4,7 +4,14 @@
<div class="no-results" i18n *ngIf="channelPagination.totalItems === 0">This account does not have channels.</div>
<div class="channels" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onChannelDataSubject.asObservable()">
<my-infinite-scroller
class="channels"
[(currentPage)]="channelPagination.currentPage"
[isLoading]="isLoading"
(nearOfBottom)="onNearOfBottom()"
(currentPageChange)="onPageChange()"
[hasMore]="hasMoreVideoChannels"
>
<div class="channel" *ngFor="let videoChannel of videoChannels">
<div class="channel-avatar-row">
@ -52,5 +59,5 @@
</div>
</div>
</div>
</div>
</my-infinite-scroller>
</div>

View File

@ -1,6 +1,6 @@
import { from, Subject, Subscription } from 'rxjs'
import { from, Subject } from 'rxjs'
import { concatMap, map, switchMap, tap } from 'rxjs/operators'
import { Component, OnDestroy, OnInit } from '@angular/core'
import { Component, OnInit } from '@angular/core'
import { ComponentPagination, hasMoreItems, MarkdownService, User, UserService } from '@app/core'
import { SimpleMemoize } from '@app/helpers'
import { NSFWPolicyType, VideoSortField } from '@peertube/peertube-models'
@ -8,7 +8,7 @@ import { MiniatureDisplayOptions, VideoMiniatureComponent } from '../../shared/s
import { SubscribeButtonComponent } from '../../shared/shared-user-subscription/subscribe-button.component'
import { RouterLink } from '@angular/router'
import { ActorAvatarComponent } from '../../shared/shared-actor-image/actor-avatar.component'
import { InfiniteScrollerDirective } from '../../shared/shared-main/common/infinite-scroller.directive'
import { InfiniteScrollerComponent } from '../../shared/shared-main/common/infinite-scroller.component'
import { NgIf, NgFor } from '@angular/common'
import { AccountService } from '@app/shared/shared-main/account/account.service'
import { VideoChannelService } from '@app/shared/shared-main/channel/video-channel.service'
@ -22,14 +22,17 @@ import { Video } from '@app/shared/shared-main/video/video.model'
templateUrl: './account-video-channels.component.html',
styleUrls: [ './account-video-channels.component.scss' ],
standalone: true,
imports: [ NgIf, InfiniteScrollerDirective, NgFor, ActorAvatarComponent, RouterLink, SubscribeButtonComponent, VideoMiniatureComponent ]
imports: [ NgIf, InfiniteScrollerComponent, NgFor, ActorAvatarComponent, RouterLink, SubscribeButtonComponent, VideoMiniatureComponent ]
})
export class AccountVideoChannelsComponent implements OnInit, OnDestroy {
export class AccountVideoChannelsComponent implements OnInit {
account: Account
videoChannels: VideoChannel[] = []
videos: { [id: number]: { total: number, videos: Video[] } } = {}
hasMoreVideoChannels = true
isLoading = true
channelsDescriptionHTML: { [ id: number ]: string } = {}
channelPagination: ComponentPagination = {
@ -60,8 +63,6 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy {
blacklistInfo: false
}
private accountSub: Subscription
constructor (
private accountService: AccountService,
private videoChannelService: VideoChannelService,
@ -71,15 +72,6 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy {
) { }
ngOnInit () {
// Parent get the account for us
this.accountSub = this.accountService.accountLoaded
.subscribe(account => {
this.account = account
this.videoChannels = []
this.loadMoreChannels()
})
this.userService.getAnonymousOrLoggedUser()
.subscribe(user => {
this.userMiniature = user
@ -88,18 +80,22 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy {
})
}
ngOnDestroy () {
if (this.accountSub) this.accountSub.unsubscribe()
}
loadMoreChannels (reset = false) {
let hasDoneReset = false
this.isLoading = true
loadMoreChannels () {
const options = {
account: this.account,
componentPagination: this.channelPagination,
sort: '-updatedAt'
}
this.videoChannelService.listAccountVideoChannels(options)
// Parent get the account for us
this.accountService.accountLoaded
.pipe(
tap(account => {
this.account = account
}),
switchMap(() => this.videoChannelService.listAccountVideoChannels({
account: this.account,
componentPagination: this.channelPagination,
sort: '-updatedAt'
}))
)
.pipe(
tap(res => {
this.channelPagination.totalItems = res.total
@ -118,13 +114,21 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy {
})
)
.subscribe(async ({ videoChannel, videos, total }) => {
this.isLoading = false
this.channelsDescriptionHTML[videoChannel.id] = await this.markdown.textMarkdownToHTML({
markdown: videoChannel.description,
withEmoji: true,
withHtml: true
})
if (reset && !hasDoneReset) {
hasDoneReset = true
this.videoChannels = []
}
this.videoChannels.push(videoChannel)
this.hasMoreVideoChannels = (this.channelPagination.currentPage * this.channelPagination.itemsPerPage) <
this.channelPagination.totalItems
this.videos[videoChannel.id] = { videos, total }
@ -150,6 +154,10 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy {
return this.channelsDescriptionHTML[videoChannel.id]
}
onPageChange () {
this.loadMoreChannels(true)
}
onNearOfBottom () {
if (!hasMoreItems(this.channelPagination)) return

View File

@ -4,7 +4,14 @@
{{ getNoResultMessage() }}
</div>
<div class="plugins" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()">
<my-infinite-scroller
class="plugins"
[(currentPage)]="pagination.currentPage"
[isLoading]="isLoading"
(nearOfBottom)="onNearOfBottom()"
(currentPageChange)="onPageChange()"
[hasMore]="hasMoreResults"
>
<ng-container *ngFor="let plugin of plugins">
<my-plugin-card [plugin]="plugin" [version]="plugin.version" [pluginType]="pluginType">
<div ngProjectAs="buttons">
@ -27,4 +34,4 @@
</div>
</my-plugin-card>
</ng-container>
</div>
</my-infinite-scroller>

View File

@ -1,4 +1,4 @@
import { Subject } from 'rxjs'
import { distinct, filter, ReplaySubject } from 'rxjs'
import { Component, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
@ -10,7 +10,7 @@ import { DeleteButtonComponent } from '../../../shared/shared-main/buttons/delet
import { ButtonComponent } from '../../../shared/shared-main/buttons/button.component'
import { EditButtonComponent } from '../../../shared/shared-main/buttons/edit-button.component'
import { PluginCardComponent } from '../shared/plugin-card.component'
import { InfiniteScrollerDirective } from '../../../shared/shared-main/common/infinite-scroller.directive'
import { InfiniteScrollerComponent } from '../../../shared/shared-main/common/infinite-scroller.component'
import { NgIf, NgFor } from '@angular/common'
import { PluginNavigationComponent } from '../shared/plugin-navigation.component'
@ -22,7 +22,7 @@ import { PluginNavigationComponent } from '../shared/plugin-navigation.component
imports: [
PluginNavigationComponent,
NgIf,
InfiniteScrollerDirective,
InfiniteScrollerComponent,
NgFor,
PluginCardComponent,
EditButtonComponent,
@ -39,12 +39,14 @@ export class PluginListInstalledComponent implements OnInit {
totalItems: null
}
sort = 'name'
hasMoreResults = true
isLoading = true
plugins: PeerTubePlugin[] = []
updating: { [name: string]: boolean } = {}
uninstalling: { [name: string]: boolean } = {}
onDataSubject = new Subject<any[]>()
private hasInitialized = new ReplaySubject<boolean>()
constructor (
private pluginService: PluginService,
@ -68,31 +70,35 @@ export class PluginListInstalledComponent implements OnInit {
this.pluginType = parseInt(query['pluginType'], 10) as PluginType_Type
this.reloadPlugins()
this.hasInitialized.next(true)
})
}
reloadPlugins () {
this.pagination.currentPage = 1
this.plugins = []
this.loadMorePlugins()
}
loadMorePlugins () {
loadMorePlugins (reset = false) {
this.isLoading = true
this.pluginApiService.getPlugins(this.pluginType, this.pagination, this.sort)
.subscribe({
next: res => {
if (reset) this.plugins = []
this.plugins = this.plugins.concat(res.data)
this.pagination.totalItems = res.total
this.hasMoreResults = (this.pagination.itemsPerPage * this.pagination.currentPage) < this.pagination.totalItems
this.onDataSubject.next(res.data)
this.isLoading = false
},
error: err => this.notifier.error(err.message)
})
}
onPageChange () {
this.hasInitialized.pipe(
distinct(),
filter(val => val)
)
.subscribe(() => this.loadMorePlugins(true))
}
onNearOfBottom () {
if (!hasMoreItems(this.pagination)) return

View File

@ -28,7 +28,14 @@
No results.
</div>
<div class="plugins" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()">
<my-infinite-scroller
class="plugins"
[(currentPage)]="pagination.currentPage"
[isLoading]="isSearching"
(nearOfBottom)="onNearOfBottom()"
(currentPageChange)="onPageChange()"
[hasMore]="hasMoreResults"
>
<ng-container *ngFor="let plugin of plugins" >
<my-plugin-card [plugin]="plugin" [version]="plugin.latestVersion" [pluginType]="pluginType">
<div ngProjectAs="badges">
@ -58,4 +65,4 @@
</my-plugin-card>
</ng-container>
</div>
</my-infinite-scroller>

View File

@ -6,14 +6,14 @@ import { ComponentPagination, ConfirmService, hasMoreItems, Notifier, PluginServ
import { AlertComponent } from '@app/shared/shared-main/common/alert.component'
import { PeerTubePluginIndex, PluginType, PluginType_Type } from '@peertube/peertube-models'
import { logger } from '@root-helpers/logger'
import { Subject } from 'rxjs'
import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
import { ReplaySubject, Subject } from 'rxjs'
import { debounceTime, distinct, distinctUntilChanged, filter } from 'rxjs/operators'
import { GlobalIconComponent } from '../../../shared/shared-icons/global-icon.component'
import { ButtonComponent } from '../../../shared/shared-main/buttons/button.component'
import { EditButtonComponent } from '../../../shared/shared-main/buttons/edit-button.component'
import { AutofocusDirective } from '../../../shared/shared-main/common/autofocus.directive'
import { InfiniteScrollerDirective } from '../../../shared/shared-main/common/infinite-scroller.directive'
import { PluginCardComponent } from '../shared/plugin-card.component'
import { InfiniteScrollerComponent } from '../../../shared/shared-main/common/infinite-scroller.component'
import { PluginNavigationComponent } from '../shared/plugin-navigation.component'
@Component({
@ -26,7 +26,7 @@ import { PluginNavigationComponent } from '../shared/plugin-navigation.component
NgIf,
GlobalIconComponent,
AutofocusDirective,
InfiniteScrollerDirective,
InfiniteScrollerComponent,
NgFor,
PluginCardComponent,
EditButtonComponent,
@ -43,17 +43,17 @@ export class PluginSearchComponent implements OnInit {
totalItems: null
}
sort = '-trending'
hasMoreResults = true
search = ''
isSearching = false
isSearching = true
plugins: PeerTubePluginIndex[] = []
installing: { [name: string]: boolean } = {}
pluginInstalled = false
onDataSubject = new Subject<any[]>()
private searchSubject = new Subject<string>()
private hasInitialized = new ReplaySubject<boolean>()
constructor (
private pluginService: PluginService,
@ -75,10 +75,15 @@ export class PluginSearchComponent implements OnInit {
this.route.queryParams.subscribe(query => {
if (!query['pluginType']) return
const oldSearch = this.search
this.pluginType = parseInt(query['pluginType'], 10) as PluginType_Type
this.search = query['search'] || ''
this.hasInitialized.next(true)
this.reloadPlugins()
if (oldSearch !== this.search) {
this.pagination.currentPage = 1
this.onPageChange()
}
})
this.searchSubject.asObservable()
@ -95,14 +100,7 @@ export class PluginSearchComponent implements OnInit {
this.searchSubject.next(target.value)
}
reloadPlugins () {
this.pagination.currentPage = 1
this.plugins = []
this.loadMorePlugins()
}
loadMorePlugins () {
loadMorePlugins (reset = false) {
this.isSearching = true
this.pluginApiService.searchAvailablePlugins(this.pluginType, this.pagination, this.sort, this.search)
@ -110,10 +108,11 @@ export class PluginSearchComponent implements OnInit {
next: res => {
this.isSearching = false
if (reset) this.plugins = []
this.plugins = this.plugins.concat(res.data)
this.pagination.totalItems = res.total
this.onDataSubject.next(res.data)
this.hasMoreResults = (this.pagination.itemsPerPage * this.pagination.currentPage) < this.pagination.totalItems
},
error: err => {
@ -125,6 +124,14 @@ export class PluginSearchComponent implements OnInit {
})
}
onPageChange () {
this.hasInitialized.pipe(
distinct(),
filter(val => val)
)
.subscribe(() => this.loadMorePlugins(true))
}
onNearOfBottom () {
if (!hasMoreItems(this.pagination)) return

View File

@ -16,7 +16,7 @@
<my-channels-setup-message [hideLink]="true"></my-channels-setup-message>
<div class="video-channels-header d-flex justify-content-between gap-2">
<my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter>
<my-advanced-input-filter [emitOnInit]="false" (search)="onSearch($event)"></my-advanced-input-filter>
<a class="peertube-create-button" routerLink="/manage/create">
<my-global-icon iconName="add" aria-hidden="true"></my-global-icon>
@ -26,7 +26,14 @@
<div class="no-results" i18n *ngIf="this.pagination.totalItems === 0">No channel found.</div>
<div class="video-channels" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onChannelDataSubject.asObservable()">
<my-infinite-scroller
class="video-channels"
[(currentPage)]="pagination.currentPage"
[isLoading]="isLoading"
(nearOfBottom)="onNearOfBottom()"
(currentPageChange)="onPageChange()"
[hasMore]="hasMoreResults"
>
<div *ngFor="let videoChannel of videoChannels; let i = index" class="video-channel">
<my-actor-avatar [actor]="videoChannel" actorType="channel" [internalHref]="[ '/c', videoChannel.nameWithHost ]" size="80"></my-actor-avatar>
@ -68,4 +75,4 @@
</div>
</div>
</div>
</div>
</my-infinite-scroller>

View File

@ -13,7 +13,7 @@ import { ActorAvatarComponent } from '../../shared/shared-actor-image/actor-avat
import { AdvancedInputFilterComponent } from '../../shared/shared-forms/advanced-input-filter.component'
import { GlobalIconComponent } from '../../shared/shared-icons/global-icon.component'
import { DeferLoadingDirective } from '../../shared/shared-main/common/defer-loading.directive'
import { InfiniteScrollerDirective } from '../../shared/shared-main/common/infinite-scroller.directive'
import { InfiniteScrollerComponent } from '../../shared/shared-main/common/infinite-scroller.component'
import { NumberFormatterPipe } from '../../shared/shared-main/common/number-formatter.pipe'
import { DeleteButtonComponent } from '../../shared/shared-main/buttons/delete-button.component'
import { EditButtonComponent } from '../../shared/shared-main/buttons/edit-button.component'
@ -31,7 +31,7 @@ type CustomChartData = (ChartData & { startDate: string, total: number })
RouterLink,
ChannelsSetupMessageComponent,
AdvancedInputFilterComponent,
InfiniteScrollerDirective,
InfiniteScrollerComponent,
NgFor,
ActorAvatarComponent,
EditButtonComponent,
@ -43,6 +43,8 @@ type CustomChartData = (ChartData & { startDate: string, total: number })
})
export class MyVideoChannelsComponent {
videoChannels: VideoChannel[] = []
hasMoreResults = true
isLoading = true
videoChannelsChartData: CustomChartData[]
@ -76,10 +78,9 @@ export class MyVideoChannelsComponent {
this.search = search
this.pagination.currentPage = 1
this.videoChannels = []
this.pagesDone.clear()
this.loadMoreVideoChannels()
this.onPageChange()
}
async deleteVideoChannel (videoChannel: VideoChannel) {
@ -111,6 +112,10 @@ export class MyVideoChannelsComponent {
})
}
onPageChange () {
this.loadMoreVideoChannels(true)
}
onNearOfBottom () {
if (!hasMoreItems(this.pagination)) return
@ -119,9 +124,10 @@ export class MyVideoChannelsComponent {
this.loadMoreVideoChannels()
}
private loadMoreVideoChannels () {
if (this.pagesDone.has(this.pagination.currentPage)) return
private loadMoreVideoChannels (reset = false) {
if (!reset && this.pagesDone.has(this.pagination.currentPage)) return
this.pagesDone.add(this.pagination.currentPage)
this.isLoading = true
return this.authService.userInformationLoaded
.pipe(
@ -136,6 +142,9 @@ export class MyVideoChannelsComponent {
switchMap(options => this.videoChannelService.listAccountVideoChannels(options))
)
.subscribe(res => {
this.isLoading = false
this.hasMoreResults = res.data.length === this.pagination.itemsPerPage
if (reset) this.videoChannels = []
this.videoChannels = this.videoChannels.concat(res.data)
this.pagination.totalItems = res.total

View File

@ -7,12 +7,19 @@
</div>
<div class="followers-header">
<my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter>
<my-advanced-input-filter [emitOnInit]="false" [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter>
</div>
<div class="no-results" i18n *ngIf="pagination.totalItems === 0">No follower found.</div>
<div class="actors" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()">
<my-infinite-scroller
class="actors"
[(currentPage)]="pagination.currentPage"
[isLoading]="isLoading"
(nearOfBottom)="onNearOfBottom()"
(currentPageChange)="onPageChange()"
[hasMore]="hasMoreResults"
>
<div *ngFor="let follow of follows" class="actor">
<my-actor-avatar [actor]="follow.follower" actorType="account" [href]="follow.follower.url" size="40"></my-actor-avatar>
@ -28,4 +35,4 @@
</div>
</div>
</div>
</div>
</my-infinite-scroller>

View File

@ -4,21 +4,22 @@ import { ActivatedRoute } from '@angular/router'
import { AuthService, ComponentPagination, Notifier } from '@app/core'
import { UserSubscriptionService } from '@app/shared/shared-user-subscription/user-subscription.service'
import { ActorFollow } from '@peertube/peertube-models'
import { Subject } from 'rxjs'
import { ActorAvatarComponent } from '../../shared/shared-actor-image/actor-avatar.component'
import { AdvancedInputFilter, AdvancedInputFilterComponent } from '../../shared/shared-forms/advanced-input-filter.component'
import { GlobalIconComponent } from '../../shared/shared-icons/global-icon.component'
import { InfiniteScrollerDirective } from '../../shared/shared-main/common/infinite-scroller.directive'
import { InfiniteScrollerComponent } from '../../shared/shared-main/common/infinite-scroller.component'
import { formatICU } from '@app/helpers'
@Component({
templateUrl: './my-followers.component.html',
styleUrls: [ './my-followers.component.scss' ],
standalone: true,
imports: [ GlobalIconComponent, NgIf, AdvancedInputFilterComponent, InfiniteScrollerDirective, NgFor, ActorAvatarComponent ]
imports: [ GlobalIconComponent, NgIf, AdvancedInputFilterComponent, InfiniteScrollerComponent, NgFor, ActorAvatarComponent ]
})
export class MyFollowersComponent implements OnInit {
follows: ActorFollow[] = []
hasMoreResults = true
isLoading = true
pagination: ComponentPagination = {
currentPage: 1,
@ -26,7 +27,6 @@ export class MyFollowersComponent implements OnInit {
totalItems: null
}
onDataSubject = new Subject<any[]>()
search: string
inputFilters: AdvancedInputFilter[]
@ -58,6 +58,10 @@ export class MyFollowersComponent implements OnInit {
]
}
onPageChange () {
this.loadFollowers(false)
}
onNearOfBottom () {
// Last page
if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return
@ -83,6 +87,8 @@ export class MyFollowersComponent implements OnInit {
}
private loadFollowers (more = true) {
this.isLoading = true
this.userSubscriptionService.listFollowers({
pagination: this.pagination,
nameWithHost: this.getUsername(),
@ -93,8 +99,9 @@ export class MyFollowersComponent implements OnInit {
? this.follows.concat(res.data)
: res.data
this.pagination.totalItems = res.total
this.hasMoreResults = (this.pagination.itemsPerPage * this.pagination.currentPage) < this.pagination.totalItems
this.onDataSubject.next(res.data)
this.isLoading = false
},
error: err => this.notifier.error(err.message)

View File

@ -7,12 +7,19 @@
</div>
<div class="mb-3">
<my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter>
<my-advanced-input-filter [emitOnInit]="false" (search)="onSearch($event)"></my-advanced-input-filter>
</div>
<div class="no-results" i18n *ngIf="pagination.totalItems === 0">You don't have any subscription yet.</div>
<div class="actors" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()">
<my-infinite-scroller
class="actors"
[(currentPage)]="pagination.currentPage"
[isLoading]="isLoading"
(nearOfBottom)="onNearOfBottom()"
(currentPageChange)="onPageChange()"
[hasMore]="hasMoreResults"
>
<div *ngFor="let videoChannel of videoChannels" class="actor">
<my-actor-avatar [actor]="videoChannel" actorType="channel" [internalHref]="[ '/c', videoChannel.nameWithHost ]" size="80"></my-actor-avatar>
@ -33,4 +40,4 @@
<my-subscribe-button [videoChannels]="[videoChannel]"></my-subscribe-button>
</div>
</div>
</my-infinite-scroller>

View File

@ -1,10 +1,9 @@
import { Subject } from 'rxjs'
import { Component } from '@angular/core'
import { ComponentPagination, Notifier } from '@app/core'
import { SubscribeButtonComponent } from '../../shared/shared-user-subscription/subscribe-button.component'
import { RouterLink } from '@angular/router'
import { ActorAvatarComponent } from '../../shared/shared-actor-image/actor-avatar.component'
import { InfiniteScrollerDirective } from '../../shared/shared-main/common/infinite-scroller.directive'
import { InfiniteScrollerComponent } from '../../shared/shared-main/common/infinite-scroller.component'
import { AdvancedInputFilterComponent } from '../../shared/shared-forms/advanced-input-filter.component'
import { NgIf, NgFor } from '@angular/common'
import { GlobalIconComponent } from '../../shared/shared-icons/global-icon.component'
@ -20,7 +19,7 @@ import { formatICU } from '@app/helpers'
GlobalIconComponent,
NgIf,
AdvancedInputFilterComponent,
InfiniteScrollerDirective,
InfiniteScrollerComponent,
NgFor,
ActorAvatarComponent,
RouterLink,
@ -29,15 +28,15 @@ import { formatICU } from '@app/helpers'
})
export class MySubscriptionsComponent {
videoChannels: VideoChannel[] = []
hasMoreResults = true
isLoading = true
pagination: ComponentPagination = {
currentPage: 1,
itemsPerPage: 10,
totalItems: null
}
onDataSubject = new Subject<any[]>()
search: string
constructor (
@ -45,9 +44,15 @@ export class MySubscriptionsComponent {
private notifier: Notifier
) {}
onPageChange () {
this.loadSubscriptions()
}
onNearOfBottom () {
// Last page
if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return
if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) {
return
}
this.pagination.currentPage += 1
this.loadSubscriptions()
@ -66,6 +71,7 @@ export class MySubscriptionsComponent {
}
private loadSubscriptions (more = true) {
this.isLoading = true
this.userSubscriptionService.listSubscriptions({ pagination: this.pagination, search: this.search })
.subscribe({
next: res => {
@ -73,8 +79,8 @@ export class MySubscriptionsComponent {
? this.videoChannels.concat(res.data)
: res.data
this.pagination.totalItems = res.total
this.onDataSubject.next(res.data)
this.hasMoreResults = (this.pagination.itemsPerPage * this.pagination.currentPage) < this.pagination.totalItems
this.isLoading = false
},
error: err => this.notifier.error(err.message)

View File

@ -33,9 +33,15 @@
</div>
</div>
<div
class="videos" myInfiniteScroller (nearOfBottom)="onNearOfBottom()"
cdkDropList (cdkDropListDropped)="drop($event)" [dataObservable]="onDataSubject.asObservable()"
<my-infinite-scroller
class="videos"
[(currentPage)]="pagination.currentPage"
[isLoading]="isLoading"
(nearOfBottom)="onNearOfBottom()"
(currentPageChange)="onPageChange()"
cdkDropList
(cdkDropListDropped)="drop($event)"
[hasMore]="hasMoreResults"
>
<div class="video" *ngFor="let playlistElement of playlistElements; trackBy: trackByFn" cdkDrag [cdkDragStartDelay]="getDragStartDelay()">
<my-video-playlist-element-miniature
@ -44,7 +50,7 @@
>
</my-video-playlist-element-miniature>
</div>
</div>
</my-infinite-scroller>
</div>
</div>

View File

@ -56,8 +56,8 @@
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.video:last-child {
border: 0;
.video:not(:has(+ .video)) {
margin-bottom: 20px;
}
.videos.cdk-drop-list-dragging .video:not(.cdk-drag-placeholder) {

View File

@ -1,11 +1,11 @@
import { Subject, Subscription } from 'rxjs'
import { Subscription } from 'rxjs'
import { CdkDragDrop, CdkDropList, CdkDrag } from '@angular/cdk/drag-drop'
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { ComponentPagination, ConfirmService, HooksService, Notifier, ScreenService } from '@app/core'
import { VideoPlaylistType } from '@peertube/peertube-models'
import { VideoPlaylistElementMiniatureComponent } from '../../shared/shared-video-playlist/video-playlist-element-miniature.component'
import { InfiniteScrollerDirective } from '../../shared/shared-main/common/infinite-scroller.directive'
import { InfiniteScrollerComponent } from '../../shared/shared-main/common/infinite-scroller.component'
import { ActionDropdownComponent, DropdownAction } from '../../shared/shared-main/buttons/action-dropdown.component'
import { GlobalIconComponent } from '../../shared/shared-icons/global-icon.component'
import { VideoPlaylistMiniatureComponent } from '../../shared/shared-video-playlist/video-playlist-miniature.component'
@ -24,7 +24,7 @@ import { VideoPlaylistService } from '@app/shared/shared-video-playlist/video-pl
VideoPlaylistMiniatureComponent,
GlobalIconComponent,
ActionDropdownComponent,
InfiniteScrollerDirective,
InfiniteScrollerComponent,
CdkDropList,
NgFor,
CdkDrag,
@ -35,6 +35,8 @@ import { VideoPlaylistService } from '@app/shared/shared-video-playlist/video-pl
export class MyVideoPlaylistElementsComponent implements OnInit, OnDestroy {
@ViewChild('videoShareModal') videoShareModal: VideoShareComponent
hasMoreResults = true
isLoading = true
playlistElements: VideoPlaylistElement[] = []
playlist: VideoPlaylist
@ -46,8 +48,6 @@ export class MyVideoPlaylistElementsComponent implements OnInit, OnDestroy {
totalItems: null
}
onDataSubject = new Subject<any[]>()
private videoPlaylistId: string | number
private paramsSub: Subscription
@ -122,6 +122,10 @@ export class MyVideoPlaylistElementsComponent implements OnInit, OnDestroy {
this.reorderClientPositions(oldFirst)
}
onPageChange () {
this.loadElements(true)
}
onNearOfBottom () {
// Last page
if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return
@ -175,7 +179,9 @@ export class MyVideoPlaylistElementsComponent implements OnInit, OnDestroy {
return null
}
private loadElements () {
private loadElements (reset = false) {
this.isLoading = true
this.hooks.wrapObsFun(
this.videoPlaylistService.getPlaylistVideos.bind(this.videoPlaylistService),
{ videoPlaylistId: this.videoPlaylistId, componentPagination: this.pagination },
@ -184,10 +190,13 @@ export class MyVideoPlaylistElementsComponent implements OnInit, OnDestroy {
'filter:api.my-library.video-playlist-elements.list.result'
)
.subscribe(({ total, data }) => {
if (reset) this.playlistElements = []
this.playlistElements = this.playlistElements.concat(data)
this.pagination.totalItems = total
this.hasMoreResults = (this.pagination.itemsPerPage * this.pagination.currentPage) < this.pagination.totalItems
this.onDataSubject.next(data)
this.isLoading = false
})
}

View File

@ -9,7 +9,7 @@
<my-channels-setup-message></my-channels-setup-message>
<div class="video-playlists-header d-flex justify-content-between gap-2">
<my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter>
<my-advanced-input-filter [emitOnInit]="false" (search)="onSearch($event)"></my-advanced-input-filter>
<a class="peertube-create-button" routerLink="create">
<my-global-icon iconName="add" aria-hidden="true"></my-global-icon>
@ -17,7 +17,14 @@
</a>
</div>
<div class="video-playlists" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()">
<my-infinite-scroller
class="video-playlists"
[(currentPage)]="pagination.currentPage"
[isLoading]="isLoading"
(nearOfBottom)="onNearOfBottom()"
(currentPageChange)="onPageChange()"
[hasMore]="hasMoreResults"
>
<div *ngFor="let playlist of videoPlaylists" class="video-playlist">
<my-video-playlist-miniature
[playlist]="playlist" [toManage]="true" [displayChannel]="true"
@ -30,4 +37,4 @@
<my-edit-button label [ptRouterLink]="[ 'update', playlist.shortUUID ]"></my-edit-button>
</div>
</div>
</div>
</my-infinite-scroller>

View File

@ -1,4 +1,3 @@
import { Subject } from 'rxjs'
import { mergeMap } from 'rxjs/operators'
import { Component } from '@angular/core'
import { AuthService, ComponentPagination, ConfirmService, Notifier } from '@app/core'
@ -6,7 +5,7 @@ import { VideoPlaylistType } from '@peertube/peertube-models'
import { EditButtonComponent } from '../../shared/shared-main/buttons/edit-button.component'
import { DeleteButtonComponent } from '../../shared/shared-main/buttons/delete-button.component'
import { VideoPlaylistMiniatureComponent } from '../../shared/shared-video-playlist/video-playlist-miniature.component'
import { InfiniteScrollerDirective } from '../../shared/shared-main/common/infinite-scroller.directive'
import { InfiniteScrollerComponent } from '../../shared/shared-main/common/infinite-scroller.component'
import { RouterLink } from '@angular/router'
import { AdvancedInputFilterComponent } from '../../shared/shared-forms/advanced-input-filter.component'
import { ChannelsSetupMessageComponent } from '../../shared/shared-main/channel/channels-setup-message.component'
@ -26,7 +25,7 @@ import { formatICU } from '@app/helpers'
ChannelsSetupMessageComponent,
AdvancedInputFilterComponent,
RouterLink,
InfiniteScrollerDirective,
InfiniteScrollerComponent,
NgFor,
VideoPlaylistMiniatureComponent,
DeleteButtonComponent,
@ -35,6 +34,8 @@ import { formatICU } from '@app/helpers'
})
export class MyVideoPlaylistsComponent {
videoPlaylists: VideoPlaylist[] = []
hasMoreResults = true
isLoading = true
pagination: ComponentPagination = {
currentPage: 1,
@ -42,8 +43,6 @@ export class MyVideoPlaylistsComponent {
totalItems: null
}
onDataSubject = new Subject<any[]>()
search: string
constructor (
@ -77,6 +76,10 @@ export class MyVideoPlaylistsComponent {
return playlist.type.id === VideoPlaylistType.REGULAR
}
onPageChange () {
this.loadVideoPlaylists(true)
}
onNearOfBottom () {
// Last page
if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return
@ -98,6 +101,8 @@ export class MyVideoPlaylistsComponent {
}
private loadVideoPlaylists (reset = false) {
this.isLoading = true
this.authService.userInformationLoaded
.pipe(mergeMap(() => {
const user = this.authService.getUser()
@ -108,8 +113,9 @@ export class MyVideoPlaylistsComponent {
this.videoPlaylists = this.videoPlaylists.concat(res.data)
this.pagination.totalItems = res.total
this.hasMoreResults = (this.pagination.itemsPerPage * this.pagination.currentPage) < this.pagination.totalItems
this.onDataSubject.next(res.data)
this.isLoading = false
})
}
}

View File

@ -1,4 +1,11 @@
<div myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onSearchDataSubject.asObservable()" class="search-result">
<my-infinite-scroller
[(currentPage)]="pagination.currentPage"
[isLoading]="isSearching"
(nearOfBottom)="onNearOfBottom()"
(currentPageChange)="onPageChange()"
class="search-result"
[hasMore]="hasMoreResults"
>
<div class="results-header">
<div class="first-line">
<div class="results-counter" *ngIf="pagination.totalItems">
@ -79,4 +86,4 @@
</div>
</ng-container>
</div>
</my-infinite-scroller>

View File

@ -11,11 +11,11 @@ import { AdvancedSearch } from '@app/shared/shared-search/advanced-search.model'
import { SearchService } from '@app/shared/shared-search/search.service'
import { VideoPlaylist } from '@app/shared/shared-video-playlist/video-playlist.model'
import { NgbCollapse } from '@ng-bootstrap/ng-bootstrap'
import { HTMLServerConfig, SearchTargetType } from '@peertube/peertube-models'
import { forkJoin, Subject, Subscription } from 'rxjs'
import { HTMLServerConfig, ResultList, SearchTargetType } from '@peertube/peertube-models'
import { forkJoin, Subscription } from 'rxjs'
import { LinkType } from 'src/types/link.type'
import { ActorAvatarComponent } from '../shared/shared-actor-image/actor-avatar.component'
import { InfiniteScrollerDirective } from '../shared/shared-main/common/infinite-scroller.directive'
import { InfiniteScrollerComponent } from '../shared/shared-main/common/infinite-scroller.component'
import { NumberFormatterPipe } from '../shared/shared-main/common/number-formatter.pipe'
import { SubscribeButtonComponent } from '../shared/shared-user-subscription/subscribe-button.component'
import { MiniatureDisplayOptions, VideoMiniatureComponent } from '../shared/shared-video-miniature/video-miniature.component'
@ -28,7 +28,7 @@ import { SearchFiltersComponent } from './search-filters.component'
templateUrl: './search.component.html',
standalone: true,
imports: [
InfiniteScrollerDirective,
InfiniteScrollerComponent,
NgIf,
NgbCollapse,
SearchFiltersComponent,
@ -52,6 +52,8 @@ export class SearchComponent implements OnInit, OnDestroy {
currentPage: 1,
totalItems: null as number
}
hasMoreResults = true
isSearching = false
advancedSearch: AdvancedSearch = new AdvancedSearch()
isSearchFilterCollapsed = true
currentSearch: string
@ -71,14 +73,9 @@ export class SearchComponent implements OnInit, OnDestroy {
userMiniature: User
onSearchDataSubject = new Subject<any>()
private subActivatedRoute: Subscription
private isInitialLoad = false // set to false to show the search filters on first arrival
private hasMoreResults = true
private isSearching = false
private lastSearchTarget: SearchTargetType
private serverConfig: HTMLServerConfig
@ -123,8 +120,6 @@ export class SearchComponent implements OnInit, OnDestroy {
// Don't hide filters if we have some of them AND the user just came on the webpage, or we have an error
this.isSearchFilterCollapsed = !this.error && (this.isInitialLoad === false || !this.advancedSearch.containsValues())
this.isInitialLoad = false
this.search()
},
error: err => this.notifier.error(err.message)
@ -175,9 +170,7 @@ export class SearchComponent implements OnInit, OnDestroy {
this.pagination.totalItems = results.reduce((p, r) => p += r.total, 0)
this.lastSearchTarget = this.advancedSearch.searchTarget
this.hasMoreResults = this.results.length < this.pagination.totalItems
this.onSearchDataSubject.next(results)
this.hasMoreResults = this.calculateHasMoreResults(results)
},
error: err => {
@ -200,6 +193,11 @@ export class SearchComponent implements OnInit, OnDestroy {
})
}
onPageChange () {
this.results = []
this.search()
}
onNearOfBottom () {
// Last page
if (!this.hasMoreResults || this.isSearching) return
@ -280,7 +278,7 @@ export class SearchComponent implements OnInit, OnDestroy {
this.pagination.currentPage = 1
this.pagination.totalItems = null
this.results = []
this.onPageChange()
}
private updateTitle () {
@ -303,7 +301,7 @@ export class SearchComponent implements OnInit, OnDestroy {
private getVideosObs () {
const params = {
search: this.currentSearch,
componentPagination: immutableAssign(this.pagination, { itemsPerPage: 10 }),
componentPagination: immutableAssign(this.pagination, { itemsPerPage: this.buildVideosPerPage() }),
advancedSearch: this.advancedSearch
}
@ -366,6 +364,28 @@ export class SearchComponent implements OnInit, OnDestroy {
return undefined
}
private calculateHasMoreResults (results: [ResultList<VideoChannel>, ResultList<VideoPlaylist>, ResultList<Video>]) {
const [ channels, playlists, videos ] = results
if ((this.pagination.currentPage * this.buildChannelsPerPage()) < channels.total) {
return true
}
if ((this.pagination.currentPage * this.buildPlaylistsPerPage()) < playlists.total) {
return true
}
if ((this.pagination.currentPage * this.buildVideosPerPage()) < videos.total) {
return true
}
return false
}
private buildVideosPerPage () {
return 10
}
private buildChannelsPerPage () {
if (this.advancedSearch.resultType === 'channels') return 10

View File

@ -5,9 +5,16 @@
<div i18n class="no-results" *ngIf="pagination.totalItems === 0">This channel does not have playlists.</div>
<div class="playlists" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()">
<my-infinite-scroller
class="playlists"
[(currentPage)]="pagination.currentPage"
[isLoading]="isLoading"
(nearOfBottom)="onNearOfBottom()"
(currentPageChange)="onPageChange()"
[hasMore]="hasMoreResults"
>
<div *ngFor="let playlist of videoPlaylists" class="playlist-wrapper">
<my-video-playlist-miniature [playlist]="playlist" [toManage]="false" [displayAsRow]="displayAsRow()"></my-video-playlist-miniature>
</div>
</div>
</my-infinite-scroller>
</div>

View File

@ -1,8 +1,8 @@
import { Subject, Subscription } from 'rxjs'
import { Subscription } from 'rxjs'
import { AfterViewInit, Component, OnDestroy, OnInit } from '@angular/core'
import { ComponentPagination, hasMoreItems, HooksService, ScreenService } from '@app/core'
import { VideoPlaylistMiniatureComponent } from '../../shared/shared-video-playlist/video-playlist-miniature.component'
import { InfiniteScrollerDirective } from '../../shared/shared-main/common/infinite-scroller.directive'
import { InfiniteScrollerComponent } from '../../shared/shared-main/common/infinite-scroller.component'
import { NgIf, NgFor } from '@angular/common'
import { VideoChannel } from '@app/shared/shared-main/channel/video-channel.model'
import { VideoChannelService } from '@app/shared/shared-main/channel/video-channel.service'
@ -14,7 +14,7 @@ import { VideoPlaylistService } from '@app/shared/shared-video-playlist/video-pl
templateUrl: './video-channel-playlists.component.html',
styleUrls: [ './video-channel-playlists.component.scss' ],
standalone: true,
imports: [ NgIf, InfiniteScrollerDirective, NgFor, VideoPlaylistMiniatureComponent ]
imports: [ NgIf, InfiniteScrollerComponent, NgFor, VideoPlaylistMiniatureComponent ]
})
export class VideoChannelPlaylistsComponent implements OnInit, AfterViewInit, OnDestroy {
videoPlaylists: VideoPlaylist[] = []
@ -24,8 +24,8 @@ export class VideoChannelPlaylistsComponent implements OnInit, AfterViewInit, On
itemsPerPage: 20,
totalItems: null
}
onDataSubject = new Subject<any[]>()
hasMoreResults = true
isLoading = false
private videoChannelSub: Subscription
private videoChannel: VideoChannel
@ -46,8 +46,6 @@ export class VideoChannelPlaylistsComponent implements OnInit, AfterViewInit, On
this.hooks.runAction('action:video-channel-playlists.video-channel.loaded', 'video-channel', { videoChannel })
this.videoPlaylists = []
this.pagination.currentPage = 1
this.loadVideoPlaylists()
})
}
@ -59,6 +57,10 @@ export class VideoChannelPlaylistsComponent implements OnInit, AfterViewInit, On
if (this.videoChannelSub) this.videoChannelSub.unsubscribe()
}
onPageChange () {
this.loadVideoPlaylists(true)
}
onNearOfBottom () {
if (!hasMoreItems(this.pagination)) return
@ -70,15 +72,19 @@ export class VideoChannelPlaylistsComponent implements OnInit, AfterViewInit, On
return this.screenService.isInMobileView()
}
private loadVideoPlaylists () {
private loadVideoPlaylists (reset = false) {
this.isLoading = true
this.videoPlaylistService.listChannelPlaylists(this.videoChannel, this.pagination)
.subscribe(res => {
if (reset) this.videoPlaylists = []
this.videoPlaylists = this.videoPlaylists.concat(res.data)
this.pagination.totalItems = res.total
this.hasMoreResults = this.videoPlaylists.length < this.pagination.totalItems
this.hooks.runAction('action:video-channel-playlists.playlists.loaded', 'video-channel', { playlists: this.videoPlaylists })
this.onDataSubject.next(res.data)
this.isLoading = false
})
}
}

View File

@ -28,7 +28,14 @@
<div *ngIf="totalNotDeletedComments === 0 && comments.length === 0" i18n>No comments.</div>
<div class="comment-threads" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()">
<my-infinite-scroller
class="comment-threads"
[(currentPage)]="componentPagination.currentPage"
[isLoading]="isLoading"
(nearOfBottom)="onNearOfBottom()"
(currentPageChange)="onPageChange($event)"
[hasMore]="hasMoreResults"
>
<div>
<div class="anchor" #commentHighlightBlock id="highlighted-comment"></div>
<my-video-comment
@ -90,7 +97,7 @@
</my-video-comment>
</div>
</div>
</my-infinite-scroller>
} @else {
<div i18n>Comments are disabled.</div>
}

View File

@ -10,8 +10,8 @@ import { VideoComment } from '@app/shared/shared-video-comment/video-comment.mod
import { VideoCommentService } from '@app/shared/shared-video-comment/video-comment.service'
import { NgbDropdown, NgbDropdownButtonItem, NgbDropdownItem, NgbDropdownMenu, NgbDropdownToggle } from '@ng-bootstrap/ng-bootstrap'
import { PeerTubeProblemDocument, ServerErrorCode, VideoCommentPolicy } from '@peertube/peertube-models'
import { Subject, Subscription } from 'rxjs'
import { InfiniteScrollerDirective } from '../../../../shared/shared-main/common/infinite-scroller.directive'
import { Subscription } from 'rxjs'
import { InfiniteScrollerComponent } from '../../../../shared/shared-main/common/infinite-scroller.component'
import { FeedComponent } from '../../../../shared/shared-main/feeds/feed.component'
import { LoaderComponent } from '../../../../shared/shared-main/common/loader.component'
import { VideoCommentAddComponent } from './video-comment-add.component'
@ -31,7 +31,7 @@ import { VideoCommentComponent } from './video-comment.component'
NgbDropdownItem,
NgIf,
VideoCommentAddComponent,
InfiniteScrollerDirective,
InfiniteScrollerComponent,
VideoCommentComponent,
NgFor,
LoaderComponent
@ -56,6 +56,8 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
totalItems: null
}
totalNotDeletedComments: number
hasMoreResults = true
isLoading = false
inReplyToCommentId: number
commentReplyRedraftValue: string
@ -68,8 +70,6 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
syndicationItems: Syndication[] = []
onDataSubject = new Subject<any[]>()
private sub: Subscription
constructor (
@ -161,13 +161,16 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
'filter:api.video-watch.video-threads.list.result'
)
this.isLoading = true
obs.subscribe({
next: res => {
this.comments = this.comments.concat(res.data)
this.componentPagination.totalItems = res.total
this.totalNotDeletedComments = res.totalNotDeletedComments
this.hasMoreResults = hasMoreItems(this.componentPagination)
this.onDataSubject.next(res.data)
this.isLoading = false
this.hooks.runAction('action:video-watch.video-threads.loaded', 'video-watch', { data: this.componentPagination })
},
@ -277,6 +280,10 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
return this.authService.isLoggedIn()
}
onPageChange (newPage: number) {
this.resetVideo(newPage)
}
onNearOfBottom () {
if (hasMoreItems(this.componentPagination)) {
this.componentPagination.currentPage++
@ -291,7 +298,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
comment.account = null
}
private resetVideo () {
private resetVideo (page = 1) {
if (this.video.commentsPolicy.id === VideoCommentPolicy.DISABLED) return
// Reset all our fields
@ -300,7 +307,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
this.threadComments = {}
this.threadLoading = {}
this.inReplyToCommentId = undefined
this.componentPagination.currentPage = 1
this.componentPagination.currentPage = page
this.componentPagination.totalItems = null
this.totalNotDeletedComments = null

View File

@ -10,6 +10,7 @@
background-color: pvar(--mainBackgroundColor);
overflow-y: auto;
border-bottom: 1px solid $separator-border-color;
display: block;
.widget-header {
background-color: pvar(--submenuBackgroundColor);

View File

@ -1,6 +1,12 @@
<div
*ngIf="playlist && (currentPlaylistPosition || noPlaylistVideos)" class="widget-root playlist"
myInfiniteScroller [onItself]="true" (nearOfBottom)="onPlaylistVideosNearOfBottom()"
<my-infinite-scroller
*ngIf="playlist && (currentPlaylistPosition || noPlaylistVideos)"
class="widget-root playlist"
[(currentPage)]="playlistPagination.currentPage"
[isLoading]="isLoading"
[hasMore]="hasMoreResults"
[onItself]="true"
(nearOfBottom)="onPlaylistVideosNearOfBottom()"
(currentPageChange)="onPageChange()"
>
<div class="widget-header playlist-info">
<div class="widget-title playlist-display-name">
@ -44,4 +50,4 @@
[touchScreenEditButton]="true"
></my-video-playlist-element-miniature>
</div>
</div>
</my-infinite-scroller>

View File

@ -27,6 +27,10 @@
}
}
::ng-deep .load-more {
margin: 20px 0;
}
my-video-playlist-element-miniature {
::ng-deep {
.video {

View File

@ -1,6 +1,6 @@
import { Component, EventEmitter, Input, Output } from '@angular/core'
import { Router } from '@angular/router'
import { AuthService, ComponentPagination, HooksService, Notifier, SessionStorageService, UserService } from '@app/core'
import { AuthService, ComponentPagination, hasMoreItems, HooksService, Notifier, SessionStorageService, UserService } from '@app/core'
import { isInViewport } from '@app/helpers'
import { getBoolOrDefault } from '@root-helpers/local-storage-utils'
import { peertubeSessionStorage } from '@root-helpers/peertube-web-storage'
@ -8,7 +8,7 @@ import { VideoPlaylistPrivacy } from '@peertube/peertube-models'
import { VideoPlaylistElementMiniatureComponent } from '../../../../shared/shared-video-playlist/video-playlist-element-miniature.component'
import { GlobalIconComponent } from '../../../../shared/shared-icons/global-icon.component'
import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'
import { InfiniteScrollerDirective } from '../../../../shared/shared-main/common/infinite-scroller.directive'
import { InfiniteScrollerComponent } from '../../../../shared/shared-main/common/infinite-scroller.component'
import { NgIf, NgClass, NgFor } from '@angular/common'
import { VideoPlaylist } from '@app/shared/shared-video-playlist/video-playlist.model'
import { VideoPlaylistElement } from '@app/shared/shared-video-playlist/video-playlist-element.model'
@ -19,7 +19,7 @@ import { VideoPlaylistService } from '@app/shared/shared-video-playlist/video-pl
templateUrl: './video-watch-playlist.component.html',
styleUrls: [ './player-widget.component.scss', './video-watch-playlist.component.scss' ],
standalone: true,
imports: [ NgIf, InfiniteScrollerDirective, NgClass, NgbTooltip, GlobalIconComponent, NgFor, VideoPlaylistElementMiniatureComponent ]
imports: [ NgIf, InfiniteScrollerComponent, NgClass, NgbTooltip, GlobalIconComponent, NgFor, VideoPlaylistElementMiniatureComponent ]
})
export class VideoWatchPlaylistComponent {
static SESSION_STORAGE_LOOP_PLAYLIST = 'loop_playlist'
@ -29,6 +29,9 @@ export class VideoWatchPlaylistComponent {
@Output() videoFound = new EventEmitter<string>()
@Output() noVideoFound = new EventEmitter<void>()
hasMoreResults = true
isLoading = true
playlistElements: VideoPlaylistElement[] = []
playlistPagination: ComponentPagination = {
currentPage: 1,
@ -42,7 +45,7 @@ export class VideoWatchPlaylistComponent {
loopPlaylist: boolean
loopPlaylistSwitchText = ''
noPlaylistVideos = false
noPlaylistVideos = true
currentPlaylistPosition: number
constructor (
@ -63,6 +66,14 @@ export class VideoWatchPlaylistComponent {
this.setLoopPlaylistSwitchText()
}
onPageChange () {
// Prevent triggering upon initial page load
if (this.isLoading) return
this.playlistElements = []
this.loadPlaylistElements(this.playlist, false)
}
onPlaylistVideosNearOfBottom (position?: number) {
// Last page
if (this.playlistPagination.totalItems <= (this.playlistPagination.currentPage * this.playlistPagination.itemsPerPage)) return
@ -103,10 +114,13 @@ export class VideoWatchPlaylistComponent {
'filter:api.video-watch.video-playlist-elements.get.params',
'filter:api.video-watch.video-playlist-elements.get.result'
)
this.isLoading = true
obs.subscribe(({ total, data: playlistElements }) => {
this.playlistElements = this.playlistElements.concat(playlistElements)
this.playlistPagination.totalItems = total
this.hasMoreResults = hasMoreItems(this.playlistPagination)
this.isLoading = false
const firstAvailableVideo = this.playlistElements.find(e => !!e.video)
if (!firstAvailableVideo) {

View File

@ -3,8 +3,12 @@
<div class="no-results" i18n *ngIf="notResults">No results.</div>
<div
myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()"
<my-infinite-scroller
[(currentPage)]="currentPage"
[isLoading]="isLoading"
(nearOfBottom)="onNearOfBottom()"
(currentPageChange)="onPageChange()"
[hasMore]="hasMoreResults"
>
<ng-container *ngFor="let overview of overviews">
@ -47,6 +51,6 @@
</ng-container>
</div>
</my-infinite-scroller>
</div>

View File

@ -1,4 +1,4 @@
import { Subject, Subscription, switchMap } from 'rxjs'
import { Subscription, switchMap } from 'rxjs'
import { Component, OnDestroy, OnInit } from '@angular/core'
import { Notifier, ScreenService, User, UserService } from '@app/core'
import { Video } from '@app/shared/shared-main/video/video.model'
@ -7,7 +7,7 @@ import { VideosOverview } from './videos-overview.model'
import { ActorAvatarComponent } from '../../../shared/shared-actor-image/actor-avatar.component'
import { VideoMiniatureComponent } from '../../../shared/shared-video-miniature/video-miniature.component'
import { RouterLink } from '@angular/router'
import { InfiniteScrollerDirective } from '../../../shared/shared-main/common/infinite-scroller.directive'
import { InfiniteScrollerComponent } from '../../../shared/shared-main/common/infinite-scroller.component'
import { NgIf, NgFor } from '@angular/common'
@Component({
@ -15,21 +15,21 @@ import { NgIf, NgFor } from '@angular/common'
templateUrl: './video-overview.component.html',
styleUrls: [ './video-overview.component.scss' ],
standalone: true,
imports: [ NgIf, InfiniteScrollerDirective, NgFor, RouterLink, VideoMiniatureComponent, ActorAvatarComponent ]
imports: [ NgIf, InfiniteScrollerComponent, NgFor, RouterLink, VideoMiniatureComponent, ActorAvatarComponent ]
})
export class VideoOverviewComponent implements OnInit, OnDestroy {
onDataSubject = new Subject<any>()
hasMoreResults = true
overviews: VideosOverview[] = []
notResults = false
userMiniature: User
currentPage = 1
isLoading = true
private loaded = false
private currentPage = 1
private maxPage = 20
private lastWasEmpty = false
private isLoading = false
private userSub: Subscription
@ -74,22 +74,26 @@ export class VideoOverviewComponent implements OnInit, OnDestroy {
return videos.slice(0, numberOfVideos * 2)
}
onPageChange () {
this.loadMoreResults(true)
}
onNearOfBottom () {
if (this.currentPage >= this.maxPage) return
if (this.lastWasEmpty) return
if (this.isLoading) return
this.currentPage++
this.loadMoreResults()
}
private loadMoreResults () {
private loadMoreResults (reset = false) {
this.isLoading = true
this.overviewService.getVideosOverview(this.currentPage)
.subscribe({
next: overview => {
this.isLoading = false
this.hasMoreResults = this.currentPage < this.maxPage
if (overview.tags.length === 0 && overview.channels.length === 0 && overview.categories.length === 0) {
this.lastWasEmpty = true
@ -99,8 +103,8 @@ export class VideoOverviewComponent implements OnInit, OnDestroy {
}
this.loaded = true
this.onDataSubject.next(overview)
if (reset) this.overviews = []
this.overviews.push(overview)
},

View File

@ -0,0 +1,7 @@
<ng-content></ng-content>
<div *ngIf="hasMore && !isLoading" class="load-more d-flex justify-content-center">
<a class="peertube-button-link orange-button" i18n [routerLink]="['.']" [queryParams]="{ page: currentPage + 1}" [queryParamsHandling]="'merge'">
Load more
</a>
</div>

View File

@ -0,0 +1,4 @@
.load-more {
grid-column-start: 1;
grid-column-end: -1;
}

View File

@ -1,16 +1,26 @@
import { fromEvent, Observable, Subscription } from 'rxjs'
import { fromEvent, Subscription } from 'rxjs'
import { distinctUntilChanged, filter, map, share, startWith, throttleTime } from 'rxjs/operators'
import { AfterViewChecked, Directive, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'
import { AfterViewChecked, Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'
import { PeerTubeRouterService, RouterSetting } from '@app/core'
import { I18nSelectPipe, NgIf } from '@angular/common'
import { ActivatedRoute, NavigationEnd, Router, RouterLink } from '@angular/router'
@Directive({
selector: '[myInfiniteScroller]',
standalone: true
@Component({
selector: 'my-infinite-scroller',
standalone: true,
templateUrl: './infinite-scroller.component.html',
styleUrl: './infinite-scroller.component.scss',
imports: [
NgIf,
RouterLink,
I18nSelectPipe
]
})
export class InfiniteScrollerDirective implements OnInit, OnDestroy, AfterViewChecked {
export class InfiniteScrollerComponent implements OnInit, OnDestroy, AfterViewChecked {
@Input() hasMore: boolean
@Input() isLoading: boolean
@Input() percentLimit = 70
@Input() onItself = false
@Input() dataObservable: Observable<any[]>
// Add angular state in query params to reuse the routed component
@Input() setAngularState: boolean
@ -18,37 +28,61 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy, AfterViewCh
@Output() nearOfBottom = new EventEmitter<void>()
@Input() currentPage!: number
@Output() currentPageChange = new EventEmitter<number>()
private disabled: boolean
private decimalLimit = 0
private lastCurrentBottom = -1
private scrollDownSub: Subscription
private container: HTMLElement
private checkScroll = false
private routeEventSub: Subscription
constructor (
private peertubeRouter: PeerTubeRouterService,
private el: ElementRef
private el: ElementRef,
private route: ActivatedRoute,
private router: Router
) {
this.decimalLimit = this.percentLimit / 100
}
ngAfterViewChecked () {
if (this.checkScroll) {
this.checkScroll = false
if (this.hasMore && !this.isLoading) {
// Wait HTML update
setTimeout(() => {
if (this.hasScroll() === false) this.nearOfBottom.emit()
if (this.hasScroll() === false && !this.disabled) this.nearOfBottom.emit()
})
}
}
ngOnInit () {
this.disabled = !!this.route.snapshot.queryParams.page
this.changePage(+this.route.snapshot.queryParams['page'] || 1)
this.routeEventSub = this.router.events
.pipe(
filter(event => event instanceof NavigationEnd)
)
.subscribe((event: NavigationEnd) => {
const search = event.url.split('?')[1]
const params = new URLSearchParams(search)
const newPage = +params.get('page') || 1
if (newPage === this.currentPage) return
this.changePage(newPage)
})
this.initialize()
}
ngOnDestroy () {
if (this.scrollDownSub) this.scrollDownSub.unsubscribe()
if (this.routeEventSub) this.routeEventSub.unsubscribe()
}
initialize () {
@ -78,14 +112,13 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy, AfterViewCh
.subscribe(() => {
if (this.setAngularState && !this.parentDisabled) this.setScrollRouteParams()
this.nearOfBottom.emit()
if (!this.disabled) this.nearOfBottom.emit()
})
}
if (this.dataObservable) {
this.dataObservable
.pipe(filter(d => d.length !== 0))
.subscribe(() => this.checkScroll = true)
}
private changePage (newPage: number) {
this.currentPage = newPage
this.currentPageChange.emit(newPage)
}
private getScrollInfo () {

View File

@ -40,10 +40,15 @@
<div class="no-results" i18n *ngIf="hasDoneFirstQuery && videos.length === 0 && highlightedLives.length === 0">No results.</div>
<div
myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onVideosDataSubject.asObservable()"
[setAngularState]="true" [parentDisabled]="disabled"
class="videos" [ngClass]="{ 'display-as-row': displayAsRow }"
<my-infinite-scroller
[(currentPage)]="pagination.currentPage"
(currentPageChange)="onPageChange()"
(nearOfBottom)="onNearOfBottom()"
[hasMore]="hasMoreResults"
[setAngularState]="true"
[parentDisabled]="disabled"
class="videos"
[ngClass]="{ 'display-as-row': displayAsRow }"
>
<ng-container *ngIf="highlightedLives.length !== 0">
<h2 class="date-title">
@ -80,11 +85,5 @@
</my-video-miniature>
</div>
</ng-container>
</div>
<div class="d-flex justify-content-center">
<a *ngIf="lastQueryLength === this.pagination.itemsPerPage" class="peertube-button-link orange-button" i18n [routerLink]="['.']" [queryParams]="{ page: pagination.currentPage + 1}" [queryParamsHandling]="'merge'">
Load more
</a>
</div>
</my-infinite-scroller>
</div>

View File

@ -1,6 +1,6 @@
import { NgClass, NgFor, NgIf, NgTemplateOutlet } from '@angular/common'
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, booleanAttribute } from '@angular/core'
import { ActivatedRoute, NavigationEnd, Router, RouterLink, RouterLinkActive } from '@angular/router'
import { ActivatedRoute, RouterLink, RouterLinkActive } from '@angular/router'
import {
AuthService,
ComponentPaginationLight,
@ -18,8 +18,8 @@ import { ResultList, UserRight, VideoSortField } from '@peertube/peertube-models
import { logger } from '@root-helpers/logger'
import debug from 'debug'
import { Observable, Subject, Subscription, forkJoin, fromEvent, of } from 'rxjs'
import { concatMap, debounceTime, filter, map, switchMap } from 'rxjs/operators'
import { InfiniteScrollerDirective } from '../shared-main/common/infinite-scroller.directive'
import { concatMap, debounceTime, map, switchMap } from 'rxjs/operators'
import { InfiniteScrollerComponent } from '../shared-main/common/infinite-scroller.component'
import { ButtonComponent } from '../shared-main/buttons/button.component'
import { FeedComponent } from '../shared-main/feeds/feed.component'
import { Syndication } from '../shared-main/feeds/syndication.model'
@ -65,10 +65,9 @@ enum GroupDate {
NgTemplateOutlet,
ButtonComponent,
VideoFiltersHeaderComponent,
InfiniteScrollerDirective,
InfiniteScrollerComponent,
VideoMiniatureComponent,
GlobalIconComponent,
RouterLink
GlobalIconComponent
]
})
export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
@ -98,11 +97,12 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
@Input() displayOptions: MiniatureDisplayOptions
@Input({ transform: booleanAttribute }) disabled: boolean
@Input({ transform: booleanAttribute }) disabled = false
@Output() filtersChanged = new EventEmitter<VideoFilters>()
@Output() videosLoaded = new EventEmitter<Video[]>()
hasMoreResults = true
videos: Video[] = []
highlightedLives: Video[] = []
@ -119,8 +119,6 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
itemsPerPage: 25
}
lastQueryLength: number
private defaultDisplayOptions: MiniatureDisplayOptions = {
date: true,
views: true,
@ -138,6 +136,8 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
private groupedDateLabels: { [id in GroupDate]: string }
private groupedDates: { [id: number]: GroupDate } = {}
private lastQueryLength: number
private videoRequests = new Subject<{
reset: boolean
obsVideos: Observable<Pick<ResultList<Video>, 'data'>>
@ -153,32 +153,13 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
private route: ActivatedRoute,
private screenService: ScreenService,
private peertubeRouter: PeerTubeRouterService,
private serverService: ServerService,
public router: Router
private serverService: ServerService
) {
}
ngOnInit () {
this.subscribeToVideoRequests()
this.disabled = this.disabled || this.route.snapshot.queryParams.finiteScroll === 'true'
this.router.events
.pipe(
filter(event => event instanceof NavigationEnd)
)
.subscribe((event: NavigationEnd) => {
const search = event.url.split('?')[1]
const params = new URLSearchParams(search)
const newPage = +params.get('page') || this.pagination.currentPage
if (newPage === this.pagination.currentPage) {
return
}
this.pagination.currentPage = newPage
this.loadMoreVideos(true)
})
const hiddenFilters = this.hideScopeFilter
? [ 'scope' ]
@ -211,8 +192,6 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
this.loadUserSettings(user)
}
this.scheduleOnFiltersChanged(false)
this.subscribeToAnonymousUpdate()
this.subscribeToSearchChange()
})
@ -272,6 +251,10 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
return video.id
}
onPageChange () {
this.loadMoreVideos(true)
}
onNearOfBottom () {
if (this.disabled) return
@ -312,7 +295,6 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
}
reloadVideos () {
this.pagination.currentPage = +this.route.snapshot.queryParams.page || 1
this.loadMoreVideos(true)
}
@ -504,6 +486,7 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
.subscribe({
next: ({ videos, highlightedLives, reset }) => {
this.hasDoneFirstQuery = true
this.hasMoreResults = videos.length === this.pagination.itemsPerPage
this.lastQueryLength = videos.length
if (reset) {

View File

@ -1,9 +1,14 @@
<div class="no-results" i18n *ngIf="hasDoneFirstQuery && videos.length === 0">{{ noResultMessage }}</div>
<div
<my-infinite-scroller
class="videos"
myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()"
[parentDisabled]="disabled" [setAngularState]="true"
[(currentPage)]="pagination.currentPage"
(nearOfBottom)="onNearOfBottom()"
[isLoading]="isLoading"
(currentPageChange)="onPageChange()"
[hasMore]="hasMoreResults"
[parentDisabled]="disabled"
[setAngularState]="true"
>
<div class="video" *ngFor="let video of videos; let i = index; trackBy: videoById">
@ -32,4 +37,4 @@
<ng-container *ngTemplateOutlet="rowButtonsTemplate; context: {$implicit: video}"></ng-container>
</ng-container>
</div>
</div>
</my-infinite-scroller>

View File

@ -1,4 +1,4 @@
import { Observable, Subject } from 'rxjs'
import { Observable } from 'rxjs'
import { AfterContentInit, Component, ContentChildren, EventEmitter, Input, Output, QueryList, TemplateRef } from '@angular/core'
import { ComponentPagination, Notifier, User } from '@app/core'
import { logger } from '@root-helpers/logger'
@ -7,7 +7,7 @@ import { ResultList, VideosExistInPlaylists, VideoSortField } from '@peertube/pe
import { MiniatureDisplayOptions, VideoMiniatureComponent } from './video-miniature.component'
import { FormsModule } from '@angular/forms'
import { PeertubeCheckboxComponent } from '../shared-forms/peertube-checkbox.component'
import { InfiniteScrollerDirective } from '../shared-main/common/infinite-scroller.directive'
import { InfiniteScrollerComponent } from '../shared-main/common/infinite-scroller.component'
import { NgIf, NgFor, NgTemplateOutlet } from '@angular/common'
import { Video } from '../shared-main/video/video.model'
import { PeerTubeTemplateDirective } from '../shared-main/common/peertube-template.directive'
@ -19,7 +19,7 @@ export type SelectionType = { [ id: number ]: boolean }
templateUrl: './videos-selection.component.html',
styleUrls: [ './videos-selection.component.scss' ],
standalone: true,
imports: [ NgIf, InfiniteScrollerDirective, NgFor, PeertubeCheckboxComponent, FormsModule, VideoMiniatureComponent, NgTemplateOutlet ]
imports: [ NgIf, InfiniteScrollerComponent, NgFor, PeertubeCheckboxComponent, FormsModule, VideoMiniatureComponent, NgTemplateOutlet ]
})
export class VideosSelectionComponent implements AfterContentInit {
@Input() videosContainedInPlaylists: VideosExistInPlaylists
@ -44,14 +44,14 @@ export class VideosSelectionComponent implements AfterContentInit {
_selection: SelectionType = {}
hasMoreResults = true
isLoading = true
rowButtonsTemplate: TemplateRef<any>
globalButtonsTemplate: TemplateRef<any>
videos: Video[] = []
sort: VideoSortField = '-publishedAt'
onDataSubject = new Subject<any[]>()
hasDoneFirstQuery = false
private lastQueryLength: number
@ -88,8 +88,6 @@ export class VideosSelectionComponent implements AfterContentInit {
const t = this.templates.find(t => t.name === 'globalButtons')
if (t) this.globalButtonsTemplate = t.template
}
this.loadMoreVideos()
}
getVideosObservable (page: number) {
@ -108,6 +106,10 @@ export class VideosSelectionComponent implements AfterContentInit {
return video.id
}
onPageChange () {
this.loadMoreVideos(true)
}
onNearOfBottom () {
if (this.disabled) return
@ -121,10 +123,12 @@ export class VideosSelectionComponent implements AfterContentInit {
loadMoreVideos (reset = false) {
if (reset) this.hasDoneFirstQuery = false
this.isLoading = true
this.getVideosObservable(this.pagination.currentPage)
.subscribe({
next: ({ data }) => {
this.hasMoreResults = data.length === this.pagination.itemsPerPage
this.hasDoneFirstQuery = true
this.lastQueryLength = data.length
@ -132,7 +136,7 @@ export class VideosSelectionComponent implements AfterContentInit {
this.videos = this.videos.concat(data)
this.videosModel = this.videos
this.onDataSubject.next(data)
this.isLoading = false
},
error: err => {
@ -146,7 +150,7 @@ export class VideosSelectionComponent implements AfterContentInit {
reloadVideos () {
this.pagination.currentPage = 1
this.loadMoreVideos(true)
this.onPageChange()
}
removeVideoFromArray (video: Video) {

View File

@ -1,6 +1,13 @@
<div *ngIf="componentPagination.totalItems === 0" class="no-notification" i18n>You don't have notifications.</div>
<div class="notifications" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()">
<my-infinite-scroller
class="notifications"
[(currentPage)]="componentPagination.currentPage"
[isLoading]="isLoading"
(currentPageChange)="onPageChange()"
(nearOfBottom)="onNearOfBottom()"
[hasMore]="hasMoreResults"
>
<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->
<div *ngFor="let notification of notifications" class="notification" [ngClass]="{ unread: !notification.read }" (click)="markAsRead(notification)">
@ -258,4 +265,4 @@
<div [title]="notification.createdAt" class="from-date">{{ notification.createdAt | myFromNow }}</div>
</div>
</div>
</my-infinite-scroller>

View File

@ -6,7 +6,7 @@ import { CommonModule } from '@angular/common'
import { GlobalIconComponent } from '../shared-icons/global-icon.component'
import { RouterLink } from '@angular/router'
import { FromNowPipe } from '../shared-main/date/from-now.pipe'
import { InfiniteScrollerDirective } from '../shared-main/common/infinite-scroller.directive'
import { InfiniteScrollerComponent } from '../shared-main/common/infinite-scroller.component'
import { UserNotificationService } from '../shared-main/users/user-notification.service'
import { UserNotification } from '../shared-main/users/user-notification.model'
@ -15,7 +15,7 @@ import { UserNotification } from '../shared-main/users/user-notification.model'
templateUrl: 'user-notifications.component.html',
styleUrls: [ 'user-notifications.component.scss' ],
standalone: true,
imports: [ CommonModule, GlobalIconComponent, RouterLink, FromNowPipe, InfiniteScrollerDirective ]
imports: [ CommonModule, GlobalIconComponent, RouterLink, FromNowPipe, InfiniteScrollerComponent ]
})
export class UserNotificationsComponent implements OnInit {
@Input() ignoreLoadingBar = false
@ -29,8 +29,8 @@ export class UserNotificationsComponent implements OnInit {
sortField = 'createdAt'
componentPagination: ComponentPagination
onDataSubject = new Subject<any[]>()
hasMoreResults = true
isLoading = true
constructor (
private userNotificationService: UserNotificationService,
@ -44,8 +44,6 @@ export class UserNotificationsComponent implements OnInit {
totalItems: null
}
this.loadNotifications()
if (this.markAllAsReadSubject) {
this.markAllAsReadSubject.subscribe(() => this.markAllAsRead())
}
@ -61,22 +59,27 @@ export class UserNotificationsComponent implements OnInit {
order: this.sortField === 'createdAt' ? -1 : 1
}
}
this.isLoading = true
this.userNotificationService.listMyNotifications(options)
.subscribe({
next: result => {
this.notifications = reset ? result.data : this.notifications.concat(result.data)
this.componentPagination.totalItems = result.total
this.hasMoreResults = hasMoreItems(this.componentPagination)
this.notificationsLoaded.emit()
this.onDataSubject.next(result.data)
this.isLoading = false
},
error: err => this.notifier.error(err.message)
})
}
onPageChange () {
this.loadNotifications(true)
}
onNearOfBottom () {
if (this.infiniteScroll === false) return