aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/+my-account
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-05-09 09:26:41 +0200
committerChocobozzz <me@florianbigard.com>2018-05-09 09:32:26 +0200
commit62e62f118d5da57acd3494fece2e8ed357564ffe (patch)
treeff5b7c6ec5708d71a76a2eb5744d810015ae29a5 /client/src/app/+my-account
parent1952a538baa330b13bd11631b3975f7ef1c37e70 (diff)
downloadPeerTube-62e62f118d5da57acd3494fece2e8ed357564ffe.tar.gz
PeerTube-62e62f118d5da57acd3494fece2e8ed357564ffe.tar.zst
PeerTube-62e62f118d5da57acd3494fece2e8ed357564ffe.zip
Load my-account module lazily
Diffstat (limited to 'client/src/app/+my-account')
-rw-r--r--client/src/app/+my-account/index.ts3
-rw-r--r--client/src/app/+my-account/my-account-routing.module.ts71
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-change-password/index.ts1
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html20
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.scss19
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts62
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-profile/index.ts1
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.html24
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.scss23
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts65
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-settings.component.html32
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-settings.component.scss67
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-settings.component.ts70
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-video-settings/index.ts1
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html25
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.scss20
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts60
-rw-r--r--client/src/app/+my-account/my-account-video-channels/my-account-video-channel-create.component.ts89
-rw-r--r--client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.html42
-rw-r--r--client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.scss27
-rw-r--r--client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.ts6
-rw-r--r--client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts117
-rw-r--r--client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html30
-rw-r--r--client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.scss72
-rw-r--r--client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts59
-rw-r--r--client/src/app/+my-account/my-account-videos/my-account-videos.component.html45
-rw-r--r--client/src/app/+my-account/my-account-videos/my-account-videos.component.scss114
-rw-r--r--client/src/app/+my-account/my-account-videos/my-account-videos.component.ts131
-rw-r--r--client/src/app/+my-account/my-account.component.html13
-rw-r--r--client/src/app/+my-account/my-account.component.ts7
-rw-r--r--client/src/app/+my-account/my-account.module.ts38
31 files changed, 1354 insertions, 0 deletions
diff --git a/client/src/app/+my-account/index.ts b/client/src/app/+my-account/index.ts
new file mode 100644
index 000000000..3df96dd7a
--- /dev/null
+++ b/client/src/app/+my-account/index.ts
@@ -0,0 +1,3 @@
1export * from './my-account-routing.module'
2export * from './my-account.component'
3export * from './my-account.module'
diff --git a/client/src/app/+my-account/my-account-routing.module.ts b/client/src/app/+my-account/my-account-routing.module.ts
new file mode 100644
index 000000000..7e19cd994
--- /dev/null
+++ b/client/src/app/+my-account/my-account-routing.module.ts
@@ -0,0 +1,71 @@
1import { NgModule } from '@angular/core'
2import { RouterModule, Routes } from '@angular/router'
3import { MetaGuard } from '@ngx-meta/core'
4import { LoginGuard } from '../core'
5import { MyAccountComponent } from './my-account.component'
6import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component'
7import { MyAccountVideosComponent } from './my-account-videos/my-account-videos.component'
8import { MyAccountVideoChannelsComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channels.component'
9import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component'
10import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component'
11
12const myAccountRoutes: Routes = [
13 {
14 path: '',
15 component: MyAccountComponent,
16 canActivateChild: [ MetaGuard, LoginGuard ],
17 children: [
18 {
19 path: 'settings',
20 component: MyAccountSettingsComponent,
21 data: {
22 meta: {
23 title: 'Account settings'
24 }
25 }
26 },
27 {
28 path: 'video-channels',
29 component: MyAccountVideoChannelsComponent,
30 data: {
31 meta: {
32 title: 'Account video channels'
33 }
34 }
35 },
36 {
37 path: 'video-channels/create',
38 component: MyAccountVideoChannelCreateComponent,
39 data: {
40 meta: {
41 title: 'Create new video channel'
42 }
43 }
44 },
45 {
46 path: 'video-channels/update/:videoChannelId',
47 component: MyAccountVideoChannelUpdateComponent,
48 data: {
49 meta: {
50 title: 'Update video channel'
51 }
52 }
53 },
54 {
55 path: 'videos',
56 component: MyAccountVideosComponent,
57 data: {
58 meta: {
59 title: 'Account videos'
60 }
61 }
62 }
63 ]
64 }
65]
66
67@NgModule({
68 imports: [ RouterModule.forChild(myAccountRoutes) ],
69 exports: [ RouterModule ]
70})
71export class MyAccountRoutingModule {}
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-password/index.ts b/client/src/app/+my-account/my-account-settings/my-account-change-password/index.ts
new file mode 100644
index 000000000..644047c5f
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-change-password/index.ts
@@ -0,0 +1 @@
export * from './my-account-change-password.component'
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html
new file mode 100644
index 000000000..b0e3cada4
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html
@@ -0,0 +1,20 @@
1<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
2
3<form role="form" (ngSubmit)="changePassword()" [formGroup]="form">
4
5 <label for="new-password">Change password</label>
6 <input
7 type="password" id="new-password" placeholder="New password"
8 formControlName="new-password" [ngClass]="{ 'input-error': formErrors['new-password'] }"
9 >
10 <div *ngIf="formErrors['new-password']" class="form-error">
11 {{ formErrors['new-password'] }}
12 </div>
13
14 <input
15 type="password" id="new-confirmed-password" placeholder="Confirm new password"
16 formControlName="new-confirmed-password"
17 >
18
19 <input type="submit" value="Change password" [disabled]="!form.valid">
20</form>
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.scss b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.scss
new file mode 100644
index 000000000..f8279ffd3
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.scss
@@ -0,0 +1,19 @@
1@import '_variables';
2@import '_mixins';
3
4input[type=password] {
5 @include peertube-input-text(340px);
6 display: block;
7
8 &#new-confirmed-password {
9 margin-top: 15px;
10 }
11}
12
13input[type=submit] {
14 @include peertube-button;
15 @include orange-button;
16
17 margin-top: 15px;
18}
19
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts
new file mode 100644
index 000000000..80af668f9
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts
@@ -0,0 +1,62 @@
1import { Component, OnInit } from '@angular/core'
2import { FormBuilder, FormGroup } from '@angular/forms'
3import { NotificationsService } from 'angular2-notifications'
4import { FormReactive, USER_PASSWORD, UserService } from '../../../shared'
5
6@Component({
7 selector: 'my-account-change-password',
8 templateUrl: './my-account-change-password.component.html',
9 styleUrls: [ './my-account-change-password.component.scss' ]
10})
11export class MyAccountChangePasswordComponent extends FormReactive implements OnInit {
12 error: string = null
13
14 form: FormGroup
15 formErrors = {
16 'new-password': '',
17 'new-confirmed-password': ''
18 }
19 validationMessages = {
20 'new-password': USER_PASSWORD.MESSAGES,
21 'new-confirmed-password': USER_PASSWORD.MESSAGES
22 }
23
24 constructor (
25 private formBuilder: FormBuilder,
26 private notificationsService: NotificationsService,
27 private userService: UserService
28 ) {
29 super()
30 }
31
32 buildForm () {
33 this.form = this.formBuilder.group({
34 'new-password': [ '', USER_PASSWORD.VALIDATORS ],
35 'new-confirmed-password': [ '', USER_PASSWORD.VALIDATORS ]
36 })
37
38 this.form.valueChanges.subscribe(data => this.onValueChanged(data))
39 }
40
41 ngOnInit () {
42 this.buildForm()
43 }
44
45 changePassword () {
46 const newPassword = this.form.value['new-password']
47 const newConfirmedPassword = this.form.value['new-confirmed-password']
48
49 this.error = null
50
51 if (newPassword !== newConfirmedPassword) {
52 this.error = 'The new password and the confirmed password do not correspond.'
53 return
54 }
55
56 this.userService.changePassword(newPassword).subscribe(
57 () => this.notificationsService.success('Success', 'Password updated.'),
58
59 err => this.error = err.message
60 )
61 }
62}
diff --git a/client/src/app/+my-account/my-account-settings/my-account-profile/index.ts b/client/src/app/+my-account/my-account-settings/my-account-profile/index.ts
new file mode 100644
index 000000000..3cc049f8f
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-profile/index.ts
@@ -0,0 +1 @@
export * from './my-account-profile.component'
diff --git a/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.html b/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.html
new file mode 100644
index 000000000..306f3a12c
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.html
@@ -0,0 +1,24 @@
1<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
2
3<form role="form" (ngSubmit)="updateMyProfile()" [formGroup]="form">
4
5 <label for="display-name">Display name</label>
6 <input
7 type="text" id="display-name"
8 formControlName="display-name" [ngClass]="{ 'input-error': formErrors['display-name'] }"
9 >
10 <div *ngIf="formErrors['display-name']" class="form-error">
11 {{ formErrors['display-name'] }}
12 </div>
13
14 <label for="description">Description</label>
15 <textarea
16 id="description" formControlName="description"
17 [ngClass]="{ 'input-error': formErrors['description'] }"
18 ></textarea>
19 <div *ngIf="formErrors.description" class="form-error">
20 {{ formErrors.description }}
21 </div>
22
23 <input type="submit" value="Update my profile" [disabled]="!form.valid">
24</form>
diff --git a/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.scss b/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.scss
new file mode 100644
index 000000000..fc2b92c89
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.scss
@@ -0,0 +1,23 @@
1@import '_variables';
2@import '_mixins';
3
4input[type=text] {
5 @include peertube-input-text(340px);
6
7 display: block;
8 margin-bottom: 15px;
9}
10
11textarea {
12 @include peertube-textarea(500px, 150px);
13
14 display: block;
15}
16
17input[type=submit] {
18 @include peertube-button;
19 @include orange-button;
20
21 margin-top: 15px;
22}
23
diff --git a/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts b/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts
new file mode 100644
index 000000000..2b7ba353c
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts
@@ -0,0 +1,65 @@
1import { Component, Input, OnInit } from '@angular/core'
2import { FormBuilder, FormGroup } from '@angular/forms'
3import { NotificationsService } from 'angular2-notifications'
4import { FormReactive, USER_DESCRIPTION, USER_DISPLAY_NAME, UserService } from '../../../shared'
5import { User } from '@app/shared'
6
7@Component({
8 selector: 'my-account-profile',
9 templateUrl: './my-account-profile.component.html',
10 styleUrls: [ './my-account-profile.component.scss' ]
11})
12export class MyAccountProfileComponent extends FormReactive implements OnInit {
13 @Input() user: User = null
14
15 error: string = null
16
17 form: FormGroup
18 formErrors = {
19 'display-name': '',
20 'description': ''
21 }
22 validationMessages = {
23 'display-name': USER_DISPLAY_NAME.MESSAGES,
24 'description': USER_DESCRIPTION.MESSAGES
25 }
26
27 constructor (
28 private formBuilder: FormBuilder,
29 private notificationsService: NotificationsService,
30 private userService: UserService
31 ) {
32 super()
33 }
34
35 buildForm () {
36 this.form = this.formBuilder.group({
37 'display-name': [ this.user.account.displayName, USER_DISPLAY_NAME.VALIDATORS ],
38 'description': [ this.user.account.description, USER_DESCRIPTION.VALIDATORS ]
39 })
40
41 this.form.valueChanges.subscribe(data => this.onValueChanged(data))
42 }
43
44 ngOnInit () {
45 this.buildForm()
46 }
47
48 updateMyProfile () {
49 const displayName = this.form.value['display-name']
50 const description = this.form.value['description']
51
52 this.error = null
53
54 this.userService.updateMyProfile({ displayName, description }).subscribe(
55 () => {
56 this.user.account.displayName = displayName
57 this.user.account.description = description
58
59 this.notificationsService.success('Success', 'Profile updated.')
60 },
61
62 err => this.error = err.message
63 )
64 }
65}
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html
new file mode 100644
index 000000000..0fcc7782e
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html
@@ -0,0 +1,32 @@
1<div class="user">
2 <img [src]="user.accountAvatarUrl" alt="Avatar" />
3
4 <div class="user-info">
5 <div class="user-info-names">
6 <div class="user-info-display-name">{{ user.account.displayName }}</div>
7 <div class="user-info-username">{{ user.username }}</div>
8 </div>
9 <div class="user-info-followers">{{ user.account?.followersCount }} subscribers</div>
10 </div>
11</div>
12
13<div class="button-file">
14 <span>Change your avatar</span>
15 <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="changeAvatar()" />
16</div>
17<div class="file-max-size">(extensions: {{ avatarExtensions }}, max size: {{ maxAvatarSize | bytes }})</div>
18
19<div class="user-quota">
20 <span class="user-quota-label">Video quota:</span> {{ userVideoQuotaUsed | bytes: 0 }} / {{ userVideoQuota }}
21</div>
22
23<ng-template [ngIf]="user && user.account">
24 <div class="account-title">Profile</div>
25 <my-account-profile [user]="user"></my-account-profile>
26</ng-template>
27
28<div class="account-title">Password</div>
29<my-account-change-password></my-account-change-password>
30
31<div class="account-title">Video settings</div>
32<my-account-video-settings [user]="user"></my-account-video-settings>
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.scss b/client/src/app/+my-account/my-account-settings/my-account-settings.component.scss
new file mode 100644
index 000000000..ec0d40b93
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.scss
@@ -0,0 +1,67 @@
1@import '_variables';
2@import '_mixins';
3
4.user {
5 display: flex;
6
7 img {
8 @include avatar(50px);
9
10 margin-right: 15px;
11 }
12
13 .user-info {
14 .user-info-names {
15 display: flex;
16 align-items: center;
17
18 .user-info-display-name {
19 font-size: 20px;
20 font-weight: $font-bold;
21 }
22
23 .user-info-username {
24 margin-left: 7px;
25 position: relative;
26 top: 2px;
27 font-size: 14px;
28 color: #777272;
29 }
30 }
31
32 .user-info-followers {
33 font-size: 15px;
34 }
35 }
36}
37
38.button-file {
39 @include peertube-button-file(160px);
40
41 margin-top: 10px;
42 margin-bottom: 5px;
43}
44
45.file-max-size {
46 display: inline-block;
47 font-size: 13px;
48
49 position: relative;
50 top: -10px;
51}
52
53.user-quota {
54 font-size: 15px;
55 margin-top: 20px;
56
57 .user-quota-label {
58 font-weight: $font-semibold;
59 }
60}
61
62.account-title {
63 @include in-content-small-title;
64
65 margin-top: 55px;
66 margin-bottom: 30px;
67}
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts
new file mode 100644
index 000000000..06d1138e7
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts
@@ -0,0 +1,70 @@
1import { Component, OnInit, ViewChild } from '@angular/core'
2import { NotificationsService } from 'angular2-notifications'
3import { BytesPipe } from 'ngx-pipes'
4import { AuthService } from '../../core'
5import { ServerService } from '../../core/server'
6import { User } from '../../shared'
7import { UserService } from '../../shared/users'
8
9@Component({
10 selector: 'my-account-settings',
11 templateUrl: './my-account-settings.component.html',
12 styleUrls: [ './my-account-settings.component.scss' ]
13})
14export class MyAccountSettingsComponent implements OnInit {
15 @ViewChild('avatarfileInput') avatarfileInput
16
17 user: User = null
18 userVideoQuota = '0'
19 userVideoQuotaUsed = 0
20
21 constructor (
22 private userService: UserService,
23 private authService: AuthService,
24 private serverService: ServerService,
25 private notificationsService: NotificationsService
26 ) {}
27
28 ngOnInit () {
29 this.user = this.authService.getUser()
30
31 this.authService.userInformationLoaded.subscribe(
32 () => {
33 if (this.user.videoQuota !== -1) {
34 this.userVideoQuota = new BytesPipe().transform(this.user.videoQuota, 0).toString()
35 } else {
36 this.userVideoQuota = 'Unlimited'
37 }
38 }
39 )
40
41 this.userService.getMyVideoQuotaUsed()
42 .subscribe(data => this.userVideoQuotaUsed = data.videoQuotaUsed)
43 }
44
45 changeAvatar () {
46 const avatarfile = this.avatarfileInput.nativeElement.files[ 0 ]
47
48 const formData = new FormData()
49 formData.append('avatarfile', avatarfile)
50
51 this.userService.changeAvatar(formData)
52 .subscribe(
53 data => {
54 this.notificationsService.success('Success', 'Avatar changed.')
55
56 this.user.account.avatar = data.avatar
57 },
58
59 err => this.notificationsService.error('Error', err.message)
60 )
61 }
62
63 get maxAvatarSize () {
64 return this.serverService.getConfig().avatar.file.size.max
65 }
66
67 get avatarExtensions () {
68 return this.serverService.getConfig().avatar.file.extensions.join(',')
69 }
70}
diff --git a/client/src/app/+my-account/my-account-settings/my-account-video-settings/index.ts b/client/src/app/+my-account/my-account-settings/my-account-video-settings/index.ts
new file mode 100644
index 000000000..1253bd369
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-video-settings/index.ts
@@ -0,0 +1 @@
export * from './my-account-video-settings.component'
diff --git a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html
new file mode 100644
index 000000000..0e8598e9e
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html
@@ -0,0 +1,25 @@
1<form role="form" (ngSubmit)="updateDetails()" [formGroup]="form">
2 <div class="form-group">
3 <label for="nsfwPolicy">Default policy on videos containing sensitive content</label>
4 <my-help helpType="custom" customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video."></my-help>
5
6 <div class="peertube-select-container">
7 <select id="nsfwPolicy" formControlName="nsfwPolicy">
8 <option value="do_not_list">Do not list</option>
9 <option value="blur">Blur thumbnails</option>
10 <option value="display">Display</option>
11 </select>
12 </div>
13 </div>
14
15 <div class="form-group">
16 <input
17 type="checkbox" id="autoPlayVideo"
18 formControlName="autoPlayVideo"
19 >
20 <label for="autoPlayVideo"></label>
21 <label for="autoPlayVideo">Automatically plays video</label>
22 </div>
23
24 <input type="submit" value="Save" [disabled]="!form.valid">
25</form>
diff --git a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.scss b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.scss
new file mode 100644
index 000000000..ed59e4689
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.scss
@@ -0,0 +1,20 @@
1@import '_variables';
2@import '_mixins';
3
4input[type=checkbox] {
5 @include peertube-checkbox(1px);
6}
7
8input[type=submit] {
9 @include peertube-button;
10 @include orange-button;
11
12 display: block;
13 margin-top: 15px;
14}
15
16.peertube-select-container {
17 @include peertube-select-container(340px);
18
19 margin-bottom: 30px;
20} \ No newline at end of file
diff --git a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts
new file mode 100644
index 000000000..acc70c14d
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts
@@ -0,0 +1,60 @@
1import { Component, Input, OnInit } from '@angular/core'
2import { FormBuilder, FormGroup } from '@angular/forms'
3import { NotificationsService } from 'angular2-notifications'
4import { UserUpdateMe } from '../../../../../../shared'
5import { AuthService } from '../../../core'
6import { FormReactive, User, UserService } from '../../../shared'
7
8@Component({
9 selector: 'my-account-video-settings',
10 templateUrl: './my-account-video-settings.component.html',
11 styleUrls: [ './my-account-video-settings.component.scss' ]
12})
13export class MyAccountVideoSettingsComponent extends FormReactive implements OnInit {
14 @Input() user: User = null
15
16 form: FormGroup
17 formErrors = {}
18 validationMessages = {}
19
20 constructor (
21 private authService: AuthService,
22 private formBuilder: FormBuilder,
23 private notificationsService: NotificationsService,
24 private userService: UserService
25 ) {
26 super()
27 }
28
29 buildForm () {
30 this.form = this.formBuilder.group({
31 nsfwPolicy: [ this.user.nsfwPolicy ],
32 autoPlayVideo: [ this.user.autoPlayVideo ]
33 })
34
35 this.form.valueChanges.subscribe(data => this.onValueChanged(data))
36 }
37
38 ngOnInit () {
39 this.buildForm()
40 }
41
42 updateDetails () {
43 const nsfwPolicy = this.form.value['nsfwPolicy']
44 const autoPlayVideo = this.form.value['autoPlayVideo']
45 const details: UserUpdateMe = {
46 nsfwPolicy,
47 autoPlayVideo
48 }
49
50 this.userService.updateMyProfile(details).subscribe(
51 () => {
52 this.notificationsService.success('Success', 'Information updated.')
53
54 this.authService.refreshUserInformation()
55 },
56
57 err => this.notificationsService.error('Error', err.message)
58 )
59 }
60}
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-create.component.ts b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-create.component.ts
new file mode 100644
index 000000000..0f03548ad
--- /dev/null
+++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-create.component.ts
@@ -0,0 +1,89 @@
1import { Component, OnInit } from '@angular/core'
2import { Router } from '@angular/router'
3import { NotificationsService } from 'angular2-notifications'
4import 'rxjs/add/observable/from'
5import 'rxjs/add/operator/concatAll'
6import { MyAccountVideoChannelEdit } from './my-account-video-channel-edit'
7import { FormBuilder, FormGroup } from '@angular/forms'
8import { VideoChannelCreate } from '../../../../../shared/models/videos'
9import {
10 VIDEO_CHANNEL_DESCRIPTION,
11 VIDEO_CHANNEL_DISPLAY_NAME,
12 VIDEO_CHANNEL_SUPPORT
13} from '@app/shared/forms/form-validators/video-channel'
14import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
15import { AuthService } from '@app/core'
16
17@Component({
18 selector: 'my-account-video-channel-create',
19 templateUrl: './my-account-video-channel-edit.component.html',
20 styleUrls: [ './my-account-video-channel-edit.component.scss' ]
21})
22export class MyAccountVideoChannelCreateComponent extends MyAccountVideoChannelEdit implements OnInit {
23 error: string
24
25 form: FormGroup
26 formErrors = {
27 'display-name': '',
28 'description': '',
29 'support': ''
30 }
31 validationMessages = {
32 'display-name': VIDEO_CHANNEL_DISPLAY_NAME.MESSAGES,
33 'description': VIDEO_CHANNEL_DESCRIPTION.MESSAGES,
34 'support': VIDEO_CHANNEL_SUPPORT.MESSAGES
35 }
36
37 constructor (
38 private authService: AuthService,
39 private notificationsService: NotificationsService,
40 private router: Router,
41 private formBuilder: FormBuilder,
42 private videoChannelService: VideoChannelService
43 ) {
44 super()
45 }
46
47 buildForm () {
48 this.form = this.formBuilder.group({
49 'display-name': [ '', VIDEO_CHANNEL_DISPLAY_NAME.VALIDATORS ],
50 description: [ '', VIDEO_CHANNEL_DESCRIPTION.VALIDATORS ],
51 support: [ '', VIDEO_CHANNEL_SUPPORT.VALIDATORS ]
52 })
53
54 this.form.valueChanges.subscribe(data => this.onValueChanged(data))
55 }
56
57 ngOnInit () {
58 this.buildForm()
59 }
60
61 formValidated () {
62 this.error = undefined
63
64 const body = this.form.value
65 const videoChannelCreate: VideoChannelCreate = {
66 displayName: body['display-name'],
67 description: body.description || undefined,
68 support: body.support || undefined
69 }
70
71 this.videoChannelService.createVideoChannel(videoChannelCreate).subscribe(
72 () => {
73 this.authService.refreshUserInformation()
74 this.notificationsService.success('Success', `Video channel ${videoChannelCreate.displayName} created.`)
75 this.router.navigate([ '/my-account', 'video-channels' ])
76 },
77
78 err => this.error = err.message
79 )
80 }
81
82 isCreation () {
83 return true
84 }
85
86 getFormButtonTitle () {
87 return 'Create'
88 }
89}
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.html b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.html
new file mode 100644
index 000000000..d5fb6262a
--- /dev/null
+++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.html
@@ -0,0 +1,42 @@
1<div class="form-sub-title" *ngIf="isCreation() === true">Create a video channel</div>
2<div class="form-sub-title" *ngIf="isCreation() === false">Update {{ videoChannel?.displayName }}</div>
3
4<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
5
6<form role="form" (ngSubmit)="formValidated()" [formGroup]="form">
7 <div class="form-group">
8 <label for="display-name">Display name</label>
9 <input
10 type="text" id="display-name"
11 formControlName="display-name" [ngClass]="{ 'input-error': formErrors['display-name'] }"
12 >
13 <div *ngIf="formErrors['display-name']" class="form-error">
14 {{ formErrors['display-name'] }}
15 </div>
16 </div>
17
18 <div class="form-group">
19 <label for="description">Description</label>
20 <textarea
21 id="description" formControlName="description"
22 [ngClass]="{ 'input-error': formErrors['description'] }"
23 ></textarea>
24 <div *ngIf="formErrors.description" class="form-error">
25 {{ formErrors.description }}
26 </div>
27 </div>
28
29 <div class="form-group">
30 <label for="support">Support</label>
31 <my-help helpType="markdownEnhanced" preHtml="Short text to tell people how they can support your channel (membership platform...)."></my-help>
32 <my-markdown-textarea
33 id="support" formControlName="support" textareaWidth="500px" [previewColumn]="true" markdownType="enhanced"
34 [classes]="{ 'input-error': formErrors['support'] }"
35 ></my-markdown-textarea>
36 <div *ngIf="formErrors.support" class="form-error">
37 {{ formErrors.support }}
38 </div>
39 </div>
40
41 <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid">
42</form>
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.scss b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.scss
new file mode 100644
index 000000000..6fbb8ae8b
--- /dev/null
+++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.scss
@@ -0,0 +1,27 @@
1@import '_variables';
2@import '_mixins';
3
4.form-sub-title {
5 margin-bottom: 20px;
6}
7
8input[type=text] {
9 @include peertube-input-text(340px);
10
11 display: block;
12}
13
14textarea {
15 @include peertube-textarea(500px, 150px);
16
17 display: block;
18}
19
20.peertube-select-container {
21 @include peertube-select-container(340px);
22}
23
24input[type=submit] {
25 @include peertube-button;
26 @include orange-button;
27} \ No newline at end of file
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.ts b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.ts
new file mode 100644
index 000000000..e56f46262
--- /dev/null
+++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.ts
@@ -0,0 +1,6 @@
1import { FormReactive } from '@app/shared'
2
3export abstract class MyAccountVideoChannelEdit extends FormReactive {
4 abstract isCreation (): boolean
5 abstract getFormButtonTitle (): string
6}
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts
new file mode 100644
index 000000000..c0dc6a939
--- /dev/null
+++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts
@@ -0,0 +1,117 @@
1import { Component, OnInit, OnDestroy } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router'
3import { NotificationsService } from 'angular2-notifications'
4import 'rxjs/add/observable/from'
5import 'rxjs/add/operator/concatAll'
6import { MyAccountVideoChannelEdit } from './my-account-video-channel-edit'
7import { FormBuilder, FormGroup } from '@angular/forms'
8import { VideoChannelUpdate } from '../../../../../shared/models/videos'
9import {
10 VIDEO_CHANNEL_DESCRIPTION,
11 VIDEO_CHANNEL_DISPLAY_NAME,
12 VIDEO_CHANNEL_SUPPORT
13} from '@app/shared/forms/form-validators/video-channel'
14import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
15import { Subscription } from 'rxjs/Subscription'
16import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
17import { AuthService } from '@app/core'
18
19@Component({
20 selector: 'my-account-video-channel-update',
21 templateUrl: './my-account-video-channel-edit.component.html',
22 styleUrls: [ './my-account-video-channel-edit.component.scss' ]
23})
24export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelEdit implements OnInit, OnDestroy {
25 error: string
26
27 form: FormGroup
28 formErrors = {
29 'display-name': '',
30 'description': '',
31 'support': ''
32 }
33 validationMessages = {
34 'display-name': VIDEO_CHANNEL_DISPLAY_NAME.MESSAGES,
35 'description': VIDEO_CHANNEL_DESCRIPTION.MESSAGES,
36 'support': VIDEO_CHANNEL_SUPPORT.MESSAGES
37 }
38
39 private videoChannelToUpdate: VideoChannel
40 private paramsSub: Subscription
41
42 constructor (
43 private authService: AuthService,
44 private notificationsService: NotificationsService,
45 private router: Router,
46 private route: ActivatedRoute,
47 private formBuilder: FormBuilder,
48 private videoChannelService: VideoChannelService
49 ) {
50 super()
51 }
52
53 buildForm () {
54 this.form = this.formBuilder.group({
55 'display-name': [ '', VIDEO_CHANNEL_DISPLAY_NAME.VALIDATORS ],
56 description: [ '', VIDEO_CHANNEL_DESCRIPTION.VALIDATORS ],
57 support: [ '', VIDEO_CHANNEL_SUPPORT.VALIDATORS ]
58 })
59
60 this.form.valueChanges.subscribe(data => this.onValueChanged(data))
61 }
62
63 ngOnInit () {
64 this.buildForm()
65
66 this.paramsSub = this.route.params.subscribe(routeParams => {
67 const videoChannelId = routeParams['videoChannelId']
68
69 this.videoChannelService.getVideoChannel(videoChannelId).subscribe(
70 videoChannelToUpdate => {
71 this.videoChannelToUpdate = videoChannelToUpdate
72
73 this.form.patchValue({
74 'display-name': videoChannelToUpdate.displayName,
75 description: videoChannelToUpdate.description,
76 support: videoChannelToUpdate.support
77 })
78 },
79
80 err => this.error = err.message
81 )
82 })
83 }
84
85 ngOnDestroy () {
86 if (this.paramsSub) this.paramsSub.unsubscribe()
87 }
88
89 formValidated () {
90 this.error = undefined
91
92 const body = this.form.value
93 const videoChannelUpdate: VideoChannelUpdate = {
94 displayName: body['display-name'],
95 description: body.description || undefined,
96 support: body.support || undefined
97 }
98
99 this.videoChannelService.updateVideoChannel(this.videoChannelToUpdate.uuid, videoChannelUpdate).subscribe(
100 () => {
101 this.authService.refreshUserInformation()
102 this.notificationsService.success('Success', `Video channel ${videoChannelUpdate.displayName} updated.`)
103 this.router.navigate([ '/my-account', 'video-channels' ])
104 },
105
106 err => this.error = err.message
107 )
108 }
109
110 isCreation () {
111 return false
112 }
113
114 getFormButtonTitle () {
115 return 'Update'
116 }
117}
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
new file mode 100644
index 000000000..90c401bc5
--- /dev/null
+++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html
@@ -0,0 +1,30 @@
1<div class="video-channels-header">
2 <a class="create-button" routerLink="create">
3 <span class="icon icon-add"></span>
4 Create another video channel
5 </a>
6</div>
7
8<div class="video-channels">
9 <div *ngFor="let videoChannel of videoChannels" class="video-channel">
10 <a [routerLink]="[ '/video-channels', videoChannel.uuid ]">
11 <img [src]="videoChannel.avatarUrl" alt="Avatar" />
12 </a>
13
14 <div class="video-channel-info">
15 <a [routerLink]="[ '/video-channels', videoChannel.uuid ]" class="video-channel-names" title="Go to the channel">
16 <div class="video-channel-display-name">{{ videoChannel.displayName }}</div>
17 <!-- Hide the name for now, because it's an UUID not very friendly -->
18 <!--<div class="video-channel-name">{{ videoChannel.name }}</div>-->
19 </a>
20
21 <div class="video-channel-followers">{{ videoChannel.followersCount }} subscribers</div>
22 </div>
23
24 <div class="video-channel-buttons">
25 <my-delete-button (click)="deleteVideoChannel(videoChannel)"></my-delete-button>
26
27 <my-edit-button [routerLink]="[ 'update', videoChannel.uuid ]"></my-edit-button>
28 </div>
29 </div>
30</div>
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
new file mode 100644
index 000000000..bcb58eac2
--- /dev/null
+++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.scss
@@ -0,0 +1,72 @@
1@import '_variables';
2@import '_mixins';
3
4.create-button {
5 @include create-button;
6}
7
8/deep/ .action-button {
9 &.action-button-delete {
10 margin-right: 10px;
11 }
12}
13
14.video-channel {
15 display: flex;
16 min-height: 130px;
17 padding-bottom: 20px;
18 margin-bottom: 20px;
19 border-bottom: 1px solid #C6C6C6;
20
21 img {
22 @include avatar(80px);
23
24 margin-right: 10px;
25 }
26
27 .video-channel-info {
28 flex-grow: 1;
29
30 a.video-channel-names {
31 @include disable-default-a-behaviour;
32
33 display: flex;
34 color: #000;
35
36 .video-channel-display-name {
37 font-weight: $font-semibold;
38 font-size: 18px;
39 }
40
41 .video-channel-name {
42 font-size: 14px;
43 color: #777272;
44 }
45 }
46 }
47
48 .video-channel-buttons {
49 min-width: 190px;
50 }
51}
52
53.video-channels-header {
54 text-align: right;
55 margin: 20px 0 50px;
56}
57
58@media screen and (max-width: 800px) {
59 .video-channel {
60 flex-direction: column;
61 height: auto;
62 text-align: center;
63
64 img {
65 margin-right: 0;
66 }
67
68 .video-channel-buttons {
69 margin-top: 10px;
70 }
71 }
72}
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
new file mode 100644
index 000000000..eeaca11df
--- /dev/null
+++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts
@@ -0,0 +1,59 @@
1import { Component, OnInit } from '@angular/core'
2import { NotificationsService } from 'angular2-notifications'
3import 'rxjs/add/observable/from'
4import 'rxjs/add/operator/concatAll'
5import { AuthService } from '../../core/auth'
6import { ConfirmService } from '../../core/confirm'
7import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
8import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
9import { User } from '@app/shared'
10
11@Component({
12 selector: 'my-account-video-channels',
13 templateUrl: './my-account-video-channels.component.html',
14 styleUrls: [ './my-account-video-channels.component.scss' ]
15})
16export class MyAccountVideoChannelsComponent implements OnInit {
17 videoChannels: VideoChannel[] = []
18
19 private user: User
20
21 constructor (
22 private authService: AuthService,
23 private notificationsService: NotificationsService,
24 private confirmService: ConfirmService,
25 private videoChannelService: VideoChannelService
26 ) {}
27
28 ngOnInit () {
29 this.user = this.authService.getUser()
30
31 this.loadVideoChannels()
32 }
33
34 async deleteVideoChannel (videoChannel: VideoChannel) {
35 const res = await this.confirmService.confirmWithInput(
36 `Do you really want to delete ${videoChannel.displayName}? It will delete all videos uploaded in this channel too.`,
37 'Please type the name of the video channel to confirm',
38 videoChannel.displayName,
39 'Delete'
40 )
41 if (res === false) return
42
43 this.videoChannelService.removeVideoChannel(videoChannel)
44 .subscribe(
45 status => {
46 this.loadVideoChannels()
47 this.notificationsService.success('Success', `Video channel ${videoChannel.name} deleted.`)
48 },
49
50 error => this.notificationsService.error('Error', error.message)
51 )
52 }
53
54 private loadVideoChannels () {
55 this.authService.userInformationLoaded
56 .flatMap(() => this.videoChannelService.listAccountVideoChannels(this.user.account.id))
57 .subscribe(res => this.videoChannels = res.data)
58 }
59}
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
new file mode 100644
index 000000000..66ce3a77b
--- /dev/null
+++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.html
@@ -0,0 +1,45 @@
1<div *ngIf="pagination.totalItems === 0">No results.</div>
2
3<div
4 myInfiniteScroller
5 [pageHeight]="pageHeight"
6 (nearOfTop)="onNearOfTop()" (nearOfBottom)="onNearOfBottom()" (pageChanged)="onPageChanged($event)"
7 class="videos" #videosElement
8>
9 <div *ngFor="let videos of videoPages; let i = index" class="videos-page">
10 <div class="video" *ngFor="let video of videos; let j = index">
11 <div class="checkbox-container">
12 <input [id]="'video-check-' + video.id" type="checkbox" [(ngModel)]="checkedVideos[video.id]" />
13 <label [for]="'video-check-' + video.id"></label>
14 </div>
15
16 <my-video-thumbnail [video]="video"></my-video-thumbnail>
17
18 <div class="video-info">
19 <a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
20 <span class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
21 <div class="video-info-private">{{ video.privacy.label }}</div>
22 </div>
23
24 <!-- Display only once -->
25 <div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0 && j === 0">
26 <div class="action-selection-mode-child">
27 <span class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
28 Cancel
29 </span>
30
31 <span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()">
32 <span class="icon icon-delete-white"></span>
33 Delete
34 </span>
35 </div>
36 </div>
37
38 <div class="video-buttons" *ngIf="isInSelectionMode() === false">
39 <my-delete-button (click)="deleteVideo(video)"></my-delete-button>
40
41 <my-edit-button [routerLink]="[ '/videos', 'edit', video.uuid ]"></my-edit-button>
42 </div>
43 </div>
44 </div>
45</div>
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
new file mode 100644
index 000000000..f276ea389
--- /dev/null
+++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss
@@ -0,0 +1,114 @@
1@import '_variables';
2@import '_mixins';
3
4.action-selection-mode {
5 width: 174px;
6 display: flex;
7 justify-content: flex-end;
8
9 .action-selection-mode-child {
10 position: fixed;
11
12 .action-button {
13 display: inline-block;
14 }
15
16 .action-button-cancel-selection {
17 @include peertube-button;
18 @include grey-button;
19
20 margin-right: 10px;
21 }
22
23 .action-button-delete-selection {
24 @include peertube-button;
25 @include orange-button;
26 }
27
28 .icon.icon-delete-white {
29 @include icon(21px);
30
31 position: relative;
32 top: -2px;
33 background-image: url('../../../assets/images/global/delete-white.svg');
34 }
35 }
36}
37
38/deep/ .action-button {
39 &.action-button-delete {
40 margin-right: 10px;
41 }
42}
43
44.video {
45 display: flex;
46 min-height: 130px;
47 padding-bottom: 20px;
48 margin-bottom: 20px;
49 border-bottom: 1px solid #C6C6C6;
50
51 &:first-child {
52 margin-top: 47px;
53 }
54
55 .checkbox-container {
56 display: flex;
57 align-items: center;
58 margin-right: 20px;
59 margin-left: 12px;
60
61 input[type=checkbox] {
62 @include peertube-checkbox(2px);
63 }
64 }
65
66 my-video-thumbnail {
67 margin-right: 10px;
68 }
69
70 .video-info {
71 flex-grow: 1;
72
73 .video-info-name {
74 @include disable-default-a-behaviour;
75
76 color: #000;
77 display: block;
78 font-size: 16px;
79 font-weight: $font-semibold;
80 }
81
82 .video-info-date-views, .video-info-private {
83 font-size: 13px;
84
85 &.video-info-private {
86 font-weight: $font-semibold;
87 }
88 }
89 }
90
91 .video-buttons {
92 min-width: 190px;
93 }
94}
95
96@media screen and (max-width: 800px) {
97 .video {
98 flex-direction: column;
99 height: auto;
100 text-align: center;
101
102 input[type=checkbox] {
103 display: none;
104 }
105
106 my-video-thumbnail {
107 margin-right: 0;
108 }
109
110 .video-buttons {
111 margin-top: 10px;
112 }
113 }
114}
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
new file mode 100644
index 000000000..c1b53bcd5
--- /dev/null
+++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts
@@ -0,0 +1,131 @@
1import { Component, OnInit, OnDestroy } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router'
3import { Location } from '@angular/common'
4import { immutableAssign } from '@app/shared/misc/utils'
5import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
6import { NotificationsService } from 'angular2-notifications'
7import 'rxjs/add/observable/from'
8import 'rxjs/add/operator/concatAll'
9import { Observable } from 'rxjs/Observable'
10import { AuthService } from '../../core/auth'
11import { ConfirmService } from '../../core/confirm'
12import { AbstractVideoList } from '../../shared/video/abstract-video-list'
13import { Video } from '../../shared/video/video.model'
14import { VideoService } from '../../shared/video/video.service'
15
16@Component({
17 selector: 'my-account-videos',
18 templateUrl: './my-account-videos.component.html',
19 styleUrls: [ './my-account-videos.component.scss' ]
20})
21export class MyAccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy {
22 titlePage = 'My videos'
23 currentRoute = '/my-account/videos'
24 checkedVideos: { [ id: number ]: boolean } = {}
25 pagination: ComponentPagination = {
26 currentPage: 1,
27 itemsPerPage: 5,
28 totalItems: null
29 }
30
31 protected baseVideoWidth = -1
32 protected baseVideoHeight = 155
33
34 constructor (protected router: Router,
35 protected route: ActivatedRoute,
36 protected authService: AuthService,
37 protected notificationsService: NotificationsService,
38 protected confirmService: ConfirmService,
39 protected location: Location,
40 private videoService: VideoService) {
41 super()
42 }
43
44 ngOnInit () {
45 super.ngOnInit()
46 }
47
48 ngOnDestroy () {
49 super.ngOnDestroy()
50 }
51
52 abortSelectionMode () {
53 this.checkedVideos = {}
54 }
55
56 isInSelectionMode () {
57 return Object.keys(this.checkedVideos).some(k => this.checkedVideos[k] === true)
58 }
59
60 getVideosObservable (page: number) {
61 const newPagination = immutableAssign(this.pagination, { currentPage: page })
62
63 return this.videoService.getMyVideos(newPagination, this.sort)
64 }
65
66 generateSyndicationList () {
67 throw new Error('Method not implemented.')
68 }
69
70 async deleteSelectedVideos () {
71 const toDeleteVideosIds = Object.keys(this.checkedVideos)
72 .filter(k => this.checkedVideos[k] === true)
73 .map(k => parseInt(k, 10))
74
75 const res = await this.confirmService.confirm(`Do you really want to delete ${toDeleteVideosIds.length} videos?`, 'Delete')
76 if (res === false) return
77
78 const observables: Observable<any>[] = []
79 for (const videoId of toDeleteVideosIds) {
80 const o = this.videoService
81 .removeVideo(videoId)
82 .do(() => this.spliceVideosById(videoId))
83
84 observables.push(o)
85 }
86
87 Observable.from(observables)
88 .concatAll()
89 .subscribe(
90 res => {
91 this.notificationsService.success('Success', `${toDeleteVideosIds.length} videos deleted.`)
92 this.buildVideoPages()
93 },
94
95 err => this.notificationsService.error('Error', err.message)
96 )
97 }
98
99 async deleteVideo (video: Video) {
100 const res = await this.confirmService.confirm(`Do you really want to delete ${video.name}?`, 'Delete')
101 if (res === false) return
102
103 this.videoService.removeVideo(video.id)
104 .subscribe(
105 status => {
106 this.notificationsService.success('Success', `Video ${video.name} deleted.`)
107 this.spliceVideosById(video.id)
108 this.buildVideoPages()
109 },
110
111 error => this.notificationsService.error('Error', error.message)
112 )
113 }
114
115 protected buildVideoHeight () {
116 // In account videos, the video height is fixed
117 return this.baseVideoHeight
118 }
119
120 private spliceVideosById (id: number) {
121 for (const key of Object.keys(this.loadedPages)) {
122 const videos = this.loadedPages[key]
123 const index = videos.findIndex(v => v.id === id)
124
125 if (index !== -1) {
126 videos.splice(index, 1)
127 return
128 }
129 }
130 }
131}
diff --git a/client/src/app/+my-account/my-account.component.html b/client/src/app/+my-account/my-account.component.html
new file mode 100644
index 000000000..591d58cf9
--- /dev/null
+++ b/client/src/app/+my-account/my-account.component.html
@@ -0,0 +1,13 @@
1<div class="row">
2 <div class="sub-menu">
3 <a routerLink="/my-account/settings" routerLinkActive="active" class="title-page">My settings</a>
4
5 <a routerLink="/my-account/video-channels" routerLinkActive="active" class="title-page">My video channels</a>
6
7 <a routerLink="/my-account/videos" routerLinkActive="active" class="title-page">My videos</a>
8 </div>
9
10 <div class="margin-content">
11 <router-outlet></router-outlet>
12 </div>
13</div>
diff --git a/client/src/app/+my-account/my-account.component.ts b/client/src/app/+my-account/my-account.component.ts
new file mode 100644
index 000000000..7bb461d3c
--- /dev/null
+++ b/client/src/app/+my-account/my-account.component.ts
@@ -0,0 +1,7 @@
1import { Component } from '@angular/core'
2
3@Component({
4 selector: 'my-my-account',
5 templateUrl: './my-account.component.html'
6})
7export class MyAccountComponent {}
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts
new file mode 100644
index 000000000..7e6b8c03e
--- /dev/null
+++ b/client/src/app/+my-account/my-account.module.ts
@@ -0,0 +1,38 @@
1import { NgModule } from '@angular/core'
2import { SharedModule } from '../shared'
3import { MyAccountRoutingModule } from './my-account-routing.module'
4import { MyAccountChangePasswordComponent } from './my-account-settings/my-account-change-password/my-account-change-password.component'
5import { MyAccountVideoSettingsComponent } from './my-account-settings/my-account-video-settings/my-account-video-settings.component'
6import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component'
7import { MyAccountComponent } from './my-account.component'
8import { MyAccountVideosComponent } from './my-account-videos/my-account-videos.component'
9import { MyAccountProfileComponent } from '@app/+my-account/my-account-settings/my-account-profile/my-account-profile.component'
10import { MyAccountVideoChannelsComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channels.component'
11import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component'
12import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component'
13
14@NgModule({
15 imports: [
16 MyAccountRoutingModule,
17 SharedModule
18 ],
19
20 declarations: [
21 MyAccountComponent,
22 MyAccountSettingsComponent,
23 MyAccountChangePasswordComponent,
24 MyAccountVideoSettingsComponent,
25 MyAccountProfileComponent,
26 MyAccountVideosComponent,
27 MyAccountVideoChannelsComponent,
28 MyAccountVideoChannelCreateComponent,
29 MyAccountVideoChannelUpdateComponent
30 ],
31
32 exports: [
33 MyAccountComponent
34 ],
35
36 providers: []
37})
38export class MyAccountModule { }