-<div *ngIf="account" class="row">
- <a
- *ngFor="let videoChannel of videoChannels" [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]"
- class="video-channel" i18n-title title="See this video channel"
- >
- <img [src]="videoChannel.avatarUrl" alt="Avatar" />
+<div class="margin-content">
- <div class="video-channel-display-name">{{ videoChannel.displayName }}</div>
- <div i18n class="video-channel-followers">{{ videoChannel.followersCount }} subscribers</div>
- </a>
-</div>
\ No newline at end of file
+ <div class="channels" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true">
+ <div class="section channel" *ngFor="let videoChannel of videoChannels">
+ <div class="section-title">
+ <a [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]" i18n-title title="See this video channel">
+ <img [src]="videoChannel.avatarUrl" alt="Avatar" />
+
+ <div>{{ videoChannel.displayName }}</div>
+ <div i18n class="followers">{{ videoChannel.followersCount }} subscribers</div>
+ </a>
+ </div>
+
+ <my-video-miniature *ngFor="let video of getVideosOf(videoChannel)" [video]="video" [user]="user" [displayVideoActions]="false"></my-video-miniature>
+ </div>
+ </div>
+</div>
@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;
+}
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',
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()
+ }
}
private videoService: VideoService
) {
super()
-
- this.titlePage = this.i18n('Published videos')
}
ngOnInit () {
children: [
{
path: '',
- redirectTo: 'videos',
+ redirectTo: 'video-channels',
pathMatch: 'full'
},
{
</div>
<div class="links">
- <a i18n routerLink="videos" routerLinkActive="active" class="title-page">Videos</a>
-
<a i18n routerLink="video-channels" routerLinkActive="active" class="title-page">Video channels</a>
+ <a i18n routerLink="videos" routerLinkActive="active" class="title-page">Videos</a>
+
<a i18n routerLink="about" routerLinkActive="active" class="title-page">About</a>
</div>
</div>
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({
}
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()
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'
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 {
constructor (
private authHttp: HttpClient,
+ private restService: RestService,
private restExtractor: RestExtractor
) { }
)
}
- listAccountVideoChannels (account: Account): Observable<ResultList<VideoChannel>> {
- return this.authHttp.get<ResultList<VideoChannelServer>>(AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/video-channels')
+ listAccountVideoChannels (account: Account, componentPagination?: ComponentPagination): Observable<ResultList<VideoChannel>> {
+ 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<ResultList<VideoChannelServer>>(url, { params })
.pipe(
map(res => VideoChannelService.extractVideoChannels(res)),
catchError(err => this.restExtractor.handleError(err))
</div>
</div>
- <my-feed [syndicationItems]="syndicationItems"></my-feed>
+ <my-feed *ngIf="titlePage" [syndicationItems]="syndicationItems"></my-feed>
<div class="moderation-block" *ngIf="displayModerationBlock">
<my-peertube-checkbox
}
.margin-content {
- 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;
- }
- }
+ @include adapt-margin-content-width;
}
<div class="no-results" i18n *ngIf="notResults">No results.</div>
<div class="section" *ngFor="let object of overview.categories">
- <div class="section-title" i18n>
+ <div class="section-title">
<a routerLink="/search" [queryParams]="{ categoryOneOf: [ object.category.id ] }">{{ object.category.label }}</a>
</div>
</div>
<div class="section" *ngFor="let object of overview.tags">
- <div class="section-title" i18n>
+ <div class="section-title">
<a routerLink="/search" [queryParams]="{ tagsOneOf: [ object.tag ] }">#{{ object.tag }}</a>
</div>
</div>
<div class="section channel" *ngFor="let object of overview.channels">
- <div class="section-title" i18n>
+ <div class="section-title">
<a [routerLink]="[ '/video-channels', buildVideoChannelBy(object) ]">
<img [src]="buildVideoChannelAvatarUrl(object)" alt="Avatar" />
@import '_mixins';
@import '_miniature';
-.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);
- }
+.margin-content {
+ @include adapt-margin-content-width;
}
-.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;
- }
+.section {
+ @include miniature-rows;
}
}
}
}
+
+@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;
+ }
+ }
+}