Rename account module to my-account
authorChocobozzz <me@florianbigard.com>
Mon, 23 Apr 2018 14:16:05 +0000 (16:16 +0200)
committerChocobozzz <me@florianbigard.com>
Mon, 23 Apr 2018 14:16:05 +0000 (16:16 +0200)
41 files changed:
client/src/app/account/account-routing.module.ts [deleted file]
client/src/app/account/account-settings/account-change-password/account-change-password.component.html [deleted file]
client/src/app/account/account-settings/account-change-password/account-change-password.component.scss [deleted file]
client/src/app/account/account-settings/account-change-password/account-change-password.component.ts [deleted file]
client/src/app/account/account-settings/account-change-password/index.ts [deleted file]
client/src/app/account/account-settings/account-details/account-details.component.html [deleted file]
client/src/app/account/account-settings/account-details/account-details.component.scss [deleted file]
client/src/app/account/account-settings/account-details/account-details.component.ts [deleted file]
client/src/app/account/account-settings/account-details/index.ts [deleted file]
client/src/app/account/account-settings/account-settings.component.html [deleted file]
client/src/app/account/account-settings/account-settings.component.scss [deleted file]
client/src/app/account/account-settings/account-settings.component.ts [deleted file]
client/src/app/account/account-videos/account-videos.component.html [deleted file]
client/src/app/account/account-videos/account-videos.component.scss [deleted file]
client/src/app/account/account-videos/account-videos.component.ts [deleted file]
client/src/app/account/account.component.html [deleted file]
client/src/app/account/account.component.scss [deleted file]
client/src/app/account/account.component.ts [deleted file]
client/src/app/account/account.module.ts [deleted file]
client/src/app/account/index.ts [deleted file]
client/src/app/app.module.ts
client/src/app/menu/menu.component.html
client/src/app/my-account/index.ts [new file with mode: 0644]
client/src/app/my-account/my-account-routing.module.ts [new file with mode: 0644]
client/src/app/my-account/my-account-settings/my-account-change-password/index.ts [new file with mode: 0644]
client/src/app/my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html [new file with mode: 0644]
client/src/app/my-account/my-account-settings/my-account-change-password/my-account-change-password.component.scss [new file with mode: 0644]
client/src/app/my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts [new file with mode: 0644]
client/src/app/my-account/my-account-settings/my-account-details/index.ts [new file with mode: 0644]
client/src/app/my-account/my-account-settings/my-account-details/my-account-details.component.html [new file with mode: 0644]
client/src/app/my-account/my-account-settings/my-account-details/my-account-details.component.scss [new file with mode: 0644]
client/src/app/my-account/my-account-settings/my-account-details/my-account-details.component.ts [new file with mode: 0644]
client/src/app/my-account/my-account-settings/my-account-settings.component.html [new file with mode: 0644]
client/src/app/my-account/my-account-settings/my-account-settings.component.scss [new file with mode: 0644]
client/src/app/my-account/my-account-settings/my-account-settings.component.ts [new file with mode: 0644]
client/src/app/my-account/my-account-videos/my-account-videos.component.html [new file with mode: 0644]
client/src/app/my-account/my-account-videos/my-account-videos.component.scss [new file with mode: 0644]
client/src/app/my-account/my-account-videos/my-account-videos.component.ts [new file with mode: 0644]
client/src/app/my-account/my-account.component.html [new file with mode: 0644]
client/src/app/my-account/my-account.component.ts [new file with mode: 0644]
client/src/app/my-account/my-account.module.ts [new file with mode: 0644]

diff --git a/client/src/app/account/account-routing.module.ts b/client/src/app/account/account-routing.module.ts
deleted file mode 100644 (file)
index 070b9b5..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-import { NgModule } from '@angular/core'
-import { RouterModule, Routes } from '@angular/router'
-
-import { MetaGuard } from '@ngx-meta/core'
-
-import { LoginGuard } from '../core'
-import { AccountComponent } from './account.component'
-import { AccountSettingsComponent } from './account-settings/account-settings.component'
-import { AccountVideosComponent } from './account-videos/account-videos.component'
-
-const accountRoutes: Routes = [
-  {
-    path: 'account',
-    component: AccountComponent,
-    canActivateChild: [ MetaGuard, LoginGuard ],
-    children: [
-      {
-        path: 'settings',
-        component: AccountSettingsComponent,
-        data: {
-          meta: {
-            title: 'Account settings'
-          }
-        }
-      },
-      {
-        path: 'videos',
-        component: AccountVideosComponent,
-        data: {
-          meta: {
-            title: 'Account videos'
-          }
-        }
-      }
-    ]
-  }
-]
-
-@NgModule({
-  imports: [ RouterModule.forChild(accountRoutes) ],
-  exports: [ RouterModule ]
-})
-export class AccountRoutingModule {}
diff --git a/client/src/app/account/account-settings/account-change-password/account-change-password.component.html b/client/src/app/account/account-settings/account-change-password/account-change-password.component.html
deleted file mode 100644 (file)
index b0e3cad..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
-
-<form role="form" (ngSubmit)="changePassword()" [formGroup]="form">
-
-  <label for="new-password">Change password</label>
-  <input
-    type="password" id="new-password" placeholder="New password"
-    formControlName="new-password" [ngClass]="{ 'input-error': formErrors['new-password'] }"
-  >
-  <div *ngIf="formErrors['new-password']" class="form-error">
-    {{ formErrors['new-password'] }}
-  </div>
-
-  <input
-    type="password" id="new-confirmed-password" placeholder="Confirm new password"
-    formControlName="new-confirmed-password"
-  >
-
-  <input type="submit" value="Change password" [disabled]="!form.valid">
-</form>
diff --git a/client/src/app/account/account-settings/account-change-password/account-change-password.component.scss b/client/src/app/account/account-settings/account-change-password/account-change-password.component.scss
deleted file mode 100644 (file)
index f8279ff..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-@import '_variables';
-@import '_mixins';
-
-input[type=password] {
-  @include peertube-input-text(340px);
-  display: block;
-
-  &#new-confirmed-password {
-    margin-top: 15px;
-  }
-}
-
-input[type=submit] {
-  @include peertube-button;
-  @include orange-button;
-
-  margin-top: 15px;
-}
-
diff --git a/client/src/app/account/account-settings/account-change-password/account-change-password.component.ts b/client/src/app/account/account-settings/account-change-password/account-change-password.component.ts
deleted file mode 100644 (file)
index 8979e17..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-import { Component, OnInit } from '@angular/core'
-import { FormBuilder, FormGroup } from '@angular/forms'
-import { NotificationsService } from 'angular2-notifications'
-import { FormReactive, USER_PASSWORD, UserService } from '../../../shared'
-
-@Component({
-  selector: 'my-account-change-password',
-  templateUrl: './account-change-password.component.html',
-  styleUrls: [ './account-change-password.component.scss' ]
-})
-export class AccountChangePasswordComponent extends FormReactive implements OnInit {
-  error: string = null
-
-  form: FormGroup
-  formErrors = {
-    'new-password': '',
-    'new-confirmed-password': ''
-  }
-  validationMessages = {
-    'new-password': USER_PASSWORD.MESSAGES,
-    'new-confirmed-password': USER_PASSWORD.MESSAGES
-  }
-
-  constructor (
-    private formBuilder: FormBuilder,
-    private notificationsService: NotificationsService,
-    private userService: UserService
-  ) {
-    super()
-  }
-
-  buildForm () {
-    this.form = this.formBuilder.group({
-      'new-password': [ '', USER_PASSWORD.VALIDATORS ],
-      'new-confirmed-password': [ '', USER_PASSWORD.VALIDATORS ]
-    })
-
-    this.form.valueChanges.subscribe(data => this.onValueChanged(data))
-  }
-
-  ngOnInit () {
-    this.buildForm()
-  }
-
-  changePassword () {
-    const newPassword = this.form.value['new-password']
-    const newConfirmedPassword = this.form.value['new-confirmed-password']
-
-    this.error = null
-
-    if (newPassword !== newConfirmedPassword) {
-      this.error = 'The new password and the confirmed password do not correspond.'
-      return
-    }
-
-    this.userService.changePassword(newPassword).subscribe(
-      () => this.notificationsService.success('Success', 'Password updated.'),
-
-      err => this.error = err.message
-    )
-  }
-}
diff --git a/client/src/app/account/account-settings/account-change-password/index.ts b/client/src/app/account/account-settings/account-change-password/index.ts
deleted file mode 100644 (file)
index 44c330b..0000000
+++ /dev/null
@@ -1 +0,0 @@
-export * from './account-change-password.component'
diff --git a/client/src/app/account/account-settings/account-details/account-details.component.html b/client/src/app/account/account-settings/account-details/account-details.component.html
deleted file mode 100644 (file)
index 0e8598e..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<form role="form" (ngSubmit)="updateDetails()" [formGroup]="form">
-  <div class="form-group">
-    <label for="nsfwPolicy">Default policy on videos containing sensitive content</label>
-    <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>
-
-    <div class="peertube-select-container">
-      <select id="nsfwPolicy" formControlName="nsfwPolicy">
-        <option value="do_not_list">Do not list</option>
-        <option value="blur">Blur thumbnails</option>
-        <option value="display">Display</option>
-      </select>
-    </div>
-  </div>
-
-  <div class="form-group">
-    <input
-      type="checkbox" id="autoPlayVideo"
-      formControlName="autoPlayVideo"
-    >
-    <label for="autoPlayVideo"></label>
-    <label for="autoPlayVideo">Automatically plays video</label>
-  </div>
-
-  <input type="submit" value="Save" [disabled]="!form.valid">
-</form>
diff --git a/client/src/app/account/account-settings/account-details/account-details.component.scss b/client/src/app/account/account-settings/account-details/account-details.component.scss
deleted file mode 100644 (file)
index ed59e46..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-@import '_variables';
-@import '_mixins';
-
-input[type=checkbox] {
-  @include peertube-checkbox(1px);
-}
-
-input[type=submit] {
-  @include peertube-button;
-  @include orange-button;
-
-  display: block;
-  margin-top: 15px;
-}
-
-.peertube-select-container {
-  @include peertube-select-container(340px);
-
-  margin-bottom: 30px;
-}
\ No newline at end of file
diff --git a/client/src/app/account/account-settings/account-details/account-details.component.ts b/client/src/app/account/account-settings/account-details/account-details.component.ts
deleted file mode 100644 (file)
index de21371..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-import { Component, Input, OnInit } from '@angular/core'
-import { FormBuilder, FormGroup } from '@angular/forms'
-import { NotificationsService } from 'angular2-notifications'
-import { UserUpdateMe } from '../../../../../../shared'
-import { AuthService } from '../../../core'
-import { FormReactive, User, UserService } from '../../../shared'
-
-@Component({
-  selector: 'my-account-details',
-  templateUrl: './account-details.component.html',
-  styleUrls: [ './account-details.component.scss' ]
-})
-
-export class AccountDetailsComponent extends FormReactive implements OnInit {
-  @Input() user: User = null
-
-  form: FormGroup
-  formErrors = {}
-  validationMessages = {}
-
-  constructor (
-    private authService: AuthService,
-    private formBuilder: FormBuilder,
-    private notificationsService: NotificationsService,
-    private userService: UserService
-  ) {
-    super()
-  }
-
-  buildForm () {
-    this.form = this.formBuilder.group({
-      nsfwPolicy: [ this.user.nsfwPolicy ],
-      autoPlayVideo: [ this.user.autoPlayVideo ]
-    })
-
-    this.form.valueChanges.subscribe(data => this.onValueChanged(data))
-  }
-
-  ngOnInit () {
-    this.buildForm()
-  }
-
-  updateDetails () {
-    const nsfwPolicy = this.form.value['nsfwPolicy']
-    const autoPlayVideo = this.form.value['autoPlayVideo']
-    const details: UserUpdateMe = {
-      nsfwPolicy,
-      autoPlayVideo
-    }
-
-    this.userService.updateMyDetails(details).subscribe(
-      () => {
-        this.notificationsService.success('Success', 'Information updated.')
-
-        this.authService.refreshUserInformation()
-      },
-
-      err => this.notificationsService.error('Error', err.message)
-    )
-  }
-}
diff --git a/client/src/app/account/account-settings/account-details/index.ts b/client/src/app/account/account-settings/account-details/index.ts
deleted file mode 100644 (file)
index 4829f60..0000000
+++ /dev/null
@@ -1 +0,0 @@
-export * from './account-details.component'
diff --git a/client/src/app/account/account-settings/account-settings.component.html b/client/src/app/account/account-settings/account-settings.component.html
deleted file mode 100644 (file)
index 7ae27dc..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-<div class="user">
-  <img [src]="getAvatarUrl()" alt="Avatar" />
-
-  <div class="user-info">
-    <div class="user-info-username">{{ user.username }}</div>
-    <div class="user-info-followers">{{ user.account?.followersCount }} subscribers</div>
-  </div>
-</div>
-
-<div class="button-file">
-  <span>Change your avatar</span>
-  <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="changeAvatar()" />
-</div>
-<div class="file-max-size">(extensions: {{ avatarExtensions }}, max size: {{ maxAvatarSize | bytes }})</div>
-
-<div class="user-quota">
-  <span class="user-quota-label">Video quota:</span> {{ userVideoQuotaUsed | bytes: 0 }} / {{ userVideoQuota }}
-</div>
-
-<div class="account-title">Account settings</div>
-<my-account-change-password></my-account-change-password>
-
-<div class="account-title">Video settings</div>
-<my-account-details [user]="user"></my-account-details>
diff --git a/client/src/app/account/account-settings/account-settings.component.scss b/client/src/app/account/account-settings/account-settings.component.scss
deleted file mode 100644 (file)
index 1cc00ca..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-@import '_variables';
-@import '_mixins';
-
-.user {
-  display: flex;
-
-  img {
-    @include avatar(50px);
-
-    margin-right: 15px;
-  }
-
-  .user-info {
-    .user-info-username {
-      font-size: 20px;
-      font-weight: $font-bold;
-    }
-
-    .user-info-followers {
-      font-size: 15px;
-    }
-  }
-}
-
-.button-file {
-  @include peertube-button-file(160px);
-
-  margin-top: 10px;
-  margin-bottom: 5px;
-}
-
-.file-max-size {
-  display: inline-block;
-  font-size: 13px;
-
-  position: relative;
-  top: -10px;
-}
-
-.user-quota {
-  font-size: 15px;
-  margin-top: 20px;
-
-  .user-quota-label {
-    font-weight: $font-semibold;
-  }
-}
-
-.account-title {
-  text-transform: uppercase;
-  color: $orange-color;
-  font-weight: $font-bold;
-  font-size: 13px;
-  margin-top: 55px;
-  margin-bottom: 30px;
-}
diff --git a/client/src/app/account/account-settings/account-settings.component.ts b/client/src/app/account/account-settings/account-settings.component.ts
deleted file mode 100644 (file)
index 5246078..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-import { Component, OnInit, ViewChild } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
-import { BytesPipe } from 'ngx-pipes'
-import { AuthService } from '../../core'
-import { ServerService } from '../../core/server'
-import { User } from '../../shared'
-import { UserService } from '../../shared/users'
-
-@Component({
-  selector: 'my-account-settings',
-  templateUrl: './account-settings.component.html',
-  styleUrls: [ './account-settings.component.scss' ]
-})
-export class AccountSettingsComponent implements OnInit {
-  @ViewChild('avatarfileInput') avatarfileInput
-
-  user: User = null
-  userVideoQuota = '0'
-  userVideoQuotaUsed = 0
-
-  constructor (
-    private userService: UserService,
-    private authService: AuthService,
-    private serverService: ServerService,
-    private notificationsService: NotificationsService
-  ) {}
-
-  ngOnInit () {
-    this.user = this.authService.getUser()
-
-    this.authService.userInformationLoaded.subscribe(
-      () => {
-        if (this.user.videoQuota !== -1) {
-          this.userVideoQuota = new BytesPipe().transform(this.user.videoQuota, 0).toString()
-        } else {
-          this.userVideoQuota = 'Unlimited'
-        }
-      }
-    )
-
-    this.userService.getMyVideoQuotaUsed()
-      .subscribe(data => this.userVideoQuotaUsed = data.videoQuotaUsed)
-  }
-
-  getAvatarUrl () {
-    return this.user.getAvatarUrl()
-  }
-
-  changeAvatar () {
-    const avatarfile = this.avatarfileInput.nativeElement.files[ 0 ]
-
-    const formData = new FormData()
-    formData.append('avatarfile', avatarfile)
-
-    this.userService.changeAvatar(formData)
-      .subscribe(
-        data => {
-          this.notificationsService.success('Success', 'Avatar changed.')
-
-          this.user.account.avatar = data.avatar
-        },
-
-        err => this.notificationsService.error('Error', err.message)
-      )
-  }
-
-  get maxAvatarSize () {
-    return this.serverService.getConfig().avatar.file.size.max
-  }
-
-  get avatarExtensions () {
-    return this.serverService.getConfig().avatar.file.extensions.join(',')
-  }
-}
diff --git a/client/src/app/account/account-videos/account-videos.component.html b/client/src/app/account/account-videos/account-videos.component.html
deleted file mode 100644 (file)
index 66ce3a7..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-<div *ngIf="pagination.totalItems === 0">No results.</div>
-
-<div
-  myInfiniteScroller
-  [pageHeight]="pageHeight"
-  (nearOfTop)="onNearOfTop()" (nearOfBottom)="onNearOfBottom()" (pageChanged)="onPageChanged($event)"
-  class="videos" #videosElement
->
-  <div *ngFor="let videos of videoPages; let i = index" class="videos-page">
-    <div class="video" *ngFor="let video of videos; let j = index">
-      <div class="checkbox-container">
-        <input [id]="'video-check-' + video.id" type="checkbox" [(ngModel)]="checkedVideos[video.id]" />
-        <label [for]="'video-check-' + video.id"></label>
-      </div>
-
-      <my-video-thumbnail [video]="video"></my-video-thumbnail>
-
-      <div class="video-info">
-        <a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
-        <span class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
-        <div class="video-info-private">{{ video.privacy.label }}</div>
-      </div>
-
-      <!-- Display only once -->
-      <div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0 && j === 0">
-        <div class="action-selection-mode-child">
-          <span class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
-            Cancel
-          </span>
-
-          <span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()">
-            <span class="icon icon-delete-white"></span>
-            Delete
-          </span>
-        </div>
-      </div>
-
-      <div class="video-buttons" *ngIf="isInSelectionMode() === false">
-        <my-delete-button (click)="deleteVideo(video)"></my-delete-button>
-
-        <my-edit-button [routerLink]="[ '/videos', 'edit', video.uuid ]"></my-edit-button>
-      </div>
-    </div>
-  </div>
-</div>
diff --git a/client/src/app/account/account-videos/account-videos.component.scss b/client/src/app/account/account-videos/account-videos.component.scss
deleted file mode 100644 (file)
index f276ea3..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-@import '_variables';
-@import '_mixins';
-
-.action-selection-mode {
-  width: 174px;
-  display: flex;
-  justify-content: flex-end;
-
-  .action-selection-mode-child {
-    position: fixed;
-
-    .action-button {
-      display: inline-block;
-    }
-
-    .action-button-cancel-selection {
-      @include peertube-button;
-      @include grey-button;
-
-      margin-right: 10px;
-    }
-
-    .action-button-delete-selection {
-      @include peertube-button;
-      @include orange-button;
-    }
-
-    .icon.icon-delete-white {
-      @include icon(21px);
-
-      position: relative;
-      top: -2px;
-      background-image: url('../../../assets/images/global/delete-white.svg');
-    }
-  }
-}
-
-/deep/ .action-button {
-  &.action-button-delete {
-    margin-right: 10px;
-  }
-}
-
-.video {
-  display: flex;
-  min-height: 130px;
-  padding-bottom: 20px;
-  margin-bottom: 20px;
-  border-bottom: 1px solid #C6C6C6;
-
-  &:first-child {
-    margin-top: 47px;
-  }
-
-  .checkbox-container {
-    display: flex;
-    align-items: center;
-    margin-right: 20px;
-    margin-left: 12px;
-
-    input[type=checkbox] {
-      @include peertube-checkbox(2px);
-    }
-  }
-
-  my-video-thumbnail {
-    margin-right: 10px;
-  }
-
-  .video-info {
-    flex-grow: 1;
-
-    .video-info-name {
-      @include disable-default-a-behaviour;
-
-      color: #000;
-      display: block;
-      font-size: 16px;
-      font-weight: $font-semibold;
-    }
-
-    .video-info-date-views, .video-info-private {
-      font-size: 13px;
-
-      &.video-info-private {
-        font-weight: $font-semibold;
-      }
-    }
-  }
-
-  .video-buttons {
-    min-width: 190px;
-  }
-}
-
-@media screen and (max-width: 800px) {
-  .video {
-    flex-direction: column;
-    height: auto;
-    text-align: center;
-
-    input[type=checkbox] {
-      display: none;
-    }
-
-    my-video-thumbnail {
-      margin-right: 0;
-    }
-
-    .video-buttons {
-      margin-top: 10px;
-    }
-  }
-}
diff --git a/client/src/app/account/account-videos/account-videos.component.ts b/client/src/app/account/account-videos/account-videos.component.ts
deleted file mode 100644 (file)
index 91bc1b6..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-import { Component, OnInit, OnDestroy } from '@angular/core'
-import { ActivatedRoute, Router } from '@angular/router'
-import { Location } from '@angular/common'
-import { immutableAssign } from '@app/shared/misc/utils'
-import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
-import { NotificationsService } from 'angular2-notifications'
-import 'rxjs/add/observable/from'
-import 'rxjs/add/operator/concatAll'
-import { Observable } from 'rxjs/Observable'
-import { AuthService } from '../../core/auth'
-import { ConfirmService } from '../../core/confirm'
-import { AbstractVideoList } from '../../shared/video/abstract-video-list'
-import { Video } from '../../shared/video/video.model'
-import { VideoService } from '../../shared/video/video.service'
-
-@Component({
-  selector: 'my-account-videos',
-  templateUrl: './account-videos.component.html',
-  styleUrls: [ './account-videos.component.scss' ]
-})
-export class AccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy {
-  titlePage = 'My videos'
-  currentRoute = '/account/videos'
-  checkedVideos: { [ id: number ]: boolean } = {}
-  pagination: ComponentPagination = {
-    currentPage: 1,
-    itemsPerPage: 5,
-    totalItems: null
-  }
-
-  protected baseVideoWidth = -1
-  protected baseVideoHeight = 155
-
-  constructor (protected router: Router,
-               protected route: ActivatedRoute,
-               protected authService: AuthService,
-               protected notificationsService: NotificationsService,
-               protected confirmService: ConfirmService,
-               protected location: Location,
-               private videoService: VideoService) {
-    super()
-  }
-
-  ngOnInit () {
-    super.ngOnInit()
-
-    // this.generateSyndicationList()
-  }
-
-  ngOnDestroy () {
-    super.ngOnDestroy()
-  }
-
-  abortSelectionMode () {
-    this.checkedVideos = {}
-  }
-
-  isInSelectionMode () {
-    return Object.keys(this.checkedVideos).some(k => this.checkedVideos[k] === true)
-  }
-
-  getVideosObservable (page: number) {
-    const newPagination = immutableAssign(this.pagination, { currentPage: page })
-
-    return this.videoService.getMyVideos(newPagination, this.sort)
-  }
-
-  generateSyndicationList () {
-    throw new Error('Method not implemented.')
-  }
-
-  async deleteSelectedVideos () {
-    const toDeleteVideosIds = Object.keys(this.checkedVideos)
-      .filter(k => this.checkedVideos[k] === true)
-      .map(k => parseInt(k, 10))
-
-    const res = await this.confirmService.confirm(`Do you really want to delete ${toDeleteVideosIds.length} videos?`, 'Delete')
-    if (res === false) return
-
-    const observables: Observable<any>[] = []
-    for (const videoId of toDeleteVideosIds) {
-      const o = this.videoService
-        .removeVideo(videoId)
-        .do(() => this.spliceVideosById(videoId))
-
-      observables.push(o)
-    }
-
-    Observable.from(observables)
-      .concatAll()
-      .subscribe(
-        res => {
-          this.notificationsService.success('Success', `${toDeleteVideosIds.length} videos deleted.`)
-          this.buildVideoPages()
-        },
-
-        err => this.notificationsService.error('Error', err.message)
-      )
-  }
-
-  async deleteVideo (video: Video) {
-    const res = await this.confirmService.confirm(`Do you really want to delete ${video.name}?`, 'Delete')
-    if (res === false) return
-
-    this.videoService.removeVideo(video.id)
-      .subscribe(
-        status => {
-          this.notificationsService.success('Success', `Video ${video.name} deleted.`)
-          this.spliceVideosById(video.id)
-          this.buildVideoPages()
-        },
-
-        error => this.notificationsService.error('Error', error.message)
-      )
-  }
-
-  protected buildVideoHeight () {
-    // In account videos, the video height is fixed
-    return this.baseVideoHeight
-  }
-
-  private spliceVideosById (id: number) {
-    for (const key of Object.keys(this.loadedPages)) {
-      const videos = this.loadedPages[key]
-      const index = videos.findIndex(v => v.id === id)
-
-      if (index !== -1) {
-        videos.splice(index, 1)
-        return
-      }
-    }
-  }
-}
diff --git a/client/src/app/account/account.component.html b/client/src/app/account/account.component.html
deleted file mode 100644 (file)
index d82a4ca..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<div class="row">
-  <div class="sub-menu">
-    <a routerLink="/account/settings" routerLinkActive="active" class="title-page">My account</a>
-
-    <a routerLink="/account/videos" routerLinkActive="active" class="title-page">My videos</a>
-  </div>
-
-  <div class="margin-content">
-    <router-outlet></router-outlet>
-  </div>
-</div>
diff --git a/client/src/app/account/account.component.scss b/client/src/app/account/account.component.scss
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/client/src/app/account/account.component.ts b/client/src/app/account/account.component.ts
deleted file mode 100644 (file)
index 3d3677a..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-import { Component } from '@angular/core'
-
-@Component({
-  selector: 'my-account',
-  templateUrl: './account.component.html',
-  styleUrls: [ './account.component.scss' ]
-})
-export class AccountComponent {}
diff --git a/client/src/app/account/account.module.ts b/client/src/app/account/account.module.ts
deleted file mode 100644 (file)
index 2299c19..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-import { NgModule } from '@angular/core'
-import { SharedModule } from '../shared'
-import { AccountRoutingModule } from './account-routing.module'
-import { AccountChangePasswordComponent } from './account-settings/account-change-password/account-change-password.component'
-import { AccountDetailsComponent } from './account-settings/account-details/account-details.component'
-import { AccountSettingsComponent } from './account-settings/account-settings.component'
-import { AccountComponent } from './account.component'
-import { AccountVideosComponent } from './account-videos/account-videos.component'
-
-@NgModule({
-  imports: [
-    AccountRoutingModule,
-    SharedModule
-  ],
-
-  declarations: [
-    AccountComponent,
-    AccountSettingsComponent,
-    AccountChangePasswordComponent,
-    AccountDetailsComponent,
-    AccountVideosComponent
-  ],
-
-  exports: [
-    AccountComponent
-  ],
-
-  providers: []
-})
-export class AccountModule { }
diff --git a/client/src/app/account/index.ts b/client/src/app/account/index.ts
deleted file mode 100644 (file)
index dc56ffd..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from './account-routing.module'
-export * from './account.component'
-export * from './account.module'
index 77d68a4ec1ad5a236b16d684a2b2f56b00aade64..c22632fb85d1714608e49be6232ac0db1fdc55d4 100644 (file)
@@ -6,7 +6,7 @@ import { ResetPasswordModule } from '@app/reset-password'
 
 import { MetaLoader, MetaModule, MetaStaticLoader, PageTitlePositioning } from '@ngx-meta/core'
 
-import { AccountModule } from './account'
+import { MyAccountModule } from './my-account'
 
 import { AppRoutingModule } from './app-routing.module'
 import { AppComponent } from './app.component'
@@ -46,7 +46,7 @@ export function metaFactory (serverService: ServerService): MetaLoader {
 
     AppRoutingModule,
 
-    AccountModule,
+    MyAccountModule,
     CoreModule,
     LoginModule,
     ResetPasswordModule,
index d827a4dd432c98f41a17b5cf4600e8da1e4a411e..832cd9e78fdfd1bf9a953bb7f2dd6db4350f4610 100644 (file)
@@ -5,7 +5,7 @@
     </a>
 
     <div class="logged-in-info">
-      <a routerLink="/account/settings" class="logged-in-username">{{ user.username }}</a>
+      <a routerLink="/my-account/settings" class="logged-in-username">{{ user.username }}</a>
       <div class="logged-in-email">{{ user.email }}</div>
     </div>
 
@@ -14,7 +14,7 @@
 
       <ul *dropdownMenu class="dropdown-menu">
         <li>
-          <a i18n routerLink="/account/settings" class="dropdown-item" title="My account">
+          <a i18n routerLink="/my-account/settings" class="dropdown-item" title="My account">
             My account
           </a>
 
diff --git a/client/src/app/my-account/index.ts b/client/src/app/my-account/index.ts
new file mode 100644 (file)
index 0000000..3df96dd
--- /dev/null
@@ -0,0 +1,3 @@
+export * from './my-account-routing.module'
+export * from './my-account.component'
+export * 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 (file)
index 0000000..5a61db4
--- /dev/null
@@ -0,0 +1,41 @@
+import { NgModule } from '@angular/core'
+import { RouterModule, Routes } from '@angular/router'
+import { MetaGuard } from '@ngx-meta/core'
+import { LoginGuard } from '../core'
+import { MyAccountComponent } from './my-account.component'
+import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component'
+import { MyAccountVideosComponent } from './my-account-videos/my-account-videos.component'
+
+const myAccountRoutes: Routes = [
+  {
+    path: 'my-account',
+    component: MyAccountComponent,
+    canActivateChild: [ MetaGuard, LoginGuard ],
+    children: [
+      {
+        path: 'settings',
+        component: MyAccountSettingsComponent,
+        data: {
+          meta: {
+            title: 'Account settings'
+          }
+        }
+      },
+      {
+        path: 'videos',
+        component: MyAccountVideosComponent,
+        data: {
+          meta: {
+            title: 'Account videos'
+          }
+        }
+      }
+    ]
+  }
+]
+
+@NgModule({
+  imports: [ RouterModule.forChild(myAccountRoutes) ],
+  exports: [ RouterModule ]
+})
+export 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 (file)
index 0000000..644047c
--- /dev/null
@@ -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 (file)
index 0000000..b0e3cad
--- /dev/null
@@ -0,0 +1,20 @@
+<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
+
+<form role="form" (ngSubmit)="changePassword()" [formGroup]="form">
+
+  <label for="new-password">Change password</label>
+  <input
+    type="password" id="new-password" placeholder="New password"
+    formControlName="new-password" [ngClass]="{ 'input-error': formErrors['new-password'] }"
+  >
+  <div *ngIf="formErrors['new-password']" class="form-error">
+    {{ formErrors['new-password'] }}
+  </div>
+
+  <input
+    type="password" id="new-confirmed-password" placeholder="Confirm new password"
+    formControlName="new-confirmed-password"
+  >
+
+  <input type="submit" value="Change password" [disabled]="!form.valid">
+</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 (file)
index 0000000..f8279ff
--- /dev/null
@@ -0,0 +1,19 @@
+@import '_variables';
+@import '_mixins';
+
+input[type=password] {
+  @include peertube-input-text(340px);
+  display: block;
+
+  &#new-confirmed-password {
+    margin-top: 15px;
+  }
+}
+
+input[type=submit] {
+  @include peertube-button;
+  @include orange-button;
+
+  margin-top: 15px;
+}
+
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 (file)
index 0000000..80af668
--- /dev/null
@@ -0,0 +1,62 @@
+import { Component, OnInit } from '@angular/core'
+import { FormBuilder, FormGroup } from '@angular/forms'
+import { NotificationsService } from 'angular2-notifications'
+import { FormReactive, USER_PASSWORD, UserService } from '../../../shared'
+
+@Component({
+  selector: 'my-account-change-password',
+  templateUrl: './my-account-change-password.component.html',
+  styleUrls: [ './my-account-change-password.component.scss' ]
+})
+export class MyAccountChangePasswordComponent extends FormReactive implements OnInit {
+  error: string = null
+
+  form: FormGroup
+  formErrors = {
+    'new-password': '',
+    'new-confirmed-password': ''
+  }
+  validationMessages = {
+    'new-password': USER_PASSWORD.MESSAGES,
+    'new-confirmed-password': USER_PASSWORD.MESSAGES
+  }
+
+  constructor (
+    private formBuilder: FormBuilder,
+    private notificationsService: NotificationsService,
+    private userService: UserService
+  ) {
+    super()
+  }
+
+  buildForm () {
+    this.form = this.formBuilder.group({
+      'new-password': [ '', USER_PASSWORD.VALIDATORS ],
+      'new-confirmed-password': [ '', USER_PASSWORD.VALIDATORS ]
+    })
+
+    this.form.valueChanges.subscribe(data => this.onValueChanged(data))
+  }
+
+  ngOnInit () {
+    this.buildForm()
+  }
+
+  changePassword () {
+    const newPassword = this.form.value['new-password']
+    const newConfirmedPassword = this.form.value['new-confirmed-password']
+
+    this.error = null
+
+    if (newPassword !== newConfirmedPassword) {
+      this.error = 'The new password and the confirmed password do not correspond.'
+      return
+    }
+
+    this.userService.changePassword(newPassword).subscribe(
+      () => this.notificationsService.success('Success', 'Password updated.'),
+
+      err => this.error = err.message
+    )
+  }
+}
diff --git a/client/src/app/my-account/my-account-settings/my-account-details/index.ts b/client/src/app/my-account/my-account-settings/my-account-details/index.ts
new file mode 100644 (file)
index 0000000..b7f58e3
--- /dev/null
@@ -0,0 +1 @@
+export * from './my-account-details.component'
diff --git a/client/src/app/my-account/my-account-settings/my-account-details/my-account-details.component.html b/client/src/app/my-account/my-account-settings/my-account-details/my-account-details.component.html
new file mode 100644 (file)
index 0000000..0e8598e
--- /dev/null
@@ -0,0 +1,25 @@
+<form role="form" (ngSubmit)="updateDetails()" [formGroup]="form">
+  <div class="form-group">
+    <label for="nsfwPolicy">Default policy on videos containing sensitive content</label>
+    <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>
+
+    <div class="peertube-select-container">
+      <select id="nsfwPolicy" formControlName="nsfwPolicy">
+        <option value="do_not_list">Do not list</option>
+        <option value="blur">Blur thumbnails</option>
+        <option value="display">Display</option>
+      </select>
+    </div>
+  </div>
+
+  <div class="form-group">
+    <input
+      type="checkbox" id="autoPlayVideo"
+      formControlName="autoPlayVideo"
+    >
+    <label for="autoPlayVideo"></label>
+    <label for="autoPlayVideo">Automatically plays video</label>
+  </div>
+
+  <input type="submit" value="Save" [disabled]="!form.valid">
+</form>
diff --git a/client/src/app/my-account/my-account-settings/my-account-details/my-account-details.component.scss b/client/src/app/my-account/my-account-settings/my-account-details/my-account-details.component.scss
new file mode 100644 (file)
index 0000000..ed59e46
--- /dev/null
@@ -0,0 +1,20 @@
+@import '_variables';
+@import '_mixins';
+
+input[type=checkbox] {
+  @include peertube-checkbox(1px);
+}
+
+input[type=submit] {
+  @include peertube-button;
+  @include orange-button;
+
+  display: block;
+  margin-top: 15px;
+}
+
+.peertube-select-container {
+  @include peertube-select-container(340px);
+
+  margin-bottom: 30px;
+}
\ No newline at end of file
diff --git a/client/src/app/my-account/my-account-settings/my-account-details/my-account-details.component.ts b/client/src/app/my-account/my-account-settings/my-account-details/my-account-details.component.ts
new file mode 100644 (file)
index 0000000..4c14565
--- /dev/null
@@ -0,0 +1,60 @@
+import { Component, Input, OnInit } from '@angular/core'
+import { FormBuilder, FormGroup } from '@angular/forms'
+import { NotificationsService } from 'angular2-notifications'
+import { UserUpdateMe } from '../../../../../../shared'
+import { AuthService } from '../../../core'
+import { FormReactive, User, UserService } from '../../../shared'
+
+@Component({
+  selector: 'my-account-details',
+  templateUrl: './my-account-details.component.html',
+  styleUrls: [ './my-account-details.component.scss' ]
+})
+export class MyAccountDetailsComponent extends FormReactive implements OnInit {
+  @Input() user: User = null
+
+  form: FormGroup
+  formErrors = {}
+  validationMessages = {}
+
+  constructor (
+    private authService: AuthService,
+    private formBuilder: FormBuilder,
+    private notificationsService: NotificationsService,
+    private userService: UserService
+  ) {
+    super()
+  }
+
+  buildForm () {
+    this.form = this.formBuilder.group({
+      nsfwPolicy: [ this.user.nsfwPolicy ],
+      autoPlayVideo: [ this.user.autoPlayVideo ]
+    })
+
+    this.form.valueChanges.subscribe(data => this.onValueChanged(data))
+  }
+
+  ngOnInit () {
+    this.buildForm()
+  }
+
+  updateDetails () {
+    const nsfwPolicy = this.form.value['nsfwPolicy']
+    const autoPlayVideo = this.form.value['autoPlayVideo']
+    const details: UserUpdateMe = {
+      nsfwPolicy,
+      autoPlayVideo
+    }
+
+    this.userService.updateMyDetails(details).subscribe(
+      () => {
+        this.notificationsService.success('Success', 'Information updated.')
+
+        this.authService.refreshUserInformation()
+      },
+
+      err => this.notificationsService.error('Error', err.message)
+    )
+  }
+}
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 (file)
index 0000000..7ae27dc
--- /dev/null
@@ -0,0 +1,24 @@
+<div class="user">
+  <img [src]="getAvatarUrl()" alt="Avatar" />
+
+  <div class="user-info">
+    <div class="user-info-username">{{ user.username }}</div>
+    <div class="user-info-followers">{{ user.account?.followersCount }} subscribers</div>
+  </div>
+</div>
+
+<div class="button-file">
+  <span>Change your avatar</span>
+  <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="changeAvatar()" />
+</div>
+<div class="file-max-size">(extensions: {{ avatarExtensions }}, max size: {{ maxAvatarSize | bytes }})</div>
+
+<div class="user-quota">
+  <span class="user-quota-label">Video quota:</span> {{ userVideoQuotaUsed | bytes: 0 }} / {{ userVideoQuota }}
+</div>
+
+<div class="account-title">Account settings</div>
+<my-account-change-password></my-account-change-password>
+
+<div class="account-title">Video settings</div>
+<my-account-details [user]="user"></my-account-details>
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 (file)
index 0000000..1cc00ca
--- /dev/null
@@ -0,0 +1,56 @@
+@import '_variables';
+@import '_mixins';
+
+.user {
+  display: flex;
+
+  img {
+    @include avatar(50px);
+
+    margin-right: 15px;
+  }
+
+  .user-info {
+    .user-info-username {
+      font-size: 20px;
+      font-weight: $font-bold;
+    }
+
+    .user-info-followers {
+      font-size: 15px;
+    }
+  }
+}
+
+.button-file {
+  @include peertube-button-file(160px);
+
+  margin-top: 10px;
+  margin-bottom: 5px;
+}
+
+.file-max-size {
+  display: inline-block;
+  font-size: 13px;
+
+  position: relative;
+  top: -10px;
+}
+
+.user-quota {
+  font-size: 15px;
+  margin-top: 20px;
+
+  .user-quota-label {
+    font-weight: $font-semibold;
+  }
+}
+
+.account-title {
+  text-transform: uppercase;
+  color: $orange-color;
+  font-weight: $font-bold;
+  font-size: 13px;
+  margin-top: 55px;
+  margin-bottom: 30px;
+}
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 (file)
index 0000000..91420cc
--- /dev/null
@@ -0,0 +1,74 @@
+import { Component, OnInit, ViewChild } from '@angular/core'
+import { NotificationsService } from 'angular2-notifications'
+import { BytesPipe } from 'ngx-pipes'
+import { AuthService } from '../../core'
+import { ServerService } from '../../core/server'
+import { User } from '../../shared'
+import { UserService } from '../../shared/users'
+
+@Component({
+  selector: 'my-account-settings',
+  templateUrl: './my-account-settings.component.html',
+  styleUrls: [ './my-account-settings.component.scss' ]
+})
+export class MyAccountSettingsComponent implements OnInit {
+  @ViewChild('avatarfileInput') avatarfileInput
+
+  user: User = null
+  userVideoQuota = '0'
+  userVideoQuotaUsed = 0
+
+  constructor (
+    private userService: UserService,
+    private authService: AuthService,
+    private serverService: ServerService,
+    private notificationsService: NotificationsService
+  ) {}
+
+  ngOnInit () {
+    this.user = this.authService.getUser()
+
+    this.authService.userInformationLoaded.subscribe(
+      () => {
+        if (this.user.videoQuota !== -1) {
+          this.userVideoQuota = new BytesPipe().transform(this.user.videoQuota, 0).toString()
+        } else {
+          this.userVideoQuota = 'Unlimited'
+        }
+      }
+    )
+
+    this.userService.getMyVideoQuotaUsed()
+      .subscribe(data => this.userVideoQuotaUsed = data.videoQuotaUsed)
+  }
+
+  getAvatarUrl () {
+    return this.user.getAvatarUrl()
+  }
+
+  changeAvatar () {
+    const avatarfile = this.avatarfileInput.nativeElement.files[ 0 ]
+
+    const formData = new FormData()
+    formData.append('avatarfile', avatarfile)
+
+    this.userService.changeAvatar(formData)
+      .subscribe(
+        data => {
+          this.notificationsService.success('Success', 'Avatar changed.')
+
+          this.user.account.avatar = data.avatar
+        },
+
+        err => this.notificationsService.error('Error', err.message)
+      )
+  }
+
+  get maxAvatarSize () {
+    return this.serverService.getConfig().avatar.file.size.max
+  }
+
+  get avatarExtensions () {
+    return this.serverService.getConfig().avatar.file.extensions.join(',')
+  }
+}
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 (file)
index 0000000..66ce3a7
--- /dev/null
@@ -0,0 +1,45 @@
+<div *ngIf="pagination.totalItems === 0">No results.</div>
+
+<div
+  myInfiniteScroller
+  [pageHeight]="pageHeight"
+  (nearOfTop)="onNearOfTop()" (nearOfBottom)="onNearOfBottom()" (pageChanged)="onPageChanged($event)"
+  class="videos" #videosElement
+>
+  <div *ngFor="let videos of videoPages; let i = index" class="videos-page">
+    <div class="video" *ngFor="let video of videos; let j = index">
+      <div class="checkbox-container">
+        <input [id]="'video-check-' + video.id" type="checkbox" [(ngModel)]="checkedVideos[video.id]" />
+        <label [for]="'video-check-' + video.id"></label>
+      </div>
+
+      <my-video-thumbnail [video]="video"></my-video-thumbnail>
+
+      <div class="video-info">
+        <a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
+        <span class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
+        <div class="video-info-private">{{ video.privacy.label }}</div>
+      </div>
+
+      <!-- Display only once -->
+      <div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0 && j === 0">
+        <div class="action-selection-mode-child">
+          <span class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
+            Cancel
+          </span>
+
+          <span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()">
+            <span class="icon icon-delete-white"></span>
+            Delete
+          </span>
+        </div>
+      </div>
+
+      <div class="video-buttons" *ngIf="isInSelectionMode() === false">
+        <my-delete-button (click)="deleteVideo(video)"></my-delete-button>
+
+        <my-edit-button [routerLink]="[ '/videos', 'edit', video.uuid ]"></my-edit-button>
+      </div>
+    </div>
+  </div>
+</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 (file)
index 0000000..f276ea3
--- /dev/null
@@ -0,0 +1,114 @@
+@import '_variables';
+@import '_mixins';
+
+.action-selection-mode {
+  width: 174px;
+  display: flex;
+  justify-content: flex-end;
+
+  .action-selection-mode-child {
+    position: fixed;
+
+    .action-button {
+      display: inline-block;
+    }
+
+    .action-button-cancel-selection {
+      @include peertube-button;
+      @include grey-button;
+
+      margin-right: 10px;
+    }
+
+    .action-button-delete-selection {
+      @include peertube-button;
+      @include orange-button;
+    }
+
+    .icon.icon-delete-white {
+      @include icon(21px);
+
+      position: relative;
+      top: -2px;
+      background-image: url('../../../assets/images/global/delete-white.svg');
+    }
+  }
+}
+
+/deep/ .action-button {
+  &.action-button-delete {
+    margin-right: 10px;
+  }
+}
+
+.video {
+  display: flex;
+  min-height: 130px;
+  padding-bottom: 20px;
+  margin-bottom: 20px;
+  border-bottom: 1px solid #C6C6C6;
+
+  &:first-child {
+    margin-top: 47px;
+  }
+
+  .checkbox-container {
+    display: flex;
+    align-items: center;
+    margin-right: 20px;
+    margin-left: 12px;
+
+    input[type=checkbox] {
+      @include peertube-checkbox(2px);
+    }
+  }
+
+  my-video-thumbnail {
+    margin-right: 10px;
+  }
+
+  .video-info {
+    flex-grow: 1;
+
+    .video-info-name {
+      @include disable-default-a-behaviour;
+
+      color: #000;
+      display: block;
+      font-size: 16px;
+      font-weight: $font-semibold;
+    }
+
+    .video-info-date-views, .video-info-private {
+      font-size: 13px;
+
+      &.video-info-private {
+        font-weight: $font-semibold;
+      }
+    }
+  }
+
+  .video-buttons {
+    min-width: 190px;
+  }
+}
+
+@media screen and (max-width: 800px) {
+  .video {
+    flex-direction: column;
+    height: auto;
+    text-align: center;
+
+    input[type=checkbox] {
+      display: none;
+    }
+
+    my-video-thumbnail {
+      margin-right: 0;
+    }
+
+    .video-buttons {
+      margin-top: 10px;
+    }
+  }
+}
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 (file)
index 0000000..a6cef36
--- /dev/null
@@ -0,0 +1,133 @@
+import { Component, OnInit, OnDestroy } from '@angular/core'
+import { ActivatedRoute, Router } from '@angular/router'
+import { Location } from '@angular/common'
+import { immutableAssign } from '@app/shared/misc/utils'
+import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
+import { NotificationsService } from 'angular2-notifications'
+import 'rxjs/add/observable/from'
+import 'rxjs/add/operator/concatAll'
+import { Observable } from 'rxjs/Observable'
+import { AuthService } from '../../core/auth'
+import { ConfirmService } from '../../core/confirm'
+import { AbstractVideoList } from '../../shared/video/abstract-video-list'
+import { Video } from '../../shared/video/video.model'
+import { VideoService } from '../../shared/video/video.service'
+
+@Component({
+  selector: 'my-account-videos',
+  templateUrl: './my-account-videos.component.html',
+  styleUrls: [ './my-account-videos.component.scss' ]
+})
+export class MyAccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy {
+  titlePage = 'My videos'
+  currentRoute = '/my-account/videos'
+  checkedVideos: { [ id: number ]: boolean } = {}
+  pagination: ComponentPagination = {
+    currentPage: 1,
+    itemsPerPage: 5,
+    totalItems: null
+  }
+
+  protected baseVideoWidth = -1
+  protected baseVideoHeight = 155
+
+  constructor (protected router: Router,
+               protected route: ActivatedRoute,
+               protected authService: AuthService,
+               protected notificationsService: NotificationsService,
+               protected confirmService: ConfirmService,
+               protected location: Location,
+               private videoService: VideoService) {
+    super()
+  }
+
+  ngOnInit () {
+    super.ngOnInit()
+
+    // this.generateSyndicationList()
+  }
+
+  ngOnDestroy () {
+    super.ngOnDestroy()
+  }
+
+  abortSelectionMode () {
+    this.checkedVideos = {}
+  }
+
+  isInSelectionMode () {
+    return Object.keys(this.checkedVideos).some(k => this.checkedVideos[k] === true)
+  }
+
+  getVideosObservable (page: number) {
+    const newPagination = immutableAssign(this.pagination, { currentPage: page })
+
+    return this.videoService.getMyVideos(newPagination, this.sort)
+  }
+
+  generateSyndicationList () {
+    throw new Error('Method not implemented.')
+  }
+
+  async deleteSelectedVideos () {
+    const toDeleteVideosIds = Object.keys(this.checkedVideos)
+      .filter(k => this.checkedVideos[k] === true)
+      .map(k => parseInt(k, 10))
+
+    const res = await this.confirmService.confirm(`Do you really want to delete ${toDeleteVideosIds.length} videos?`, 'Delete')
+    if (res === false) return
+
+    const observables: Observable<any>[] = []
+    for (const videoId of toDeleteVideosIds) {
+      const o = this.videoService
+        .removeVideo(videoId)
+        .do(() => this.spliceVideosById(videoId))
+
+      observables.push(o)
+    }
+
+    Observable.from(observables)
+      .concatAll()
+      .subscribe(
+        res => {
+          this.notificationsService.success('Success', `${toDeleteVideosIds.length} videos deleted.`)
+          this.buildVideoPages()
+        },
+
+        err => this.notificationsService.error('Error', err.message)
+      )
+  }
+
+  async deleteVideo (video: Video) {
+    const res = await this.confirmService.confirm(`Do you really want to delete ${video.name}?`, 'Delete')
+    if (res === false) return
+
+    this.videoService.removeVideo(video.id)
+      .subscribe(
+        status => {
+          this.notificationsService.success('Success', `Video ${video.name} deleted.`)
+          this.spliceVideosById(video.id)
+          this.buildVideoPages()
+        },
+
+        error => this.notificationsService.error('Error', error.message)
+      )
+  }
+
+  protected buildVideoHeight () {
+    // In account videos, the video height is fixed
+    return this.baseVideoHeight
+  }
+
+  private spliceVideosById (id: number) {
+    for (const key of Object.keys(this.loadedPages)) {
+      const videos = this.loadedPages[key]
+      const index = videos.findIndex(v => v.id === id)
+
+      if (index !== -1) {
+        videos.splice(index, 1)
+        return
+      }
+    }
+  }
+}
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 (file)
index 0000000..637b258
--- /dev/null
@@ -0,0 +1,11 @@
+<div class="row">
+  <div class="sub-menu">
+    <a routerLink="/my-account/settings" routerLinkActive="active" class="title-page">My account</a>
+
+    <a routerLink="/my-account/videos" routerLinkActive="active" class="title-page">My videos</a>
+  </div>
+
+  <div class="margin-content">
+    <router-outlet></router-outlet>
+  </div>
+</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 (file)
index 0000000..0955e2b
--- /dev/null
@@ -0,0 +1,7 @@
+import { Component } from '@angular/core'
+
+@Component({
+  selector: 'my-account',
+  templateUrl: './my-account.component.html'
+})
+export 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 (file)
index 0000000..3172773
--- /dev/null
@@ -0,0 +1,30 @@
+import { NgModule } from '@angular/core'
+import { SharedModule } from '../shared'
+import { MyAccountRoutingModule } from './my-account-routing.module'
+import { MyAccountChangePasswordComponent } from './my-account-settings/my-account-change-password/my-account-change-password.component'
+import { MyAccountDetailsComponent } from './my-account-settings/my-account-details/my-account-details.component'
+import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component'
+import { MyAccountComponent } from './my-account.component'
+import { MyAccountVideosComponent } from './my-account-videos/my-account-videos.component'
+
+@NgModule({
+  imports: [
+    MyAccountRoutingModule,
+    SharedModule
+  ],
+
+  declarations: [
+    MyAccountComponent,
+    MyAccountSettingsComponent,
+    MyAccountChangePasswordComponent,
+    MyAccountDetailsComponent,
+    MyAccountVideosComponent
+  ],
+
+  exports: [
+    MyAccountComponent
+  ],
+
+  providers: []
+})
+export class MyAccountModule { }