aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app')
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.html6
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.ts17
-rw-r--r--client/src/app/+my-account/+my-account-video-channels/my-account-video-channels.component.html19
-rw-r--r--client/src/app/+my-account/+my-account-video-channels/my-account-video-channels.component.scss9
-rw-r--r--client/src/app/+my-account/+my-account-video-channels/my-account-video-channels.component.ts26
-rw-r--r--client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html6
-rw-r--r--client/src/app/+my-account/my-account-notifications/my-account-notifications.component.ts18
-rw-r--r--client/src/app/+my-account/my-account-ownership/my-account-ownership.component.html6
-rw-r--r--client/src/app/+my-account/my-account-ownership/my-account-ownership.component.scss4
-rw-r--r--client/src/app/+my-account/my-account-ownership/my-account-ownership.component.ts13
-rw-r--r--client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.html15
-rw-r--r--client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.scss4
-rw-r--r--client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts43
-rw-r--r--client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html7
-rw-r--r--client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.scss6
-rw-r--r--client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts13
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.html19
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.scss14
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.ts5
-rw-r--r--client/src/app/+my-account/my-account-videos/my-account-videos.component.html19
-rw-r--r--client/src/app/+my-account/my-account-videos/my-account-videos.component.scss10
-rw-r--r--client/src/app/+my-account/my-account-videos/my-account-videos.component.ts8
-rw-r--r--client/src/app/core/users/user.service.ts10
-rw-r--r--client/src/app/shared/shared-user-subscription/user-subscription.service.ts11
24 files changed, 223 insertions, 85 deletions
diff --git a/client/src/app/+admin/users/user-list/user-list.component.html b/client/src/app/+admin/users/user-list/user-list.component.html
index 571c780d6..e8a084259 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.html
+++ b/client/src/app/+admin/users/user-list/user-list.component.html
@@ -112,8 +112,10 @@
112 </a> 112 </a>
113 </td> 113 </td>
114 114
115 <td *ngIf="!requiresEmailVerification || user.blocked; else emailWithVerificationStatus" [title]="user.email"> 115 <td *ngIf="getColumn('email')" [title]="user.email">
116 <a class="table-email" [href]="'mailto:' + user.email">{{ user.email }}</a> 116 <ng-container *ngIf="!requiresEmailVerification || user.blocked; else emailWithVerificationStatus">
117 <a class="table-email" [href]="'mailto:' + user.email">{{ user.email }}</a>
118 </ng-container>
117 </td> 119 </td>
118 120
119 <ng-template #emailWithVerificationStatus> 121 <ng-template #emailWithVerificationStatus>
diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts
index b2978212e..699b2a6da 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.ts
+++ b/client/src/app/+admin/users/user-list/user-list.component.ts
@@ -7,6 +7,13 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
7import { ServerConfig, User, UserRole } from '@shared/models' 7import { ServerConfig, User, UserRole } from '@shared/models'
8import { Params, Router, ActivatedRoute } from '@angular/router' 8import { Params, Router, ActivatedRoute } from '@angular/router'
9 9
10type UserForList = User & {
11 rawVideoQuota: number
12 rawVideoQuotaUsed: number
13 rawVideoQuotaDaily: number
14 rawVideoQuotaUsedDaily: number
15}
16
10@Component({ 17@Component({
11 selector: 'my-user-list', 18 selector: 'my-user-list',
12 templateUrl: './user-list.component.html', 19 templateUrl: './user-list.component.html',
@@ -24,8 +31,8 @@ export class UserListComponent extends RestTable implements OnInit {
24 selectedUsers: User[] = [] 31 selectedUsers: User[] = []
25 bulkUserActions: DropdownAction<User[]>[][] = [] 32 bulkUserActions: DropdownAction<User[]>[][] = []
26 columns: { key: string, label: string }[] 33 columns: { key: string, label: string }[]
27 _selectedColumns: { key: string, label: string }[]
28 34
35 private _selectedColumns: { key: string, label: string }[]
29 private serverConfig: ServerConfig 36 private serverConfig: ServerConfig
30 37
31 constructor ( 38 constructor (
@@ -111,7 +118,7 @@ export class UserListComponent extends RestTable implements OnInit {
111 { key: 'role', label: 'Role' }, 118 { key: 'role', label: 'Role' },
112 { key: 'createdAt', label: 'Created' } 119 { key: 'createdAt', label: 'Created' }
113 ] 120 ]
114 this.selectedColumns = [...this.columns] 121 this.selectedColumns = [ ...this.columns ] // make a full copy of the array
115 this.columns.push({ key: 'quotaDaily', label: 'Daily quota' }) 122 this.columns.push({ key: 'quotaDaily', label: 'Daily quota' })
116 this.columns.push({ key: 'pluginAuth', label: 'Auth plugin' }) 123 this.columns.push({ key: 'pluginAuth', label: 'Auth plugin' })
117 this.columns.push({ key: 'lastLoginDate', label: 'Last login' }) 124 this.columns.push({ key: 'lastLoginDate', label: 'Last login' })
@@ -133,14 +140,14 @@ export class UserListComponent extends RestTable implements OnInit {
133 } 140 }
134 141
135 getColumn (key: string) { 142 getColumn (key: string) {
136 return this.selectedColumns.find((col: any) => col.key === key) 143 return this.selectedColumns.find((col: { key: string }) => col.key === key)
137 } 144 }
138 145
139 getUserVideoQuotaPercentage (user: User & { rawVideoQuota: number, rawVideoQuotaUsed: number}) { 146 getUserVideoQuotaPercentage (user: UserForList) {
140 return user.rawVideoQuotaUsed * 100 / user.rawVideoQuota 147 return user.rawVideoQuotaUsed * 100 / user.rawVideoQuota
141 } 148 }
142 149
143 getUserVideoQuotaDailyPercentage (user: User & { rawVideoQuotaDaily: number, rawVideoQuotaUsedDaily: number}) { 150 getUserVideoQuotaDailyPercentage (user: UserForList) {
144 return user.rawVideoQuotaUsedDaily * 100 / user.rawVideoQuotaDaily 151 return user.rawVideoQuotaUsedDaily * 100 / user.rawVideoQuotaDaily
145 } 152 }
146 153
diff --git a/client/src/app/+my-account/+my-account-video-channels/my-account-video-channels.component.html b/client/src/app/+my-account/+my-account-video-channels/my-account-video-channels.component.html
index e8d44a45e..c20215cf9 100644
--- a/client/src/app/+my-account/+my-account-video-channels/my-account-video-channels.component.html
+++ b/client/src/app/+my-account/+my-account-video-channels/my-account-video-channels.component.html
@@ -1,14 +1,21 @@
1<h1> 1<h1 class="d-flex justify-content-between">
2 <my-global-icon iconName="channel" aria-hidden="true"></my-global-icon> 2 <span>
3 <ng-container i18n>My channels</ng-container> 3 <my-global-icon iconName="channel" aria-hidden="true"></my-global-icon>
4</h1> 4 <ng-container i18n>My channels</ng-container>
5 <span class="badge badge-secondary">{{ totalItems }}</span>
6 </span>
7
8 <div class="has-feedback has-clear">
9 <input type="text" placeholder="Search your channels" i18n-placeholder [(ngModel)]="channelsSearch" (ngModelChange)="onChannelsSearchChanged()" />
10 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
11 <span class="sr-only" i18n>Clear filters</span>
12 </div>
5 13
6<div class="video-channels-header">
7 <a class="create-button" routerLink="create"> 14 <a class="create-button" routerLink="create">
8 <my-global-icon iconName="add" aria-hidden="true"></my-global-icon> 15 <my-global-icon iconName="add" aria-hidden="true"></my-global-icon>
9 <ng-container i18n>Create video channel</ng-container> 16 <ng-container i18n>Create video channel</ng-container>
10 </a> 17 </a>
11</div> 18</h1>
12 19
13<div class="video-channels"> 20<div class="video-channels">
14 <div *ngFor="let videoChannel of videoChannels; let i = index" class="video-channel"> 21 <div *ngFor="let videoChannel of videoChannels; let i = index" class="video-channel">
diff --git a/client/src/app/+my-account/+my-account-video-channels/my-account-video-channels.component.scss b/client/src/app/+my-account/+my-account-video-channels/my-account-video-channels.component.scss
index 76fb2cde0..4ecb4f408 100644
--- a/client/src/app/+my-account/+my-account-video-channels/my-account-video-channels.component.scss
+++ b/client/src/app/+my-account/+my-account-video-channels/my-account-video-channels.component.scss
@@ -5,6 +5,10 @@
5 @include create-button; 5 @include create-button;
6} 6}
7 7
8input[type=text] {
9 @include peertube-input-text(300px);
10}
11
8::ng-deep .action-button { 12::ng-deep .action-button {
9 &.action-button-edit { 13 &.action-button-edit {
10 margin-right: 10px; 14 margin-right: 10px;
@@ -55,11 +59,6 @@
55 } 59 }
56} 60}
57 61
58.video-channels-header {
59 text-align: right;
60 margin: 20px 0 50px;
61}
62
63::ng-deep .chartjs-render-monitor { 62::ng-deep .chartjs-render-monitor {
64 position: relative; 63 position: relative;
65 top: 1px; 64 top: 1px;
diff --git a/client/src/app/+my-account/+my-account-video-channels/my-account-video-channels.component.ts b/client/src/app/+my-account/+my-account-video-channels/my-account-video-channels.component.ts
index 70510d7c9..da8c7298f 100644
--- a/client/src/app/+my-account/+my-account-video-channels/my-account-video-channels.component.ts
+++ b/client/src/app/+my-account/+my-account-video-channels/my-account-video-channels.component.ts
@@ -1,10 +1,11 @@
1import { ChartData } from 'chart.js' 1import { ChartData } from 'chart.js'
2import { max, maxBy, min, minBy } from 'lodash-es' 2import { max, maxBy, min, minBy } from 'lodash-es'
3import { flatMap } from 'rxjs/operators' 3import { flatMap, debounceTime } from 'rxjs/operators'
4import { Component, OnInit } from '@angular/core' 4import { Component, OnInit } from '@angular/core'
5import { AuthService, ConfirmService, Notifier, ScreenService, User } from '@app/core' 5import { AuthService, ConfirmService, Notifier, ScreenService, User } from '@app/core'
6import { VideoChannel, VideoChannelService } from '@app/shared/shared-main' 6import { VideoChannel, VideoChannelService } from '@app/shared/shared-main'
7import { I18n } from '@ngx-translate/i18n-polyfill' 7import { I18n } from '@ngx-translate/i18n-polyfill'
8import { Subject } from 'rxjs'
8 9
9@Component({ 10@Component({
10 selector: 'my-account-video-channels', 11 selector: 'my-account-video-channels',
@@ -12,11 +13,16 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
12 styleUrls: [ './my-account-video-channels.component.scss' ] 13 styleUrls: [ './my-account-video-channels.component.scss' ]
13}) 14})
14export class MyAccountVideoChannelsComponent implements OnInit { 15export class MyAccountVideoChannelsComponent implements OnInit {
16 totalItems: number
17
15 videoChannels: VideoChannel[] = [] 18 videoChannels: VideoChannel[] = []
16 videoChannelsChartData: ChartData[] 19 videoChannelsChartData: ChartData[]
17 videoChannelsMinimumDailyViews = 0 20 videoChannelsMinimumDailyViews = 0
18 videoChannelsMaximumDailyViews: number 21 videoChannelsMaximumDailyViews: number
19 22
23 channelsSearch: string
24 channelsSearchChanged = new Subject<string>()
25
20 private user: User 26 private user: User
21 27
22 constructor ( 28 constructor (
@@ -32,6 +38,12 @@ export class MyAccountVideoChannelsComponent implements OnInit {
32 this.user = this.authService.getUser() 38 this.user = this.authService.getUser()
33 39
34 this.loadVideoChannels() 40 this.loadVideoChannels()
41
42 this.channelsSearchChanged
43 .pipe(debounceTime(500))
44 .subscribe(() => {
45 this.loadVideoChannels()
46 })
35 } 47 }
36 48
37 get isInSmallView () { 49 get isInSmallView () {
@@ -87,6 +99,15 @@ export class MyAccountVideoChannelsComponent implements OnInit {
87 } 99 }
88 } 100 }
89 101
102 resetSearch() {
103 this.channelsSearch = ''
104 this.onChannelsSearchChanged()
105 }
106
107 onChannelsSearchChanged () {
108 this.channelsSearchChanged.next()
109 }
110
90 async deleteVideoChannel (videoChannel: VideoChannel) { 111 async deleteVideoChannel (videoChannel: VideoChannel) {
91 const res = await this.confirmService.confirmWithInput( 112 const res = await this.confirmService.confirmWithInput(
92 this.i18n( 113 this.i18n(
@@ -118,9 +139,10 @@ export class MyAccountVideoChannelsComponent implements OnInit {
118 139
119 private loadVideoChannels () { 140 private loadVideoChannels () {
120 this.authService.userInformationLoaded 141 this.authService.userInformationLoaded
121 .pipe(flatMap(() => this.videoChannelService.listAccountVideoChannels(this.user.account, null, true))) 142 .pipe(flatMap(() => this.videoChannelService.listAccountVideoChannels(this.user.account, null, true, this.channelsSearch)))
122 .subscribe(res => { 143 .subscribe(res => {
123 this.videoChannels = res.data 144 this.videoChannels = res.data
145 this.totalItems = res.total
124 146
125 // chart data 147 // chart data
126 this.videoChannelsChartData = this.videoChannels.map(v => ({ 148 this.videoChannelsChartData = this.videoChannels.map(v => ({
diff --git a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html
index 9b5f2dd2f..8de152b5e 100644
--- a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html
+++ b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html
@@ -6,10 +6,10 @@
6 </a> 6 </a>
7 7
8 <div class="peertube-select-container peertube-select-button ml-2"> 8 <div class="peertube-select-container peertube-select-button ml-2">
9 <select [(ngModel)]="notificationSortType" class="form-control"> 9 <select [(ngModel)]="notificationSortType" (ngModelChange)="onChangeSortColumn()" class="form-control">
10 <option value="undefined" disabled>Sort by</option> 10 <option value="undefined" disabled>Sort by</option>
11 <option value="created" i18n>Newest first</option> 11 <option value="createdAt" i18n>Newest first</option>
12 <option value="unread-created" i18n>Unread first</option> 12 <option value="read" [disabled]="!hasUnreadNotifications()" i18n>Unread first</option>
13 </select> 13 </select>
14 </div> 14 </div>
15 15
diff --git a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.ts b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.ts
index 8a51319fe..0ec67d401 100644
--- a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.ts
+++ b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.ts
@@ -1,6 +1,8 @@
1import { Component, ViewChild } from '@angular/core' 1import { Component, ViewChild } from '@angular/core'
2import { UserNotificationsComponent } from '@app/shared/shared-main' 2import { UserNotificationsComponent } from '@app/shared/shared-main'
3 3
4type NotificationSortType = 'createdAt' | 'read'
5
4@Component({ 6@Component({
5 templateUrl: './my-account-notifications.component.html', 7 templateUrl: './my-account-notifications.component.html',
6 styleUrls: [ './my-account-notifications.component.scss' ] 8 styleUrls: [ './my-account-notifications.component.scss' ]
@@ -8,7 +10,17 @@ import { UserNotificationsComponent } from '@app/shared/shared-main'
8export class MyAccountNotificationsComponent { 10export class MyAccountNotificationsComponent {
9 @ViewChild('userNotification', { static: true }) userNotification: UserNotificationsComponent 11 @ViewChild('userNotification', { static: true }) userNotification: UserNotificationsComponent
10 12
11 notificationSortType = 'created' 13 _notificationSortType: NotificationSortType = 'createdAt'
14
15 get notificationSortType () {
16 return !this.hasUnreadNotifications()
17 ? 'createdAt'
18 : this._notificationSortType
19 }
20
21 set notificationSortType (type: NotificationSortType) {
22 this._notificationSortType = type
23 }
12 24
13 markAllAsRead () { 25 markAllAsRead () {
14 this.userNotification.markAllAsRead() 26 this.userNotification.markAllAsRead()
@@ -17,4 +29,8 @@ export class MyAccountNotificationsComponent {
17 hasUnreadNotifications () { 29 hasUnreadNotifications () {
18 return this.userNotification.notifications.filter(n => n.read === false).length !== 0 30 return this.userNotification.notifications.filter(n => n.read === false).length !== 0
19 } 31 }
32
33 onChangeSortColumn () {
34 this.userNotification.changeSortColumn(this.notificationSortType)
35 }
20} 36}
diff --git a/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.html b/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.html
index be5d41f3b..4475178c7 100644
--- a/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.html
+++ b/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.html
@@ -62,7 +62,11 @@
62 </td> 62 </td>
63 63
64 <td>{{ videoChangeOwnership.createdAt | date: 'short' }}</td> 64 <td>{{ videoChangeOwnership.createdAt | date: 'short' }}</td>
65 <td i18n>{{ videoChangeOwnership.status }}</td> 65
66 <td>
67 <span class="badge" [ngClass]="getStatusClass(videoChangeOwnership.status)">{{ videoChangeOwnership.status }}</span>
68 </td>
69
66 <td class="action-cell"> 70 <td class="action-cell">
67 <ng-container *ngIf="videoChangeOwnership.status === 'WAITING'"> 71 <ng-container *ngIf="videoChangeOwnership.status === 'WAITING'">
68 <my-button i18n-label label="Accept" icon="tick" (click)="openAcceptModal(videoChangeOwnership)"></my-button> 72 <my-button i18n-label label="Accept" icon="tick" (click)="openAcceptModal(videoChangeOwnership)"></my-button>
diff --git a/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.scss b/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.scss
index c04e26374..7cac9c9f3 100644
--- a/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.scss
+++ b/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.scss
@@ -5,6 +5,10 @@
5 @include chip; 5 @include chip;
6} 6}
7 7
8.badge {
9 @include table-badge;
10}
11
8.video-table-video { 12.video-table-video {
9 display: inline-flex; 13 display: inline-flex;
10 14
diff --git a/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.ts b/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.ts
index 98360dfb3..7473470aa 100644
--- a/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.ts
+++ b/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.ts
@@ -2,7 +2,7 @@ import { SortMeta } from 'primeng/api'
2import { Component, OnInit, ViewChild } from '@angular/core' 2import { Component, OnInit, ViewChild } from '@angular/core'
3import { Notifier, RestPagination, RestTable } from '@app/core' 3import { Notifier, RestPagination, RestTable } from '@app/core'
4import { VideoOwnershipService, Actor, Video, Account } from '@app/shared/shared-main' 4import { VideoOwnershipService, Actor, Video, Account } from '@app/shared/shared-main'
5import { VideoChangeOwnership } from '@shared/models' 5import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '@shared/models'
6import { MyAccountAcceptOwnershipComponent } from './my-account-accept-ownership/my-account-accept-ownership.component' 6import { MyAccountAcceptOwnershipComponent } from './my-account-accept-ownership/my-account-accept-ownership.component'
7import { getAbsoluteAPIUrl } from '@app/helpers' 7import { getAbsoluteAPIUrl } from '@app/helpers'
8 8
@@ -34,6 +34,17 @@ export class MyAccountOwnershipComponent extends RestTable implements OnInit {
34 return 'MyAccountOwnershipComponent' 34 return 'MyAccountOwnershipComponent'
35 } 35 }
36 36
37 getStatusClass (status: VideoChangeOwnershipStatus) {
38 switch (status) {
39 case VideoChangeOwnershipStatus.ACCEPTED:
40 return 'badge-green'
41 case VideoChangeOwnershipStatus.REFUSED:
42 return 'badge-red'
43 default:
44 return 'badge-yellow'
45 }
46 }
47
37 switchToDefaultAvatar ($event: Event) { 48 switchToDefaultAvatar ($event: Event) {
38 ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL() 49 ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL()
39 } 50 }
diff --git a/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.html b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.html
index 3b4c3022e..6cec7c0d5 100644
--- a/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.html
+++ b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.html
@@ -1,6 +1,15 @@
1<h1> 1<h1 class="d-flex justify-content-between">
2 <my-global-icon iconName="subscriptions" aria-hidden="true"></my-global-icon> 2 <span>
3 <ng-container i18n>My subscriptions</ng-container> 3 <my-global-icon iconName="subscriptions" aria-hidden="true"></my-global-icon>
4 <ng-container i18n>My subscriptions</ng-container>
5 <span class="badge badge-secondary"> {{ pagination.totalItems }}</span>
6 </span>
7
8 <div class="has-feedback has-clear">
9 <input type="text" placeholder="Search your subscriptions" i18n-placeholder [(ngModel)]="subscriptionsSearch" (ngModelChange)="onSubscriptionsSearchChanged()" />
10 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
11 <span class="sr-only" i18n>Clear filters</span>
12 </div>
4</h1> 13</h1>
5 14
6<div class="no-results" i18n *ngIf="pagination.totalItems === 0">You don't have any subscriptions yet.</div> 15<div class="no-results" i18n *ngIf="pagination.totalItems === 0">You don't have any subscriptions yet.</div>
diff --git a/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.scss b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.scss
index dd990c42b..884959070 100644
--- a/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.scss
+++ b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.scss
@@ -1,6 +1,10 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3 3
4input[type=text] {
5 @include peertube-input-text(300px);
6}
7
4.video-channel { 8.video-channel {
5 @include row-blocks; 9 @include row-blocks;
6 10
diff --git a/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts
index 390293a28..994fe5142 100644
--- a/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts
+++ b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts
@@ -3,6 +3,7 @@ import { Component, OnInit } from '@angular/core'
3import { ComponentPagination, Notifier } from '@app/core' 3import { ComponentPagination, Notifier } from '@app/core'
4import { VideoChannel } from '@app/shared/shared-main' 4import { VideoChannel } from '@app/shared/shared-main'
5import { UserSubscriptionService } from '@app/shared/shared-user-subscription' 5import { UserSubscriptionService } from '@app/shared/shared-user-subscription'
6import { debounceTime } from 'rxjs/operators'
6 7
7@Component({ 8@Component({
8 selector: 'my-account-subscriptions', 9 selector: 'my-account-subscriptions',
@@ -20,6 +21,9 @@ export class MyAccountSubscriptionsComponent implements OnInit {
20 21
21 onDataSubject = new Subject<any[]>() 22 onDataSubject = new Subject<any[]>()
22 23
24 subscriptionsSearch: string
25 subscriptionsSearchChanged = new Subject<string>()
26
23 constructor ( 27 constructor (
24 private userSubscriptionService: UserSubscriptionService, 28 private userSubscriptionService: UserSubscriptionService,
25 private notifier: Notifier 29 private notifier: Notifier
@@ -27,20 +31,22 @@ export class MyAccountSubscriptionsComponent implements OnInit {
27 31
28 ngOnInit () { 32 ngOnInit () {
29 this.loadSubscriptions() 33 this.loadSubscriptions()
30 }
31 34
32 loadSubscriptions () { 35 this.subscriptionsSearchChanged
33 this.userSubscriptionService.listSubscriptions(this.pagination) 36 .pipe(debounceTime(500))
34 .subscribe( 37 .subscribe(() => {
35 res => { 38 this.pagination.currentPage = 1
36 this.videoChannels = this.videoChannels.concat(res.data) 39 this.loadSubscriptions(false)
37 this.pagination.totalItems = res.total 40 })
41 }
38 42
39 this.onDataSubject.next(res.data) 43 resetSearch () {
40 }, 44 this.subscriptionsSearch = ''
45 this.onSubscriptionsSearchChanged()
46 }
41 47
42 error => this.notifier.error(error.message) 48 onSubscriptionsSearchChanged () {
43 ) 49 this.subscriptionsSearchChanged.next()
44 } 50 }
45 51
46 onNearOfBottom () { 52 onNearOfBottom () {
@@ -51,4 +57,19 @@ export class MyAccountSubscriptionsComponent implements OnInit {
51 this.loadSubscriptions() 57 this.loadSubscriptions()
52 } 58 }
53 59
60 private loadSubscriptions (more = true) {
61 this.userSubscriptionService.listSubscriptions({ pagination: this.pagination, search: this.subscriptionsSearch })
62 .subscribe(
63 res => {
64 this.videoChannels = more
65 ? this.videoChannels.concat(res.data)
66 : res.data
67 this.pagination.totalItems = res.total
68
69 this.onDataSubject.next(res.data)
70 },
71
72 error => this.notifier.error(error.message)
73 )
74 }
54} 75}
diff --git a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html
index 98a2039cc..854126443 100644
--- a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html
+++ b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html
@@ -45,7 +45,12 @@
45 <ng-container *ngIf="isVideoImportFailed(videoImport)"></ng-container> 45 <ng-container *ngIf="isVideoImportFailed(videoImport)"></ng-container>
46 </td> 46 </td>
47 47
48 <td>{{ videoImport.state.label }}</td> 48 <td>
49 <span class="badge" [ngClass]="getVideoImportStateClass(videoImport.state)">
50 {{ videoImport.state.label }}
51 </span>
52 </td>
53
49 <td>{{ videoImport.createdAt | date: 'short' }}</td> 54 <td>{{ videoImport.createdAt | date: 'short' }}</td>
50 55
51 <td class="action-cell"> 56 <td class="action-cell">
diff --git a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.scss b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.scss
index bdd2f8270..a93c28028 100644
--- a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.scss
+++ b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.scss
@@ -7,4 +7,8 @@ pre {
7 7
8.video-import-error { 8.video-import-error {
9 color: red; 9 color: red;
10} \ No newline at end of file 10}
11
12.badge {
13 @include table-badge;
14}
diff --git a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts
index 42ddb0ee2..9dd5ef142 100644
--- a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts
+++ b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts
@@ -30,6 +30,19 @@ export class MyAccountVideoImportsComponent extends RestTable implements OnInit
30 return 'MyAccountVideoImportsComponent' 30 return 'MyAccountVideoImportsComponent'
31 } 31 }
32 32
33 getVideoImportStateClass (state: VideoImportState) {
34 switch (state) {
35 case VideoImportState.FAILED:
36 return 'badge-red'
37 case VideoImportState.REJECTED:
38 return 'badge-banned'
39 case VideoImportState.PENDING:
40 return 'badge-yellow'
41 default:
42 return 'badge-green'
43 }
44 }
45
33 isVideoImportSuccess (videoImport: VideoImport) { 46 isVideoImportSuccess (videoImport: VideoImport) {
34 return videoImport.state.id === VideoImportState.SUCCESS 47 return videoImport.state.id === VideoImportState.SUCCESS
35 } 48 }
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.html b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.html
index 8d69c3a5a..d8e3fb2fa 100644
--- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.html
+++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.html
@@ -1,17 +1,20 @@
1<h1> 1<h1 class="d-flex justify-content-between">
2 <my-global-icon iconName="playlists" aria-hidden="true"></my-global-icon> 2 <span>
3 <ng-container i18n>My playlists</ng-container> <span class="badge badge-secondary">{{ pagination.totalItems }}</span> 3 <my-global-icon iconName="playlists" aria-hidden="true"></my-global-icon>
4</h1> 4 <ng-container i18n>My playlists</ng-container> <span class="badge badge-secondary">{{ pagination.totalItems }}</span>
5 5 </span>
6 6
7<div class="video-playlists-header"> 7 <div class="has-feedback has-clear">
8 <input type="text" placeholder="Search your playlists" i18n-placeholder [(ngModel)]="videoPlaylistsSearch" (ngModelChange)="onVideoPlaylistSearchChanged()" /> 8 <input type="text" placeholder="Search your playlists" i18n-placeholder [(ngModel)]="videoPlaylistsSearch" (ngModelChange)="onVideoPlaylistSearchChanged()" />
9 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
10 <span class="sr-only" i18n>Clear filters</span>
11 </div>
9 12
10 <a class="create-button" routerLink="create"> 13 <a class="create-button" routerLink="create">
11 <my-global-icon iconName="add" aria-hidden="true"></my-global-icon> 14 <my-global-icon iconName="add" aria-hidden="true"></my-global-icon>
12 <ng-container i18n>Create playlist</ng-container> 15 <ng-container i18n>Create playlist</ng-container>
13 </a> 16 </a>
14</div> 17</h1>
15 18
16<div class="video-playlists" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()"> 19<div class="video-playlists" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()">
17 <div *ngFor="let playlist of videoPlaylists" class="video-playlist"> 20 <div *ngFor="let playlist of videoPlaylists" class="video-playlist">
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.scss b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.scss
index 4381d74b0..ade28c70b 100644
--- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.scss
+++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.scss
@@ -5,6 +5,10 @@
5 @include create-button; 5 @include create-button;
6} 6}
7 7
8input[type=text] {
9 @include peertube-input-text(300px);
10}
11
8::ng-deep .action-button { 12::ng-deep .action-button {
9 &.action-button-delete { 13 &.action-button-delete {
10 margin-right: 10px; 14 margin-right: 10px;
@@ -33,16 +37,6 @@
33 } 37 }
34} 38}
35 39
36.video-playlists-header {
37 display: flex;
38 justify-content: space-between;
39 margin: 20px 0 50px;
40
41 input[type=text] {
42 @include peertube-input-text(300px);
43 }
44}
45
46@media screen and (max-width: $small-view) { 40@media screen and (max-width: $small-view) {
47 .video-playlists-header { 41 .video-playlists-header {
48 text-align: center; 42 text-align: center;
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.ts b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.ts
index ea3bcde4f..668a23d8f 100644
--- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.ts
+++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.ts
@@ -84,6 +84,11 @@ export class MyAccountVideoPlaylistsComponent implements OnInit {
84 this.loadVideoPlaylists() 84 this.loadVideoPlaylists()
85 } 85 }
86 86
87 resetSearch () {
88 this.videoPlaylistsSearch = ''
89 this.onVideoPlaylistSearchChanged()
90 }
91
87 onVideoPlaylistSearchChanged () { 92 onVideoPlaylistSearchChanged () {
88 this.videoPlaylistSearchChanged.next() 93 this.videoPlaylistSearchChanged.next()
89 } 94 }
diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.html b/client/src/app/+my-account/my-account-videos/my-account-videos.component.html
index 6d098b507..faeb3b56c 100644
--- a/client/src/app/+my-account/my-account-videos/my-account-videos.component.html
+++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.html
@@ -1,11 +1,16 @@
1<h1> 1<h1 class="d-flex justify-content-between">
2 <my-global-icon iconName="videos" aria-hidden="true"></my-global-icon> 2 <span>
3 <ng-container i18n>My videos</ng-container><span class="badge badge-secondary"> {{ pagination.totalItems }}</span> 3 <my-global-icon iconName="videos" aria-hidden="true"></my-global-icon>
4</h1> 4 <ng-container i18n>My videos</ng-container>
5 <span class="badge badge-secondary"> {{ pagination.totalItems }}</span>
6 </span>
5 7
6<div class="videos-header"> 8 <div class="has-feedback has-clear">
7 <input type="text" placeholder="Search your videos" i18n-placeholder [(ngModel)]="videosSearch" (ngModelChange)="onVideosSearchChanged()" /> 9 <input type="text" placeholder="Search your videos" i18n-placeholder [(ngModel)]="videosSearch" (ngModelChange)="onVideosSearchChanged()" />
8</div> 10 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
11 <span class="sr-only" i18n>Clear filters</span>
12 </div>
13</h1>
9 14
10<my-videos-selection 15<my-videos-selection
11 [pagination]="pagination" 16 [pagination]="pagination"
diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss b/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss
index 9225fc5fd..0930b1959 100644
--- a/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss
+++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss
@@ -1,14 +1,8 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3 3
4.videos-header { 4input[type=text] {
5 display: flex; 5 @include peertube-input-text(300px);
6 justify-content: space-between;
7 margin: 20px 0 50px;
8
9 input[type=text] {
10 @include peertube-input-text(300px);
11 }
12} 6}
13 7
14.action-button-delete-selection { 8.action-button-delete-selection {
diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts b/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts
index 3cfe8fb38..2274c6a7b 100644
--- a/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts
+++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts
@@ -59,13 +59,17 @@ export class MyAccountVideosComponent implements OnInit, DisableForReuseHook {
59 59
60 ngOnInit () { 60 ngOnInit () {
61 this.videosSearchChanged 61 this.videosSearchChanged
62 .pipe( 62 .pipe(debounceTime(500))
63 debounceTime(500))
64 .subscribe(() => { 63 .subscribe(() => {
65 this.videosSelection.reloadVideos() 64 this.videosSelection.reloadVideos()
66 }) 65 })
67 } 66 }
68 67
68 resetSearch () {
69 this.videosSearch = ''
70 this.onVideosSearchChanged()
71 }
72
69 onVideosSearchChanged () { 73 onVideosSearchChanged () {
70 this.videosSearchChanged.next() 74 this.videosSearchChanged.next()
71 } 75 }
diff --git a/client/src/app/core/users/user.service.ts b/client/src/app/core/users/user.service.ts
index 5f9300bec..c98b3844c 100644
--- a/client/src/app/core/users/user.service.ts
+++ b/client/src/app/core/users/user.service.ts
@@ -381,14 +381,14 @@ export class UserService {
381 381
382 const videoQuotaUsed = this.bytesPipe.transform(user.videoQuotaUsed, 0) 382 const videoQuotaUsed = this.bytesPipe.transform(user.videoQuotaUsed, 0)
383 383
384 let videoQuotaDaily 384 let videoQuotaDaily: string
385 let videoQuotaUsedDaily 385 let videoQuotaUsedDaily: string
386 if (user.videoQuotaDaily === -1) { 386 if (user.videoQuotaDaily === -1) {
387 videoQuotaDaily = '∞' 387 videoQuotaDaily = '∞'
388 videoQuotaUsedDaily = this.bytesPipe.transform(0, 0) 388 videoQuotaUsedDaily = this.bytesPipe.transform(0, 0) + ''
389 } else { 389 } else {
390 videoQuotaDaily = this.bytesPipe.transform(user.videoQuotaDaily, 0) 390 videoQuotaDaily = this.bytesPipe.transform(user.videoQuotaDaily, 0) + ''
391 videoQuotaUsedDaily = this.bytesPipe.transform(user.videoQuotaUsedDaily || 0, 0) 391 videoQuotaUsedDaily = this.bytesPipe.transform(user.videoQuotaUsedDaily || 0, 0) + ''
392 } 392 }
393 393
394 const roleLabels: { [ id in UserRole ]: string } = { 394 const roleLabels: { [ id in UserRole ]: string } = {
diff --git a/client/src/app/shared/shared-user-subscription/user-subscription.service.ts b/client/src/app/shared/shared-user-subscription/user-subscription.service.ts
index 732ed6bcb..eb1fdf91c 100644
--- a/client/src/app/shared/shared-user-subscription/user-subscription.service.ts
+++ b/client/src/app/shared/shared-user-subscription/user-subscription.service.ts
@@ -105,13 +105,18 @@ export class UserSubscriptionService {
105 ) 105 )
106 } 106 }
107 107
108 listSubscriptions (componentPagination: ComponentPaginationLight): Observable<ResultList<VideoChannel>> { 108 listSubscriptions (parameters: {
109 pagination: ComponentPaginationLight
110 search: string
111 }): Observable<ResultList<VideoChannel>> {
112 const { pagination, search } = parameters
109 const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL 113 const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL
110 114
111 const pagination = this.restService.componentPaginationToRestPagination(componentPagination) 115 const restPagination = this.restService.componentPaginationToRestPagination(pagination)
112 116
113 let params = new HttpParams() 117 let params = new HttpParams()
114 params = this.restService.addRestGetParams(params, pagination) 118 params = this.restService.addRestGetParams(params, restPagination)
119 if (search) params = params.append('search', search)
115 120
116 return this.authHttp.get<ResultList<VideoChannelServer>>(url, { params }) 121 return this.authHttp.get<ResultList<VideoChannelServer>>(url, { params })
117 .pipe( 122 .pipe(