mirror of https://github.com/Chocobozzz/PeerTube
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
parent
91b6631984
commit
c8487f3f63
|
@ -1,11 +1,17 @@
|
|||
<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"
|
||||
>
|
||||
<div class="margin-content">
|
||||
|
||||
<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 class="video-channel-display-name">{{ videoChannel.displayName }}</div>
|
||||
<div i18n class="video-channel-followers">{{ videoChannel.followersCount }} subscribers</div>
|
||||
<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>
|
|
@ -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;
|
||||
}
|
||||
padding-top: 0 !important;
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,8 +41,6 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit,
|
|||
private videoService: VideoService
|
||||
) {
|
||||
super()
|
||||
|
||||
this.titlePage = this.i18n('Published videos')
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
|
|
|
@ -14,7 +14,7 @@ const accountsRoutes: Routes = [
|
|||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'videos',
|
||||
redirectTo: 'video-channels',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
|
|
|
@ -26,10 +26,10 @@
|
|||
</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>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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<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))
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</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
|
||||
|
|
|
@ -25,32 +25,6 @@
|
|||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<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>
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
|||
</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>
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
|||
</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" />
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue