diff --git a/.eslintrc.json b/.eslintrc.cjs
similarity index 97%
rename from .eslintrc.json
rename to .eslintrc.cjs
index 17afd1b835c..af0b9c9e44f 100644
--- a/.eslintrc.json
+++ b/.eslintrc.cjs
@@ -1,4 +1,4 @@
-{
+module.exports = {
"extends": "standard-with-typescript",
"root": true,
"rules": {
@@ -146,6 +146,7 @@
"project": [
"./tsconfig.eslint.json"
],
- "EXPERIMENTAL_useSourceOfProjectReferenceRedirect": true
+ "EXPERIMENTAL_useSourceOfProjectReferenceRedirect": true,
+ "tsconfigRootDir": __dirname
}
}
diff --git a/client/.eslintrc.json b/client/.eslintrc.cjs
similarity index 97%
rename from client/.eslintrc.json
rename to client/.eslintrc.cjs
index d00e6e7abbc..5c2e1ae3255 100644
--- a/client/.eslintrc.json
+++ b/client/.eslintrc.cjs
@@ -1,4 +1,4 @@
-{
+module.exports = {
"root": true,
"ignorePatterns": [
"projects/**/*",
@@ -15,10 +15,11 @@
"tsconfig.eslint.json"
],
"EXPERIMENTAL_useSourceOfProjectReferenceRedirect": true,
- "createDefaultProgram": false
+ "createDefaultProgram": false,
+ "tsconfigRootDir": __dirname,
},
"extends": [
- "../.eslintrc.json",
+ "../.eslintrc.cjs",
"plugin:@angular-eslint/recommended",
"plugin:@angular-eslint/template/process-inline-templates"
],
diff --git a/client/src/app/+accounts/account-video-channels/account-video-channels.component.html b/client/src/app/+accounts/account-video-channels/account-video-channels.component.html
index e3b4997737f..a80ac5e4779 100644
--- a/client/src/app/+accounts/account-video-channels/account-video-channels.component.html
+++ b/client/src/app/+accounts/account-video-channels/account-video-channels.component.html
@@ -4,7 +4,14 @@
Video channels
This account does not have channels.
-
-
+
diff --git a/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts b/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts
index 34e03d6fd59..31d91e68265 100644
--- a/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts
+++ b/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts
@@ -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/angular/infinite-scroller.directive'
+import { InfiniteScrollerComponent } from '../../shared/shared-main/angular/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/video-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 () {
- const options = {
- account: this.account,
- componentPagination: this.channelPagination,
- sort: '-updatedAt'
- }
+ loadMoreChannels (reset = false) {
+ let hasDoneReset = false
+ this.isLoading = true
- 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
diff --git a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.html b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.html
index e80855a023b..6f16435b5f2 100644
--- a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.html
+++ b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.html
@@ -4,7 +4,14 @@
{{ getNoResultMessage() }}
-
+
diff --git a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts
index 61407a7b3ec..dac3af36174 100644
--- a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts
+++ b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts
@@ -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/angular/infinite-scroller.directive'
+import { InfiniteScrollerComponent } from '../../../shared/shared-main/angular/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()
+ private hasInitialized = new ReplaySubject()
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
diff --git a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html
index 189002eae01..ad3b784f52f 100644
--- a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html
+++ b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html
@@ -28,7 +28,14 @@
No results.
-
+
@@ -58,4 +65,4 @@
-
+
diff --git a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts
index fbb59628b7d..70914a1bedd 100644
--- a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts
+++ b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts
@@ -1,5 +1,5 @@
-import { Subject } from 'rxjs'
-import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
+import { ReplaySubject, Subject } from 'rxjs'
+import { debounceTime, distinct, distinctUntilChanged, filter } from 'rxjs/operators'
import { Component, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
@@ -9,7 +9,7 @@ import { logger } from '@root-helpers/logger'
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/angular/infinite-scroller.directive'
+import { InfiniteScrollerComponent } from '../../../shared/shared-main/angular/infinite-scroller.component'
import { AutofocusDirective } from '../../../shared/shared-main/angular/autofocus.directive'
import { GlobalIconComponent } from '../../../shared/shared-icons/global-icon.component'
import { NgIf, NgFor } from '@angular/common'
@@ -25,7 +25,7 @@ import { PluginNavigationComponent } from '../shared/plugin-navigation.component
NgIf,
GlobalIconComponent,
AutofocusDirective,
- InfiniteScrollerDirective,
+ InfiniteScrollerComponent,
NgFor,
PluginCardComponent,
EditButtonComponent,
@@ -41,17 +41,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
()
-
private searchSubject = new Subject()
+ private hasInitialized = new ReplaySubject()
constructor (
private pluginService: PluginService,
@@ -73,10 +73,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()
@@ -93,14 +98,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)
@@ -108,10 +106,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 => {
@@ -123,6 +122,14 @@ export class PluginSearchComponent implements OnInit {
})
}
+ onPageChange () {
+ this.hasInitialized.pipe(
+ distinct(),
+ filter(val => val)
+ )
+ .subscribe(() => this.loadMorePlugins(true))
+ }
+
onNearOfBottom () {
if (!hasMoreItems(this.pagination)) return
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html
index 58a43272e9f..e9c3151ccfe 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html
+++ b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html
@@ -16,7 +16,7 @@
-
+
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts
index a0060864e2b..ddead5ed070 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts
+++ b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts
@@ -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/angular/defer-loading.directive'
-import { InfiniteScrollerDirective } from '../../shared/shared-main/angular/infinite-scroller.directive'
+import { InfiniteScrollerComponent } from '../../shared/shared-main/angular/infinite-scroller.component'
import { NumberFormatterPipe } from '../../shared/shared-main/angular/number-formatter.pipe'
import { DeleteButtonComponent } from '../../shared/shared-main/buttons/delete-button.component'
import { EditButtonComponent } from '../../shared/shared-main/buttons/edit-button.component'
@@ -29,7 +29,7 @@ import { ChannelsSetupMessageComponent } from '../../shared/shared-main/misc/cha
RouterLink,
ChannelsSetupMessageComponent,
AdvancedInputFilterComponent,
- InfiniteScrollerDirective,
+ InfiniteScrollerComponent,
NgFor,
ActorAvatarComponent,
EditButtonComponent,
@@ -41,6 +41,8 @@ import { ChannelsSetupMessageComponent } from '../../shared/shared-main/misc/cha
})
export class MyVideoChannelsComponent {
videoChannels: VideoChannel[] = []
+ hasMoreResults = true
+ isLoading = true
videoChannelsChartData: ChartData[]
@@ -74,9 +76,7 @@ export class MyVideoChannelsComponent {
this.search = search
this.pagination.currentPage = 1
- this.videoChannels = []
-
- this.loadMoreVideoChannels()
+ this.onPageChange()
}
async deleteVideoChannel (videoChannel: VideoChannel) {
@@ -108,17 +108,24 @@ export class MyVideoChannelsComponent {
})
}
+ onPageChange () {
+ this.loadMoreVideoChannels(true)
+ }
+
onNearOfBottom () {
- if (!hasMoreItems(this.pagination)) return
+ if (!hasMoreItems(this.pagination)) {
+ return
+ }
this.pagination.currentPage += 1
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(
@@ -133,6 +140,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
diff --git a/client/src/app/+my-library/my-follows/my-followers.component.html b/client/src/app/+my-library/my-follows/my-followers.component.html
index 743187edb75..4a220140e93 100644
--- a/client/src/app/+my-library/my-follows/my-followers.component.html
+++ b/client/src/app/+my-library/my-follows/my-followers.component.html
@@ -7,12 +7,19 @@
No follower found.
-
-
+
diff --git a/client/src/app/+my-library/my-follows/my-followers.component.ts b/client/src/app/+my-library/my-follows/my-followers.component.ts
index 7b1a8edc891..8355f6a745c 100644
--- a/client/src/app/+my-library/my-follows/my-followers.component.ts
+++ b/client/src/app/+my-library/my-follows/my-followers.component.ts
@@ -4,20 +4,21 @@ 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/angular/infinite-scroller.directive'
+import { InfiniteScrollerComponent } from '../../shared/shared-main/angular/infinite-scroller.component'
@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,
@@ -25,7 +26,6 @@ export class MyFollowersComponent implements OnInit {
totalItems: null
}
- onDataSubject = new Subject()
search: string
inputFilters: AdvancedInputFilter[]
@@ -57,9 +57,15 @@ export class MyFollowersComponent implements OnInit {
]
}
+ onPageChange () {
+ this.loadFollowers(false)
+ }
+
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.loadFollowers()
@@ -75,6 +81,8 @@ export class MyFollowersComponent implements OnInit {
}
private loadFollowers (more = true) {
+ this.isLoading = true
+
this.userSubscriptionService.listFollowers({
pagination: this.pagination,
nameWithHost: this.getUsername(),
@@ -85,8 +93,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)
diff --git a/client/src/app/+my-library/my-follows/my-subscriptions.component.html b/client/src/app/+my-library/my-follows/my-subscriptions.component.html
index f928d8caba7..23527187fd6 100644
--- a/client/src/app/+my-library/my-follows/my-subscriptions.component.html
+++ b/client/src/app/+my-library/my-follows/my-subscriptions.component.html
@@ -7,12 +7,19 @@
You don't have any subscription yet.
-
+
diff --git a/client/src/app/+my-library/my-follows/my-subscriptions.component.ts b/client/src/app/+my-library/my-follows/my-subscriptions.component.ts
index 0cf70d6f911..fe4ac039d32 100644
--- a/client/src/app/+my-library/my-follows/my-subscriptions.component.ts
+++ b/client/src/app/+my-library/my-follows/my-subscriptions.component.ts
@@ -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/angular/infinite-scroller.directive'
+import { InfiniteScrollerComponent } from '../../shared/shared-main/angular/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'
@@ -19,7 +18,7 @@ import { UserSubscriptionService } from '@app/shared/shared-user-subscription/us
GlobalIconComponent,
NgIf,
AdvancedInputFilterComponent,
- InfiniteScrollerDirective,
+ InfiniteScrollerComponent,
NgFor,
ActorAvatarComponent,
RouterLink,
@@ -28,25 +27,31 @@ import { UserSubscriptionService } from '@app/shared/shared-user-subscription/us
})
export class MySubscriptionsComponent {
videoChannels: VideoChannel[] = []
+ hasMoreResults = true
+ isLoading = true
pagination: ComponentPagination = {
currentPage: 1,
itemsPerPage: 10,
totalItems: null
}
- onDataSubject = new Subject()
-
search: string
constructor (
private userSubscriptionService: UserSubscriptionService,
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()
@@ -58,6 +63,7 @@ export class MySubscriptionsComponent {
}
private loadSubscriptions (more = true) {
+ this.isLoading = true
this.userSubscriptionService.listSubscriptions({ pagination: this.pagination, search: this.search })
.subscribe({
next: res => {
@@ -65,8 +71,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)
diff --git a/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.html b/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.html
index df4687ce97a..532bcb92182 100644
--- a/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.html
+++ b/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.html
@@ -33,9 +33,15 @@
-
+
diff --git a/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.scss b/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.scss
index 295f2015d70..4ac317d1ddf 100644
--- a/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.scss
+++ b/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.scss
@@ -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) {
diff --git a/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.ts b/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.ts
index 27b5158d361..f7bae203335 100644
--- a/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.ts
+++ b/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.ts
@@ -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/angular/infinite-scroller.directive'
+import { InfiniteScrollerComponent } from '../../shared/shared-main/angular/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()
-
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
})
}
diff --git a/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.html b/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.html
index 79b90807b8f..91c26cb831d 100644
--- a/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.html
+++ b/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.html
@@ -7,7 +7,7 @@
-
-
+
diff --git a/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.ts b/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.ts
index 109cab80052..4a651aef78b 100644
--- a/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.ts
+++ b/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.ts
@@ -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/angular/infinite-scroller.directive'
+import { InfiniteScrollerComponent } from '../../shared/shared-main/angular/infinite-scroller.component'
import { RouterLink } from '@angular/router'
import { AdvancedInputFilterComponent } from '../../shared/shared-forms/advanced-input-filter.component'
import { ChannelsSetupMessageComponent } from '../../shared/shared-main/misc/channels-setup-message.component'
@@ -25,7 +24,7 @@ import { VideoPlaylistService } from '@app/shared/shared-video-playlist/video-pl
ChannelsSetupMessageComponent,
AdvancedInputFilterComponent,
RouterLink,
- InfiniteScrollerDirective,
+ InfiniteScrollerComponent,
NgFor,
VideoPlaylistMiniatureComponent,
DeleteButtonComponent,
@@ -34,6 +33,8 @@ import { VideoPlaylistService } from '@app/shared/shared-video-playlist/video-pl
})
export class MyVideoPlaylistsComponent {
videoPlaylists: VideoPlaylist[] = []
+ hasMoreResults = true
+ isLoading = true
pagination: ComponentPagination = {
currentPage: 1,
@@ -41,8 +42,6 @@ export class MyVideoPlaylistsComponent {
totalItems: null
}
- onDataSubject = new Subject()
-
search: string
constructor (
@@ -76,9 +75,15 @@ 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
+ if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) {
+ return
+ }
this.pagination.currentPage += 1
this.loadVideoPlaylists()
@@ -90,6 +95,8 @@ export class MyVideoPlaylistsComponent {
}
private loadVideoPlaylists (reset = false) {
+ this.isLoading = true
+
this.authService.userInformationLoaded
.pipe(mergeMap(() => {
const user = this.authService.getUser()
@@ -100,8 +107,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
})
}
}
diff --git a/client/src/app/+search/search.component.html b/client/src/app/+search/search.component.html
index 7e154e953dc..312ecc03651 100644
--- a/client/src/app/+search/search.component.html
+++ b/client/src/app/+search/search.component.html
@@ -1,4 +1,11 @@
-
+
diff --git a/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.ts b/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.ts
index 3afc16a914c..969c460fb7e 100644
--- a/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.ts
+++ b/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.ts
@@ -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/angular/infinite-scroller.directive'
+import { InfiniteScrollerComponent } from '../../shared/shared-main/angular/infinite-scroller.component'
import { NgIf, NgFor } from '@angular/common'
import { VideoChannel } from '@app/shared/shared-main/video-channel/video-channel.model'
import { VideoChannelService } from '@app/shared/shared-main/video-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()
+ 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
})
}
}
diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.html b/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.html
index b2cbd01f63e..c8a104e7002 100644
--- a/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.html
+++ b/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.html
@@ -28,7 +28,14 @@
No comments.
-
+
} @else {
Comments are disabled.
}
diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.ts b/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.ts
index e88b1699cf2..67f7373e2e0 100644
--- a/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.ts
+++ b/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.ts
@@ -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/angular/infinite-scroller.directive'
+import { Subscription } from 'rxjs'
+import { InfiniteScrollerComponent } from '../../../../shared/shared-main/angular/infinite-scroller.component'
import { FeedComponent } from '../../../../shared/shared-main/feeds/feed.component'
import { LoaderComponent } from '../../../../shared/shared-main/loaders/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
()
-
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
diff --git a/client/src/app/+videos/+video-watch/shared/player-widgets/player-widget.component.scss b/client/src/app/+videos/+video-watch/shared/player-widgets/player-widget.component.scss
index 13163d4ee67..cbe5d6a4a79 100644
--- a/client/src/app/+videos/+video-watch/shared/player-widgets/player-widget.component.scss
+++ b/client/src/app/+videos/+video-watch/shared/player-widgets/player-widget.component.scss
@@ -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);
diff --git a/client/src/app/+videos/+video-watch/shared/player-widgets/video-watch-playlist.component.html b/client/src/app/+videos/+video-watch/shared/player-widgets/video-watch-playlist.component.html
index 3cbfa33ad88..7737289c0c8 100644
--- a/client/src/app/+videos/+video-watch/shared/player-widgets/video-watch-playlist.component.html
+++ b/client/src/app/+videos/+video-watch/shared/player-widgets/video-watch-playlist.component.html
@@ -1,6 +1,12 @@
-
-
+
diff --git a/client/src/app/+videos/+video-watch/shared/player-widgets/video-watch-playlist.component.scss b/client/src/app/+videos/+video-watch/shared/player-widgets/video-watch-playlist.component.scss
index 24c9fb5c068..dd2faf166c2 100644
--- a/client/src/app/+videos/+video-watch/shared/player-widgets/video-watch-playlist.component.scss
+++ b/client/src/app/+videos/+video-watch/shared/player-widgets/video-watch-playlist.component.scss
@@ -27,6 +27,10 @@
}
}
+ ::ng-deep .load-more {
+ margin: 20px 0;
+ }
+
my-video-playlist-element-miniature {
::ng-deep {
.video {
diff --git a/client/src/app/+videos/+video-watch/shared/player-widgets/video-watch-playlist.component.ts b/client/src/app/+videos/+video-watch/shared/player-widgets/video-watch-playlist.component.ts
index 85601fd8afe..8038084722a 100644
--- a/client/src/app/+videos/+video-watch/shared/player-widgets/video-watch-playlist.component.ts
+++ b/client/src/app/+videos/+video-watch/shared/player-widgets/video-watch-playlist.component.ts
@@ -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/angular/infinite-scroller.directive'
+import { InfiniteScrollerComponent } from '../../../../shared/shared-main/angular/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
()
@Output() noVideoFound = new EventEmitter()
+ 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) {
diff --git a/client/src/app/+videos/video-list/overview/video-overview.component.html b/client/src/app/+videos/video-list/overview/video-overview.component.html
index b38516af82e..9a7d070f1ca 100644
--- a/client/src/app/+videos/video-list/overview/video-overview.component.html
+++ b/client/src/app/+videos/video-list/overview/video-overview.component.html
@@ -3,8 +3,12 @@ Discover
No results.
-
@@ -47,6 +51,6 @@ {{ object.channel.displayName }}
-
+
diff --git a/client/src/app/+videos/video-list/overview/video-overview.component.ts b/client/src/app/+videos/video-list/overview/video-overview.component.ts
index 3aaf338ba26..ac39dd2b9a9 100644
--- a/client/src/app/+videos/video-list/overview/video-overview.component.ts
+++ b/client/src/app/+videos/video-list/overview/video-overview.component.ts
@@ -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/angular/infinite-scroller.directive'
+import { InfiniteScrollerComponent } from '../../../shared/shared-main/angular/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()
+ 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)
},
diff --git a/client/src/app/shared/shared-main/angular/infinite-scroller.component.html b/client/src/app/shared/shared-main/angular/infinite-scroller.component.html
new file mode 100644
index 00000000000..8a00e78a105
--- /dev/null
+++ b/client/src/app/shared/shared-main/angular/infinite-scroller.component.html
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/client/src/app/shared/shared-main/angular/infinite-scroller.component.scss b/client/src/app/shared/shared-main/angular/infinite-scroller.component.scss
new file mode 100644
index 00000000000..61deddbe92f
--- /dev/null
+++ b/client/src/app/shared/shared-main/angular/infinite-scroller.component.scss
@@ -0,0 +1,4 @@
+.load-more {
+ grid-column-start: 1;
+ grid-column-end: -1;
+}
diff --git a/client/src/app/shared/shared-main/angular/infinite-scroller.directive.ts b/client/src/app/shared/shared-main/angular/infinite-scroller.component.ts
similarity index 58%
rename from client/src/app/shared/shared-main/angular/infinite-scroller.directive.ts
rename to client/src/app/shared/shared-main/angular/infinite-scroller.component.ts
index 2ad446e9287..56c897c17f9 100644
--- a/client/src/app/shared/shared-main/angular/infinite-scroller.directive.ts
+++ b/client/src/app/shared/shared-main/angular/infinite-scroller.component.ts
@@ -1,54 +1,89 @@
-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'
-
-@Directive({
- selector: '[myInfiniteScroller]',
- standalone: true
+import { I18nSelectPipe, NgIf } from '@angular/common'
+import { ActivatedRoute, NavigationEnd, Router, RouterLink } from '@angular/router'
+
+@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
// Add angular state in query params to reuse the routed component
@Input() setAngularState: boolean
@Input() parentDisabled = false
@Output() nearOfBottom = new EventEmitter()
+ @Output() pageChange = new EventEmitter()
+
+ @Input() currentPage!: number
+ @Output() currentPageChange = new EventEmitter()
+
+ 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 +113,14 @@ 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)
+ this.pageChange.emit(newPage)
}
private getScrollInfo () {
diff --git a/client/src/app/shared/shared-video-miniature/videos-list.component.html b/client/src/app/shared/shared-video-miniature/videos-list.component.html
index e6f735e7c62..7818ae5db0e 100644
--- a/client/src/app/shared/shared-video-miniature/videos-list.component.html
+++ b/client/src/app/shared/shared-video-miniature/videos-list.component.html
@@ -40,10 +40,15 @@ No results.
-
@@ -80,5 +85,5 @@
-
+
diff --git a/client/src/app/shared/shared-video-miniature/videos-list.component.ts b/client/src/app/shared/shared-video-miniature/videos-list.component.ts
index cbd555a7279..23cdbb78d12 100644
--- a/client/src/app/shared/shared-video-miniature/videos-list.component.ts
+++ b/client/src/app/shared/shared-video-miniature/videos-list.component.ts
@@ -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, RouterLink, RouterLinkActive } from '@angular/router'
+import { ActivatedRoute, Router, RouterLink, RouterLinkActive } from '@angular/router'
import {
AuthService,
ComponentPaginationLight,
@@ -19,7 +19,7 @@ import { logger } from '@root-helpers/logger'
import debug from 'debug'
import { Observable, Subject, Subscription, forkJoin, fromEvent, of } from 'rxjs'
import { concatMap, debounceTime, map, switchMap } from 'rxjs/operators'
-import { InfiniteScrollerDirective } from '../shared-main/angular/infinite-scroller.directive'
+import { InfiniteScrollerComponent } from '../shared-main/angular/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,7 +65,7 @@ enum GroupDate {
NgTemplateOutlet,
ButtonComponent,
VideoFiltersHeaderComponent,
- InfiniteScrollerDirective,
+ InfiniteScrollerComponent,
VideoMiniatureComponent,
GlobalIconComponent
]
@@ -97,11 +97,12 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
@Input() displayOptions: MiniatureDisplayOptions
- @Input({ transform: booleanAttribute }) disabled = false
+ @Input({ transform: booleanAttribute }) disabled: boolean
@Output() filtersChanged = new EventEmitter()
@Output() videosLoaded = new EventEmitter