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 c3ef1d894..63f0514fd 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 @@ -1,11 +1,17 @@ -
- - Avatar +
-
{{ videoChannel.displayName }}
-
{{ videoChannel.followersCount }} subscribers
-
-
\ No newline at end of file +
+ +
+
diff --git a/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss b/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss index 0c6de2efa..f2604684e 100644 --- a/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss +++ b/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss @@ -1,30 +1,13 @@ @import '_variables'; @import '_mixins'; +@import '_miniature'; -.row { - justify-content: center; +.margin-content { + @include adapt-margin-content-width; } -a.video-channel { - @include disable-default-a-behaviour; +.section { + @include miniature-rows; - display: inline-block; - text-align: center; - color: var(--mainForegroundColor); - margin: 10px 30px; - - img { - @include avatar(80px); - - margin-bottom: 10px; - } - - .video-channel-display-name { - font-size: 20px; - font-weight: $font-bold; - } - - .video-channel-followers { - font-size: 15px; - } -} \ No newline at end of file + padding-top: 0 !important; +} 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 44f5626bb..ee3b5f8e4 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 @@ -3,9 +3,14 @@ import { ActivatedRoute } from '@angular/router' import { Account } from '@app/shared/account/account.model' import { AccountService } from '@app/shared/account/account.service' import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' -import { flatMap, map, tap } from 'rxjs/operators' -import { Subscription } from 'rxjs' +import { concatMap, map, switchMap, tap } from 'rxjs/operators' +import { from, Subscription } from 'rxjs' import { VideoChannel } from '@app/shared/video-channel/video-channel.model' +import { Video } from '@app/shared/video/video.model' +import { AuthService } from '@app/core' +import { VideoService } from '@app/shared/video/video.service' +import { VideoSortField } from '@app/shared/video/sort-field.type' +import { ComponentPagination, hasMoreItems } from '@app/shared/rest/component-pagination.model' @Component({ selector: 'my-account-video-channels', @@ -15,27 +20,73 @@ import { VideoChannel } from '@app/shared/video-channel/video-channel.model' export class AccountVideoChannelsComponent implements OnInit, OnDestroy { account: Account videoChannels: VideoChannel[] = [] + videos: { [id: number]: Video[] } = {} + + channelPagination: ComponentPagination = { + currentPage: 1, + itemsPerPage: 2 + } + + videosPagination: ComponentPagination = { + currentPage: 1, + itemsPerPage: 12 + } + videosSort: VideoSortField = '-publishedAt' private accountSub: Subscription constructor ( - protected route: ActivatedRoute, + private route: ActivatedRoute, + private authService: AuthService, private accountService: AccountService, - private videoChannelService: VideoChannelService + private videoChannelService: VideoChannelService, + private videoService: VideoService ) { } + get user () { + return this.authService.getUser() + } + ngOnInit () { // Parent get the account for us this.accountSub = this.accountService.accountLoaded - .pipe( - tap(account => this.account = account), - flatMap(account => this.videoChannelService.listAccountVideoChannels(account)), - map(res => res.data) - ) - .subscribe(videoChannels => this.videoChannels = videoChannels) + .subscribe(account => { + this.account = account + + this.loadMoreChannels() + }) } ngOnDestroy () { if (this.accountSub) this.accountSub.unsubscribe() } + + loadMoreChannels () { + this.videoChannelService.listAccountVideoChannels(this.account, this.channelPagination) + .pipe( + tap(res => this.channelPagination.totalItems = res.total), + switchMap(res => from(res.data)), + concatMap(videoChannel => { + return this.videoService.getVideoChannelVideos(videoChannel, this.videosPagination, this.videosSort) + .pipe(map(data => ({ videoChannel, videos: data.videos }))) + }) + ) + .subscribe(({ videoChannel, videos }) => { + this.videoChannels.push(videoChannel) + + this.videos[videoChannel.id] = videos + }) + } + + getVideosOf (videoChannel: VideoChannel) { + return this.videos[ videoChannel.id ] || [] + } + + onNearOfBottom () { + if (!hasMoreItems(this.channelPagination)) return + + this.channelPagination.currentPage += 1 + + this.loadMoreChannels() + } } diff --git a/client/src/app/+accounts/account-videos/account-videos.component.ts b/client/src/app/+accounts/account-videos/account-videos.component.ts index 0d579fa0c..6d26a4322 100644 --- a/client/src/app/+accounts/account-videos/account-videos.component.ts +++ b/client/src/app/+accounts/account-videos/account-videos.component.ts @@ -41,8 +41,6 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit, private videoService: VideoService ) { super() - - this.titlePage = this.i18n('Published videos') } ngOnInit () { diff --git a/client/src/app/+accounts/accounts-routing.module.ts b/client/src/app/+accounts/accounts-routing.module.ts index 531d763c4..55bce351a 100644 --- a/client/src/app/+accounts/accounts-routing.module.ts +++ b/client/src/app/+accounts/accounts-routing.module.ts @@ -14,7 +14,7 @@ const accountsRoutes: Routes = [ children: [ { path: '', - redirectTo: 'videos', + redirectTo: 'video-channels', pathMatch: 'full' }, { diff --git a/client/src/app/+accounts/accounts.component.html b/client/src/app/+accounts/accounts.component.html index c1377c1ea..038e18c4b 100644 --- a/client/src/app/+accounts/accounts.component.html +++ b/client/src/app/+accounts/accounts.component.html @@ -26,10 +26,10 @@ 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 907aefae1..7990044a2 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 @@ -5,7 +5,7 @@ import { VideoChannel } from '@app/shared/video-channel/video-channel.model' import { Subscription } from 'rxjs' import { Notifier } from '@app/core' import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' -import { ComponentPagination } from '@app/shared/rest/component-pagination.model' +import { ComponentPagination, hasMoreItems } from '@app/shared/rest/component-pagination.model' import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' @Component({ @@ -46,8 +46,7 @@ export class VideoChannelPlaylistsComponent implements OnInit, OnDestroy { } onNearOfBottom () { - // Last page - if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return + if (!hasMoreItems(this.pagination)) return this.pagination.currentPage += 1 this.loadVideoPlaylists() diff --git a/client/src/app/shared/video-channel/video-channel.service.ts b/client/src/app/shared/video-channel/video-channel.service.ts index d0bec649a..0168d37d9 100644 --- a/client/src/app/shared/video-channel/video-channel.service.ts +++ b/client/src/app/shared/video-channel/video-channel.service.ts @@ -2,7 +2,7 @@ import { catchError, map, tap } from 'rxjs/operators' import { Injectable } from '@angular/core' import { Observable, ReplaySubject } from 'rxjs' import { RestExtractor } from '../rest/rest-extractor.service' -import { HttpClient } from '@angular/common/http' +import { HttpClient, HttpParams } from '@angular/common/http' import { VideoChannel as VideoChannelServer, VideoChannelCreate, VideoChannelUpdate } from '../../../../../shared/models/videos' import { AccountService } from '../account/account.service' import { ResultList } from '../../../../../shared' @@ -10,6 +10,8 @@ import { VideoChannel } from './video-channel.model' import { environment } from '../../../environments/environment' import { Account } from '@app/shared/account/account.model' import { Avatar } from '../../../../../shared/models/avatars/avatar.model' +import { ComponentPagination } from '@app/shared/rest/component-pagination.model' +import { RestService } from '@app/shared/rest' @Injectable() export class VideoChannelService { @@ -29,6 +31,7 @@ export class VideoChannelService { constructor ( private authHttp: HttpClient, + private restService: RestService, private restExtractor: RestExtractor ) { } @@ -41,8 +44,16 @@ export class VideoChannelService { ) } - listAccountVideoChannels (account: Account): Observable> { - return this.authHttp.get>(AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/video-channels') + listAccountVideoChannels (account: Account, componentPagination?: ComponentPagination): Observable> { + const pagination = componentPagination + ? this.restService.componentPaginationToRestPagination(componentPagination) + : { start: 0, count: 20 } + + let params = new HttpParams() + params = this.restService.addRestGetParams(params, pagination) + + const url = AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/video-channels' + return this.authHttp.get>(url, { params }) .pipe( map(res => VideoChannelService.extractVideoChannels(res)), catchError(err => this.restExtractor.handleError(err)) diff --git a/client/src/app/shared/video/abstract-video-list.html b/client/src/app/shared/video/abstract-video-list.html index 268677977..14f48b54b 100644 --- a/client/src/app/shared/video/abstract-video-list.html +++ b/client/src/app/shared/video/abstract-video-list.html @@ -6,7 +6,7 @@ - +
No results.
-
+ @@ -11,7 +11,7 @@
-
+ @@ -19,7 +19,7 @@
-
+
Avatar diff --git a/client/src/app/videos/video-list/video-overview.component.scss b/client/src/app/videos/video-list/video-overview.component.scss index a24766783..ade6f53b7 100644 --- a/client/src/app/videos/video-list/video-overview.component.scss +++ b/client/src/app/videos/video-list/video-overview.component.scss @@ -2,62 +2,10 @@ @import '_mixins'; @import '_miniature'; +.margin-content { + @include adapt-margin-content-width; +} + .section { - max-height: 500px; // 2 rows max - overflow: hidden; - padding-top: 10px; - - &:first-child { - padding-top: 30px; - } - - my-video-miniature { - text-align: left; - } -} - -.section-title { - font-size: 24px; - font-weight: $font-semibold; - margin-bottom: 10px; - - a { - &:hover, &:focus:not(.focus-visible), &:active { - text-decoration: none; - outline: none; - } - - color: var(--mainForegroundColor); - } -} - -.channel { - .section-title a { - display: flex; - width: fit-content; - align-items: center; - - img { - @include avatar(28px); - - margin-right: 8px; - } - } -} - -@media screen and (max-width: 500px) { - .margin-content { - margin: 0 !important; - } - - .section-title { - font-size: 17px; - } - - .section { - max-height: initial; - overflow: initial; - - @include video-miniature-small-screen; - } + @include miniature-rows; } diff --git a/client/src/sass/include/_miniature.scss b/client/src/sass/include/_miniature.scss index b62187fd2..3afcca310 100644 --- a/client/src/sass/include/_miniature.scss +++ b/client/src/sass/include/_miniature.scss @@ -138,3 +138,98 @@ $play-overlay-width: 18px; } } } + +@mixin miniature-rows { + max-height: 540px; // 2 rows max + overflow: hidden; + padding-top: 10px; + + &:first-child { + padding-top: 30px; + } + + my-video-miniature { + text-align: left; + } + + .section-title { + font-size: 24px; + font-weight: $font-semibold; + margin-bottom: 30px; + + a { + &:hover, &:focus:not(.focus-visible), &:active { + text-decoration: none; + outline: none; + } + + color: var(--mainForegroundColor); + } + } + + &.channel { + .section-title { + a { + display: flex; + width: fit-content; + align-items: center; + + img { + @include avatar(28px); + + margin-right: 8px; + } + } + + .followers { + color: $grey-foreground-color; + font-weight: normal; + font-size: 14px; + margin-left: 10px; + position: relative; + top: 2px; + } + } + } + + @media screen and (max-width: $mobile-view) { + max-height: initial; + overflow: initial; + + @include video-miniature-small-screen; + + .section-title { + font-size: 17px; + } + } +} + +@mixin adapt-margin-content-width { + width: $video-miniature-width * 6; + margin: auto !important; + + @media screen and (max-width: 1800px) { + width: $video-miniature-width * 5; + } + + @media screen and (max-width: 1800px - $video-miniature-width) { + width: $video-miniature-width * 4; + } + + @media screen and (max-width: 1800px - (2* $video-miniature-width)) { + width: $video-miniature-width * 3; + } + + @media screen and (max-width: 1800px - (3* $video-miniature-width)) { + width: $video-miniature-width * 2; + } + + @media screen and (max-width: 500px) { + width: auto; + margin: 0 !important; + + .videos { + @include video-miniature-small-screen; + } + } +}