diff options
Diffstat (limited to 'client/src/app/+accounts/account-video-channels')
3 files changed, 222 insertions, 40 deletions
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 5dbb341d2..19a4b3c9c 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,33 +1,50 @@ | |||
1 | <h1 class="sr-only" i18n>Video channels</h1> | 1 | <h1 class="sr-only" i18n>Video channels</h1> |
2 | |||
2 | <div class="margin-content"> | 3 | <div class="margin-content"> |
3 | 4 | ||
4 | <div class="no-results" i18n *ngIf="channelPagination.totalItems === 0">This account does not have channels.</div> | 5 | <div class="no-results" i18n *ngIf="channelPagination.totalItems === 0">This account does not have channels.</div> |
5 | 6 | ||
6 | <div class="channels" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" [dataObservable]="onChannelDataSubject.asObservable()"> | 7 | <div class="channels" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" [dataObservable]="onChannelDataSubject.asObservable()"> |
7 | <div class="section channel" *ngFor="let videoChannel of videoChannels"> | 8 | <div class="channel" *ngFor="let videoChannel of videoChannels"> |
8 | <div class="section-title"> | 9 | |
9 | <a [routerLink]="getVideoChannelLink(videoChannel)" i18n-title title="See this video channel"> | 10 | <div class="channel-avatar-row"> |
11 | <a class="avatar-link" [routerLink]="getVideoChannelLink(videoChannel)" i18n-title title="See this video channel"> | ||
10 | <img [src]="videoChannel.avatarUrl" alt="Avatar" /> | 12 | <img [src]="videoChannel.avatarUrl" alt="Avatar" /> |
13 | </a> | ||
11 | 14 | ||
12 | <h2 class="section-title">{{ videoChannel.displayName }}</h2> | 15 | <h2> |
16 | <a [routerLink]="getVideoChannelLink(videoChannel)" i18n-title title="See this video channel"> | ||
17 | {{ videoChannel.displayName }} | ||
18 | </a> | ||
19 | </h2> | ||
20 | |||
21 | <div class="actor-counters"> | ||
13 | <div class="followers" i18n>{videoChannel.followersCount, plural, =1 {1 subscriber} other {{{ videoChannel.followersCount }} subscribers}}</div> | 22 | <div class="followers" i18n>{videoChannel.followersCount, plural, =1 {1 subscriber} other {{{ videoChannel.followersCount }} subscribers}}</div> |
14 | </a> | ||
15 | 23 | ||
16 | <my-subscribe-button [videoChannels]="[videoChannel]"></my-subscribe-button> | 24 | <span class="videos-count" *ngIf="getTotalVideosOf(videoChannel) !== undefined" i18n> |
25 | {getTotalVideosOf(videoChannel), plural, =1 {1 videos} other {{{ getTotalVideosOf(videoChannel) }} videos}} | ||
26 | </span> | ||
27 | </div> | ||
28 | |||
29 | <div class="description-html" [innerHTML]="getChannelDescription(videoChannel)"></div> | ||
17 | </div> | 30 | </div> |
18 | 31 | ||
19 | <div *ngIf="getVideosOf(videoChannel)" class="videos"> | 32 | <my-subscribe-button [videoChannels]="[videoChannel]"></my-subscribe-button> |
20 | <div class="no-results my-5" i18n *ngIf="getVideosOf(videoChannel).length === 0">This channel doesn't have any videos.</div> | 33 | |
34 | <a i18n class="button-show-channel peertube-button-link orange-button-inverted" [routerLink]="getVideoChannelLink(videoChannel)">Show this channel</a> | ||
35 | |||
36 | <div class="videos"> | ||
37 | <div class="no-results" i18n *ngIf="getTotalVideosOf(videoChannel) === 0">This channel doesn't have any videos.</div> | ||
21 | 38 | ||
22 | <my-video-miniature | 39 | <my-video-miniature |
23 | *ngFor="let video of getVideosOf(videoChannel)" | 40 | *ngFor="let video of getVideosOf(videoChannel)" |
24 | [video]="video" [user]="userMiniature" [displayVideoActions]="true" | 41 | [video]="video" [user]="userMiniature" [displayVideoActions]="true" [displayOptions]="miniatureDisplayOptions" |
25 | ></my-video-miniature> | 42 | ></my-video-miniature> |
26 | </div> | ||
27 | 43 | ||
28 | <a *ngIf="getVideosOf(videoChannel).length !== 0" class="show-more" i18n [routerLink]="getVideoChannelLink(videoChannel)"> | 44 | <div *ngIf="getTotalVideosOf(videoChannel)" class="miniature-show-channel"> |
29 | SHOW THIS CHANNEL | 45 | <a i18n [routerLink]="getVideoChannelLink(videoChannel)">SHOW THIS CHANNEL ></a> |
30 | </a> | 46 | </div> |
47 | </div> | ||
31 | </div> | 48 | </div> |
32 | </div> | 49 | </div> |
33 | </div> | 50 | </div> |
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 4957e91d7..7e88802f3 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 | |||
@@ -3,37 +3,175 @@ | |||
3 | @import '_miniature'; | 3 | @import '_miniature'; |
4 | 4 | ||
5 | .margin-content { | 5 | .margin-content { |
6 | @include fluid-videos-miniature-layout; | 6 | @include grid-videos-miniature-margins; |
7 | } | 7 | } |
8 | 8 | ||
9 | .section { | 9 | .channel { |
10 | @include miniature-rows; | 10 | max-width: $max-channels-width; |
11 | background-color: pvar(--channelBackgroundColor); | ||
12 | padding: 30px; | ||
11 | 13 | ||
12 | padding-top: 0 !important; | 14 | margin: 30px 0; |
13 | 15 | ||
14 | .section-title { | 16 | display: grid; |
17 | grid-template-columns: 1fr auto; | ||
18 | grid-template-rows: auto auto; | ||
19 | column-gap: 15px; | ||
20 | } | ||
21 | |||
22 | .channel-avatar-row { | ||
23 | grid-column: 1; | ||
24 | grid-row: 1; | ||
25 | |||
26 | display: grid; | ||
27 | grid-template-columns: auto auto 1fr; | ||
28 | grid-template-rows: auto 1fr; | ||
29 | |||
30 | .avatar-link { | ||
31 | grid-column: 1; | ||
32 | grid-row: 1 / 3; | ||
33 | margin-right: 30px; | ||
34 | } | ||
35 | |||
36 | img { | ||
37 | @include channel-avatar(75px); | ||
38 | } | ||
39 | |||
40 | a { | ||
41 | color: pvar(--mainForegroundColor); | ||
42 | } | ||
43 | |||
44 | h2 { | ||
45 | grid-row: 1; | ||
46 | grid-column: 2; | ||
47 | font-size: 20px; | ||
48 | line-height: 1; | ||
49 | font-weight: $font-bold; | ||
50 | margin: 0; | ||
51 | } | ||
52 | |||
53 | .actor-counters { | ||
54 | grid-row: 1; | ||
55 | grid-column: 3; | ||
56 | color: pvar(--greyForegroundColor); | ||
57 | font-size: 16px; | ||
58 | display: flex; | ||
15 | align-items: center; | 59 | align-items: center; |
60 | margin-left: 15px; | ||
16 | } | 61 | } |
17 | 62 | ||
18 | .videos { | 63 | .actor-counters > *:not(:last-child)::after { |
19 | overflow: hidden; | 64 | content: '•'; |
65 | margin: 0 10px; | ||
66 | color: pvar(--mainColor); | ||
67 | } | ||
20 | 68 | ||
21 | .no-results { | 69 | .description-html { |
22 | height: 50px; | 70 | grid-column: 2 / 4; |
23 | } | 71 | grid-row: 2; |
72 | |||
73 | max-height: 80px; | ||
74 | font-size: 16px; | ||
75 | |||
76 | @include fade-text(30px, pvar(--channelBackgroundColor)); | ||
77 | } | ||
78 | } | ||
79 | |||
80 | my-subscribe-button { | ||
81 | grid-row: 1; | ||
82 | grid-column: 2; | ||
83 | } | ||
84 | |||
85 | .videos { | ||
86 | display: flex; | ||
87 | grid-column: 1 / 3; | ||
88 | grid-row: 2; | ||
89 | margin-top: 30px; | ||
90 | |||
91 | position: relative; | ||
92 | overflow: hidden; | ||
93 | |||
94 | my-video-miniature { | ||
95 | margin-right: 15px; | ||
96 | min-width: $video-thumbnail-medium-width; | ||
97 | max-width: $video-thumbnail-medium-width; | ||
98 | } | ||
99 | |||
100 | .no-results { | ||
101 | height: auto; | ||
24 | } | 102 | } |
103 | } | ||
25 | 104 | ||
26 | my-video-miniature ::ng-deep my-video-actions-dropdown > my-action-dropdown { | 105 | .miniature-show-channel { |
27 | // Fix our overflow | 106 | height: 100%; |
28 | position: absolute; | 107 | position: absolute; |
108 | right: 0; | ||
109 | background: linear-gradient(90deg, transparent 0, pvar(--channelBackgroundColor) 45px); | ||
110 | padding: ($video-thumbnail-medium-height / 2 - 10px) 15px 0 60px; | ||
111 | z-index: z(miniature) + 1; | ||
112 | |||
113 | a { | ||
114 | color: pvar(--mainColor); | ||
115 | font-size: 16px; | ||
116 | font-weight: $font-semibold; | ||
29 | } | 117 | } |
30 | } | 118 | } |
31 | 119 | ||
120 | .button-show-channel { | ||
121 | display: none; | ||
122 | } | ||
123 | |||
32 | @media screen and (max-width: $mobile-view) { | 124 | @media screen and (max-width: $mobile-view) { |
33 | .section { | 125 | .channel { |
34 | .section-title { | 126 | padding: 15px; |
35 | flex-direction: column; | 127 | } |
36 | align-items: normal; | 128 | |
129 | .channel-avatar-row { | ||
130 | grid-template-columns: auto auto auto 1fr; | ||
131 | |||
132 | .avatar-link { | ||
133 | grid-row: 1 / 4; | ||
134 | } | ||
135 | |||
136 | h2 { | ||
137 | font-size: 16px; | ||
37 | } | 138 | } |
139 | |||
140 | .actor-counters { | ||
141 | margin: 0; | ||
142 | font-size: 13px; | ||
143 | grid-row: 2; | ||
144 | grid-column: 2 / 4; | ||
145 | } | ||
146 | |||
147 | .description-html { | ||
148 | grid-row: 3; | ||
149 | font-size: 14px; | ||
150 | } | ||
151 | } | ||
152 | |||
153 | .show-channel a { | ||
154 | @include peertube-button-link; | ||
155 | @include orange-button-inverted; | ||
156 | } | ||
157 | |||
158 | .videos { | ||
159 | display: none; | ||
160 | } | ||
161 | |||
162 | my-subscribe-button, | ||
163 | .button-show-channel { | ||
164 | grid-column: 1 / 4; | ||
165 | grid-row: 3; | ||
166 | margin-top: 15px; | ||
167 | } | ||
168 | |||
169 | my-subscribe-button { | ||
170 | justify-self: start; | ||
171 | } | ||
172 | |||
173 | .button-show-channel { | ||
174 | display: block; | ||
175 | justify-self: end; | ||
38 | } | 176 | } |
39 | } | 177 | } |
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 f2beb6689..0628c7a96 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,9 +1,10 @@ | |||
1 | import { from, Subject, Subscription } from 'rxjs' | 1 | import { from, Subject, Subscription } from 'rxjs' |
2 | import { concatMap, map, switchMap, tap } from 'rxjs/operators' | 2 | import { concatMap, map, switchMap, tap } from 'rxjs/operators' |
3 | import { Component, OnDestroy, OnInit } from '@angular/core' | 3 | import { Component, OnDestroy, OnInit } from '@angular/core' |
4 | import { ComponentPagination, hasMoreItems, ScreenService, User, UserService } from '@app/core' | 4 | import { ComponentPagination, hasMoreItems, MarkdownService, ScreenService, User, UserService } from '@app/core' |
5 | import { Account, AccountService, Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' | 5 | import { Account, AccountService, Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' |
6 | import { NSFWPolicyType, VideoSortField } from '@shared/models' | 6 | import { NSFWPolicyType, VideoSortField } from '@shared/models' |
7 | import { MiniatureDisplayOptions } from '@app/shared/shared-video-miniature' | ||
7 | 8 | ||
8 | @Component({ | 9 | @Component({ |
9 | selector: 'my-account-video-channels', | 10 | selector: 'my-account-video-channels', |
@@ -13,7 +14,10 @@ import { NSFWPolicyType, VideoSortField } from '@shared/models' | |||
13 | export class AccountVideoChannelsComponent implements OnInit, OnDestroy { | 14 | export class AccountVideoChannelsComponent implements OnInit, OnDestroy { |
14 | account: Account | 15 | account: Account |
15 | videoChannels: VideoChannel[] = [] | 16 | videoChannels: VideoChannel[] = [] |
16 | videos: { [id: number]: Video[] } = {} | 17 | |
18 | videos: { [id: number]: { total: number, videos: Video[] } } = {} | ||
19 | |||
20 | channelsDescriptionHTML: { [ id: number ]: string } = {} | ||
17 | 21 | ||
18 | channelPagination: ComponentPagination = { | 22 | channelPagination: ComponentPagination = { |
19 | currentPage: 1, | 23 | currentPage: 1, |
@@ -23,7 +27,7 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy { | |||
23 | 27 | ||
24 | videosPagination: ComponentPagination = { | 28 | videosPagination: ComponentPagination = { |
25 | currentPage: 1, | 29 | currentPage: 1, |
26 | itemsPerPage: 12, | 30 | itemsPerPage: 5, |
27 | totalItems: null | 31 | totalItems: null |
28 | } | 32 | } |
29 | videosSort: VideoSortField = '-publishedAt' | 33 | videosSort: VideoSortField = '-publishedAt' |
@@ -32,6 +36,16 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy { | |||
32 | 36 | ||
33 | userMiniature: User | 37 | userMiniature: User |
34 | nsfwPolicy: NSFWPolicyType | 38 | nsfwPolicy: NSFWPolicyType |
39 | miniatureDisplayOptions: MiniatureDisplayOptions = { | ||
40 | date: true, | ||
41 | views: true, | ||
42 | by: false, | ||
43 | avatar: false, | ||
44 | privacyLabel: false, | ||
45 | privacyText: false, | ||
46 | state: false, | ||
47 | blacklistInfo: false | ||
48 | } | ||
35 | 49 | ||
36 | private accountSub: Subscription | 50 | private accountSub: Subscription |
37 | 51 | ||
@@ -39,7 +53,7 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy { | |||
39 | private accountService: AccountService, | 53 | private accountService: AccountService, |
40 | private videoChannelService: VideoChannelService, | 54 | private videoChannelService: VideoChannelService, |
41 | private videoService: VideoService, | 55 | private videoService: VideoService, |
42 | private screenService: ScreenService, | 56 | private markdown: MarkdownService, |
43 | private userService: UserService | 57 | private userService: UserService |
44 | ) { } | 58 | ) { } |
45 | 59 | ||
@@ -78,23 +92,36 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy { | |||
78 | } | 92 | } |
79 | 93 | ||
80 | return this.videoService.getVideoChannelVideos(options) | 94 | return this.videoService.getVideoChannelVideos(options) |
81 | .pipe(map(data => ({ videoChannel, videos: data.data }))) | 95 | .pipe(map(data => ({ videoChannel, videos: data.data, total: data.total }))) |
82 | }) | 96 | }) |
83 | ) | 97 | ) |
84 | .subscribe(({ videoChannel, videos }) => { | 98 | .subscribe(async ({ videoChannel, videos, total }) => { |
99 | this.channelsDescriptionHTML[videoChannel.id] = await this.markdown.textMarkdownToHTML(videoChannel.description) | ||
100 | |||
85 | this.videoChannels.push(videoChannel) | 101 | this.videoChannels.push(videoChannel) |
86 | 102 | ||
87 | this.videos[videoChannel.id] = videos | 103 | this.videos[videoChannel.id] = { videos, total } |
88 | 104 | ||
89 | this.onChannelDataSubject.next([ videoChannel ]) | 105 | this.onChannelDataSubject.next([ videoChannel ]) |
90 | }) | 106 | }) |
91 | } | 107 | } |
92 | 108 | ||
93 | getVideosOf (videoChannel: VideoChannel) { | 109 | getVideosOf (videoChannel: VideoChannel) { |
94 | const numberOfVideos = this.screenService.getNumberOfAvailableMiniatures() | 110 | const obj = this.videos[ videoChannel.id ] |
111 | if (!obj) return [] | ||
112 | |||
113 | return obj.videos | ||
114 | } | ||
115 | |||
116 | getTotalVideosOf (videoChannel: VideoChannel) { | ||
117 | const obj = this.videos[ videoChannel.id ] | ||
118 | if (!obj) return undefined | ||
119 | |||
120 | return obj.total | ||
121 | } | ||
95 | 122 | ||
96 | // 2 rows | 123 | getChannelDescription (videoChannel: VideoChannel) { |
97 | return this.videos[ videoChannel.id ].slice(0, numberOfVideos * 2) | 124 | return this.channelsDescriptionHTML[videoChannel.id] |
98 | } | 125 | } |
99 | 126 | ||
100 | onNearOfBottom () { | 127 | onNearOfBottom () { |