Improve account channel page

Set it as the default route for account page. The main goal is to better
differentiate the channel page from the account page. With the channel
page set as default, I hope people will better understand they are in
the account page, and that this account could have multiple channels.
pull/1856/head
Chocobozzz 2019-05-29 16:45:59 +02:00
parent 91b6631984
commit c8487f3f63
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
13 changed files with 208 additions and 143 deletions

View File

@ -1,11 +1,17 @@
<div *ngIf="account" class="row"> <div class="margin-content">
<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="video-channel-display-name">{{ videoChannel.displayName }}</div> <div class="channels" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true">
<div i18n class="video-channel-followers">{{ videoChannel.followersCount }} subscribers</div> <div class="section channel" *ngFor="let videoChannel of videoChannels">
</a> <div class="section-title">
</div> <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>

View File

@ -1,30 +1,13 @@
@import '_variables'; @import '_variables';
@import '_mixins'; @import '_mixins';
@import '_miniature';
.row { .margin-content {
justify-content: center; @include adapt-margin-content-width;
} }
a.video-channel { .section {
@include disable-default-a-behaviour; @include miniature-rows;
display: inline-block; padding-top: 0 !important;
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;
}
}

View File

@ -3,9 +3,14 @@ import { ActivatedRoute } from '@angular/router'
import { Account } from '@app/shared/account/account.model' import { Account } from '@app/shared/account/account.model'
import { AccountService } from '@app/shared/account/account.service' import { AccountService } from '@app/shared/account/account.service'
import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
import { flatMap, map, tap } from 'rxjs/operators' import { concatMap, map, switchMap, tap } from 'rxjs/operators'
import { Subscription } from 'rxjs' import { from, Subscription } from 'rxjs'
import { VideoChannel } from '@app/shared/video-channel/video-channel.model' 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({ @Component({
selector: 'my-account-video-channels', 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 { export class AccountVideoChannelsComponent implements OnInit, OnDestroy {
account: Account account: Account
videoChannels: VideoChannel[] = [] videoChannels: VideoChannel[] = []
videos: { [id: number]: Video[] } = {}
channelPagination: ComponentPagination = {
currentPage: 1,
itemsPerPage: 2
}
videosPagination: ComponentPagination = {
currentPage: 1,
itemsPerPage: 12
}
videosSort: VideoSortField = '-publishedAt'
private accountSub: Subscription private accountSub: Subscription
constructor ( constructor (
protected route: ActivatedRoute, private route: ActivatedRoute,
private authService: AuthService,
private accountService: AccountService, private accountService: AccountService,
private videoChannelService: VideoChannelService private videoChannelService: VideoChannelService,
private videoService: VideoService
) { } ) { }
get user () {
return this.authService.getUser()
}
ngOnInit () { ngOnInit () {
// Parent get the account for us // Parent get the account for us
this.accountSub = this.accountService.accountLoaded this.accountSub = this.accountService.accountLoaded
.pipe( .subscribe(account => {
tap(account => this.account = account), this.account = account
flatMap(account => this.videoChannelService.listAccountVideoChannels(account)),
map(res => res.data) this.loadMoreChannels()
) })
.subscribe(videoChannels => this.videoChannels = videoChannels)
} }
ngOnDestroy () { ngOnDestroy () {
if (this.accountSub) this.accountSub.unsubscribe() 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()
}
} }

View File

@ -41,8 +41,6 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit,
private videoService: VideoService private videoService: VideoService
) { ) {
super() super()
this.titlePage = this.i18n('Published videos')
} }
ngOnInit () { ngOnInit () {

View File

@ -14,7 +14,7 @@ const accountsRoutes: Routes = [
children: [ children: [
{ {
path: '', path: '',
redirectTo: 'videos', redirectTo: 'video-channels',
pathMatch: 'full' pathMatch: 'full'
}, },
{ {

View File

@ -26,10 +26,10 @@
</div> </div>
<div class="links"> <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="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> <a i18n routerLink="about" routerLinkActive="active" class="title-page">About</a>
</div> </div>
</div> </div>

View File

@ -5,7 +5,7 @@ import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
import { Subscription } from 'rxjs' import { Subscription } from 'rxjs'
import { Notifier } from '@app/core' import { Notifier } from '@app/core'
import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' 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' import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
@Component({ @Component({
@ -46,8 +46,7 @@ export class VideoChannelPlaylistsComponent implements OnInit, OnDestroy {
} }
onNearOfBottom () { onNearOfBottom () {
// Last page if (!hasMoreItems(this.pagination)) return
if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return
this.pagination.currentPage += 1 this.pagination.currentPage += 1
this.loadVideoPlaylists() this.loadVideoPlaylists()

View File

@ -2,7 +2,7 @@ import { catchError, map, tap } from 'rxjs/operators'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { Observable, ReplaySubject } from 'rxjs' import { Observable, ReplaySubject } from 'rxjs'
import { RestExtractor } from '../rest/rest-extractor.service' 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 { VideoChannel as VideoChannelServer, VideoChannelCreate, VideoChannelUpdate } from '../../../../../shared/models/videos'
import { AccountService } from '../account/account.service' import { AccountService } from '../account/account.service'
import { ResultList } from '../../../../../shared' import { ResultList } from '../../../../../shared'
@ -10,6 +10,8 @@ import { VideoChannel } from './video-channel.model'
import { environment } from '../../../environments/environment' import { environment } from '../../../environments/environment'
import { Account } from '@app/shared/account/account.model' import { Account } from '@app/shared/account/account.model'
import { Avatar } from '../../../../../shared/models/avatars/avatar.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() @Injectable()
export class VideoChannelService { export class VideoChannelService {
@ -29,6 +31,7 @@ export class VideoChannelService {
constructor ( constructor (
private authHttp: HttpClient, private authHttp: HttpClient,
private restService: RestService,
private restExtractor: RestExtractor private restExtractor: RestExtractor
) { } ) { }
@ -41,8 +44,16 @@ export class VideoChannelService {
) )
} }
listAccountVideoChannels (account: Account): Observable<ResultList<VideoChannel>> { listAccountVideoChannels (account: Account, componentPagination?: ComponentPagination): Observable<ResultList<VideoChannel>> {
return this.authHttp.get<ResultList<VideoChannelServer>>(AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/video-channels') 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( .pipe(
map(res => VideoChannelService.extractVideoChannels(res)), map(res => VideoChannelService.extractVideoChannels(res)),
catchError(err => this.restExtractor.handleError(err)) catchError(err => this.restExtractor.handleError(err))

View File

@ -6,7 +6,7 @@
</div> </div>
</div> </div>
<my-feed [syndicationItems]="syndicationItems"></my-feed> <my-feed *ngIf="titlePage" [syndicationItems]="syndicationItems"></my-feed>
<div class="moderation-block" *ngIf="displayModerationBlock"> <div class="moderation-block" *ngIf="displayModerationBlock">
<my-peertube-checkbox <my-peertube-checkbox

View File

@ -25,32 +25,6 @@
} }
.margin-content { .margin-content {
width: $video-miniature-width * 6; @include adapt-margin-content-width;
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;
}
}
} }

View File

@ -3,7 +3,7 @@
<div class="no-results" i18n *ngIf="notResults">No results.</div> <div class="no-results" i18n *ngIf="notResults">No results.</div>
<div class="section" *ngFor="let object of overview.categories"> <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> <a routerLink="/search" [queryParams]="{ categoryOneOf: [ object.category.id ] }">{{ object.category.label }}</a>
</div> </div>
@ -11,7 +11,7 @@
</div> </div>
<div class="section" *ngFor="let object of overview.tags"> <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> <a routerLink="/search" [queryParams]="{ tagsOneOf: [ object.tag ] }">#{{ object.tag }}</a>
</div> </div>
@ -19,7 +19,7 @@
</div> </div>
<div class="section channel" *ngFor="let object of overview.channels"> <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) ]"> <a [routerLink]="[ '/video-channels', buildVideoChannelBy(object) ]">
<img [src]="buildVideoChannelAvatarUrl(object)" alt="Avatar" /> <img [src]="buildVideoChannelAvatarUrl(object)" alt="Avatar" />

View File

@ -2,62 +2,10 @@
@import '_mixins'; @import '_mixins';
@import '_miniature'; @import '_miniature';
.margin-content {
@include adapt-margin-content-width;
}
.section { .section {
max-height: 500px; // 2 rows max @include miniature-rows;
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;
}
} }

View File

@ -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;
}
}
}