From b33f657c304b77938c1f68164d8e754787f5aae5 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Fri, 1 Dec 2017 09:20:19 +0100
Subject: Begin new menu design

---
 client/config/webpack.common.js                    |   2 +-
 client/package.json                                |   3 +-
 client/src/app/app.component.html                  |  18 +--
 client/src/app/app.component.scss                  |  63 +++++-----
 client/src/app/app.component.ts                    |  17 +--
 client/src/app/app.module.ts                       |   6 +-
 client/src/app/core/core.module.ts                 |   8 +-
 client/src/app/core/index.ts                       |   1 -
 client/src/app/core/menu/index.ts                  |   2 -
 client/src/app/core/menu/menu-admin.component.html |  35 ------
 client/src/app/core/menu/menu-admin.component.ts   |  33 ------
 client/src/app/core/menu/menu.component.html       |  55 ---------
 client/src/app/core/menu/menu.component.scss       |  51 --------
 client/src/app/core/menu/menu.component.ts         |  92 ---------------
 client/src/app/menu/index.ts                       |   2 +
 client/src/app/menu/menu-admin.component.html      |  35 ++++++
 client/src/app/menu/menu-admin.component.ts        |  33 ++++++
 client/src/app/menu/menu.component.html            |  48 ++++++++
 client/src/app/menu/menu.component.scss            | 131 +++++++++++++++++++++
 client/src/app/menu/menu.component.ts              |  97 +++++++++++++++
 client/src/app/shared/search/search.component.html |  26 +---
 client/src/app/shared/search/search.component.scss |  51 --------
 client/src/assets/logo.png                         | Bin 838 -> 0 bytes
 client/src/assets/logo.svg                         | 118 +++++++++++++++++++
 client/src/assets/menu/administration.svg          |  14 +++
 client/src/assets/menu/recently-added.svg          |  13 ++
 client/src/assets/menu/trending.svg                |  16 +++
 client/src/sass/_variables.scss                    |  15 ++-
 client/src/sass/application.scss                   |   8 ++
 client/yarn.lock                                   |  10 +-
 30 files changed, 586 insertions(+), 417 deletions(-)
 delete mode 100644 client/src/app/core/menu/index.ts
 delete mode 100644 client/src/app/core/menu/menu-admin.component.html
 delete mode 100644 client/src/app/core/menu/menu-admin.component.ts
 delete mode 100644 client/src/app/core/menu/menu.component.html
 delete mode 100644 client/src/app/core/menu/menu.component.scss
 delete mode 100644 client/src/app/core/menu/menu.component.ts
 create mode 100644 client/src/app/menu/index.ts
 create mode 100644 client/src/app/menu/menu-admin.component.html
 create mode 100644 client/src/app/menu/menu-admin.component.ts
 create mode 100644 client/src/app/menu/menu.component.html
 create mode 100644 client/src/app/menu/menu.component.scss
 create mode 100644 client/src/app/menu/menu.component.ts
 delete mode 100644 client/src/assets/logo.png
 create mode 100644 client/src/assets/logo.svg
 create mode 100644 client/src/assets/menu/administration.svg
 create mode 100644 client/src/assets/menu/recently-added.svg
 create mode 100644 client/src/assets/menu/trending.svg

(limited to 'client')

diff --git a/client/config/webpack.common.js b/client/config/webpack.common.js
index 9cd33d2ed..583f4ba07 100644
--- a/client/config/webpack.common.js
+++ b/client/config/webpack.common.js
@@ -153,7 +153,7 @@ module.exports = function (options) {
           ]
         },
         { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, use: 'url-loader?limit=10000&minetype=application/font-woff' },
-        { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, use: 'file-loader' },
+        { test: /\.(otf|ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, use: 'url-loader?limit=10000' },
 
         /* Raw loader support for *.html
          * Returns file content as string
diff --git a/client/package.json b/client/package.json
index 39b3185cc..c551c995a 100644
--- a/client/package.json
+++ b/client/package.json
@@ -70,7 +70,7 @@
     "markdown-it": "^8.4.0",
     "ng-router-loader": "^2.0.0",
     "ngc-webpack": "3.2.2",
-    "ngx-bootstrap": "1.9.3",
+    "ngx-bootstrap": "2.0.0-beta.9",
     "ngx-chips": "1.5.3",
     "node-sass": "^4.1.1",
     "normalize.css": "^7.0.0",
@@ -86,6 +86,7 @@
     "sass-resources-loader": "^1.2.1",
     "script-ext-html-webpack-plugin": "^1.3.2",
     "source-map-loader": "^0.2.1",
+    "source-sans-pro": "^2.0.10",
     "standard": "^10.0.0",
     "string-replace-loader": "^1.0.3",
     "style-loader": "^0.19.0",
diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html
index 8a826e783..f4672c7ec 100644
--- a/client/src/app/app.component.html
+++ b/client/src/app/app.component.html
@@ -1,7 +1,7 @@
 <div class="container-fluid">
   <div class="row header">
 
-    <div class="col-md-2 col-sm-3 col-xs-3 top-left-block" [ngClass]="{ 'border-bottom': isMenuDisplayed === false }">
+    <div class="top-left-block" [ngClass]="{ 'border-bottom': isMenuDisplayed === false }">
       <div class="hamburger-block" (click)="toggleMenu()">
         <span class="glyphicon glyphicon-menu-hamburger"></span>
       </div>
@@ -11,15 +11,13 @@
       </div>
     </div>
 
-    <!-- Used for the fixed title -->
-    <div class="col-md-2 col-sm-3 col-xs-3 fake-title-block"></div>
-
-    <!-- We need to reset col-md-* because my-search is in fixed position -->
-    <my-search class="col-md-10 col-sm-9 col-xs-9"></my-search>
+    <div class="header-right">
+      <my-search></my-search>
+    </div>
   </div>
 
-  <div class="row">
-    <div class="col-md-2 col-sm-3 col-xs-3 title-menu-left">
+  <div class="row sub-header-container">
+    <div class="title-menu-left">
 
       <div class="title-menu-left-block menu">
         <my-menu *ngIf="isMenuDisplayed && isInAdmin() === false"></my-menu>
@@ -27,10 +25,6 @@
       </div>
     </div>
 
-    <!-- Used for the fixed menu -->
-    <div class="fake-menu col-md-2 col-sm-3 col-xs-3">
-    </div>
-
     <div class="main-col" [ngClass]="getMainColClasses()">
 
       <div class="main-row">
diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss
index a656d5c29..28e86097c 100644
--- a/client/src/app/app.component.scss
+++ b/client/src/app/app.component.scss
@@ -2,10 +2,23 @@
   min-height: calc(100vh - #{$header-height} - #{$footer-height} - #{$footer-margin});
 }
 
+.main-col {
+  margin-left: $menu-width;
+
+  &.expanded {
+    margin-left: 0;
+  }
+}
+
+.sub-header-container {
+  margin-top: $header-height;
+}
+
 .title-menu-left {
   position: fixed;
   height: calc(100vh - #{$header-height});
   padding: 0;
+  width: $menu-width;
 
   .title-menu-left-block.menu {
     height: 100%;
@@ -14,35 +27,28 @@
 
 .header {
   height: $header-height;
-
-  .fake-title-block {
-    display: inline-block;
-  }
+  position: fixed;
+  width: 100%;
+  background-color: #fff;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.16);
+  display: flex;
 
   .top-left-block {
-    z-index: 100;
-    background-color: #fff;
-    border-right: 1px solid $header-border-color;
+    width: $menu-width;
+    z-index: 1001;
     height: $header-height;
     line-height: $header-height;
     margin-top: 0;
     margin-bottom: 0;
     display: flex;
-    position: fixed;
     padding: 0;
 
-    &.border-bottom {
-      border-bottom: 1px solid $header-border-color;
-    }
-
     .hamburger-block {
-      margin-right: 15px;
-      margin-left: 15px;
+      margin-right: 10px;
+      margin-left: 25px;
 
       .glyphicon {
         cursor: pointer;
-        position: relative;
-        top: 4px;
       }
     }
 
@@ -50,12 +56,9 @@
       a {
         color: inherit !important;
         display: block;
-        background: url('../assets/logo.png') no-repeat;
-        background-size: contain;
-        background-position: center;
-        height: 100%;
-        margin: auto;
-        width: 135px;
+        background: url('../assets/logo.svg') no-repeat;
+        width: 24px;
+        height: 24px;
 
         &:hover {
           color: inherit !important;
@@ -122,17 +125,11 @@
     }
   }
 
-  my-search {
-    position: fixed;
-    z-index: 1000;
-    // Fix col-md-* padding
-    padding: 0;
-  }
-
-  .search-col {
-    height: 100%;
-    margin-left: -15px;
-    padding: 0;
+  .header-right {
+    text-align: right;
+    height: $header-height;
+    margin-left: $menu-width;
+    flex-grow: 1;
   }
 }
 
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts
index 9b699fafd..b1818c298 100644
--- a/client/src/app/app.component.ts
+++ b/client/src/app/app.component.ts
@@ -1,8 +1,6 @@
 import { Component, OnInit } from '@angular/core'
 import { Router } from '@angular/router'
-
 import { AuthService, ServerService } from './core'
-import { UserService } from './shared'
 
 @Component({
   selector: 'my-app',
@@ -62,20 +60,9 @@ export class AppComponent implements OnInit {
   }
 
   getMainColClasses () {
-    const colSizes = {
-      md: 10,
-      sm: 9,
-      xs: 9
-    }
-
     // Take all width is the menu is not displayed
-    if (this.isMenuDisplayed === false) {
-      Object.keys(colSizes).forEach(col => colSizes[col] = 12)
-    }
-
-    const classes = []
-    Object.keys(colSizes).forEach(col => classes.push(`col-${col}-${colSizes[col]}`))
+    if (this.isMenuDisplayed === false) return [ 'expanded' ]
 
-    return classes
+    return []
   }
 }
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts
index e71641e0d..342589003 100644
--- a/client/src/app/app.module.ts
+++ b/client/src/app/app.module.ts
@@ -20,6 +20,7 @@ import { LoginModule } from './login'
 import { SignupModule } from './signup'
 import { SharedModule } from './shared'
 import { VideosModule } from './videos'
+import { MenuComponent, MenuAdminComponent } from './menu'
 
 export function metaFactory (): MetaLoader {
   return new MetaStaticLoader({
@@ -47,7 +48,10 @@ const APP_PROVIDERS = [
 @NgModule({
   bootstrap: [ AppComponent ],
   declarations: [
-    AppComponent
+    AppComponent,
+
+    MenuComponent,
+    MenuAdminComponent
   ],
   imports: [
     BrowserModule,
diff --git a/client/src/app/core/core.module.ts b/client/src/app/core/core.module.ts
index c4ce2b637..75262e6cf 100644
--- a/client/src/app/core/core.module.ts
+++ b/client/src/app/core/core.module.ts
@@ -26,17 +26,13 @@ import { throwIfAlreadyLoaded } from './module-import-guard'
   ],
 
   declarations: [
-    ConfirmComponent,
-    MenuComponent,
-    MenuAdminComponent
+    ConfirmComponent
   ],
 
   exports: [
     SimpleNotificationsModule,
 
-    ConfirmComponent,
-    MenuComponent,
-    MenuAdminComponent
+    ConfirmComponent
   ],
 
   providers: [
diff --git a/client/src/app/core/index.ts b/client/src/app/core/index.ts
index 8358261ae..3c01e05aa 100644
--- a/client/src/app/core/index.ts
+++ b/client/src/app/core/index.ts
@@ -1,6 +1,5 @@
 export * from './auth'
 export * from './server'
 export * from './confirm'
-export * from './menu'
 export * from './routing'
 export * from './core.module'
diff --git a/client/src/app/core/menu/index.ts b/client/src/app/core/menu/index.ts
deleted file mode 100644
index c905ed20a..000000000
--- a/client/src/app/core/menu/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from './menu.component'
-export * from './menu-admin.component'
diff --git a/client/src/app/core/menu/menu-admin.component.html b/client/src/app/core/menu/menu-admin.component.html
deleted file mode 100644
index 9857b2e3e..000000000
--- a/client/src/app/core/menu/menu-admin.component.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<menu>
-  <div class="panel-block">
-    <a *ngIf="hasUsersRight()" routerLink="/admin/users" routerLinkActive="active">
-      <span class="hidden-xs glyphicon glyphicon-user"></span>
-      List users
-    </a>
-
-    <a *ngIf="hasServerFollowRight()" routerLink="/admin/follows" routerLinkActive="active">
-      <span class="hidden-xs glyphicon glyphicon-cloud"></span>
-      Manage follows
-    </a>
-
-    <a *ngIf="hasVideoAbusesRight()" routerLink="/admin/video-abuses" routerLinkActive="active">
-      <span class="hidden-xs glyphicon glyphicon-alert"></span>
-      Video abuses
-    </a>
-
-    <a *ngIf="hasVideoBlacklistRight()" routerLink="/admin/video-blacklist" routerLinkActive="active">
-      <span class="hidden-xs glyphicon glyphicon-eye-close"></span>
-      Video blacklist
-    </a>
-
-    <a *ngIf="hasJobsRight()" routerLink="/admin/jobs" routerLinkActive="active">
-      <span class="hidden-xs glyphicon glyphicon-tasks"></span>
-      Jobs
-    </a>
-  </div>
-
-  <div class="panel-block">
-    <a routerLink="/videos/list" routerLinkActive="active">
-      <span class="hidden-xs glyphicon glyphicon-cog"></span>
-      Quit admin.
-    </a>
-  </div>
-</menu>
diff --git a/client/src/app/core/menu/menu-admin.component.ts b/client/src/app/core/menu/menu-admin.component.ts
deleted file mode 100644
index ea8d5f57c..000000000
--- a/client/src/app/core/menu/menu-admin.component.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Component } from '@angular/core'
-
-import { AuthService } from '../auth/auth.service'
-import { UserRight } from '../../../../../shared'
-
-@Component({
-  selector: 'my-menu-admin',
-  templateUrl: './menu-admin.component.html',
-  styleUrls: [ './menu.component.scss' ]
-})
-export class MenuAdminComponent {
-  constructor (private auth: AuthService) {}
-
-  hasUsersRight () {
-    return this.auth.getUser().hasRight(UserRight.MANAGE_USERS)
-  }
-
-  hasServerFollowRight () {
-    return this.auth.getUser().hasRight(UserRight.MANAGE_SERVER_FOLLOW)
-  }
-
-  hasVideoAbusesRight () {
-    return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_ABUSES)
-  }
-
-  hasVideoBlacklistRight () {
-    return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)
-  }
-
-  hasJobsRight () {
-    return this.auth.getUser().hasRight(UserRight.MANAGE_JOBS)
-  }
-}
diff --git a/client/src/app/core/menu/menu.component.html b/client/src/app/core/menu/menu.component.html
deleted file mode 100644
index fcde23fdd..000000000
--- a/client/src/app/core/menu/menu.component.html
+++ /dev/null
@@ -1,55 +0,0 @@
-<menu>
-  <div class="panel-block">
-    <div class="block-title">Account</div>
-
-    <div id="panel-user-login" class="panel-button">
-      <a *ngIf="!isLoggedIn" routerLink="/login" routerLinkActive="active">
-        <span class="hidden-xs glyphicon glyphicon-log-in"></span>
-        Login
-      </a>
-
-      <a *ngIf="isLoggedIn" (click)="logout()">
-        <span class="hidden-xs glyphicon glyphicon-log-out"></span>
-        Logout
-      </a>
-    </div>
-
-    <a *ngIf="!isLoggedIn && isRegistrationAllowed()" routerLink="/signup" routerLinkActive="active">
-      <span class="hidden-xs glyphicon glyphicon-user"></span>
-      Signup
-    </a>
-
-    <a *ngIf="isLoggedIn" routerLink="/account" routerLinkActive="active">
-      <span class="hidden-xs glyphicon glyphicon-user"></span>
-      My account
-    </a>
-
-    <a *ngIf="isLoggedIn" routerLink="/videos/mine" routerLinkActive="active">
-      <span class="hidden-xs glyphicon glyphicon-folder-open"></span>
-      My videos
-    </a>
-  </div>
-
-  <div class="panel-block">
-    <div class="block-title">Videos</div>
-
-    <a routerLink="/videos/list" routerLinkActive="active">
-      <span class="hidden-xs glyphicon glyphicon-list"></span>
-      See videos
-    </a>
-
-    <a *ngIf="isLoggedIn" routerLink="/videos/upload" routerLinkActive="active">
-      <span class="hidden-xs glyphicon glyphicon-cloud-upload"></span>
-      Upload a video
-    </a>
-  </div>
-
-  <div *ngIf="userHasAdminAccess" class="panel-block">
-    <div class="block-title">Other</div>
-
-    <a [routerLink]="getFirstAdminRouteAvailable()" routerLinkActive="active">
-      <span class="hidden-xs glyphicon glyphicon-cog"></span>
-      Administration
-    </a>
-  </div>
-</menu>
diff --git a/client/src/app/core/menu/menu.component.scss b/client/src/app/core/menu/menu.component.scss
deleted file mode 100644
index 45679c310..000000000
--- a/client/src/app/core/menu/menu.component.scss
+++ /dev/null
@@ -1,51 +0,0 @@
-menu {
-  background-color: $black-background;
-  padding: 15px;
-  margin: 0;
-  height: 100%;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-  overflow: hidden;
-  z-index: 1000;
-
-  @media screen and (max-width: 550px) {
-    font-size: 90%;
-  }
-
-  @media screen and (min-width: 1200px) {
-    padding: 25px;
-  }
-
-  .panel-block {
-    margin-bottom: 15px;
-  }
-
-  .block-title {
-    text-transform: uppercase;
-    font-weight: bold;
-    color: $menu-color-block;
-    margin-bottom: 10px;
-  }
-
-  a {
-    display: block;
-    margin-left: 5px;
-    height: 30px;
-    color: $menu-color-link;
-    cursor: pointer;
-    transition: color 0.3s;
-
-    &:hover, &:focus {
-      text-decoration: none !important;
-      outline: none !important;
-    }
-
-    .glyphicon {
-      margin-right: 15px;
-    }
-
-    &:hover, &.active {
-      color: #fff;
-    }
-  }
-}
diff --git a/client/src/app/core/menu/menu.component.ts b/client/src/app/core/menu/menu.component.ts
deleted file mode 100644
index d2bd71534..000000000
--- a/client/src/app/core/menu/menu.component.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import { Component, OnInit } from '@angular/core'
-import { Router } from '@angular/router'
-
-import { AuthService, AuthStatus } from '../auth'
-import { ServerService } from '../server'
-import { UserRight } from '../../../../../shared/models/users/user-right.enum'
-
-@Component({
-  selector: 'my-menu',
-  templateUrl: './menu.component.html',
-  styleUrls: [ './menu.component.scss' ]
-})
-export class MenuComponent implements OnInit {
-  isLoggedIn: boolean
-  userHasAdminAccess = false
-
-  private routesPerRight = {
-    [UserRight.MANAGE_USERS]: '/admin/users',
-    [UserRight.MANAGE_SERVER_FOLLOW]: '/admin/friends',
-    [UserRight.MANAGE_VIDEO_ABUSES]: '/admin/video-abuses',
-    [UserRight.MANAGE_VIDEO_BLACKLIST]: '/admin/video-blacklist'
-  }
-
-  constructor (
-    private authService: AuthService,
-    private serverService: ServerService,
-    private router: Router
-  ) {}
-
-  ngOnInit () {
-    this.isLoggedIn = this.authService.isLoggedIn()
-    this.computeIsUserHasAdminAccess()
-
-    this.authService.loginChangedSource.subscribe(
-      status => {
-        if (status === AuthStatus.LoggedIn) {
-          this.isLoggedIn = true
-          this.computeIsUserHasAdminAccess()
-          console.log('Logged in.')
-        } else if (status === AuthStatus.LoggedOut) {
-          this.isLoggedIn = false
-          this.computeIsUserHasAdminAccess()
-          console.log('Logged out.')
-        } else {
-          console.error('Unknown auth status: ' + status)
-        }
-      }
-    )
-  }
-
-  isRegistrationAllowed () {
-    return this.serverService.getConfig().signup.allowed
-  }
-
-  getFirstAdminRightAvailable () {
-    const user = this.authService.getUser()
-    if (!user) return undefined
-
-    const adminRights = [
-      UserRight.MANAGE_USERS,
-      UserRight.MANAGE_SERVER_FOLLOW,
-      UserRight.MANAGE_VIDEO_ABUSES,
-      UserRight.MANAGE_VIDEO_BLACKLIST
-    ]
-
-    for (const adminRight of adminRights) {
-      if (user.hasRight(adminRight)) {
-        return adminRight
-      }
-    }
-
-    return undefined
-  }
-
-  getFirstAdminRouteAvailable () {
-    const right = this.getFirstAdminRightAvailable()
-
-    return this.routesPerRight[right]
-  }
-
-  logout () {
-    this.authService.logout()
-    // Redirect to home page
-    this.router.navigate(['/videos/list'])
-  }
-
-  private computeIsUserHasAdminAccess () {
-    const right = this.getFirstAdminRightAvailable()
-
-    this.userHasAdminAccess = right !== undefined
-  }
-}
diff --git a/client/src/app/menu/index.ts b/client/src/app/menu/index.ts
new file mode 100644
index 000000000..c905ed20a
--- /dev/null
+++ b/client/src/app/menu/index.ts
@@ -0,0 +1,2 @@
+export * from './menu.component'
+export * from './menu-admin.component'
diff --git a/client/src/app/menu/menu-admin.component.html b/client/src/app/menu/menu-admin.component.html
new file mode 100644
index 000000000..9857b2e3e
--- /dev/null
+++ b/client/src/app/menu/menu-admin.component.html
@@ -0,0 +1,35 @@
+<menu>
+  <div class="panel-block">
+    <a *ngIf="hasUsersRight()" routerLink="/admin/users" routerLinkActive="active">
+      <span class="hidden-xs glyphicon glyphicon-user"></span>
+      List users
+    </a>
+
+    <a *ngIf="hasServerFollowRight()" routerLink="/admin/follows" routerLinkActive="active">
+      <span class="hidden-xs glyphicon glyphicon-cloud"></span>
+      Manage follows
+    </a>
+
+    <a *ngIf="hasVideoAbusesRight()" routerLink="/admin/video-abuses" routerLinkActive="active">
+      <span class="hidden-xs glyphicon glyphicon-alert"></span>
+      Video abuses
+    </a>
+
+    <a *ngIf="hasVideoBlacklistRight()" routerLink="/admin/video-blacklist" routerLinkActive="active">
+      <span class="hidden-xs glyphicon glyphicon-eye-close"></span>
+      Video blacklist
+    </a>
+
+    <a *ngIf="hasJobsRight()" routerLink="/admin/jobs" routerLinkActive="active">
+      <span class="hidden-xs glyphicon glyphicon-tasks"></span>
+      Jobs
+    </a>
+  </div>
+
+  <div class="panel-block">
+    <a routerLink="/videos/list" routerLinkActive="active">
+      <span class="hidden-xs glyphicon glyphicon-cog"></span>
+      Quit admin.
+    </a>
+  </div>
+</menu>
diff --git a/client/src/app/menu/menu-admin.component.ts b/client/src/app/menu/menu-admin.component.ts
new file mode 100644
index 000000000..1babf5eb6
--- /dev/null
+++ b/client/src/app/menu/menu-admin.component.ts
@@ -0,0 +1,33 @@
+import { Component } from '@angular/core'
+
+import { AuthService } from '../core/auth/auth.service'
+import { UserRight } from '../../../../shared'
+
+@Component({
+  selector: 'my-menu-admin',
+  templateUrl: './menu-admin.component.html',
+  styleUrls: [ './menu.component.scss' ]
+})
+export class MenuAdminComponent {
+  constructor (private auth: AuthService) {}
+
+  hasUsersRight () {
+    return this.auth.getUser().hasRight(UserRight.MANAGE_USERS)
+  }
+
+  hasServerFollowRight () {
+    return this.auth.getUser().hasRight(UserRight.MANAGE_SERVER_FOLLOW)
+  }
+
+  hasVideoAbusesRight () {
+    return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_ABUSES)
+  }
+
+  hasVideoBlacklistRight () {
+    return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)
+  }
+
+  hasJobsRight () {
+    return this.auth.getUser().hasRight(UserRight.MANAGE_JOBS)
+  }
+}
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html
new file mode 100644
index 000000000..bb0caaef5
--- /dev/null
+++ b/client/src/app/menu/menu.component.html
@@ -0,0 +1,48 @@
+<menu>
+  <div *ngIf="isLoggedIn" class="logged-in-block">
+    <div class="logged-in-info">
+      <div class="logged-in-username">{{ user.username  }}</div>
+      <div class="logged-in-email">{{ user.email }}</div>
+    </div>
+
+    <div class="logged-in-more" dropdown placement="right" container="body">
+      <span class="glyphicon glyphicon-option-vertical" dropdownToggle></span>
+
+      <ul *dropdownMenu class="dropdown-menu">
+        <li>
+          <a (click)="logout($event)" class="dropdown-item" title="Log out" href="#">
+            Log out
+          </a>
+        </li>
+      </ul>
+    </div>
+  </div>
+
+  <div *ngIf="!isLoggedIn" class="button-block">
+    <a routerLink="/login"class="login-button">Login</a>
+    <a *ngIf="isRegistrationAllowed()" routerLink="/signup" class="create-account-button">Create an account</a>
+  </div>
+
+  <div class="panel-block">
+    <div class="block-title">Videos</div>
+
+    <a routerLink="/videos/list" routerLinkActive="active">
+      <span class="icon icon-videos-trending"></span>
+      Trending
+    </a>
+
+    <a routerLink="/videos/list" routerLinkActive="active">
+      <span class="icon icon-videos-recently-added"></span>
+      Recently added
+    </a>
+  </div>
+
+  <div *ngIf="userHasAdminAccess" class="panel-block">
+    <div class="block-title">More</div>
+
+    <a [routerLink]="getFirstAdminRouteAvailable()" routerLinkActive="active">
+      <span class="icon icon-administration"></span>
+      Administration
+    </a>
+  </div>
+</menu>
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss
new file mode 100644
index 000000000..8a4910605
--- /dev/null
+++ b/client/src/app/menu/menu.component.scss
@@ -0,0 +1,131 @@
+menu {
+  background-color: $black-background;
+  margin: 0;
+  padding: 0;
+  height: 100%;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  overflow: hidden;
+  z-index: 1000;
+  color: $menu-color;
+
+  @media screen and (max-width: 550px) {
+    font-size: 90%;
+  }
+
+  .logged-in-block {
+    height: 100px;
+    background-color: rgba(255, 255, 255, 0.15);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-bottom: 35px;
+
+    .logged-in-info {
+      flex-grow: 1;
+      margin-left: 40px;
+
+      .logged-in-username {
+        font-size: 16px;
+        font-weight: $font-semibold;
+      }
+
+      .logged-in-email {
+        font-size: 13px;
+        color: #C6C6C6;
+      }
+    }
+
+    .logged-in-more {
+      margin-right: 20px;
+
+      .glyphicon {
+        cursor: pointer;
+        font-size: 18px;
+      }
+    }
+  }
+
+  .button-block {
+    margin: 30px 25px 35px 25px;
+
+    .login-button, .create-account-button {
+      font-weight: $font-semibold;
+      font-size: 15px;
+      height: $button-height;
+      line-height: $button-height;
+      width: 190px;
+      border-radius: 3px;
+      text-align: center;
+
+      &.login-button {
+        background-color: $orange-color;
+        margin-bottom: 10px;
+      }
+
+      &.create-account-button {
+        background-color: rgba(255, 255, 255, 0.25);
+      }
+    }
+  }
+
+  .block-title {
+    text-transform: uppercase;
+    font-weight: $font-bold; // Bold
+    font-size: 13px;
+    margin-bottom: 25px;
+  }
+
+  .panel-block {
+    margin-bottom: 45px;
+    margin-left: 26px;
+
+    a {
+      display: flex;
+
+      .icon {
+        width: 22px;
+        height: 22px;
+        display: inline-block;
+        margin-right: 18px;
+        background-size: contain;
+
+        &.icon-videos-trending {
+          position: relative;
+          top: -2px;
+          background-image: url('../../assets/menu/trending.svg');
+        }
+
+        &.icon-videos-recently-added {
+          width: 23px;
+          height: 23px;
+          position: relative;
+          top: -1px;
+          background-image: url('../../assets/menu/recently-added.svg');
+        }
+
+        &.icon-administration {
+          width: 23px;
+          height: 23px;
+
+          background-image: url('../../assets/menu/administration.svg');
+        }
+      }
+    }
+  }
+
+  a {
+    color: $menu-color;
+    height: 22px;
+    line-height: 22px;
+    display: block;
+    font-size: 16px;
+    cursor: pointer;
+    margin-bottom: 15px;
+
+    &:hover, &:focus {
+      text-decoration: none !important;
+      outline: none !important;
+    }
+  }
+}
diff --git a/client/src/app/menu/menu.component.ts b/client/src/app/menu/menu.component.ts
new file mode 100644
index 000000000..4c35bb3a5
--- /dev/null
+++ b/client/src/app/menu/menu.component.ts
@@ -0,0 +1,97 @@
+import { Component, OnInit } from '@angular/core'
+import { Router } from '@angular/router'
+import { UserRight } from '../../../../shared/models/users/user-right.enum'
+import { AuthService, AuthStatus, ServerService } from '../core'
+import { User } from '../shared/users/user.model'
+
+@Component({
+  selector: 'my-menu',
+  templateUrl: './menu.component.html',
+  styleUrls: [ './menu.component.scss' ]
+})
+export class MenuComponent implements OnInit {
+  user: User
+  isLoggedIn: boolean
+  userHasAdminAccess = false
+
+  private routesPerRight = {
+    [UserRight.MANAGE_USERS]: '/admin/users',
+    [UserRight.MANAGE_SERVER_FOLLOW]: '/admin/friends',
+    [UserRight.MANAGE_VIDEO_ABUSES]: '/admin/video-abuses',
+    [UserRight.MANAGE_VIDEO_BLACKLIST]: '/admin/video-blacklist'
+  }
+
+  constructor (
+    private authService: AuthService,
+    private serverService: ServerService,
+    private router: Router
+  ) {}
+
+  ngOnInit () {
+    this.isLoggedIn = this.authService.isLoggedIn()
+    if (this.isLoggedIn === true) this.user = this.authService.getUser()
+    this.computeIsUserHasAdminAccess()
+
+    this.authService.loginChangedSource.subscribe(
+      status => {
+        if (status === AuthStatus.LoggedIn) {
+          this.isLoggedIn = true
+          this.user = this.authService.getUser()
+          this.computeIsUserHasAdminAccess()
+          console.log('Logged in.')
+        } else if (status === AuthStatus.LoggedOut) {
+          this.isLoggedIn = false
+          this.user = undefined
+          this.computeIsUserHasAdminAccess()
+          console.log('Logged out.')
+        } else {
+          console.error('Unknown auth status: ' + status)
+        }
+      }
+    )
+  }
+
+  isRegistrationAllowed () {
+    return this.serverService.getConfig().signup.allowed
+  }
+
+  getFirstAdminRightAvailable () {
+    const user = this.authService.getUser()
+    if (!user) return undefined
+
+    const adminRights = [
+      UserRight.MANAGE_USERS,
+      UserRight.MANAGE_SERVER_FOLLOW,
+      UserRight.MANAGE_VIDEO_ABUSES,
+      UserRight.MANAGE_VIDEO_BLACKLIST
+    ]
+
+    for (const adminRight of adminRights) {
+      if (user.hasRight(adminRight)) {
+        return adminRight
+      }
+    }
+
+    return undefined
+  }
+
+  getFirstAdminRouteAvailable () {
+    const right = this.getFirstAdminRightAvailable()
+
+    return this.routesPerRight[right]
+  }
+
+  logout (event: Event) {
+    event.preventDefault()
+
+    this.authService.logout()
+    // Redirect to home page
+    this.router.navigate(['/videos/list'])
+  }
+
+  private computeIsUserHasAdminAccess () {
+    const right = this.getFirstAdminRightAvailable()
+
+    this.userHasAdminAccess = right !== undefined
+  }
+}
diff --git a/client/src/app/shared/search/search.component.html b/client/src/app/shared/search/search.component.html
index 75e9dfa59..0e3de150c 100644
--- a/client/src/app/shared/search/search.component.html
+++ b/client/src/app/shared/search/search.component.html
@@ -1,22 +1,6 @@
-<div class="input-group">
+<input
+  type="text" id="search-video" name="search-video" placeholder="Search"
+  [(ngModel)]="searchCriteria.value" (keyup.enter)="doSearch()"
+>
 
-  <span class="hidden-xs input-group-addon icon-addon">
-    <span class="glyphicon glyphicon-search"></span>
-  </span>
-
-  <input
-    type="text" id="search-video" name="search-video" class="form-control" placeholder="Search" class="form-control"
-    [(ngModel)]="searchCriteria.value" (keyup.enter)="doSearch()"
-  >
-
-  <div class="input-group-btn" dropdown placement="bottom right">
-    <button id="simple-btn-keyboard-nav" type="button" class="btn btn-default" dropdownToggle>
-      {{ getStringChoice(searchCriteria.field) }} <span class="caret"></span>
-    </button>
-    <ul class="dropdown-menu dropdown-menu-right" role="menu" aria-labelledby="simple-btn-keyboard-nav" *dropdownMenu>
-      <li *ngFor="let choice of choiceKeys" class="dropdown-item" role="menu-item">
-        <a class="dropdown-item" href="#" (click)="choose($event, choice)">{{ getStringChoice(choice) }}</a>
-      </li>
-    </ul>
-  </div>
-</div>
+<a routerLink="/videos/upload">Upload</a>
diff --git a/client/src/app/shared/search/search.component.scss b/client/src/app/shared/search/search.component.scss
index 583f9586f..e69de29bb 100644
--- a/client/src/app/shared/search/search.component.scss
+++ b/client/src/app/shared/search/search.component.scss
@@ -1,51 +0,0 @@
-.icon-addon {
-  background-color: #fff;
-  border-radius: 0;
-  border-color: $header-border-color;
-  border-width: 0 0 1px 0;
-  text-align: right;
-
-  .glyphicon-search {
-    width: 30px;
-    font-size: 20px;
-  }
-}
-
-input, button, .input-group {
-  height: 100%;
-}
-
-input, .input-group-btn {
-  border-radius: 0;
-  border-top: none;
-  border-left: none;
-}
-
-input {
-  height: $header-height;
-  border-right: none;
-  font-weight: bold;
-  box-shadow: none;
-
-  &, &:focus {
-    border-bottom: 1px solid $header-border-color !important;
-    outline: none !important;
-    box-shadow: none !important;
-  }
-}
-
-button {
-
-  &, &:hover, &:focus, &:active, &:visited {
-    background-color: #fff !important;
-    border-color: $header-border-color !important;
-    color: #858585 !important;
-    outline: none !important;
-
-    height: $header-height;
-    border-width: 0 0 1px 0;
-    font-weight: bold;
-    text-decoration: none;
-    box-shadow: none;
-  }
-}
diff --git a/client/src/assets/logo.png b/client/src/assets/logo.png
deleted file mode 100644
index c1d77a24c..000000000
Binary files a/client/src/assets/logo.png and /dev/null differ
diff --git a/client/src/assets/logo.svg b/client/src/assets/logo.svg
new file mode 100644
index 000000000..8777acd5b
--- /dev/null
+++ b/client/src/assets/logo.svg
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   viewBox="2799 -911 16 22"
+   version="1.1"
+   id="svg13"
+   sodipodi:docname="logo.svg"
+   width="16"
+   height="22"
+   inkscape:version="0.92.2 5c3e80d, 2017-08-06">
+  <metadata
+     id="metadata17">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1916"
+     inkscape:window-height="1040"
+     id="namedview15"
+     showgrid="false"
+     inkscape:zoom="29.790476"
+     inkscape:cx="-1.1827326"
+     inkscape:cy="12.088"
+     inkscape:window-x="0"
+     inkscape:window-y="18"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg13" />
+  <defs
+     id="defs4">
+    <style
+       id="style2">
+      .cls-3 {
+        fill: #211f20;
+      }
+
+      .cls-4 {
+        fill: #737373;
+      }
+
+      .cls-5 {
+        fill: #f1680d;
+      }
+
+      .cls-6 {
+        fill: #fff;
+      }
+    </style>
+  </defs>
+  <g
+     id="Artboard_1"
+     data-name="Artboard – 1"
+     class="cls-1"
+     transform="translate(0.03356777,-1.9929667)">
+    <g
+       id="Symbol_3_1"
+       data-name="Symbol 3 – 1"
+       transform="translate(2759,-975)">
+      <g
+         id="Group_44"
+         data-name="Group 44"
+         transform="translate(0,2.333)">
+        <path
+           id="Path_4"
+           data-name="Path 4"
+           class="cls-3"
+           d="m -949,-500 v 10.667 l 8,-5.333"
+           transform="translate(989,564)"
+           inkscape:connector-curvature="0"
+           style="fill:#211f20" />
+        <path
+           id="Path_5"
+           data-name="Path 5"
+           class="cls-4"
+           d="m -949,-500 v 10.667 l 8,-5.333"
+           transform="translate(989,574.667)"
+           inkscape:connector-curvature="0"
+           style="fill:#737373" />
+        <path
+           id="Path_6"
+           data-name="Path 6"
+           class="cls-5"
+           d="m -949,-500 v 10.667 l 8,-5.333"
+           transform="translate(997,569.333)"
+           inkscape:connector-curvature="0"
+           style="fill:#f1680d" />
+        <path
+           id="Path_7"
+           data-name="Path 7"
+           class="cls-6"
+           d="M 0,0 V 10.667 L 8,5.333 Z"
+           transform="rotate(180,24,40)"
+           inkscape:connector-curvature="0"
+           style="fill:#ffffff" />
+      </g>
+    </g>
+  </g>
+</svg>
diff --git a/client/src/assets/menu/administration.svg b/client/src/assets/menu/administration.svg
new file mode 100644
index 000000000..b6da837d2
--- /dev/null
+++ b/client/src/assets/menu/administration.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>filter</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Artboard-4" transform="translate(-444.000000, -247.000000)" fill="#808080">
+            <g id="70" transform="translate(444.000000, 247.000000)">
+                <path d="M8.82929429,17 L20.0066023,17 C20.5552407,17 21,17.4438648 21,18 C21,18.5522847 20.5550537,19 20.0066023,19 L8.82929429,19 C8.41745788,20.1651924 7.30621883,21 6,21 C4.34314575,21 3,19.6568542 3,18 C3,16.3431458 4.34314575,15 6,15 C7.30621883,15 8.41745788,15.8348076 8.82929429,17 Z M9.17070571,13 L3.99339768,13 C3.44475929,13 3,12.5561352 3,12 C3,11.4477153 3.44494629,11 3.99339768,11 L9.17070571,11 C9.58254212,9.83480763 10.6937812,9 12,9 C13.3062188,9 14.4174579,9.83480763 14.8292943,11 L20.0066023,11 C20.5552407,11 21,11.4438648 21,12 C21,12.5522847 20.5550537,13 20.0066023,13 L14.8292943,13 C14.4174579,14.1651924 13.3062188,15 12,15 C10.6937812,15 9.58254212,14.1651924 9.17070571,13 Z M15.1659641,6.98648118 C15.1124525,6.99537358 15.05751,7 15.0014977,7 L3.99850233,7 C3.44704472,7 3,6.55613518 3,6 C3,5.44771525 3.44748943,5 3.99850233,5 L15.0014977,5 C15.0575314,5 15.1124871,5.00458274 15.1660053,5.01340035 C15.5740343,3.84121344 16.6887792,3 18,3 C19.6568542,3 21,4.34314575 21,6 C21,7.65685425 19.6568542,9 18,9 C16.688735,9 15.5739592,8.15872988 15.1659641,6.98648118 Z M18,7 C18.5522847,7 19,6.55228475 19,6 C19,5.44771525 18.5522847,5 18,5 C17.4477153,5 17,5.44771525 17,6 C17,6.55228475 17.4477153,7 18,7 Z M12,13 C12.5522847,13 13,12.5522847 13,12 C13,11.4477153 12.5522847,11 12,11 C11.4477153,11 11,11.4477153 11,12 C11,12.5522847 11.4477153,13 12,13 Z M6,19 C6.55228475,19 7,18.5522847 7,18 C7,17.4477153 6.55228475,17 6,17 C5.44771525,17 5,17.4477153 5,18 C5,18.5522847 5.44771525,19 6,19 Z" id="Combined-Shape"></path>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/menu/recently-added.svg b/client/src/assets/menu/recently-added.svg
new file mode 100644
index 000000000..6473837f8
--- /dev/null
+++ b/client/src/assets/menu/recently-added.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Artboard-4" transform="translate(-92.000000, -115.000000)">
+            <g id="2" transform="translate(92.000000, 115.000000)">
+                <circle id="Oval-1" stroke="#808080" stroke-width="2" cx="12" cy="12" r="10"></circle>
+                <rect id="Rectangle-1" fill="#808080" x="11" y="7" width="2" height="10" rx="1"></rect>
+                <rect id="Rectangle-1" fill="#808080" x="7" y="11" width="10" height="2" rx="1"></rect>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/menu/trending.svg b/client/src/assets/menu/trending.svg
new file mode 100644
index 000000000..ffc65cc04
--- /dev/null
+++ b/client/src/assets/menu/trending.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>graph</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
+        <g id="Artboard-4" transform="translate(-444.000000, -203.000000)" stroke-width="2" stroke="#808080">
+            <g id="50" transform="translate(444.000000, 203.000000)">
+                <polyline id="Path-96" points="3 3 3 21.006249 21.0246733 21.006249"></polyline>
+                <polyline id="Path-101" points="6 18 11 12 14 13 19 7"></polyline>
+                <polygon id="Path-102" points="20 9 20 6 17 6"></polygon>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/sass/_variables.scss b/client/src/sass/_variables.scss
index f0ffb43ba..640746722 100644
--- a/client/src/sass/_variables.scss
+++ b/client/src/sass/_variables.scss
@@ -1,12 +1,19 @@
+$font-regular: 400;
+$font-semibold: 600;
+$font-bold: 700;
+
 $grey-color: #555;
+$orange-color: #F1680D;
 
-$black-background: #1d2125;
+$black-background: #000;
 $grey-background: #f6f2f2;
 
-$menu-color-link: #9cabb8;
-$menu-color-block: #686f77;
+$button-height: 30px;
+
+$menu-color: #fff;
+$menu-width: 240px;
 
-$header-height: 65px;
+$header-height: 50px;
 $header-border-color: #e9eff6;
 
 $footer-height: 30px;
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index 47e1b6df0..58f07612b 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -1,3 +1,5 @@
+$FontPathSourceSansPro: "../fonts/source-sans-pro";
+@import '~source-sans-pro/source-sans-pro';
 @import '~primeng/resources/themes/bootstrap/theme.css';
 @import '~primeng/resources/primeng.css';
 @import '~video.js/dist/video-js.css';
@@ -7,6 +9,12 @@
   display: none !important;
 }
 
+body {
+  font-family: 'Source Sans Pro';
+  font-weight: $font-regular;
+  color: #000;
+}
+
 input.readonly {
   /* Force blank on readonly inputs */
   background-color: #fff !important;
diff --git a/client/yarn.lock b/client/yarn.lock
index c5a47bb89..8f148e431 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -4708,9 +4708,9 @@ ngc-webpack@3.2.2:
     source-map "^0.5.6"
     ts-node "^3.2.0"
 
-ngx-bootstrap@1.9.3:
-  version "1.9.3"
-  resolved "https://registry.yarnpkg.com/ngx-bootstrap/-/ngx-bootstrap-1.9.3.tgz#28e75d14fb1beaee609383d7694de4eb3ba03b26"
+ngx-bootstrap@2.0.0-beta.9:
+  version "2.0.0-beta.9"
+  resolved "https://registry.yarnpkg.com/ngx-bootstrap/-/ngx-bootstrap-2.0.0-beta.9.tgz#9aa7c88269534e7a5440481f31b137549f749796"
 
 ngx-chips@1.5.3:
   version "1.5.3"
@@ -6602,6 +6602,10 @@ source-map@^0.6.1, source-map@~0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
 
+source-sans-pro@^2.0.10:
+  version "2.0.10"
+  resolved "https://registry.yarnpkg.com/source-sans-pro/-/source-sans-pro-2.0.10.tgz#c1ca859cf164a088944c5e83745085e87cd533a9"
+
 spdx-correct@~1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
-- 
cgit v1.2.3


From 26c6ee80d0fecfce595e8970f15717560b4f4ceb Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Fri, 1 Dec 2017 13:08:46 +0100
Subject: Implement header design

---
 client/config/webpack.common.js                    |   3 +-
 client/config/webpack.video-embed.js               |   3 +-
 client/src/app/app.component.html                  |  19 ++--
 client/src/app/app.component.scss                  | 101 ++++++---------------
 client/src/app/menu/menu.component.html            |   2 +-
 client/src/app/menu/menu.component.scss            |   5 +-
 client/src/app/shared/search/search.component.html |  10 +-
 client/src/app/shared/search/search.component.scss |  55 +++++++++++
 client/src/assets/header/menu.svg                  |  14 +++
 client/src/assets/header/search.svg                |  12 +++
 client/src/assets/header/upload.svg                |  16 ++++
 client/src/sass/_mixins.scss                       |   6 ++
 client/src/sass/_variables.scss                    |   8 +-
 13 files changed, 158 insertions(+), 96 deletions(-)
 create mode 100644 client/src/assets/header/menu.svg
 create mode 100644 client/src/assets/header/search.svg
 create mode 100644 client/src/assets/header/upload.svg
 create mode 100644 client/src/sass/_mixins.scss

(limited to 'client')

diff --git a/client/config/webpack.common.js b/client/config/webpack.common.js
index 583f4ba07..acf22dab1 100644
--- a/client/config/webpack.common.js
+++ b/client/config/webpack.common.js
@@ -146,7 +146,8 @@ module.exports = function (options) {
               loader: 'sass-resources-loader',
               options: {
                 resources: [
-                  helpers.root('src/sass/_variables.scss')
+                  helpers.root('src/sass/_variables.scss'),
+                  helpers.root('src/sass/_mixins.scss')
                 ]
               }
             }
diff --git a/client/config/webpack.video-embed.js b/client/config/webpack.video-embed.js
index fe40194cf..2b70b6681 100644
--- a/client/config/webpack.video-embed.js
+++ b/client/config/webpack.video-embed.js
@@ -74,7 +74,8 @@ module.exports = function (options) {
                 loader: 'sass-resources-loader',
                 options: {
                   resources: [
-                    helpers.root('src/sass/_variables.scss')
+                    helpers.root('src/sass/_variables.scss'),
+                    helpers.root('src/sass/_mixins.scss')
                   ]
                 }
               }
diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html
index f4672c7ec..640524e23 100644
--- a/client/src/app/app.component.html
+++ b/client/src/app/app.component.html
@@ -1,14 +1,13 @@
-<div class="container-fluid">
-  <div class="row header">
+<div>
+  <div class="header">
 
     <div class="top-left-block" [ngClass]="{ 'border-bottom': isMenuDisplayed === false }">
-      <div class="hamburger-block" (click)="toggleMenu()">
-        <span class="glyphicon glyphicon-menu-hamburger"></span>
-      </div>
+      <span class="icon icon-menu" (click)="toggleMenu()"></span>
 
-      <div id="peertube-title">
-        <a [routerLink]="['/videos/list']" title="Homepage"></a>
-      </div>
+      <a id="peertube-title" [routerLink]="['/videos/list']" title="Homepage">
+        <span class="icon icon-logo"></span>
+        PeerTube
+      </a>
     </div>
 
     <div class="header-right">
@@ -16,7 +15,7 @@
     </div>
   </div>
 
-  <div class="row sub-header-container">
+  <div class="sub-header-container">
     <div class="title-menu-left">
 
       <div class="title-menu-left-block menu">
@@ -25,7 +24,7 @@
       </div>
     </div>
 
-    <div class="main-col" [ngClass]="getMainColClasses()">
+    <div class="main-col container-fluid" [ngClass]="getMainColClasses()">
 
       <div class="main-row">
         <router-outlet></router-outlet>
diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss
index 28e86097c..f245d0563 100644
--- a/client/src/app/app.component.scss
+++ b/client/src/app/app.component.scss
@@ -28,8 +28,10 @@
 .header {
   height: $header-height;
   position: fixed;
+  top: 0;
   width: 100%;
   background-color: #fff;
+  z-index: 1000;
   box-shadow: 0 1px 3px rgba(0, 0, 0, 0.16);
   display: flex;
 
@@ -37,33 +39,36 @@
     width: $menu-width;
     z-index: 1001;
     height: $header-height;
-    line-height: $header-height;
-    margin-top: 0;
-    margin-bottom: 0;
     display: flex;
-    padding: 0;
-
-    .hamburger-block {
-      margin-right: 10px;
-      margin-left: 25px;
-
-      .glyphicon {
-        cursor: pointer;
+    align-items: center;
+
+    .icon {
+      cursor: pointer;
+      width: 22px;
+      height: 22px;
+      display: inline-block;
+      background-size: contain;
+
+      &.icon-menu {
+        background-image: url('../assets/header/menu.svg');
+        margin: 0 18px 0 24px;
       }
     }
 
     #peertube-title {
-      a {
-        color: inherit !important;
-        display: block;
+      font-size: 20px;
+      font-weight: $font-bold;
+      color: inherit !important;
+      display: flex;
+      align-items: center;
+
+      @include disable-default-a-behaviour;
+
+      .icon.icon-logo {
+        display: inline-block;
         background: url('../assets/logo.svg') no-repeat;
-        width: 24px;
+        width: 20px;
         height: 24px;
-
-        &:hover {
-          color: inherit !important;
-          text-decoration: none !important;
-        }
       }
     }
 
@@ -71,65 +76,15 @@
       #peertube-title {
         display: none;
       }
-
-      .hamburger-block {
-        width: 100%;
-        text-align: center;
-      }
-    }
-
-    @media screen and (min-width: 500px) and (max-width: 600px) {
-      #peertube-title a {
-        width: 80px;
-      }
-    }
-
-    @media screen and (min-width: 600px) and (max-width: 700px) {
-      #peertube-title a {
-        width: 100px;
-      }
-    }
-
-    @media screen and (min-width: 1000px) {
-      #peertube-title a {
-        width: 120px;
-      }
-    }
-
-    @media screen and (min-width: 1000px) {
-      #peertube-title a {
-        width: 120px;
-      }
-    }
-
-    @media screen and (min-width: 1200px) {
-      padding-left: 15px;
-
-      .hamburger-block {
-        margin-right: 15px;
-      }
-
-      #peertube-title a {
-        width: 135px;
-      }
-    }
-
-    @media screen and (min-width: 1600px) {
-      .hamburger-block {
-        margin-right: 20px;
-      }
-
-      #peertube-title a {
-        width: 180px;
-      }
     }
   }
 
   .header-right {
-    text-align: right;
     height: $header-height;
-    margin-left: $menu-width;
+    display: flex;
+    align-items: center;
     flex-grow: 1;
+    justify-content: flex-end;
   }
 }
 
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html
index bb0caaef5..fb31c0734 100644
--- a/client/src/app/menu/menu.component.html
+++ b/client/src/app/menu/menu.component.html
@@ -19,7 +19,7 @@
   </div>
 
   <div *ngIf="!isLoggedIn" class="button-block">
-    <a routerLink="/login"class="login-button">Login</a>
+    <a routerLink="/login" class="login-button">Login</a>
     <a *ngIf="isRegistrationAllowed()" routerLink="/signup" class="create-account-button">Create an account</a>
   </div>
 
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss
index 8a4910605..2c2106733 100644
--- a/client/src/app/menu/menu.component.scss
+++ b/client/src/app/menu/menu.component.scss
@@ -123,9 +123,6 @@ menu {
     cursor: pointer;
     margin-bottom: 15px;
 
-    &:hover, &:focus {
-      text-decoration: none !important;
-      outline: none !important;
-    }
+    @include disable-default-a-behaviour;
   }
 }
diff --git a/client/src/app/shared/search/search.component.html b/client/src/app/shared/search/search.component.html
index 0e3de150c..9bc9bafe4 100644
--- a/client/src/app/shared/search/search.component.html
+++ b/client/src/app/shared/search/search.component.html
@@ -1,6 +1,10 @@
 <input
-  type="text" id="search-video" name="search-video" placeholder="Search"
-  [(ngModel)]="searchCriteria.value" (keyup.enter)="doSearch()"
+    type="text" id="search-video" name="search-video" placeholder="Search..."
+    [(ngModel)]="searchCriteria.value" (keyup.enter)="doSearch()"
 >
+<span (click)="doSearch()" class="icon icon-search"></span>
 
-<a routerLink="/videos/upload">Upload</a>
+<a class="upload-button" routerLink="/videos/upload">
+  <span class="icon icon-upload"></span>
+  Upload
+</a>
diff --git a/client/src/app/shared/search/search.component.scss b/client/src/app/shared/search/search.component.scss
index e69de29bb..ffd891904 100644
--- a/client/src/app/shared/search/search.component.scss
+++ b/client/src/app/shared/search/search.component.scss
@@ -0,0 +1,55 @@
+#search-video {
+  display: inline-block;
+  height: $button-height;
+  width: $search-input-width;
+  margin-right: 15px;
+  padding-right: 25px; // For the search icon
+  background: #fff;
+  border: 1px solid #C6C6C6;
+  border-radius: 3px;
+  padding-left: 15px;
+
+  &::placeholder {
+    color: #000;
+  }
+}
+
+.icon.icon-search {
+  display: inline-block;
+  background: url('../../../assets/header/search.svg') no-repeat;
+  background-size: contain;
+  width: 25px;
+  height: 21px;
+  vertical-align: middle;
+  cursor: pointer;
+  // yolo
+  position: absolute;
+  margin-left: -50px;
+  margin-top: 5px;
+}
+
+.upload-button {
+  display: inline-block;
+  color: #fff;
+  font-weight: $font-semibold;
+  font-size: 15px;
+  height: $button-height;
+  line-height: $button-height;
+  border-radius: 3px;
+  text-align: center;
+  margin-right: 25px;
+  background-color: $orange-color;
+  padding: 0 17px 0 13px;
+
+  @include disable-default-a-behaviour;
+
+  .icon.icon-upload {
+    display: inline-block;
+    background: url('../../../assets/header/upload.svg') no-repeat;
+    background-size: contain;
+    width: 22px;
+    height: 24px;
+    vertical-align: middle;
+    margin-right: 6px;
+  }
+}
diff --git a/client/src/assets/header/menu.svg b/client/src/assets/header/menu.svg
new file mode 100644
index 000000000..7101bf73b
--- /dev/null
+++ b/client/src/assets/header/menu.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>menu</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Artboard-4" transform="translate(-180.000000, -203.000000)" stroke="#333333">
+            <g id="44" transform="translate(180.000000, 203.000000)">
+                <path d="M3.5,7 C3.5,6.72319836 3.72175357,6.5 3.99339768,6.5 L20.0066023,6.5 C20.2799786,6.5 20.5,6.72089465 20.5,7 C20.5,7.27680164 20.2782464,7.5 20.0066023,7.5 L3.99339768,7.5 C3.72002141,7.5 3.5,7.27910535 3.5,7 Z M3.5,12 C3.5,11.7231984 3.72175357,11.5 3.99339768,11.5 L20.0066023,11.5 C20.2799786,11.5 20.5,11.7208946 20.5,12 C20.5,12.2768016 20.2782464,12.5 20.0066023,12.5 L3.99339768,12.5 C3.72002141,12.5 3.5,12.2791054 3.5,12 Z M3.5,17 C3.5,16.7231984 3.72175357,16.5 3.99339768,16.5 L20.0066023,16.5 C20.2799786,16.5 20.5,16.7208946 20.5,17 C20.5,17.2768016 20.2782464,17.5 20.0066023,17.5 L3.99339768,17.5 C3.72002141,17.5 3.5,17.2791054 3.5,17 Z" id="Combined-Shape"></path>
+            </g>
+        </g>
+    </g>
+</svg>
\ No newline at end of file
diff --git a/client/src/assets/header/search.svg b/client/src/assets/header/search.svg
new file mode 100644
index 000000000..489b59e9b
--- /dev/null
+++ b/client/src/assets/header/search.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Artboard-4" transform="translate(-136.000000, -115.000000)" stroke="#000" stroke-width="2">
+            <g id="3" transform="translate(136.000000, 115.000000)">
+                <circle id="Oval-3" cx="10" cy="10" r="7"></circle>
+                <path d="M15,15 L21,21" id="Path-3" stroke-linecap="round" stroke-linejoin="round"></path>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/header/upload.svg b/client/src/assets/header/upload.svg
new file mode 100644
index 000000000..2b07caf76
--- /dev/null
+++ b/client/src/assets/header/upload.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>cloud-upload</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g id="Artboard-4" transform="translate(-312.000000, -775.000000)" stroke="#fff" stroke-width="2">
+            <g id="307" transform="translate(312.000000, 775.000000)">
+                <path d="M8,18 L5,18 L5,18 C2.790861,18 1,16.209139 1,14 C1,11.790861 2.790861,10 5,10 C5.35840468,10 5.70579988,10.0471371 6.03632437,10.1355501 C6.01233106,9.92702603 6,9.71495305 6,9.5 C6,6.46243388 8.46243388,4 11.5,4 C14.0673313,4 16.2238156,5.7590449 16.8299648,8.1376465 C17.2052921,8.04765874 17.5970804,8 18,8 C20.7614237,8 23,10.2385763 23,13 C23,15.7614237 20.7614237,18 18,18 L16,18" id="Combined-Shape" stroke-linejoin="round"></path>
+                <path d="M12,13 L12,21" id="Path-58"></path>
+                <polyline id="Path-59" stroke-linejoin="round" transform="translate(12.000000, 12.500000) scale(1, -1) translate(-12.000000, -12.500000) " points="15 11 12 14 9 11"></polyline>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/sass/_mixins.scss b/client/src/sass/_mixins.scss
new file mode 100644
index 000000000..681657d90
--- /dev/null
+++ b/client/src/sass/_mixins.scss
@@ -0,0 +1,6 @@
+@mixin disable-default-a-behaviour {
+  &:hover, &:focus {
+    text-decoration: none !important;
+    outline: none !important;
+  }
+}
diff --git a/client/src/sass/_variables.scss b/client/src/sass/_variables.scss
index 640746722..e32b37462 100644
--- a/client/src/sass/_variables.scss
+++ b/client/src/sass/_variables.scss
@@ -10,12 +10,14 @@ $grey-background: #f6f2f2;
 
 $button-height: 30px;
 
-$menu-color: #fff;
-$menu-width: 240px;
-
 $header-height: 50px;
 $header-border-color: #e9eff6;
 
+$search-input-width: 375px;
+
+$menu-color: #fff;
+$menu-width: 240px;
+
 $footer-height: 30px;
 $footer-margin: 30px;
 
-- 
cgit v1.2.3


From 9bf9d2a5c223bf006496ae7adf0c0bd7a7975108 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Fri, 1 Dec 2017 14:46:22 +0100
Subject: Begin videos list new design

---
 client/package.json                                |  2 +-
 client/src/app/menu/menu.component.html            |  4 +-
 client/src/app/shared/misc/from-now.pipe.ts        | 37 +++++++++
 .../src/app/shared/misc/number-formatter.pipe.ts   | 19 +++++
 client/src/app/shared/shared.module.ts             | 26 +++---
 .../app/videos/+video-edit/video-add.component.ts  |  2 +-
 client/src/app/videos/video-list/index.ts          |  3 +-
 .../app/videos/video-list/my-videos.component.ts   |  2 +-
 .../video-list/shared/abstract-video-list.html     | 18 +----
 .../video-list/shared/abstract-video-list.scss     | 14 ----
 .../video-list/shared/abstract-video-list.ts       | 28 ++-----
 client/src/app/videos/video-list/shared/index.ts   |  1 -
 .../shared/video-miniature.component.html          | 13 +--
 .../shared/video-miniature.component.scss          | 66 +++++----------
 .../video-list/shared/video-sort.component.html    |  5 --
 .../video-list/shared/video-sort.component.ts      | 39 ---------
 .../app/videos/video-list/video-list.component.ts  | 94 ----------------------
 .../video-list/video-recently-added.component.ts   | 33 ++++++++
 .../videos/video-list/video-trending.component.ts  | 33 ++++++++
 client/src/app/videos/videos-routing.module.ts     | 26 ++++--
 client/src/app/videos/videos.module.ts             | 10 ++-
 client/src/sass/application.scss                   | 26 ++----
 client/yarn.lock                                   |  8 +-
 23 files changed, 213 insertions(+), 296 deletions(-)
 create mode 100644 client/src/app/shared/misc/from-now.pipe.ts
 create mode 100644 client/src/app/shared/misc/number-formatter.pipe.ts
 delete mode 100644 client/src/app/videos/video-list/shared/video-sort.component.html
 delete mode 100644 client/src/app/videos/video-list/shared/video-sort.component.ts
 delete mode 100644 client/src/app/videos/video-list/video-list.component.ts
 create mode 100644 client/src/app/videos/video-list/video-recently-added.component.ts
 create mode 100644 client/src/app/videos/video-list/video-trending.component.ts

(limited to 'client')

diff --git a/client/package.json b/client/package.json
index c551c995a..310860fec 100644
--- a/client/package.json
+++ b/client/package.json
@@ -43,7 +43,6 @@
     "@types/webpack": "^3.0.0",
     "@types/webtorrent": "^0.98.4",
     "add-asset-html-webpack-plugin": "^2.0.1",
-    "angular-pipes": "^6.0.0",
     "angular2-notifications": "^0.7.7",
     "angular2-template-loader": "^0.6.0",
     "assets-webpack-plugin": "^3.4.0",
@@ -72,6 +71,7 @@
     "ngc-webpack": "3.2.2",
     "ngx-bootstrap": "2.0.0-beta.9",
     "ngx-chips": "1.5.3",
+    "ngx-pipes": "^2.0.5",
     "node-sass": "^4.1.1",
     "normalize.css": "^7.0.0",
     "optimize-js-plugin": "0.0.4",
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html
index fb31c0734..21f8d8ba4 100644
--- a/client/src/app/menu/menu.component.html
+++ b/client/src/app/menu/menu.component.html
@@ -26,12 +26,12 @@
   <div class="panel-block">
     <div class="block-title">Videos</div>
 
-    <a routerLink="/videos/list" routerLinkActive="active">
+    <a routerLink="/videos/trending" routerLinkActive="active">
       <span class="icon icon-videos-trending"></span>
       Trending
     </a>
 
-    <a routerLink="/videos/list" routerLinkActive="active">
+    <a routerLink="/videos/recently-added" routerLinkActive="active">
       <span class="icon icon-videos-recently-added"></span>
       Recently added
     </a>
diff --git a/client/src/app/shared/misc/from-now.pipe.ts b/client/src/app/shared/misc/from-now.pipe.ts
new file mode 100644
index 000000000..25e5d6a85
--- /dev/null
+++ b/client/src/app/shared/misc/from-now.pipe.ts
@@ -0,0 +1,37 @@
+import { Pipe, PipeTransform } from '@angular/core'
+
+// Thanks: https://github.com/danrevah/ngx-pipes/blob/master/src/pipes/math/bytes.ts
+
+@Pipe({name: 'fromNow'})
+export class FromNowPipe implements PipeTransform {
+
+  transform (value: number) {
+    const seconds = Math.floor((Date.now() - value) / 1000)
+
+    let interval = Math.floor(seconds / 31536000)
+    if (interval > 1) {
+      return interval + ' years ago'
+    }
+
+    interval = Math.floor(seconds / 2592000)
+    if (interval > 1) return interval + ' months ago'
+    if (interval === 1) return interval + ' month ago'
+
+    interval = Math.floor(seconds / 604800)
+    if (interval > 1) return interval + ' weeks ago'
+    if (interval === 1) return interval + ' week ago'
+
+    interval = Math.floor(seconds / 86400)
+    if (interval > 1) return interval + ' days ago'
+    if (interval === 1) return interval + ' day ago'
+
+    interval = Math.floor(seconds / 3600)
+    if (interval > 1) return interval + ' hours ago'
+    if (interval === 1) return interval + ' hour ago'
+
+    interval = Math.floor(seconds / 60)
+    if (interval >= 1) return interval + ' min ago'
+
+    return Math.floor(seconds) + ' sec ago'
+  }
+}
diff --git a/client/src/app/shared/misc/number-formatter.pipe.ts b/client/src/app/shared/misc/number-formatter.pipe.ts
new file mode 100644
index 000000000..2491fb1d6
--- /dev/null
+++ b/client/src/app/shared/misc/number-formatter.pipe.ts
@@ -0,0 +1,19 @@
+import { Pipe, PipeTransform } from '@angular/core'
+
+// Thanks: https://github.com/danrevah/ngx-pipes/blob/master/src/pipes/math/bytes.ts
+
+@Pipe({name: 'numberFormatter'})
+export class NumberFormatterPipe implements PipeTransform {
+  private dictionary: Array<{max: number, type: string}> = [
+    { max: 1000, type: '' },
+    { max: 1000000, type: 'K' },
+    { max: 1000000000, type: 'M' }
+  ]
+
+  transform (value: number) {
+    const format = this.dictionary.find(d => value < d.max) || this.dictionary[this.dictionary.length - 1]
+    const calc = Math.floor(value / (format.max / 1000))
+
+    return `${calc}${format.type}`
+  }
+}
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 456ce851e..c7ea6e603 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -1,25 +1,26 @@
-import { NgModule } from '@angular/core'
-import { HttpClientModule } from '@angular/common/http'
 import { CommonModule } from '@angular/common'
+import { HttpClientModule } from '@angular/common/http'
+import { NgModule } from '@angular/core'
 import { FormsModule, ReactiveFormsModule } from '@angular/forms'
 import { RouterModule } from '@angular/router'
 
-import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe'
-import { KeysPipe } from 'angular-pipes/src/object/keys.pipe'
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown'
-import { ProgressbarModule } from 'ngx-bootstrap/progressbar'
-import { PaginationModule } from 'ngx-bootstrap/pagination'
 import { ModalModule } from 'ngx-bootstrap/modal'
-import { DataTableModule } from 'primeng/components/datatable/datatable'
+import { PaginationModule } from 'ngx-bootstrap/pagination'
+import { ProgressbarModule } from 'ngx-bootstrap/progressbar'
+import { BytesPipe, KeysPipe } from 'ngx-pipes'
 import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared'
+import { DataTableModule } from 'primeng/components/datatable/datatable'
 
 import { AUTH_INTERCEPTOR_PROVIDER } from './auth'
+import { LoaderComponent } from './misc/loader.component'
 import { RestExtractor, RestService } from './rest'
 import { SearchComponent, SearchService } from './search'
 import { UserService } from './users'
 import { VideoAbuseService } from './video-abuse'
 import { VideoBlacklistService } from './video-blacklist'
-import { LoaderComponent } from './misc/loader.component'
+import { NumberFormatterPipe } from './misc/number-formatter.pipe'
+import { FromNowPipe } from './misc/from-now.pipe'
 
 @NgModule({
   imports: [
@@ -42,7 +43,9 @@ import { LoaderComponent } from './misc/loader.component'
     BytesPipe,
     KeysPipe,
     SearchComponent,
-    LoaderComponent
+    LoaderComponent,
+    NumberFormatterPipe,
+    FromNowPipe
   ],
 
   exports: [
@@ -62,7 +65,10 @@ import { LoaderComponent } from './misc/loader.component'
     KeysPipe,
 
     SearchComponent,
-    LoaderComponent
+    LoaderComponent,
+
+    NumberFormatterPipe,
+    FromNowPipe
   ],
 
   providers: [
diff --git a/client/src/app/videos/+video-edit/video-add.component.ts b/client/src/app/videos/+video-edit/video-add.component.ts
index 1704cf486..76bfbb515 100644
--- a/client/src/app/videos/+video-edit/video-add.component.ts
+++ b/client/src/app/videos/+video-edit/video-add.component.ts
@@ -184,7 +184,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
           this.notificationsService.success('Success', 'Video uploaded.')
 
           // Display all the videos once it's finished
-          this.router.navigate([ '/videos/list' ])
+          this.router.navigate([ '/videos/trending' ])
         }
       },
 
diff --git a/client/src/app/videos/video-list/index.ts b/client/src/app/videos/video-list/index.ts
index ed2bb1657..a5a60364a 100644
--- a/client/src/app/videos/video-list/index.ts
+++ b/client/src/app/videos/video-list/index.ts
@@ -1,3 +1,4 @@
 export * from './my-videos.component'
-export * from './video-list.component'
+export * from './video-recently-added.component'
+export * from './video-trending.component'
 export * from './shared'
diff --git a/client/src/app/videos/video-list/my-videos.component.ts b/client/src/app/videos/video-list/my-videos.component.ts
index 648741a40..146db8262 100644
--- a/client/src/app/videos/video-list/my-videos.component.ts
+++ b/client/src/app/videos/video-list/my-videos.component.ts
@@ -27,7 +27,7 @@ export class MyVideosComponent extends AbstractVideoList implements OnInit, OnDe
   }
 
   ngOnDestroy () {
-    this.subActivatedRoute.unsubscribe()
+    super.ngOnDestroy()
   }
 
   getVideosObservable () {
diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.html b/client/src/app/videos/video-list/shared/abstract-video-list.html
index 680fba3f5..ab5530e68 100644
--- a/client/src/app/videos/video-list/shared/abstract-video-list.html
+++ b/client/src/app/videos/video-list/shared/abstract-video-list.html
@@ -1,20 +1,8 @@
-<div class="row">
-  <div class="content-padding">
-    <div class="videos-info">
-      <div class="col-md-9 col-xs-5 videos-total-results">
-        <span *ngIf="pagination.totalItems !== null">{{ pagination.totalItems }} videos</span>
-
-        <my-loader [loading]="loading | async"></my-loader>
-      </div>
-
-      <my-video-sort class="col-md-3 col-xs-7" [currentSort]="sort" (sort)="onSort($event)"></my-video-sort>
-    </div>
-  </div>
+<div class="title-page">
+  {{ titlePage }}
 </div>
 
-<div class="content-padding videos-miniatures">
-  <div class="no-video" *ngIf="isThereNoVideo()">There is no video.</div>
-
+<div class="videos-miniatures">
   <my-video-miniature
     class="ng-animate"
     *ngFor="let video of videos" [video]="video" [user]="user" [currentSort]="sort"
diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.scss b/client/src/app/videos/video-list/shared/abstract-video-list.scss
index 4b4409602..de174802b 100644
--- a/client/src/app/videos/video-list/shared/abstract-video-list.scss
+++ b/client/src/app/videos/video-list/shared/abstract-video-list.scss
@@ -17,20 +17,6 @@
   }
 }
 
-.videos-miniatures {
-  text-align: center;
-  padding-top: 0;
-
-  my-video-miniature {
-    text-align: left;
-  }
-
-  .no-video {
-    margin-top: 50px;
-    text-align: center;
-  }
-}
-
 pagination {
   display: block;
   text-align: center;
diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.ts b/client/src/app/videos/video-list/shared/abstract-video-list.ts
index 87d5bc48a..262ea4e21 100644
--- a/client/src/app/videos/video-list/shared/abstract-video-list.ts
+++ b/client/src/app/videos/video-list/shared/abstract-video-list.ts
@@ -1,25 +1,19 @@
 import { OnDestroy, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
-import { Subscription } from 'rxjs/Subscription'
-import { BehaviorSubject } from 'rxjs/BehaviorSubject'
-import { Observable } from 'rxjs/Observable'
 
 import { NotificationsService } from 'angular2-notifications'
+import { Observable } from 'rxjs/Observable'
+import { Subscription } from 'rxjs/Subscription'
 
-import {
-  SortField,
-  Video,
-  VideoPagination
-} from '../../shared'
+import { SortField, Video, VideoPagination } from '../../shared'
 
 export abstract class AbstractVideoList implements OnInit, OnDestroy {
-  loading: BehaviorSubject<boolean> = new BehaviorSubject(false)
   pagination: VideoPagination = {
     currentPage: 1,
     itemsPerPage: 25,
     totalItems: null
   }
-  sort: SortField
+  sort: SortField = '-createdAt'
   videos: Video[] = []
 
   protected notificationsService: NotificationsService
@@ -28,6 +22,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
 
   protected subActivatedRoute: Subscription
 
+  abstract titlePage: string
   abstract getVideosObservable (): Observable<{ videos: Video[], totalVideos: number}>
 
   ngOnInit () {
@@ -44,7 +39,6 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
   }
 
   getVideos () {
-    this.loading.next(true)
     this.videos = []
 
     const observable = this.getVideosObservable()
@@ -53,17 +47,11 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
       ({ videos, totalVideos }) => {
         this.videos = videos
         this.pagination.totalItems = totalVideos
-
-        this.loading.next(false)
       },
       error => this.notificationsService.error('Error', error.text)
     )
   }
 
-  isThereNoVideo () {
-    return !this.loading.getValue() && this.videos.length === 0
-  }
-
   onPageChanged (event: { page: number }) {
     // Be sure the current page is set
     this.pagination.currentPage = event.page
@@ -71,12 +59,6 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
     this.navigateToNewParams()
   }
 
-  onSort (sort: SortField) {
-    this.sort = sort
-
-    this.navigateToNewParams()
-  }
-
   protected buildRouteParams () {
     // There is always a sort and a current page
     const params = {
diff --git a/client/src/app/videos/video-list/shared/index.ts b/client/src/app/videos/video-list/shared/index.ts
index d8f73bcda..170ca4832 100644
--- a/client/src/app/videos/video-list/shared/index.ts
+++ b/client/src/app/videos/video-list/shared/index.ts
@@ -1,3 +1,2 @@
 export * from './abstract-video-list'
 export * from './video-miniature.component'
-export * from './video-sort.component'
diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.html b/client/src/app/videos/video-list/shared/video-miniature.component.html
index 6bbd29666..aea85b6c6 100644
--- a/client/src/app/videos/video-list/shared/video-miniature.component.html
+++ b/client/src/app/videos/video-list/shared/video-miniature.component.html
@@ -6,8 +6,7 @@
     <img [attr.src]="video.thumbnailUrl" alt="video thumbnail" [ngClass]="{ 'blur-filter': isVideoNSFWForThisUser() }" />
 
     <div class="video-miniature-thumbnail-overlay">
-      <span class="video-miniature-thumbnail-overlay-views">{{ video.views }} views</span>
-      <span class="video-miniature-thumbnail-overlay-duration">{{ video.durationLabel }}</span>
+      {{ video.durationLabel }}
     </div>
   </a>
 
@@ -21,13 +20,7 @@
       </a>
     </span>
 
-    <div class="video-miniature-tags">
-      <span *ngFor="let tag of video.tags" class="video-miniature-tag">
-        <a [routerLink]="['/videos/list', { field: 'tags', search: tag, sort: currentSort }]" class="label label-primary">{{ tag }}</a>
-      </span>
-    </div>
-
-    <a [routerLink]="['/videos/list', { field: 'account', search: video.account, sort: currentSort }]" class="video-miniature-account">{{ video.by }}</a>
-    <span class="video-miniature-created-at">{{ video.createdAt | date:'short' }}</span>
+    <span class="video-miniature-created-at-views">{{ video.createdAt | fromNow }} - {{ video.views | numberFormatter }} views</span>
+    <span class="video-miniature-account">{{ video.by }}</span>
   </div>
 </div>
diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.scss b/client/src/app/videos/video-list/shared/video-miniature.component.scss
index 507ace098..ed15864d9 100644
--- a/client/src/app/videos/video-list/shared/video-miniature.component.scss
+++ b/client/src/app/videos/video-list/shared/video-miniature.component.scss
@@ -1,14 +1,14 @@
 .video-miniature {
-  margin: 15px 10px;
   display: inline-block;
-  position: relative;
-  height: 190px;
+  padding-right: 15px;
+  margin-bottom: 30px;
+  height: 175px;
   vertical-align: top;
 
   .video-miniature-thumbnail {
     display: inline-block;
     position: relative;
-    border-radius: 3px;
+    border-radius: 4px;
     overflow: hidden;
 
     &:hover {
@@ -22,38 +22,33 @@
 
     .video-miniature-thumbnail-overlay {
       position: absolute;
-      right: 0px;
-      bottom: 0px;
+      right: 5px;
+      bottom: 5px;
       display: inline-block;
       background-color: rgba(0, 0, 0, 0.7);
       color: #fff;
-      padding: 3px 5px;
-      font-size: 11px;
-      font-weight: bold;
-      width: 100%;
-
-      .video-miniature-thumbnail-overlay-views {
-
-      }
-
-      .video-miniature-thumbnail-overlay-duration {
-        float: right;
-      }
+      font-size: 12px;
+      font-weight: $font-bold;
+      border-radius: 3px;
+      padding: 0 5px;
     }
   }
 
   .video-miniature-information {
     width: 200px;
+    margin-top: 2px;
+    line-height: normal;
 
     .video-miniature-name {
-      height: 23px;
       display: block;
       overflow: hidden;
       text-overflow: ellipsis;
       white-space: nowrap;
       font-weight: bold;
       transition: color 0.2s;
-      font-size: 15px;
+      font-size: 16px;
+      font-weight: $font-semibold;
+      color: #000;
 
       &:hover {
         text-decoration: none;
@@ -63,39 +58,16 @@
         filter: blur(3px);
         padding-left: 4px;
       }
-
-      .video-miniature-tags {
-        // Fix for chrome when tags are long
-        width: 201px;
-
-        .video-miniature-tag {
-          font-size: 13px;
-          cursor: pointer;
-          position: relative;
-          top: -2px;
-
-          .label {
-            transition: background-color 0.2s;
-          }
-        }
-      }
     }
 
-    .video-miniature-account, .video-miniature-created-at {
+    .video-miniature-created-at-views {
       display: block;
-      margin-left: 1px;
-      font-size: 11px;
-      color: $video-miniature-other-infos;
-      opacity: 0.9;
+      font-size: 13px;
     }
 
     .video-miniature-account {
-      transition: color 0.2s;
-
-      &:hover {
-        color: #23527c;
-        text-decoration: none;
-      }
+      font-size: 12px;
+      color: #585858;
     }
   }
 }
diff --git a/client/src/app/videos/video-list/shared/video-sort.component.html b/client/src/app/videos/video-list/shared/video-sort.component.html
deleted file mode 100644
index 3bece0b22..000000000
--- a/client/src/app/videos/video-list/shared/video-sort.component.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<select class="form-control input-sm" [(ngModel)]="currentSort" (ngModelChange)="onSortChange()">
-  <option *ngFor="let choice of choiceKeys" [value]="choice">
-    {{ getStringChoice(choice) }}
-  </option>
-</select>
diff --git a/client/src/app/videos/video-list/shared/video-sort.component.ts b/client/src/app/videos/video-list/shared/video-sort.component.ts
deleted file mode 100644
index 8aa89d32b..000000000
--- a/client/src/app/videos/video-list/shared/video-sort.component.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { Component, EventEmitter, Input, Output } from '@angular/core'
-
-import { SortField } from '../../shared'
-
-@Component({
-  selector: 'my-video-sort',
-  templateUrl: './video-sort.component.html'
-})
-
-export class VideoSortComponent {
-  @Output() sort = new EventEmitter<any>()
-
-  @Input() currentSort: SortField
-
-  sortChoices: { [ P in SortField ]: string } = {
-    'name': 'Name - Asc',
-    '-name': 'Name - Desc',
-    'duration': 'Duration - Asc',
-    '-duration': 'Duration - Desc',
-    'createdAt': 'Created Date - Asc',
-    '-createdAt': 'Created Date - Desc',
-    'views': 'Views - Asc',
-    '-views': 'Views - Desc',
-    'likes': 'Likes - Asc',
-    '-likes': 'Likes - Desc'
-  }
-
-  get choiceKeys () {
-    return Object.keys(this.sortChoices)
-  }
-
-  getStringChoice (choiceKey: SortField) {
-    return this.sortChoices[choiceKey]
-  }
-
-  onSortChange () {
-    this.sort.emit(this.currentSort)
-  }
-}
diff --git a/client/src/app/videos/video-list/video-list.component.ts b/client/src/app/videos/video-list/video-list.component.ts
deleted file mode 100644
index 784162679..000000000
--- a/client/src/app/videos/video-list/video-list.component.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import { Component, OnDestroy, OnInit } from '@angular/core'
-import { ActivatedRoute, Router } from '@angular/router'
-import { Subscription } from 'rxjs/Subscription'
-
-import { NotificationsService } from 'angular2-notifications'
-
-import { VideoService } from '../shared'
-import { Search, SearchField, SearchService } from '../../shared'
-import { AbstractVideoList } from './shared'
-
-@Component({
-  selector: 'my-videos-list',
-  styleUrls: [ './shared/abstract-video-list.scss' ],
-  templateUrl: './shared/abstract-video-list.html'
-})
-export class VideoListComponent extends AbstractVideoList implements OnInit, OnDestroy {
-  private search: Search
-  private subSearch: Subscription
-
-  constructor (
-    protected router: Router,
-    protected route: ActivatedRoute,
-    protected notificationsService: NotificationsService,
-    private videoService: VideoService,
-    private searchService: SearchService
-  ) {
-    super()
-  }
-
-  ngOnInit () {
-    // Subscribe to route changes
-    this.subActivatedRoute = this.route.params.subscribe(routeParams => {
-      this.loadRouteParams(routeParams)
-
-      // Update the search service component
-      this.searchService.updateSearch.next(this.search)
-      this.getVideos()
-    })
-
-    // Subscribe to search changes
-    this.subSearch = this.searchService.searchUpdated.subscribe(search => {
-      this.search = search
-      // Reset pagination
-      this.pagination.currentPage = 1
-
-      this.navigateToNewParams()
-    })
-  }
-
-  ngOnDestroy () {
-    super.ngOnDestroy()
-
-    this.subSearch.unsubscribe()
-  }
-
-  getVideosObservable () {
-    let observable = null
-    if (this.search.value) {
-      observable = this.videoService.searchVideos(this.search, this.pagination, this.sort)
-    } else {
-      observable = this.videoService.getVideos(this.pagination, this.sort)
-    }
-
-    return observable
-  }
-
-  protected buildRouteParams () {
-    const params = super.buildRouteParams()
-
-    // Maybe there is a search
-    if (this.search.value) {
-      params['field'] = this.search.field
-      params['search'] = this.search.value
-    }
-
-    return params
-  }
-
-  protected loadRouteParams (routeParams: { [ key: string ]: any }) {
-    super.loadRouteParams(routeParams)
-
-    if (routeParams['search'] !== undefined) {
-      this.search = {
-        value: routeParams['search'],
-        field: routeParams['field'] as SearchField
-      }
-    } else {
-      this.search = {
-        value: '',
-        field: 'name'
-      }
-    }
-  }
-}
diff --git a/client/src/app/videos/video-list/video-recently-added.component.ts b/client/src/app/videos/video-list/video-recently-added.component.ts
new file mode 100644
index 000000000..dbba264df
--- /dev/null
+++ b/client/src/app/videos/video-list/video-recently-added.component.ts
@@ -0,0 +1,33 @@
+import { Component, OnDestroy, OnInit } from '@angular/core'
+import { ActivatedRoute, Router } from '@angular/router'
+import { NotificationsService } from 'angular2-notifications'
+import { VideoService } from '../shared'
+import { AbstractVideoList } from './shared'
+
+@Component({
+  selector: 'my-videos-recently-added',
+  styleUrls: [ './shared/abstract-video-list.scss' ],
+  templateUrl: './shared/abstract-video-list.html'
+})
+export class VideoRecentlyAddedComponent extends AbstractVideoList implements OnInit, OnDestroy {
+  titlePage = 'Recently added'
+
+  constructor (protected router: Router,
+               protected route: ActivatedRoute,
+               protected notificationsService: NotificationsService,
+               private videoService: VideoService) {
+    super()
+  }
+
+  ngOnInit () {
+    super.ngOnInit()
+  }
+
+  ngOnDestroy () {
+    super.ngOnDestroy()
+  }
+
+  getVideosObservable () {
+    return this.videoService.getVideos(this.pagination, this.sort)
+  }
+}
diff --git a/client/src/app/videos/video-list/video-trending.component.ts b/client/src/app/videos/video-list/video-trending.component.ts
new file mode 100644
index 000000000..b97966c12
--- /dev/null
+++ b/client/src/app/videos/video-list/video-trending.component.ts
@@ -0,0 +1,33 @@
+import { Component, OnDestroy, OnInit } from '@angular/core'
+import { ActivatedRoute, Router } from '@angular/router'
+import { NotificationsService } from 'angular2-notifications'
+import { VideoService } from '../shared'
+import { AbstractVideoList } from './shared'
+
+@Component({
+  selector: 'my-videos-trending',
+  styleUrls: [ './shared/abstract-video-list.scss' ],
+  templateUrl: './shared/abstract-video-list.html'
+})
+export class VideoTrendingComponent extends AbstractVideoList implements OnInit, OnDestroy {
+  titlePage = 'Trending'
+
+  constructor (protected router: Router,
+               protected route: ActivatedRoute,
+               protected notificationsService: NotificationsService,
+               private videoService: VideoService) {
+    super()
+  }
+
+  ngOnInit () {
+    super.ngOnInit()
+  }
+
+  ngOnDestroy () {
+    super.ngOnDestroy()
+  }
+
+  getVideosObservable () {
+    return this.videoService.getVideos(this.pagination, this.sort)
+  }
+}
diff --git a/client/src/app/videos/videos-routing.module.ts b/client/src/app/videos/videos-routing.module.ts
index 3ca3e5486..1f894df7a 100644
--- a/client/src/app/videos/videos-routing.module.ts
+++ b/client/src/app/videos/videos-routing.module.ts
@@ -1,9 +1,9 @@
 import { NgModule } from '@angular/core'
 import { RouterModule, Routes } from '@angular/router'
-
 import { MetaGuard } from '@ngx-meta/core'
-
-import { VideoListComponent, MyVideosComponent } from './video-list'
+import { MyVideosComponent } from './video-list'
+import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component'
+import { VideoTrendingComponent } from './video-list/video-trending.component'
 import { VideosComponent } from './videos.component'
 
 const videosRoutes: Routes = [
@@ -12,6 +12,11 @@ const videosRoutes: Routes = [
     component: VideosComponent,
     canActivateChild: [ MetaGuard ],
     children: [
+      {
+        path: 'list',
+        pathMatch: 'full',
+        redirectTo: 'recently-added'
+      },
       {
         path: 'mine',
         component: MyVideosComponent,
@@ -22,11 +27,20 @@ const videosRoutes: Routes = [
         }
       },
       {
-        path: 'list',
-        component: VideoListComponent,
+        path: 'trending',
+        component: VideoTrendingComponent,
+        data: {
+          meta: {
+            title: 'Trending videos'
+          }
+        }
+      },
+      {
+        path: 'recently-added',
+        component: VideoRecentlyAddedComponent,
         data: {
           meta: {
-            title: 'Videos list'
+            title: 'Recently added videos'
           }
         }
       },
diff --git a/client/src/app/videos/videos.module.ts b/client/src/app/videos/videos.module.ts
index 4f3054c3a..93193000c 100644
--- a/client/src/app/videos/videos.module.ts
+++ b/client/src/app/videos/videos.module.ts
@@ -1,7 +1,9 @@
 import { NgModule } from '@angular/core'
 import { SharedModule } from '../shared'
 import { VideoService } from './shared'
-import { MyVideosComponent, VideoListComponent, VideoMiniatureComponent, VideoSortComponent } from './video-list'
+import { MyVideosComponent, VideoMiniatureComponent } from './video-list'
+import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component'
+import { VideoTrendingComponent } from './video-list/video-trending.component'
 import { VideosRoutingModule } from './videos-routing.module'
 import { VideosComponent } from './videos.component'
 
@@ -14,10 +16,10 @@ import { VideosComponent } from './videos.component'
   declarations: [
     VideosComponent,
 
-    VideoListComponent,
+    VideoTrendingComponent,
+    VideoRecentlyAddedComponent,
     MyVideosComponent,
-    VideoMiniatureComponent,
-    VideoSortComponent
+    VideoMiniatureComponent
   ],
 
   exports: [
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index 58f07612b..fc61a22da 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -33,24 +33,14 @@ input.readonly {
 }
 
 .main-col {
-  .content-padding {
-    padding: 15px 30px;
-
-    @media screen and (max-width: 800px) {
-      padding: 15px 10px;
-    }
-
-    @media screen and (min-width: 1400px) {
-      padding: 15px 40px;
-    }
-
-    @media screen and (min-width: 1600px) {
-      padding: 15px 50px;
-    }
-
-    @media screen and (min-width: 1800px) {
-      padding: 15px 60px;
-    }
+  padding: 30px;
+
+  .title-page {
+    font-size: 16px;
+    font-weight: $font-bold;
+    display: inline-block;
+    border-bottom: 2px solid $orange-color;
+    margin-bottom: 25px;
   }
 }
 
diff --git a/client/yarn.lock b/client/yarn.lock
index 8f148e431..fa1802a29 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -264,10 +264,6 @@ amdefine@>=0.0.4:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
 
-angular-pipes@^6.0.0:
-  version "6.5.3"
-  resolved "https://registry.yarnpkg.com/angular-pipes/-/angular-pipes-6.5.3.tgz#6bed37c51ebc2adaf3412663bfe25179d0489b02"
-
 angular2-notifications@^0.7.7:
   version "0.7.8"
   resolved "https://registry.yarnpkg.com/angular2-notifications/-/angular2-notifications-0.7.8.tgz#ecbcb95a8d2d402af94a9a080d6664c70d33a029"
@@ -4718,6 +4714,10 @@ ngx-chips@1.5.3:
   dependencies:
     ng2-material-dropdown "0.7.10"
 
+ngx-pipes@^2.0.5:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/ngx-pipes/-/ngx-pipes-2.0.5.tgz#743b827e350b1e66f5bdae49e90a02fa631d4c54"
+
 no-case@^2.2.0:
   version "2.3.2"
   resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac"
-- 
cgit v1.2.3


From 2bbb34127fccd187ed690949b6791e49fdd77194 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Fri, 1 Dec 2017 16:17:32 +0100
Subject: Add auto scroll to videos list

---
 client/package.json                                |  1 +
 client/src/app/app-routing.module.ts               |  2 +-
 client/src/app/shared/misc/from-now.pipe.ts        |  2 +-
 client/src/app/shared/search/search.component.ts   | 33 ++----------
 client/src/app/shared/shared.module.ts             |  7 +--
 .../video-list/shared/abstract-video-list.html     | 14 ++---
 .../video-list/shared/abstract-video-list.ts       | 63 ++++++++++++++++------
 .../video-list/video-recently-added.component.ts   |  1 +
 .../videos/video-list/video-trending.component.ts  |  1 +
 client/src/app/videos/videos.module.ts             |  4 +-
 client/yarn.lock                                   |  4 ++
 11 files changed, 73 insertions(+), 59 deletions(-)

(limited to 'client')

diff --git a/client/package.json b/client/package.json
index 310860fec..45f555f29 100644
--- a/client/package.json
+++ b/client/package.json
@@ -71,6 +71,7 @@
     "ngc-webpack": "3.2.2",
     "ngx-bootstrap": "2.0.0-beta.9",
     "ngx-chips": "1.5.3",
+    "ngx-infinite-scroll": "^0.7.0",
     "ngx-pipes": "^2.0.5",
     "node-sass": "^4.1.1",
     "normalize.css": "^7.0.0",
diff --git a/client/src/app/app-routing.module.ts b/client/src/app/app-routing.module.ts
index 0f9484344..fe72c9181 100644
--- a/client/src/app/app-routing.module.ts
+++ b/client/src/app/app-routing.module.ts
@@ -6,7 +6,7 @@ import { PreloadSelectedModulesList } from './core'
 const routes: Routes = [
   {
     path: '',
-    redirectTo: '/videos/list',
+    redirectTo: '/videos/trending',
     pathMatch: 'full'
   },
   {
diff --git a/client/src/app/shared/misc/from-now.pipe.ts b/client/src/app/shared/misc/from-now.pipe.ts
index 25e5d6a85..80dab02ba 100644
--- a/client/src/app/shared/misc/from-now.pipe.ts
+++ b/client/src/app/shared/misc/from-now.pipe.ts
@@ -1,6 +1,6 @@
 import { Pipe, PipeTransform } from '@angular/core'
 
-// Thanks: https://github.com/danrevah/ngx-pipes/blob/master/src/pipes/math/bytes.ts
+// Thanks: https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site
 
 @Pipe({name: 'fromNow'})
 export class FromNowPipe implements PipeTransform {
diff --git a/client/src/app/shared/search/search.component.ts b/client/src/app/shared/search/search.component.ts
index 6ef19c97a..f49ecc8ad 100644
--- a/client/src/app/shared/search/search.component.ts
+++ b/client/src/app/shared/search/search.component.ts
@@ -1,8 +1,6 @@
 import { Component, OnInit } from '@angular/core'
 import { Router } from '@angular/router'
-
 import { Search } from './search.model'
-import { SearchField } from './search-field.type'
 import { SearchService } from './search.service'
 
 @Component({
@@ -12,12 +10,6 @@ import { SearchService } from './search.service'
 })
 
 export class SearchComponent implements OnInit {
-  fieldChoices = {
-    name: 'Name',
-    account: 'Account',
-    host: 'Host',
-    tags: 'Tags'
-  }
   searchCriteria: Search = {
     field: 'name',
     value: ''
@@ -40,30 +32,11 @@ export class SearchComponent implements OnInit {
     )
   }
 
-  get choiceKeys () {
-    return Object.keys(this.fieldChoices)
-  }
-
-  choose ($event: MouseEvent, choice: SearchField) {
-    $event.preventDefault()
-    $event.stopPropagation()
-
-    this.searchCriteria.field = choice
-
-    if (this.searchCriteria.value) {
-      this.doSearch()
-    }
-  }
-
   doSearch () {
-    if (this.router.url.indexOf('/videos/list') === -1) {
-      this.router.navigate([ '/videos/list' ])
-    }
+    // if (this.router.url.indexOf('/videos/list') === -1) {
+    //   this.router.navigate([ '/videos/list' ])
+    // }
 
     this.searchService.searchUpdated.next(this.searchCriteria)
   }
-
-  getStringChoice (choiceKey: SearchField) {
-    return this.fieldChoices[choiceKey]
-  }
 }
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index c7ea6e603..7618748e9 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -6,21 +6,20 @@ import { RouterModule } from '@angular/router'
 
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown'
 import { ModalModule } from 'ngx-bootstrap/modal'
-import { PaginationModule } from 'ngx-bootstrap/pagination'
 import { ProgressbarModule } from 'ngx-bootstrap/progressbar'
 import { BytesPipe, KeysPipe } from 'ngx-pipes'
 import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared'
 import { DataTableModule } from 'primeng/components/datatable/datatable'
 
 import { AUTH_INTERCEPTOR_PROVIDER } from './auth'
+import { FromNowPipe } from './misc/from-now.pipe'
 import { LoaderComponent } from './misc/loader.component'
+import { NumberFormatterPipe } from './misc/number-formatter.pipe'
 import { RestExtractor, RestService } from './rest'
 import { SearchComponent, SearchService } from './search'
 import { UserService } from './users'
 import { VideoAbuseService } from './video-abuse'
 import { VideoBlacklistService } from './video-blacklist'
-import { NumberFormatterPipe } from './misc/number-formatter.pipe'
-import { FromNowPipe } from './misc/from-now.pipe'
 
 @NgModule({
   imports: [
@@ -32,7 +31,6 @@ import { FromNowPipe } from './misc/from-now.pipe'
 
     BsDropdownModule.forRoot(),
     ModalModule.forRoot(),
-    PaginationModule.forRoot(),
     ProgressbarModule.forRoot(),
 
     DataTableModule,
@@ -57,7 +55,6 @@ import { FromNowPipe } from './misc/from-now.pipe'
 
     BsDropdownModule,
     ModalModule,
-    PaginationModule,
     ProgressbarModule,
     DataTableModule,
     PrimeSharedModule,
diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.html b/client/src/app/videos/video-list/shared/abstract-video-list.html
index ab5530e68..69e16319e 100644
--- a/client/src/app/videos/video-list/shared/abstract-video-list.html
+++ b/client/src/app/videos/video-list/shared/abstract-video-list.html
@@ -2,15 +2,17 @@
   {{ titlePage }}
 </div>
 
-<div class="videos-miniatures">
+<div
+  class="videos-miniatures"
+  infiniteScroll
+  [infiniteScrollUpDistance]="1.5"
+  [infiniteScrollDistance]="0.5"
+  (scrolled)="onNearOfBottom()"
+  (scrolledUp)="onNearOfTop()"
+>
   <my-video-miniature
     class="ng-animate"
     *ngFor="let video of videos" [video]="video" [user]="user" [currentSort]="sort"
   >
   </my-video-miniature>
 </div>
-
-<pagination *ngIf="pagination.totalItems !== null && pagination.totalItems !== 0"
-  [totalItems]="pagination.totalItems" [itemsPerPage]="pagination.itemsPerPage" [maxSize]="6" [boundaryLinks]="true" [rotate]="false"
-  [(ngModel)]="pagination.currentPage" (pageChanged)="onPageChanged($event)"
-></pagination>
diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.ts b/client/src/app/videos/video-list/shared/abstract-video-list.ts
index 262ea4e21..44cdc1d9f 100644
--- a/client/src/app/videos/video-list/shared/abstract-video-list.ts
+++ b/client/src/app/videos/video-list/shared/abstract-video-list.ts
@@ -19,44 +19,77 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
   protected notificationsService: NotificationsService
   protected router: Router
   protected route: ActivatedRoute
-
   protected subActivatedRoute: Subscription
 
+  protected abstract currentRoute: string
+
   abstract titlePage: string
+  private loadedPages: { [ id: number ]: boolean } = {}
+
   abstract getVideosObservable (): Observable<{ videos: Video[], totalVideos: number}>
 
   ngOnInit () {
     // Subscribe to route changes
-    this.subActivatedRoute = this.route.params.subscribe(routeParams => {
-      this.loadRouteParams(routeParams)
-
-      this.getVideos()
-    })
+    const routeParams = this.route.snapshot.params
+    this.loadRouteParams(routeParams)
+    this.loadMoreVideos('after')
   }
 
   ngOnDestroy () {
     this.subActivatedRoute.unsubscribe()
   }
 
-  getVideos () {
-    this.videos = []
+  onNearOfTop () {
+    if (this.pagination.currentPage > 1) {
+      this.previousPage()
+    }
+  }
+
+  onNearOfBottom () {
+    if (this.hasMoreVideos()) {
+      this.nextPage()
+    }
+  }
+
+  loadMoreVideos (where: 'before' | 'after') {
+    if (this.loadedPages[this.pagination.currentPage] === true) return
 
     const observable = this.getVideosObservable()
 
     observable.subscribe(
       ({ videos, totalVideos }) => {
-        this.videos = videos
+        this.loadedPages[this.pagination.currentPage] = true
         this.pagination.totalItems = totalVideos
+
+        if (where === 'before') {
+          this.videos = videos.concat(this.videos)
+        } else {
+          this.videos = this.videos.concat(videos)
+        }
       },
       error => this.notificationsService.error('Error', error.text)
     )
   }
 
-  onPageChanged (event: { page: number }) {
-    // Be sure the current page is set
-    this.pagination.currentPage = event.page
+  protected hasMoreVideos () {
+    if (!this.pagination.totalItems) return true
+
+    const maxPage = this.pagination.totalItems/this.pagination.itemsPerPage
+    return maxPage > this.pagination.currentPage
+  }
+
+  protected previousPage () {
+    this.pagination.currentPage--
+
+    this.setNewRouteParams()
+    this.loadMoreVideos('before')
+  }
+
+  protected nextPage () {
+    this.pagination.currentPage++
 
-    this.navigateToNewParams()
+    this.setNewRouteParams()
+    this.loadMoreVideos('after')
   }
 
   protected buildRouteParams () {
@@ -79,8 +112,8 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
     }
   }
 
-  protected navigateToNewParams () {
+  protected setNewRouteParams () {
     const routeParams = this.buildRouteParams()
-    this.router.navigate([ '/videos/list', routeParams ])
+    this.router.navigate([ this.currentRoute, routeParams ])
   }
 }
diff --git a/client/src/app/videos/video-list/video-recently-added.component.ts b/client/src/app/videos/video-list/video-recently-added.component.ts
index dbba264df..9bf69bd78 100644
--- a/client/src/app/videos/video-list/video-recently-added.component.ts
+++ b/client/src/app/videos/video-list/video-recently-added.component.ts
@@ -11,6 +11,7 @@ import { AbstractVideoList } from './shared'
 })
 export class VideoRecentlyAddedComponent extends AbstractVideoList implements OnInit, OnDestroy {
   titlePage = 'Recently added'
+  currentRoute = '/videos/recently-added'
 
   constructor (protected router: Router,
                protected route: ActivatedRoute,
diff --git a/client/src/app/videos/video-list/video-trending.component.ts b/client/src/app/videos/video-list/video-trending.component.ts
index b97966c12..a1df68711 100644
--- a/client/src/app/videos/video-list/video-trending.component.ts
+++ b/client/src/app/videos/video-list/video-trending.component.ts
@@ -11,6 +11,7 @@ import { AbstractVideoList } from './shared'
 })
 export class VideoTrendingComponent extends AbstractVideoList implements OnInit, OnDestroy {
   titlePage = 'Trending'
+  currentRoute = '/videos/trending'
 
   constructor (protected router: Router,
                protected route: ActivatedRoute,
diff --git a/client/src/app/videos/videos.module.ts b/client/src/app/videos/videos.module.ts
index 93193000c..f3981d275 100644
--- a/client/src/app/videos/videos.module.ts
+++ b/client/src/app/videos/videos.module.ts
@@ -1,4 +1,5 @@
 import { NgModule } from '@angular/core'
+import { InfiniteScrollModule } from 'ngx-infinite-scroll'
 import { SharedModule } from '../shared'
 import { VideoService } from './shared'
 import { MyVideosComponent, VideoMiniatureComponent } from './video-list'
@@ -10,7 +11,8 @@ import { VideosComponent } from './videos.component'
 @NgModule({
   imports: [
     VideosRoutingModule,
-    SharedModule
+    SharedModule,
+    InfiniteScrollModule
   ],
 
   declarations: [
diff --git a/client/yarn.lock b/client/yarn.lock
index fa1802a29..bd6870061 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -4714,6 +4714,10 @@ ngx-chips@1.5.3:
   dependencies:
     ng2-material-dropdown "0.7.10"
 
+ngx-infinite-scroll@^0.7.0:
+  version "0.7.0"
+  resolved "https://registry.yarnpkg.com/ngx-infinite-scroll/-/ngx-infinite-scroll-0.7.0.tgz#a390c61c6a05ac14485e1c5bc8b4e6f6bd62fd6a"
+
 ngx-pipes@^2.0.5:
   version "2.0.5"
   resolved "https://registry.yarnpkg.com/ngx-pipes/-/ngx-pipes-2.0.5.tgz#743b827e350b1e66f5bdae49e90a02fa631d4c54"
-- 
cgit v1.2.3


From c30745f342480b59fb0856a059c8c2fbffbcfc6a Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Fri, 1 Dec 2017 17:38:26 +0100
Subject: Add account settings new design

---
 client/src/app/+admin/users/shared/user.service.ts | 14 ++---
 .../account-change-password.component.html         | 24 --------
 .../account-change-password.component.ts           | 65 ---------------------
 .../app/account/account-change-password/index.ts   |  1 -
 .../account-details/account-details.component.html | 16 -----
 .../account-details/account-details.component.ts   | 68 ----------------------
 client/src/app/account/account-details/index.ts    |  1 -
 client/src/app/account/account-routing.module.ts   | 28 +++++++--
 .../account-change-password.component.html         | 18 ++++++
 .../account-change-password.component.scss         |  9 +++
 .../account-change-password.component.ts           | 62 ++++++++++++++++++++
 .../account-change-password/index.ts               |  1 +
 .../account-details/account-details.component.html | 14 +++++
 .../account-details/account-details.component.scss | 11 ++++
 .../account-details/account-details.component.ts   | 61 +++++++++++++++++++
 .../account-settings/account-details/index.ts      |  1 +
 .../account-settings.component.html                |  9 +++
 .../account-settings.component.scss                | 13 +++++
 .../account-settings/account-settings.component.ts | 18 ++++++
 client/src/app/account/account.component.html      | 26 ++-------
 client/src/app/account/account.component.scss      |  3 -
 client/src/app/account/account.component.ts        | 24 +-------
 client/src/app/account/account.module.ts           |  9 +--
 client/src/app/app.component.scss                  |  8 ---
 client/src/app/menu/menu.component.html            |  2 +-
 client/src/app/menu/menu.component.scss            | 29 +++++----
 client/src/app/shared/search/search.component.scss | 22 +------
 client/src/app/videos/video-list/index.ts          |  1 -
 .../app/videos/video-list/my-videos.component.ts   | 36 ------------
 .../video-list/shared/abstract-video-list.html     | 31 +++++-----
 .../video-list/shared/abstract-video-list.scss     | 23 --------
 .../video-list/shared/abstract-video-list.ts       |  4 +-
 client/src/app/videos/videos-routing.module.ts     | 10 ----
 client/src/app/videos/videos.module.ts             |  3 +-
 client/src/sass/_mixins.scss                       | 35 +++++++++++
 client/src/sass/_variables.scss                    |  2 +
 client/src/sass/application.scss                   | 44 ++++++++++++--
 client/src/sass/pre-customizations.scss            |  1 +
 38 files changed, 377 insertions(+), 370 deletions(-)
 delete mode 100644 client/src/app/account/account-change-password/account-change-password.component.html
 delete mode 100644 client/src/app/account/account-change-password/account-change-password.component.ts
 delete mode 100644 client/src/app/account/account-change-password/index.ts
 delete mode 100644 client/src/app/account/account-details/account-details.component.html
 delete mode 100644 client/src/app/account/account-details/account-details.component.ts
 delete mode 100644 client/src/app/account/account-details/index.ts
 create mode 100644 client/src/app/account/account-settings/account-change-password/account-change-password.component.html
 create mode 100644 client/src/app/account/account-settings/account-change-password/account-change-password.component.scss
 create mode 100644 client/src/app/account/account-settings/account-change-password/account-change-password.component.ts
 create mode 100644 client/src/app/account/account-settings/account-change-password/index.ts
 create mode 100644 client/src/app/account/account-settings/account-details/account-details.component.html
 create mode 100644 client/src/app/account/account-settings/account-details/account-details.component.scss
 create mode 100644 client/src/app/account/account-settings/account-details/account-details.component.ts
 create mode 100644 client/src/app/account/account-settings/account-details/index.ts
 create mode 100644 client/src/app/account/account-settings/account-settings.component.html
 create mode 100644 client/src/app/account/account-settings/account-settings.component.scss
 create mode 100644 client/src/app/account/account-settings/account-settings.component.ts
 delete mode 100644 client/src/app/videos/video-list/my-videos.component.ts

(limited to 'client')

diff --git a/client/src/app/+admin/users/shared/user.service.ts b/client/src/app/+admin/users/shared/user.service.ts
index e4bd5df37..dc77cc1d8 100644
--- a/client/src/app/+admin/users/shared/user.service.ts
+++ b/client/src/app/+admin/users/shared/user.service.ts
@@ -1,14 +1,12 @@
-import { Injectable } from '@angular/core'
 import { HttpClient, HttpParams } from '@angular/common/http'
-import { Observable } from 'rxjs/Observable'
+import { Injectable } from '@angular/core'
+import { BytesPipe } from 'ngx-pipes'
+import { SortMeta } from 'primeng/components/common/sortmeta'
 import 'rxjs/add/operator/catch'
 import 'rxjs/add/operator/map'
-
-import { SortMeta } from 'primeng/components/common/sortmeta'
-import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe'
-
-import { RestExtractor, User, RestPagination, RestService } from '../../../shared'
-import { UserCreate, UserUpdate, ResultList } from '../../../../../../shared'
+import { Observable } from 'rxjs/Observable'
+import { ResultList, UserCreate, UserUpdate } from '../../../../../../shared'
+import { RestExtractor, RestPagination, RestService, User } from '../../../shared'
 
 @Injectable()
 export class UserService {
diff --git a/client/src/app/account/account-change-password/account-change-password.component.html b/client/src/app/account/account-change-password/account-change-password.component.html
deleted file mode 100644
index 92d9f900a..000000000
--- a/client/src/app/account/account-change-password/account-change-password.component.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
-
-<form role="form" (ngSubmit)="changePassword()" [formGroup]="form">
-  <div class="form-group">
-    <label for="new-password">New password</label>
-    <input
-      type="password" class="form-control" id="new-password"
-      formControlName="new-password"
-    >
-    <div *ngIf="formErrors['new-password']" class="alert alert-danger">
-      {{ formErrors['new-password'] }}
-    </div>
-  </div>
-
-  <div class="form-group">
-    <label for="name">Confirm new password</label>
-    <input
-      type="password" class="form-control" id="new-confirmed-password"
-      formControlName="new-confirmed-password"
-    >
-  </div>
-
-  <input type="submit" value="Change password" class="btn btn-default" [disabled]="!form.valid">
-</form>
diff --git a/client/src/app/account/account-change-password/account-change-password.component.ts b/client/src/app/account/account-change-password/account-change-password.component.ts
deleted file mode 100644
index 69edec54b..000000000
--- a/client/src/app/account/account-change-password/account-change-password.component.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { Component, OnInit } from '@angular/core'
-import { FormBuilder, FormGroup } from '@angular/forms'
-import { Router } from '@angular/router'
-
-import { NotificationsService } from 'angular2-notifications'
-
-import { FormReactive, UserService, USER_PASSWORD } from '../../shared'
-
-@Component({
-  selector: 'my-account-change-password',
-  templateUrl: './account-change-password.component.html'
-})
-
-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-change-password/index.ts b/client/src/app/account/account-change-password/index.ts
deleted file mode 100644
index 44c330b66..000000000
--- a/client/src/app/account/account-change-password/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './account-change-password.component'
diff --git a/client/src/app/account/account-details/account-details.component.html b/client/src/app/account/account-details/account-details.component.html
deleted file mode 100644
index 8f4f176af..000000000
--- a/client/src/app/account/account-details/account-details.component.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
-
-<form role="form" (ngSubmit)="updateDetails()" [formGroup]="form">
-  <div class="form-group">
-    <input
-      type="checkbox" id="displayNSFW"
-      formControlName="displayNSFW"
-    >
-    <label for="displayNSFW">Display videos that contain mature or explicit content</label>
-    <div *ngIf="formErrors['displayNSFW']" class="alert alert-danger">
-      {{ formErrors['displayNSFW'] }}
-    </div>
-  </div>
-
-  <input type="submit" value="Update" class="btn btn-default" [disabled]="!form.valid">
-</form>
diff --git a/client/src/app/account/account-details/account-details.component.ts b/client/src/app/account/account-details/account-details.component.ts
deleted file mode 100644
index d7a6e6871..000000000
--- a/client/src/app/account/account-details/account-details.component.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import { Component, OnInit, Input } from '@angular/core'
-import { FormBuilder, FormGroup } from '@angular/forms'
-import { Router } from '@angular/router'
-
-import { NotificationsService } from 'angular2-notifications'
-
-import { AuthService } from '../../core'
-import {
-  FormReactive,
-  User,
-  UserService,
-  USER_PASSWORD
-} from '../../shared'
-import { UserUpdateMe } from '../../../../../shared'
-
-@Component({
-  selector: 'my-account-details',
-  templateUrl: './account-details.component.html'
-})
-
-export class AccountDetailsComponent extends FormReactive implements OnInit {
-  @Input() user: User = null
-
-  error: string = 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({
-      displayNSFW: [ this.user.displayNSFW ]
-    })
-
-    this.form.valueChanges.subscribe(data => this.onValueChanged(data))
-  }
-
-  ngOnInit () {
-    this.buildForm()
-  }
-
-  updateDetails () {
-    const displayNSFW = this.form.value['displayNSFW']
-    const details: UserUpdateMe = {
-      displayNSFW
-    }
-
-    this.error = null
-    this.userService.updateMyDetails(details).subscribe(
-      () => {
-        this.notificationsService.success('Success', 'Information updated.')
-
-        this.authService.refreshUserInformation()
-      },
-
-      err => this.error = err.message
-    )
-  }
-}
diff --git a/client/src/app/account/account-details/index.ts b/client/src/app/account/account-details/index.ts
deleted file mode 100644
index 4829f608a..000000000
--- a/client/src/app/account/account-details/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './account-details.component'
diff --git a/client/src/app/account/account-routing.module.ts b/client/src/app/account/account-routing.module.ts
index 74d9aa03e..2e9de1cfb 100644
--- a/client/src/app/account/account-routing.module.ts
+++ b/client/src/app/account/account-routing.module.ts
@@ -5,17 +5,33 @@ import { MetaGuard } from '@ngx-meta/core'
 
 import { LoginGuard } from '../core'
 import { AccountComponent } from './account.component'
+import { AccountSettingsComponent } from './account-settings/account-settings.component'
 
 const accountRoutes: Routes = [
   {
     path: 'account',
     component: AccountComponent,
-    canActivate: [ MetaGuard, LoginGuard ],
-    data: {
-      meta: {
-        title: 'My account'
-      }
-    }
+    canActivateChild: [ MetaGuard, LoginGuard ],
+    children: [
+      {
+        path: 'settings',
+        component: AccountSettingsComponent,
+        data: {
+          meta: {
+            title: 'Account settings'
+          }
+        }
+      },
+      // {
+      //   path: 'videos',
+      //   component: AccountVideosComponent,
+      //   data: {
+      //     meta: {
+      //       title: 'Account videos'
+      //     }
+      //   }
+      // }
+    ]
   }
 ]
 
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
new file mode 100644
index 000000000..bfb55218f
--- /dev/null
+++ b/client/src/app/account/account-settings/account-change-password/account-change-password.component.html
@@ -0,0 +1,18 @@
+<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
+
+<form role="form" (ngSubmit)="changePassword()" [formGroup]="form">
+  <input
+    type="password" class="form-control" id="new-password" placeholder="Old password"
+    formControlName="new-password"
+  >
+  <div *ngIf="formErrors['new-password']" class="alert alert-danger">
+    {{ formErrors['new-password'] }}
+  </div>
+
+  <input
+    type="password" id="new-confirmed-password" placeholder="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
new file mode 100644
index 000000000..593355b70
--- /dev/null
+++ b/client/src/app/account/account-settings/account-change-password/account-change-password.component.scss
@@ -0,0 +1,9 @@
+input[type=password] {
+  @include peertube-input-text(340px);
+  display: block;
+  margin-bottom: 10px;
+}
+
+input[type=submit] {
+  @include peertube-button;
+}
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
new file mode 100644
index 000000000..8979e1734
--- /dev/null
+++ b/client/src/app/account/account-settings/account-change-password/account-change-password.component.ts
@@ -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: './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
new file mode 100644
index 000000000..44c330b66
--- /dev/null
+++ b/client/src/app/account/account-settings/account-change-password/index.ts
@@ -0,0 +1 @@
+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
new file mode 100644
index 000000000..c3cf6b629
--- /dev/null
+++ b/client/src/app/account/account-settings/account-details/account-details.component.html
@@ -0,0 +1,14 @@
+<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
+
+<form role="form" (ngSubmit)="updateDetails()" [formGroup]="form">
+  <input
+    type="checkbox" id="displayNSFW"
+    formControlName="displayNSFW"
+  >
+  <label for="displayNSFW">Display videos that contain mature or explicit content</label>
+  <div *ngIf="formErrors['displayNSFW']" class="alert alert-danger">
+    {{ formErrors['displayNSFW'] }}
+  </div>
+
+  <input type="submit" value="Update" [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
new file mode 100644
index 000000000..b1810d4f9
--- /dev/null
+++ b/client/src/app/account/account-settings/account-details/account-details.component.scss
@@ -0,0 +1,11 @@
+label {
+  font-size: 15px;
+  font-weight: $font-regular;
+  margin-left: 5px;
+}
+
+input[type=submit] {
+  @include peertube-button;
+
+  display: block;
+}
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
new file mode 100644
index 000000000..d835c53e5
--- /dev/null
+++ b/client/src/app/account/account-settings/account-details/account-details.component.ts
@@ -0,0 +1,61 @@
+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
+
+  error: string = 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({
+      displayNSFW: [ this.user.displayNSFW ]
+    })
+
+    this.form.valueChanges.subscribe(data => this.onValueChanged(data))
+  }
+
+  ngOnInit () {
+    this.buildForm()
+  }
+
+  updateDetails () {
+    const displayNSFW = this.form.value['displayNSFW']
+    const details: UserUpdateMe = {
+      displayNSFW
+    }
+
+    this.error = null
+    this.userService.updateMyDetails(details).subscribe(
+      () => {
+        this.notificationsService.success('Success', 'Information updated.')
+
+        this.authService.refreshUserInformation()
+      },
+
+      err => this.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
new file mode 100644
index 000000000..4829f608a
--- /dev/null
+++ b/client/src/app/account/account-settings/account-details/index.ts
@@ -0,0 +1 @@
+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
new file mode 100644
index 000000000..2509eb5aa
--- /dev/null
+++ b/client/src/app/account/account-settings/account-settings.component.html
@@ -0,0 +1,9 @@
+<div class="user-info">
+  {{ user.username }}
+</div>
+
+<div class="account-title">Account settings</div>
+<my-account-change-password></my-account-change-password>
+
+<div class="account-title">Filtering</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
new file mode 100644
index 000000000..a0822631d
--- /dev/null
+++ b/client/src/app/account/account-settings/account-settings.component.scss
@@ -0,0 +1,13 @@
+.user-info {
+  font-size: 20px;
+  font-weight: $font-bold;
+}
+
+.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
new file mode 100644
index 000000000..c3b670e02
--- /dev/null
+++ b/client/src/app/account/account-settings/account-settings.component.ts
@@ -0,0 +1,18 @@
+import { Component, OnInit } from '@angular/core'
+import { User } from '../../shared'
+import { AuthService } from '../../core'
+
+@Component({
+  selector: 'my-account-settings',
+  templateUrl: './account-settings.component.html',
+  styleUrls: [ './account-settings.component.scss' ]
+})
+export class AccountSettingsComponent implements OnInit {
+  user: User = null
+
+  constructor (private authService: AuthService) {}
+
+  ngOnInit () {
+    this.user = this.authService.getUser()
+  }
+}
diff --git a/client/src/app/account/account.component.html b/client/src/app/account/account.component.html
index 177e54999..d82a4ca4d 100644
--- a/client/src/app/account/account.component.html
+++ b/client/src/app/account/account.component.html
@@ -1,25 +1,11 @@
 <div class="row">
-  <div class="content-padding">
-    <h3>Account</h3>
+  <div class="sub-menu">
+    <a routerLink="/account/settings" routerLinkActive="active" class="title-page">My account</a>
 
-    <div class="col-md-6 col-sm-12">
-      <div class="panel panel-default">
-        <div class="panel-heading">Change password</div>
-
-        <div class="panel-body">
-          <my-account-change-password></my-account-change-password>
-        </div>
-      </div>
-    </div>
-
-    <div class="col-md-6 col-sm-12">
-      <div class="panel panel-default">
-        <div class="panel-heading">Update my informations</div>
+    <a routerLink="/account/videos" routerLinkActive="active" class="title-page">My videos</a>
+  </div>
 
-        <div class="panel-body">
-          <my-account-details [user]="user"></my-account-details>
-        </div>
-      </div>
-    </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
index 61b80d0a7..e69de29bb 100644
--- a/client/src/app/account/account.component.scss
+++ b/client/src/app/account/account.component.scss
@@ -1,3 +0,0 @@
-.panel {
-  margin-top: 40px;
-}
diff --git a/client/src/app/account/account.component.ts b/client/src/app/account/account.component.ts
index 929934f67..3d3677ab0 100644
--- a/client/src/app/account/account.component.ts
+++ b/client/src/app/account/account.component.ts
@@ -1,28 +1,8 @@
-import { Component, OnInit } from '@angular/core'
-import { FormBuilder, FormGroup } from '@angular/forms'
-import { Router } from '@angular/router'
-
-import { NotificationsService } from 'angular2-notifications'
-
-import { AuthService } from '../core'
-import {
-  FormReactive,
-  User,
-  UserService,
-  USER_PASSWORD
-} from '../shared'
+import { Component } from '@angular/core'
 
 @Component({
   selector: 'my-account',
   templateUrl: './account.component.html',
   styleUrls: [ './account.component.scss' ]
 })
-export class AccountComponent implements OnInit {
-  user: User = null
-
-  constructor (private authService: AuthService) {}
-
-  ngOnInit () {
-    this.user = this.authService.getUser()
-  }
-}
+export class AccountComponent {}
diff --git a/client/src/app/account/account.module.ts b/client/src/app/account/account.module.ts
index 380e9d235..ff444ddeb 100644
--- a/client/src/app/account/account.module.ts
+++ b/client/src/app/account/account.module.ts
@@ -1,11 +1,11 @@
 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 { AccountChangePasswordComponent } from './account-change-password'
-import { AccountDetailsComponent } from './account-details'
 import { AccountService } from './account.service'
-import { SharedModule } from '../shared'
 
 @NgModule({
   imports: [
@@ -15,6 +15,7 @@ import { SharedModule } from '../shared'
 
   declarations: [
     AccountComponent,
+    AccountSettingsComponent,
     AccountChangePasswordComponent,
     AccountDetailsComponent
   ],
diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss
index f245d0563..1baffa5c8 100644
--- a/client/src/app/app.component.scss
+++ b/client/src/app/app.component.scss
@@ -2,14 +2,6 @@
   min-height: calc(100vh - #{$header-height} - #{$footer-height} - #{$footer-margin});
 }
 
-.main-col {
-  margin-left: $menu-width;
-
-  &.expanded {
-    margin-left: 0;
-  }
-}
-
 .sub-header-container {
   margin-top: $header-height;
 }
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html
index 21f8d8ba4..0ed8ec518 100644
--- a/client/src/app/menu/menu.component.html
+++ b/client/src/app/menu/menu.component.html
@@ -1,7 +1,7 @@
 <menu>
   <div *ngIf="isLoggedIn" class="logged-in-block">
     <div class="logged-in-info">
-      <div class="logged-in-username">{{ user.username  }}</div>
+      <a routerLink="/account/settings" class="logged-in-username">{{ user.username  }}</a>
       <div class="logged-in-email">{{ user.email }}</div>
     </div>
 
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss
index 2c2106733..9d67ca66c 100644
--- a/client/src/app/menu/menu.component.scss
+++ b/client/src/app/menu/menu.component.scss
@@ -28,6 +28,10 @@ menu {
       .logged-in-username {
         font-size: 16px;
         font-weight: $font-semibold;
+        color: $menu-color;
+        cursor: pointer;
+
+        @include disable-default-a-behaviour;
       }
 
       .logged-in-email {
@@ -57,6 +61,12 @@ menu {
       width: 190px;
       border-radius: 3px;
       text-align: center;
+      color: $menu-color;
+      display: block;
+      cursor: pointer;
+      margin-bottom: 15px;
+
+      @include disable-default-a-behaviour;
 
       &.login-button {
         background-color: $orange-color;
@@ -82,6 +92,13 @@ menu {
 
     a {
       display: flex;
+      color: $menu-color;
+      cursor: pointer;
+      height: 22px;
+      line-height: 22px;
+      font-size: 16px;
+      margin-bottom: 15px;
+      @include disable-default-a-behaviour;
 
       .icon {
         width: 22px;
@@ -113,16 +130,4 @@ menu {
       }
     }
   }
-
-  a {
-    color: $menu-color;
-    height: 22px;
-    line-height: 22px;
-    display: block;
-    font-size: 16px;
-    cursor: pointer;
-    margin-bottom: 15px;
-
-    @include disable-default-a-behaviour;
-  }
 }
diff --git a/client/src/app/shared/search/search.component.scss b/client/src/app/shared/search/search.component.scss
index ffd891904..191d3d597 100644
--- a/client/src/app/shared/search/search.component.scss
+++ b/client/src/app/shared/search/search.component.scss
@@ -1,13 +1,7 @@
 #search-video {
-  display: inline-block;
-  height: $button-height;
-  width: $search-input-width;
+  @include peertube-input-text($search-input-width);
   margin-right: 15px;
   padding-right: 25px; // For the search icon
-  background: #fff;
-  border: 1px solid #C6C6C6;
-  border-radius: 3px;
-  padding-left: 15px;
 
   &::placeholder {
     color: #000;
@@ -29,19 +23,9 @@
 }
 
 .upload-button {
-  display: inline-block;
-  color: #fff;
-  font-weight: $font-semibold;
-  font-size: 15px;
-  height: $button-height;
-  line-height: $button-height;
-  border-radius: 3px;
-  text-align: center;
-  margin-right: 25px;
-  background-color: $orange-color;
-  padding: 0 17px 0 13px;
+  @include peertube-button-link;
 
-  @include disable-default-a-behaviour;
+  margin-right: 25px;
 
   .icon.icon-upload {
     display: inline-block;
diff --git a/client/src/app/videos/video-list/index.ts b/client/src/app/videos/video-list/index.ts
index a5a60364a..594e31984 100644
--- a/client/src/app/videos/video-list/index.ts
+++ b/client/src/app/videos/video-list/index.ts
@@ -1,4 +1,3 @@
-export * from './my-videos.component'
 export * from './video-recently-added.component'
 export * from './video-trending.component'
 export * from './shared'
diff --git a/client/src/app/videos/video-list/my-videos.component.ts b/client/src/app/videos/video-list/my-videos.component.ts
deleted file mode 100644
index 146db8262..000000000
--- a/client/src/app/videos/video-list/my-videos.component.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { Component, OnDestroy, OnInit } from '@angular/core'
-import { ActivatedRoute, Router } from '@angular/router'
-
-import { NotificationsService } from 'angular2-notifications'
-
-import { AbstractVideoList } from './shared'
-import { VideoService } from '../shared'
-
-@Component({
-  selector: 'my-videos',
-  styleUrls: [ './shared/abstract-video-list.scss' ],
-  templateUrl: './shared/abstract-video-list.html'
-})
-export class MyVideosComponent extends AbstractVideoList implements OnInit, OnDestroy {
-
-  constructor (
-    protected router: Router,
-    protected route: ActivatedRoute,
-    protected notificationsService: NotificationsService,
-    private videoService: VideoService
-  ) {
-    super()
-  }
-
-  ngOnInit () {
-    super.ngOnInit()
-  }
-
-  ngOnDestroy () {
-    super.ngOnDestroy()
-  }
-
-  getVideosObservable () {
-    return this.videoService.getMyVideos(this.pagination, this.sort)
-  }
-}
diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.html b/client/src/app/videos/video-list/shared/abstract-video-list.html
index 69e16319e..bd4f6b1f8 100644
--- a/client/src/app/videos/video-list/shared/abstract-video-list.html
+++ b/client/src/app/videos/video-list/shared/abstract-video-list.html
@@ -1,18 +1,19 @@
-<div class="title-page">
-  {{ titlePage }}
-</div>
+<div class="margin-content">
+  <div class="title-page title-page-single">
+    {{ titlePage }}
+  </div>
 
-<div
-  class="videos-miniatures"
-  infiniteScroll
-  [infiniteScrollUpDistance]="1.5"
-  [infiniteScrollDistance]="0.5"
-  (scrolled)="onNearOfBottom()"
-  (scrolledUp)="onNearOfTop()"
->
-  <my-video-miniature
-    class="ng-animate"
-    *ngFor="let video of videos" [video]="video" [user]="user" [currentSort]="sort"
+  <div
+    infiniteScroll
+    [infiniteScrollUpDistance]="1.5"
+    [infiniteScrollDistance]="0.5"
+    (scrolled)="onNearOfBottom()"
+    (scrolledUp)="onNearOfTop()"
   >
-  </my-video-miniature>
+    <my-video-miniature
+      class="ng-animate"
+      *ngFor="let video of videos" [video]="video" [user]="user" [currentSort]="sort"
+    >
+    </my-video-miniature>
+  </div>
 </div>
diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.scss b/client/src/app/videos/video-list/shared/abstract-video-list.scss
index de174802b..e69de29bb 100644
--- a/client/src/app/videos/video-list/shared/abstract-video-list.scss
+++ b/client/src/app/videos/video-list/shared/abstract-video-list.scss
@@ -1,23 +0,0 @@
-.videos-info {
-  @media screen and (max-width: 400px) {
-    margin-left: 0;
-  }
-
-  border-bottom: 1px solid #f1f1f1;
-  height: 40px;
-  line-height: 40px;
-
-  .videos-total-results {
-    font-size: 13px;
-  }
-
-  my-loader {
-    display: inline-block;
-    margin-left: 5px;
-  }
-}
-
-pagination {
-  display: block;
-  text-align: center;
-}
diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.ts b/client/src/app/videos/video-list/shared/abstract-video-list.ts
index 44cdc1d9f..a684ffef4 100644
--- a/client/src/app/videos/video-list/shared/abstract-video-list.ts
+++ b/client/src/app/videos/video-list/shared/abstract-video-list.ts
@@ -36,7 +36,9 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
   }
 
   ngOnDestroy () {
-    this.subActivatedRoute.unsubscribe()
+    if (this.subActivatedRoute) {
+      this.subActivatedRoute.unsubscribe()
+    }
   }
 
   onNearOfTop () {
diff --git a/client/src/app/videos/videos-routing.module.ts b/client/src/app/videos/videos-routing.module.ts
index 1f894df7a..204851c81 100644
--- a/client/src/app/videos/videos-routing.module.ts
+++ b/client/src/app/videos/videos-routing.module.ts
@@ -1,7 +1,6 @@
 import { NgModule } from '@angular/core'
 import { RouterModule, Routes } from '@angular/router'
 import { MetaGuard } from '@ngx-meta/core'
-import { MyVideosComponent } from './video-list'
 import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component'
 import { VideoTrendingComponent } from './video-list/video-trending.component'
 import { VideosComponent } from './videos.component'
@@ -17,15 +16,6 @@ const videosRoutes: Routes = [
         pathMatch: 'full',
         redirectTo: 'recently-added'
       },
-      {
-        path: 'mine',
-        component: MyVideosComponent,
-        data: {
-          meta: {
-            title: 'My videos'
-          }
-        }
-      },
       {
         path: 'trending',
         component: VideoTrendingComponent,
diff --git a/client/src/app/videos/videos.module.ts b/client/src/app/videos/videos.module.ts
index f3981d275..1d6194158 100644
--- a/client/src/app/videos/videos.module.ts
+++ b/client/src/app/videos/videos.module.ts
@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core'
 import { InfiniteScrollModule } from 'ngx-infinite-scroll'
 import { SharedModule } from '../shared'
 import { VideoService } from './shared'
-import { MyVideosComponent, VideoMiniatureComponent } from './video-list'
+import { VideoMiniatureComponent } from './video-list'
 import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component'
 import { VideoTrendingComponent } from './video-list/video-trending.component'
 import { VideosRoutingModule } from './videos-routing.module'
@@ -20,7 +20,6 @@ import { VideosComponent } from './videos.component'
 
     VideoTrendingComponent,
     VideoRecentlyAddedComponent,
-    MyVideosComponent,
     VideoMiniatureComponent
   ],
 
diff --git a/client/src/sass/_mixins.scss b/client/src/sass/_mixins.scss
index 681657d90..5798b8f6e 100644
--- a/client/src/sass/_mixins.scss
+++ b/client/src/sass/_mixins.scss
@@ -4,3 +4,38 @@
     outline: none !important;
   }
 }
+
+@mixin peertube-input-text($width) {
+  display: inline-block;
+  height: $button-height;
+  width: $width;
+  background: #fff;
+  border: 1px solid #C6C6C6;
+  border-radius: 3px;
+  padding-left: 15px;
+
+  &::placeholder {
+    color: #585858;
+  }
+}
+
+@mixin peertube-button {
+  border: none;
+  color: #fff;
+  font-weight: $font-semibold;
+  font-size: 15px;
+  height: $button-height;
+  line-height: $button-height;
+  border-radius: 3px;
+  text-align: center;
+  background-color: $orange-color;
+  padding: 0 17px 0 13px;
+  cursor: pointer;
+}
+
+@mixin peertube-button-link {
+  display: inline-block;
+
+  @include peertube-button;
+  @include disable-default-a-behaviour;
+}
diff --git a/client/src/sass/_variables.scss b/client/src/sass/_variables.scss
index e32b37462..d05452367 100644
--- a/client/src/sass/_variables.scss
+++ b/client/src/sass/_variables.scss
@@ -8,6 +8,8 @@ $orange-color: #F1680D;
 $black-background: #000;
 $grey-background: #f6f2f2;
 
+$expanded-horizontal-margins: 150px;
+
 $button-height: 30px;
 
 $header-height: 50px;
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index fc61a22da..db63db5f5 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -33,14 +33,50 @@ input.readonly {
 }
 
 .main-col {
-  padding: 30px;
+  margin-left: $menu-width;
 
   .title-page {
+    color: #000;
     font-size: 16px;
-    font-weight: $font-bold;
     display: inline-block;
-    border-bottom: 2px solid $orange-color;
-    margin-bottom: 25px;
+    margin-right: 55px;
+    font-weight: $font-semibold;
+    @include disable-default-a-behaviour;
+
+    &.active, &.title-page-single {
+      border-bottom: 2px solid $orange-color;
+      font-weight: $font-bold;
+      margin-top: 30px;
+      margin-bottom: 25px;
+    }
+  }
+
+  .margin-content {
+    margin-left: 10px;
+    margin-right: 10px;
+  }
+
+  .sub-menu {
+    background-color: #F7F7F7;
+    width: 100%;
+    height: 81px;
+    margin-bottom: 30px;
+    display: flex;
+    align-items: center;
+  }
+
+  // Override some properties if the main content is expanded (no menu on the left)
+  &.expanded {
+    margin-left: 0;
+
+    .margin-content {
+      margin-left: $expanded-horizontal-margins;
+      margin-right: $expanded-horizontal-margins;
+    }
+
+    .sub-menu {
+      padding-left: $expanded-horizontal-margins;
+    }
   }
 }
 
diff --git a/client/src/sass/pre-customizations.scss b/client/src/sass/pre-customizations.scss
index 693489828..52eef50f2 100644
--- a/client/src/sass/pre-customizations.scss
+++ b/client/src/sass/pre-customizations.scss
@@ -1,4 +1,5 @@
 @import '_variables.scss';
+@import '_mixins.scss';
 
 $bootstrap-sass-asset-helper: false !default;
 //
-- 
cgit v1.2.3


From 202f6b6c9dcc9b0aec4b0c1b15e455c22a7952a7 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Fri, 1 Dec 2017 18:56:26 +0100
Subject: Begin videos of an account

---
 client/src/app/account/account-routing.module.ts   |  19 +--
 .../account-videos/account-videos.component.html   |   9 ++
 .../account-videos/account-videos.component.scss   |   0
 .../account-videos/account-videos.component.ts     |  35 ++++
 client/src/app/account/account.module.ts           |   4 +-
 client/src/app/shared/shared.module.ts             |  12 +-
 .../src/app/shared/video/abstract-video-list.html  |  19 +++
 .../src/app/shared/video/abstract-video-list.scss  |   0
 client/src/app/shared/video/abstract-video-list.ts | 121 ++++++++++++++
 client/src/app/shared/video/sort-field.type.ts     |   5 +
 client/src/app/shared/video/video-details.model.ts |  84 ++++++++++
 client/src/app/shared/video/video-edit.model.ts    |  50 ++++++
 .../src/app/shared/video/video-pagination.model.ts |   5 +
 .../shared/video/video-thumbnail.component.html    |  10 ++
 .../shared/video/video-thumbnail.component.scss    |  28 ++++
 .../app/shared/video/video-thumbnail.component.ts  |  12 ++
 client/src/app/shared/video/video.model.ts         |  90 +++++++++++
 client/src/app/shared/video/video.service.ts       | 170 ++++++++++++++++++++
 .../videos/+video-edit/shared/video-edit.module.ts |   3 +-
 .../app/videos/+video-edit/video-add.component.ts  |  22 ++-
 .../videos/+video-edit/video-update.component.ts   |  19 ++-
 .../+video-watch/video-download.component.ts       |   4 +-
 .../videos/+video-watch/video-report.component.ts  |   8 +-
 .../videos/+video-watch/video-share.component.ts   |   4 +-
 .../videos/+video-watch/video-watch.component.ts   |   4 +-
 .../app/videos/+video-watch/video-watch.module.ts  |   5 +-
 client/src/app/videos/shared/index.ts              |   6 -
 client/src/app/videos/shared/sort-field.type.ts    |   5 -
 .../src/app/videos/shared/video-details.model.ts   |  84 ----------
 client/src/app/videos/shared/video-edit.model.ts   |  50 ------
 .../app/videos/shared/video-pagination.model.ts    |   5 -
 client/src/app/videos/shared/video.model.ts        |  90 -----------
 client/src/app/videos/shared/video.service.ts      | 176 ---------------------
 .../video-list/shared/abstract-video-list.html     |  19 ---
 .../video-list/shared/abstract-video-list.scss     |   0
 .../video-list/shared/abstract-video-list.ts       | 121 --------------
 client/src/app/videos/video-list/shared/index.ts   |   1 -
 .../shared/video-miniature.component.html          |  11 +-
 .../shared/video-miniature.component.scss          |  29 ----
 .../video-list/shared/video-miniature.component.ts |   4 +-
 .../video-list/video-recently-added.component.ts   |   8 +-
 .../videos/video-list/video-trending.component.ts  |   8 +-
 client/src/app/videos/videos.module.ts             |   9 +-
 43 files changed, 704 insertions(+), 664 deletions(-)
 create mode 100644 client/src/app/account/account-videos/account-videos.component.html
 create mode 100644 client/src/app/account/account-videos/account-videos.component.scss
 create mode 100644 client/src/app/account/account-videos/account-videos.component.ts
 create mode 100644 client/src/app/shared/video/abstract-video-list.html
 create mode 100644 client/src/app/shared/video/abstract-video-list.scss
 create mode 100644 client/src/app/shared/video/abstract-video-list.ts
 create mode 100644 client/src/app/shared/video/sort-field.type.ts
 create mode 100644 client/src/app/shared/video/video-details.model.ts
 create mode 100644 client/src/app/shared/video/video-edit.model.ts
 create mode 100644 client/src/app/shared/video/video-pagination.model.ts
 create mode 100644 client/src/app/shared/video/video-thumbnail.component.html
 create mode 100644 client/src/app/shared/video/video-thumbnail.component.scss
 create mode 100644 client/src/app/shared/video/video-thumbnail.component.ts
 create mode 100644 client/src/app/shared/video/video.model.ts
 create mode 100644 client/src/app/shared/video/video.service.ts
 delete mode 100644 client/src/app/videos/shared/sort-field.type.ts
 delete mode 100644 client/src/app/videos/shared/video-details.model.ts
 delete mode 100644 client/src/app/videos/shared/video-edit.model.ts
 delete mode 100644 client/src/app/videos/shared/video-pagination.model.ts
 delete mode 100644 client/src/app/videos/shared/video.model.ts
 delete mode 100644 client/src/app/videos/shared/video.service.ts
 delete mode 100644 client/src/app/videos/video-list/shared/abstract-video-list.html
 delete mode 100644 client/src/app/videos/video-list/shared/abstract-video-list.scss
 delete mode 100644 client/src/app/videos/video-list/shared/abstract-video-list.ts

(limited to 'client')

diff --git a/client/src/app/account/account-routing.module.ts b/client/src/app/account/account-routing.module.ts
index 2e9de1cfb..070b9b5c5 100644
--- a/client/src/app/account/account-routing.module.ts
+++ b/client/src/app/account/account-routing.module.ts
@@ -6,6 +6,7 @@ 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 = [
   {
@@ -22,15 +23,15 @@ const accountRoutes: Routes = [
           }
         }
       },
-      // {
-      //   path: 'videos',
-      //   component: AccountVideosComponent,
-      //   data: {
-      //     meta: {
-      //       title: 'Account videos'
-      //     }
-      //   }
-      // }
+      {
+        path: 'videos',
+        component: AccountVideosComponent,
+        data: {
+          meta: {
+            title: 'Account videos'
+          }
+        }
+      }
     ]
   }
 ]
diff --git a/client/src/app/account/account-videos/account-videos.component.html b/client/src/app/account/account-videos/account-videos.component.html
new file mode 100644
index 000000000..6c8ac4508
--- /dev/null
+++ b/client/src/app/account/account-videos/account-videos.component.html
@@ -0,0 +1,9 @@
+<div
+  infiniteScroll
+  [infiniteScrollDistance]="0.5"
+  (scrolled)="onNearOfBottom()"
+>
+  <div *ngFor="let video of videos">
+    <my-video-thumbnail [video]="video"></my-video-thumbnail>
+  </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
new file mode 100644
index 000000000..e69de29bb
diff --git a/client/src/app/account/account-videos/account-videos.component.ts b/client/src/app/account/account-videos/account-videos.component.ts
new file mode 100644
index 000000000..ff945825d
--- /dev/null
+++ b/client/src/app/account/account-videos/account-videos.component.ts
@@ -0,0 +1,35 @@
+import { Component, OnDestroy, OnInit } from '@angular/core'
+import { AbstractVideoList } from '../../shared/video/abstract-video-list'
+import { ActivatedRoute } from '@angular/router'
+import { Router } from '@angular/router'
+import { NotificationsService } from 'angular2-notifications'
+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'
+
+  constructor (protected router: Router,
+               protected route: ActivatedRoute,
+               protected notificationsService: NotificationsService,
+               private videoService: VideoService) {
+    super()
+  }
+
+  ngOnInit () {
+    super.ngOnInit()
+  }
+
+  ngOnDestroy () {
+    super.ngOnDestroy()
+  }
+
+  getVideosObservable () {
+    return this.videoService.getMyVideos(this.pagination, this.sort)
+  }
+}
diff --git a/client/src/app/account/account.module.ts b/client/src/app/account/account.module.ts
index ff444ddeb..020199e23 100644
--- a/client/src/app/account/account.module.ts
+++ b/client/src/app/account/account.module.ts
@@ -6,6 +6,7 @@ import { AccountDetailsComponent } from './account-settings/account-details/acco
 import { AccountSettingsComponent } from './account-settings/account-settings.component'
 import { AccountComponent } from './account.component'
 import { AccountService } from './account.service'
+import { AccountVideosComponent } from './account-videos/account-videos.component'
 
 @NgModule({
   imports: [
@@ -17,7 +18,8 @@ import { AccountService } from './account.service'
     AccountComponent,
     AccountSettingsComponent,
     AccountChangePasswordComponent,
-    AccountDetailsComponent
+    AccountDetailsComponent,
+    AccountVideosComponent
   ],
 
   exports: [
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 7618748e9..e76f7636a 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -20,6 +20,9 @@ import { SearchComponent, SearchService } from './search'
 import { UserService } from './users'
 import { VideoAbuseService } from './video-abuse'
 import { VideoBlacklistService } from './video-blacklist'
+import { VideoThumbnailComponent } from './video/video-thumbnail.component'
+import { VideoService } from './video/video.service'
+import { InfiniteScrollModule } from 'ngx-infinite-scroll'
 
 @NgModule({
   imports: [
@@ -34,7 +37,8 @@ import { VideoBlacklistService } from './video-blacklist'
     ProgressbarModule.forRoot(),
 
     DataTableModule,
-    PrimeSharedModule
+    PrimeSharedModule,
+    InfiniteScrollModule
   ],
 
   declarations: [
@@ -42,6 +46,7 @@ import { VideoBlacklistService } from './video-blacklist'
     KeysPipe,
     SearchComponent,
     LoaderComponent,
+    VideoThumbnailComponent,
     NumberFormatterPipe,
     FromNowPipe
   ],
@@ -58,11 +63,13 @@ import { VideoBlacklistService } from './video-blacklist'
     ProgressbarModule,
     DataTableModule,
     PrimeSharedModule,
+    InfiniteScrollModule,
     BytesPipe,
     KeysPipe,
 
     SearchComponent,
     LoaderComponent,
+    VideoThumbnailComponent,
 
     NumberFormatterPipe,
     FromNowPipe
@@ -75,7 +82,8 @@ import { VideoBlacklistService } from './video-blacklist'
     SearchService,
     VideoAbuseService,
     VideoBlacklistService,
-    UserService
+    UserService,
+    VideoService
   ]
 })
 export class SharedModule { }
diff --git a/client/src/app/shared/video/abstract-video-list.html b/client/src/app/shared/video/abstract-video-list.html
new file mode 100644
index 000000000..bd4f6b1f8
--- /dev/null
+++ b/client/src/app/shared/video/abstract-video-list.html
@@ -0,0 +1,19 @@
+<div class="margin-content">
+  <div class="title-page title-page-single">
+    {{ titlePage }}
+  </div>
+
+  <div
+    infiniteScroll
+    [infiniteScrollUpDistance]="1.5"
+    [infiniteScrollDistance]="0.5"
+    (scrolled)="onNearOfBottom()"
+    (scrolledUp)="onNearOfTop()"
+  >
+    <my-video-miniature
+      class="ng-animate"
+      *ngFor="let video of videos" [video]="video" [user]="user" [currentSort]="sort"
+    >
+    </my-video-miniature>
+  </div>
+</div>
diff --git a/client/src/app/shared/video/abstract-video-list.scss b/client/src/app/shared/video/abstract-video-list.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts
new file mode 100644
index 000000000..cf717cf4c
--- /dev/null
+++ b/client/src/app/shared/video/abstract-video-list.ts
@@ -0,0 +1,121 @@
+import { OnDestroy, OnInit } from '@angular/core'
+import { ActivatedRoute, Router } from '@angular/router'
+import { NotificationsService } from 'angular2-notifications'
+import { Observable } from 'rxjs/Observable'
+import { Subscription } from 'rxjs/Subscription'
+import { SortField } from './sort-field.type'
+import { VideoPagination } from './video-pagination.model'
+import { Video } from './video.model'
+
+export abstract class AbstractVideoList implements OnInit, OnDestroy {
+  pagination: VideoPagination = {
+    currentPage: 1,
+    itemsPerPage: 25,
+    totalItems: null
+  }
+  sort: SortField = '-createdAt'
+  videos: Video[] = []
+
+  protected notificationsService: NotificationsService
+  protected router: Router
+  protected route: ActivatedRoute
+  protected subActivatedRoute: Subscription
+
+  protected abstract currentRoute: string
+
+  abstract titlePage: string
+  private loadedPages: { [ id: number ]: boolean } = {}
+
+  abstract getVideosObservable (): Observable<{ videos: Video[], totalVideos: number}>
+
+  ngOnInit () {
+    // Subscribe to route changes
+    const routeParams = this.route.snapshot.params
+    this.loadRouteParams(routeParams)
+    this.loadMoreVideos('after')
+  }
+
+  ngOnDestroy () {
+    if (this.subActivatedRoute) {
+      this.subActivatedRoute.unsubscribe()
+    }
+  }
+
+  onNearOfTop () {
+    if (this.pagination.currentPage > 1) {
+      this.previousPage()
+    }
+  }
+
+  onNearOfBottom () {
+    if (this.hasMoreVideos()) {
+      this.nextPage()
+    }
+  }
+
+  loadMoreVideos (where: 'before' | 'after') {
+    if (this.loadedPages[this.pagination.currentPage] === true) return
+
+    const observable = this.getVideosObservable()
+
+    observable.subscribe(
+      ({ videos, totalVideos }) => {
+        this.loadedPages[this.pagination.currentPage] = true
+        this.pagination.totalItems = totalVideos
+
+        if (where === 'before') {
+          this.videos = videos.concat(this.videos)
+        } else {
+          this.videos = this.videos.concat(videos)
+        }
+      },
+      error => this.notificationsService.error('Error', error.text)
+    )
+  }
+
+  protected hasMoreVideos () {
+    if (!this.pagination.totalItems) return true
+
+    const maxPage = this.pagination.totalItems / this.pagination.itemsPerPage
+    return maxPage > this.pagination.currentPage
+  }
+
+  protected previousPage () {
+    this.pagination.currentPage--
+
+    this.setNewRouteParams()
+    this.loadMoreVideos('before')
+  }
+
+  protected nextPage () {
+    this.pagination.currentPage++
+
+    this.setNewRouteParams()
+    this.loadMoreVideos('after')
+  }
+
+  protected buildRouteParams () {
+    // There is always a sort and a current page
+    const params = {
+      sort: this.sort,
+      page: this.pagination.currentPage
+    }
+
+    return params
+  }
+
+  protected loadRouteParams (routeParams: { [ key: string ]: any }) {
+    this.sort = routeParams['sort'] as SortField || '-createdAt'
+
+    if (routeParams['page'] !== undefined) {
+      this.pagination.currentPage = parseInt(routeParams['page'], 10)
+    } else {
+      this.pagination.currentPage = 1
+    }
+  }
+
+  protected setNewRouteParams () {
+    const routeParams = this.buildRouteParams()
+    this.router.navigate([ this.currentRoute, routeParams ])
+  }
+}
diff --git a/client/src/app/shared/video/sort-field.type.ts b/client/src/app/shared/video/sort-field.type.ts
new file mode 100644
index 000000000..776f360f8
--- /dev/null
+++ b/client/src/app/shared/video/sort-field.type.ts
@@ -0,0 +1,5 @@
+export type SortField = 'name' | '-name'
+                      | 'duration' | '-duration'
+                      | 'createdAt' | '-createdAt'
+                      | 'views' | '-views'
+                      | 'likes' | '-likes'
diff --git a/client/src/app/shared/video/video-details.model.ts b/client/src/app/shared/video/video-details.model.ts
new file mode 100644
index 000000000..93c380b73
--- /dev/null
+++ b/client/src/app/shared/video/video-details.model.ts
@@ -0,0 +1,84 @@
+import { Video } from '../../shared/video/video.model'
+import { AuthUser } from '../../core'
+import {
+  VideoDetails as VideoDetailsServerModel,
+  VideoFile,
+  VideoChannel,
+  VideoResolution,
+  UserRight,
+  VideoPrivacy
+} from '../../../../../shared'
+
+export class VideoDetails extends Video implements VideoDetailsServerModel {
+  account: string
+  by: string
+  createdAt: Date
+  updatedAt: Date
+  categoryLabel: string
+  category: number
+  licenceLabel: string
+  licence: number
+  languageLabel: string
+  language: number
+  description: string
+  duration: number
+  durationLabel: string
+  id: number
+  uuid: string
+  isLocal: boolean
+  name: string
+  serverHost: string
+  tags: string[]
+  thumbnailPath: string
+  thumbnailUrl: string
+  previewPath: string
+  previewUrl: string
+  embedPath: string
+  embedUrl: string
+  views: number
+  likes: number
+  dislikes: number
+  nsfw: boolean
+  descriptionPath: string
+  files: VideoFile[]
+  channel: VideoChannel
+  privacy: VideoPrivacy
+  privacyLabel: string
+
+  constructor (hash: VideoDetailsServerModel) {
+    super(hash)
+
+    this.privacy = hash.privacy
+    this.privacyLabel = hash.privacyLabel
+    this.descriptionPath = hash.descriptionPath
+    this.files = hash.files
+    this.channel = hash.channel
+  }
+
+  getAppropriateMagnetUri (actualDownloadSpeed = 0) {
+    if (this.files === undefined || this.files.length === 0) return ''
+    if (this.files.length === 1) return this.files[0].magnetUri
+
+    // Find first video that is good for our download speed (remember they are sorted)
+    let betterResolutionFile = this.files.find(f => actualDownloadSpeed > (f.size / this.duration))
+
+    // If the download speed is too bad, return the lowest resolution we have
+    if (betterResolutionFile === undefined) {
+      betterResolutionFile = this.files.find(f => f.resolution === VideoResolution.H_240P)
+    }
+
+    return betterResolutionFile.magnetUri
+  }
+
+  isRemovableBy (user: AuthUser) {
+    return user && this.isLocal === true && (this.account === user.username || user.hasRight(UserRight.REMOVE_ANY_VIDEO))
+  }
+
+  isBlackistableBy (user: AuthUser) {
+    return user && user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) === true && this.isLocal === false
+  }
+
+  isUpdatableBy (user: AuthUser) {
+    return user && this.isLocal === true && user.username === this.account
+  }
+}
diff --git a/client/src/app/shared/video/video-edit.model.ts b/client/src/app/shared/video/video-edit.model.ts
new file mode 100644
index 000000000..88d23a59f
--- /dev/null
+++ b/client/src/app/shared/video/video-edit.model.ts
@@ -0,0 +1,50 @@
+import { VideoDetails } from './video-details.model'
+import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum'
+
+export class VideoEdit {
+  category: number
+  licence: number
+  language: number
+  description: string
+  name: string
+  tags: string[]
+  nsfw: boolean
+  channel: number
+  privacy: VideoPrivacy
+  uuid?: string
+  id?: number
+
+  constructor (videoDetails: VideoDetails) {
+    this.id = videoDetails.id
+    this.uuid = videoDetails.uuid
+    this.category = videoDetails.category
+    this.licence = videoDetails.licence
+    this.language = videoDetails.language
+    this.description = videoDetails.description
+    this.name = videoDetails.name
+    this.tags = videoDetails.tags
+    this.nsfw = videoDetails.nsfw
+    this.channel = videoDetails.channel.id
+    this.privacy = videoDetails.privacy
+  }
+
+  patch (values: Object) {
+    Object.keys(values).forEach((key) => {
+      this[key] = values[key]
+    })
+  }
+
+  toJSON () {
+    return {
+      category: this.category,
+      licence: this.licence,
+      language: this.language,
+      description: this.description,
+      name: this.name,
+      tags: this.tags,
+      nsfw: this.nsfw,
+      channel: this.channel,
+      privacy: this.privacy
+    }
+  }
+}
diff --git a/client/src/app/shared/video/video-pagination.model.ts b/client/src/app/shared/video/video-pagination.model.ts
new file mode 100644
index 000000000..9e71769cb
--- /dev/null
+++ b/client/src/app/shared/video/video-pagination.model.ts
@@ -0,0 +1,5 @@
+export interface VideoPagination {
+  currentPage: number
+  itemsPerPage: number
+  totalItems: number
+}
diff --git a/client/src/app/shared/video/video-thumbnail.component.html b/client/src/app/shared/video/video-thumbnail.component.html
new file mode 100644
index 000000000..5c698e8f6
--- /dev/null
+++ b/client/src/app/shared/video/video-thumbnail.component.html
@@ -0,0 +1,10 @@
+<a
+  [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name"
+class="video-thumbnail"
+>
+<img [attr.src]="video.thumbnailUrl" alt="video thumbnail" [ngClass]="{ 'blur-filter': nsfw }" />
+
+<div class="video-thumbnail-overlay">
+  {{ video.durationLabel }}
+</div>
+</a>
diff --git a/client/src/app/shared/video/video-thumbnail.component.scss b/client/src/app/shared/video/video-thumbnail.component.scss
new file mode 100644
index 000000000..ab4f9bcb1
--- /dev/null
+++ b/client/src/app/shared/video/video-thumbnail.component.scss
@@ -0,0 +1,28 @@
+.video-thumbnail {
+  display: inline-block;
+  position: relative;
+  border-radius: 4px;
+  overflow: hidden;
+
+  &:hover {
+    text-decoration: none !important;
+  }
+
+  img.blur-filter {
+    filter: blur(5px);
+    transform : scale(1.03);
+  }
+
+  .video-thumbnail-overlay {
+    position: absolute;
+    right: 5px;
+    bottom: 5px;
+    display: inline-block;
+    background-color: rgba(0, 0, 0, 0.7);
+    color: #fff;
+    font-size: 12px;
+    font-weight: $font-bold;
+    border-radius: 3px;
+    padding: 0 5px;
+  }
+}
diff --git a/client/src/app/shared/video/video-thumbnail.component.ts b/client/src/app/shared/video/video-thumbnail.component.ts
new file mode 100644
index 000000000..e543e9903
--- /dev/null
+++ b/client/src/app/shared/video/video-thumbnail.component.ts
@@ -0,0 +1,12 @@
+import { Component, Input } from '@angular/core'
+import { Video } from './video.model'
+
+@Component({
+  selector: 'my-video-thumbnail',
+  styleUrls: [ './video-thumbnail.component.scss' ],
+  templateUrl: './video-thumbnail.component.html'
+})
+export class VideoThumbnailComponent {
+  @Input() video: Video
+  @Input() nsfw = false
+}
diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/video/video.model.ts
new file mode 100644
index 000000000..6929c8755
--- /dev/null
+++ b/client/src/app/shared/video/video.model.ts
@@ -0,0 +1,90 @@
+import { Video as VideoServerModel } from '../../../../../shared'
+import { User } from '../'
+
+export class Video implements VideoServerModel {
+  account: string
+  by: string
+  createdAt: Date
+  updatedAt: Date
+  categoryLabel: string
+  category: number
+  licenceLabel: string
+  licence: number
+  languageLabel: string
+  language: number
+  description: string
+  duration: number
+  durationLabel: string
+  id: number
+  uuid: string
+  isLocal: boolean
+  name: string
+  serverHost: string
+  tags: string[]
+  thumbnailPath: string
+  thumbnailUrl: string
+  previewPath: string
+  previewUrl: string
+  embedPath: string
+  embedUrl: string
+  views: number
+  likes: number
+  dislikes: number
+  nsfw: boolean
+
+  private static createByString (account: string, serverHost: string) {
+    return account + '@' + serverHost
+  }
+
+  private static createDurationString (duration: number) {
+    const minutes = Math.floor(duration / 60)
+    const seconds = duration % 60
+    const minutesPadding = minutes >= 10 ? '' : '0'
+    const secondsPadding = seconds >= 10 ? '' : '0'
+
+    return minutesPadding + minutes.toString() + ':' + secondsPadding + seconds.toString()
+  }
+
+  constructor (hash: VideoServerModel) {
+    let absoluteAPIUrl = API_URL
+    if (!absoluteAPIUrl) {
+      // The API is on the same domain
+      absoluteAPIUrl = window.location.origin
+    }
+
+    this.account = hash.account
+    this.createdAt = new Date(hash.createdAt.toString())
+    this.categoryLabel = hash.categoryLabel
+    this.category = hash.category
+    this.licenceLabel = hash.licenceLabel
+    this.licence = hash.licence
+    this.languageLabel = hash.languageLabel
+    this.language = hash.language
+    this.description = hash.description
+    this.duration = hash.duration
+    this.durationLabel = Video.createDurationString(hash.duration)
+    this.id = hash.id
+    this.uuid = hash.uuid
+    this.isLocal = hash.isLocal
+    this.name = hash.name
+    this.serverHost = hash.serverHost
+    this.tags = hash.tags
+    this.thumbnailPath = hash.thumbnailPath
+    this.thumbnailUrl = absoluteAPIUrl + hash.thumbnailPath
+    this.previewPath = hash.previewPath
+    this.previewUrl = absoluteAPIUrl + hash.previewPath
+    this.embedPath = hash.embedPath
+    this.embedUrl = absoluteAPIUrl + hash.embedPath
+    this.views = hash.views
+    this.likes = hash.likes
+    this.dislikes = hash.dislikes
+    this.nsfw = hash.nsfw
+
+    this.by = Video.createByString(hash.account, hash.serverHost)
+  }
+
+  isVideoNSFWForUser (user: User) {
+    // If the video is NSFW and the user is not logged in, or the user does not want to display NSFW videos...
+    return (this.nsfw && (!user || user.displayNSFW === false))
+  }
+}
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts
new file mode 100644
index 000000000..b2a26417c
--- /dev/null
+++ b/client/src/app/shared/video/video.service.ts
@@ -0,0 +1,170 @@
+import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http'
+import { Injectable } from '@angular/core'
+import 'rxjs/add/operator/catch'
+import 'rxjs/add/operator/map'
+import { Observable } from 'rxjs/Observable'
+import { Video as VideoServerModel, VideoDetails as VideoDetailsServerModel } from '../../../../../shared'
+import { ResultList } from '../../../../../shared/models/result-list.model'
+import { UserVideoRateUpdate } from '../../../../../shared/models/videos/user-video-rate-update.model'
+import { UserVideoRate } from '../../../../../shared/models/videos/user-video-rate.model'
+import { VideoRateType } from '../../../../../shared/models/videos/video-rate.type'
+import { VideoUpdate } from '../../../../../shared/models/videos/video-update.model'
+import { RestExtractor } from '../rest/rest-extractor.service'
+import { RestService } from '../rest/rest.service'
+import { Search } from '../search/search.model'
+import { UserService } from '../users/user.service'
+import { SortField } from './sort-field.type'
+import { VideoDetails } from './video-details.model'
+import { VideoEdit } from './video-edit.model'
+import { VideoPagination } from './video-pagination.model'
+import { Video } from './video.model'
+
+@Injectable()
+export class VideoService {
+  private static BASE_VIDEO_URL = API_URL + '/api/v1/videos/'
+
+  constructor (
+    private authHttp: HttpClient,
+    private restExtractor: RestExtractor,
+    private restService: RestService
+  ) {}
+
+  getVideo (uuid: string): Observable<VideoDetails> {
+    return this.authHttp.get<VideoDetailsServerModel>(VideoService.BASE_VIDEO_URL + uuid)
+                        .map(videoHash => new VideoDetails(videoHash))
+                        .catch((res) => this.restExtractor.handleError(res))
+  }
+
+  viewVideo (uuid: string): Observable<VideoDetails> {
+    return this.authHttp.post(VideoService.BASE_VIDEO_URL + uuid + '/views', {})
+      .map(this.restExtractor.extractDataBool)
+      .catch(this.restExtractor.handleError)
+  }
+
+  updateVideo (video: VideoEdit) {
+    const language = video.language ? video.language : null
+
+    const body: VideoUpdate = {
+      name: video.name,
+      category: video.category,
+      licence: video.licence,
+      language,
+      description: video.description,
+      privacy: video.privacy,
+      tags: video.tags,
+      nsfw: video.nsfw
+    }
+
+    return this.authHttp.put(VideoService.BASE_VIDEO_URL + video.id, body)
+                        .map(this.restExtractor.extractDataBool)
+                        .catch(this.restExtractor.handleError)
+  }
+
+  uploadVideo (video: FormData) {
+    const req = new HttpRequest('POST', VideoService.BASE_VIDEO_URL + 'upload', video, { reportProgress: true })
+
+    return this.authHttp
+      .request(req)
+      .catch(this.restExtractor.handleError)
+  }
+
+  getMyVideos (videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> {
+    const pagination = this.videoPaginationToRestPagination(videoPagination)
+
+    let params = new HttpParams()
+    params = this.restService.addRestGetParams(params, pagination, sort)
+
+    return this.authHttp.get(UserService.BASE_USERS_URL + '/me/videos', { params })
+      .map(this.extractVideos)
+      .catch((res) => this.restExtractor.handleError(res))
+  }
+
+  getVideos (videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> {
+    const pagination = this.videoPaginationToRestPagination(videoPagination)
+
+    let params = new HttpParams()
+    params = this.restService.addRestGetParams(params, pagination, sort)
+
+    return this.authHttp
+      .get(VideoService.BASE_VIDEO_URL, { params })
+      .map(this.extractVideos)
+      .catch((res) => this.restExtractor.handleError(res))
+  }
+
+  searchVideos (search: Search, videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> {
+    const url = VideoService.BASE_VIDEO_URL + 'search/' + encodeURIComponent(search.value)
+
+    const pagination = this.videoPaginationToRestPagination(videoPagination)
+
+    let params = new HttpParams()
+    params = this.restService.addRestGetParams(params, pagination, sort)
+
+    if (search.field) params.set('field', search.field)
+
+    return this.authHttp
+      .get<ResultList<VideoServerModel>>(url, { params })
+      .map(this.extractVideos)
+      .catch((res) => this.restExtractor.handleError(res))
+  }
+
+  removeVideo (id: number) {
+    return this.authHttp
+      .delete(VideoService.BASE_VIDEO_URL + id)
+      .map(this.restExtractor.extractDataBool)
+      .catch((res) => this.restExtractor.handleError(res))
+  }
+
+  loadCompleteDescription (descriptionPath: string) {
+    return this.authHttp
+      .get(API_URL + descriptionPath)
+      .map(res => res['description'])
+      .catch((res) => this.restExtractor.handleError(res))
+  }
+
+  setVideoLike (id: number) {
+    return this.setVideoRate(id, 'like')
+  }
+
+  setVideoDislike (id: number) {
+    return this.setVideoRate(id, 'dislike')
+  }
+
+  getUserVideoRating (id: number): Observable<UserVideoRate> {
+    const url = UserService.BASE_USERS_URL + 'me/videos/' + id + '/rating'
+
+    return this.authHttp
+      .get(url)
+      .catch(res => this.restExtractor.handleError(res))
+  }
+
+  private videoPaginationToRestPagination (videoPagination: VideoPagination) {
+    const start: number = (videoPagination.currentPage - 1) * videoPagination.itemsPerPage
+    const count: number = videoPagination.itemsPerPage
+
+    return { start, count }
+  }
+
+  private setVideoRate (id: number, rateType: VideoRateType) {
+    const url = VideoService.BASE_VIDEO_URL + id + '/rate'
+    const body: UserVideoRateUpdate = {
+      rating: rateType
+    }
+
+    return this.authHttp
+      .put(url, body)
+      .map(this.restExtractor.extractDataBool)
+      .catch(res => this.restExtractor.handleError(res))
+  }
+
+  private extractVideos (result: ResultList<VideoServerModel>) {
+    const videosJson = result.data
+    const totalVideos = result.total
+    const videos = []
+
+    for (const videoJson of videosJson) {
+      videos.push(new Video(videoJson))
+    }
+
+    return { videos, totalVideos }
+  }
+}
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.module.ts b/client/src/app/videos/+video-edit/shared/video-edit.module.ts
index c64cea920..cdab694f9 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.module.ts
+++ b/client/src/app/videos/+video-edit/shared/video-edit.module.ts
@@ -3,7 +3,7 @@ import { NgModule } from '@angular/core'
 import { TagInputModule } from 'ngx-chips'
 import { TabsModule } from 'ngx-bootstrap/tabs'
 
-import { VideoService, MarkdownService, VideoDescriptionComponent } from '../../shared'
+import { MarkdownService, VideoDescriptionComponent } from '../../shared'
 import { SharedModule } from '../../../shared'
 
 @NgModule({
@@ -26,7 +26,6 @@ import { SharedModule } from '../../../shared'
   ],
 
   providers: [
-    VideoService,
     MarkdownService
   ]
 })
diff --git a/client/src/app/videos/+video-edit/video-add.component.ts b/client/src/app/videos/+video-edit/video-add.component.ts
index 76bfbb515..989addbd7 100644
--- a/client/src/app/videos/+video-edit/video-add.component.ts
+++ b/client/src/app/videos/+video-edit/video-add.component.ts
@@ -1,25 +1,23 @@
+import { HttpEventType, HttpResponse } from '@angular/common/http'
 import { Component, OnInit, ViewChild } from '@angular/core'
 import { FormBuilder, FormGroup } from '@angular/forms'
 import { Router } from '@angular/router'
-
 import { NotificationsService } from 'angular2-notifications'
-
+import { VideoService } from 'app/shared/video/video.service'
+import { VideoCreate } from '../../../../../shared'
+import { AuthService, ServerService } from '../../core'
 import {
   FormReactive,
-  VIDEO_NAME,
   VIDEO_CATEGORY,
-  VIDEO_LICENCE,
-  VIDEO_LANGUAGE,
-  VIDEO_DESCRIPTION,
-  VIDEO_TAGS,
   VIDEO_CHANNEL,
+  VIDEO_DESCRIPTION,
   VIDEO_FILE,
-  VIDEO_PRIVACY
+  VIDEO_LANGUAGE,
+  VIDEO_LICENCE,
+  VIDEO_NAME,
+  VIDEO_PRIVACY,
+  VIDEO_TAGS
 } from '../../shared'
-import { AuthService, ServerService } from '../../core'
-import { VideoService } from '../shared'
-import { VideoCreate } from '../../../../../shared'
-import { HttpEventType, HttpResponse } from '@angular/common/http'
 
 @Component({
   selector: 'my-videos-add',
diff --git a/client/src/app/videos/+video-edit/video-update.component.ts b/client/src/app/videos/+video-edit/video-update.component.ts
index 0e966cb50..017781866 100644
--- a/client/src/app/videos/+video-edit/video-update.component.ts
+++ b/client/src/app/videos/+video-edit/video-update.component.ts
@@ -1,23 +1,22 @@
 import { Component, OnInit } from '@angular/core'
 import { FormBuilder, FormGroup } from '@angular/forms'
 import { ActivatedRoute, Router } from '@angular/router'
-import 'rxjs/add/observable/forkJoin'
-
 import { NotificationsService } from 'angular2-notifications'
-
+import 'rxjs/add/observable/forkJoin'
+import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum'
 import { ServerService } from '../../core'
 import {
   FormReactive,
-  VIDEO_NAME,
   VIDEO_CATEGORY,
-  VIDEO_LICENCE,
-  VIDEO_LANGUAGE,
   VIDEO_DESCRIPTION,
-  VIDEO_TAGS,
-  VIDEO_PRIVACY
+  VIDEO_LANGUAGE,
+  VIDEO_LICENCE,
+  VIDEO_NAME,
+  VIDEO_PRIVACY,
+  VIDEO_TAGS
 } from '../../shared'
-import { VideoEdit, VideoService } from '../shared'
-import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum'
+import { VideoService } from '../../shared/video/video.service'
+import { VideoEdit } from '../../shared/video/video-edit.model'
 
 @Component({
   selector: 'my-videos-update',
diff --git a/client/src/app/videos/+video-watch/video-download.component.ts b/client/src/app/videos/+video-watch/video-download.component.ts
index c32f8d586..010a246cd 100644
--- a/client/src/app/videos/+video-watch/video-download.component.ts
+++ b/client/src/app/videos/+video-watch/video-download.component.ts
@@ -1,8 +1,6 @@
 import { Component, Input, ViewChild } from '@angular/core'
-
 import { ModalDirective } from 'ngx-bootstrap/modal'
-
-import { VideoDetails } from '../shared'
+import { VideoDetails } from '../../shared/video/video-details.model'
 
 @Component({
   selector: 'my-video-download',
diff --git a/client/src/app/videos/+video-watch/video-report.component.ts b/client/src/app/videos/+video-watch/video-report.component.ts
index fc9b5a9d4..b94e4144e 100644
--- a/client/src/app/videos/+video-watch/video-report.component.ts
+++ b/client/src/app/videos/+video-watch/video-report.component.ts
@@ -1,11 +1,9 @@
 import { Component, Input, OnInit, ViewChild } from '@angular/core'
 import { FormBuilder, FormGroup } from '@angular/forms'
-
-import { ModalDirective } from 'ngx-bootstrap/modal'
 import { NotificationsService } from 'angular2-notifications'
-
-import { FormReactive, VideoAbuseService, VIDEO_ABUSE_REASON } from '../../shared'
-import { VideoDetails, VideoService } from '../shared'
+import { ModalDirective } from 'ngx-bootstrap/modal'
+import { FormReactive, VIDEO_ABUSE_REASON, VideoAbuseService } from '../../shared'
+import { VideoDetails } from '../../shared/video/video-details.model'
 
 @Component({
   selector: 'my-video-report',
diff --git a/client/src/app/videos/+video-watch/video-share.component.ts b/client/src/app/videos/+video-watch/video-share.component.ts
index aeef65ecf..4df9adf29 100644
--- a/client/src/app/videos/+video-watch/video-share.component.ts
+++ b/client/src/app/videos/+video-watch/video-share.component.ts
@@ -1,8 +1,6 @@
 import { Component, Input, ViewChild } from '@angular/core'
-
 import { ModalDirective } from 'ngx-bootstrap/modal'
-
-import { VideoDetails } from '../shared'
+import { VideoDetails } from '../../shared/video/video-details.model'
 
 @Component({
   selector: 'my-video-share',
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts
index b26f3092f..eac676be8 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -2,6 +2,7 @@ import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/co
 import { ActivatedRoute, Router } from '@angular/router'
 import { MetaService } from '@ngx-meta/core'
 import { NotificationsService } from 'angular2-notifications'
+import { VideoService } from 'app/shared/video/video.service'
 import { Observable } from 'rxjs/Observable'
 import { Subscription } from 'rxjs/Subscription'
 import videojs from 'video.js'
@@ -9,10 +10,11 @@ import { UserVideoRateType, VideoRateType } from '../../../../../shared'
 import '../../../assets/player/peertube-videojs-plugin'
 import { AuthService, ConfirmService } from '../../core'
 import { VideoBlacklistService } from '../../shared'
-import { MarkdownService, VideoDetails, VideoService } from '../shared'
+import { MarkdownService } from '../shared'
 import { VideoDownloadComponent } from './video-download.component'
 import { VideoReportComponent } from './video-report.component'
 import { VideoShareComponent } from './video-share.component'
+import { VideoDetails } from '../../shared/video/video-details.model'
 
 @Component({
   selector: 'my-video-watch',
diff --git a/client/src/app/videos/+video-watch/video-watch.module.ts b/client/src/app/videos/+video-watch/video-watch.module.ts
index 1b983200d..0b1dd5c15 100644
--- a/client/src/app/videos/+video-watch/video-watch.module.ts
+++ b/client/src/app/videos/+video-watch/video-watch.module.ts
@@ -1,7 +1,7 @@
 import { NgModule } from '@angular/core'
 
 import { VideoWatchRoutingModule } from './video-watch-routing.module'
-import { VideoService, MarkdownService } from '../shared'
+import { MarkdownService } from '../shared'
 import { SharedModule } from '../../shared'
 
 import { VideoWatchComponent } from './video-watch.component'
@@ -28,8 +28,7 @@ import { VideoDownloadComponent } from './video-download.component'
   ],
 
   providers: [
-    MarkdownService,
-    VideoService
+    MarkdownService
   ]
 })
 export class VideoWatchModule { }
diff --git a/client/src/app/videos/shared/index.ts b/client/src/app/videos/shared/index.ts
index 3f1458088..3c72ed895 100644
--- a/client/src/app/videos/shared/index.ts
+++ b/client/src/app/videos/shared/index.ts
@@ -1,8 +1,2 @@
-export * from './sort-field.type'
 export * from './markdown.service'
-export * from './video.model'
-export * from './video-details.model'
-export * from './video-edit.model'
-export * from './video.service'
 export * from './video-description.component'
-export * from './video-pagination.model'
diff --git a/client/src/app/videos/shared/sort-field.type.ts b/client/src/app/videos/shared/sort-field.type.ts
deleted file mode 100644
index 776f360f8..000000000
--- a/client/src/app/videos/shared/sort-field.type.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export type SortField = 'name' | '-name'
-                      | 'duration' | '-duration'
-                      | 'createdAt' | '-createdAt'
-                      | 'views' | '-views'
-                      | 'likes' | '-likes'
diff --git a/client/src/app/videos/shared/video-details.model.ts b/client/src/app/videos/shared/video-details.model.ts
deleted file mode 100644
index 64cb4f847..000000000
--- a/client/src/app/videos/shared/video-details.model.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import { Video } from './video.model'
-import { AuthUser } from '../../core'
-import {
-  VideoDetails as VideoDetailsServerModel,
-  VideoFile,
-  VideoChannel,
-  VideoResolution,
-  UserRight,
-  VideoPrivacy
-} from '../../../../../shared'
-
-export class VideoDetails extends Video implements VideoDetailsServerModel {
-  account: string
-  by: string
-  createdAt: Date
-  updatedAt: Date
-  categoryLabel: string
-  category: number
-  licenceLabel: string
-  licence: number
-  languageLabel: string
-  language: number
-  description: string
-  duration: number
-  durationLabel: string
-  id: number
-  uuid: string
-  isLocal: boolean
-  name: string
-  serverHost: string
-  tags: string[]
-  thumbnailPath: string
-  thumbnailUrl: string
-  previewPath: string
-  previewUrl: string
-  embedPath: string
-  embedUrl: string
-  views: number
-  likes: number
-  dislikes: number
-  nsfw: boolean
-  descriptionPath: string
-  files: VideoFile[]
-  channel: VideoChannel
-  privacy: VideoPrivacy
-  privacyLabel: string
-
-  constructor (hash: VideoDetailsServerModel) {
-    super(hash)
-
-    this.privacy = hash.privacy
-    this.privacyLabel = hash.privacyLabel
-    this.descriptionPath = hash.descriptionPath
-    this.files = hash.files
-    this.channel = hash.channel
-  }
-
-  getAppropriateMagnetUri (actualDownloadSpeed = 0) {
-    if (this.files === undefined || this.files.length === 0) return ''
-    if (this.files.length === 1) return this.files[0].magnetUri
-
-    // Find first video that is good for our download speed (remember they are sorted)
-    let betterResolutionFile = this.files.find(f => actualDownloadSpeed > (f.size / this.duration))
-
-    // If the download speed is too bad, return the lowest resolution we have
-    if (betterResolutionFile === undefined) {
-      betterResolutionFile = this.files.find(f => f.resolution === VideoResolution.H_240P)
-    }
-
-    return betterResolutionFile.magnetUri
-  }
-
-  isRemovableBy (user: AuthUser) {
-    return user && this.isLocal === true && (this.account === user.username || user.hasRight(UserRight.REMOVE_ANY_VIDEO))
-  }
-
-  isBlackistableBy (user: AuthUser) {
-    return user && user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) === true && this.isLocal === false
-  }
-
-  isUpdatableBy (user: AuthUser) {
-    return user && this.isLocal === true && user.username === this.account
-  }
-}
diff --git a/client/src/app/videos/shared/video-edit.model.ts b/client/src/app/videos/shared/video-edit.model.ts
deleted file mode 100644
index 88d23a59f..000000000
--- a/client/src/app/videos/shared/video-edit.model.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { VideoDetails } from './video-details.model'
-import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum'
-
-export class VideoEdit {
-  category: number
-  licence: number
-  language: number
-  description: string
-  name: string
-  tags: string[]
-  nsfw: boolean
-  channel: number
-  privacy: VideoPrivacy
-  uuid?: string
-  id?: number
-
-  constructor (videoDetails: VideoDetails) {
-    this.id = videoDetails.id
-    this.uuid = videoDetails.uuid
-    this.category = videoDetails.category
-    this.licence = videoDetails.licence
-    this.language = videoDetails.language
-    this.description = videoDetails.description
-    this.name = videoDetails.name
-    this.tags = videoDetails.tags
-    this.nsfw = videoDetails.nsfw
-    this.channel = videoDetails.channel.id
-    this.privacy = videoDetails.privacy
-  }
-
-  patch (values: Object) {
-    Object.keys(values).forEach((key) => {
-      this[key] = values[key]
-    })
-  }
-
-  toJSON () {
-    return {
-      category: this.category,
-      licence: this.licence,
-      language: this.language,
-      description: this.description,
-      name: this.name,
-      tags: this.tags,
-      nsfw: this.nsfw,
-      channel: this.channel,
-      privacy: this.privacy
-    }
-  }
-}
diff --git a/client/src/app/videos/shared/video-pagination.model.ts b/client/src/app/videos/shared/video-pagination.model.ts
deleted file mode 100644
index 9e71769cb..000000000
--- a/client/src/app/videos/shared/video-pagination.model.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export interface VideoPagination {
-  currentPage: number
-  itemsPerPage: number
-  totalItems: number
-}
diff --git a/client/src/app/videos/shared/video.model.ts b/client/src/app/videos/shared/video.model.ts
deleted file mode 100644
index 0dd41d71b..000000000
--- a/client/src/app/videos/shared/video.model.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import { Video as VideoServerModel } from '../../../../../shared'
-import { User } from '../../shared'
-
-export class Video implements VideoServerModel {
-  account: string
-  by: string
-  createdAt: Date
-  updatedAt: Date
-  categoryLabel: string
-  category: number
-  licenceLabel: string
-  licence: number
-  languageLabel: string
-  language: number
-  description: string
-  duration: number
-  durationLabel: string
-  id: number
-  uuid: string
-  isLocal: boolean
-  name: string
-  serverHost: string
-  tags: string[]
-  thumbnailPath: string
-  thumbnailUrl: string
-  previewPath: string
-  previewUrl: string
-  embedPath: string
-  embedUrl: string
-  views: number
-  likes: number
-  dislikes: number
-  nsfw: boolean
-
-  private static createByString (account: string, serverHost: string) {
-    return account + '@' + serverHost
-  }
-
-  private static createDurationString (duration: number) {
-    const minutes = Math.floor(duration / 60)
-    const seconds = duration % 60
-    const minutesPadding = minutes >= 10 ? '' : '0'
-    const secondsPadding = seconds >= 10 ? '' : '0'
-
-    return minutesPadding + minutes.toString() + ':' + secondsPadding + seconds.toString()
-  }
-
-  constructor (hash: VideoServerModel) {
-    let absoluteAPIUrl = API_URL
-    if (!absoluteAPIUrl) {
-      // The API is on the same domain
-      absoluteAPIUrl = window.location.origin
-    }
-
-    this.account = hash.account
-    this.createdAt = new Date(hash.createdAt.toString())
-    this.categoryLabel = hash.categoryLabel
-    this.category = hash.category
-    this.licenceLabel = hash.licenceLabel
-    this.licence = hash.licence
-    this.languageLabel = hash.languageLabel
-    this.language = hash.language
-    this.description = hash.description
-    this.duration = hash.duration
-    this.durationLabel = Video.createDurationString(hash.duration)
-    this.id = hash.id
-    this.uuid = hash.uuid
-    this.isLocal = hash.isLocal
-    this.name = hash.name
-    this.serverHost = hash.serverHost
-    this.tags = hash.tags
-    this.thumbnailPath = hash.thumbnailPath
-    this.thumbnailUrl = absoluteAPIUrl + hash.thumbnailPath
-    this.previewPath = hash.previewPath
-    this.previewUrl = absoluteAPIUrl + hash.previewPath
-    this.embedPath = hash.embedPath
-    this.embedUrl = absoluteAPIUrl + hash.embedPath
-    this.views = hash.views
-    this.likes = hash.likes
-    this.dislikes = hash.dislikes
-    this.nsfw = hash.nsfw
-
-    this.by = Video.createByString(hash.account, hash.serverHost)
-  }
-
-  isVideoNSFWForUser (user: User) {
-    // If the video is NSFW and the user is not logged in, or the user does not want to display NSFW videos...
-    return (this.nsfw && (!user || user.displayNSFW === false))
-  }
-}
diff --git a/client/src/app/videos/shared/video.service.ts b/client/src/app/videos/shared/video.service.ts
deleted file mode 100644
index 5d25a26d4..000000000
--- a/client/src/app/videos/shared/video.service.ts
+++ /dev/null
@@ -1,176 +0,0 @@
-import { Injectable } from '@angular/core'
-import { Observable } from 'rxjs/Observable'
-import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http'
-import 'rxjs/add/operator/catch'
-import 'rxjs/add/operator/map'
-
-import { SortField } from './sort-field.type'
-import {
-  RestExtractor,
-  RestService,
-  UserService,
-  Search
-} from '../../shared'
-import { Video } from './video.model'
-import { VideoDetails } from './video-details.model'
-import { VideoEdit } from './video-edit.model'
-import { VideoPagination } from './video-pagination.model'
-import {
-  UserVideoRate,
-  VideoRateType,
-  VideoUpdate,
-  UserVideoRateUpdate,
-  Video as VideoServerModel,
-  VideoDetails as VideoDetailsServerModel,
-  ResultList
-} from '../../../../../shared'
-
-@Injectable()
-export class VideoService {
-  private static BASE_VIDEO_URL = API_URL + '/api/v1/videos/'
-
-  constructor (
-    private authHttp: HttpClient,
-    private restExtractor: RestExtractor,
-    private restService: RestService
-  ) {}
-
-  getVideo (uuid: string): Observable<VideoDetails> {
-    return this.authHttp.get<VideoDetailsServerModel>(VideoService.BASE_VIDEO_URL + uuid)
-                        .map(videoHash => new VideoDetails(videoHash))
-                        .catch((res) => this.restExtractor.handleError(res))
-  }
-
-  viewVideo (uuid: string): Observable<VideoDetails> {
-    return this.authHttp.post(VideoService.BASE_VIDEO_URL + uuid + '/views', {})
-      .map(this.restExtractor.extractDataBool)
-      .catch(this.restExtractor.handleError)
-  }
-
-  updateVideo (video: VideoEdit) {
-    const language = video.language ? video.language : null
-
-    const body: VideoUpdate = {
-      name: video.name,
-      category: video.category,
-      licence: video.licence,
-      language,
-      description: video.description,
-      privacy: video.privacy,
-      tags: video.tags,
-      nsfw: video.nsfw
-    }
-
-    return this.authHttp.put(VideoService.BASE_VIDEO_URL + video.id, body)
-                        .map(this.restExtractor.extractDataBool)
-                        .catch(this.restExtractor.handleError)
-  }
-
-  uploadVideo (video: FormData) {
-    const req = new HttpRequest('POST', VideoService.BASE_VIDEO_URL + 'upload', video, { reportProgress: true })
-
-    return this.authHttp
-      .request(req)
-      .catch(this.restExtractor.handleError)
-  }
-
-  getMyVideos (videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> {
-    const pagination = this.videoPaginationToRestPagination(videoPagination)
-
-    let params = new HttpParams()
-    params = this.restService.addRestGetParams(params, pagination, sort)
-
-    return this.authHttp.get(UserService.BASE_USERS_URL + '/me/videos', { params })
-      .map(this.extractVideos)
-      .catch((res) => this.restExtractor.handleError(res))
-  }
-
-  getVideos (videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> {
-    const pagination = this.videoPaginationToRestPagination(videoPagination)
-
-    let params = new HttpParams()
-    params = this.restService.addRestGetParams(params, pagination, sort)
-
-    return this.authHttp
-      .get(VideoService.BASE_VIDEO_URL, { params })
-      .map(this.extractVideos)
-      .catch((res) => this.restExtractor.handleError(res))
-  }
-
-  searchVideos (search: Search, videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> {
-    const url = VideoService.BASE_VIDEO_URL + 'search/' + encodeURIComponent(search.value)
-
-    const pagination = this.videoPaginationToRestPagination(videoPagination)
-
-    let params = new HttpParams()
-    params = this.restService.addRestGetParams(params, pagination, sort)
-
-    if (search.field) params.set('field', search.field)
-
-    return this.authHttp
-      .get<ResultList<VideoServerModel>>(url, { params })
-      .map(this.extractVideos)
-      .catch((res) => this.restExtractor.handleError(res))
-  }
-
-  removeVideo (id: number) {
-    return this.authHttp
-      .delete(VideoService.BASE_VIDEO_URL + id)
-      .map(this.restExtractor.extractDataBool)
-      .catch((res) => this.restExtractor.handleError(res))
-  }
-
-  loadCompleteDescription (descriptionPath: string) {
-    return this.authHttp
-      .get(API_URL + descriptionPath)
-      .map(res => res['description'])
-      .catch((res) => this.restExtractor.handleError(res))
-  }
-
-  setVideoLike (id: number) {
-    return this.setVideoRate(id, 'like')
-  }
-
-  setVideoDislike (id: number) {
-    return this.setVideoRate(id, 'dislike')
-  }
-
-  getUserVideoRating (id: number): Observable<UserVideoRate> {
-    const url = UserService.BASE_USERS_URL + 'me/videos/' + id + '/rating'
-
-    return this.authHttp
-      .get(url)
-      .catch(res => this.restExtractor.handleError(res))
-  }
-
-  private videoPaginationToRestPagination (videoPagination: VideoPagination) {
-    const start: number = (videoPagination.currentPage - 1) * videoPagination.itemsPerPage
-    const count: number = videoPagination.itemsPerPage
-
-    return { start, count }
-  }
-
-  private setVideoRate (id: number, rateType: VideoRateType) {
-    const url = VideoService.BASE_VIDEO_URL + id + '/rate'
-    const body: UserVideoRateUpdate = {
-      rating: rateType
-    }
-
-    return this.authHttp
-      .put(url, body)
-      .map(this.restExtractor.extractDataBool)
-      .catch(res => this.restExtractor.handleError(res))
-  }
-
-  private extractVideos (result: ResultList<VideoServerModel>) {
-    const videosJson = result.data
-    const totalVideos = result.total
-    const videos = []
-
-    for (const videoJson of videosJson) {
-      videos.push(new Video(videoJson))
-    }
-
-    return { videos, totalVideos }
-  }
-}
diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.html b/client/src/app/videos/video-list/shared/abstract-video-list.html
deleted file mode 100644
index bd4f6b1f8..000000000
--- a/client/src/app/videos/video-list/shared/abstract-video-list.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<div class="margin-content">
-  <div class="title-page title-page-single">
-    {{ titlePage }}
-  </div>
-
-  <div
-    infiniteScroll
-    [infiniteScrollUpDistance]="1.5"
-    [infiniteScrollDistance]="0.5"
-    (scrolled)="onNearOfBottom()"
-    (scrolledUp)="onNearOfTop()"
-  >
-    <my-video-miniature
-      class="ng-animate"
-      *ngFor="let video of videos" [video]="video" [user]="user" [currentSort]="sort"
-    >
-    </my-video-miniature>
-  </div>
-</div>
diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.scss b/client/src/app/videos/video-list/shared/abstract-video-list.scss
deleted file mode 100644
index e69de29bb..000000000
diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.ts b/client/src/app/videos/video-list/shared/abstract-video-list.ts
deleted file mode 100644
index a684ffef4..000000000
--- a/client/src/app/videos/video-list/shared/abstract-video-list.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import { OnDestroy, OnInit } from '@angular/core'
-import { ActivatedRoute, Router } from '@angular/router'
-
-import { NotificationsService } from 'angular2-notifications'
-import { Observable } from 'rxjs/Observable'
-import { Subscription } from 'rxjs/Subscription'
-
-import { SortField, Video, VideoPagination } from '../../shared'
-
-export abstract class AbstractVideoList implements OnInit, OnDestroy {
-  pagination: VideoPagination = {
-    currentPage: 1,
-    itemsPerPage: 25,
-    totalItems: null
-  }
-  sort: SortField = '-createdAt'
-  videos: Video[] = []
-
-  protected notificationsService: NotificationsService
-  protected router: Router
-  protected route: ActivatedRoute
-  protected subActivatedRoute: Subscription
-
-  protected abstract currentRoute: string
-
-  abstract titlePage: string
-  private loadedPages: { [ id: number ]: boolean } = {}
-
-  abstract getVideosObservable (): Observable<{ videos: Video[], totalVideos: number}>
-
-  ngOnInit () {
-    // Subscribe to route changes
-    const routeParams = this.route.snapshot.params
-    this.loadRouteParams(routeParams)
-    this.loadMoreVideos('after')
-  }
-
-  ngOnDestroy () {
-    if (this.subActivatedRoute) {
-      this.subActivatedRoute.unsubscribe()
-    }
-  }
-
-  onNearOfTop () {
-    if (this.pagination.currentPage > 1) {
-      this.previousPage()
-    }
-  }
-
-  onNearOfBottom () {
-    if (this.hasMoreVideos()) {
-      this.nextPage()
-    }
-  }
-
-  loadMoreVideos (where: 'before' | 'after') {
-    if (this.loadedPages[this.pagination.currentPage] === true) return
-
-    const observable = this.getVideosObservable()
-
-    observable.subscribe(
-      ({ videos, totalVideos }) => {
-        this.loadedPages[this.pagination.currentPage] = true
-        this.pagination.totalItems = totalVideos
-
-        if (where === 'before') {
-          this.videos = videos.concat(this.videos)
-        } else {
-          this.videos = this.videos.concat(videos)
-        }
-      },
-      error => this.notificationsService.error('Error', error.text)
-    )
-  }
-
-  protected hasMoreVideos () {
-    if (!this.pagination.totalItems) return true
-
-    const maxPage = this.pagination.totalItems/this.pagination.itemsPerPage
-    return maxPage > this.pagination.currentPage
-  }
-
-  protected previousPage () {
-    this.pagination.currentPage--
-
-    this.setNewRouteParams()
-    this.loadMoreVideos('before')
-  }
-
-  protected nextPage () {
-    this.pagination.currentPage++
-
-    this.setNewRouteParams()
-    this.loadMoreVideos('after')
-  }
-
-  protected buildRouteParams () {
-    // There is always a sort and a current page
-    const params = {
-      sort: this.sort,
-      page: this.pagination.currentPage
-    }
-
-    return params
-  }
-
-  protected loadRouteParams (routeParams: { [ key: string ]: any }) {
-    this.sort = routeParams['sort'] as SortField || '-createdAt'
-
-    if (routeParams['page'] !== undefined) {
-      this.pagination.currentPage = parseInt(routeParams['page'], 10)
-    } else {
-      this.pagination.currentPage = 1
-    }
-  }
-
-  protected setNewRouteParams () {
-    const routeParams = this.buildRouteParams()
-    this.router.navigate([ this.currentRoute, routeParams ])
-  }
-}
diff --git a/client/src/app/videos/video-list/shared/index.ts b/client/src/app/videos/video-list/shared/index.ts
index 170ca4832..2778f2d9e 100644
--- a/client/src/app/videos/video-list/shared/index.ts
+++ b/client/src/app/videos/video-list/shared/index.ts
@@ -1,2 +1 @@
-export * from './abstract-video-list'
 export * from './video-miniature.component'
diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.html b/client/src/app/videos/video-list/shared/video-miniature.component.html
index aea85b6c6..f2756ca3d 100644
--- a/client/src/app/videos/video-list/shared/video-miniature.component.html
+++ b/client/src/app/videos/video-list/shared/video-miniature.component.html
@@ -1,14 +1,5 @@
 <div class="video-miniature">
-  <a
-    [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.description"
-    class="video-miniature-thumbnail"
-  >
-    <img [attr.src]="video.thumbnailUrl" alt="video thumbnail" [ngClass]="{ 'blur-filter': isVideoNSFWForThisUser() }" />
-
-    <div class="video-miniature-thumbnail-overlay">
-      {{ video.durationLabel }}
-    </div>
-  </a>
+  <my-video-thumbnail [video]="video" [nsfw]="isVideoNSFWForThisUser()"></my-video-thumbnail>
 
   <div class="video-miniature-information">
     <span class="video-miniature-name">
diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.scss b/client/src/app/videos/video-list/shared/video-miniature.component.scss
index ed15864d9..658d7af9d 100644
--- a/client/src/app/videos/video-list/shared/video-miniature.component.scss
+++ b/client/src/app/videos/video-list/shared/video-miniature.component.scss
@@ -5,35 +5,6 @@
   height: 175px;
   vertical-align: top;
 
-  .video-miniature-thumbnail {
-    display: inline-block;
-    position: relative;
-    border-radius: 4px;
-    overflow: hidden;
-
-    &:hover {
-      text-decoration: none !important;
-    }
-
-    img.blur-filter {
-      filter: blur(5px);
-      transform : scale(1.03);
-    }
-
-    .video-miniature-thumbnail-overlay {
-      position: absolute;
-      right: 5px;
-      bottom: 5px;
-      display: inline-block;
-      background-color: rgba(0, 0, 0, 0.7);
-      color: #fff;
-      font-size: 12px;
-      font-weight: $font-bold;
-      border-radius: 3px;
-      padding: 0 5px;
-    }
-  }
-
   .video-miniature-information {
     width: 200px;
     margin-top: 2px;
diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.ts b/client/src/app/videos/video-list/shared/video-miniature.component.ts
index e5a87907b..e8fc8e911 100644
--- a/client/src/app/videos/video-list/shared/video-miniature.component.ts
+++ b/client/src/app/videos/video-list/shared/video-miniature.component.ts
@@ -1,7 +1,7 @@
 import { Component, Input } from '@angular/core'
-
-import { SortField, Video } from '../../shared'
 import { User } from '../../../shared'
+import { SortField } from '../../../shared/video/sort-field.type'
+import { Video } from '../../../shared/video/video.model'
 
 @Component({
   selector: 'my-video-miniature',
diff --git a/client/src/app/videos/video-list/video-recently-added.component.ts b/client/src/app/videos/video-list/video-recently-added.component.ts
index 9bf69bd78..d48804414 100644
--- a/client/src/app/videos/video-list/video-recently-added.component.ts
+++ b/client/src/app/videos/video-list/video-recently-added.component.ts
@@ -1,13 +1,13 @@
 import { Component, OnDestroy, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { NotificationsService } from 'angular2-notifications'
-import { VideoService } from '../shared'
-import { AbstractVideoList } from './shared'
+import { VideoService } from '../../shared/video/video.service'
+import { AbstractVideoList } from '../../shared/video/abstract-video-list'
 
 @Component({
   selector: 'my-videos-recently-added',
-  styleUrls: [ './shared/abstract-video-list.scss' ],
-  templateUrl: './shared/abstract-video-list.html'
+  styleUrls: [ '../../shared/video/abstract-video-list.scss' ],
+  templateUrl: '../../shared/video/abstract-video-list.html'
 })
 export class VideoRecentlyAddedComponent extends AbstractVideoList implements OnInit, OnDestroy {
   titlePage = 'Recently added'
diff --git a/client/src/app/videos/video-list/video-trending.component.ts b/client/src/app/videos/video-list/video-trending.component.ts
index a1df68711..9108289c9 100644
--- a/client/src/app/videos/video-list/video-trending.component.ts
+++ b/client/src/app/videos/video-list/video-trending.component.ts
@@ -1,13 +1,13 @@
 import { Component, OnDestroy, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { NotificationsService } from 'angular2-notifications'
-import { VideoService } from '../shared'
-import { AbstractVideoList } from './shared'
+import { VideoService } from '../../shared/video/video.service'
+import { AbstractVideoList } from 'app/shared/video/abstract-video-list'
 
 @Component({
   selector: 'my-videos-trending',
-  styleUrls: [ './shared/abstract-video-list.scss' ],
-  templateUrl: './shared/abstract-video-list.html'
+  styleUrls: [ '../../shared/video/abstract-video-list.scss' ],
+  templateUrl: '../../shared/video/abstract-video-list.html'
 })
 export class VideoTrendingComponent extends AbstractVideoList implements OnInit, OnDestroy {
   titlePage = 'Trending'
diff --git a/client/src/app/videos/videos.module.ts b/client/src/app/videos/videos.module.ts
index 1d6194158..6d846fd3b 100644
--- a/client/src/app/videos/videos.module.ts
+++ b/client/src/app/videos/videos.module.ts
@@ -1,7 +1,5 @@
 import { NgModule } from '@angular/core'
-import { InfiniteScrollModule } from 'ngx-infinite-scroll'
 import { SharedModule } from '../shared'
-import { VideoService } from './shared'
 import { VideoMiniatureComponent } from './video-list'
 import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component'
 import { VideoTrendingComponent } from './video-list/video-trending.component'
@@ -11,8 +9,7 @@ import { VideosComponent } from './videos.component'
 @NgModule({
   imports: [
     VideosRoutingModule,
-    SharedModule,
-    InfiniteScrollModule
+    SharedModule
   ],
 
   declarations: [
@@ -27,8 +24,6 @@ import { VideosComponent } from './videos.component'
     VideosComponent
   ],
 
-  providers: [
-    VideoService
-  ]
+  providers: []
 })
 export class VideosModule { }
-- 
cgit v1.2.3


From 2295ce6c4e7ba805cc100ff961527bebc2cd89e5 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Mon, 4 Dec 2017 10:34:40 +0100
Subject: Add account avatar

---
 .../account-settings.component.html                |  10 ++++--
 .../account-settings.component.scss                |  21 ++++++++++--
 .../account-settings/account-settings.component.ts |   4 +++
 client/src/app/core/auth/auth.service.ts           |  36 ++++++++-------------
 client/src/app/menu/menu.component.html            |   2 ++
 client/src/app/menu/menu.component.scss            |   8 ++++-
 client/src/app/menu/menu.component.ts              |   4 +++
 client/src/app/shared/users/user.model.ts          |  25 ++++++--------
 client/src/assets/default-avatar.png               | Bin 0 -> 1674 bytes
 client/src/sass/_mixins.scss                       |   5 +++
 10 files changed, 72 insertions(+), 43 deletions(-)
 create mode 100644 client/src/assets/default-avatar.png

(limited to 'client')

diff --git a/client/src/app/account/account-settings/account-settings.component.html b/client/src/app/account/account-settings/account-settings.component.html
index 2509eb5aa..9e9f688d2 100644
--- a/client/src/app/account/account-settings/account-settings.component.html
+++ b/client/src/app/account/account-settings/account-settings.component.html
@@ -1,7 +1,13 @@
-<div class="user-info">
-  {{ user.username }}
+<div class="user">
+  <img [src]="getAvatarPath()" 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="account-title">Account settings</div>
 <my-account-change-password></my-account-change-password>
 
diff --git a/client/src/app/account/account-settings/account-settings.component.scss b/client/src/app/account/account-settings/account-settings.component.scss
index a0822631d..f514809b0 100644
--- a/client/src/app/account/account-settings/account-settings.component.scss
+++ b/client/src/app/account/account-settings/account-settings.component.scss
@@ -1,6 +1,21 @@
-.user-info {
-  font-size: 20px;
-  font-weight: $font-bold;
+.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;
+    }
+  }
 }
 
 .account-title {
diff --git a/client/src/app/account/account-settings/account-settings.component.ts b/client/src/app/account/account-settings/account-settings.component.ts
index c3b670e02..cba251000 100644
--- a/client/src/app/account/account-settings/account-settings.component.ts
+++ b/client/src/app/account/account-settings/account-settings.component.ts
@@ -15,4 +15,8 @@ export class AccountSettingsComponent implements OnInit {
   ngOnInit () {
     this.user = this.authService.getUser()
   }
+
+  getAvatarPath () {
+    return this.user.getAvatarPath()
+  }
 }
diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts
index 9e6c6b888..fd2708c11 100644
--- a/client/src/app/core/auth/auth.service.ts
+++ b/client/src/app/core/auth/auth.service.ts
@@ -1,30 +1,25 @@
+import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'
 import { Injectable } from '@angular/core'
 import { Router } from '@angular/router'
-import { Observable } from 'rxjs/Observable'
-import { Subject } from 'rxjs/Subject'
-import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'
-import { ReplaySubject } from 'rxjs/ReplaySubject'
+
+import { NotificationsService } from 'angular2-notifications'
+import 'rxjs/add/observable/throw'
 import 'rxjs/add/operator/do'
 import 'rxjs/add/operator/map'
 import 'rxjs/add/operator/mergeMap'
-import 'rxjs/add/observable/throw'
-
-import { NotificationsService } from 'angular2-notifications'
-
-import { AuthStatus } from './auth-status.model'
-import { AuthUser } from './auth-user.model'
-import {
-  OAuthClientLocal,
-  UserRole,
-  UserRefreshToken,
-  VideoChannel,
-  User as UserServerModel
-} from '../../../../../shared'
+import { Observable } from 'rxjs/Observable'
+import { ReplaySubject } from 'rxjs/ReplaySubject'
+import { Subject } from 'rxjs/Subject'
+import { OAuthClientLocal, User as UserServerModel, UserRefreshToken, UserRole, VideoChannel } from '../../../../../shared'
+import { Account } from '../../../../../shared/models/accounts'
+import { UserLogin } from '../../../../../shared/models/users/user-login.model'
 // Do not use the barrel (dependency loop)
 import { RestExtractor } from '../../shared/rest'
-import { UserLogin } from '../../../../../shared/models/users/user-login.model'
 import { UserConstructorHash } from '../../shared/users/user.model'
 
+import { AuthStatus } from './auth-status.model'
+import { AuthUser } from './auth-user.model'
+
 interface UserLoginWithUsername extends UserLogin {
   access_token: string
   refresh_token: string
@@ -42,10 +37,7 @@ interface UserLoginWithUserInformation extends UserLogin {
   displayNSFW: boolean
   email: string
   videoQuota: number
-  account: {
-    id: number
-    uuid: string
-  }
+  account: Account
   videoChannels: VideoChannel[]
 }
 
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html
index 0ed8ec518..7a80fa4de 100644
--- a/client/src/app/menu/menu.component.html
+++ b/client/src/app/menu/menu.component.html
@@ -1,5 +1,7 @@
 <menu>
   <div *ngIf="isLoggedIn" class="logged-in-block">
+    <img [src]="getUserAvatarPath()" alt="Avatar" />
+
     <div class="logged-in-info">
       <a routerLink="/account/settings" class="logged-in-username">{{ user.username  }}</a>
       <div class="logged-in-email">{{ user.email }}</div>
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss
index 9d67ca66c..5d6fd61c6 100644
--- a/client/src/app/menu/menu.component.scss
+++ b/client/src/app/menu/menu.component.scss
@@ -21,9 +21,15 @@ menu {
     justify-content: center;
     margin-bottom: 35px;
 
+    img {
+      margin-left: 20px;
+      margin-right: 10px;
+
+      @include avatar(34px);
+    }
+
     .logged-in-info {
       flex-grow: 1;
-      margin-left: 40px;
 
       .logged-in-username {
         font-size: 16px;
diff --git a/client/src/app/menu/menu.component.ts b/client/src/app/menu/menu.component.ts
index 4c35bb3a5..8b8b714a8 100644
--- a/client/src/app/menu/menu.component.ts
+++ b/client/src/app/menu/menu.component.ts
@@ -51,6 +51,10 @@ export class MenuComponent implements OnInit {
     )
   }
 
+  getUserAvatarPath () {
+    return this.user.getAvatarPath()
+  }
+
   isRegistrationAllowed () {
     return this.serverService.getConfig().signup.allowed
   }
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts
index b075ab717..83990d8b8 100644
--- a/client/src/app/shared/users/user.model.ts
+++ b/client/src/app/shared/users/user.model.ts
@@ -1,10 +1,5 @@
-import {
-  User as UserServerModel,
-  UserRole,
-  VideoChannel,
-  UserRight,
-  hasUserRight
-} from '../../../../../shared'
+import { hasUserRight, User as UserServerModel, UserRight, UserRole, VideoChannel } from '../../../../../shared'
+import { Account } from '../../../../../shared/models/accounts'
 
 export type UserConstructorHash = {
   id: number,
@@ -14,10 +9,7 @@ export type UserConstructorHash = {
   videoQuota?: number,
   displayNSFW?: boolean,
   createdAt?: Date,
-  account?: {
-    id: number
-    uuid: string
-  },
+  account?: Account,
   videoChannels?: VideoChannel[]
 }
 export class User implements UserServerModel {
@@ -27,10 +19,7 @@ export class User implements UserServerModel {
   role: UserRole
   displayNSFW: boolean
   videoQuota: number
-  account: {
-    id: number
-    uuid: string
-  }
+  account: Account
   videoChannels: VideoChannel[]
   createdAt: Date
 
@@ -61,4 +50,10 @@ export class User implements UserServerModel {
   hasRight (right: UserRight) {
     return hasUserRight(this.role, right)
   }
+
+  getAvatarPath () {
+    if (this.account && this.account.avatar) return this.account.avatar.path
+
+    return '/assets/default-avatar.png'
+  }
 }
diff --git a/client/src/assets/default-avatar.png b/client/src/assets/default-avatar.png
new file mode 100644
index 000000000..4b7fd2c0a
Binary files /dev/null and b/client/src/assets/default-avatar.png differ
diff --git a/client/src/sass/_mixins.scss b/client/src/sass/_mixins.scss
index 5798b8f6e..e44cf064d 100644
--- a/client/src/sass/_mixins.scss
+++ b/client/src/sass/_mixins.scss
@@ -39,3 +39,8 @@
   @include peertube-button;
   @include disable-default-a-behaviour;
 }
+
+@mixin avatar ($size) {
+  width: $size;
+  height: $size;
+}
-- 
cgit v1.2.3


From d178b5c1f8eabb0face854d48e708f0ac72e7d2e Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Mon, 4 Dec 2017 10:40:02 +0100
Subject: Add margin when main content is not expanded

---
 .../src/app/account/account-settings/account-settings.component.html | 2 +-
 client/src/sass/_variables.scss                                      | 1 +
 client/src/sass/application.scss                                     | 5 +++--
 3 files changed, 5 insertions(+), 3 deletions(-)

(limited to 'client')

diff --git a/client/src/app/account/account-settings/account-settings.component.html b/client/src/app/account/account-settings/account-settings.component.html
index 9e9f688d2..c0a74cc47 100644
--- a/client/src/app/account/account-settings/account-settings.component.html
+++ b/client/src/app/account/account-settings/account-settings.component.html
@@ -3,7 +3,7 @@
 
   <div class="user-info">
     <div class="user-info-username">{{ user.username }}</div>
-    <div class="user-info-followers">{{ user.account.followersCount }} subscribers</div>
+    <div class="user-info-followers">{{ user.account?.followersCount }} subscribers</div>
   </div>
 </div>
 
diff --git a/client/src/sass/_variables.scss b/client/src/sass/_variables.scss
index d05452367..0d655e85c 100644
--- a/client/src/sass/_variables.scss
+++ b/client/src/sass/_variables.scss
@@ -9,6 +9,7 @@ $black-background: #000;
 $grey-background: #f6f2f2;
 
 $expanded-horizontal-margins: 150px;
+$not-expanded-horizontal-margins: 30px;
 
 $button-height: 30px;
 
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index db63db5f5..5e401f93b 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -52,8 +52,8 @@ input.readonly {
   }
 
   .margin-content {
-    margin-left: 10px;
-    margin-right: 10px;
+    margin-left: $not-expanded-horizontal-margins;
+    margin-right: $not-expanded-horizontal-margins;
   }
 
   .sub-menu {
@@ -63,6 +63,7 @@ input.readonly {
     margin-bottom: 30px;
     display: flex;
     align-items: center;
+    padding-left: $not-expanded-horizontal-margins;
   }
 
   // Override some properties if the main content is expanded (no menu on the left)
-- 
cgit v1.2.3


From d2cc03aaad62fa6cf1c64622229bcc83f24fccb6 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Mon, 4 Dec 2017 11:04:08 +0100
Subject: Design account videos

---
 .../account-videos/account-videos.component.html   |  12 ++-
 .../account-videos/account-videos.component.scss   |  44 ++++++++
 client/src/app/app.component.scss                  |   4 +-
 client/src/app/menu/menu.component.scss            |   6 +-
 client/src/app/shared/search/search.component.scss |   4 +-
 client/src/app/shared/users/user.model.ts          |   2 +-
 client/src/assets/default-avatar.png               | Bin 1674 -> 0 bytes
 client/src/assets/favicon.png                      | Bin 2335 -> 0 bytes
 client/src/assets/header/menu.svg                  |  14 ---
 client/src/assets/header/search.svg                |  12 ---
 client/src/assets/header/upload.svg                |  16 ---
 client/src/assets/images/account/edit.svg          |  15 +++
 client/src/assets/images/default-avatar.png        | Bin 0 -> 1674 bytes
 client/src/assets/images/favicon.png               | Bin 0 -> 2335 bytes
 client/src/assets/images/header/menu.svg           |  14 +++
 client/src/assets/images/header/search.svg         |  12 +++
 client/src/assets/images/header/upload.svg         |  16 +++
 client/src/assets/images/logo.svg                  | 118 +++++++++++++++++++++
 client/src/assets/images/menu/administration.svg   |  14 +++
 client/src/assets/images/menu/recently-added.svg   |  13 +++
 client/src/assets/images/menu/trending.svg         |  16 +++
 client/src/assets/logo.svg                         | 118 ---------------------
 client/src/assets/menu/administration.svg          |  14 ---
 client/src/assets/menu/recently-added.svg          |  13 ---
 client/src/assets/menu/trending.svg                |  16 ---
 25 files changed, 281 insertions(+), 212 deletions(-)
 delete mode 100644 client/src/assets/default-avatar.png
 delete mode 100644 client/src/assets/favicon.png
 delete mode 100644 client/src/assets/header/menu.svg
 delete mode 100644 client/src/assets/header/search.svg
 delete mode 100644 client/src/assets/header/upload.svg
 create mode 100644 client/src/assets/images/account/edit.svg
 create mode 100644 client/src/assets/images/default-avatar.png
 create mode 100644 client/src/assets/images/favicon.png
 create mode 100644 client/src/assets/images/header/menu.svg
 create mode 100644 client/src/assets/images/header/search.svg
 create mode 100644 client/src/assets/images/header/upload.svg
 create mode 100644 client/src/assets/images/logo.svg
 create mode 100644 client/src/assets/images/menu/administration.svg
 create mode 100644 client/src/assets/images/menu/recently-added.svg
 create mode 100644 client/src/assets/images/menu/trending.svg
 delete mode 100644 client/src/assets/logo.svg
 delete mode 100644 client/src/assets/menu/administration.svg
 delete mode 100644 client/src/assets/menu/recently-added.svg
 delete mode 100644 client/src/assets/menu/trending.svg

(limited to 'client')

diff --git a/client/src/app/account/account-videos/account-videos.component.html b/client/src/app/account/account-videos/account-videos.component.html
index 6c8ac4508..94b976869 100644
--- a/client/src/app/account/account-videos/account-videos.component.html
+++ b/client/src/app/account/account-videos/account-videos.component.html
@@ -3,7 +3,17 @@
   [infiniteScrollDistance]="0.5"
   (scrolled)="onNearOfBottom()"
 >
-  <div *ngFor="let video of videos">
+  <div class="video" *ngFor="let video of videos">
     <my-video-thumbnail [video]="video"></my-video-thumbnail>
+
+    <div class="video-info">
+      <div class="video-info-name">{{ video.name }}</div>
+      <span class="video-info-date-views">{{ video.createdAt | fromNow }} - {{ video.views | numberFormatter }} views</span>
+    </div>
+
+    <a class="edit-button" [routerLink]="[ '/videos', video.id, '/edit' ]">
+      <span class="icon icon-edit"></span>
+      Edit
+    </a>
   </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
index e69de29bb..b26933d22 100644
--- a/client/src/app/account/account-videos/account-videos.component.scss
+++ b/client/src/app/account/account-videos/account-videos.component.scss
@@ -0,0 +1,44 @@
+.video {
+  display: flex;
+  height: 130px;
+  padding-bottom: 20px;
+  margin-bottom: 20px;
+  border-bottom: 1px solid #C6C6C6;
+
+  my-video-thumbnail {
+    margin-right: 10px;
+  }
+
+  .video-info {
+    flex-grow: 1;
+
+    .video-info-name {
+      font-size: 16px;
+      font-weight: $font-semibold;
+    }
+
+    .video-info-date-views {
+      font-size: 13px;
+    }
+  }
+
+  .edit-button {
+    @include peertube-button-link;
+
+    font-size: 15px;
+    font-weight: $font-semibold;
+    color: #585858;
+    background-color: #E5E5E5;
+
+    .icon.icon-edit {
+      display: inline-block;
+      background: url('../../../assets/images/account/edit.svg') no-repeat;
+      background-size: contain;
+      width: 21px;
+      height: 21px;
+      vertical-align: middle;
+      position: relative;
+      top: -2px;
+    }
+  }
+}
diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss
index 1baffa5c8..97c5d461a 100644
--- a/client/src/app/app.component.scss
+++ b/client/src/app/app.component.scss
@@ -42,7 +42,7 @@
       background-size: contain;
 
       &.icon-menu {
-        background-image: url('../assets/header/menu.svg');
+        background-image: url('../assets/images/header/menu.svg');
         margin: 0 18px 0 24px;
       }
     }
@@ -58,7 +58,7 @@
 
       .icon.icon-logo {
         display: inline-block;
-        background: url('../assets/logo.svg') no-repeat;
+        background: url('../assets/images/logo.svg') no-repeat;
         width: 20px;
         height: 24px;
       }
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss
index 5d6fd61c6..c93c59622 100644
--- a/client/src/app/menu/menu.component.scss
+++ b/client/src/app/menu/menu.component.scss
@@ -116,7 +116,7 @@ menu {
         &.icon-videos-trending {
           position: relative;
           top: -2px;
-          background-image: url('../../assets/menu/trending.svg');
+          background-image: url('../../assets/images/menu/trending.svg');
         }
 
         &.icon-videos-recently-added {
@@ -124,14 +124,14 @@ menu {
           height: 23px;
           position: relative;
           top: -1px;
-          background-image: url('../../assets/menu/recently-added.svg');
+          background-image: url('../../assets/images/menu/recently-added.svg');
         }
 
         &.icon-administration {
           width: 23px;
           height: 23px;
 
-          background-image: url('../../assets/menu/administration.svg');
+          background-image: url('../../assets/images/menu/administration.svg');
         }
       }
     }
diff --git a/client/src/app/shared/search/search.component.scss b/client/src/app/shared/search/search.component.scss
index 191d3d597..7ba8ef26c 100644
--- a/client/src/app/shared/search/search.component.scss
+++ b/client/src/app/shared/search/search.component.scss
@@ -10,7 +10,7 @@
 
 .icon.icon-search {
   display: inline-block;
-  background: url('../../../assets/header/search.svg') no-repeat;
+  background: url('../../../assets/images/header/search.svg') no-repeat;
   background-size: contain;
   width: 25px;
   height: 21px;
@@ -29,7 +29,7 @@
 
   .icon.icon-upload {
     display: inline-block;
-    background: url('../../../assets/header/upload.svg') no-repeat;
+    background: url('../../../assets/images/header/upload.svg') no-repeat;
     background-size: contain;
     width: 22px;
     height: 24px;
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts
index 83990d8b8..220362ef0 100644
--- a/client/src/app/shared/users/user.model.ts
+++ b/client/src/app/shared/users/user.model.ts
@@ -54,6 +54,6 @@ export class User implements UserServerModel {
   getAvatarPath () {
     if (this.account && this.account.avatar) return this.account.avatar.path
 
-    return '/assets/default-avatar.png'
+    return '/assets/images/default-avatar.png'
   }
 }
diff --git a/client/src/assets/default-avatar.png b/client/src/assets/default-avatar.png
deleted file mode 100644
index 4b7fd2c0a..000000000
Binary files a/client/src/assets/default-avatar.png and /dev/null differ
diff --git a/client/src/assets/favicon.png b/client/src/assets/favicon.png
deleted file mode 100644
index bb57ee6b0..000000000
Binary files a/client/src/assets/favicon.png and /dev/null differ
diff --git a/client/src/assets/header/menu.svg b/client/src/assets/header/menu.svg
deleted file mode 100644
index 7101bf73b..000000000
--- a/client/src/assets/header/menu.svg
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
-    <title>menu</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-180.000000, -203.000000)" stroke="#333333">
-            <g id="44" transform="translate(180.000000, 203.000000)">
-                <path d="M3.5,7 C3.5,6.72319836 3.72175357,6.5 3.99339768,6.5 L20.0066023,6.5 C20.2799786,6.5 20.5,6.72089465 20.5,7 C20.5,7.27680164 20.2782464,7.5 20.0066023,7.5 L3.99339768,7.5 C3.72002141,7.5 3.5,7.27910535 3.5,7 Z M3.5,12 C3.5,11.7231984 3.72175357,11.5 3.99339768,11.5 L20.0066023,11.5 C20.2799786,11.5 20.5,11.7208946 20.5,12 C20.5,12.2768016 20.2782464,12.5 20.0066023,12.5 L3.99339768,12.5 C3.72002141,12.5 3.5,12.2791054 3.5,12 Z M3.5,17 C3.5,16.7231984 3.72175357,16.5 3.99339768,16.5 L20.0066023,16.5 C20.2799786,16.5 20.5,16.7208946 20.5,17 C20.5,17.2768016 20.2782464,17.5 20.0066023,17.5 L3.99339768,17.5 C3.72002141,17.5 3.5,17.2791054 3.5,17 Z" id="Combined-Shape"></path>
-            </g>
-        </g>
-    </g>
-</svg>
\ No newline at end of file
diff --git a/client/src/assets/header/search.svg b/client/src/assets/header/search.svg
deleted file mode 100644
index 489b59e9b..000000000
--- a/client/src/assets/header/search.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-136.000000, -115.000000)" stroke="#000" stroke-width="2">
-            <g id="3" transform="translate(136.000000, 115.000000)">
-                <circle id="Oval-3" cx="10" cy="10" r="7"></circle>
-                <path d="M15,15 L21,21" id="Path-3" stroke-linecap="round" stroke-linejoin="round"></path>
-            </g>
-        </g>
-    </g>
-</svg>
diff --git a/client/src/assets/header/upload.svg b/client/src/assets/header/upload.svg
deleted file mode 100644
index 2b07caf76..000000000
--- a/client/src/assets/header/upload.svg
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
-    <title>cloud-upload</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
-        <g id="Artboard-4" transform="translate(-312.000000, -775.000000)" stroke="#fff" stroke-width="2">
-            <g id="307" transform="translate(312.000000, 775.000000)">
-                <path d="M8,18 L5,18 L5,18 C2.790861,18 1,16.209139 1,14 C1,11.790861 2.790861,10 5,10 C5.35840468,10 5.70579988,10.0471371 6.03632437,10.1355501 C6.01233106,9.92702603 6,9.71495305 6,9.5 C6,6.46243388 8.46243388,4 11.5,4 C14.0673313,4 16.2238156,5.7590449 16.8299648,8.1376465 C17.2052921,8.04765874 17.5970804,8 18,8 C20.7614237,8 23,10.2385763 23,13 C23,15.7614237 20.7614237,18 18,18 L16,18" id="Combined-Shape" stroke-linejoin="round"></path>
-                <path d="M12,13 L12,21" id="Path-58"></path>
-                <polyline id="Path-59" stroke-linejoin="round" transform="translate(12.000000, 12.500000) scale(1, -1) translate(-12.000000, -12.500000) " points="15 11 12 14 9 11"></polyline>
-            </g>
-        </g>
-    </g>
-</svg>
diff --git a/client/src/assets/images/account/edit.svg b/client/src/assets/images/account/edit.svg
new file mode 100644
index 000000000..23ece68f1
--- /dev/null
+++ b/client/src/assets/images/account/edit.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>edit</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Artboard-4" transform="translate(-48.000000, -203.000000)" stroke="#585858" stroke-width="2">
+            <g id="41" transform="translate(48.000000, 203.000000)">
+                <path d="M3,21.0000003 L3,17 L15.8898356,4.11016442 C17.0598483,2.9401517 18.9638992,2.94723715 20.1306896,4.11402752 L19.9181432,3.90148112 C21.0902894,5.07362738 21.0882407,6.97202708 19.9174652,8.1377941 L7,21.0000003 L3,21.0000003 Z" id="Path-74" stroke-linecap="round" stroke-linejoin="round"></path>
+                <path d="M14.5,5.5 L18.5,9.5" id="Path-75"></path>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/default-avatar.png b/client/src/assets/images/default-avatar.png
new file mode 100644
index 000000000..4b7fd2c0a
Binary files /dev/null and b/client/src/assets/images/default-avatar.png differ
diff --git a/client/src/assets/images/favicon.png b/client/src/assets/images/favicon.png
new file mode 100644
index 000000000..bb57ee6b0
Binary files /dev/null and b/client/src/assets/images/favicon.png differ
diff --git a/client/src/assets/images/header/menu.svg b/client/src/assets/images/header/menu.svg
new file mode 100644
index 000000000..7101bf73b
--- /dev/null
+++ b/client/src/assets/images/header/menu.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>menu</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Artboard-4" transform="translate(-180.000000, -203.000000)" stroke="#333333">
+            <g id="44" transform="translate(180.000000, 203.000000)">
+                <path d="M3.5,7 C3.5,6.72319836 3.72175357,6.5 3.99339768,6.5 L20.0066023,6.5 C20.2799786,6.5 20.5,6.72089465 20.5,7 C20.5,7.27680164 20.2782464,7.5 20.0066023,7.5 L3.99339768,7.5 C3.72002141,7.5 3.5,7.27910535 3.5,7 Z M3.5,12 C3.5,11.7231984 3.72175357,11.5 3.99339768,11.5 L20.0066023,11.5 C20.2799786,11.5 20.5,11.7208946 20.5,12 C20.5,12.2768016 20.2782464,12.5 20.0066023,12.5 L3.99339768,12.5 C3.72002141,12.5 3.5,12.2791054 3.5,12 Z M3.5,17 C3.5,16.7231984 3.72175357,16.5 3.99339768,16.5 L20.0066023,16.5 C20.2799786,16.5 20.5,16.7208946 20.5,17 C20.5,17.2768016 20.2782464,17.5 20.0066023,17.5 L3.99339768,17.5 C3.72002141,17.5 3.5,17.2791054 3.5,17 Z" id="Combined-Shape"></path>
+            </g>
+        </g>
+    </g>
+</svg>
\ No newline at end of file
diff --git a/client/src/assets/images/header/search.svg b/client/src/assets/images/header/search.svg
new file mode 100644
index 000000000..489b59e9b
--- /dev/null
+++ b/client/src/assets/images/header/search.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Artboard-4" transform="translate(-136.000000, -115.000000)" stroke="#000" stroke-width="2">
+            <g id="3" transform="translate(136.000000, 115.000000)">
+                <circle id="Oval-3" cx="10" cy="10" r="7"></circle>
+                <path d="M15,15 L21,21" id="Path-3" stroke-linecap="round" stroke-linejoin="round"></path>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/header/upload.svg b/client/src/assets/images/header/upload.svg
new file mode 100644
index 000000000..2b07caf76
--- /dev/null
+++ b/client/src/assets/images/header/upload.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>cloud-upload</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g id="Artboard-4" transform="translate(-312.000000, -775.000000)" stroke="#fff" stroke-width="2">
+            <g id="307" transform="translate(312.000000, 775.000000)">
+                <path d="M8,18 L5,18 L5,18 C2.790861,18 1,16.209139 1,14 C1,11.790861 2.790861,10 5,10 C5.35840468,10 5.70579988,10.0471371 6.03632437,10.1355501 C6.01233106,9.92702603 6,9.71495305 6,9.5 C6,6.46243388 8.46243388,4 11.5,4 C14.0673313,4 16.2238156,5.7590449 16.8299648,8.1376465 C17.2052921,8.04765874 17.5970804,8 18,8 C20.7614237,8 23,10.2385763 23,13 C23,15.7614237 20.7614237,18 18,18 L16,18" id="Combined-Shape" stroke-linejoin="round"></path>
+                <path d="M12,13 L12,21" id="Path-58"></path>
+                <polyline id="Path-59" stroke-linejoin="round" transform="translate(12.000000, 12.500000) scale(1, -1) translate(-12.000000, -12.500000) " points="15 11 12 14 9 11"></polyline>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/logo.svg b/client/src/assets/images/logo.svg
new file mode 100644
index 000000000..8777acd5b
--- /dev/null
+++ b/client/src/assets/images/logo.svg
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   viewBox="2799 -911 16 22"
+   version="1.1"
+   id="svg13"
+   sodipodi:docname="logo.svg"
+   width="16"
+   height="22"
+   inkscape:version="0.92.2 5c3e80d, 2017-08-06">
+  <metadata
+     id="metadata17">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1916"
+     inkscape:window-height="1040"
+     id="namedview15"
+     showgrid="false"
+     inkscape:zoom="29.790476"
+     inkscape:cx="-1.1827326"
+     inkscape:cy="12.088"
+     inkscape:window-x="0"
+     inkscape:window-y="18"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg13" />
+  <defs
+     id="defs4">
+    <style
+       id="style2">
+      .cls-3 {
+        fill: #211f20;
+      }
+
+      .cls-4 {
+        fill: #737373;
+      }
+
+      .cls-5 {
+        fill: #f1680d;
+      }
+
+      .cls-6 {
+        fill: #fff;
+      }
+    </style>
+  </defs>
+  <g
+     id="Artboard_1"
+     data-name="Artboard – 1"
+     class="cls-1"
+     transform="translate(0.03356777,-1.9929667)">
+    <g
+       id="Symbol_3_1"
+       data-name="Symbol 3 – 1"
+       transform="translate(2759,-975)">
+      <g
+         id="Group_44"
+         data-name="Group 44"
+         transform="translate(0,2.333)">
+        <path
+           id="Path_4"
+           data-name="Path 4"
+           class="cls-3"
+           d="m -949,-500 v 10.667 l 8,-5.333"
+           transform="translate(989,564)"
+           inkscape:connector-curvature="0"
+           style="fill:#211f20" />
+        <path
+           id="Path_5"
+           data-name="Path 5"
+           class="cls-4"
+           d="m -949,-500 v 10.667 l 8,-5.333"
+           transform="translate(989,574.667)"
+           inkscape:connector-curvature="0"
+           style="fill:#737373" />
+        <path
+           id="Path_6"
+           data-name="Path 6"
+           class="cls-5"
+           d="m -949,-500 v 10.667 l 8,-5.333"
+           transform="translate(997,569.333)"
+           inkscape:connector-curvature="0"
+           style="fill:#f1680d" />
+        <path
+           id="Path_7"
+           data-name="Path 7"
+           class="cls-6"
+           d="M 0,0 V 10.667 L 8,5.333 Z"
+           transform="rotate(180,24,40)"
+           inkscape:connector-curvature="0"
+           style="fill:#ffffff" />
+      </g>
+    </g>
+  </g>
+</svg>
diff --git a/client/src/assets/images/menu/administration.svg b/client/src/assets/images/menu/administration.svg
new file mode 100644
index 000000000..b6da837d2
--- /dev/null
+++ b/client/src/assets/images/menu/administration.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>filter</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Artboard-4" transform="translate(-444.000000, -247.000000)" fill="#808080">
+            <g id="70" transform="translate(444.000000, 247.000000)">
+                <path d="M8.82929429,17 L20.0066023,17 C20.5552407,17 21,17.4438648 21,18 C21,18.5522847 20.5550537,19 20.0066023,19 L8.82929429,19 C8.41745788,20.1651924 7.30621883,21 6,21 C4.34314575,21 3,19.6568542 3,18 C3,16.3431458 4.34314575,15 6,15 C7.30621883,15 8.41745788,15.8348076 8.82929429,17 Z M9.17070571,13 L3.99339768,13 C3.44475929,13 3,12.5561352 3,12 C3,11.4477153 3.44494629,11 3.99339768,11 L9.17070571,11 C9.58254212,9.83480763 10.6937812,9 12,9 C13.3062188,9 14.4174579,9.83480763 14.8292943,11 L20.0066023,11 C20.5552407,11 21,11.4438648 21,12 C21,12.5522847 20.5550537,13 20.0066023,13 L14.8292943,13 C14.4174579,14.1651924 13.3062188,15 12,15 C10.6937812,15 9.58254212,14.1651924 9.17070571,13 Z M15.1659641,6.98648118 C15.1124525,6.99537358 15.05751,7 15.0014977,7 L3.99850233,7 C3.44704472,7 3,6.55613518 3,6 C3,5.44771525 3.44748943,5 3.99850233,5 L15.0014977,5 C15.0575314,5 15.1124871,5.00458274 15.1660053,5.01340035 C15.5740343,3.84121344 16.6887792,3 18,3 C19.6568542,3 21,4.34314575 21,6 C21,7.65685425 19.6568542,9 18,9 C16.688735,9 15.5739592,8.15872988 15.1659641,6.98648118 Z M18,7 C18.5522847,7 19,6.55228475 19,6 C19,5.44771525 18.5522847,5 18,5 C17.4477153,5 17,5.44771525 17,6 C17,6.55228475 17.4477153,7 18,7 Z M12,13 C12.5522847,13 13,12.5522847 13,12 C13,11.4477153 12.5522847,11 12,11 C11.4477153,11 11,11.4477153 11,12 C11,12.5522847 11.4477153,13 12,13 Z M6,19 C6.55228475,19 7,18.5522847 7,18 C7,17.4477153 6.55228475,17 6,17 C5.44771525,17 5,17.4477153 5,18 C5,18.5522847 5.44771525,19 6,19 Z" id="Combined-Shape"></path>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/menu/recently-added.svg b/client/src/assets/images/menu/recently-added.svg
new file mode 100644
index 000000000..6473837f8
--- /dev/null
+++ b/client/src/assets/images/menu/recently-added.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Artboard-4" transform="translate(-92.000000, -115.000000)">
+            <g id="2" transform="translate(92.000000, 115.000000)">
+                <circle id="Oval-1" stroke="#808080" stroke-width="2" cx="12" cy="12" r="10"></circle>
+                <rect id="Rectangle-1" fill="#808080" x="11" y="7" width="2" height="10" rx="1"></rect>
+                <rect id="Rectangle-1" fill="#808080" x="7" y="11" width="10" height="2" rx="1"></rect>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/menu/trending.svg b/client/src/assets/images/menu/trending.svg
new file mode 100644
index 000000000..ffc65cc04
--- /dev/null
+++ b/client/src/assets/images/menu/trending.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>graph</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
+        <g id="Artboard-4" transform="translate(-444.000000, -203.000000)" stroke-width="2" stroke="#808080">
+            <g id="50" transform="translate(444.000000, 203.000000)">
+                <polyline id="Path-96" points="3 3 3 21.006249 21.0246733 21.006249"></polyline>
+                <polyline id="Path-101" points="6 18 11 12 14 13 19 7"></polyline>
+                <polygon id="Path-102" points="20 9 20 6 17 6"></polygon>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/logo.svg b/client/src/assets/logo.svg
deleted file mode 100644
index 8777acd5b..000000000
--- a/client/src/assets/logo.svg
+++ /dev/null
@@ -1,118 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg
-   xmlns:dc="http://purl.org/dc/elements/1.1/"
-   xmlns:cc="http://creativecommons.org/ns#"
-   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-   xmlns:svg="http://www.w3.org/2000/svg"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
-   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   viewBox="2799 -911 16 22"
-   version="1.1"
-   id="svg13"
-   sodipodi:docname="logo.svg"
-   width="16"
-   height="22"
-   inkscape:version="0.92.2 5c3e80d, 2017-08-06">
-  <metadata
-     id="metadata17">
-    <rdf:RDF>
-      <cc:Work
-         rdf:about="">
-        <dc:format>image/svg+xml</dc:format>
-        <dc:type
-           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
-        <dc:title></dc:title>
-      </cc:Work>
-    </rdf:RDF>
-  </metadata>
-  <sodipodi:namedview
-     pagecolor="#ffffff"
-     bordercolor="#666666"
-     borderopacity="1"
-     objecttolerance="10"
-     gridtolerance="10"
-     guidetolerance="10"
-     inkscape:pageopacity="0"
-     inkscape:pageshadow="2"
-     inkscape:window-width="1916"
-     inkscape:window-height="1040"
-     id="namedview15"
-     showgrid="false"
-     inkscape:zoom="29.790476"
-     inkscape:cx="-1.1827326"
-     inkscape:cy="12.088"
-     inkscape:window-x="0"
-     inkscape:window-y="18"
-     inkscape:window-maximized="0"
-     inkscape:current-layer="svg13" />
-  <defs
-     id="defs4">
-    <style
-       id="style2">
-      .cls-3 {
-        fill: #211f20;
-      }
-
-      .cls-4 {
-        fill: #737373;
-      }
-
-      .cls-5 {
-        fill: #f1680d;
-      }
-
-      .cls-6 {
-        fill: #fff;
-      }
-    </style>
-  </defs>
-  <g
-     id="Artboard_1"
-     data-name="Artboard – 1"
-     class="cls-1"
-     transform="translate(0.03356777,-1.9929667)">
-    <g
-       id="Symbol_3_1"
-       data-name="Symbol 3 – 1"
-       transform="translate(2759,-975)">
-      <g
-         id="Group_44"
-         data-name="Group 44"
-         transform="translate(0,2.333)">
-        <path
-           id="Path_4"
-           data-name="Path 4"
-           class="cls-3"
-           d="m -949,-500 v 10.667 l 8,-5.333"
-           transform="translate(989,564)"
-           inkscape:connector-curvature="0"
-           style="fill:#211f20" />
-        <path
-           id="Path_5"
-           data-name="Path 5"
-           class="cls-4"
-           d="m -949,-500 v 10.667 l 8,-5.333"
-           transform="translate(989,574.667)"
-           inkscape:connector-curvature="0"
-           style="fill:#737373" />
-        <path
-           id="Path_6"
-           data-name="Path 6"
-           class="cls-5"
-           d="m -949,-500 v 10.667 l 8,-5.333"
-           transform="translate(997,569.333)"
-           inkscape:connector-curvature="0"
-           style="fill:#f1680d" />
-        <path
-           id="Path_7"
-           data-name="Path 7"
-           class="cls-6"
-           d="M 0,0 V 10.667 L 8,5.333 Z"
-           transform="rotate(180,24,40)"
-           inkscape:connector-curvature="0"
-           style="fill:#ffffff" />
-      </g>
-    </g>
-  </g>
-</svg>
diff --git a/client/src/assets/menu/administration.svg b/client/src/assets/menu/administration.svg
deleted file mode 100644
index b6da837d2..000000000
--- a/client/src/assets/menu/administration.svg
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
-    <title>filter</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-444.000000, -247.000000)" fill="#808080">
-            <g id="70" transform="translate(444.000000, 247.000000)">
-                <path d="M8.82929429,17 L20.0066023,17 C20.5552407,17 21,17.4438648 21,18 C21,18.5522847 20.5550537,19 20.0066023,19 L8.82929429,19 C8.41745788,20.1651924 7.30621883,21 6,21 C4.34314575,21 3,19.6568542 3,18 C3,16.3431458 4.34314575,15 6,15 C7.30621883,15 8.41745788,15.8348076 8.82929429,17 Z M9.17070571,13 L3.99339768,13 C3.44475929,13 3,12.5561352 3,12 C3,11.4477153 3.44494629,11 3.99339768,11 L9.17070571,11 C9.58254212,9.83480763 10.6937812,9 12,9 C13.3062188,9 14.4174579,9.83480763 14.8292943,11 L20.0066023,11 C20.5552407,11 21,11.4438648 21,12 C21,12.5522847 20.5550537,13 20.0066023,13 L14.8292943,13 C14.4174579,14.1651924 13.3062188,15 12,15 C10.6937812,15 9.58254212,14.1651924 9.17070571,13 Z M15.1659641,6.98648118 C15.1124525,6.99537358 15.05751,7 15.0014977,7 L3.99850233,7 C3.44704472,7 3,6.55613518 3,6 C3,5.44771525 3.44748943,5 3.99850233,5 L15.0014977,5 C15.0575314,5 15.1124871,5.00458274 15.1660053,5.01340035 C15.5740343,3.84121344 16.6887792,3 18,3 C19.6568542,3 21,4.34314575 21,6 C21,7.65685425 19.6568542,9 18,9 C16.688735,9 15.5739592,8.15872988 15.1659641,6.98648118 Z M18,7 C18.5522847,7 19,6.55228475 19,6 C19,5.44771525 18.5522847,5 18,5 C17.4477153,5 17,5.44771525 17,6 C17,6.55228475 17.4477153,7 18,7 Z M12,13 C12.5522847,13 13,12.5522847 13,12 C13,11.4477153 12.5522847,11 12,11 C11.4477153,11 11,11.4477153 11,12 C11,12.5522847 11.4477153,13 12,13 Z M6,19 C6.55228475,19 7,18.5522847 7,18 C7,17.4477153 6.55228475,17 6,17 C5.44771525,17 5,17.4477153 5,18 C5,18.5522847 5.44771525,19 6,19 Z" id="Combined-Shape"></path>
-            </g>
-        </g>
-    </g>
-</svg>
diff --git a/client/src/assets/menu/recently-added.svg b/client/src/assets/menu/recently-added.svg
deleted file mode 100644
index 6473837f8..000000000
--- a/client/src/assets/menu/recently-added.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-92.000000, -115.000000)">
-            <g id="2" transform="translate(92.000000, 115.000000)">
-                <circle id="Oval-1" stroke="#808080" stroke-width="2" cx="12" cy="12" r="10"></circle>
-                <rect id="Rectangle-1" fill="#808080" x="11" y="7" width="2" height="10" rx="1"></rect>
-                <rect id="Rectangle-1" fill="#808080" x="7" y="11" width="10" height="2" rx="1"></rect>
-            </g>
-        </g>
-    </g>
-</svg>
diff --git a/client/src/assets/menu/trending.svg b/client/src/assets/menu/trending.svg
deleted file mode 100644
index ffc65cc04..000000000
--- a/client/src/assets/menu/trending.svg
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
-    <title>graph</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
-        <g id="Artboard-4" transform="translate(-444.000000, -203.000000)" stroke-width="2" stroke="#808080">
-            <g id="50" transform="translate(444.000000, 203.000000)">
-                <polyline id="Path-96" points="3 3 3 21.006249 21.0246733 21.006249"></polyline>
-                <polyline id="Path-101" points="6 18 11 12 14 13 19 7"></polyline>
-                <polygon id="Path-102" points="20 9 20 6 17 6"></polygon>
-            </g>
-        </g>
-    </g>
-</svg>
-- 
cgit v1.2.3


From be44767854709dbf7da4ba37fe4f16ac4e297f08 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Mon, 4 Dec 2017 11:17:08 +0100
Subject: Fix lint

---
 client/src/app/account/account-videos/account-videos.component.html  | 2 +-
 client/src/app/account/account-videos/account-videos.component.ts    | 5 ++---
 client/src/app/shared/misc/from-now.pipe.ts                          | 3 +--
 client/src/app/shared/misc/number-formatter.pipe.ts                  | 2 +-
 .../src/app/videos/video-list/shared/video-miniature.component.html  | 2 +-
 5 files changed, 6 insertions(+), 8 deletions(-)

(limited to 'client')

diff --git a/client/src/app/account/account-videos/account-videos.component.html b/client/src/app/account/account-videos/account-videos.component.html
index 94b976869..eb0a32fd3 100644
--- a/client/src/app/account/account-videos/account-videos.component.html
+++ b/client/src/app/account/account-videos/account-videos.component.html
@@ -8,7 +8,7 @@
 
     <div class="video-info">
       <div class="video-info-name">{{ video.name }}</div>
-      <span class="video-info-date-views">{{ video.createdAt | fromNow }} - {{ video.views | numberFormatter }} views</span>
+      <span class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
     </div>
 
     <a class="edit-button" [routerLink]="[ '/videos', video.id, '/edit' ]">
diff --git a/client/src/app/account/account-videos/account-videos.component.ts b/client/src/app/account/account-videos/account-videos.component.ts
index ff945825d..cc28f511a 100644
--- a/client/src/app/account/account-videos/account-videos.component.ts
+++ b/client/src/app/account/account-videos/account-videos.component.ts
@@ -1,8 +1,7 @@
 import { Component, OnDestroy, OnInit } from '@angular/core'
-import { AbstractVideoList } from '../../shared/video/abstract-video-list'
-import { ActivatedRoute } from '@angular/router'
-import { Router } from '@angular/router'
+import { ActivatedRoute, Router } from '@angular/router'
 import { NotificationsService } from 'angular2-notifications'
+import { AbstractVideoList } from '../../shared/video/abstract-video-list'
 import { VideoService } from '../../shared/video/video.service'
 
 @Component({
diff --git a/client/src/app/shared/misc/from-now.pipe.ts b/client/src/app/shared/misc/from-now.pipe.ts
index 80dab02ba..fac02af0b 100644
--- a/client/src/app/shared/misc/from-now.pipe.ts
+++ b/client/src/app/shared/misc/from-now.pipe.ts
@@ -1,8 +1,7 @@
 import { Pipe, PipeTransform } from '@angular/core'
 
 // Thanks: https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site
-
-@Pipe({name: 'fromNow'})
+@Pipe({ name: 'myFromNow' })
 export class FromNowPipe implements PipeTransform {
 
   transform (value: number) {
diff --git a/client/src/app/shared/misc/number-formatter.pipe.ts b/client/src/app/shared/misc/number-formatter.pipe.ts
index 2491fb1d6..8a0756a36 100644
--- a/client/src/app/shared/misc/number-formatter.pipe.ts
+++ b/client/src/app/shared/misc/number-formatter.pipe.ts
@@ -2,7 +2,7 @@ import { Pipe, PipeTransform } from '@angular/core'
 
 // Thanks: https://github.com/danrevah/ngx-pipes/blob/master/src/pipes/math/bytes.ts
 
-@Pipe({name: 'numberFormatter'})
+@Pipe({ name: 'myNumberFormatter' })
 export class NumberFormatterPipe implements PipeTransform {
   private dictionary: Array<{max: number, type: string}> = [
     { max: 1000, type: '' },
diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.html b/client/src/app/videos/video-list/shared/video-miniature.component.html
index f2756ca3d..7ac017235 100644
--- a/client/src/app/videos/video-list/shared/video-miniature.component.html
+++ b/client/src/app/videos/video-list/shared/video-miniature.component.html
@@ -11,7 +11,7 @@
       </a>
     </span>
 
-    <span class="video-miniature-created-at-views">{{ video.createdAt | fromNow }} - {{ video.views | numberFormatter }} views</span>
+    <span class="video-miniature-created-at-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
     <span class="video-miniature-account">{{ video.by }}</span>
   </div>
 </div>
-- 
cgit v1.2.3


From 62e23e40dad8fbd6148a9201fba2e73ced3f1877 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Mon, 4 Dec 2017 11:40:45 +0100
Subject: Fix client build

---
 client/src/app/shared/shared.module.ts | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

(limited to 'client')

diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index e76f7636a..f7ced040d 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -7,7 +7,8 @@ import { RouterModule } from '@angular/router'
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown'
 import { ModalModule } from 'ngx-bootstrap/modal'
 import { ProgressbarModule } from 'ngx-bootstrap/progressbar'
-import { BytesPipe, KeysPipe } from 'ngx-pipes'
+import { InfiniteScrollModule } from 'ngx-infinite-scroll'
+import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes'
 import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared'
 import { DataTableModule } from 'primeng/components/datatable/datatable'
 
@@ -22,7 +23,6 @@ import { VideoAbuseService } from './video-abuse'
 import { VideoBlacklistService } from './video-blacklist'
 import { VideoThumbnailComponent } from './video/video-thumbnail.component'
 import { VideoService } from './video/video.service'
-import { InfiniteScrollModule } from 'ngx-infinite-scroll'
 
 @NgModule({
   imports: [
@@ -38,12 +38,11 @@ import { InfiniteScrollModule } from 'ngx-infinite-scroll'
 
     DataTableModule,
     PrimeSharedModule,
-    InfiniteScrollModule
+    InfiniteScrollModule,
+    NgPipesModule
   ],
 
   declarations: [
-    BytesPipe,
-    KeysPipe,
     SearchComponent,
     LoaderComponent,
     VideoThumbnailComponent,
-- 
cgit v1.2.3


From be6a4802326b1748e85c0d6fdadf06e70e6ecbb0 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Tue, 5 Dec 2017 10:44:11 +0100
Subject: Design video player

---
 .../videos/+video-watch/video-watch.component.html |  11 +-
 .../videos/+video-watch/video-watch.component.scss |  18 +-
 .../videos/+video-watch/video-watch.component.ts   |  11 +-
 client/src/assets/player/images/arrow-down.svg     |  14 +
 client/src/assets/player/images/arrow-up.svg       |  14 +
 client/src/assets/player/images/fullscreen.svg     |  18 +
 client/src/assets/player/images/volume-mute.svg    |  16 +
 client/src/assets/player/images/volume.svg         |  13 +
 .../src/assets/player/peertube-videojs-plugin.ts   |  98 +++-
 client/src/sass/video-js-custom.scss               | 559 ++++++++++-----------
 10 files changed, 445 insertions(+), 327 deletions(-)
 create mode 100644 client/src/assets/player/images/arrow-down.svg
 create mode 100644 client/src/assets/player/images/arrow-up.svg
 create mode 100644 client/src/assets/player/images/fullscreen.svg
 create mode 100644 client/src/assets/player/images/volume-mute.svg
 create mode 100644 client/src/assets/player/images/volume.svg

(limited to 'client')

diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html
index f528d73c3..aa1f2f77e 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -15,20 +15,13 @@
 
 <div class="row">
   <!-- We need the video container for videojs so we just hide it -->
-  <div [hidden]="videoNotFound" class="embed-responsive embed-responsive-19by9">
-     <video id="video-container" class="video-js vjs-sublime-skin"></video>
+  <div [hidden]="videoNotFound" id="video-container">
+     <video id="video-element" class="video-js vjs-peertube-skin"></video>
   </div>
 
   <div *ngIf="videoNotFound" id="video-not-found">Video not found :'(</div>
 </div>
 
-<!-- P2P information -->
-<div id="torrent-info" class="row">
-  <div id="torrent-info-download" class="col-md-4 col-sm-4 col-xs-4">Download: {{ downloadSpeed | bytes }}/s</div>
-  <div id="torrent-info-upload" class="col-md-4 col-sm-4 col-xs-4">Upload: {{ uploadSpeed | bytes }}/s</div>
-  <div id="torrent-info-peers" class="col-md-4 col-sm-4 col-xs-4">Number of peers: {{ numPeers }}</div>
-</div>
-
 <!-- Video information -->
 <div *ngIf="video !== null" id="video-info">
   <div class="row video-name-views">
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss
index cad21dd18..06c2de7c6 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.scss
+++ b/client/src/app/videos/+video-watch/video-watch.component.scss
@@ -1,6 +1,12 @@
 #video-container {
-  width: 100%;
-  height: 100%;
+  background-color: #000;
+  display: flex;
+  justify-content: center;
+
+  #video-element {
+    width: 888px;
+    height: 500px;
+  }
 }
 
 #video-not-found {
@@ -11,14 +17,6 @@
   font-weight: bold;
 }
 
-.embed-responsive {
-  height: 500px;
-
-  @media screen and (max-width: 600px) {
-    height: 300px;
-  }
-}
-
 #torrent-info {
   font-size: 10px;
   margin-top: 10px;
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts
index eac676be8..48842602e 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -26,13 +26,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
   @ViewChild('videoShareModal') videoShareModal: VideoShareComponent
   @ViewChild('videoReportModal') videoReportModal: VideoReportComponent
 
-  downloadSpeed: number
   error = false
   loading = false
-  numPeers: number
   player: videojs.Player
   playerElement: HTMLMediaElement
-  uploadSpeed: number
   userRating: UserVideoRateType = null
   video: VideoDetails = null
   videoPlayerLoaded = false
@@ -283,7 +280,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
           return this.router.navigate([ '/videos/list' ])
         }
 
-        this.playerElement = this.elementRef.nativeElement.querySelector('#video-container')
+        this.playerElement = this.elementRef.nativeElement.querySelector('#video-element')
 
         const videojsOptions = {
           controls: true,
@@ -306,12 +303,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
           this.on('customError', (event, data) => {
             self.handleError(data.err)
           })
-
-          this.on('torrentInfo', (event, data) => {
-            self.downloadSpeed = data.downloadSpeed
-            self.numPeers = data.numPeers
-            self.uploadSpeed = data.uploadSpeed
-          })
         })
 
         this.setVideoDescriptionHTML()
diff --git a/client/src/assets/player/images/arrow-down.svg b/client/src/assets/player/images/arrow-down.svg
new file mode 100644
index 000000000..3377cdab2
--- /dev/null
+++ b/client/src/assets/player/images/arrow-down.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g id="Artboard-4" transform="translate(-532.000000, -1046.000000)" stroke="#fff" stroke-width="2">
+            <g id="Extras" transform="translate(48.000000, 1046.000000)">
+                <g id="down" transform="translate(484.000000, 0.000000)">
+                    <path d="M12,3 L12,20" id="Path-58"></path>
+                    <polyline id="Path-59" stroke-linejoin="round" transform="translate(12.000000, 17.000000) scale(-1, -1) translate(-12.000000, -17.000000) " points="4 21 12 13 20 21"></polyline>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/player/images/arrow-up.svg b/client/src/assets/player/images/arrow-up.svg
new file mode 100644
index 000000000..b1a7890a8
--- /dev/null
+++ b/client/src/assets/player/images/arrow-up.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g id="Artboard-4" transform="translate(-488.000000, -1046.000000)" stroke="#fff" stroke-width="2">
+            <g id="Extras" transform="translate(48.000000, 1046.000000)">
+                <g id="up" transform="translate(440.000000, 0.000000)">
+                    <path d="M12,4 L12,21" id="Path-58"></path>
+                    <polyline id="Path-59" stroke-linejoin="round" transform="translate(12.000000, 7.000000) scale(-1, 1) translate(-12.000000, -7.000000) " points="4 11 12 3 20 11"></polyline>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/player/images/fullscreen.svg b/client/src/assets/player/images/fullscreen.svg
new file mode 100644
index 000000000..44e0041a4
--- /dev/null
+++ b/client/src/assets/player/images/fullscreen.svg
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>fullscreen</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Artboard-4" transform="translate(-576.000000, -159.000000)" stroke="#fff" stroke-width="2">
+            <g id="33" transform="translate(576.000000, 159.000000)">
+                <rect id="Rectangle-433" x="1" y="4" width="22" height="16" rx="1"></rect>
+                <polyline id="Path-42" stroke-linecap="round" stroke-linejoin="round" points="20 10 20 7 17 7"></polyline>
+                <polyline id="Path-42" stroke-linecap="round" stroke-linejoin="round" points="7 17 4 17 4 14"></polyline>
+                <polyline id="Path-42" stroke-linecap="round" stroke-linejoin="round" transform="translate(18.500000, 15.500000) scale(1, -1) translate(-18.500000, -15.500000) " points="20 17 20 14 17 14"></polyline>
+                <polyline id="Path-42" stroke-linecap="round" stroke-linejoin="round" transform="translate(5.500000, 8.500000) scale(1, -1) translate(-5.500000, -8.500000) " points="7 10 4 10 4 7"></polyline>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/player/images/volume-mute.svg b/client/src/assets/player/images/volume-mute.svg
new file mode 100644
index 000000000..0c7c296bc
--- /dev/null
+++ b/client/src/assets/player/images/volume-mute.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>volume-mute</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g id="Artboard-4" transform="translate(-92.000000, -159.000000)" stroke="#fff" stroke-width="2">
+            <g id="22" transform="translate(92.000000, 159.000000)">
+                <path d="M2,8.99703014 C2,8.4463856 2.44335318,8 3.0093689,8 L6,8 L12,4 L12,20 L6,16 L3.0093689,16 C2.45190985,16 2,15.5469637 2,15.0029699 L2,8.99703014 Z" id="Rectangle-415" stroke-linejoin="round"></path>
+                <path d="M16,15 L22,9" id="Path-28"></path>
+                <path d="M16.0000002,15 L22.0249378,9" id="Path-28" transform="translate(19.012469, 12.000000) scale(-1, 1) translate(-19.012469, -12.000000) "></path>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/player/images/volume.svg b/client/src/assets/player/images/volume.svg
new file mode 100644
index 000000000..590913add
--- /dev/null
+++ b/client/src/assets/player/images/volume.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g id="Artboard-4" transform="translate(-884.000000, -115.000000)" stroke="#fff" stroke-width="2">
+            <g id="20" transform="translate(884.000000, 115.000000)">
+                <path d="M2,8.99703014 C2,8.4463856 2.44335318,8 3.0093689,8 L6,8 L12,4 L12,20 L6,16 L3.0093689,16 C2.45190985,16 2,15.5469637 2,15.0029699 L2,8.99703014 Z" id="Rectangle-415" stroke-linejoin="round"></path>
+                <path d="M16,8 C16,8 18,9.5 18,12 C18,14.5 16,16 16,16" id="Path-26"></path>
+                <path d="M16.0734116,20 C19.3093571,18.9698098 22.0000001,15.5773201 22.0000001,12 C22.0000001,8.43619491 19.2903975,5.04132966 16.0734116,4" id="Oval-33"></path>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/player/peertube-videojs-plugin.ts b/client/src/assets/player/peertube-videojs-plugin.ts
index c54d8b5ea..977455bff 100644
--- a/client/src/assets/player/peertube-videojs-plugin.ts
+++ b/client/src/assets/player/peertube-videojs-plugin.ts
@@ -2,9 +2,24 @@
 
 import videojs, { Player } from 'video.js'
 import * as WebTorrent from 'webtorrent'
+import { VideoFile } from '../../../../shared'
 
 import { renderVideo } from './video-renderer'
-import { VideoFile } from '../../../../shared'
+
+// https://github.com/danrevah/ngx-pipes/blob/master/src/pipes/math/bytes.ts
+// Don't import all Angular stuff, just copy the code with shame
+const dictionaryBytes: Array<{max: number, type: string}> = [
+  { max: 1024, type: 'B' },
+  { max: 1048576, type: 'KB' },
+  { max: 1073741824, type: 'MB' },
+  { max: 1.0995116e12, type: 'GB' }
+]
+function bytes (value) {
+  const format = dictionaryBytes.find(d => value < d.max) || dictionaryBytes[dictionaryBytes.length - 1]
+  const calc = Math.floor(value / (format.max / 1024)).toString()
+
+  return [ calc, format.type ]
+}
 
 // videojs typings don't have some method we need
 const videojsUntyped = videojs as any
@@ -62,6 +77,7 @@ const ResolutionMenuButton = videojsUntyped.extend(MenuButton, {
 
   update: function () {
     this.label.innerHTML = this.player_.getCurrentResolutionLabel()
+    this.hide()
     return MenuButton.prototype.update.call(this)
   },
 
@@ -74,8 +90,7 @@ MenuButton.registerComponent('ResolutionMenuButton', ResolutionMenuButton)
 const Button = videojsUntyped.getComponent('Button')
 const PeertubeLinkButton = videojsUntyped.extend(Button, {
   constructor: function (player) {
-    Button.apply(this, arguments)
-    this.player = player
+    Button.call(this, player)
   },
 
   createEl: function () {
@@ -90,11 +105,78 @@ const PeertubeLinkButton = videojsUntyped.extend(Button, {
   },
 
   handleClick: function () {
-    this.player.pause()
+    this.player_.pause()
   }
 })
 Button.registerComponent('PeerTubeLinkButton', PeertubeLinkButton)
 
+const WebTorrentButton = videojsUntyped.extend(Button, {
+  constructor: function (player) {
+    Button.call(this, player)
+  },
+
+  createEl: function () {
+    const div = document.createElement('div')
+
+    const downloadIcon = document.createElement('span')
+    downloadIcon.classList.add('icon', 'icon-download')
+    div.appendChild(downloadIcon)
+
+    const downloadSpeedText = document.createElement('span')
+    downloadSpeedText.classList.add('download-speed-text')
+    const downloadSpeedNumber = document.createElement('span')
+    downloadSpeedNumber.classList.add('download-speed-number')
+    const downloadSpeedUnit = document.createElement('span')
+    downloadSpeedText.appendChild(downloadSpeedNumber)
+    downloadSpeedText.appendChild(downloadSpeedUnit)
+    div.appendChild(downloadSpeedText)
+
+    const uploadIcon = document.createElement('span')
+    uploadIcon.classList.add('icon', 'icon-upload')
+    div.appendChild(uploadIcon)
+
+    const uploadSpeedText = document.createElement('span')
+    uploadSpeedText.classList.add('upload-speed-text')
+    const uploadSpeedNumber = document.createElement('span')
+    uploadSpeedNumber.classList.add('upload-speed-number')
+    const uploadSpeedUnit = document.createElement('span')
+    uploadSpeedText.appendChild(uploadSpeedNumber)
+    uploadSpeedText.appendChild(uploadSpeedUnit)
+    div.appendChild(uploadSpeedText)
+
+    const peersText = document.createElement('span')
+    peersText.textContent = ' peers'
+    peersText.classList.add('peers-text')
+    const peersNumber = document.createElement('span')
+    peersNumber.classList.add('peers-number')
+    div.appendChild(peersNumber)
+    div.appendChild(peersText)
+
+    div.className = 'vjs-webtorrent'
+    // Hide the stats before we get the info
+    div.style.display = 'none'
+
+    this.player_.on('torrentInfo', (event, data) => {
+      const downloadSpeed = bytes(data.downloadSpeed)
+      const uploadSpeed = bytes(data.uploadSpeed)
+      const numPeers = data.numPeers
+
+      downloadSpeedNumber.textContent = downloadSpeed[0]
+      downloadSpeedUnit.textContent = ' ' + downloadSpeed[1]
+
+      uploadSpeedNumber.textContent = uploadSpeed[0]
+      uploadSpeedUnit.textContent = ' ' + uploadSpeed[1]
+
+      peersNumber.textContent = numPeers
+
+      div.style.display = 'block'
+    })
+
+    return div
+  }
+})
+Button.registerComponent('WebTorrentButton', WebTorrentButton)
+
 type PeertubePluginOptions = {
   videoFiles: VideoFile[]
   playerElement: HTMLVideoElement
@@ -223,6 +305,12 @@ const peertubePlugin = function (options: PeertubePluginOptions) {
       }
     }
 
+    const webTorrentButton = new WebTorrentButton(player)
+    controlBar.webTorrent = controlBar.el().insertBefore(webTorrentButton.el(), controlBar.progressControl.el())
+    controlBar.webTorrent.dispose = function () {
+      this.parentNode.removeChild(this)
+    }
+
     if (options.autoplay === true) {
       player.updateVideoFile()
     } else {
@@ -245,7 +333,7 @@ const peertubePlugin = function (options: PeertubePluginOptions) {
     }, 1000)
   })
 
-  function handleError (err: Error|string) {
+  function handleError (err: Error | string) {
     return player.trigger('customError', { err })
   }
 }
diff --git a/client/src/sass/video-js-custom.scss b/client/src/sass/video-js-custom.scss
index 34a958764..1200c07a5 100644
--- a/client/src/sass/video-js-custom.scss
+++ b/client/src/sass/video-js-custom.scss
@@ -1,341 +1,314 @@
-// Thanks: https://github.com/kmoskwiak/videojs-resolution-switcher/pull/92/files
-.vjs-resolution-button-label {
-  font-size: 1em;
-  line-height: 3em;
-  position: absolute;
-  top: 0;
-  left: -1px;
-  width: 100%;
-  height: 100%;
-  text-align: center;
-  box-sizing: inherit;
-}
-
-.vjs-resolution-button {
-  outline: 0 !important;
+// Thanks: https://github.com/zanechua/videojs-sublime-inspired-skin
+$primary-foreground-color: #fff;
+$primary-background-color: #000;
+$font-size: 13px;
+$control-bar-height: 34px;
 
-  .vjs-menu {
-    .vjs-menu-content {
-      width: 4em;
-      left: 50%; /* Center the menu, in it's parent */
-      margin-left: -2em; /* half of width, to center */
-    }
+.video-js.vjs-peertube-skin {
+  font-size: $font-size;
+  color: $primary-foreground-color;
 
-    li {
-      text-transform: none;
-      font-size: 1em;
-    }
+  .vjs-button > .vjs-icon-placeholder::before {
+    line-height: $control-bar-height;
   }
-}
-
-// Thanks: https://github.com/zanechua/videojs-sublime-inspired-skin
 
-// Video JS Sublime Skin
-// The following are SCSS variables to automate some of the values.
-// But don't feel limited by them. Change/replace whatever you want.
+  .vjs-mouse-display:before,
+  .vjs-play-progress:before,
+  .vjs-volume-level:before {
+    content: ''; /* Remove Circle From Progress Bar */
+  }
 
-// The color of icons, text, and the big play button border.
-// Try changing to #0f0
-$primary-foreground-color: #fff; // #fff default
+  .vjs-audio-button {
+    display: none;
+  }
 
-// The default color of control backgrounds is mostly black but with a little
-// bit of blue so it can still be seen on all-black video frames, which are common.
-// Try changing to #900
-$primary-background-color: #2B333F;  // #2B333F default
+  .vjs-big-play-button {
+    font-size: 8em;
 
-// Try changing to true
-$center-big-play-button: true; // true default
+    $big-play-width: 3em;
+    $big-play-height: 1.5em;
 
-.video-js {
-  /* The base font size controls the size of everything, not just text.
-     All dimensions use em-based sizes so that the scale along with the font size.
-     Try increasing it to 15px and see what happens. */
-  font-size: 10px;
+    line-height: $big-play-height;
+    height: $big-play-height;
+    width: $big-play-width;
 
-  /* The main font color changes the ICON COLORS as well as the text */
-  color: $primary-foreground-color;
-}
+    border: 0;
+    border-radius: 0.3em;
 
-/* The "Big Play Button" is the play button that shows before the video plays.
-   To center it set the align values to center and middle. The typical location
-   of the button is the center, but there is trend towards moving it to a corner
-   where it gets out of the way of valuable content in the poster image.*/
-.vjs-sublime-skin .vjs-big-play-button {
-  /* The font size is what makes the big play button...big.
-     All width/height values use ems, which are a multiple of the font size.
-     If the .video-js font-size is 10px, then 3em equals 30px.*/
-  font-size: 8em;
-
-  /* We're using SCSS vars here because the values are used in multiple places.
-     Now that font size is set, the following em values will be a multiple of the
-     new font size. If the font-size is 3em (30px), then setting any of
-     the following values to 3em would equal 30px. 3 * font-size. */
-  $big-play-width: 3em;
-  /* 1.5em = 45px default */
-  $big-play-height: 1.5em;
-
-  line-height: $big-play-height;
-  height: $big-play-height;
-  width: $big-play-width;
-
-  /* 0.06666em = 2px default */
-  border: 0;
-  /* 0.3em = 9px default */
-  border-radius: 0.3em;
-
-  @if $center-big-play-button {
-    /* Align center */
     left: 50%;
     top: 50%;
     margin-left: -($big-play-width / 2);
     margin-top: -($big-play-height / 2);
-  } @else {
-    /* Align top left. 0.5em = 15px default */
-    left: 0.5em;
-    top: 0.5em;
   }
-}
-
-/* The default color of control backgrounds is mostly black but with a little
-   bit of blue so it can still be seen on all-black video frames, which are common. */
-.video-js .vjs-control-bar,
-.video-js .vjs-big-play-button,
-.video-js .vjs-menu-button .vjs-menu-content {
-  /* IE8 - has no alpha support */
-  background-color: $primary-background-color;
-  /* Opacity: 1.0 = 100%, 0.0 = 0% */
-  background-color: rgba($primary-background-color, 0.7);
-  background-color: transparent;
-}
-
-// Make a slightly lighter version of the main background
-// for the slider background.
-$slider-bg-color: lighten($primary-background-color, 33%);
-
-/* Slider - used for Volume bar and Progress bar */
-.video-js .vjs-slider {
-  background-color: $slider-bg-color;
-  background-color: rgba($slider-bg-color, 0.5);
-  background-color: rgba(255,255,255,.3);
-  border-radius: 2px;
-  height: 6.5px;
-}
-
-/* The slider bar color is used for the progress bar and the volume bar
-   (the first two can be removed after a fix that's coming) */
-.video-js .vjs-volume-level,
-.video-js .vjs-play-progress,
-.video-js .vjs-slider-bar {
-  background: $primary-foreground-color;
-}
-
-/* Enlarged Slider to enable easier tracking. Adjust all the height:6.5px to preferred height for the slider if necessary. */
-.video-js .vjs-progress-holder .vjs-load-progress,
-.video-js .vjs-progress-holder .vjs-load-progress div,
-.video-js .vjs-progress-holder .vjs-play-progress,
-.video-js .vjs-progress-holder .vjs-tooltip-progress-bar {
-  height: 6.5px;
-}
-
-/* The main progress bar also has a bar that shows how much has been loaded. */
-.video-js .vjs-load-progress {
-  /* For IE8 we'll lighten the color */
-  background: ligthen($slider-bg-color, 25%);
-  /* Otherwise we'll rely on stacked opacities */
-  background: rgba($slider-bg-color, 0.5);
-}
-
-/* The load progress bar also has internal divs that represent
-   smaller disconnected loaded time ranges */
-.video-js .vjs-load-progress div {
-  /* For IE8 we'll lighten the color */
-  background: ligthen($slider-bg-color, 50%);
-  /* Otherwise we'll rely on stacked opacities */
-  background: rgba($slider-bg-color, 0.75);
-}
-
-//Skin Style Starts
-.vjs-sublime-skin .vjs-poster {
-    outline: none; /* Remove Blue Outline on Click*/
-    outline: 0;
-}
 
-.vjs-sublime-skin:hover .vjs-big-play-button {
+  &:hover .vjs-big-play-button {
     background-color: transparent;
-}
-
-.vjs-sublime-skin .vjs-fullscreen-control:before,
-.vjs-sublime-skin.vjs-fullscreen .vjs-fullscreen-control:before {
-    content: ''; /* Remove Fullscreen Exit Icon */
-}
-
-.vjs-sublime-skin.vjs-fullscreen .vjs-fullscreen-control {
-    background: #fff;
-}
-
-.vjs-sublime-skin .vjs-fullscreen-control {
-    border: 3px solid #fff;
-    box-sizing: border-box;
-    cursor: pointer;
-    margin-top: -7px;
-    top: 50%;
-    height: 14px;
-    width: 22px;
-    margin-right: 10px;
-}
+  }
 
-.vjs-sublime-skin.vjs-fullscreen .vjs-fullscreen-control:after {
-    background: #000;
-    content: "";
-    display: block;
-    position: absolute;
-    bottom: 0;
-    left: 0;
-    height: 5px;
-    width: 5px;
-}
+  .vjs-control-bar,
+  .vjs-big-play-button,
+  .vjs-menu-button .vjs-menu-content {
+    background-color: rgba($primary-background-color, 0.5);
+  }
 
-.vjs-sublime-skin .vjs-progress-holder {
-    margin: 0;
-}
+  $slider-bg-color: lighten($primary-background-color, 33%);
 
-.vjs-sublime-skin .vjs-progress-control .vjs-progress-holder:after {
+  .vjs-slider {
+    background-color: rgba(255, 255, 255, .3);
     border-radius: 2px;
-    display: block;
-    height: 6.5px;
-}
-
-.vjs-sublime-skin .vjs-progress-control .vjs-load-progres,
-.vjs-sublime-skin .vjs-progress-control .vjs-play-progress {
-    border-radius: 2px;
-    height: 6.5px;
-}
+    height: 5px;
+  }
 
-.vjs-sublime-skin .vjs-playback-rate {
-    display: none; /* Remove Playback Rate */
-}
+  /* The slider bar color is used for the progress bar and the volume bar
+     (the first two can be removed after a fix that's coming) */
+  .vjs-volume-level,
+  .vjs-play-progress,
+  .vjs-slider-bar {
+    background: $primary-foreground-color;
+  }
 
-.vjs-sublime-skin .vjs-progress-control {
-    margin-right: 50px;
-}
+  .vjs-load-progress {
+    background: rgba($slider-bg-color, 0.5);
+  }
 
-.vjs-sublime-skin .vjs-time-control {
-    right: 55px;
-}
+  .vjs-load-progress div {
+    background: rgba($slider-bg-color, 0.75);
+  }
 
-.vjs-sublime-skin .vjs-volume-menu-button:before {
-    width: 1.2em;
-    z-index: 1;
-}
+  .vjs-poster {
+    outline: none; /* Remove Blue Outline on Click*/
+    outline: 0;
+  }
 
-.vjs-sublime-skin .vjs-volume-menu-button .vjs-menu,
-.vjs-sublime-skin .vjs-volume-menu-button:focus .vjs-menu,
-.vjs-sublime-skin .vjs-volume-menu-button.vjs-slider-active .vjs-menu {
-    display: block;
-    opacity: 1;
-}
+  .vjs-control-bar {
+    height: $control-bar-height;
 
-.vjs-sublime-skin .vjs-volume-menu-button,
-.vjs-sublime-skin .vjs-volume-panel {
-    width: 6em;
-    position: absolute;
-    right: 0;
-    margin-right: 65px;
-}
+    .vjs-progress-control {
+      bottom: 34px;
+      width: 100%;
+      position: absolute;
+      height: 5px;
 
-.vjs-sublime-skin .vjs-volume-menu-button .vjs-menu-content,
-.vjs-sublime-skin .vjs-volume-menu-button:hover,
-.vjs-sublime-skin .vjs-volume-menu-button:focus,
-.vjs-sublime-skin .vjs-volume-menu-button.vjs-slider-active,
-.vjs-sublime-skin .vjs-volume-panel .vjs-volume-control,
-.vjs-sublime-skin .vjs-volume-panel:hover,
-.vjs-sublime-skin .vjs-volume-panel:focus,
-.vjs-sublime-skin .vjs-volume-panel.vjs-slider-active {
-    width: 6em;
-}
+      .vjs-progress-holder {
+        margin: 0;
+        border-radius: 0;
+      }
+    }
 
-.vjs-sublime-skin .vjs-volume-menu-button .vjs-menu {
-    left: 23px;
-}
+    .vjs-play-control {
+      font-size: $font-size;
+      padding: 0 17px;
+      margin-right: 5px;
+    }
 
-.vjs-sublime-skin .vjs-mouse-display:before,
-.vjs-sublime-skin .vjs-play-progress:before,
-.vjs-sublime-skin .vjs-volume-level:before {
-    content: ''; /* Remove Circle From Progress Bar */
-}
+    .vjs-time-control {
+      &.vjs-current-time {
+        font-size: $font-size;
+        display: inline-block;
+        padding: 0;
+
+        .vjs-current-time-display {
+          line-height: $control-bar-height;
+
+          &::after {
+            content: "/";
+            margin: 0 1px 0 2px;
+          }
+        }
+      }
+
+      &.vjs-duration {
+        font-size: $font-size;
+        display: inline-block;
+        padding: 0;
+
+        .vjs-duration-display {
+          line-height: $control-bar-height;
+        }
+      }
+
+      &.vjs-remaining-time {
+        display: none;
+      }
+    }
 
-.vjs-sublime-skin .vjs-mouse-display:after,
-.vjs-sublime-skin .vjs-play-progress:after,
-.vjs-sublime-skin .vjs-time-tooltip {
-    width: 5.5em;
-}
+    .vjs-webtorrent {
+      width: 100%;
+      line-height: $control-bar-height;
+      text-align: right;
+      padding-right: 60px;
+
+      .download-speed-number, .upload-speed-number, .peers-number {
+        font-weight: $font-semibold;
+      }
+
+      .download-speed-text, .upload-speed-text, .peers-text {
+        margin-right: 15px;
+      }
+
+      .icon {
+        display: inline-block;
+        width: 15px;
+        height: 15px;
+        background-size: contain;
+        vertical-align: middle;
+        background-repeat: no-repeat;
+        margin-right: 6px;
+        position: relative;
+        top: -1px;
+
+        &.icon-download {
+          background-image: url('../assets/player/images/arrow-down.svg');
+        }
+
+        &.icon-upload {
+          background-image: url('../assets/player/images/arrow-up.svg');
+        }
+      }
+    }
 
-.vjs-sublime-skin .vjs-audio-button {
-    display: none;
-}
+    .vjs-mute-control {
+      .vjs-icon-placeholder {
+        display: inline-block;
+        width: 22px;
+        height: 22px;
+        vertical-align: middle;
+        background: url('../assets/player/images/volume.svg') no-repeat;
+        background-size: contain;
+
+        &::before {
+          content: '';
+        }
+      }
+
+      &.vjs-vol-0 .vjs-icon-placeholder {
+        background: url('../assets/player/images/volume-mute.svg') no-repeat;
+        background-size: contain;
+      }
+    }
 
-.vjs-sublime-skin .vjs-volume-bar {
-    background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAcCAQAAACw95UnAAAAMElEQVRIx2NgoBL4n4YKGUYNHkEG4zJg1OCRYDCpBowaPJwMppbLRg0eNXjUYBLEAXWNUA6QNm1lAAAAAElFTkSuQmCC);
-    background-size: 22px 14px;
-    background-repeat: no-repeat;
-    height: 100%;
-    width: 100%;
-    max-width: 22px;
-    max-height: 14px;
-    margin: 7px 4px;
-    border-radius: 0;
-}
+    .vjs-volume-menu-button,
+    .vjs-volume-panel {
+      width: 6em;
+      position: absolute;
+      right: 0;
+      margin-right: 65px;
+    }
 
-.vjs-sublime-skin .vjs-volume-level {
-    background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAcAQAAAAAyhWABAAAAAnRSTlMAAHaTzTgAAAAZSURBVHgBYwAB/g9EUv+JokCqiaT+U4MCAPKPS7WUUOc1AAAAAElFTkSuQmCC);
-    background-size: 22px 14px;
-    background-repeat: no-repeat;
-    max-width: 22px;
-    max-height: 14px;
-    height: 100%;
-}
+    .vjs-volume-bar {
+      background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAcCAQAAACw95UnAAAAMElEQVRIx2NgoBL4n4YKGUYNHkEG4zJg1OCRYDCpBowaPJwMppbLRg0eNXjUYBLEAXWNUA6QNm1lAAAAAElFTkSuQmCC) no-repeat;
+      background-size: 22px 14px;
+      height: 100%;
+      width: 100%;
+      max-width: 22px;
+      max-height: 14px;
+      margin: 7px 4px;
+      border-radius: 0;
+      top: 3px;
+
+      .vjs-volume-level {
+        background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAcAQAAAAAyhWABAAAAAnRSTlMAAHaTzTgAAAAZSURBVHgBYwAB/g9EUv+JokCqiaT+U4MCAPKPS7WUUOc1AAAAAElFTkSuQmCC) no-repeat;
+        background-size: 22px 14px;
+        max-width: 22px;
+        max-height: 14px;
+        height: 100%;
+      }
+    }
 
-/* New for VideoJS v6 */
-.vjs-sublime-skin .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active,
-.vjs-sublime-skin .vjs-volume-panel.vjs-volume-panel-horizontal:active,
-.vjs-sublime-skin .vjs-volume-panel.vjs-volume-panel-horizontal:focus,
-.vjs-sublime-skin .vjs-volume-panel.vjs-volume-panel-horizontal:hover {
-    width: 6em;
-    transition-property: none;
-}
+    .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active,
+    .vjs-volume-panel.vjs-volume-panel-horizontal:active,
+    .vjs-volume-panel.vjs-volume-panel-horizontal:focus,
+    .vjs-volume-panel.vjs-volume-panel-horizontal:hover {
+      width: 6em;
+      transition-property: none;
+    }
 
-.vjs-sublime-skin .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control.vjs-volume-horizontal {
-    width: 3em;
-    height: auto;
-}
+    .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control.vjs-volume-horizontal {
+      width: 3em;
+      height: auto;
+    }
 
-.vjs-sublime-skin .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control {
-    transition-property: none;
-}
+    .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control {
+      transition-property: none;
+    }
 
-.vjs-sublime-skin .vjs-fullscreen-control .vjs-icon-placeholder {
-    display: none; /* Remove Duplicate Fullscreen Icon */
-}
+    .vjs-volume-panel {
+      .vjs-mute-control {
+        width: 2em;
+        z-index: 1;
+        padding: 0;
+      }
+
+      .vjs-volume-control {
+        display: inline-block;
+        position: relative;
+        left: 5px;
+        opacity: 1;
+        width: 3em;
+        height: auto;
+      }
+    }
 
-.vjs-sublime-skin .vjs-volume-panel .vjs-mute-control {
-    width: 2em;
-    z-index: 1;
-    padding: 0;
-}
+    .vjs-fullscreen-control {
+      width: 37px;
+
+      .vjs-icon-placeholder {
+        display: inline-block;
+        width: 22px;
+        height: 22px;
+        vertical-align: middle;
+        background: url('../assets/player/images/fullscreen.svg') no-repeat;
+        background-size: contain;
+
+        &::before {
+          content: '';
+        }
+      }
+    }
 
-.vjs-sublime-skin .vjs-volume-panel .vjs-volume-control {
-    display: inline-block;
-    position: relative;
-    left: 5px;
-    opacity: 1;
-    width: 3em;
-    height: auto;
+    .vjs-menu-button-popup {
+      font-size: 13px;
+      font-weight: $font-semibold;
+      width: 42px;
+
+      // Thanks: https://github.com/kmoskwiak/videojs-resolution-switcher/pull/92/files
+      .vjs-resolution-button-label {
+        line-height: $control-bar-height;
+        position: absolute;
+        top: 0;
+        left: -1px;
+        width: 100%;
+        height: 100%;
+        text-align: center;
+        box-sizing: inherit;
+      }
+
+      .vjs-resolution-button {
+        outline: 0 !important;
+      }
+
+      .vjs-menu {
+        top: 20px;
+
+        .vjs-menu-content {
+          width: 4em;
+          left: 50%; /* Center the menu, in it's parent */
+          margin-left: -2em; /* half of width, to center */
+        }
+
+        li {
+          text-transform: none;
+          font-size: 13px;
+        }
+      }
+    }
+  }
 }
 
 // Thanks: https://projects.lukehaas.me/css-loaders/
 .vjs-loading-spinner {
   margin: 0 !important;
-  position: absolute;
+  //position: absolute;
   // 15px is the nav bar height
   top: calc(50% - 15px);
   left: 50%;
-- 
cgit v1.2.3


From 6bafac54bf375cd60f1c06f6afdc648e0e19743d Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Tue, 5 Dec 2017 15:01:47 +0100
Subject: Fix missing default avatar

---
 client/config/webpack.common.js           |  12 ++++++++++++
 client/src/app/shared/users/user.model.ts |   2 +-
 client/src/assets/images/favicon.png      | Bin 2335 -> 539 bytes
 client/src/index.html                     |   2 +-
 4 files changed, 14 insertions(+), 2 deletions(-)

(limited to 'client')

diff --git a/client/config/webpack.common.js b/client/config/webpack.common.js
index acf22dab1..c37516271 100644
--- a/client/config/webpack.common.js
+++ b/client/config/webpack.common.js
@@ -13,6 +13,7 @@ const LoaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin')
 const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin')
 const InlineManifestWebpackPlugin = require('inline-manifest-webpack-plugin')
 const ngcWebpack = require('ngc-webpack')
+const CopyWebpackPlugin = require('copy-webpack-plugin')
 
 const WebpackNotifierPlugin = require('webpack-notifier')
 
@@ -267,6 +268,17 @@ module.exports = function (options) {
         inject: 'body'
       }),
 
+      new CopyWebpackPlugin([
+        {
+          from: helpers.root('src/assets/images/favicon.png'),
+          to: 'assets/images/favicon.png'
+        },
+        {
+          from: helpers.root('src/assets/images/default-avatar.png'),
+          to: 'assets/images/default-avatar.png'
+        }
+      ]),
+
       /*
        * Plugin: ScriptExtHtmlWebpackPlugin
        * Description: Enhances html-webpack-plugin functionality
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts
index 220362ef0..9364ae721 100644
--- a/client/src/app/shared/users/user.model.ts
+++ b/client/src/app/shared/users/user.model.ts
@@ -54,6 +54,6 @@ export class User implements UserServerModel {
   getAvatarPath () {
     if (this.account && this.account.avatar) return this.account.avatar.path
 
-    return '/assets/images/default-avatar.png'
+    return '/client/assets/images/default-avatar.png'
   }
 }
diff --git a/client/src/assets/images/favicon.png b/client/src/assets/images/favicon.png
index bb57ee6b0..cef0f0d2e 100644
Binary files a/client/src/assets/images/favicon.png and b/client/src/assets/images/favicon.png differ
diff --git a/client/src/index.html b/client/src/index.html
index 8e94b903d..4af6b12f6 100644
--- a/client/src/index.html
+++ b/client/src/index.html
@@ -11,7 +11,7 @@
     <!-- open graph and oembed tags -->
     <!-- Do not remove it! -->
 
-    <link rel="icon" type="image/png" href="/client/assets/favicon.png" />
+    <link rel="icon" type="image/png" href="/client/assets/images/favicon.png" />
 
     <!-- base url -->
     <base href="<%= htmlWebpackPlugin.options.metadata.baseUrl %>">
-- 
cgit v1.2.3


From 35fb2b68ff9cd9b755dae6f073c32a97dc237e35 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Tue, 5 Dec 2017 16:30:03 +0100
Subject: Fix favicon ratio

---
 client/src/assets/images/favicon.png | Bin 539 -> 833 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)

(limited to 'client')

diff --git a/client/src/assets/images/favicon.png b/client/src/assets/images/favicon.png
index cef0f0d2e..2e589cf6c 100644
Binary files a/client/src/assets/images/favicon.png and b/client/src/assets/images/favicon.png differ
-- 
cgit v1.2.3


From d235f6b0d1054a2a3451dacade927caefce8f30c Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Tue, 5 Dec 2017 16:48:26 +0100
Subject: Design signup and login pages

---
 client/config/webpack.common.js                    |  1 +
 .../account-change-password.component.html         |  2 +
 client/src/app/login/login.component.html          | 53 +++++++++++-----------
 client/src/app/login/login.component.scss          |  8 ++++
 client/src/app/login/login.component.ts            |  3 +-
 client/src/app/shared/users/user.model.ts          |  2 +-
 client/src/app/signup/signup.component.html        | 10 ++--
 client/src/app/signup/signup.component.scss        |  8 ++++
 client/src/app/signup/signup.component.ts          |  3 +-
 client/src/sass/application.scss                   |  9 ++--
 10 files changed, 58 insertions(+), 41 deletions(-)
 create mode 100644 client/src/app/login/login.component.scss
 create mode 100644 client/src/app/signup/signup.component.scss

(limited to 'client')

diff --git a/client/config/webpack.common.js b/client/config/webpack.common.js
index c37516271..f387b44f9 100644
--- a/client/config/webpack.common.js
+++ b/client/config/webpack.common.js
@@ -302,6 +302,7 @@ module.exports = function (options) {
       */
       new LoaderOptionsPlugin({
         options: {
+          context: '',
           sassLoader: {
             precision: 10,
             includePaths: [ helpers.root('src/sass') ]
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
index bfb55218f..c57e705f9 100644
--- 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
@@ -1,6 +1,8 @@
 <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" class="form-control" id="new-password" placeholder="Old password"
     formControlName="new-password"
diff --git a/client/src/app/login/login.component.html b/client/src/app/login/login.component.html
index bcea0a27a..82b70c98c 100644
--- a/client/src/app/login/login.component.html
+++ b/client/src/app/login/login.component.html
@@ -1,34 +1,33 @@
-<div class="row">
-  <div class="content-padding">
-
-    <h3>Login</h3>
+<div class="margin-content">
+  <div class="title-page title-page-single">
+    Login
+  </div>
 
-    <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
+  <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
 
-    <form role="form" (ngSubmit)="login()" [formGroup]="form">
-      <div class="form-group">
-        <label for="username">Username</label>
-        <input
-          type="text" class="form-control" id="username" placeholder="Username" required
-          formControlName="username"
-        >
-        <div *ngIf="formErrors.username" class="alert alert-danger">
-          {{ formErrors.username }}
-        </div>
+  <form role="form" (ngSubmit)="login()" [formGroup]="form">
+    <div class="form-group">
+      <label for="username">Username</label>
+      <input
+        type="text" id="username" placeholder="Username" required
+        formControlName="username"
+      >
+      <div *ngIf="formErrors.username" class="alert alert-danger">
+        {{ formErrors.username }}
       </div>
+    </div>
 
-      <div class="form-group">
-        <label for="password">Password</label>
-        <input
-          type="password" class="form-control" name="password" id="password" placeholder="Password" required
-          formControlName="password"
-        >
-        <div *ngIf="formErrors.password" class="alert alert-danger">
-          {{ formErrors.password }}
-        </div>
+    <div class="form-group">
+      <label for="password">Password</label>
+      <input
+        type="password" name="password" id="password" placeholder="Password" required
+        formControlName="password"
+      >
+      <div *ngIf="formErrors.password" class="alert alert-danger">
+        {{ formErrors.password }}
       </div>
+    </div>
 
-      <input type="submit" value="Login" class="btn btn-default" [disabled]="!form.valid">
-    </form>
-  </div>
+    <input type="submit" value="Login" [disabled]="!form.valid">
+  </form>
 </div>
diff --git a/client/src/app/login/login.component.scss b/client/src/app/login/login.component.scss
new file mode 100644
index 000000000..fd6981c59
--- /dev/null
+++ b/client/src/app/login/login.component.scss
@@ -0,0 +1,8 @@
+input:not([type=submit]) {
+  @include peertube-input-text(340px);
+  display: block;
+}
+
+input[type=submit] {
+  @include peertube-button;
+}
diff --git a/client/src/app/login/login.component.ts b/client/src/app/login/login.component.ts
index 32dc9e36f..dfede5924 100644
--- a/client/src/app/login/login.component.ts
+++ b/client/src/app/login/login.component.ts
@@ -7,7 +7,8 @@ import { FormReactive } from '../shared'
 
 @Component({
   selector: 'my-login',
-  templateUrl: './login.component.html'
+  templateUrl: './login.component.html',
+  styleUrls: [ './login.component.scss' ]
 })
 
 export class LoginComponent extends FormReactive implements OnInit {
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts
index 9364ae721..b1c323114 100644
--- a/client/src/app/shared/users/user.model.ts
+++ b/client/src/app/shared/users/user.model.ts
@@ -54,6 +54,6 @@ export class User implements UserServerModel {
   getAvatarPath () {
     if (this.account && this.account.avatar) return this.account.avatar.path
 
-    return '/client/assets/images/default-avatar.png'
+    return API_URL + '/client/assets/images/default-avatar.png'
   }
 }
diff --git a/client/src/app/signup/signup.component.html b/client/src/app/signup/signup.component.html
index b8b7826eb..1e9f7f949 100644
--- a/client/src/app/signup/signup.component.html
+++ b/client/src/app/signup/signup.component.html
@@ -1,7 +1,8 @@
-<div class="row">
-  <div class="content-padding">
+<div class="margin-content">
 
-  <h3>Signup</h3>
+  <div class="title-page title-page-single">
+    Signup
+  </div>
 
   <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
 
@@ -39,8 +40,7 @@
       </div>
     </div>
 
-    <input type="submit" value="Signup" class="btn btn-default" [disabled]="!form.valid">
+    <input type="submit" value="Signup" [disabled]="!form.valid">
   </form>
 
-  </div>
 </div>
diff --git a/client/src/app/signup/signup.component.scss b/client/src/app/signup/signup.component.scss
new file mode 100644
index 000000000..fd6981c59
--- /dev/null
+++ b/client/src/app/signup/signup.component.scss
@@ -0,0 +1,8 @@
+input:not([type=submit]) {
+  @include peertube-input-text(340px);
+  display: block;
+}
+
+input[type=submit] {
+  @include peertube-button;
+}
diff --git a/client/src/app/signup/signup.component.ts b/client/src/app/signup/signup.component.ts
index 28e1ed0a8..13390a32a 100644
--- a/client/src/app/signup/signup.component.ts
+++ b/client/src/app/signup/signup.component.ts
@@ -16,7 +16,8 @@ import { UserCreate } from '../../../../shared'
 
 @Component({
   selector: 'my-signup',
-  templateUrl: './signup.component.html'
+  templateUrl: './signup.component.html',
+  styleUrls: [ './signup.component.scss' ]
 })
 export class SignupComponent extends FormReactive implements OnInit {
   error: string = null
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index 5e401f93b..4c5c0202c 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -20,12 +20,9 @@ input.readonly {
   background-color: #fff !important;
 }
 
-.form-control, .btn {
-  border-radius: 0;
-}
-
-.dropdown-menu {
-  border-radius: 0;
+label {
+  font-weight: $font-bold;
+  font-size: 15px;
 }
 
 .glyphicon-black {
-- 
cgit v1.2.3


From f3aaa9a95cc2b61f1f255472d7014d08faa66561 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Tue, 5 Dec 2017 17:46:33 +0100
Subject: Fix client search

---
 .../account-videos/account-videos.component.ts     |  8 +---
 client/src/app/app.component.html                  |  2 +-
 client/src/app/app.module.ts                       |  4 +-
 client/src/app/header/header.component.html        | 10 +++++
 client/src/app/header/header.component.scss        | 39 +++++++++++++++++
 client/src/app/header/header.component.ts          | 28 ++++++++++++
 client/src/app/header/index.ts                     |  1 +
 client/src/app/shared/index.ts                     |  1 -
 client/src/app/shared/misc/utils.ts                | 18 ++++++++
 client/src/app/shared/search/index.ts              |  4 --
 client/src/app/shared/search/search-field.type.ts  |  1 -
 client/src/app/shared/search/search.component.html | 10 -----
 client/src/app/shared/search/search.component.scss | 39 -----------------
 client/src/app/shared/search/search.component.ts   | 42 ------------------
 client/src/app/shared/search/search.model.ts       |  6 ---
 client/src/app/shared/search/search.service.ts     | 18 --------
 client/src/app/shared/shared.module.ts             |  4 --
 client/src/app/shared/video/abstract-video-list.ts | 24 +++++-----
 client/src/app/shared/video/video.service.ts       |  9 ++--
 client/src/app/signup/signup.component.html        |  2 +-
 client/src/app/videos/video-list/index.ts          |  1 +
 .../video-list/video-recently-added.component.ts   | 12 +++--
 .../videos/video-list/video-search.component.ts    | 51 ++++++++++++++++++++++
 .../videos/video-list/video-trending.component.ts  | 12 +++--
 client/src/app/videos/videos-routing.module.ts     | 11 +++++
 client/src/app/videos/videos.module.ts             |  5 ++-
 26 files changed, 195 insertions(+), 167 deletions(-)
 create mode 100644 client/src/app/header/header.component.html
 create mode 100644 client/src/app/header/header.component.scss
 create mode 100644 client/src/app/header/header.component.ts
 create mode 100644 client/src/app/header/index.ts
 create mode 100644 client/src/app/shared/misc/utils.ts
 delete mode 100644 client/src/app/shared/search/index.ts
 delete mode 100644 client/src/app/shared/search/search-field.type.ts
 delete mode 100644 client/src/app/shared/search/search.component.html
 delete mode 100644 client/src/app/shared/search/search.component.scss
 delete mode 100644 client/src/app/shared/search/search.component.ts
 delete mode 100644 client/src/app/shared/search/search.model.ts
 delete mode 100644 client/src/app/shared/search/search.service.ts
 create mode 100644 client/src/app/videos/video-list/video-search.component.ts

(limited to 'client')

diff --git a/client/src/app/account/account-videos/account-videos.component.ts b/client/src/app/account/account-videos/account-videos.component.ts
index cc28f511a..1bc6c0a35 100644
--- a/client/src/app/account/account-videos/account-videos.component.ts
+++ b/client/src/app/account/account-videos/account-videos.component.ts
@@ -1,4 +1,4 @@
-import { Component, OnDestroy, OnInit } from '@angular/core'
+import { Component, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { NotificationsService } from 'angular2-notifications'
 import { AbstractVideoList } from '../../shared/video/abstract-video-list'
@@ -9,7 +9,7 @@ import { VideoService } from '../../shared/video/video.service'
   templateUrl: './account-videos.component.html',
   styleUrls: [ './account-videos.component.scss' ]
 })
-export class AccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy {
+export class AccountVideosComponent extends AbstractVideoList implements OnInit {
   titlePage = 'My videos'
   currentRoute = '/account/videos'
 
@@ -24,10 +24,6 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit,
     super.ngOnInit()
   }
 
-  ngOnDestroy () {
-    super.ngOnDestroy()
-  }
-
   getVideosObservable () {
     return this.videoService.getMyVideos(this.pagination, this.sort)
   }
diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html
index 640524e23..b095e44d6 100644
--- a/client/src/app/app.component.html
+++ b/client/src/app/app.component.html
@@ -11,7 +11,7 @@
     </div>
 
     <div class="header-right">
-      <my-search></my-search>
+      <my-header></my-header>
     </div>
   </div>
 
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts
index 342589003..ee7cb0c8a 100644
--- a/client/src/app/app.module.ts
+++ b/client/src/app/app.module.ts
@@ -21,6 +21,7 @@ import { SignupModule } from './signup'
 import { SharedModule } from './shared'
 import { VideosModule } from './videos'
 import { MenuComponent, MenuAdminComponent } from './menu'
+import { HeaderComponent } from './header'
 
 export function metaFactory (): MetaLoader {
   return new MetaStaticLoader({
@@ -51,7 +52,8 @@ const APP_PROVIDERS = [
     AppComponent,
 
     MenuComponent,
-    MenuAdminComponent
+    MenuAdminComponent,
+    HeaderComponent
   ],
   imports: [
     BrowserModule,
diff --git a/client/src/app/header/header.component.html b/client/src/app/header/header.component.html
new file mode 100644
index 000000000..aa72fb68a
--- /dev/null
+++ b/client/src/app/header/header.component.html
@@ -0,0 +1,10 @@
+<input
+    type="text" id="search-video" name="search-video" placeholder="Search..."
+    [(ngModel)]="searchValue" (keyup.enter)="doSearch()"
+>
+<span (click)="doSearch()" class="icon icon-search"></span>
+
+<a class="upload-button" routerLink="/videos/upload">
+  <span class="icon icon-upload"></span>
+  Upload
+</a>
diff --git a/client/src/app/header/header.component.scss b/client/src/app/header/header.component.scss
new file mode 100644
index 000000000..7ba8ef26c
--- /dev/null
+++ b/client/src/app/header/header.component.scss
@@ -0,0 +1,39 @@
+#search-video {
+  @include peertube-input-text($search-input-width);
+  margin-right: 15px;
+  padding-right: 25px; // For the search icon
+
+  &::placeholder {
+    color: #000;
+  }
+}
+
+.icon.icon-search {
+  display: inline-block;
+  background: url('../../../assets/images/header/search.svg') no-repeat;
+  background-size: contain;
+  width: 25px;
+  height: 21px;
+  vertical-align: middle;
+  cursor: pointer;
+  // yolo
+  position: absolute;
+  margin-left: -50px;
+  margin-top: 5px;
+}
+
+.upload-button {
+  @include peertube-button-link;
+
+  margin-right: 25px;
+
+  .icon.icon-upload {
+    display: inline-block;
+    background: url('../../../assets/images/header/upload.svg') no-repeat;
+    background-size: contain;
+    width: 22px;
+    height: 24px;
+    vertical-align: middle;
+    margin-right: 6px;
+  }
+}
diff --git a/client/src/app/header/header.component.ts b/client/src/app/header/header.component.ts
new file mode 100644
index 000000000..a903048f2
--- /dev/null
+++ b/client/src/app/header/header.component.ts
@@ -0,0 +1,28 @@
+import { Component, OnInit } from '@angular/core'
+import { Router } from '@angular/router'
+import { getParameterByName } from '../shared/misc/utils'
+
+@Component({
+  selector: 'my-header',
+  templateUrl: './header.component.html',
+  styleUrls: [ './header.component.scss' ]
+})
+
+export class HeaderComponent implements OnInit {
+  searchValue = ''
+
+  constructor (private router: Router) {}
+
+  ngOnInit () {
+    const searchQuery = getParameterByName('search', window.location.href)
+    if (searchQuery) this.searchValue = searchQuery
+  }
+
+  doSearch () {
+    if (!this.searchValue) return
+
+    this.router.navigate([ '/videos', 'search' ], {
+      queryParams: { search: this.searchValue }
+    })
+  }
+}
diff --git a/client/src/app/header/index.ts b/client/src/app/header/index.ts
new file mode 100644
index 000000000..d98d2d00a
--- /dev/null
+++ b/client/src/app/header/index.ts
@@ -0,0 +1 @@
+export * from './header.component'
diff --git a/client/src/app/shared/index.ts b/client/src/app/shared/index.ts
index 79bf5ef43..413dda16a 100644
--- a/client/src/app/shared/index.ts
+++ b/client/src/app/shared/index.ts
@@ -1,7 +1,6 @@
 export * from './auth'
 export * from './forms'
 export * from './rest'
-export * from './search'
 export * from './users'
 export * from './video-abuse'
 export * from './video-blacklist'
diff --git a/client/src/app/shared/misc/utils.ts b/client/src/app/shared/misc/utils.ts
new file mode 100644
index 000000000..2b5c3686e
--- /dev/null
+++ b/client/src/app/shared/misc/utils.ts
@@ -0,0 +1,18 @@
+// Thanks: https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
+
+function getParameterByName (name: string, url: string) {
+  if (!url) url = window.location.href
+  name = name.replace(/[\[\]]/g, '\\$&')
+
+  const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)')
+  const results = regex.exec(url)
+
+  if (!results) return null
+  if (!results[2]) return ''
+
+  return decodeURIComponent(results[2].replace(/\+/g, ' '))
+}
+
+export {
+  getParameterByName
+}
diff --git a/client/src/app/shared/search/index.ts b/client/src/app/shared/search/index.ts
deleted file mode 100644
index d4016cf89..000000000
--- a/client/src/app/shared/search/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export * from './search-field.type'
-export * from './search.component'
-export * from './search.model'
-export * from './search.service'
diff --git a/client/src/app/shared/search/search-field.type.ts b/client/src/app/shared/search/search-field.type.ts
deleted file mode 100644
index 7323d6cc3..000000000
--- a/client/src/app/shared/search/search-field.type.ts
+++ /dev/null
@@ -1 +0,0 @@
-export type SearchField = 'name' | 'account' | 'host' | 'tags'
diff --git a/client/src/app/shared/search/search.component.html b/client/src/app/shared/search/search.component.html
deleted file mode 100644
index 9bc9bafe4..000000000
--- a/client/src/app/shared/search/search.component.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<input
-    type="text" id="search-video" name="search-video" placeholder="Search..."
-    [(ngModel)]="searchCriteria.value" (keyup.enter)="doSearch()"
->
-<span (click)="doSearch()" class="icon icon-search"></span>
-
-<a class="upload-button" routerLink="/videos/upload">
-  <span class="icon icon-upload"></span>
-  Upload
-</a>
diff --git a/client/src/app/shared/search/search.component.scss b/client/src/app/shared/search/search.component.scss
deleted file mode 100644
index 7ba8ef26c..000000000
--- a/client/src/app/shared/search/search.component.scss
+++ /dev/null
@@ -1,39 +0,0 @@
-#search-video {
-  @include peertube-input-text($search-input-width);
-  margin-right: 15px;
-  padding-right: 25px; // For the search icon
-
-  &::placeholder {
-    color: #000;
-  }
-}
-
-.icon.icon-search {
-  display: inline-block;
-  background: url('../../../assets/images/header/search.svg') no-repeat;
-  background-size: contain;
-  width: 25px;
-  height: 21px;
-  vertical-align: middle;
-  cursor: pointer;
-  // yolo
-  position: absolute;
-  margin-left: -50px;
-  margin-top: 5px;
-}
-
-.upload-button {
-  @include peertube-button-link;
-
-  margin-right: 25px;
-
-  .icon.icon-upload {
-    display: inline-block;
-    background: url('../../../assets/images/header/upload.svg') no-repeat;
-    background-size: contain;
-    width: 22px;
-    height: 24px;
-    vertical-align: middle;
-    margin-right: 6px;
-  }
-}
diff --git a/client/src/app/shared/search/search.component.ts b/client/src/app/shared/search/search.component.ts
deleted file mode 100644
index f49ecc8ad..000000000
--- a/client/src/app/shared/search/search.component.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { Component, OnInit } from '@angular/core'
-import { Router } from '@angular/router'
-import { Search } from './search.model'
-import { SearchService } from './search.service'
-
-@Component({
-  selector: 'my-search',
-  templateUrl: './search.component.html',
-  styleUrls: [ './search.component.scss' ]
-})
-
-export class SearchComponent implements OnInit {
-  searchCriteria: Search = {
-    field: 'name',
-    value: ''
-  }
-
-  constructor (private searchService: SearchService, private router: Router) {}
-
-  ngOnInit () {
-    // Subscribe if the search changed
-    // Usually changed by videos list component
-    this.searchService.updateSearch.subscribe(
-      newSearchCriteria => {
-        // Put a field by default
-        if (!newSearchCriteria.field) {
-          newSearchCriteria.field = 'name'
-        }
-
-        this.searchCriteria = newSearchCriteria
-      }
-    )
-  }
-
-  doSearch () {
-    // if (this.router.url.indexOf('/videos/list') === -1) {
-    //   this.router.navigate([ '/videos/list' ])
-    // }
-
-    this.searchService.searchUpdated.next(this.searchCriteria)
-  }
-}
diff --git a/client/src/app/shared/search/search.model.ts b/client/src/app/shared/search/search.model.ts
deleted file mode 100644
index 174adf2c6..000000000
--- a/client/src/app/shared/search/search.model.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { SearchField } from './search-field.type'
-
-export interface Search {
-  field: SearchField
-  value: string
-}
diff --git a/client/src/app/shared/search/search.service.ts b/client/src/app/shared/search/search.service.ts
deleted file mode 100644
index 0480b46bd..000000000
--- a/client/src/app/shared/search/search.service.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Injectable } from '@angular/core'
-import { Subject } from 'rxjs/Subject'
-import { ReplaySubject } from 'rxjs/ReplaySubject'
-
-import { Search } from './search.model'
-
-// This class is needed to communicate between videos/ and search component
-// Remove it when we'll be able to subscribe to router changes
-@Injectable()
-export class SearchService {
-  searchUpdated: Subject<Search>
-  updateSearch: Subject<Search>
-
-  constructor () {
-    this.updateSearch = new Subject<Search>()
-    this.searchUpdated = new ReplaySubject<Search>(1)
-  }
-}
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index f7ced040d..86e1a380e 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -17,7 +17,6 @@ import { FromNowPipe } from './misc/from-now.pipe'
 import { LoaderComponent } from './misc/loader.component'
 import { NumberFormatterPipe } from './misc/number-formatter.pipe'
 import { RestExtractor, RestService } from './rest'
-import { SearchComponent, SearchService } from './search'
 import { UserService } from './users'
 import { VideoAbuseService } from './video-abuse'
 import { VideoBlacklistService } from './video-blacklist'
@@ -43,7 +42,6 @@ import { VideoService } from './video/video.service'
   ],
 
   declarations: [
-    SearchComponent,
     LoaderComponent,
     VideoThumbnailComponent,
     NumberFormatterPipe,
@@ -66,7 +64,6 @@ import { VideoService } from './video/video.service'
     BytesPipe,
     KeysPipe,
 
-    SearchComponent,
     LoaderComponent,
     VideoThumbnailComponent,
 
@@ -78,7 +75,6 @@ import { VideoService } from './video/video.service'
     AUTH_INTERCEPTOR_PROVIDER,
     RestExtractor,
     RestService,
-    SearchService,
     VideoAbuseService,
     VideoBlacklistService,
     UserService,
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts
index cf717cf4c..84ca5cbe4 100644
--- a/client/src/app/shared/video/abstract-video-list.ts
+++ b/client/src/app/shared/video/abstract-video-list.ts
@@ -1,25 +1,25 @@
-import { OnDestroy, OnInit } from '@angular/core'
+import { OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { NotificationsService } from 'angular2-notifications'
 import { Observable } from 'rxjs/Observable'
-import { Subscription } from 'rxjs/Subscription'
 import { SortField } from './sort-field.type'
 import { VideoPagination } from './video-pagination.model'
 import { Video } from './video.model'
 
-export abstract class AbstractVideoList implements OnInit, OnDestroy {
+export abstract class AbstractVideoList implements OnInit {
   pagination: VideoPagination = {
     currentPage: 1,
     itemsPerPage: 25,
     totalItems: null
   }
   sort: SortField = '-createdAt'
+  defaultSort: SortField = '-createdAt'
   videos: Video[] = []
+  loadOnInit = true
 
   protected notificationsService: NotificationsService
   protected router: Router
   protected route: ActivatedRoute
-  protected subActivatedRoute: Subscription
 
   protected abstract currentRoute: string
 
@@ -32,13 +32,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
     // Subscribe to route changes
     const routeParams = this.route.snapshot.params
     this.loadRouteParams(routeParams)
-    this.loadMoreVideos('after')
-  }
-
-  ngOnDestroy () {
-    if (this.subActivatedRoute) {
-      this.subActivatedRoute.unsubscribe()
-    }
+    if (this.loadOnInit === true) this.loadMoreVideos('after')
   }
 
   onNearOfTop () {
@@ -53,6 +47,12 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
     }
   }
 
+  reloadVideos () {
+    this.videos = []
+    this.loadedPages = {}
+    this.loadMoreVideos('before')
+  }
+
   loadMoreVideos (where: 'before' | 'after') {
     if (this.loadedPages[this.pagination.currentPage] === true) return
 
@@ -105,7 +105,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
   }
 
   protected loadRouteParams (routeParams: { [ key: string ]: any }) {
-    this.sort = routeParams['sort'] as SortField || '-createdAt'
+    this.sort = routeParams['sort'] as SortField || this.defaultSort
 
     if (routeParams['page'] !== undefined) {
       this.pagination.currentPage = parseInt(routeParams['page'], 10)
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts
index b2a26417c..3f35b67c4 100644
--- a/client/src/app/shared/video/video.service.ts
+++ b/client/src/app/shared/video/video.service.ts
@@ -11,7 +11,7 @@ import { VideoRateType } from '../../../../../shared/models/videos/video-rate.ty
 import { VideoUpdate } from '../../../../../shared/models/videos/video-update.model'
 import { RestExtractor } from '../rest/rest-extractor.service'
 import { RestService } from '../rest/rest.service'
-import { Search } from '../search/search.model'
+import { Search } from '../header/search.model'
 import { UserService } from '../users/user.service'
 import { SortField } from './sort-field.type'
 import { VideoDetails } from './video-details.model'
@@ -91,15 +91,14 @@ export class VideoService {
       .catch((res) => this.restExtractor.handleError(res))
   }
 
-  searchVideos (search: Search, videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> {
-    const url = VideoService.BASE_VIDEO_URL + 'search/' + encodeURIComponent(search.value)
+  searchVideos (search: string, videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> {
+    const url = VideoService.BASE_VIDEO_URL + 'search'
 
     const pagination = this.videoPaginationToRestPagination(videoPagination)
 
     let params = new HttpParams()
     params = this.restService.addRestGetParams(params, pagination, sort)
-
-    if (search.field) params.set('field', search.field)
+    params = params.append('search', search)
 
     return this.authHttp
       .get<ResultList<VideoServerModel>>(url, { params })
diff --git a/client/src/app/signup/signup.component.html b/client/src/app/signup/signup.component.html
index 1e9f7f949..8a30ab512 100644
--- a/client/src/app/signup/signup.component.html
+++ b/client/src/app/signup/signup.component.html
@@ -1,7 +1,7 @@
 <div class="margin-content">
 
   <div class="title-page title-page-single">
-    Signup
+    Create an account
   </div>
 
   <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
diff --git a/client/src/app/videos/video-list/index.ts b/client/src/app/videos/video-list/index.ts
index 594e31984..13024294e 100644
--- a/client/src/app/videos/video-list/index.ts
+++ b/client/src/app/videos/video-list/index.ts
@@ -1,3 +1,4 @@
 export * from './video-recently-added.component'
 export * from './video-trending.component'
+export * from './video-search.component'
 export * from './shared'
diff --git a/client/src/app/videos/video-list/video-recently-added.component.ts b/client/src/app/videos/video-list/video-recently-added.component.ts
index d48804414..6168fac95 100644
--- a/client/src/app/videos/video-list/video-recently-added.component.ts
+++ b/client/src/app/videos/video-list/video-recently-added.component.ts
@@ -1,17 +1,19 @@
-import { Component, OnDestroy, OnInit } from '@angular/core'
+import { Component, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { NotificationsService } from 'angular2-notifications'
-import { VideoService } from '../../shared/video/video.service'
 import { AbstractVideoList } from '../../shared/video/abstract-video-list'
+import { SortField } from '../../shared/video/sort-field.type'
+import { VideoService } from '../../shared/video/video.service'
 
 @Component({
   selector: 'my-videos-recently-added',
   styleUrls: [ '../../shared/video/abstract-video-list.scss' ],
   templateUrl: '../../shared/video/abstract-video-list.html'
 })
-export class VideoRecentlyAddedComponent extends AbstractVideoList implements OnInit, OnDestroy {
+export class VideoRecentlyAddedComponent extends AbstractVideoList implements OnInit {
   titlePage = 'Recently added'
   currentRoute = '/videos/recently-added'
+  sort: SortField = '-createdAt'
 
   constructor (protected router: Router,
                protected route: ActivatedRoute,
@@ -24,10 +26,6 @@ export class VideoRecentlyAddedComponent extends AbstractVideoList implements On
     super.ngOnInit()
   }
 
-  ngOnDestroy () {
-    super.ngOnDestroy()
-  }
-
   getVideosObservable () {
     return this.videoService.getVideos(this.pagination, this.sort)
   }
diff --git a/client/src/app/videos/video-list/video-search.component.ts b/client/src/app/videos/video-list/video-search.component.ts
new file mode 100644
index 000000000..ba851d27e
--- /dev/null
+++ b/client/src/app/videos/video-list/video-search.component.ts
@@ -0,0 +1,51 @@
+import { Component, OnDestroy, OnInit } from '@angular/core'
+import { ActivatedRoute, Router } from '@angular/router'
+import { NotificationsService } from 'angular2-notifications'
+import { AbstractVideoList } from 'app/shared/video/abstract-video-list'
+import { Subscription } from 'rxjs/Subscription'
+import { SortField } from '../../shared/video/sort-field.type'
+import { VideoService } from '../../shared/video/video.service'
+
+@Component({
+  selector: 'my-videos-search',
+  styleUrls: [ '../../shared/video/abstract-video-list.scss' ],
+  templateUrl: '../../shared/video/abstract-video-list.html'
+})
+export class VideoSearchComponent extends AbstractVideoList implements OnInit, OnDestroy {
+  titlePage = 'Search'
+  currentRoute = '/videos/search'
+  loadOnInit = false
+
+  private search = ''
+  private subActivatedRoute: Subscription
+
+  constructor (protected router: Router,
+               protected route: ActivatedRoute,
+               protected notificationsService: NotificationsService,
+               private videoService: VideoService) {
+    super()
+  }
+
+  ngOnInit () {
+    super.ngOnInit()
+
+    this.subActivatedRoute = this.route.queryParams.subscribe(
+      queryParams => {
+        this.search = queryParams['search']
+        this.reloadVideos()
+      },
+
+      err => this.notificationsService.error('Error', err.text)
+    )
+  }
+
+  ngOnDestroy () {
+    if (this.subActivatedRoute) {
+      this.subActivatedRoute.unsubscribe()
+    }
+  }
+
+  getVideosObservable () {
+    return this.videoService.searchVideos(this.search, this.pagination, this.sort)
+  }
+}
diff --git a/client/src/app/videos/video-list/video-trending.component.ts b/client/src/app/videos/video-list/video-trending.component.ts
index 9108289c9..e80fd7f2c 100644
--- a/client/src/app/videos/video-list/video-trending.component.ts
+++ b/client/src/app/videos/video-list/video-trending.component.ts
@@ -1,17 +1,19 @@
-import { Component, OnDestroy, OnInit } from '@angular/core'
+import { Component, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { NotificationsService } from 'angular2-notifications'
-import { VideoService } from '../../shared/video/video.service'
 import { AbstractVideoList } from 'app/shared/video/abstract-video-list'
+import { SortField } from '../../shared/video/sort-field.type'
+import { VideoService } from '../../shared/video/video.service'
 
 @Component({
   selector: 'my-videos-trending',
   styleUrls: [ '../../shared/video/abstract-video-list.scss' ],
   templateUrl: '../../shared/video/abstract-video-list.html'
 })
-export class VideoTrendingComponent extends AbstractVideoList implements OnInit, OnDestroy {
+export class VideoTrendingComponent extends AbstractVideoList implements OnInit {
   titlePage = 'Trending'
   currentRoute = '/videos/trending'
+  defaultSort: SortField = '-views'
 
   constructor (protected router: Router,
                protected route: ActivatedRoute,
@@ -24,10 +26,6 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit,
     super.ngOnInit()
   }
 
-  ngOnDestroy () {
-    super.ngOnDestroy()
-  }
-
   getVideosObservable () {
     return this.videoService.getVideos(this.pagination, this.sort)
   }
diff --git a/client/src/app/videos/videos-routing.module.ts b/client/src/app/videos/videos-routing.module.ts
index 204851c81..6910421b7 100644
--- a/client/src/app/videos/videos-routing.module.ts
+++ b/client/src/app/videos/videos-routing.module.ts
@@ -1,6 +1,7 @@
 import { NgModule } from '@angular/core'
 import { RouterModule, Routes } from '@angular/router'
 import { MetaGuard } from '@ngx-meta/core'
+import { VideoSearchComponent } from './video-list'
 import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component'
 import { VideoTrendingComponent } from './video-list/video-trending.component'
 import { VideosComponent } from './videos.component'
@@ -34,6 +35,15 @@ const videosRoutes: Routes = [
           }
         }
       },
+      {
+        path: 'search',
+        component: VideoSearchComponent,
+        data: {
+          meta: {
+            title: 'Search videos'
+          }
+        }
+      },
       {
         path: 'upload',
         loadChildren: 'app/videos/+video-edit#VideoAddModule',
@@ -54,6 +64,7 @@ const videosRoutes: Routes = [
       },
       {
         path: ':uuid',
+        pathMatch: 'full',
         redirectTo: 'watch/:uuid'
       },
       {
diff --git a/client/src/app/videos/videos.module.ts b/client/src/app/videos/videos.module.ts
index 6d846fd3b..8c8d52ad9 100644
--- a/client/src/app/videos/videos.module.ts
+++ b/client/src/app/videos/videos.module.ts
@@ -1,6 +1,6 @@
 import { NgModule } from '@angular/core'
 import { SharedModule } from '../shared'
-import { VideoMiniatureComponent } from './video-list'
+import { VideoMiniatureComponent, VideoSearchComponent } from './video-list'
 import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component'
 import { VideoTrendingComponent } from './video-list/video-trending.component'
 import { VideosRoutingModule } from './videos-routing.module'
@@ -17,7 +17,8 @@ import { VideosComponent } from './videos.component'
 
     VideoTrendingComponent,
     VideoRecentlyAddedComponent,
-    VideoMiniatureComponent
+    VideoMiniatureComponent,
+    VideoSearchComponent
   ],
 
   exports: [
-- 
cgit v1.2.3


From a06a31c75c0cd4d337e3e193c670a77cabcd9507 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Tue, 5 Dec 2017 18:43:15 +0100
Subject: Fix player control bar when video is not loaded

---
 client/src/assets/player/peertube-videojs-plugin.ts | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

(limited to 'client')

diff --git a/client/src/assets/player/peertube-videojs-plugin.ts b/client/src/assets/player/peertube-videojs-plugin.ts
index 977455bff..add4e521e 100644
--- a/client/src/assets/player/peertube-videojs-plugin.ts
+++ b/client/src/assets/player/peertube-videojs-plugin.ts
@@ -117,10 +117,12 @@ const WebTorrentButton = videojsUntyped.extend(Button, {
 
   createEl: function () {
     const div = document.createElement('div')
+    const subDiv = document.createElement('div')
+    div.appendChild(subDiv)
 
     const downloadIcon = document.createElement('span')
     downloadIcon.classList.add('icon', 'icon-download')
-    div.appendChild(downloadIcon)
+    subDiv.appendChild(downloadIcon)
 
     const downloadSpeedText = document.createElement('span')
     downloadSpeedText.classList.add('download-speed-text')
@@ -129,11 +131,11 @@ const WebTorrentButton = videojsUntyped.extend(Button, {
     const downloadSpeedUnit = document.createElement('span')
     downloadSpeedText.appendChild(downloadSpeedNumber)
     downloadSpeedText.appendChild(downloadSpeedUnit)
-    div.appendChild(downloadSpeedText)
+    subDiv.appendChild(downloadSpeedText)
 
     const uploadIcon = document.createElement('span')
     uploadIcon.classList.add('icon', 'icon-upload')
-    div.appendChild(uploadIcon)
+    subDiv.appendChild(uploadIcon)
 
     const uploadSpeedText = document.createElement('span')
     uploadSpeedText.classList.add('upload-speed-text')
@@ -142,19 +144,19 @@ const WebTorrentButton = videojsUntyped.extend(Button, {
     const uploadSpeedUnit = document.createElement('span')
     uploadSpeedText.appendChild(uploadSpeedNumber)
     uploadSpeedText.appendChild(uploadSpeedUnit)
-    div.appendChild(uploadSpeedText)
+    subDiv.appendChild(uploadSpeedText)
 
     const peersText = document.createElement('span')
     peersText.textContent = ' peers'
     peersText.classList.add('peers-text')
     const peersNumber = document.createElement('span')
     peersNumber.classList.add('peers-number')
-    div.appendChild(peersNumber)
-    div.appendChild(peersText)
+    subDiv.appendChild(peersNumber)
+    subDiv.appendChild(peersText)
 
     div.className = 'vjs-webtorrent'
     // Hide the stats before we get the info
-    div.style.display = 'none'
+    subDiv.style.display = 'none'
 
     this.player_.on('torrentInfo', (event, data) => {
       const downloadSpeed = bytes(data.downloadSpeed)
@@ -169,7 +171,7 @@ const WebTorrentButton = videojsUntyped.extend(Button, {
 
       peersNumber.textContent = numPeers
 
-      div.style.display = 'block'
+      subDiv.style.display = 'block'
     })
 
     return div
-- 
cgit v1.2.3


From a2b817d322ef4074bdaaf2589ada567f338323f4 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Wed, 6 Dec 2017 09:19:25 +0100
Subject: Better error messages

---
 .../account-change-password.component.html                   |  8 ++++----
 .../account-change-password.component.scss                   |  7 ++++++-
 .../account-details/account-details.component.html           |  2 +-
 .../account-details/account-details.component.scss           |  1 +
 .../app/account/account-videos/account-videos.component.html |  2 ++
 .../app/account/account-videos/account-videos.component.scss |  7 +++++--
 client/src/app/login/login.component.html                    |  8 ++++----
 client/src/app/shared/video/abstract-video-list.ts           |  8 ++++++++
 client/src/app/signup/signup.component.html                  | 12 ++++++------
 client/src/sass/_variables.scss                              |  1 +
 client/src/sass/application.scss                             | 10 ++++++++++
 11 files changed, 48 insertions(+), 18 deletions(-)

(limited to 'client')

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
index c57e705f9..b0e3cada4 100644
--- 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
@@ -4,15 +4,15 @@
 
   <label for="new-password">Change password</label>
   <input
-    type="password" class="form-control" id="new-password" placeholder="Old password"
-    formControlName="new-password"
+    type="password" id="new-password" placeholder="New password"
+    formControlName="new-password" [ngClass]="{ 'input-error': formErrors['new-password'] }"
   >
-  <div *ngIf="formErrors['new-password']" class="alert alert-danger">
+  <div *ngIf="formErrors['new-password']" class="form-error">
     {{ formErrors['new-password'] }}
   </div>
 
   <input
-    type="password" id="new-confirmed-password" placeholder="New password"
+    type="password" id="new-confirmed-password" placeholder="Confirm new password"
     formControlName="new-confirmed-password"
   >
 
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
index 593355b70..75827abbf 100644
--- 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
@@ -1,9 +1,14 @@
 input[type=password] {
   @include peertube-input-text(340px);
   display: block;
-  margin-bottom: 10px;
+
+  &#new-confirmed-password {
+    margin-top: 15px;
+  }
 }
 
 input[type=submit] {
   @include peertube-button;
+  margin-top: 15px;
 }
+
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
index c3cf6b629..bc18b39b4 100644
--- 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
@@ -10,5 +10,5 @@
     {{ formErrors['displayNSFW'] }}
   </div>
 
-  <input type="submit" value="Update" [disabled]="!form.valid">
+  <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
index b1810d4f9..687166d9e 100644
--- 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
@@ -8,4 +8,5 @@ input[type=submit] {
   @include peertube-button;
 
   display: block;
+  margin-top: 15px;
 }
diff --git a/client/src/app/account/account-videos/account-videos.component.html b/client/src/app/account/account-videos/account-videos.component.html
index eb0a32fd3..81bda9477 100644
--- a/client/src/app/account/account-videos/account-videos.component.html
+++ b/client/src/app/account/account-videos/account-videos.component.html
@@ -1,7 +1,9 @@
 <div
   infiniteScroll
   [infiniteScrollDistance]="0.5"
+  [infiniteScrollUpDistance]="1.5"
   (scrolled)="onNearOfBottom()"
+  (scrolledUp)="onNearOfTop()"
 >
   <div class="video" *ngFor="let video of videos">
     <my-video-thumbnail [video]="video"></my-video-thumbnail>
diff --git a/client/src/app/account/account-videos/account-videos.component.scss b/client/src/app/account/account-videos/account-videos.component.scss
index b26933d22..c31497350 100644
--- a/client/src/app/account/account-videos/account-videos.component.scss
+++ b/client/src/app/account/account-videos/account-videos.component.scss
@@ -2,8 +2,11 @@
   display: flex;
   height: 130px;
   padding-bottom: 20px;
-  margin-bottom: 20px;
-  border-bottom: 1px solid #C6C6C6;
+
+  &:not(:last-child) {
+    margin-bottom: 20px;
+    border-bottom: 1px solid #C6C6C6;
+  }
 
   my-video-thumbnail {
     margin-right: 10px;
diff --git a/client/src/app/login/login.component.html b/client/src/app/login/login.component.html
index 82b70c98c..24807987c 100644
--- a/client/src/app/login/login.component.html
+++ b/client/src/app/login/login.component.html
@@ -10,9 +10,9 @@
       <label for="username">Username</label>
       <input
         type="text" id="username" placeholder="Username" required
-        formControlName="username"
+        formControlName="username" [ngClass]="{ 'input-error': formErrors['username'] }"
       >
-      <div *ngIf="formErrors.username" class="alert alert-danger">
+      <div *ngIf="formErrors.username" class="form-error">
         {{ formErrors.username }}
       </div>
     </div>
@@ -21,9 +21,9 @@
       <label for="password">Password</label>
       <input
         type="password" name="password" id="password" placeholder="Password" required
-        formControlName="password"
+        formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }"
       >
-      <div *ngIf="formErrors.password" class="alert alert-danger">
+      <div *ngIf="formErrors.password" class="form-error">
         {{ formErrors.password }}
       </div>
     </div>
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts
index 84ca5cbe4..ee1ed2cb2 100644
--- a/client/src/app/shared/video/abstract-video-list.ts
+++ b/client/src/app/shared/video/abstract-video-list.ts
@@ -32,6 +32,7 @@ export abstract class AbstractVideoList implements OnInit {
     // Subscribe to route changes
     const routeParams = this.route.snapshot.params
     this.loadRouteParams(routeParams)
+
     if (this.loadOnInit === true) this.loadMoreVideos('after')
   }
 
@@ -60,6 +61,13 @@ export abstract class AbstractVideoList implements OnInit {
 
     observable.subscribe(
       ({ videos, totalVideos }) => {
+        // Paging is too high, return to the first one
+        if (totalVideos <= ((this.pagination.currentPage - 1) * this.pagination.itemsPerPage)) {
+          this.pagination.currentPage = 1
+          this.setNewRouteParams()
+          return this.reloadVideos()
+        }
+
         this.loadedPages[this.pagination.currentPage] = true
         this.pagination.totalItems = totalVideos
 
diff --git a/client/src/app/signup/signup.component.html b/client/src/app/signup/signup.component.html
index 8a30ab512..eb36b29f6 100644
--- a/client/src/app/signup/signup.component.html
+++ b/client/src/app/signup/signup.component.html
@@ -11,9 +11,9 @@
       <label for="username">Username</label>
       <input
         type="text" class="form-control" id="username" placeholder="Username"
-        formControlName="username"
+        formControlName="username" [ngClass]="{ 'input-error': formErrors['username'] }"
       >
-      <div *ngIf="formErrors.username" class="alert alert-danger">
+      <div *ngIf="formErrors.username" class="form-error">
         {{ formErrors.username }}
       </div>
     </div>
@@ -22,9 +22,9 @@
       <label for="email">Email</label>
       <input
         type="text" class="form-control" id="email" placeholder="Email"
-        formControlName="email"
+        formControlName="email" [ngClass]="{ 'input-error': formErrors['email'] }"
       >
-      <div *ngIf="formErrors.email" class="alert alert-danger">
+      <div *ngIf="formErrors.email" class="form-error">
         {{ formErrors.email }}
       </div>
     </div>
@@ -33,9 +33,9 @@
       <label for="password">Password</label>
       <input
         type="password" class="form-control" id="password" placeholder="Password"
-        formControlName="password"
+        formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }"
       >
-      <div *ngIf="formErrors.password" class="alert alert-danger">
+      <div *ngIf="formErrors.password" class="form-error">
         {{ formErrors.password }}
       </div>
     </div>
diff --git a/client/src/sass/_variables.scss b/client/src/sass/_variables.scss
index 0d655e85c..81dafdc19 100644
--- a/client/src/sass/_variables.scss
+++ b/client/src/sass/_variables.scss
@@ -7,6 +7,7 @@ $orange-color: #F1680D;
 
 $black-background: #000;
 $grey-background: #f6f2f2;
+$red-error: #FF0000;
 
 $expanded-horizontal-margins: 150px;
 $not-expanded-horizontal-margins: 30px;
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index 4c5c0202c..b860e1bf2 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -25,6 +25,16 @@ label {
   font-size: 15px;
 }
 
+.form-error {
+  display: block;
+  color: $red-error;
+  margin-top: 5px;
+}
+
+.input-error {
+  border-color: $red-error !important;
+}
+
 .glyphicon-black {
   color: black;
 }
-- 
cgit v1.2.3


From 332542bc6814bd16c2daf47dc776f9f4b126ec2e Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Wed, 6 Dec 2017 14:05:38 +0100
Subject: Add delete button to my videos

---
 .../account-videos/account-videos.component.html   |  7 ++++++-
 .../account-videos/account-videos.component.scss   | 18 +++++++++++++++---
 .../account-videos/account-videos.component.ts     | 22 ++++++++++++++++++++++
 .../shared/video-miniature.component.scss          |  2 +-
 client/src/assets/images/account/delete.svg        | 14 ++++++++++++++
 5 files changed, 58 insertions(+), 5 deletions(-)
 create mode 100644 client/src/assets/images/account/delete.svg

(limited to 'client')

diff --git a/client/src/app/account/account-videos/account-videos.component.html b/client/src/app/account/account-videos/account-videos.component.html
index 81bda9477..30db69429 100644
--- a/client/src/app/account/account-videos/account-videos.component.html
+++ b/client/src/app/account/account-videos/account-videos.component.html
@@ -13,7 +13,12 @@
       <span class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
     </div>
 
-    <a class="edit-button" [routerLink]="[ '/videos', video.id, '/edit' ]">
+    <a class="action-button action-button-delete" (click)="deleteVideo(video)">
+      <span class="icon icon-delete"></span>
+      Delete
+    </a>
+
+    <a class="action-button" [routerLink]="[ '/videos', video.id, '/edit' ]">
       <span class="icon icon-edit"></span>
       Edit
     </a>
diff --git a/client/src/app/account/account-videos/account-videos.component.scss b/client/src/app/account/account-videos/account-videos.component.scss
index c31497350..7ac25afc3 100644
--- a/client/src/app/account/account-videos/account-videos.component.scss
+++ b/client/src/app/account/account-videos/account-videos.component.scss
@@ -25,7 +25,7 @@
     }
   }
 
-  .edit-button {
+  .action-button {
     @include peertube-button-link;
 
     font-size: 15px;
@@ -33,15 +33,27 @@
     color: #585858;
     background-color: #E5E5E5;
 
-    .icon.icon-edit {
+    &.action-button-delete {
+      margin-right: 10px;
+    }
+
+    .icon.icon-edit, .icon.icon-delete {
       display: inline-block;
-      background: url('../../../assets/images/account/edit.svg') no-repeat;
+      background-repeat: no-repeat;
       background-size: contain;
       width: 21px;
       height: 21px;
       vertical-align: middle;
       position: relative;
       top: -2px;
+
+      &.icon-edit {
+        background-image: url('../../../assets/images/account/edit.svg');
+      }
+
+      &.icon-delete {
+        background-image: url('../../../assets/images/account/delete.svg');
+      }
     }
   }
 }
diff --git a/client/src/app/account/account-videos/account-videos.component.ts b/client/src/app/account/account-videos/account-videos.component.ts
index 1bc6c0a35..9c2cc2404 100644
--- a/client/src/app/account/account-videos/account-videos.component.ts
+++ b/client/src/app/account/account-videos/account-videos.component.ts
@@ -1,7 +1,9 @@
 import { Component, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { NotificationsService } from 'angular2-notifications'
+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({
@@ -16,6 +18,7 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit
   constructor (protected router: Router,
                protected route: ActivatedRoute,
                protected notificationsService: NotificationsService,
+               protected confirmService: ConfirmService,
                private videoService: VideoService) {
     super()
   }
@@ -27,4 +30,23 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit
   getVideosObservable () {
     return this.videoService.getMyVideos(this.pagination, this.sort)
   }
+
+  deleteVideo (video: Video) {
+    this.confirmService.confirm(`Do you really want to delete ${video.name}?`, 'Delete').subscribe(
+      res => {
+        if (res === false) return
+
+        this.videoService.removeVideo(video.id)
+          .subscribe(
+            status => {
+              this.notificationsService.success('Success', `Video ${video.name} deleted.`)
+              const index = this.videos.findIndex(v => v.id === video.id)
+              this.videos.splice(index, 1)
+            },
+
+            error => this.notificationsService.error('Error', error.text)
+          )
+      }
+    )
+  }
 }
diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.scss b/client/src/app/videos/video-list/shared/video-miniature.component.scss
index 658d7af9d..37e84897b 100644
--- a/client/src/app/videos/video-list/shared/video-miniature.component.scss
+++ b/client/src/app/videos/video-list/shared/video-miniature.component.scss
@@ -37,7 +37,7 @@
     }
 
     .video-miniature-account {
-      font-size: 12px;
+      font-size: 13px;
       color: #585858;
     }
   }
diff --git a/client/src/assets/images/account/delete.svg b/client/src/assets/images/account/delete.svg
new file mode 100644
index 000000000..67e9e2ce7
--- /dev/null
+++ b/client/src/assets/images/account/delete.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Artboard-4" transform="translate(-224.000000, -159.000000)">
+            <g id="25" transform="translate(224.000000, 159.000000)">
+                <path d="M5,7 L5,20.0081158 C5,21.1082031 5.89706013,22 7.00585866,22 L16.9941413,22 C18.1019465,22 19,21.1066027 19,20.0081158 L19,7" id="Path-296" stroke="#585858" stroke-width="2"></path>
+                <rect id="Rectangle-424" fill="#585858" x="2" y="4" width="20" height="2" rx="1"></rect>
+                <path d="M9,10.9970301 C9,10.4463856 9.44386482,10 10,10 C10.5522847,10 11,10.4530363 11,10.9970301 L11,17.0029699 C11,17.5536144 10.5561352,18 10,18 C9.44771525,18 9,17.5469637 9,17.0029699 L9,10.9970301 Z M13,10.9970301 C13,10.4463856 13.4438648,10 14,10 C14.5522847,10 15,10.4530363 15,10.9970301 L15,17.0029699 C15,17.5536144 14.5561352,18 14,18 C13.4477153,18 13,17.5469637 13,17.0029699 L13,10.9970301 Z" id="Combined-Shape" fill="#585858"></path>
+                <path d="M9,5 L9,2.99895656 C9,2.44724809 9.45097518,2 9.99077797,2 L14.009222,2 C14.5564136,2 15,2.44266033 15,2.99895656 L15,5" id="Path-33" stroke="#585858" stroke-width="2" stroke-linejoin="round"></path>
+            </g>
+        </g>
+    </g>
+</svg>
-- 
cgit v1.2.3


From 7d763d97497df1bbf7a01f61aa916d99a1338a33 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Wed, 6 Dec 2017 14:09:39 +0100
Subject: Add hover effect to buttons

---
 client/src/app/account/account-videos/account-videos.component.scss | 4 ++++
 client/src/sass/_mixins.scss                                        | 4 ++++
 2 files changed, 8 insertions(+)

(limited to 'client')

diff --git a/client/src/app/account/account-videos/account-videos.component.scss b/client/src/app/account/account-videos/account-videos.component.scss
index 7ac25afc3..e7fe662b1 100644
--- a/client/src/app/account/account-videos/account-videos.component.scss
+++ b/client/src/app/account/account-videos/account-videos.component.scss
@@ -33,6 +33,10 @@
     color: #585858;
     background-color: #E5E5E5;
 
+    &:hover {
+      background-color: #EFEFEF;
+    }
+
     &.action-button-delete {
       margin-right: 10px;
     }
diff --git a/client/src/sass/_mixins.scss b/client/src/sass/_mixins.scss
index e44cf064d..7f1063414 100644
--- a/client/src/sass/_mixins.scss
+++ b/client/src/sass/_mixins.scss
@@ -31,6 +31,10 @@
   background-color: $orange-color;
   padding: 0 17px 0 13px;
   cursor: pointer;
+
+  &:hover {
+    background-color: #F97D46;
+  }
 }
 
 @mixin peertube-button-link {
-- 
cgit v1.2.3


From ce0e281d46a7b574dcccb47958743656532bd312 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Wed, 6 Dec 2017 15:07:17 +0100
Subject: Client bulk delete

---
 .../account-videos/account-videos.component.html   |  36 ++++++--
 .../account-videos/account-videos.component.scss   | 102 +++++++++++++--------
 .../account-videos/account-videos.component.ts     |  49 +++++++++-
 client/src/assets/images/account/delete-grey.svg   |  14 +++
 client/src/assets/images/account/delete-white.svg  |  14 +++
 client/src/assets/images/account/delete.svg        |  14 ---
 client/src/sass/_mixins.scss                       |   4 +-
 client/src/sass/_variables.scss                    |   1 +
 8 files changed, 171 insertions(+), 63 deletions(-)
 create mode 100644 client/src/assets/images/account/delete-grey.svg
 create mode 100644 client/src/assets/images/account/delete-white.svg
 delete mode 100644 client/src/assets/images/account/delete.svg

(limited to 'client')

diff --git a/client/src/app/account/account-videos/account-videos.component.html b/client/src/app/account/account-videos/account-videos.component.html
index 30db69429..030c2f19c 100644
--- a/client/src/app/account/account-videos/account-videos.component.html
+++ b/client/src/app/account/account-videos/account-videos.component.html
@@ -5,7 +5,9 @@
   (scrolled)="onNearOfBottom()"
   (scrolledUp)="onNearOfTop()"
 >
-  <div class="video" *ngFor="let video of videos">
+  <div class="video" *ngFor="let video of videos; let i = index">
+    <input type="checkbox" [(ngModel)]="checkedVideos[video.id]" />
+
     <my-video-thumbnail [video]="video"></my-video-thumbnail>
 
     <div class="video-info">
@@ -13,14 +15,30 @@
       <span class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
     </div>
 
-    <a class="action-button action-button-delete" (click)="deleteVideo(video)">
-      <span class="icon icon-delete"></span>
-      Delete
-    </a>
+    <!-- Display only once -->
+    <div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0">
+      <div class="action-selection-mode-child">
+        <span class="action-button" (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>
+
+    <ng-template [ngIf]="isInSelectionMode() === false">
+      <span class="action-button action-button-delete" (click)="deleteVideo(video)">
+        <span class="icon icon-delete-grey"></span>
+        Delete
+      </span>
 
-    <a class="action-button" [routerLink]="[ '/videos', video.id, '/edit' ]">
-      <span class="icon icon-edit"></span>
-      Edit
-    </a>
+      <a class="action-button" [routerLink]="[ '/videos', 'edit', video.uuid ]">
+        <span class="icon icon-edit"></span>
+        Edit
+      </a>
+    </ng-template>
   </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
index e7fe662b1..e76e3f4e5 100644
--- a/client/src/app/account/account-videos/account-videos.component.scss
+++ b/client/src/app/account/account-videos/account-videos.component.scss
@@ -1,8 +1,74 @@
+.action-selection-mode {
+  width: 174px;
+
+  .action-selection-mode-child {
+    position: fixed;
+  }
+}
+
+.action-button {
+  @include peertube-button-link;
+
+  font-size: 15px;
+  font-weight: $font-semibold;
+  color: #585858;
+  background-color: #E5E5E5;
+
+  &:hover {
+    background-color: #EFEFEF;
+  }
+
+  &.action-button-delete {
+    margin-right: 10px;
+  }
+
+  &.action-button-delete-selection {
+    background-color: $orange-color;
+    color: #fff;
+
+    &:hover {
+      background-color: $orange-hoover-color;
+    }
+  }
+
+  .icon {
+    display: inline-block;
+    background-repeat: no-repeat;
+    background-size: contain;
+    width: 21px;
+    height: 21px;
+    vertical-align: middle;
+    position: relative;
+    top: -2px;
+
+    &.icon-edit {
+      background-image: url('../../../assets/images/account/edit.svg');
+    }
+
+    &.icon-delete-grey {
+      background-image: url('../../../assets/images/account/delete-grey.svg');
+    }
+
+    &.icon-delete-white {
+      background-image: url('../../../assets/images/account/delete-white.svg');
+    }
+  }
+}
+
 .video {
   display: flex;
   height: 130px;
   padding-bottom: 20px;
 
+  input[type=checkbox] {
+    margin-right: 20px;
+    outline: 0;
+  }
+
+  &:first-child {
+    margin-top: 47px;
+  }
+
   &:not(:last-child) {
     margin-bottom: 20px;
     border-bottom: 1px solid #C6C6C6;
@@ -24,40 +90,4 @@
       font-size: 13px;
     }
   }
-
-  .action-button {
-    @include peertube-button-link;
-
-    font-size: 15px;
-    font-weight: $font-semibold;
-    color: #585858;
-    background-color: #E5E5E5;
-
-    &:hover {
-      background-color: #EFEFEF;
-    }
-
-    &.action-button-delete {
-      margin-right: 10px;
-    }
-
-    .icon.icon-edit, .icon.icon-delete {
-      display: inline-block;
-      background-repeat: no-repeat;
-      background-size: contain;
-      width: 21px;
-      height: 21px;
-      vertical-align: middle;
-      position: relative;
-      top: -2px;
-
-      &.icon-edit {
-        background-image: url('../../../assets/images/account/edit.svg');
-      }
-
-      &.icon-delete {
-        background-image: url('../../../assets/images/account/delete.svg');
-      }
-    }
-  }
 }
diff --git a/client/src/app/account/account-videos/account-videos.component.ts b/client/src/app/account/account-videos/account-videos.component.ts
index 9c2cc2404..5f12cfce0 100644
--- a/client/src/app/account/account-videos/account-videos.component.ts
+++ b/client/src/app/account/account-videos/account-videos.component.ts
@@ -1,6 +1,9 @@
 import { Component, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { NotificationsService } from 'angular2-notifications'
+import 'rxjs/add/observable/from'
+import 'rxjs/add/operator/concatAll'
+import { Observable } from 'rxjs/Observable'
 import { ConfirmService } from '../../core/confirm'
 import { AbstractVideoList } from '../../shared/video/abstract-video-list'
 import { Video } from '../../shared/video/video.model'
@@ -14,6 +17,7 @@ import { VideoService } from '../../shared/video/video.service'
 export class AccountVideosComponent extends AbstractVideoList implements OnInit {
   titlePage = 'My videos'
   currentRoute = '/account/videos'
+  checkedVideos: { [ id: number ]: boolean } = {}
 
   constructor (protected router: Router,
                protected route: ActivatedRoute,
@@ -27,10 +31,47 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit
     super.ngOnInit()
   }
 
+  abortSelectionMode () {
+    this.checkedVideos = {}
+  }
+
+  isInSelectionMode () {
+    return Object.keys(this.checkedVideos).some(k => this.checkedVideos[k] === true)
+  }
+
   getVideosObservable () {
     return this.videoService.getMyVideos(this.pagination, this.sort)
   }
 
+  deleteSelectedVideos () {
+    const toDeleteVideosIds = Object.keys(this.checkedVideos)
+      .filter(k => this.checkedVideos[k] === true)
+      .map(k => parseInt(k, 10))
+
+    this.confirmService.confirm(`Do you really want to delete ${toDeleteVideosIds.length} videos?`, 'Delete').subscribe(
+      res => {
+        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.`),
+
+          err => this.notificationsService.error('Error', err.text)
+          )
+      }
+    )
+  }
+
   deleteVideo (video: Video) {
     this.confirmService.confirm(`Do you really want to delete ${video.name}?`, 'Delete').subscribe(
       res => {
@@ -40,8 +81,7 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit
           .subscribe(
             status => {
               this.notificationsService.success('Success', `Video ${video.name} deleted.`)
-              const index = this.videos.findIndex(v => v.id === video.id)
-              this.videos.splice(index, 1)
+              this.spliceVideosById(video.id)
             },
 
             error => this.notificationsService.error('Error', error.text)
@@ -49,4 +89,9 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit
       }
     )
   }
+
+  private spliceVideosById (id: number) {
+    const index = this.videos.findIndex(v => v.id === id)
+    this.videos.splice(index, 1)
+  }
 }
diff --git a/client/src/assets/images/account/delete-grey.svg b/client/src/assets/images/account/delete-grey.svg
new file mode 100644
index 000000000..67e9e2ce7
--- /dev/null
+++ b/client/src/assets/images/account/delete-grey.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Artboard-4" transform="translate(-224.000000, -159.000000)">
+            <g id="25" transform="translate(224.000000, 159.000000)">
+                <path d="M5,7 L5,20.0081158 C5,21.1082031 5.89706013,22 7.00585866,22 L16.9941413,22 C18.1019465,22 19,21.1066027 19,20.0081158 L19,7" id="Path-296" stroke="#585858" stroke-width="2"></path>
+                <rect id="Rectangle-424" fill="#585858" x="2" y="4" width="20" height="2" rx="1"></rect>
+                <path d="M9,10.9970301 C9,10.4463856 9.44386482,10 10,10 C10.5522847,10 11,10.4530363 11,10.9970301 L11,17.0029699 C11,17.5536144 10.5561352,18 10,18 C9.44771525,18 9,17.5469637 9,17.0029699 L9,10.9970301 Z M13,10.9970301 C13,10.4463856 13.4438648,10 14,10 C14.5522847,10 15,10.4530363 15,10.9970301 L15,17.0029699 C15,17.5536144 14.5561352,18 14,18 C13.4477153,18 13,17.5469637 13,17.0029699 L13,10.9970301 Z" id="Combined-Shape" fill="#585858"></path>
+                <path d="M9,5 L9,2.99895656 C9,2.44724809 9.45097518,2 9.99077797,2 L14.009222,2 C14.5564136,2 15,2.44266033 15,2.99895656 L15,5" id="Path-33" stroke="#585858" stroke-width="2" stroke-linejoin="round"></path>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/account/delete-white.svg b/client/src/assets/images/account/delete-white.svg
new file mode 100644
index 000000000..9c52de557
--- /dev/null
+++ b/client/src/assets/images/account/delete-white.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Artboard-4" transform="translate(-224.000000, -159.000000)">
+            <g id="25" transform="translate(224.000000, 159.000000)">
+                <path d="M5,7 L5,20.0081158 C5,21.1082031 5.89706013,22 7.00585866,22 L16.9941413,22 C18.1019465,22 19,21.1066027 19,20.0081158 L19,7" id="Path-296" stroke="#ffffff" stroke-width="2"></path>
+                <rect id="Rectangle-424" fill="#ffffff" x="2" y="4" width="20" height="2" rx="1"></rect>
+                <path d="M9,10.9970301 C9,10.4463856 9.44386482,10 10,10 C10.5522847,10 11,10.4530363 11,10.9970301 L11,17.0029699 C11,17.5536144 10.5561352,18 10,18 C9.44771525,18 9,17.5469637 9,17.0029699 L9,10.9970301 Z M13,10.9970301 C13,10.4463856 13.4438648,10 14,10 C14.5522847,10 15,10.4530363 15,10.9970301 L15,17.0029699 C15,17.5536144 14.5561352,18 14,18 C13.4477153,18 13,17.5469637 13,17.0029699 L13,10.9970301 Z" id="Combined-Shape" fill="#ffffff"></path>
+                <path d="M9,5 L9,2.99895656 C9,2.44724809 9.45097518,2 9.99077797,2 L14.009222,2 C14.5564136,2 15,2.44266033 15,2.99895656 L15,5" id="Path-33" stroke="#ffffff" stroke-width="2" stroke-linejoin="round"></path>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/account/delete.svg b/client/src/assets/images/account/delete.svg
deleted file mode 100644
index 67e9e2ce7..000000000
--- a/client/src/assets/images/account/delete.svg
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-224.000000, -159.000000)">
-            <g id="25" transform="translate(224.000000, 159.000000)">
-                <path d="M5,7 L5,20.0081158 C5,21.1082031 5.89706013,22 7.00585866,22 L16.9941413,22 C18.1019465,22 19,21.1066027 19,20.0081158 L19,7" id="Path-296" stroke="#585858" stroke-width="2"></path>
-                <rect id="Rectangle-424" fill="#585858" x="2" y="4" width="20" height="2" rx="1"></rect>
-                <path d="M9,10.9970301 C9,10.4463856 9.44386482,10 10,10 C10.5522847,10 11,10.4530363 11,10.9970301 L11,17.0029699 C11,17.5536144 10.5561352,18 10,18 C9.44771525,18 9,17.5469637 9,17.0029699 L9,10.9970301 Z M13,10.9970301 C13,10.4463856 13.4438648,10 14,10 C14.5522847,10 15,10.4530363 15,10.9970301 L15,17.0029699 C15,17.5536144 14.5561352,18 14,18 C13.4477153,18 13,17.5469637 13,17.0029699 L13,10.9970301 Z" id="Combined-Shape" fill="#585858"></path>
-                <path d="M9,5 L9,2.99895656 C9,2.44724809 9.45097518,2 9.99077797,2 L14.009222,2 C14.5564136,2 15,2.44266033 15,2.99895656 L15,5" id="Path-33" stroke="#585858" stroke-width="2" stroke-linejoin="round"></path>
-            </g>
-        </g>
-    </g>
-</svg>
diff --git a/client/src/sass/_mixins.scss b/client/src/sass/_mixins.scss
index 7f1063414..6a18f7a76 100644
--- a/client/src/sass/_mixins.scss
+++ b/client/src/sass/_mixins.scss
@@ -33,15 +33,15 @@
   cursor: pointer;
 
   &:hover {
-    background-color: #F97D46;
+    background-color: $orange-hoover-color;
   }
 }
 
 @mixin peertube-button-link {
   display: inline-block;
 
-  @include peertube-button;
   @include disable-default-a-behaviour;
+  @include peertube-button;
 }
 
 @mixin avatar ($size) {
diff --git a/client/src/sass/_variables.scss b/client/src/sass/_variables.scss
index 81dafdc19..cc1cee75b 100644
--- a/client/src/sass/_variables.scss
+++ b/client/src/sass/_variables.scss
@@ -4,6 +4,7 @@ $font-bold: 700;
 
 $grey-color: #555;
 $orange-color: #F1680D;
+$orange-hoover-color: #F97D46;
 
 $black-background: #000;
 $grey-background: #f6f2f2;
-- 
cgit v1.2.3


From b1fa3eba70dbd7d9e5b795ad251e293c88ebeee2 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Wed, 6 Dec 2017 17:15:59 +0100
Subject: Begin video watch design

---
 client/src/app/shared/account/account.model.ts     |  20 ++
 client/src/app/shared/shared.module.ts             |   3 +
 client/src/app/shared/users/user.model.ts          |   6 +-
 .../src/app/shared/video/abstract-video-list.html  |   2 +-
 client/src/app/shared/video/video-details.model.ts |   9 +-
 .../shared/video/video-miniature.component.html    |  17 ++
 .../shared/video/video-miniature.component.scss    |  44 +++
 .../app/shared/video/video-miniature.component.ts  |  17 ++
 .../src/app/shared/video/video-pagination.model.ts |   2 +-
 client/src/app/shared/video/video.model.ts         |   8 +-
 .../videos/+video-watch/video-watch.component.html | 233 +++++++---------
 .../videos/+video-watch/video-watch.component.scss | 308 ++++++---------------
 .../videos/+video-watch/video-watch.component.ts   |  43 ++-
 client/src/app/videos/video-list/index.ts          |   1 -
 client/src/app/videos/video-list/shared/index.ts   |   1 -
 .../shared/video-miniature.component.html          |  17 --
 .../shared/video-miniature.component.scss          |  44 ---
 .../video-list/shared/video-miniature.component.ts |  19 --
 client/src/app/videos/videos.module.ts             |   3 +-
 client/src/assets/images/video/dislike.svg         |  14 +
 client/src/assets/images/video/like.svg            |  15 +
 client/src/assets/images/video/more.svg            |  11 +
 client/src/assets/images/video/share.svg           |  16 ++
 23 files changed, 376 insertions(+), 477 deletions(-)
 create mode 100644 client/src/app/shared/account/account.model.ts
 create mode 100644 client/src/app/shared/video/video-miniature.component.html
 create mode 100644 client/src/app/shared/video/video-miniature.component.scss
 create mode 100644 client/src/app/shared/video/video-miniature.component.ts
 delete mode 100644 client/src/app/videos/video-list/shared/index.ts
 delete mode 100644 client/src/app/videos/video-list/shared/video-miniature.component.html
 delete mode 100644 client/src/app/videos/video-list/shared/video-miniature.component.scss
 delete mode 100644 client/src/app/videos/video-list/shared/video-miniature.component.ts
 create mode 100644 client/src/assets/images/video/dislike.svg
 create mode 100644 client/src/assets/images/video/like.svg
 create mode 100644 client/src/assets/images/video/more.svg
 create mode 100644 client/src/assets/images/video/share.svg

(limited to 'client')

diff --git a/client/src/app/shared/account/account.model.ts b/client/src/app/shared/account/account.model.ts
new file mode 100644
index 000000000..0b008188a
--- /dev/null
+++ b/client/src/app/shared/account/account.model.ts
@@ -0,0 +1,20 @@
+import { Account as ServerAccount } from '../../../../../shared/models/accounts/account.model'
+import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
+
+export class Account implements ServerAccount {
+  id: number
+  uuid: string
+  name: string
+  host: string
+  followingCount: number
+  followersCount: number
+  createdAt: Date
+  updatedAt: Date
+  avatar: Avatar
+
+  static GET_ACCOUNT_AVATAR_PATH (account: Account) {
+    if (account && account.avatar) return account.avatar.path
+
+    return API_URL + '/client/assets/images/default-avatar.png'
+  }
+}
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 86e1a380e..bd9aee345 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -20,6 +20,7 @@ import { RestExtractor, RestService } from './rest'
 import { UserService } from './users'
 import { VideoAbuseService } from './video-abuse'
 import { VideoBlacklistService } from './video-blacklist'
+import { VideoMiniatureComponent } from './video/video-miniature.component'
 import { VideoThumbnailComponent } from './video/video-thumbnail.component'
 import { VideoService } from './video/video.service'
 
@@ -44,6 +45,7 @@ import { VideoService } from './video/video.service'
   declarations: [
     LoaderComponent,
     VideoThumbnailComponent,
+    VideoMiniatureComponent,
     NumberFormatterPipe,
     FromNowPipe
   ],
@@ -66,6 +68,7 @@ import { VideoService } from './video/video.service'
 
     LoaderComponent,
     VideoThumbnailComponent,
+    VideoMiniatureComponent,
 
     NumberFormatterPipe,
     FromNowPipe
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts
index b1c323114..b4d13f37c 100644
--- a/client/src/app/shared/users/user.model.ts
+++ b/client/src/app/shared/users/user.model.ts
@@ -1,5 +1,5 @@
 import { hasUserRight, User as UserServerModel, UserRight, UserRole, VideoChannel } from '../../../../../shared'
-import { Account } from '../../../../../shared/models/accounts'
+import { Account } from '../account/account.model'
 
 export type UserConstructorHash = {
   id: number,
@@ -52,8 +52,6 @@ export class User implements UserServerModel {
   }
 
   getAvatarPath () {
-    if (this.account && this.account.avatar) return this.account.avatar.path
-
-    return API_URL + '/client/assets/images/default-avatar.png'
+    return Account.GET_ACCOUNT_AVATAR_PATH(this.account)
   }
 }
diff --git a/client/src/app/shared/video/abstract-video-list.html b/client/src/app/shared/video/abstract-video-list.html
index bd4f6b1f8..5d07a276b 100644
--- a/client/src/app/shared/video/abstract-video-list.html
+++ b/client/src/app/shared/video/abstract-video-list.html
@@ -12,7 +12,7 @@
   >
     <my-video-miniature
       class="ng-animate"
-      *ngFor="let video of videos" [video]="video" [user]="user" [currentSort]="sort"
+      *ngFor="let video of videos" [video]="video" [user]="user"
     >
     </my-video-miniature>
   </div>
diff --git a/client/src/app/shared/video/video-details.model.ts b/client/src/app/shared/video/video-details.model.ts
index 93c380b73..1a956da7c 100644
--- a/client/src/app/shared/video/video-details.model.ts
+++ b/client/src/app/shared/video/video-details.model.ts
@@ -1,3 +1,4 @@
+import { Account } from '../../../../../shared/models/accounts'
 import { Video } from '../../shared/video/video.model'
 import { AuthUser } from '../../core'
 import {
@@ -10,7 +11,7 @@ import {
 } from '../../../../../shared'
 
 export class VideoDetails extends Video implements VideoDetailsServerModel {
-  account: string
+  accountName: string
   by: string
   createdAt: Date
   updatedAt: Date
@@ -44,6 +45,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
   channel: VideoChannel
   privacy: VideoPrivacy
   privacyLabel: string
+  account: Account
 
   constructor (hash: VideoDetailsServerModel) {
     super(hash)
@@ -53,6 +55,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
     this.descriptionPath = hash.descriptionPath
     this.files = hash.files
     this.channel = hash.channel
+    this.account = hash.account
   }
 
   getAppropriateMagnetUri (actualDownloadSpeed = 0) {
@@ -71,7 +74,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
   }
 
   isRemovableBy (user: AuthUser) {
-    return user && this.isLocal === true && (this.account === user.username || user.hasRight(UserRight.REMOVE_ANY_VIDEO))
+    return user && this.isLocal === true && (this.accountName === user.username || user.hasRight(UserRight.REMOVE_ANY_VIDEO))
   }
 
   isBlackistableBy (user: AuthUser) {
@@ -79,6 +82,6 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
   }
 
   isUpdatableBy (user: AuthUser) {
-    return user && this.isLocal === true && user.username === this.account
+    return user && this.isLocal === true && user.username === this.accountName
   }
 }
diff --git a/client/src/app/shared/video/video-miniature.component.html b/client/src/app/shared/video/video-miniature.component.html
new file mode 100644
index 000000000..7ac017235
--- /dev/null
+++ b/client/src/app/shared/video/video-miniature.component.html
@@ -0,0 +1,17 @@
+<div class="video-miniature">
+  <my-video-thumbnail [video]="video" [nsfw]="isVideoNSFWForThisUser()"></my-video-thumbnail>
+
+  <div class="video-miniature-information">
+    <span class="video-miniature-name">
+      <a
+        class="video-miniature-name"
+        [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoNSFWForThisUser() }"
+      >
+          {{ video.name }}
+      </a>
+    </span>
+
+    <span class="video-miniature-created-at-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
+    <span class="video-miniature-account">{{ video.by }}</span>
+  </div>
+</div>
diff --git a/client/src/app/shared/video/video-miniature.component.scss b/client/src/app/shared/video/video-miniature.component.scss
new file mode 100644
index 000000000..37e84897b
--- /dev/null
+++ b/client/src/app/shared/video/video-miniature.component.scss
@@ -0,0 +1,44 @@
+.video-miniature {
+  display: inline-block;
+  padding-right: 15px;
+  margin-bottom: 30px;
+  height: 175px;
+  vertical-align: top;
+
+  .video-miniature-information {
+    width: 200px;
+    margin-top: 2px;
+    line-height: normal;
+
+    .video-miniature-name {
+      display: block;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      font-weight: bold;
+      transition: color 0.2s;
+      font-size: 16px;
+      font-weight: $font-semibold;
+      color: #000;
+
+      &:hover {
+        text-decoration: none;
+      }
+
+      &.blur-filter {
+        filter: blur(3px);
+        padding-left: 4px;
+      }
+    }
+
+    .video-miniature-created-at-views {
+      display: block;
+      font-size: 13px;
+    }
+
+    .video-miniature-account {
+      font-size: 13px;
+      color: #585858;
+    }
+  }
+}
diff --git a/client/src/app/shared/video/video-miniature.component.ts b/client/src/app/shared/video/video-miniature.component.ts
new file mode 100644
index 000000000..4d79a74bb
--- /dev/null
+++ b/client/src/app/shared/video/video-miniature.component.ts
@@ -0,0 +1,17 @@
+import { Component, Input } from '@angular/core'
+import { User } from '../users'
+import { Video } from './video.model'
+
+@Component({
+  selector: 'my-video-miniature',
+  styleUrls: [ './video-miniature.component.scss' ],
+  templateUrl: './video-miniature.component.html'
+})
+export class VideoMiniatureComponent {
+  @Input() user: User
+  @Input() video: Video
+
+  isVideoNSFWForThisUser () {
+    return this.video.isVideoNSFWForUser(this.user)
+  }
+}
diff --git a/client/src/app/shared/video/video-pagination.model.ts b/client/src/app/shared/video/video-pagination.model.ts
index 9e71769cb..e9db61596 100644
--- a/client/src/app/shared/video/video-pagination.model.ts
+++ b/client/src/app/shared/video/video-pagination.model.ts
@@ -1,5 +1,5 @@
 export interface VideoPagination {
   currentPage: number
   itemsPerPage: number
-  totalItems: number
+  totalItems?: number
 }
diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/video/video.model.ts
index 6929c8755..d86ef8f92 100644
--- a/client/src/app/shared/video/video.model.ts
+++ b/client/src/app/shared/video/video.model.ts
@@ -1,8 +1,9 @@
 import { Video as VideoServerModel } from '../../../../../shared'
 import { User } from '../'
+import { Account } from '../../../../../shared/models/accounts'
 
 export class Video implements VideoServerModel {
-  account: string
+  accountName: string
   by: string
   createdAt: Date
   updatedAt: Date
@@ -31,6 +32,7 @@ export class Video implements VideoServerModel {
   likes: number
   dislikes: number
   nsfw: boolean
+  account: Account
 
   private static createByString (account: string, serverHost: string) {
     return account + '@' + serverHost
@@ -52,7 +54,7 @@ export class Video implements VideoServerModel {
       absoluteAPIUrl = window.location.origin
     }
 
-    this.account = hash.account
+    this.accountName = hash.accountName
     this.createdAt = new Date(hash.createdAt.toString())
     this.categoryLabel = hash.categoryLabel
     this.category = hash.category
@@ -80,7 +82,7 @@ export class Video implements VideoServerModel {
     this.dislikes = hash.dislikes
     this.nsfw = hash.nsfw
 
-    this.by = Video.createByString(hash.account, hash.serverHost)
+    this.by = Video.createByString(hash.accountName, hash.serverHost)
   }
 
   isVideoNSFWForUser (user: User) {
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html
index aa1f2f77e..f31e82bff 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -1,18 +1,3 @@
-<div *ngIf="error" class="row">
-  <div class="alert alert-danger">
-    The video load seems to be abnormally long.
-    <ul>
-      <li>Maybe the server {{ video.serverHost }} is down :(</li>
-      <li>
-        If not, you can report an issue on
-        <a href="https://github.com/Chocobozzz/PeerTube/issues" title="Report an issue">
-          https://github.com/Chocobozzz/PeerTube/issues
-        </a>
-      </li>
-    </ul>
-  </div>
-</div>
-
 <div class="row">
   <!-- We need the video container for videojs so we just hide it -->
   <div [hidden]="videoNotFound" id="video-container">
@@ -23,167 +8,153 @@
 </div>
 
 <!-- Video information -->
-<div *ngIf="video !== null" id="video-info">
-  <div class="row video-name-views">
-    <div class="col-xs-8 col-md-8 video-name">
-      {{ video.name }}
-    </div>
-
-    <div class="col-xs-4 col-md-4 pull-right video-views">
-      {{ video.views}} views
-    </div>
-  </div>
+<div *ngIf="video" class="margin-content video-bottom">
+  <div class="video-info">
+    <div class="video-info-name-actions">
+      <div class="video-info-name">{{ video.name }}</div>
+
+      <div class="video-info-actions">
+        <div class="action-button">
+          <span
+              class="icon icon-like" title="Like this video"
+              [ngClass]="{ 'interactive': isUserLoggedIn(), 'activated': userRating === 'like' }" (click)="setLike()"
+          ></span>
+        </div>
 
-  <div class="row video-small-blocks">
-    <div class="col-xs-5 col-xs-3 col-md-3 video-small-block video-small-block-account">
-      <a class="option" title="Access to all videos of this user" [routerLink]="['/videos/list', { field: 'account', search: video.account }]">
-        <span class="glyphicon glyphicon-user"></span>
-        <span class="video-small-block-text">{{ video.by }}</span>
-      </a>
-    </div>
+        <div class="action-button">
+          <span
+            class="icon icon-dislike" title="Dislike this video"
+            [ngClass]="{ 'interactive': isUserLoggedIn(), 'activated': userRating === 'dislike' }" (click)="setDislike()"
+          ></span>
+        </div>
 
-    <div class="col-xs-2 col-md-3 video-small-block video-small-block-share">
-      <a class="option" (click)="showShareModal()" title="Share the video">
-        <span class="glyphicon glyphicon-share"></span>
-        <span class="hidden-xs video-small-block-text">Share</span>
-      </a>
-    </div>
+        <div (click)="showShareModal()" class="action-button">
+          <span class="icon icon-share"></span>
+          Share
+        </div>
 
-    <div class="col-xs-2 col-md-3 video-small-block video-small-block-more">
-      <div class="video-small-block-dropdown" dropdown dropup="true" placement="right">
-        <a class="option" title="Access to more options" dropdownToggle>
-          <span class="glyphicon glyphicon-option-horizontal"></span>
-          <span class="hidden-xs video-small-block-text">More</span>
-        </a>
-
-        <ul *dropdownMenu class="dropdown-menu" id="more-menu" role="menu" aria-labelledby="single-button">
-          <li *ngIf="canUserUpdateVideo()" role="menuitem">
-            <a class="dropdown-item" title="Update this video" href="#" [routerLink]="[ '/videos/edit', video.uuid ]">
-              <span class="glyphicon glyphicon-pencil"></span> Update
-            </a>
-          </li>
-
-          <li role="menuitem">
-            <a class="dropdown-item" title="Download the video" href="#" (click)="showDownloadModal($event)">
-              <span class="glyphicon glyphicon-download-alt"></span> Download
-            </a>
-          </li>
-
-          <li *ngIf="isUserLoggedIn()" role="menuitem">
-            <a class="dropdown-item" title="Report this video" href="#" (click)="showReportModal($event)">
-              <span class="glyphicon glyphicon-alert"></span> Report
-            </a>
-          </li>
-
-          <li *ngIf="isVideoRemovable()" role="menuitem">
-            <a class="dropdown-item" title="Delete this video" href="#" (click)="removeVideo($event)">
-              <span class="glyphicon glyphicon-remove"></span> Delete
-            </a>
-          </li>
-
-          <li *ngIf="isVideoBlacklistable()" role="menuitem">
-            <a class="dropdown-item" title="Blacklist this video" href="#" (click)="blacklistVideo($event)">
-              <span class="glyphicon glyphicon-eye-close"></span> Blacklist
-            </a>
-          </li>
-        </ul>
+        <div class="action-more" dropdown dropup="true" placement="right">
+          <div class="action-button" dropdownToggle>
+            <span class="icon icon-more"></span>
+          </div>
+
+          <ul *dropdownMenu class="dropdown-menu" id="more-menu" role="menu" aria-labelledby="single-button">
+            <li *ngIf="canUserUpdateVideo()" role="menuitem">
+              <a class="dropdown-item" title="Update this video" href="#" [routerLink]="[ '/videos/edit', video.uuid ]">
+                <span class="glyphicon glyphicon-pencil"></span> Update
+              </a>
+            </li>
+
+            <li role="menuitem">
+              <a class="dropdown-item" title="Download the video" href="#" (click)="showDownloadModal($event)">
+                <span class="glyphicon glyphicon-download-alt"></span> Download
+              </a>
+            </li>
+
+            <li *ngIf="isUserLoggedIn()" role="menuitem">
+              <a class="dropdown-item" title="Report this video" href="#" (click)="showReportModal($event)">
+                <span class="glyphicon glyphicon-alert"></span> Report
+              </a>
+            </li>
+
+            <li *ngIf="isVideoRemovable()" role="menuitem">
+              <a class="dropdown-item" title="Delete this video" href="#" (click)="removeVideo($event)">
+                <span class="glyphicon glyphicon-remove"></span> Delete
+              </a>
+            </li>
+
+            <li *ngIf="isVideoBlacklistable()" role="menuitem">
+              <a class="dropdown-item" title="Blacklist this video" href="#" (click)="blacklistVideo($event)">
+                <span class="glyphicon glyphicon-eye-close"></span> Blacklist
+              </a>
+            </li>
+          </ul>
+        </div>
       </div>
     </div>
 
-    <div class="col-xs-3 col-md-3 video-small-block video-small-block-rating">
-      <div class="video-small-block-like">
-        <span
-          class="glyphicon glyphicon-thumbs-up" title="Like this video"
-          [ngClass]="{ 'interactive': isUserLoggedIn(), 'activated': userRating === 'like' }" (click)="setLike()"
-        ></span>
-
-        <span class="video-small-block-text">
-          {{ video.likes }}
-        </span>
-      </div>
-
-      <div class="video-small-block-dislike">
-        <span
-          class="glyphicon glyphicon-thumbs-down" title="Dislike this video"
-          [ngClass]="{ 'interactive': isUserLoggedIn(), 'activated': userRating === 'dislike' }" (click)="setDislike()"
-        ></span>
+    <div class="video-info-date-views">
+      {{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views
+    </div>
 
-        <span class="video-small-block-text">
-          {{ video.dislikes }}
-        </span>
-      </div>
+    <div class="video-info-channel">
+      {{ video.channel.name }}
+      <!-- Here will be the subscribe button -->
     </div>
-  </div>
 
-  <div class="row video-details">
-    <div class="video-details-date-description col-xs-8 col-md-9">
-      <div class="video-details-date">
-        Published on {{ video.createdAt | date:'short' }}
-      </div>
+    <div class="video-info-by">
+      By {{ video.by }}
+      <img [src]="getAvatarPath()" alt="Account avatar" />
+    </div>
 
-      <div class="video-details-description" [innerHTML]="videoHTMLDescription"></div>
+    <div class="video-info-description">
+      <div class="video-info-description-html" [innerHTML]="videoHTMLDescription"></div>
 
-      <div class="video-details-description-more" *ngIf="completeDescriptionShown === false && video.description.length === 250" (click)="showMoreDescription()">
+      <div class="video-info-description-more" *ngIf="completeDescriptionShown === false && video.description.length === 250" (click)="showMoreDescription()">
         Show more
         <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-down"></span>
         <my-loader class="description-loading" [loading]="descriptionLoading"></my-loader>
       </div>
 
-      <div *ngIf="completeDescriptionShown === true" (click)="showLessDescription()" class="video-details-description-more">
+      <div *ngIf="completeDescriptionShown === true" (click)="showLessDescription()" class="video-info-description-more">
         Show less
         <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-up"></span>
       </div>
     </div>
 
-    <div class="video-details-attributes col-xs-4 col-md-3">
-      <div class="video-details-attribute">
-        <span class="video-details-attribute-label">
-          Privacy:
+    <div class="video-attributes">
+      <div class="video-attribute">
+        <span class="video-attribute-label">
+          Privacy
         </span>
-        <span class="video-details-attribute-value">
+        <span class="video-attribute-value">
           {{ video.privacyLabel }}
         </span>
       </div>
 
-      <div class="video-details-attribute">
-        <span class="video-details-attribute-label">
-          Category:
+      <div class="video-attribute">
+        <span class="video-attribute-label">
+          Category
         </span>
-        <span class="video-details-attribute-value">
+        <span class="video-attribute-value">
           {{ video.categoryLabel }}
         </span>
       </div>
 
-      <div class="video-details-attribute">
-        <span class="video-details-attribute-label">
-          Licence:
+      <div class="video-attribute">
+        <span class="video-attribute-label">
+          Licence
         </span>
-        <span class="video-details-attribute-value">
+        <span class="video-attribute-value">
           {{ video.licenceLabel }}
         </span>
       </div>
 
-      <div class="video-details-attribute">
-        <span class="video-details-attribute-label">
-          Language:
+      <div class="video-attribute">
+        <span class="video-attribute-label">
+          Language
         </span>
-        <span class="video-details-attribute-value">
+        <span class="video-attribute-value">
           {{ video.languageLabel }}
         </span>
       </div>
 
-      <div class="video-details-attribute">
-        <span class="video-details-attribute-label">
-          Tags:
+      <div class="video-attribute">
+        <span class="video-attribute-label">
+          Tags
         </span>
 
-        <div class="video-details-tags">
-          <a *ngFor="let tag of video.tags" [routerLink]="['/videos/list', { field: 'tags', search: tag }]" class="label label-primary">
-            {{ tag }}
-          </a>
-        </div>
+        <span class="video-attribute-value">
+          {{ getVideoTags() }}
+        </span>
       </div>
+    </div>
+
+  </div>
 
+  <div class="other-videos">
+    <div *ngFor="let video of otherVideos">
+      <my-video-miniature [video]="video" [user]="user"></my-video-miniature>
     </div>
   </div>
 </div>
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss
index 06c2de7c6..7bcfeb7c3 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.scss
+++ b/client/src/app/videos/+video-watch/video-watch.component.scss
@@ -17,167 +17,108 @@
   font-weight: bold;
 }
 
-#torrent-info {
-  font-size: 10px;
-  margin-top: 10px;
-  text-align: center;
-
-  div {
-    min-width: 60px;
-  }
-}
-
-#video-info {
-  .video-name-views {
-    font-weight: bold;
-    font-size: 18px;
-    min-height: $video-watch-title-height;
-    display: flex;
-    align-items: center;
-
-    .video-name {
-      padding-left: $video-watch-info-padding-left;
-    }
+.video-bottom {
+  margin-top: 40px;
+  display: flex;
 
-    .video-views {
-      text-align: right;
-      // Keep a symmetry with the video name
-      padding-right: $video-watch-info-padding-left
-    }
+  .video-info {
+    flex-grow: 1;
+    margin-right: 28px;
 
-  }
+    .video-info-name-actions {
+      display: flex;
+      align-items: center;
 
-  .video-small-blocks {
-    height: $video-watch-info-height;
-    color: $video-watch-info-color;
-    border-color: $video-watch-border-color;
-    border-width: 1px 0px;
-    border-style: solid;
+      .video-info-name {
+        font-size: 27px;
+        font-weight: $font-semibold;
+        flex-grow: 1;
+      }
 
-    .video-small-block {
-      height: $video-watch-info-height;
-      display: flex;
-      flex-direction: column;
-      justify-content: center;
-      text-align: center;
+      .video-info-actions {
+        .action-button {
+          @include peertube-button;
 
-      a {
-        cursor: pointer;
-        transition: color 0.3s;
-        white-space: nowrap;
-        overflow: hidden;
-        text-overflow: ellipsis;
+          font-size: 15px;
+          font-weight: $font-semibold;
+          color: #585858;
+          background-color: #E5E5E5;
+          display: inline-block;
+          padding: 0 10px 0 10px;
 
-        &, &:hover {
-          color: inherit;
-          text-decoration:none;
+          &:hover {
+            background-color: #EFEFEF;
+          }
         }
 
-        &:hover {
-          color: #000 !important;
+        .action-more {
+          display: inline-block;
         }
 
-        &:hover > .glyphicon {
-          opacity: 1 !important;
-        }
-      }
+        .icon {
+          display: inline-block;
+          background-repeat: no-repeat;
+          background-size: contain;
+          width: 21px;
+          height: 21px;
+          vertical-align: middle;
+          position: relative;
+          top: -2px;
 
-      .option .glyphicon {
-        font-size: 22px;
-        color: inherit;
-        opacity: 0.15;
-        margin-bottom: 10px;
-        transition: opacity 0.3s;
-      }
+          &.icon-like {
+            background-image: url('../../../assets/images/video/like.svg');
+          }
 
-      .video-small-block-text {
-        font-size: 15px;
-        font-weight: bold;
-      }
-    }
+          &.icon-dislike {
+            background-image: url('../../../assets/images/video/dislike.svg');
+          }
 
-    .video-small-block:not(:last-child) {
-      border-width: 0 1px 0 0;
-      border-color: $video-watch-border-color;
-      border-style: solid;
-    }
+          &.icon-share {
+            background-image: url('../../../assets/images/video/share.svg');
+          }
 
-    .video-small-block-account, .video-small-block-more {
-      a.option {
-        display: block;
-
-        .glyphicon {
-          display: block;
+          &.icon-more {
+            background-image: url('../../../assets/images/video/more.svg');
+          }
         }
       }
     }
 
-    .video-small-block-share, .video-small-block-more {
-      a.option {
-        display: block;
-
-        .glyphicon {
-          display: block;
-        }
-      }
+    .video-info-date-views {
+      font-size: 16px;
+      margin-bottom: 10px;
     }
 
-    .video-small-block-more .video-small-block-dropdown {
-      position: relative;
-
-      .dropdown-item .glyphicon {
-        margin-right: 5px;
-      }
+    .video-info-channel {
+      font-weight: $font-semibold;
+      font-size: 15px;
     }
 
-    .video-small-block-rating {
-
-      .video-small-block-like {
-        margin-bottom: 10px;
-      }
-
-      .video-small-block-text {
-        vertical-align: top;
-      }
-
-      .glyphicon {
-        font-size: 18px;
-        margin: 0 10px 0 0;
-        opacity: 0.3;
-      }
-
-      .interactive {
-        cursor: pointer;
-        transition: opacity, color 0.3s;
+    .video-info-by {
+      display: flex;
+      align-items: center;
+      font-size: 13px;
 
-        &.activated, &:hover {
-          opacity: 1;
-          color: #000;
-        }
+      img {
+        width: 16px;
+        height: 16px;
+        margin-left: 3px;
       }
     }
-  }
-
-  .video-details {
-    margin-top: 30px;
 
-    .video-details-date-description {
-      padding-left: $video-watch-info-padding-left;
+    .video-info-description {
+      margin: 20px 0;
+      font-size: 15px;
 
       .description-loading {
         display: inline-block;
       }
 
-      .video-details-date {
-        font-weight: bold;
-        margin-bottom: 30px;
-      }
-
-      .video-details-description-more {
+      .video-info-description-more {
         cursor: pointer;
-        margin-top: 15px;
-        font-weight: bold;
-        color: #acaeb7;
+        font-weight: $font-semibold;
+        color: #585858;
+        font-size: 14px;
 
         .glyphicon {
           position: relative;
@@ -186,109 +127,20 @@
       }
     }
 
-    .video-details-attributes {
-      font-weight: bold;
-      font-size: 12px;
-
-      .video-details-attribute {
-        display: flex;
-
-        .video-details-attribute-label {
-          color: $video-watch-info-color;
-          flex-basis: 60px;
-          flex-grow: 0;
-          flex-shrink: 0;
-          margin-right: 5px;
-        }
-      }
-    }
-
-    .video-details-tags {
-      display: flex;
-      flex-wrap: wrap;
-
-      a {
-        margin: 0 3px 3px 0;
-        font-size: 11px;
-      }
-    }
-  }
-
-  @media screen and (max-width: 800px) {
-    .video-name-views {
-      .video-name {
-        padding-left: 5px;
-        padding-right: 0px;
-      }
-
-      .video-views {
-        padding-left: 0px;
-        padding-right: 5px;
-      }
-    }
-
-    .video-small-blocks {
-      a, .video-small-block-text {
-        font-size: 13px !important;
-      }
-
-      .glyphicon {
-        font-size: 18px !important;
-      }
-
-      .video-small-block-account {
-        padding-left: 10px;
-        padding-right: 10px;
-      }
-    }
-
-    .video-details {
-      .video-details-date-description {
-        padding-left: 10px;
-        font-size: 13px !important;
-      }
-
-      .video-details-attributes {
-        font-size: 11px !important;
+    .video-attributes {
+      .video-attribute {
+        font-size: 13px;
+        display: block;
+        margin-bottom: 12px;
 
-        .video-details-attribute-label {
-          width: 50px;
+        .video-attribute-label {
+          width: 86px;
+          display: inline-block;
+          color: #585858;
+          font-weight: $font-bold;
         }
       }
     }
-  }
 
-  @media screen and (max-width: 500px) {
-    .video-name-views {
-      font-size: 16px !important;
-    }
-
-    // Keep the same hierarchy than max-width: 800px
-    .video-small-blocks {
-      a, .video-small-block-text {
-        font-size: 10px !important;
-      }
-
-      .video-small-block-account {
-        padding-left: 5px;
-        padding-right: 5px;
-      }
-    }
-
-    .video-details {
-      .video-details-date-description {
-        margin-bottom: 30px;
-        width: 100%;
-
-        .video-details-date {
-          margin-bottom: 15px;
-        }
-      }
-
-      .video-details-attributes {
-        padding-left: 10px;
-        padding-right: 10px;
-      }
-    }
   }
 }
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts
index 48842602e..3c6951403 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -10,6 +10,8 @@ import { UserVideoRateType, VideoRateType } from '../../../../../shared'
 import '../../../assets/player/peertube-videojs-plugin'
 import { AuthService, ConfirmService } from '../../core'
 import { VideoBlacklistService } from '../../shared'
+import { Account } from '../../shared/account/account.model'
+import { Video } from '../../shared/video/video.model'
 import { MarkdownService } from '../shared'
 import { VideoDownloadComponent } from './video-download.component'
 import { VideoReportComponent } from './video-report.component'
@@ -26,6 +28,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
   @ViewChild('videoShareModal') videoShareModal: VideoShareComponent
   @ViewChild('videoReportModal') videoReportModal: VideoReportComponent
 
+  otherVideos: Video[] = []
+
   error = false
   loading = false
   player: videojs.Player
@@ -57,6 +61,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
   ) {}
 
   ngOnInit () {
+    this.videoService.getVideos({ currentPage: 1, itemsPerPage: 5 }, '-createdAt')
+      .subscribe(
+        data => this.otherVideos = data.videos,
+
+    err => console.error(err)
+      )
+
     this.paramsSub = this.route.params.subscribe(routeParams => {
       let uuid = routeParams['uuid']
       this.videoService.getVideo(uuid).subscribe(
@@ -114,27 +125,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
                      )
   }
 
-  removeVideo (event: Event) {
-    event.preventDefault()
-
-    this.confirmService.confirm('Do you really want to delete this video?', 'Delete').subscribe(
-      res => {
-        if (res === false) return
-
-        this.videoService.removeVideo(this.video.id)
-                         .subscribe(
-                           status => {
-                             this.notificationsService.success('Success', `Video ${this.video.name} deleted.`)
-                             // Go back to the video-list.
-                             this.router.navigate(['/videos/list'])
-                           },
-
-                           error => this.notificationsService.error('Error', error.text)
-                          )
-      }
-    )
-  }
-
   blacklistVideo (event: Event) {
     event.preventDefault()
 
@@ -165,7 +155,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
   }
 
   showLessDescription () {
-
     this.updateVideoDescription(this.shortVideoDescription)
     this.completeDescriptionShown = false
   }
@@ -222,6 +211,16 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     return this.video.isBlackistableBy(this.authService.getUser())
   }
 
+  getAvatarPath () {
+    return Account.GET_ACCOUNT_AVATAR_PATH(this.video.account)
+  }
+
+  getVideoTags () {
+    if (!this.video || Array.isArray(this.video.tags) === false) return []
+
+    return this.video.tags.join(', ')
+  }
+
   private updateVideoDescription (description: string) {
     this.video.description = description
     this.setVideoDescriptionHTML()
diff --git a/client/src/app/videos/video-list/index.ts b/client/src/app/videos/video-list/index.ts
index 13024294e..5e7c7886c 100644
--- a/client/src/app/videos/video-list/index.ts
+++ b/client/src/app/videos/video-list/index.ts
@@ -1,4 +1,3 @@
 export * from './video-recently-added.component'
 export * from './video-trending.component'
 export * from './video-search.component'
-export * from './shared'
diff --git a/client/src/app/videos/video-list/shared/index.ts b/client/src/app/videos/video-list/shared/index.ts
deleted file mode 100644
index 2778f2d9e..000000000
--- a/client/src/app/videos/video-list/shared/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './video-miniature.component'
diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.html b/client/src/app/videos/video-list/shared/video-miniature.component.html
deleted file mode 100644
index 7ac017235..000000000
--- a/client/src/app/videos/video-list/shared/video-miniature.component.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<div class="video-miniature">
-  <my-video-thumbnail [video]="video" [nsfw]="isVideoNSFWForThisUser()"></my-video-thumbnail>
-
-  <div class="video-miniature-information">
-    <span class="video-miniature-name">
-      <a
-        class="video-miniature-name"
-        [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoNSFWForThisUser() }"
-      >
-          {{ video.name }}
-      </a>
-    </span>
-
-    <span class="video-miniature-created-at-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
-    <span class="video-miniature-account">{{ video.by }}</span>
-  </div>
-</div>
diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.scss b/client/src/app/videos/video-list/shared/video-miniature.component.scss
deleted file mode 100644
index 37e84897b..000000000
--- a/client/src/app/videos/video-list/shared/video-miniature.component.scss
+++ /dev/null
@@ -1,44 +0,0 @@
-.video-miniature {
-  display: inline-block;
-  padding-right: 15px;
-  margin-bottom: 30px;
-  height: 175px;
-  vertical-align: top;
-
-  .video-miniature-information {
-    width: 200px;
-    margin-top: 2px;
-    line-height: normal;
-
-    .video-miniature-name {
-      display: block;
-      overflow: hidden;
-      text-overflow: ellipsis;
-      white-space: nowrap;
-      font-weight: bold;
-      transition: color 0.2s;
-      font-size: 16px;
-      font-weight: $font-semibold;
-      color: #000;
-
-      &:hover {
-        text-decoration: none;
-      }
-
-      &.blur-filter {
-        filter: blur(3px);
-        padding-left: 4px;
-      }
-    }
-
-    .video-miniature-created-at-views {
-      display: block;
-      font-size: 13px;
-    }
-
-    .video-miniature-account {
-      font-size: 13px;
-      color: #585858;
-    }
-  }
-}
diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.ts b/client/src/app/videos/video-list/shared/video-miniature.component.ts
deleted file mode 100644
index e8fc8e911..000000000
--- a/client/src/app/videos/video-list/shared/video-miniature.component.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Component, Input } from '@angular/core'
-import { User } from '../../../shared'
-import { SortField } from '../../../shared/video/sort-field.type'
-import { Video } from '../../../shared/video/video.model'
-
-@Component({
-  selector: 'my-video-miniature',
-  styleUrls: [ './video-miniature.component.scss' ],
-  templateUrl: './video-miniature.component.html'
-})
-export class VideoMiniatureComponent {
-  @Input() currentSort: SortField
-  @Input() user: User
-  @Input() video: Video
-
-  isVideoNSFWForThisUser () {
-    return this.video.isVideoNSFWForUser(this.user)
-  }
-}
diff --git a/client/src/app/videos/videos.module.ts b/client/src/app/videos/videos.module.ts
index 8c8d52ad9..4b14d1da8 100644
--- a/client/src/app/videos/videos.module.ts
+++ b/client/src/app/videos/videos.module.ts
@@ -1,6 +1,6 @@
 import { NgModule } from '@angular/core'
 import { SharedModule } from '../shared'
-import { VideoMiniatureComponent, VideoSearchComponent } from './video-list'
+import { VideoSearchComponent } from './video-list'
 import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component'
 import { VideoTrendingComponent } from './video-list/video-trending.component'
 import { VideosRoutingModule } from './videos-routing.module'
@@ -17,7 +17,6 @@ import { VideosComponent } from './videos.component'
 
     VideoTrendingComponent,
     VideoRecentlyAddedComponent,
-    VideoMiniatureComponent,
     VideoSearchComponent
   ],
 
diff --git a/client/src/assets/images/video/dislike.svg b/client/src/assets/images/video/dislike.svg
new file mode 100644
index 000000000..56a7908fb
--- /dev/null
+++ b/client/src/assets/images/video/dislike.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g id="Artboard-4" transform="translate(-752.000000, -1090.000000)" stroke="#585858" stroke-width="2">
+            <g id="Extras" transform="translate(48.000000, 1046.000000)">
+                <g id="thumbs-down" transform="translate(704.000000, 44.000000)">
+                    <path d="M6,16 C6,18.5 6.5,21 8,21 L16.9938335,21 C17.5495239,21 18.1819788,20.5956028 18.4072817,20.0949295 L20.8562951,14.6526776 C21.7640882,12.6353595 20.7154925,11 18.5092545,11 L15.5,11 C15.5,11 18.5,5 15,5 C12.5,5 11.5,11 8,11 C6.5,11 6,13.5 6,16 Z" id="Path-188" stroke-linejoin="round" transform="translate(13.591488, 13.000000) scale(1, -1) translate(-13.591488, -13.000000) "></path>
+                    <path d="M4,4.5 C4,4.5 3,7 3,10 C3,13 4,15.5 4,15.5" id="Path-189" transform="translate(3.500000, 10.000000) scale(1, -1) translate(-3.500000, -10.000000) "></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/video/like.svg b/client/src/assets/images/video/like.svg
new file mode 100644
index 000000000..5ef6c7b31
--- /dev/null
+++ b/client/src/assets/images/video/like.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>thumbs-up</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g id="Artboard-4" transform="translate(-708.000000, -643.000000)" stroke="#585858" stroke-width="2">
+            <g id="256" transform="translate(708.000000, 643.000000)">
+                <path d="M6,14 C6,16.5 6.5,19 8,19 L16.9938335,19 C17.5495239,19 18.1819788,18.5956028 18.4072817,18.0949295 L20.8562951,12.6526776 C21.7640882,10.6353595 20.7154925,9 18.5092545,9 L15.5,9 C15.5,9 18.5,3 15,3 C12.5,3 11.5,9 8,9 C6.5,9 6,11.5 6,14 Z" id="Path-188" stroke-linejoin="round"></path>
+                <path d="M4,8.5 C4,8.5 3,11 3,14 C3,17 4,19.5 4,19.5" id="Path-189"></path>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/video/more.svg b/client/src/assets/images/video/more.svg
new file mode 100644
index 000000000..dea392136
--- /dev/null
+++ b/client/src/assets/images/video/more.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Artboard-4" transform="translate(-444.000000, -115.000000)" fill="#585858">
+            <g id="10" transform="translate(444.000000, 115.000000)">
+                <path d="M10,12 C10,10.8954305 10.8877296,10 12,10 C13.1045695,10 14,10.8877296 14,12 C14,13.1045695 13.1122704,14 12,14 C10.8954305,14 10,13.1122704 10,12 Z M17,12 C17,10.8954305 17.8877296,10 19,10 C20.1045695,10 21,10.8877296 21,12 C21,13.1045695 20.1122704,14 19,14 C17.8954305,14 17,13.1122704 17,12 Z M3,12 C3,10.8954305 3.88772964,10 5,10 C6.1045695,10 7,10.8877296 7,12 C7,13.1045695 6.11227036,14 5,14 C3.8954305,14 3,13.1122704 3,12 Z" id="Combined-Shape"></path>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/video/share.svg b/client/src/assets/images/video/share.svg
new file mode 100644
index 000000000..da0f43e81
--- /dev/null
+++ b/client/src/assets/images/video/share.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>share</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g id="Artboard-4" transform="translate(-312.000000, -203.000000)" stroke="#585858" stroke-width="2">
+            <g id="47" transform="translate(312.000000, 203.000000)">
+                <path d="M20,15 L20,18.0026083 C20,19.1057373 19.1073772,20 18.0049107,20 L5.99508929,20 C4.8932319,20 4,19.1073772 4,18.0049107 L4,5.99508929 C4,4.8932319 4.89585781,4 5.9973917,4 L9,4" id="Rectangle-460"></path>
+                <polyline id="Path-93" stroke-linejoin="round" points="13 4 20.0207973 4 20.0207973 11.0191059"></polyline>
+                <path d="M19,5 L12,12" id="Path-94" stroke-linejoin="round"></path>
+            </g>
+        </g>
+    </g>
+</svg>
-- 
cgit v1.2.3


From 6a9e1d42f878c55ac5e2af8a1c98e6fe28a04f36 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Wed, 6 Dec 2017 18:04:40 +0100
Subject: Add likes/dislikes bar

---
 client/src/app/shared/video/video-details.model.ts |  5 +++++
 .../videos/+video-watch/video-watch.component.html | 10 ++++++++--
 .../videos/+video-watch/video-watch.component.scss | 23 +++++++++++++++++++---
 3 files changed, 33 insertions(+), 5 deletions(-)

(limited to 'client')

diff --git a/client/src/app/shared/video/video-details.model.ts b/client/src/app/shared/video/video-details.model.ts
index 1a956da7c..b96f8f6c8 100644
--- a/client/src/app/shared/video/video-details.model.ts
+++ b/client/src/app/shared/video/video-details.model.ts
@@ -46,6 +46,8 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
   privacy: VideoPrivacy
   privacyLabel: string
   account: Account
+  likesPercent: number
+  dislikesPercent: number
 
   constructor (hash: VideoDetailsServerModel) {
     super(hash)
@@ -56,6 +58,9 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
     this.files = hash.files
     this.channel = hash.channel
     this.account = hash.account
+
+    this.likesPercent = (this.likes / (this.likes + this.dislikes)) * 100
+    this.dislikesPercent = (this.dislikes / (this.likes + this.dislikes)) * 100
   }
 
   getAppropriateMagnetUri (actualDownloadSpeed = 0) {
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html
index f31e82bff..b17392ff1 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -73,8 +73,14 @@
       </div>
     </div>
 
-    <div class="video-info-date-views">
-      {{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views
+    <div class="video-info-date-views-bar">
+      <div class="video-info-date-views">
+        {{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views
+      </div>
+
+      <div class="video-info-likes-dislikes-bar">
+        <div class="likes-bar" [ngStyle]="{ 'width.%': video.likesPercent }"></div>
+      </div>
     </div>
 
     <div class="video-info-channel">
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss
index 7bcfeb7c3..5064ceb95 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.scss
+++ b/client/src/app/videos/+video-watch/video-watch.component.scss
@@ -84,9 +84,26 @@
       }
     }
 
-    .video-info-date-views {
-      font-size: 16px;
-      margin-bottom: 10px;
+    .video-info-date-views-bar {
+      display: flex;
+
+      .video-info-date-views {
+        font-size: 16px;
+        margin-bottom: 10px;
+        flex-grow: 1;
+      }
+
+      .video-info-likes-dislikes-bar {
+        height: 5px;
+        width: 186px;
+        background-color: #E5E5E5;
+        margin-top: 25px;
+
+        .likes-bar {
+          height: 100%;
+          background-color: #39CC0B;
+        }
+      }
     }
 
     .video-info-channel {
-- 
cgit v1.2.3


From 41c3dfac99c41d2daec2a55554bb517ed5d59fc4 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Wed, 6 Dec 2017 18:10:57 +0100
Subject: Design other videos in watch video page

---
 .../app/videos/+video-watch/video-watch.component.html   |  4 ++++
 .../app/videos/+video-watch/video-watch.component.scss   | 16 ++++++++++++++++
 2 files changed, 20 insertions(+)

(limited to 'client')

diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html
index b17392ff1..88d89f9e4 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -159,6 +159,10 @@
   </div>
 
   <div class="other-videos">
+    <div class="title-page title-page-single">
+      Other videos
+    </div>
+
     <div *ngFor="let video of otherVideos">
       <my-video-miniature [video]="video" [user]="user"></my-video-miniature>
     </div>
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss
index 5064ceb95..d7f47ed75 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.scss
+++ b/client/src/app/videos/+video-watch/video-watch.component.scss
@@ -158,6 +158,22 @@
         }
       }
     }
+  }
+
+  .other-videos {
+    .title-page {
+      margin-top: 0;
+    }
+
+    /deep/ .video-miniature {
+      display: flex;
+      height: 100%;
+      margin-bottom: 20px;
 
+      .video-miniature-information {
+        margin-left: 10px;
+      }
+    }
   }
 }
+
-- 
cgit v1.2.3


From 7b272fd73f1ea67e83c1924f2cc33503b8759811 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Thu, 7 Dec 2017 10:02:01 +0100
Subject: Fix dropdown menu in video watch

---
 .../account-change-password.component.scss         |  2 +
 .../account-details/account-details.component.scss |  1 +
 .../account-videos/account-videos.component.scss   |  2 +-
 client/src/app/header/header.component.scss        |  5 +-
 client/src/app/login/login.component.scss          |  1 +
 client/src/app/signup/signup.component.scss        |  1 +
 .../videos/+video-watch/video-watch.component.html | 34 ++------
 .../videos/+video-watch/video-watch.component.scss | 92 +++++++++++++++-------
 client/src/assets/images/account/edit.svg          | 15 ----
 client/src/assets/images/global/edit.svg           | 15 ++++
 client/src/assets/images/video/alert.svg           | 16 ++++
 client/src/assets/images/video/dislike-grey.svg    | 14 ++++
 client/src/assets/images/video/dislike-white.svg   | 14 ++++
 client/src/assets/images/video/dislike.svg         | 14 ----
 client/src/assets/images/video/download.svg        | 16 ++++
 client/src/assets/images/video/eye-closed.svg      | 18 +++++
 client/src/assets/images/video/like-grey.svg       | 15 ++++
 client/src/assets/images/video/like-white.svg      | 15 ++++
 client/src/assets/images/video/like.svg            | 15 ----
 client/src/sass/_mixins.scss                       | 24 ++++--
 client/src/sass/_variables.scss                    | 12 +--
 client/src/sass/application.scss                   | 14 ++++
 22 files changed, 238 insertions(+), 117 deletions(-)
 delete mode 100644 client/src/assets/images/account/edit.svg
 create mode 100644 client/src/assets/images/global/edit.svg
 create mode 100644 client/src/assets/images/video/alert.svg
 create mode 100644 client/src/assets/images/video/dislike-grey.svg
 create mode 100644 client/src/assets/images/video/dislike-white.svg
 delete mode 100644 client/src/assets/images/video/dislike.svg
 create mode 100644 client/src/assets/images/video/download.svg
 create mode 100644 client/src/assets/images/video/eye-closed.svg
 create mode 100644 client/src/assets/images/video/like-grey.svg
 create mode 100644 client/src/assets/images/video/like-white.svg
 delete mode 100644 client/src/assets/images/video/like.svg

(limited to 'client')

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
index 75827abbf..7a4fdb34d 100644
--- 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
@@ -9,6 +9,8 @@ input[type=password] {
 
 input[type=submit] {
   @include peertube-button;
+  @include orange-button;
+
   margin-top: 15px;
 }
 
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
index 687166d9e..5c369f968 100644
--- 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
@@ -6,6 +6,7 @@ label {
 
 input[type=submit] {
   @include peertube-button;
+  @include orange-button;
 
   display: block;
   margin-top: 15px;
diff --git a/client/src/app/account/account-videos/account-videos.component.scss b/client/src/app/account/account-videos/account-videos.component.scss
index e76e3f4e5..04aaa8e89 100644
--- a/client/src/app/account/account-videos/account-videos.component.scss
+++ b/client/src/app/account/account-videos/account-videos.component.scss
@@ -42,7 +42,7 @@
     top: -2px;
 
     &.icon-edit {
-      background-image: url('../../../assets/images/account/edit.svg');
+      background-image: url('../../../assets/images/global/edit.svg');
     }
 
     &.icon-delete-grey {
diff --git a/client/src/app/header/header.component.scss b/client/src/app/header/header.component.scss
index 7ba8ef26c..e7761a9df 100644
--- a/client/src/app/header/header.component.scss
+++ b/client/src/app/header/header.component.scss
@@ -10,7 +10,7 @@
 
 .icon.icon-search {
   display: inline-block;
-  background: url('../../../assets/images/header/search.svg') no-repeat;
+  background: url('../../assets/images/header/search.svg') no-repeat;
   background-size: contain;
   width: 25px;
   height: 21px;
@@ -24,12 +24,13 @@
 
 .upload-button {
   @include peertube-button-link;
+  @include orange-button;
 
   margin-right: 25px;
 
   .icon.icon-upload {
     display: inline-block;
-    background: url('../../../assets/images/header/upload.svg') no-repeat;
+    background: url('../../assets/images/header/upload.svg') no-repeat;
     background-size: contain;
     width: 22px;
     height: 24px;
diff --git a/client/src/app/login/login.component.scss b/client/src/app/login/login.component.scss
index fd6981c59..3b4326de4 100644
--- a/client/src/app/login/login.component.scss
+++ b/client/src/app/login/login.component.scss
@@ -5,4 +5,5 @@ input:not([type=submit]) {
 
 input[type=submit] {
   @include peertube-button;
+  @include orange-button;
 }
diff --git a/client/src/app/signup/signup.component.scss b/client/src/app/signup/signup.component.scss
index fd6981c59..3b4326de4 100644
--- a/client/src/app/signup/signup.component.scss
+++ b/client/src/app/signup/signup.component.scss
@@ -5,4 +5,5 @@ input:not([type=submit]) {
 
 input[type=submit] {
   @include peertube-button;
+  @include orange-button;
 }
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html
index 88d89f9e4..583da4685 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -14,18 +14,12 @@
       <div class="video-info-name">{{ video.name }}</div>
 
       <div class="video-info-actions">
-        <div class="action-button">
-          <span
-              class="icon icon-like" title="Like this video"
-              [ngClass]="{ 'interactive': isUserLoggedIn(), 'activated': userRating === 'like' }" (click)="setLike()"
-          ></span>
+        <div *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'like' }" class="action-button">
+          <span class="icon icon-like" title="Like this video" (click)="setLike()"></span>
         </div>
 
-        <div class="action-button">
-          <span
-            class="icon icon-dislike" title="Dislike this video"
-            [ngClass]="{ 'interactive': isUserLoggedIn(), 'activated': userRating === 'dislike' }" (click)="setDislike()"
-          ></span>
+        <div *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" class="action-button">
+          <span class="icon icon-dislike" title="Dislike this video" (click)="setDislike()"></span>
         </div>
 
         <div (click)="showShareModal()" class="action-button">
@@ -39,33 +33,21 @@
           </div>
 
           <ul *dropdownMenu class="dropdown-menu" id="more-menu" role="menu" aria-labelledby="single-button">
-            <li *ngIf="canUserUpdateVideo()" role="menuitem">
-              <a class="dropdown-item" title="Update this video" href="#" [routerLink]="[ '/videos/edit', video.uuid ]">
-                <span class="glyphicon glyphicon-pencil"></span> Update
-              </a>
-            </li>
-
             <li role="menuitem">
               <a class="dropdown-item" title="Download the video" href="#" (click)="showDownloadModal($event)">
-                <span class="glyphicon glyphicon-download-alt"></span> Download
+                <span class="icon icon-download"></span> Download
               </a>
             </li>
 
             <li *ngIf="isUserLoggedIn()" role="menuitem">
               <a class="dropdown-item" title="Report this video" href="#" (click)="showReportModal($event)">
-                <span class="glyphicon glyphicon-alert"></span> Report
-              </a>
-            </li>
-
-            <li *ngIf="isVideoRemovable()" role="menuitem">
-              <a class="dropdown-item" title="Delete this video" href="#" (click)="removeVideo($event)">
-                <span class="glyphicon glyphicon-remove"></span> Delete
+                <span class="icon icon-alert"></span> Report
               </a>
             </li>
 
             <li *ngIf="isVideoBlacklistable()" role="menuitem">
               <a class="dropdown-item" title="Blacklist this video" href="#" (click)="blacklistVideo($event)">
-                <span class="glyphicon glyphicon-eye-close"></span> Blacklist
+                <span class="icon icon-blacklist"></span> Blacklist
               </a>
             </li>
           </ul>
@@ -78,7 +60,7 @@
         {{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views
       </div>
 
-      <div class="video-info-likes-dislikes-bar">
+      <div *ngIf="video.likes !== 0 || video.dislikes !== 0" class="video-info-likes-dislikes-bar">
         <div class="likes-bar" [ngStyle]="{ 'width.%': video.likesPercent }"></div>
       </div>
     </div>
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss
index d7f47ed75..3f36410f4 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.scss
+++ b/client/src/app/videos/+video-watch/video-watch.component.scss
@@ -6,6 +6,11 @@
   #video-element {
     width: 888px;
     height: 500px;
+
+    // VideoJS create an inner video player
+    video {
+      outline: 0;
+    }
   }
 }
 
@@ -38,47 +43,78 @@
       .video-info-actions {
         .action-button {
           @include peertube-button;
+          @include grey-button;
 
           font-size: 15px;
           font-weight: $font-semibold;
-          color: #585858;
-          background-color: #E5E5E5;
           display: inline-block;
           padding: 0 10px 0 10px;
 
-          &:hover {
-            background-color: #EFEFEF;
+          .icon {
+            display: inline-block;
+            background-repeat: no-repeat;
+            background-size: contain;
+            width: 21px;
+            height: 21px;
+            vertical-align: middle;
+            position: relative;
+            top: -2px;
+
+            &.icon-like {
+              background-image: url('../../../assets/images/video/like-grey.svg');
+            }
+
+            &.icon-dislike {
+              background-image: url('../../../assets/images/video/dislike-grey.svg');
+            }
+
+            &.icon-share {
+              background-image: url('../../../assets/images/video/share.svg');
+            }
+
+            &.icon-more {
+              background-image: url('../../../assets/images/video/more.svg');
+            }
           }
-        }
 
-        .action-more {
-          display: inline-block;
-        }
+          &.activated {
+            @include orange-button;
 
-        .icon {
-          display: inline-block;
-          background-repeat: no-repeat;
-          background-size: contain;
-          width: 21px;
-          height: 21px;
-          vertical-align: middle;
-          position: relative;
-          top: -2px;
-
-          &.icon-like {
-            background-image: url('../../../assets/images/video/like.svg');
-          }
+            .icon-like {
+              background-image: url('../../../assets/images/video/like-white.svg');
+            }
 
-          &.icon-dislike {
-            background-image: url('../../../assets/images/video/dislike.svg');
+            .icon-dislike {
+              background-image: url('../../../assets/images/video/dislike-white.svg');
+            }
           }
+        }
 
-          &.icon-share {
-            background-image: url('../../../assets/images/video/share.svg');
-          }
+        .action-more {
+          display: inline-block;
 
-          &.icon-more {
-            background-image: url('../../../assets/images/video/more.svg');
+          .dropdown-menu .icon {
+            display: inline-block;
+            background-repeat: no-repeat;
+            background-size: contain;
+            width: 21px;
+            height: 21px;
+            vertical-align: middle;
+            margin-right: 5px;
+            position: relative;
+            top: -1px;
+
+            &.icon-download {
+              background-image: url('../../../assets/images/video/download.svg');
+            }
+
+            &.icon-alert {
+              background-image: url('../../../assets/images/video/alert.svg');
+            }
+
+            &.icon-blacklist {
+              background-image: url('../../../assets/images/video/eye-closed.svg');
+            }
           }
         }
       }
diff --git a/client/src/assets/images/account/edit.svg b/client/src/assets/images/account/edit.svg
deleted file mode 100644
index 23ece68f1..000000000
--- a/client/src/assets/images/account/edit.svg
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
-    <title>edit</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-48.000000, -203.000000)" stroke="#585858" stroke-width="2">
-            <g id="41" transform="translate(48.000000, 203.000000)">
-                <path d="M3,21.0000003 L3,17 L15.8898356,4.11016442 C17.0598483,2.9401517 18.9638992,2.94723715 20.1306896,4.11402752 L19.9181432,3.90148112 C21.0902894,5.07362738 21.0882407,6.97202708 19.9174652,8.1377941 L7,21.0000003 L3,21.0000003 Z" id="Path-74" stroke-linecap="round" stroke-linejoin="round"></path>
-                <path d="M14.5,5.5 L18.5,9.5" id="Path-75"></path>
-            </g>
-        </g>
-    </g>
-</svg>
diff --git a/client/src/assets/images/global/edit.svg b/client/src/assets/images/global/edit.svg
new file mode 100644
index 000000000..23ece68f1
--- /dev/null
+++ b/client/src/assets/images/global/edit.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>edit</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Artboard-4" transform="translate(-48.000000, -203.000000)" stroke="#585858" stroke-width="2">
+            <g id="41" transform="translate(48.000000, 203.000000)">
+                <path d="M3,21.0000003 L3,17 L15.8898356,4.11016442 C17.0598483,2.9401517 18.9638992,2.94723715 20.1306896,4.11402752 L19.9181432,3.90148112 C21.0902894,5.07362738 21.0882407,6.97202708 19.9174652,8.1377941 L7,21.0000003 L3,21.0000003 Z" id="Path-74" stroke-linecap="round" stroke-linejoin="round"></path>
+                <path d="M14.5,5.5 L18.5,9.5" id="Path-75"></path>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/video/alert.svg b/client/src/assets/images/video/alert.svg
new file mode 100644
index 000000000..6d3af029f
--- /dev/null
+++ b/client/src/assets/images/video/alert.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>alert</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Artboard-4" transform="translate(-48.000000, -467.000000)">
+            <g id="161" transform="translate(48.000000, 467.000000)">
+                <path d="M12.8715755,3.50973876 L12,1.96027114 L11.1284245,3.50973876 L2.12842446,19.5097388 L1.29015252,21 L3,21 L21,21 L22.7098475,21 L21.8715755,19.5097388 L12.8715755,3.50973876 Z" id="Triangle-2" stroke="#585858" stroke-width="2" stroke-linejoin="round"></path>
+                <path d="M12,17.75 C12.6903559,17.75 13.25,17.1903559 13.25,16.5 C13.25,15.8096441 12.6903559,15.25 12,15.25 C11.3096441,15.25 10.75,15.8096441 10.75,16.5 C10.75,17.1903559 11.3096441,17.75 12,17.75 Z" id="Oval-8" fill="#585858"></path>
+                <rect id="Rectangle-3" fill="#585858" x="11" y="9" width="2" height="5" rx="1"></rect>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/video/dislike-grey.svg b/client/src/assets/images/video/dislike-grey.svg
new file mode 100644
index 000000000..56a7908fb
--- /dev/null
+++ b/client/src/assets/images/video/dislike-grey.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g id="Artboard-4" transform="translate(-752.000000, -1090.000000)" stroke="#585858" stroke-width="2">
+            <g id="Extras" transform="translate(48.000000, 1046.000000)">
+                <g id="thumbs-down" transform="translate(704.000000, 44.000000)">
+                    <path d="M6,16 C6,18.5 6.5,21 8,21 L16.9938335,21 C17.5495239,21 18.1819788,20.5956028 18.4072817,20.0949295 L20.8562951,14.6526776 C21.7640882,12.6353595 20.7154925,11 18.5092545,11 L15.5,11 C15.5,11 18.5,5 15,5 C12.5,5 11.5,11 8,11 C6.5,11 6,13.5 6,16 Z" id="Path-188" stroke-linejoin="round" transform="translate(13.591488, 13.000000) scale(1, -1) translate(-13.591488, -13.000000) "></path>
+                    <path d="M4,4.5 C4,4.5 3,7 3,10 C3,13 4,15.5 4,15.5" id="Path-189" transform="translate(3.500000, 10.000000) scale(1, -1) translate(-3.500000, -10.000000) "></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/video/dislike-white.svg b/client/src/assets/images/video/dislike-white.svg
new file mode 100644
index 000000000..cfc6eaa1f
--- /dev/null
+++ b/client/src/assets/images/video/dislike-white.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g id="Artboard-4" transform="translate(-752.000000, -1090.000000)" stroke="#ffffff" stroke-width="2">
+            <g id="Extras" transform="translate(48.000000, 1046.000000)">
+                <g id="thumbs-down" transform="translate(704.000000, 44.000000)">
+                    <path d="M6,16 C6,18.5 6.5,21 8,21 L16.9938335,21 C17.5495239,21 18.1819788,20.5956028 18.4072817,20.0949295 L20.8562951,14.6526776 C21.7640882,12.6353595 20.7154925,11 18.5092545,11 L15.5,11 C15.5,11 18.5,5 15,5 C12.5,5 11.5,11 8,11 C6.5,11 6,13.5 6,16 Z" id="Path-188" stroke-linejoin="round" transform="translate(13.591488, 13.000000) scale(1, -1) translate(-13.591488, -13.000000) "></path>
+                    <path d="M4,4.5 C4,4.5 3,7 3,10 C3,13 4,15.5 4,15.5" id="Path-189" transform="translate(3.500000, 10.000000) scale(1, -1) translate(-3.500000, -10.000000) "></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/video/dislike.svg b/client/src/assets/images/video/dislike.svg
deleted file mode 100644
index 56a7908fb..000000000
--- a/client/src/assets/images/video/dislike.svg
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
-        <g id="Artboard-4" transform="translate(-752.000000, -1090.000000)" stroke="#585858" stroke-width="2">
-            <g id="Extras" transform="translate(48.000000, 1046.000000)">
-                <g id="thumbs-down" transform="translate(704.000000, 44.000000)">
-                    <path d="M6,16 C6,18.5 6.5,21 8,21 L16.9938335,21 C17.5495239,21 18.1819788,20.5956028 18.4072817,20.0949295 L20.8562951,14.6526776 C21.7640882,12.6353595 20.7154925,11 18.5092545,11 L15.5,11 C15.5,11 18.5,5 15,5 C12.5,5 11.5,11 8,11 C6.5,11 6,13.5 6,16 Z" id="Path-188" stroke-linejoin="round" transform="translate(13.591488, 13.000000) scale(1, -1) translate(-13.591488, -13.000000) "></path>
-                    <path d="M4,4.5 C4,4.5 3,7 3,10 C3,13 4,15.5 4,15.5" id="Path-189" transform="translate(3.500000, 10.000000) scale(1, -1) translate(-3.500000, -10.000000) "></path>
-                </g>
-            </g>
-        </g>
-    </g>
-</svg>
diff --git a/client/src/assets/images/video/download.svg b/client/src/assets/images/video/download.svg
new file mode 100644
index 000000000..5b0cca5ef
--- /dev/null
+++ b/client/src/assets/images/video/download.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>download</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g id="Artboard-4" transform="translate(-180.000000, -291.000000)" stroke="#585858" stroke-width="2">
+            <g id="84" transform="translate(180.000000, 291.000000)">
+                <path d="M12,3 L12,15" id="Path-58"></path>
+                <polyline id="Path-59" stroke-linejoin="round" transform="translate(12.000000, 14.000000) rotate(-270.000000) translate(-12.000000, -14.000000) " points="9 8 15 14 9 20"></polyline>
+                <path d="M3,18 L3,20.0590859 C3,20.6127331 3.44494889,21.0615528 3.99340349,21.0615528 L20.0067018,21.0615528 C20.5553434,21.0615528 21.0001052,20.6098102 21.0001051,20.0590859 L21.0001049,18" id="Path-12" stroke-linejoin="round"></path>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/video/eye-closed.svg b/client/src/assets/images/video/eye-closed.svg
new file mode 100644
index 000000000..c5b739659
--- /dev/null
+++ b/client/src/assets/images/video/eye-closed.svg
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g id="Artboard-4" transform="translate(-796.000000, -1046.000000)" stroke="#585858" stroke-width="2">
+            <g id="Extras" transform="translate(48.000000, 1046.000000)">
+                <g id="eye-closed" transform="translate(760.000000, 12.000000) scale(1, -1) translate(-760.000000, -12.000000) translate(748.000000, 0.000000)">
+                    <path d="M2,14 C2,14 5,7 12,7 C19,7 22,14 22,14" id="Path-80" stroke-linejoin="round"></path>
+                    <path d="M12,7 L12,5" id="Path-81"></path>
+                    <path d="M18,8.5 L19,7" id="Path-81"></path>
+                    <path d="M21,12 L22.5,11" id="Path-81"></path>
+                    <path d="M1.5,12 L3,11" id="Path-81" transform="translate(2.250000, 11.500000) scale(1, -1) translate(-2.250000, -11.500000) "></path>
+                    <path d="M5,8.5 L6,7" id="Path-81" transform="translate(5.500000, 7.750000) scale(-1, 1) translate(-5.500000, -7.750000) "></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/video/like-grey.svg b/client/src/assets/images/video/like-grey.svg
new file mode 100644
index 000000000..5ef6c7b31
--- /dev/null
+++ b/client/src/assets/images/video/like-grey.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>thumbs-up</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g id="Artboard-4" transform="translate(-708.000000, -643.000000)" stroke="#585858" stroke-width="2">
+            <g id="256" transform="translate(708.000000, 643.000000)">
+                <path d="M6,14 C6,16.5 6.5,19 8,19 L16.9938335,19 C17.5495239,19 18.1819788,18.5956028 18.4072817,18.0949295 L20.8562951,12.6526776 C21.7640882,10.6353595 20.7154925,9 18.5092545,9 L15.5,9 C15.5,9 18.5,3 15,3 C12.5,3 11.5,9 8,9 C6.5,9 6,11.5 6,14 Z" id="Path-188" stroke-linejoin="round"></path>
+                <path d="M4,8.5 C4,8.5 3,11 3,14 C3,17 4,19.5 4,19.5" id="Path-189"></path>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/video/like-white.svg b/client/src/assets/images/video/like-white.svg
new file mode 100644
index 000000000..88e5f6a9a
--- /dev/null
+++ b/client/src/assets/images/video/like-white.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>thumbs-up</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g id="Artboard-4" transform="translate(-708.000000, -643.000000)" stroke="#ffffff" stroke-width="2">
+            <g id="256" transform="translate(708.000000, 643.000000)">
+                <path d="M6,14 C6,16.5 6.5,19 8,19 L16.9938335,19 C17.5495239,19 18.1819788,18.5956028 18.4072817,18.0949295 L20.8562951,12.6526776 C21.7640882,10.6353595 20.7154925,9 18.5092545,9 L15.5,9 C15.5,9 18.5,3 15,3 C12.5,3 11.5,9 8,9 C6.5,9 6,11.5 6,14 Z" id="Path-188" stroke-linejoin="round"></path>
+                <path d="M4,8.5 C4,8.5 3,11 3,14 C3,17 4,19.5 4,19.5" id="Path-189"></path>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/video/like.svg b/client/src/assets/images/video/like.svg
deleted file mode 100644
index 5ef6c7b31..000000000
--- a/client/src/assets/images/video/like.svg
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
-    <title>thumbs-up</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
-        <g id="Artboard-4" transform="translate(-708.000000, -643.000000)" stroke="#585858" stroke-width="2">
-            <g id="256" transform="translate(708.000000, 643.000000)">
-                <path d="M6,14 C6,16.5 6.5,19 8,19 L16.9938335,19 C17.5495239,19 18.1819788,18.5956028 18.4072817,18.0949295 L20.8562951,12.6526776 C21.7640882,10.6353595 20.7154925,9 18.5092545,9 L15.5,9 C15.5,9 18.5,3 15,3 C12.5,3 11.5,9 8,9 C6.5,9 6,11.5 6,14 Z" id="Path-188" stroke-linejoin="round"></path>
-                <path d="M4,8.5 C4,8.5 3,11 3,14 C3,17 4,19.5 4,19.5" id="Path-189"></path>
-            </g>
-        </g>
-    </g>
-</svg>
diff --git a/client/src/sass/_mixins.scss b/client/src/sass/_mixins.scss
index 6a18f7a76..ddc9c6766 100644
--- a/client/src/sass/_mixins.scss
+++ b/client/src/sass/_mixins.scss
@@ -19,22 +19,34 @@
   }
 }
 
+@mixin orange-button {
+  color: #fff;
+  background-color: $orange-color;
+
+  &:hover {
+    background-color: $orange-hoover-color;
+  }
+}
+
+@mixin grey-button {
+  background-color: $grey-color;
+  color: #585858;
+
+  &:hover {
+    background-color: $grey-hoover-color;
+  }
+}
+
 @mixin peertube-button {
   border: none;
-  color: #fff;
   font-weight: $font-semibold;
   font-size: 15px;
   height: $button-height;
   line-height: $button-height;
   border-radius: 3px;
   text-align: center;
-  background-color: $orange-color;
   padding: 0 17px 0 13px;
   cursor: pointer;
-
-  &:hover {
-    background-color: $orange-hoover-color;
-  }
 }
 
 @mixin peertube-button-link {
diff --git a/client/src/sass/_variables.scss b/client/src/sass/_variables.scss
index cc1cee75b..0d310409b 100644
--- a/client/src/sass/_variables.scss
+++ b/client/src/sass/_variables.scss
@@ -2,7 +2,8 @@ $font-regular: 400;
 $font-semibold: 600;
 $font-bold: 700;
 
-$grey-color: #555;
+$grey-color: #E5E5E5;
+$grey-hoover-color: #EFEFEF;;
 $orange-color: #F1680D;
 $orange-hoover-color: #F97D46;
 
@@ -17,7 +18,6 @@ $button-height: 30px;
 
 $header-height: 50px;
 $header-border-color: #e9eff6;
-
 $search-input-width: 375px;
 
 $menu-color: #fff;
@@ -27,11 +27,3 @@ $footer-height: 30px;
 $footer-margin: 30px;
 
 $footer-border-color: $header-border-color;
-
-$video-miniature-other-infos: #686767;
-
-$video-watch-border-color: #eceef4;
-$video-watch-title-height: 90px;
-$video-watch-info-color: #9da0ae;
-$video-watch-info-height: 120px;
-$video-watch-info-padding-left: 40px;
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index b860e1bf2..c81031021 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -138,3 +138,17 @@ p-datatable {
     }
   }
 }
+
+.dropdown-menu {
+  border-radius: 3px;
+  box-shadow: 0 3px 6px;
+  font-size: 15px;
+
+  .dropdown-item {
+    padding: 3px 15px;
+  }
+
+  a {
+    color: #000 !important;
+  }
+}
-- 
cgit v1.2.3


From 0727cab0dfd3d53e5e9c88bfbda6bc0e090d4f12 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Thu, 7 Dec 2017 10:27:33 +0100
Subject: Design video watch modals

---
 .../account-videos/account-videos.component.scss   |  8 ++----
 client/src/app/app.component.scss                  |  8 ++----
 client/src/app/header/header.component.scss        | 10 +++-----
 client/src/app/menu/menu.component.scss            |  6 ++---
 .../shared/forms/form-validators/video-abuse.ts    |  6 ++---
 .../+video-watch/video-download.component.html     | 11 ++++----
 .../+video-watch/video-download.component.scss     | 23 +++++++++++++++++
 .../+video-watch/video-download.component.ts       |  2 +-
 .../+video-watch/video-report.component.html       | 12 ++++-----
 .../videos/+video-watch/video-share.component.html |  2 +-
 .../videos/+video-watch/video-watch.component.scss | 10 +++-----
 client/src/assets/images/video/download-grey.svg   | 16 ++++++++++++
 client/src/assets/images/video/download-white.svg  | 16 ++++++++++++
 client/src/assets/images/video/download.svg        | 16 ------------
 client/src/sass/_mixins.scss                       | 16 ++++++++++--
 client/src/sass/application.scss                   | 30 ++++++++++++++++++++++
 16 files changed, 129 insertions(+), 63 deletions(-)
 create mode 100644 client/src/app/videos/+video-watch/video-download.component.scss
 create mode 100644 client/src/assets/images/video/download-grey.svg
 create mode 100644 client/src/assets/images/video/download-white.svg
 delete mode 100644 client/src/assets/images/video/download.svg

(limited to 'client')

diff --git a/client/src/app/account/account-videos/account-videos.component.scss b/client/src/app/account/account-videos/account-videos.component.scss
index 04aaa8e89..083918e29 100644
--- a/client/src/app/account/account-videos/account-videos.component.scss
+++ b/client/src/app/account/account-videos/account-videos.component.scss
@@ -32,12 +32,8 @@
   }
 
   .icon {
-    display: inline-block;
-    background-repeat: no-repeat;
-    background-size: contain;
-    width: 21px;
-    height: 21px;
-    vertical-align: middle;
+    @include icon(21px);
+
     position: relative;
     top: -2px;
 
diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss
index 97c5d461a..10af9debe 100644
--- a/client/src/app/app.component.scss
+++ b/client/src/app/app.component.scss
@@ -35,11 +35,7 @@
     align-items: center;
 
     .icon {
-      cursor: pointer;
-      width: 22px;
-      height: 22px;
-      display: inline-block;
-      background-size: contain;
+      @include icon(22px);
 
       &.icon-menu {
         background-image: url('../assets/images/header/menu.svg');
@@ -59,7 +55,7 @@
       .icon.icon-logo {
         display: inline-block;
         background: url('../assets/images/logo.svg') no-repeat;
-        width: 20px;
+        width: 23px;
         height: 24px;
       }
     }
diff --git a/client/src/app/header/header.component.scss b/client/src/app/header/header.component.scss
index e7761a9df..d1c59e8d1 100644
--- a/client/src/app/header/header.component.scss
+++ b/client/src/app/header/header.component.scss
@@ -9,13 +9,11 @@
 }
 
 .icon.icon-search {
-  display: inline-block;
-  background: url('../../assets/images/header/search.svg') no-repeat;
-  background-size: contain;
-  width: 25px;
+  @include icon(25px);
   height: 21px;
-  vertical-align: middle;
-  cursor: pointer;
+
+  background-image: url('../../assets/images/header/search.svg');
+
   // yolo
   position: absolute;
   margin-left: -50px;
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss
index c93c59622..eda3e1a85 100644
--- a/client/src/app/menu/menu.component.scss
+++ b/client/src/app/menu/menu.component.scss
@@ -107,11 +107,9 @@ menu {
       @include disable-default-a-behaviour;
 
       .icon {
-        width: 22px;
-        height: 22px;
-        display: inline-block;
+        @include icon(22px);
+
         margin-right: 18px;
-        background-size: contain;
 
         &.icon-videos-trending {
           position: relative;
diff --git a/client/src/app/shared/forms/form-validators/video-abuse.ts b/client/src/app/shared/forms/form-validators/video-abuse.ts
index 3c7f26205..4b2a2b789 100644
--- a/client/src/app/shared/forms/form-validators/video-abuse.ts
+++ b/client/src/app/shared/forms/form-validators/video-abuse.ts
@@ -3,8 +3,8 @@ import { Validators } from '@angular/forms'
 export const VIDEO_ABUSE_REASON = {
   VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(300) ],
   MESSAGES: {
-    'required': 'Report reason name is required.',
-    'minlength': 'Report reson must be at least 2 characters long.',
-    'maxlength': 'Report reson cannot be more than 300 characters long.'
+    'required': 'Report reason is required.',
+    'minlength': 'Report reason must be at least 2 characters long.',
+    'maxlength': 'Report reason cannot be more than 300 characters long.'
   }
 }
diff --git a/client/src/app/videos/+video-watch/video-download.component.html b/client/src/app/videos/+video-watch/video-download.component.html
index ddc57e999..7efc79e93 100644
--- a/client/src/app/videos/+video-watch/video-download.component.html
+++ b/client/src/app/videos/+video-watch/video-download.component.html
@@ -6,18 +6,19 @@
         <button type="button" class="close" aria-label="Close" (click)="hide()">
           <span aria-hidden="true">&times;</span>
         </button>
-        <h4 class="modal-title">Download</h4>
+        <h4 class="title-page title-page-single">Download</h4>
       </div>
 
       <div class="modal-body">
         <div *ngFor="let file of video.files" class="resolution-block">
           <label>{{ file.resolutionLabel }}</label>
-          <a class="btn btn-default " target="_blank" [href]="file.torrentUrl">
-            <span class="glyphicon glyphicon-download"></span>
+
+          <a class="orange-button-link " target="_blank" [href]="file.torrentUrl">
+            <span class="icon icon-download"></span>
             Torrent file
           </a>
-          <a class="btn btn-default" target="_blank" [href]="file.fileUrl">
-            <span class="glyphicon glyphicon-download"></span>
+          <a class="orange-button-link" target="_blank" [href]="file.fileUrl">
+            <span class="icon icon-download"></span>
             Download
           </a>
 
diff --git a/client/src/app/videos/+video-watch/video-download.component.scss b/client/src/app/videos/+video-watch/video-download.component.scss
new file mode 100644
index 000000000..c9d5af9c1
--- /dev/null
+++ b/client/src/app/videos/+video-watch/video-download.component.scss
@@ -0,0 +1,23 @@
+.resolution-block:not(:first-child) {
+  margin-top: 30px;
+}
+
+.orange-button-link {
+  margin-right: 10px;
+}
+
+label {
+  display: block;
+}
+
+.icon {
+  @include icon(21px);
+
+  margin-right: 5px;
+  position: relative;
+  top: -1px;
+
+  &.icon-download {
+    background-image: url('../../../assets/images/video/download-white.svg');
+  }
+}
diff --git a/client/src/app/videos/+video-watch/video-download.component.ts b/client/src/app/videos/+video-watch/video-download.component.ts
index 010a246cd..095df1698 100644
--- a/client/src/app/videos/+video-watch/video-download.component.ts
+++ b/client/src/app/videos/+video-watch/video-download.component.ts
@@ -5,7 +5,7 @@ import { VideoDetails } from '../../shared/video/video-details.model'
 @Component({
   selector: 'my-video-download',
   templateUrl: './video-download.component.html',
-  styles: [ '.resolution-block { margin-top: 20px; }' ]
+  styleUrls: [ './video-download.component.scss' ]
 })
 export class VideoDownloadComponent {
   @Input() video: VideoDetails = null
diff --git a/client/src/app/videos/+video-watch/video-report.component.html b/client/src/app/videos/+video-watch/video-report.component.html
index ceb7cf50a..20474bab4 100644
--- a/client/src/app/videos/+video-watch/video-report.component.html
+++ b/client/src/app/videos/+video-watch/video-report.component.html
@@ -6,28 +6,28 @@
         <button type="button" class="close" aria-label="Close" (click)="hide()">
           <span aria-hidden="true">&times;</span>
         </button>
-        <h4 class="modal-title">Report video</h4>
+        <h4 class="title-page title-page-single">Report video</h4>
       </div>
 
       <div class="modal-body">
 
-        <form novalidate [formGroup]="form">
+        <form novalidate [formGroup]="form" (ngSubmit)="report()">
           <div class="form-group">
             <label for="reason">Reason</label>
             <textarea
               id="reason" class="form-control" placeholder="Reason..."
-              formControlName="reason"
+              formControlName="reason" [ngClass]="{ 'input-error': formErrors['reason'] }"
             >
             </textarea>
-            <div *ngIf="formErrors.reason" class="alert alert-danger">
+            <div *ngIf="formErrors.reason" class="form-error">
               {{ formErrors.reason }}
             </div>
           </div>
 
           <div class="form-group">
             <input
-              type="button" value="Report" class="btn btn-default form-control"
-              [disabled]="!form.valid" (click)="report()"
+              type="submit" value="Report" class="orange-button"
+              [disabled]="!form.valid"
             >
           </div>
         </form>
diff --git a/client/src/app/videos/+video-watch/video-share.component.html b/client/src/app/videos/+video-watch/video-share.component.html
index 88f59c063..36ec38d88 100644
--- a/client/src/app/videos/+video-watch/video-share.component.html
+++ b/client/src/app/videos/+video-watch/video-share.component.html
@@ -6,7 +6,7 @@
         <button type="button" class="close" aria-label="Close" (click)="hide()">
           <span aria-hidden="true">&times;</span>
         </button>
-        <h4 class="modal-title">Share</h4>
+        <h4 class="title-page title-page-single">Share</h4>
       </div>
 
       <div class="modal-body">
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss
index 3f36410f4..6973619b2 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.scss
+++ b/client/src/app/videos/+video-watch/video-watch.component.scss
@@ -51,12 +51,8 @@
           padding: 0 10px 0 10px;
 
           .icon {
-            display: inline-block;
-            background-repeat: no-repeat;
-            background-size: contain;
-            width: 21px;
-            height: 21px;
-            vertical-align: middle;
+            @include icon(21px);
+
             position: relative;
             top: -2px;
 
@@ -105,7 +101,7 @@
             top: -1px;
 
             &.icon-download {
-              background-image: url('../../../assets/images/video/download.svg');
+              background-image: url('../../../assets/images/video/download-grey.svg');
             }
 
             &.icon-alert {
diff --git a/client/src/assets/images/video/download-grey.svg b/client/src/assets/images/video/download-grey.svg
new file mode 100644
index 000000000..5b0cca5ef
--- /dev/null
+++ b/client/src/assets/images/video/download-grey.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>download</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g id="Artboard-4" transform="translate(-180.000000, -291.000000)" stroke="#585858" stroke-width="2">
+            <g id="84" transform="translate(180.000000, 291.000000)">
+                <path d="M12,3 L12,15" id="Path-58"></path>
+                <polyline id="Path-59" stroke-linejoin="round" transform="translate(12.000000, 14.000000) rotate(-270.000000) translate(-12.000000, -14.000000) " points="9 8 15 14 9 20"></polyline>
+                <path d="M3,18 L3,20.0590859 C3,20.6127331 3.44494889,21.0615528 3.99340349,21.0615528 L20.0067018,21.0615528 C20.5553434,21.0615528 21.0001052,20.6098102 21.0001051,20.0590859 L21.0001049,18" id="Path-12" stroke-linejoin="round"></path>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/video/download-white.svg b/client/src/assets/images/video/download-white.svg
new file mode 100644
index 000000000..0e66e06e8
--- /dev/null
+++ b/client/src/assets/images/video/download-white.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>download</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g id="Artboard-4" transform="translate(-180.000000, -291.000000)" stroke="#ffffff" stroke-width="2">
+            <g id="84" transform="translate(180.000000, 291.000000)">
+                <path d="M12,3 L12,15" id="Path-58"></path>
+                <polyline id="Path-59" stroke-linejoin="round" transform="translate(12.000000, 14.000000) rotate(-270.000000) translate(-12.000000, -14.000000) " points="9 8 15 14 9 20"></polyline>
+                <path d="M3,18 L3,20.0590859 C3,20.6127331 3.44494889,21.0615528 3.99340349,21.0615528 L20.0067018,21.0615528 C20.5553434,21.0615528 21.0001052,20.6098102 21.0001051,20.0590859 L21.0001049,18" id="Path-12" stroke-linejoin="round"></path>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/video/download.svg b/client/src/assets/images/video/download.svg
deleted file mode 100644
index 5b0cca5ef..000000000
--- a/client/src/assets/images/video/download.svg
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
-    <title>download</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
-        <g id="Artboard-4" transform="translate(-180.000000, -291.000000)" stroke="#585858" stroke-width="2">
-            <g id="84" transform="translate(180.000000, 291.000000)">
-                <path d="M12,3 L12,15" id="Path-58"></path>
-                <polyline id="Path-59" stroke-linejoin="round" transform="translate(12.000000, 14.000000) rotate(-270.000000) translate(-12.000000, -14.000000) " points="9 8 15 14 9 20"></polyline>
-                <path d="M3,18 L3,20.0590859 C3,20.6127331 3.44494889,21.0615528 3.99340349,21.0615528 L20.0067018,21.0615528 C20.5553434,21.0615528 21.0001052,20.6098102 21.0001051,20.0590859 L21.0001049,18" id="Path-12" stroke-linejoin="round"></path>
-            </g>
-        </g>
-    </g>
-</svg>
diff --git a/client/src/sass/_mixins.scss b/client/src/sass/_mixins.scss
index ddc9c6766..14d9b5044 100644
--- a/client/src/sass/_mixins.scss
+++ b/client/src/sass/_mixins.scss
@@ -23,7 +23,8 @@
   color: #fff;
   background-color: $orange-color;
 
-  &:hover {
+  &:hover, &:active, &:focus, &[disabled] {
+    color: #fff;
     background-color: $orange-hoover-color;
   }
 }
@@ -32,7 +33,8 @@
   background-color: $grey-color;
   color: #585858;
 
-  &:hover {
+  &:hover, &:active, &:focus, &[disabled] {
+    color: #585858;
     background-color: $grey-hoover-color;
   }
 }
@@ -60,3 +62,13 @@
   width: $size;
   height: $size;
 }
+
+@mixin icon ($size) {
+  display: inline-block;
+  background-repeat: no-repeat;
+  background-size: contain;
+  width: $size;
+  height: $size;
+  vertical-align: middle;
+  cursor: pointer;
+}
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index c81031021..dc1f4dba0 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -152,3 +152,33 @@ p-datatable {
     color: #000 !important;
   }
 }
+
+.modal {
+  .modal-header {
+    border-bottom: none;
+
+    .title-page-single {
+      margin: 0;
+    }
+  }
+}
+
+.orange-button {
+  @include peertube-button;
+  @include orange-button;
+}
+
+.orange-button-link {
+  @include peertube-button-link;
+  @include orange-button;
+}
+
+.grey-button {
+  @include peertube-button;
+  @include grey-button;
+}
+
+.grey-button-link {
+  @include peertube-button-link;
+  @include grey-button;
+}
-- 
cgit v1.2.3


From 59aa1e5e7541f604363d2a1ebfd670a5d1db245f Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Thu, 7 Dec 2017 10:33:16 +0100
Subject: Design confirm dialog

---
 client/src/app/core/confirm/confirm.component.html |  6 ++--
 client/src/app/core/confirm/confirm.component.ts   |  3 +-
 client/src/sass/application.scss                   | 32 +++++++++++-----------
 3 files changed, 21 insertions(+), 20 deletions(-)

(limited to 'client')

diff --git a/client/src/app/core/confirm/confirm.component.html b/client/src/app/core/confirm/confirm.component.html
index 2726af6cc..31b735f97 100644
--- a/client/src/app/core/confirm/confirm.component.html
+++ b/client/src/app/core/confirm/confirm.component.html
@@ -6,14 +6,14 @@
         <button type="button" class="close" aria-label="Close" (click)="cancel()">
           <span aria-hidden="true">&times;</span>
         </button>
-        <h4 class="modal-title">{{ title }}</h4>
+        <h4 class="title-page title-page-single">{{ title }}</h4>
       </div>
 
       <div class="modal-body" [innerHtml]="message"></div>
 
       <div class="modal-footer">
-        <button type="button" class="btn btn-default" data-dismiss="modal" (click)="cancel()">Cancel</button>
-        <button type="button" class="btn btn-primary" (click)="confirm()">Confirm</button>
+        <button type="button" class="grey-button" data-dismiss="modal" (click)="cancel()">Cancel</button>
+        <button type="button" class="orange-button" (click)="confirm()">Confirm</button>
       </div>
     </div>
   </div>
diff --git a/client/src/app/core/confirm/confirm.component.ts b/client/src/app/core/confirm/confirm.component.ts
index c8e41e233..0515d969a 100644
--- a/client/src/app/core/confirm/confirm.component.ts
+++ b/client/src/app/core/confirm/confirm.component.ts
@@ -11,7 +11,8 @@ export interface ConfigChangedEvent {
 
 @Component({
   selector: 'my-confirm',
-  templateUrl: './confirm.component.html'
+  templateUrl: './confirm.component.html',
+  styles: [ '.button { padding: 0 13px; }' ]
 })
 export class ConfirmComponent implements OnInit {
   @ViewChild('confirmModal') confirmModal: ModalDirective
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index dc1f4dba0..0c999d659 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -42,22 +42,6 @@ label {
 .main-col {
   margin-left: $menu-width;
 
-  .title-page {
-    color: #000;
-    font-size: 16px;
-    display: inline-block;
-    margin-right: 55px;
-    font-weight: $font-semibold;
-    @include disable-default-a-behaviour;
-
-    &.active, &.title-page-single {
-      border-bottom: 2px solid $orange-color;
-      font-weight: $font-bold;
-      margin-top: 30px;
-      margin-bottom: 25px;
-    }
-  }
-
   .margin-content {
     margin-left: $not-expanded-horizontal-margins;
     margin-right: $not-expanded-horizontal-margins;
@@ -88,6 +72,22 @@ label {
   }
 }
 
+.title-page {
+  color: #000;
+  font-size: 16px;
+  display: inline-block;
+  margin-right: 55px;
+  font-weight: $font-semibold;
+  @include disable-default-a-behaviour;
+
+  &.active, &.title-page-single {
+    border-bottom: 2px solid $orange-color;
+    font-weight: $font-bold;
+    margin-top: 30px;
+    margin-bottom: 25px;
+  }
+}
+
 // On small screen, menu is absolute and displayed over the page
 @media screen and (max-width: 500px) {
   .title-menu-left {
-- 
cgit v1.2.3


From ff249f499ccca2e37757f338384e7ba44c906a69 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Thu, 7 Dec 2017 11:15:19 +0100
Subject: Move video form inside a component

---
 .../src/app/shared/forms/form-validators/video.ts  |  6 ++
 .../+video-edit/shared/video-edit.component.html   | 85 +++++++++++++++++++++
 .../+video-edit/shared/video-edit.component.ts     | 83 ++++++++++++++++++++
 .../videos/+video-edit/shared/video-edit.module.ts |  7 +-
 .../videos/+video-edit/video-update.component.html | 88 +---------------------
 .../videos/+video-edit/video-update.component.ts   | 46 ++---------
 .../videos/+video-watch/video-watch.component.ts   | 10 +--
 7 files changed, 190 insertions(+), 135 deletions(-)
 create mode 100644 client/src/app/videos/+video-edit/shared/video-edit.component.html
 create mode 100644 client/src/app/videos/+video-edit/shared/video-edit.component.ts

(limited to 'client')

diff --git a/client/src/app/shared/forms/form-validators/video.ts b/client/src/app/shared/forms/form-validators/video.ts
index 65f11f5da..8e512e8c8 100644
--- a/client/src/app/shared/forms/form-validators/video.ts
+++ b/client/src/app/shared/forms/form-validators/video.ts
@@ -1,5 +1,11 @@
 import { Validators } from '@angular/forms'
 
+export type ValidatorMessage = {
+  [ id: string ]: {
+    [ error: string ]: string
+  }
+}
+
 export const VIDEO_NAME = {
   VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(120) ],
   MESSAGES: {
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.html b/client/src/app/videos/+video-edit/shared/video-edit.component.html
new file mode 100644
index 000000000..e087b71a4
--- /dev/null
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.html
@@ -0,0 +1,85 @@
+<div [formGroup]="form">
+  <div class="form-group">
+    <label for="name">Name</label>
+    <input
+      type="text" class="form-control" id="name"
+      formControlName="name"
+    >
+    <div *ngIf="formErrors.name" class="alert alert-danger">
+      {{ formErrors.name }}
+    </div>
+  </div>
+
+  <div class="form-group">
+    <label for="privacy">Privacy</label>
+    <select class="form-control" id="privacy" formControlName="privacy">
+      <option></option>
+      <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option>
+    </select>
+
+    <div *ngIf="formErrors.privacy" class="alert alert-danger">
+      {{ formErrors.privacy }}
+    </div>
+  </div>
+
+  <div class="form-group">
+    <input
+      type="checkbox" id="nsfw"
+      formControlName="nsfw"
+    >
+    <label for="nsfw">This video contains mature or explicit content</label>
+  </div>
+
+  <div class="form-group">
+    <label for="category">Category</label>
+    <select class="form-control" id="category" formControlName="category">
+      <option></option>
+      <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option>
+    </select>
+
+    <div *ngIf="formErrors.category" class="alert alert-danger">
+      {{ formErrors.category }}
+    </div>
+  </div>
+
+  <div class="form-group">
+    <label for="licence">Licence</label>
+    <select class="form-control" id="licence" formControlName="licence">
+      <option></option>
+      <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option>
+    </select>
+
+    <div *ngIf="formErrors.licence" class="alert alert-danger">
+      {{ formErrors.licence }}
+    </div>
+  </div>
+
+  <div class="form-group">
+    <label for="language">Language</label>
+    <select class="form-control" id="language" formControlName="language">
+      <option></option>
+      <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option>
+    </select>
+
+    <div *ngIf="formErrors.language" class="alert alert-danger">
+      {{ formErrors.language }}
+    </div>
+  </div>
+
+  <div class="form-group">
+    <label class="label-tags">Tags</label> <span class="little-information">(press enter to add the tag)</span>
+    <tag-input
+      [ngModel]="tags" [validators]="tagValidators" [errorMessages]="tagValidatorsMessages"
+      formControlName="tags" maxItems="5" modelAsStrings="true"
+    ></tag-input>
+  </div>
+
+  <div class="form-group">
+    <label for="description">Description</label>
+    <my-video-description formControlName="description"></my-video-description>
+
+    <div *ngIf="formErrors.description" class="alert alert-danger">
+      {{ formErrors.description }}
+    </div>
+  </div>
+</div>
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.ts b/client/src/app/videos/+video-edit/shared/video-edit.component.ts
new file mode 100644
index 000000000..5b1cc3f9c
--- /dev/null
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.ts
@@ -0,0 +1,83 @@
+import { Component, Input, OnInit } from '@angular/core'
+import { FormBuilder, FormControl, FormGroup } from '@angular/forms'
+import { ActivatedRoute, Router } from '@angular/router'
+import { NotificationsService } from 'angular2-notifications'
+import { ServerService } from 'app/core'
+import { VideoEdit } from 'app/shared/video/video-edit.model'
+import 'rxjs/add/observable/forkJoin'
+import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum'
+import {
+  ValidatorMessage,
+  VIDEO_CATEGORY,
+  VIDEO_DESCRIPTION,
+  VIDEO_LANGUAGE,
+  VIDEO_LICENCE,
+  VIDEO_NAME,
+  VIDEO_PRIVACY,
+  VIDEO_TAGS
+} from '../../../shared/forms/form-validators'
+
+@Component({
+  selector: 'my-video-edit',
+  styleUrls: [ './video-edit.component.scss' ],
+  templateUrl: './video-edit.component.html'
+})
+
+export class VideoEditComponent implements OnInit {
+  @Input() form: FormGroup
+  @Input() formErrors: { [ id: string ]: string } = {}
+  @Input() validationMessages: ValidatorMessage = {}
+  @Input() videoPrivacies = []
+
+  tags: string[] = []
+  videoCategories = []
+  videoLicences = []
+  videoLanguages = []
+  video: VideoEdit
+
+  tagValidators = VIDEO_TAGS.VALIDATORS
+  tagValidatorsMessages = VIDEO_TAGS.MESSAGES
+
+  error: string = null
+
+  constructor (
+    private formBuilder: FormBuilder,
+    private route: ActivatedRoute,
+    private router: Router,
+    private notificationsService: NotificationsService,
+    private serverService: ServerService
+  ) { }
+
+  updateForm () {
+    this.formErrors['name'] = ''
+    this.formErrors['privacy'] = ''
+    this.formErrors['category'] = ''
+    this.formErrors['licence'] = ''
+    this.formErrors['language'] = ''
+    this.formErrors['description'] = ''
+
+    this.validationMessages['name'] = VIDEO_NAME.MESSAGES
+    this.validationMessages['privacy'] = VIDEO_PRIVACY.MESSAGES
+    this.validationMessages['category'] = VIDEO_CATEGORY.MESSAGES
+    this.validationMessages['licence'] = VIDEO_LICENCE.MESSAGES
+    this.validationMessages['language'] = VIDEO_LANGUAGE.MESSAGES
+    this.validationMessages['description'] = VIDEO_DESCRIPTION.MESSAGES
+
+    this.form.addControl('name', new FormControl('', VIDEO_NAME.VALIDATORS))
+    this.form.addControl('privacy', new FormControl('', VIDEO_PRIVACY.VALIDATORS))
+    this.form.addControl('nsfw', new FormControl(false))
+    this.form.addControl('category', new FormControl('', VIDEO_CATEGORY.VALIDATORS))
+    this.form.addControl('licence', new FormControl('', VIDEO_LICENCE.VALIDATORS))
+    this.form.addControl('language', new FormControl('', VIDEO_LANGUAGE.VALIDATORS))
+    this.form.addControl('description', new FormControl('', VIDEO_DESCRIPTION.VALIDATORS))
+    this.form.addControl('tags', new FormControl(''))
+  }
+
+  ngOnInit () {
+    this.updateForm()
+
+    this.videoCategories = this.serverService.getVideoCategories()
+    this.videoLicences = this.serverService.getVideoLicences()
+    this.videoLanguages = this.serverService.getVideoLanguages()
+  }
+}
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.module.ts b/client/src/app/videos/+video-edit/shared/video-edit.module.ts
index cdab694f9..c7ed8c351 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.module.ts
+++ b/client/src/app/videos/+video-edit/shared/video-edit.module.ts
@@ -5,6 +5,7 @@ import { TabsModule } from 'ngx-bootstrap/tabs'
 
 import { MarkdownService, VideoDescriptionComponent } from '../../shared'
 import { SharedModule } from '../../../shared'
+import { VideoEditComponent } from './video-edit.component'
 
 @NgModule({
   imports: [
@@ -15,14 +16,16 @@ import { SharedModule } from '../../../shared'
   ],
 
   declarations: [
-    VideoDescriptionComponent
+    VideoDescriptionComponent,
+    VideoEditComponent
   ],
 
   exports: [
     TagInputModule,
     TabsModule,
 
-    VideoDescriptionComponent
+    VideoDescriptionComponent,
+    VideoEditComponent
   ],
 
   providers: [
diff --git a/client/src/app/videos/+video-edit/video-update.component.html b/client/src/app/videos/+video-edit/video-update.component.html
index b9c6139b2..c57f35da0 100644
--- a/client/src/app/videos/+video-edit/video-update.component.html
+++ b/client/src/app/videos/+video-edit/video-update.component.html
@@ -3,92 +3,12 @@
 
   <h3>Update {{ video?.name }}</h3>
 
-  <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
-
   <form novalidate [formGroup]="form">
-    <div class="form-group">
-      <label for="name">Name</label>
-      <input
-        type="text" class="form-control" id="name"
-        formControlName="name"
-      >
-      <div *ngIf="formErrors.name" class="alert alert-danger">
-        {{ formErrors.name }}
-      </div>
-    </div>
-
-    <div class="form-group">
-      <label for="privacy">Privacy</label>
-      <select class="form-control" id="privacy" formControlName="privacy">
-        <option></option>
-        <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option>
-      </select>
-
-      <div *ngIf="formErrors.privacy" class="alert alert-danger">
-        {{ formErrors.privacy }}
-      </div>
-    </div>
-
-    <div class="form-group">
-      <input
-        type="checkbox" id="nsfw"
-        formControlName="nsfw"
-      >
-      <label for="nsfw">This video contains mature or explicit content</label>
-    </div>
-
-    <div class="form-group">
-      <label for="category">Category</label>
-      <select class="form-control" id="category" formControlName="category">
-        <option></option>
-        <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option>
-      </select>
 
-      <div *ngIf="formErrors.category" class="alert alert-danger">
-        {{ formErrors.category }}
-      </div>
-    </div>
-
-    <div class="form-group">
-      <label for="licence">Licence</label>
-      <select class="form-control" id="licence" formControlName="licence">
-        <option></option>
-        <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option>
-      </select>
-
-      <div *ngIf="formErrors.licence" class="alert alert-danger">
-        {{ formErrors.licence }}
-      </div>
-    </div>
-
-    <div class="form-group">
-      <label for="language">Language</label>
-      <select class="form-control" id="language" formControlName="language">
-        <option></option>
-        <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option>
-      </select>
-
-      <div *ngIf="formErrors.language" class="alert alert-danger">
-        {{ formErrors.language }}
-      </div>
-    </div>
-
-    <div class="form-group">
-      <label class="label-tags">Tags</label> <span class="little-information">(press enter to add the tag)</span>
-      <tag-input
-        [ngModel]="tags" [validators]="tagValidators" [errorMessages]="tagValidatorsMessages"
-        formControlName="tags" maxItems="5" modelAsStrings="true"
-      ></tag-input>
-    </div>
-
-    <div class="form-group">
-      <label for="description">Description</label>
-      <my-video-description formControlName="description"></my-video-description>
-
-      <div *ngIf="formErrors.description" class="alert alert-danger">
-        {{ formErrors.description }}
-      </div>
-    </div>
+    <my-video-edit
+      [form]="form" [formErrors]="formErrors"
+      [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies"
+    ></my-video-edit>
 
     <div class="form-group">
       <input
diff --git a/client/src/app/videos/+video-edit/video-update.component.ts b/client/src/app/videos/+video-edit/video-update.component.ts
index 017781866..01ab0a716 100644
--- a/client/src/app/videos/+video-edit/video-update.component.ts
+++ b/client/src/app/videos/+video-edit/video-update.component.ts
@@ -15,6 +15,7 @@ import {
   VIDEO_PRIVACY,
   VIDEO_TAGS
 } from '../../shared'
+import { ValidatorMessage } from '../../shared/forms/form-validators'
 import { VideoService } from '../../shared/video/video.service'
 import { VideoEdit } from '../../shared/video/video-edit.model'
 
@@ -25,34 +26,13 @@ import { VideoEdit } from '../../shared/video/video-edit.model'
 })
 
 export class VideoUpdateComponent extends FormReactive implements OnInit {
-  tags: string[] = []
-  videoCategories = []
-  videoLicences = []
-  videoLanguages = []
-  videoPrivacies = []
   video: VideoEdit
 
-  tagValidators = VIDEO_TAGS.VALIDATORS
-  tagValidatorsMessages = VIDEO_TAGS.MESSAGES
-
   error: string = null
   form: FormGroup
-  formErrors = {
-    name: '',
-    privacy: '',
-    category: '',
-    licence: '',
-    language: '',
-    description: ''
-  }
-  validationMessages = {
-    name: VIDEO_NAME.MESSAGES,
-    privacy: VIDEO_PRIVACY.MESSAGES,
-    category: VIDEO_CATEGORY.MESSAGES,
-    licence: VIDEO_LICENCE.MESSAGES,
-    language: VIDEO_LANGUAGE.MESSAGES,
-    description: VIDEO_DESCRIPTION.MESSAGES
-  }
+  formErrors: { [ id: string ]: string } = {}
+  validationMessages: ValidatorMessage = {}
+  videoPrivacies = []
 
   fileError = ''
 
@@ -68,30 +48,16 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
   }
 
   buildForm () {
-    this.form = this.formBuilder.group({
-      name: [ '', VIDEO_NAME.VALIDATORS ],
-      privacy: [ '', VIDEO_PRIVACY.VALIDATORS ],
-      nsfw: [ false ],
-      category: [ '', VIDEO_CATEGORY.VALIDATORS ],
-      licence: [ '', VIDEO_LICENCE.VALIDATORS ],
-      language: [ '', VIDEO_LANGUAGE.VALIDATORS ],
-      description: [ '', VIDEO_DESCRIPTION.VALIDATORS ],
-      tags: [ '' ]
-    })
-
+    this.form = this.formBuilder.group({})
     this.form.valueChanges.subscribe(data => this.onValueChanged(data))
   }
 
   ngOnInit () {
     this.buildForm()
 
-    this.videoCategories = this.serverService.getVideoCategories()
-    this.videoLicences = this.serverService.getVideoLicences()
-    this.videoLanguages = this.serverService.getVideoLanguages()
     this.videoPrivacies = this.serverService.getVideoPrivacies()
 
     const uuid: string = this.route.snapshot.params['uuid']
-
     this.videoService.getVideo(uuid)
       .switchMap(video => {
         return this.videoService
@@ -103,7 +69,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
         video => {
           this.video = new VideoEdit(video)
 
-          // We cannot set private a video that was not private anymore
+          // We cannot set private a video that was not private
           if (video.privacy !== VideoPrivacy.PRIVATE) {
             const newVideoPrivacies = []
             for (const p of this.videoPrivacies) {
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts
index 3c6951403..87db023bf 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -11,12 +11,12 @@ import '../../../assets/player/peertube-videojs-plugin'
 import { AuthService, ConfirmService } from '../../core'
 import { VideoBlacklistService } from '../../shared'
 import { Account } from '../../shared/account/account.model'
+import { VideoDetails } from '../../shared/video/video-details.model'
 import { Video } from '../../shared/video/video.model'
 import { MarkdownService } from '../shared'
 import { VideoDownloadComponent } from './video-download.component'
 import { VideoReportComponent } from './video-report.component'
 import { VideoShareComponent } from './video-share.component'
-import { VideoDetails } from '../../shared/video/video-details.model'
 
 @Component({
   selector: 'my-video-watch',
@@ -199,14 +199,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     return this.authService.isLoggedIn()
   }
 
-  canUserUpdateVideo () {
-    return this.video.isUpdatableBy(this.authService.getUser())
-  }
-
-  isVideoRemovable () {
-    return this.video.isRemovableBy(this.authService.getUser())
-  }
-
   isVideoBlacklistable () {
     return this.video.isBlackistableBy(this.authService.getUser())
   }
-- 
cgit v1.2.3


From 4cc66133abf1e37873316999b660c93ab92eb4a0 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Thu, 7 Dec 2017 14:48:47 +0100
Subject: Design video update

---
 .../+video-edit/shared/video-edit.component.html   | 130 ++++++++++----------
 .../+video-edit/shared/video-edit.component.scss   | 136 ++++++++++++++++++---
 .../videos/+video-edit/video-update.component.html |  19 ++-
 .../videos/+video-edit/video-update.component.ts   |  13 +-
 .../videos/+video-watch/video-watch.component.scss |   1 +
 .../videos/shared/video-description.component.html |   4 +-
 .../videos/shared/video-description.component.scss |   6 +-
 client/src/assets/images/global/validate.svg       |  14 +++
 client/src/sass/_mixins.scss                       |  22 +++-
 9 files changed, 236 insertions(+), 109 deletions(-)
 create mode 100644 client/src/assets/images/global/validate.svg

(limited to 'client')

diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.html b/client/src/app/videos/+video-edit/shared/video-edit.component.html
index e087b71a4..a6b753166 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.component.html
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.html
@@ -1,85 +1,85 @@
-<div [formGroup]="form">
-  <div class="form-group">
-    <label for="name">Name</label>
-    <input
-      type="text" class="form-control" id="name"
-      formControlName="name"
-    >
-    <div *ngIf="formErrors.name" class="alert alert-danger">
-      {{ formErrors.name }}
-    </div>
-  </div>
+<div class="video-edit row" [formGroup]="form">
 
-  <div class="form-group">
-    <label for="privacy">Privacy</label>
-    <select class="form-control" id="privacy" formControlName="privacy">
-      <option></option>
-      <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option>
-    </select>
+  <div class="col-md-8">
+    <div class="form-group">
+      <label for="name">Title</label>
+      <input type="text" id="name" formControlName="name" />
+      <div *ngIf="formErrors.name" class="form-error">
+        {{ formErrors.name }}
+      </div>
+    </div>
 
-    <div *ngIf="formErrors.privacy" class="alert alert-danger">
-      {{ formErrors.privacy }}
+    <div class="form-group">
+      <label class="label-tags">Tags</label> <span class="little-information">(press enter to add the tag)</span>
+      <tag-input
+          [ngModel]="tags" [validators]="tagValidators" [errorMessages]="tagValidatorsMessages"
+          formControlName="tags" maxItems="5" modelAsStrings="true"
+      ></tag-input>
     </div>
-  </div>
 
-  <div class="form-group">
-    <input
-      type="checkbox" id="nsfw"
-      formControlName="nsfw"
-    >
-    <label for="nsfw">This video contains mature or explicit content</label>
+    <div class="form-group">
+      <label for="description">Description</label>
+      <my-video-description formControlName="description"></my-video-description>
+
+      <div *ngIf="formErrors.description" class="form-error">
+        {{ formErrors.description }}
+      </div>
+    </div>
   </div>
 
-  <div class="form-group">
-    <label for="category">Category</label>
-    <select class="form-control" id="category" formControlName="category">
-      <option></option>
-      <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option>
-    </select>
+  <div class="col-md-4">
+    <div class="form-group">
+      <label for="category">Category</label>
+      <select id="category" formControlName="category">
+        <option></option>
+        <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option>
+      </select>
 
-    <div *ngIf="formErrors.category" class="alert alert-danger">
-      {{ formErrors.category }}
+      <div *ngIf="formErrors.category" class="form-error">
+        {{ formErrors.category }}
+      </div>
     </div>
-  </div>
 
-  <div class="form-group">
-    <label for="licence">Licence</label>
-    <select class="form-control" id="licence" formControlName="licence">
-      <option></option>
-      <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option>
-    </select>
+    <div class="form-group">
+      <label for="licence">Licence</label>
+      <select id="licence" formControlName="licence">
+        <option></option>
+        <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option>
+      </select>
 
-    <div *ngIf="formErrors.licence" class="alert alert-danger">
-      {{ formErrors.licence }}
+      <div *ngIf="formErrors.licence" class="form-error">
+        {{ formErrors.licence }}
+      </div>
     </div>
-  </div>
 
-  <div class="form-group">
-    <label for="language">Language</label>
-    <select class="form-control" id="language" formControlName="language">
-      <option></option>
-      <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option>
-    </select>
+    <div class="form-group">
+      <label for="language">Language</label>
+      <select id="language" formControlName="language">
+        <option></option>
+        <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option>
+      </select>
 
-    <div *ngIf="formErrors.language" class="alert alert-danger">
-      {{ formErrors.language }}
+      <div *ngIf="formErrors.language" class="form-error">
+        {{ formErrors.language }}
+      </div>
     </div>
-  </div>
 
-  <div class="form-group">
-    <label class="label-tags">Tags</label> <span class="little-information">(press enter to add the tag)</span>
-    <tag-input
-      [ngModel]="tags" [validators]="tagValidators" [errorMessages]="tagValidatorsMessages"
-      formControlName="tags" maxItems="5" modelAsStrings="true"
-    ></tag-input>
-  </div>
+    <div class="form-group">
+      <label for="privacy">Privacy</label>
+      <select id="privacy" formControlName="privacy">
+        <option></option>
+        <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option>
+      </select>
 
-  <div class="form-group">
-    <label for="description">Description</label>
-    <my-video-description formControlName="description"></my-video-description>
+      <div *ngIf="formErrors.privacy" class="form-error">
+        {{ formErrors.privacy }}
+      </div>
+    </div>
 
-    <div *ngIf="formErrors.description" class="alert alert-danger">
-      {{ formErrors.description }}
+    <div class="form-group form-group-checkbox">
+      <input type="checkbox" id="nsfw" formControlName="nsfw" />
+      <label for="nsfw">This video contains mature or explicit content</label>
     </div>
+
   </div>
 </div>
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.scss b/client/src/app/videos/+video-edit/shared/video-edit.component.scss
index 9ee0c520c..0af48fabe 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.component.scss
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.scss
@@ -1,3 +1,121 @@
+.video-edit {
+  height: 100%;
+
+  .form-group {
+    margin-bottom: 25px;
+  }
+
+  input {
+    @include peertube-input-text(100%);
+    display: block;
+
+    &[type=checkbox] {
+      outline: 0;
+    }
+  }
+
+  select {
+    @include peertube-select(100%);
+  }
+
+  input, select {
+    font-size: 15px
+  }
+
+  .form-group-checkbox {
+    display: flex;
+    align-items: center;
+
+    label {
+      font-weight: $font-regular;
+      margin: 0;
+    }
+
+    input {
+      width: 10px;
+      margin-right: 10px;
+    }
+  }
+}
+
+.submit-container {
+  text-align: right;
+  position: relative;
+  bottom: $button-height;
+
+  .submit-button {
+    @include peertube-button;
+    @include orange-button;
+
+    display: inline-block;
+
+    input {
+      cursor: inherit;
+      background-color: inherit;
+      border: none;
+      padding: 0;
+    }
+
+    .icon.icon-validate {
+      @include icon(20px);
+
+      cursor: inherit;
+      position: relative;
+      top: -1px;
+      margin-right: 4px;
+      background-image: url('../../../../assets/images/global/validate.svg');
+    }
+  }
+}
+
+/deep/ {
+  .ng2-tag-input {
+    border: none !important;
+  }
+
+  .ng2-tags-container {
+    display: flex;
+    align-items: center;
+    border: 1px solid #C6C6C6;
+    border-radius: 3px;
+    padding: 5px !important;
+  }
+
+  tag {
+    background-color: #E5E5E5 !important;
+    border-radius: 3px !important;
+    font-size: 15px !important;
+    color: #000 !important;
+    height: 30px !important;
+    line-height: 30px !important;
+    margin: 0 5px 0 0 !important;
+    cursor: default !important;
+    padding: 0 8px 0 10px !important;
+
+    div {
+      height: 100% !important;
+    }
+  }
+
+  delete-icon {
+    cursor: pointer !important;
+    height: auto !important;
+    vertical-align: middle !important;
+    padding-left: 6px !important;
+
+    svg {
+      height: auto !important;
+      vertical-align: middle !important;
+      fill: #585858 !important;
+    }
+
+    &:hover {
+      transform: none !important;
+    }
+  }
+}
+
+
 .btn-file {
   position: relative;
   overflow: hidden;
@@ -20,24 +138,6 @@
   display: block;
 }
 
-.form-group {
-  margin-bottom: 10px;
-}
-
-div.tags {
-  height: 40px;
-  font-size: 20px;
-  margin-top: 20px;
-
-  .tag {
-    margin-right: 10px;
-
-    .remove {
-      cursor: pointer;
-    }
-  }
-}
-
 div.file-to-upload {
   height: 40px;
 
diff --git a/client/src/app/videos/+video-edit/video-update.component.html b/client/src/app/videos/+video-edit/video-update.component.html
index c57f35da0..3163495bf 100644
--- a/client/src/app/videos/+video-edit/video-update.component.html
+++ b/client/src/app/videos/+video-edit/video-update.component.html
@@ -1,7 +1,7 @@
-<div class="row">
-  <div class="content-padding">
-
-  <h3>Update {{ video?.name }}</h3>
+<div class="margin-content">
+  <div class="title-page title-page-single">
+    Update {{ video?.name }}
+  </div>
 
   <form novalidate [formGroup]="form">
 
@@ -10,12 +10,11 @@
       [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies"
     ></my-video-edit>
 
-    <div class="form-group">
-      <input
-        type="button" value="Update" class="btn btn-default form-control"
-        (click)="update()"
-      >
+    <div class="submit-container">
+      <div class="submit-button" [ngClass]="{ disabled: !form.valid }">
+        <span class="icon icon-validate"></span>
+        <input type="button" value="Update" (click)="update()" />
+      </div>
     </div>
   </form>
-  </div>
 </div>
diff --git a/client/src/app/videos/+video-edit/video-update.component.ts b/client/src/app/videos/+video-edit/video-update.component.ts
index 01ab0a716..d1da8b6d8 100644
--- a/client/src/app/videos/+video-edit/video-update.component.ts
+++ b/client/src/app/videos/+video-edit/video-update.component.ts
@@ -5,19 +5,10 @@ import { NotificationsService } from 'angular2-notifications'
 import 'rxjs/add/observable/forkJoin'
 import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum'
 import { ServerService } from '../../core'
-import {
-  FormReactive,
-  VIDEO_CATEGORY,
-  VIDEO_DESCRIPTION,
-  VIDEO_LANGUAGE,
-  VIDEO_LICENCE,
-  VIDEO_NAME,
-  VIDEO_PRIVACY,
-  VIDEO_TAGS
-} from '../../shared'
+import { FormReactive } from '../../shared'
 import { ValidatorMessage } from '../../shared/forms/form-validators'
-import { VideoService } from '../../shared/video/video.service'
 import { VideoEdit } from '../../shared/video/video-edit.model'
+import { VideoService } from '../../shared/video/video.service'
 
 @Component({
   selector: 'my-videos-update',
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss
index 6973619b2..2ccfd2749 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.scss
+++ b/client/src/app/videos/+video-watch/video-watch.component.scss
@@ -70,6 +70,7 @@
 
             &.icon-more {
               background-image: url('../../../assets/images/video/more.svg');
+              top: -1px;
             }
           }
 
diff --git a/client/src/app/videos/shared/video-description.component.html b/client/src/app/videos/shared/video-description.component.html
index 7a228857c..da66a9753 100644
--- a/client/src/app/videos/shared/video-description.component.html
+++ b/client/src/app/videos/shared/video-description.component.html
@@ -1,6 +1,6 @@
 <textarea
-    [(ngModel)]="description" (ngModelChange)="onModelChange()"
-    id="description" class="form-control" placeholder="My super video">
+  [(ngModel)]="description" (ngModelChange)="onModelChange()"
+  id="description" placeholder="My super video">
 </textarea>
 
 <tabset #staticTabs class="previews">
diff --git a/client/src/app/videos/shared/video-description.component.scss b/client/src/app/videos/shared/video-description.component.scss
index d8d73e846..6ef81ae58 100644
--- a/client/src/app/videos/shared/video-description.component.scss
+++ b/client/src/app/videos/shared/video-description.component.scss
@@ -1,11 +1,15 @@
 textarea {
+  @include peertube-input-text(100%);
+
+  font-size: 15px;
   height: 150px;
 }
 
 .previews /deep/ {
+  font-size: 15px !important;
+
   .nav {
     margin-top: 10px;
-    font-size: 0.9em;
   }
 
   .tab-content {
diff --git a/client/src/assets/images/global/validate.svg b/client/src/assets/images/global/validate.svg
new file mode 100644
index 000000000..5c7ee9d14
--- /dev/null
+++ b/client/src/assets/images/global/validate.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Artboard-4" transform="translate(-400.000000, -1134.000000)" stroke="#ffffff" stroke-width="2">
+            <g id="Extras" transform="translate(48.000000, 1046.000000)">
+                <g id="yes" transform="translate(352.000000, 88.000000)">
+                    <circle id="Oval-1" cx="12" cy="12" r="10"></circle>
+                    <polyline id="Path-288" stroke-linecap="round" stroke-linejoin="round" points="8.5 12.5 10.5 14.5 15.5 9.5"></polyline>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/sass/_mixins.scss b/client/src/sass/_mixins.scss
index 14d9b5044..121e16e10 100644
--- a/client/src/sass/_mixins.scss
+++ b/client/src/sass/_mixins.scss
@@ -23,20 +23,28 @@
   color: #fff;
   background-color: $orange-color;
 
-  &:hover, &:active, &:focus, &[disabled] {
+  &:hover, &:active, &:focus, &[disabled], &.disabled {
     color: #fff;
     background-color: $orange-hoover-color;
   }
+
+  &[disabled], &.disabled {
+    cursor: default;
+  }
 }
 
 @mixin grey-button {
   background-color: $grey-color;
   color: #585858;
 
-  &:hover, &:active, &:focus, &[disabled] {
+  &:hover, &:active, &:focus, &[disabled], &.disabled {
     color: #585858;
     background-color: $grey-hoover-color;
   }
+
+  &[disabled], &.disabled {
+    cursor: default;
+  }
 }
 
 @mixin peertube-button {
@@ -72,3 +80,13 @@
   vertical-align: middle;
   cursor: pointer;
 }
+
+
+@mixin peertube-select ($width) {
+  background-color: #fff;
+  border: 1px solid #C6C6C6;
+  height: $button-height;
+  width: $width;
+  border-radius: 3px;
+  padding-left: 15px;
+}
-- 
cgit v1.2.3


From 27e1a06c331278e5d37bc5172ee7e4fc968e4b5e Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Thu, 7 Dec 2017 16:32:06 +0100
Subject: First step upload with new design

---
 .../+video-edit/shared/video-edit.component.html   |   1 +
 .../+video-edit/shared/video-edit.component.scss   |  35 -----
 .../videos/+video-edit/video-add.component.html    | 142 ++++-----------------
 .../videos/+video-edit/video-add.component.scss    |  60 +++++++++
 .../app/videos/+video-edit/video-add.component.ts  | 126 +++++++-----------
 client/src/assets/images/video/upload.svg          |  16 +++
 6 files changed, 145 insertions(+), 235 deletions(-)
 create mode 100644 client/src/app/videos/+video-edit/video-add.component.scss
 create mode 100644 client/src/assets/images/video/upload.svg

(limited to 'client')

diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.html b/client/src/app/videos/+video-edit/shared/video-edit.component.html
index a6b753166..8c071ce12 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.component.html
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.html
@@ -67,6 +67,7 @@
     <div class="form-group">
       <label for="privacy">Privacy</label>
       <select id="privacy" formControlName="privacy">
+
         <option></option>
         <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option>
       </select>
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.scss b/client/src/app/videos/+video-edit/shared/video-edit.component.scss
index 0af48fabe..2d0bfc36e 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.component.scss
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.scss
@@ -115,42 +115,7 @@
   }
 }
 
-
-.btn-file {
-  position: relative;
-  overflow: hidden;
-  display: block;
-}
-
-.btn-file input[type=file] {
-  position: absolute;
-  top: 0;
-  right: 0;
-  min-width: 100%;
-  min-height: 100%;
-  font-size: 100px;
-  text-align: right;
-  filter: alpha(opacity=0);
-  opacity: 0;
-  outline: none;
-  background: white;
-  cursor: inherit;
-  display: block;
-}
-
-div.file-to-upload {
-  height: 40px;
-
-  .glyphicon-remove {
-    cursor: pointer;
-  }
-}
-
 .little-information {
   font-size: 0.8em;
   font-style: italic;
 }
-
-.label-tags {
-  margin-bottom: 0;
-}
diff --git a/client/src/app/videos/+video-edit/video-add.component.html b/client/src/app/videos/+video-edit/video-add.component.html
index b4e0f9f7c..f8355f3db 100644
--- a/client/src/app/videos/+video-edit/video-add.component.html
+++ b/client/src/app/videos/+video-edit/video-add.component.html
@@ -1,141 +1,45 @@
-<div class="row">
-  <div class="content-padding">
+<div class="margin-content">
+  <div class="title-page title-page-single">
+    Upload your video
+  </div>
 
-    <h3>Upload a video</h3>
+  <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
 
-    <div *ngIf="error !== undefined" class="alert alert-danger">{{ error }}</div>
+  <div class="upload-video-container">
+    <div class="upload-video">
+      <div class="icon icon-upload"></div>
 
-    <form novalidate [formGroup]="form">
-      <div class="form-group">
-        <label for="name">Name</label>
-        <input
-          type="text" class="form-control" id="name"
-          formControlName="name"
-        >
-        <div *ngIf="formErrors.name" class="alert alert-danger">
-          {{ formErrors.name }}
-        </div>
+      <div class="button-file">
+        <span>Select the file to upload</span>
+        <input #videofileInput type="file" name="videofile" id="videofile" (change)="fileChange($event)" />
       </div>
 
       <div class="form-group">
-        <label for="privacy">Privacy</label>
-        <select class="form-control" id="privacy" formControlName="privacy">
-          <option></option>
+        <select [(ngModel)]="firstStepPrivacy">
           <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option>
         </select>
-
-        <div *ngIf="formErrors.privacy" class="alert alert-danger">
-          {{ formErrors.privacy }}
-        </div>
-      </div>
-
-      <div class="form-group">
-        <input
-          type="checkbox" id="nsfw"
-          formControlName="nsfw"
-        >
-        <label for="nsfw">This video contains mature or explicit content</label>
       </div>
 
       <div class="form-group">
-        <label for="category">Channel</label>
-        <select class="form-control" id="channelId" formControlName="channelId">
+        <select [(ngModel)]="firstStepChannel">
           <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option>
         </select>
-
-        <div *ngIf="formErrors.channelId" class="alert alert-danger">
-          {{ formErrors.channelId }}
-        </div>
       </div>
+    </div>
 
-      <div class="form-group">
-        <label for="category">Category</label>
-        <select class="form-control" id="category" formControlName="category">
-          <option></option>
-          <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option>
-        </select>
 
-        <div *ngIf="formErrors.category" class="alert alert-danger">
-          {{ formErrors.category }}
-        </div>
-      </div>
-
-      <div class="form-group">
-        <label for="licence">Licence</label>
-        <select class="form-control" id="licence" formControlName="licence">
-          <option></option>
-          <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option>
-        </select>
-
-        <div *ngIf="formErrors.licence" class="alert alert-danger">
-          {{ formErrors.licence }}
-        </div>
-      </div>
-
-      <div class="form-group">
-        <label for="language">Language</label>
-        <select class="form-control" id="language" formControlName="language">
-          <option></option>
-          <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option>
-        </select>
+    <form *ngIf="isUploadingVideo" novalidate [formGroup]="form">
+      <my-video-edit
+          [form]="form" [formErrors]="formErrors"
+          [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies"
+      ></my-video-edit>
 
-        <div *ngIf="formErrors.language" class="alert alert-danger">
-          {{ formErrors.language }}
+      <div class="submit-container">
+        <div class="submit-button" [ngClass]="{ disabled: !form.valid }">
+          <span class="icon icon-validate"></span>
+          <input type="button" value="Publish" (click)="upload()" />
         </div>
       </div>
-
-      <div class="form-group">
-        <label class="label-tags">Tags</label> <span class="little-information">(press enter to add the tag)</span>
-        <tag-input
-          [ngModel]="tags" [validators]="tagValidators" [errorMessages]="tagValidatorsMessages"
-          formControlName="tags" maxItems="5" modelAsStrings="true"
-        ></tag-input>
-      </div>
-
-      <div class="form-group">
-        <label for="videofile">File</label>
-        <div class="btn btn-default btn-file">
-          <span>Select the video...</span>
-          <input #videofileInput type="file" name="videofile" id="videofile" (change)="fileChange($event)" />
-          <input type="hidden" name="videofileHidden" formControlName="videofile"/>
-        </div>
-      </div>
-
-      <div class="file-to-upload">
-        <div class="file" *ngIf="filename">
-          <span class="filename">{{ filename }}</span>
-          <span class="glyphicon glyphicon-remove" (click)="removeFile()"></span>
-        </div>
-      </div>
-
-      <div *ngIf="formErrors.videofile" class="alert alert-danger">
-        {{ formErrors.videofile }}
-      </div>
-
-      <div class="form-group">
-        <label for="description">Description</label>
-        <my-video-description formControlName="description"></my-video-description>
-
-        <div *ngIf="formErrors.description" class="alert alert-danger">
-          {{ formErrors.description }}
-        </div>
-      </div>
-
-      <div class="progress">
-        <progressbar [value]="progressPercent" max="100">
-          <ng-template [ngIf]="progressPercent === 100">
-            <span class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></span>
-            Server is processing the video
-          </ng-template>
-        </progressbar>
-      </div>
-
-      <div class="form-group">
-        <input
-          type="button" value="Upload" class="btn btn-default form-control"
-          (click)="upload()"
-        >
-      </div>
     </form>
   </div>
 </div>
diff --git a/client/src/app/videos/+video-edit/video-add.component.scss b/client/src/app/videos/+video-edit/video-add.component.scss
new file mode 100644
index 000000000..25dfd40d2
--- /dev/null
+++ b/client/src/app/videos/+video-edit/video-add.component.scss
@@ -0,0 +1,60 @@
+.upload-video-container {
+  border-radius: 3px;
+  background-color: #F7F7F7;
+  border: 3px solid #EAEAEA;
+  width: 100%;
+  height: 440px;
+  text-align: center;
+  margin-top: 40px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+
+  .upload-video {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+
+    .icon.icon-upload {
+      @include icon(90px);
+      margin-bottom: 25px;
+
+      background-image: url('../../../assets/images/video/upload.svg');
+    }
+
+    .button-file {
+      position: relative;
+      overflow: hidden;
+      display: inline-block;
+      margin-bottom: 70px;
+
+      @include peertube-button;
+      @include orange-button;
+
+      input[type=file] {
+        position: absolute;
+        top: 0;
+        right: 0;
+        min-width: 100%;
+        min-height: 100%;
+        font-size: 100px;
+        text-align: right;
+        filter: alpha(opacity=0);
+        opacity: 0;
+        outline: none;
+        background: white;
+        cursor: inherit;
+        display: block;
+      }
+    }
+
+    select {
+      @include peertube-select(auto);
+
+      display: inline-block;
+      font-size: 15px
+    }
+  }
+}
+
+
diff --git a/client/src/app/videos/+video-edit/video-add.component.ts b/client/src/app/videos/+video-edit/video-add.component.ts
index 989addbd7..071f9a28b 100644
--- a/client/src/app/videos/+video-edit/video-add.component.ts
+++ b/client/src/app/videos/+video-edit/video-add.component.ts
@@ -6,61 +6,33 @@ import { NotificationsService } from 'angular2-notifications'
 import { VideoService } from 'app/shared/video/video.service'
 import { VideoCreate } from '../../../../../shared'
 import { AuthService, ServerService } from '../../core'
-import {
-  FormReactive,
-  VIDEO_CATEGORY,
-  VIDEO_CHANNEL,
-  VIDEO_DESCRIPTION,
-  VIDEO_FILE,
-  VIDEO_LANGUAGE,
-  VIDEO_LICENCE,
-  VIDEO_NAME,
-  VIDEO_PRIVACY,
-  VIDEO_TAGS
-} from '../../shared'
+import { FormReactive } from '../../shared'
+import { ValidatorMessage } from '../../shared/forms/form-validators'
+import { VideoEdit } from '../../shared/video/video-edit.model'
 
 @Component({
   selector: 'my-videos-add',
-  styleUrls: [ './shared/video-edit.component.scss' ],
-  templateUrl: './video-add.component.html'
+  templateUrl: './video-add.component.html',
+  styleUrls: [
+    './shared/video-edit.component.scss',
+    './video-add.component.scss'
+  ]
 })
 
 export class VideoAddComponent extends FormReactive implements OnInit {
   @ViewChild('videofileInput') videofileInput
 
+  isUploadingVideo = false
   progressPercent = 0
-  tags: string[] = []
-  videoCategories = []
-  videoLicences = []
-  videoLanguages = []
-  videoPrivacies = []
-  userVideoChannels = []
-
-  tagValidators = VIDEO_TAGS.VALIDATORS
-  tagValidatorsMessages = VIDEO_TAGS.MESSAGES
 
-  error: string
+  error: string = null
   form: FormGroup
-  formErrors = {
-    name: '',
-    privacy: '',
-    category: '',
-    licence: '',
-    language: '',
-    channelId: '',
-    description: '',
-    videofile: ''
-  }
-  validationMessages = {
-    name: VIDEO_NAME.MESSAGES,
-    privacy: VIDEO_PRIVACY.MESSAGES,
-    category: VIDEO_CATEGORY.MESSAGES,
-    licence: VIDEO_LICENCE.MESSAGES,
-    language: VIDEO_LANGUAGE.MESSAGES,
-    channelId: VIDEO_CHANNEL.MESSAGES,
-    description: VIDEO_DESCRIPTION.MESSAGES,
-    videofile: VIDEO_FILE.MESSAGES
-  }
+  formErrors: { [ id: string ]: string } = {}
+  validationMessages: ValidatorMessage = {}
+  userVideoChannels = []
+  videoPrivacies = []
+  firstStepPrivacy = 0
+  firstStepChannel = 0
 
   constructor (
     private formBuilder: FormBuilder,
@@ -73,35 +45,17 @@ export class VideoAddComponent extends FormReactive implements OnInit {
     super()
   }
 
-  get filename () {
-    return this.form.value['videofile']
-  }
-
   buildForm () {
-    this.form = this.formBuilder.group({
-      name: [ '', VIDEO_NAME.VALIDATORS ],
-      nsfw: [ false ],
-      privacy: [ '', VIDEO_PRIVACY.VALIDATORS ],
-      category: [ '', VIDEO_CATEGORY.VALIDATORS ],
-      licence: [ '', VIDEO_LICENCE.VALIDATORS ],
-      language: [ '', VIDEO_LANGUAGE.VALIDATORS ],
-      channelId: [ '', VIDEO_CHANNEL.VALIDATORS ],
-      description: [ '', VIDEO_DESCRIPTION.VALIDATORS ],
-      videofile: [ '', VIDEO_FILE.VALIDATORS ],
-      tags: [ '' ]
-    })
-
+    this.form = this.formBuilder.group({})
     this.form.valueChanges.subscribe(data => this.onValueChanged(data))
   }
 
   ngOnInit () {
-    this.videoCategories = this.serverService.getVideoCategories()
-    this.videoLicences = this.serverService.getVideoLicences()
-    this.videoLanguages = this.serverService.getVideoLanguages()
-    this.videoPrivacies = this.serverService.getVideoPrivacies()
-
     this.buildForm()
 
+    this.videoPrivacies = this.serverService.getVideoPrivacies()
+    this.firstStepPrivacy = this.videoPrivacies[0].id
+
     this.authService.userInformationLoaded
       .subscribe(
         () => {
@@ -112,21 +66,13 @@ export class VideoAddComponent extends FormReactive implements OnInit {
           if (Array.isArray(videoChannels) === false) return
 
           this.userVideoChannels = videoChannels.map(v => ({ id: v.id, label: v.name }))
-
-          this.form.patchValue({ channelId: this.userVideoChannels[0].id })
+          this.firstStepChannel = this.userVideoChannels[0].id
         }
       )
   }
 
-  // The goal is to keep reactive form validation (required field)
-  // https://stackoverflow.com/a/44238894
   fileChange ($event) {
-    this.form.controls['videofile'].setValue($event.target.files[0].name)
-  }
-
-  removeFile () {
-    this.videofileInput.nativeElement.value = ''
-    this.form.controls['videofile'].setValue('')
+    console.log('uploading file ?')
   }
 
   checkForm () {
@@ -135,11 +81,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
     return this.form.valid
   }
 
-  upload () {
-    if (this.checkForm() === false) {
-      return
-    }
-
+  uploadFirstStep () {
     const formValue: VideoCreate = this.form.value
 
     const name = formValue.name
@@ -193,4 +135,26 @@ export class VideoAddComponent extends FormReactive implements OnInit {
       }
     )
   }
+
+  updateSecondStep () {
+    if (this.checkForm() === false) {
+      return
+    }
+
+    const video = new VideoEdit(this.form.value)
+
+    this.videoService.updateVideo(video)
+      .subscribe(
+        () => {
+          this.notificationsService.success('Success', 'Video published.')
+          this.router.navigate([ '/videos/watch', video.uuid ])
+        },
+
+        err => {
+          this.error = 'Cannot update the video.'
+          console.error(err)
+        }
+      )
+
+  }
 }
diff --git a/client/src/assets/images/video/upload.svg b/client/src/assets/images/video/upload.svg
new file mode 100644
index 000000000..c5b7cb443
--- /dev/null
+++ b/client/src/assets/images/video/upload.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>cloud-upload</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g id="Artboard-4" transform="translate(-312.000000, -775.000000)" stroke="#C6C6C6" stroke-width="2">
+            <g id="307" transform="translate(312.000000, 775.000000)">
+                <path d="M8,18 L5,18 L5,18 C2.790861,18 1,16.209139 1,14 C1,11.790861 2.790861,10 5,10 C5.35840468,10 5.70579988,10.0471371 6.03632437,10.1355501 C6.01233106,9.92702603 6,9.71495305 6,9.5 C6,6.46243388 8.46243388,4 11.5,4 C14.0673313,4 16.2238156,5.7590449 16.8299648,8.1376465 C17.2052921,8.04765874 17.5970804,8 18,8 C20.7614237,8 23,10.2385763 23,13 C23,15.7614237 20.7614237,18 18,18 L16,18" id="Combined-Shape" stroke-linejoin="round"></path>
+                <path d="M12,13 L12,21" id="Path-58"></path>
+                <polyline id="Path-59" stroke-linejoin="round" transform="translate(12.000000, 12.500000) scale(1, -1) translate(-12.000000, -12.500000) " points="15 11 12 14 9 11"></polyline>
+            </g>
+        </g>
+    </g>
+</svg>
-- 
cgit v1.2.3


From baeefe22caf8ae6cb58dc40754e5db14b50168bf Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Thu, 7 Dec 2017 17:22:44 +0100
Subject: First upload step is ok

---
 client/src/app/core/server/server.service.ts       | 37 +++++++++------
 .../videos/+video-edit/video-add.component.html    | 32 ++++++-------
 .../app/videos/+video-edit/video-add.component.ts  | 52 +++++++++-------------
 3 files changed, 61 insertions(+), 60 deletions(-)

(limited to 'client')

diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts
index cbc4074c9..43a836c5a 100644
--- a/client/src/app/core/server/server.service.ts
+++ b/client/src/app/core/server/server.service.ts
@@ -1,5 +1,7 @@
-import { Injectable } from '@angular/core'
 import { HttpClient } from '@angular/common/http'
+import { Injectable } from '@angular/core'
+import 'rxjs/add/operator/do'
+import { ReplaySubject } from 'rxjs/ReplaySubject'
 
 import { ServerConfig } from '../../../../../shared'
 
@@ -8,6 +10,11 @@ export class ServerService {
   private static BASE_CONFIG_URL = API_URL + '/api/v1/config/'
   private static BASE_VIDEO_URL = API_URL + '/api/v1/videos/'
 
+  videoPrivaciesLoaded = new ReplaySubject<boolean>(1)
+  videoCategoriesLoaded = new ReplaySubject<boolean>(1)
+  videoLicencesLoaded = new ReplaySubject<boolean>(1)
+  videoLanguagesLoaded = new ReplaySubject<boolean>(1)
+
   private config: ServerConfig = {
     signup: {
       allowed: false
@@ -29,19 +36,19 @@ export class ServerService {
   }
 
   loadVideoCategories () {
-    return this.loadVideoAttributeEnum('categories', this.videoCategories)
+    return this.loadVideoAttributeEnum('categories', this.videoCategories, this.videoCategoriesLoaded)
   }
 
   loadVideoLicences () {
-    return this.loadVideoAttributeEnum('licences', this.videoLicences)
+    return this.loadVideoAttributeEnum('licences', this.videoLicences, this.videoLicencesLoaded)
   }
 
   loadVideoLanguages () {
-    return this.loadVideoAttributeEnum('languages', this.videoLanguages)
+    return this.loadVideoAttributeEnum('languages', this.videoLanguages, this.videoLanguagesLoaded)
   }
 
   loadVideoPrivacies () {
-    return this.loadVideoAttributeEnum('privacies', this.videoPrivacies)
+    return this.loadVideoAttributeEnum('privacies', this.videoPrivacies, this.videoPrivaciesLoaded)
   }
 
   getConfig () {
@@ -66,17 +73,19 @@ export class ServerService {
 
   private loadVideoAttributeEnum (
     attributeName: 'categories' | 'licences' | 'languages' | 'privacies',
-    hashToPopulate: { id: number, label: string }[]
+    hashToPopulate: { id: number, label: string }[],
+    notifier: ReplaySubject<boolean>
   ) {
     return this.http.get(ServerService.BASE_VIDEO_URL + attributeName)
-               .subscribe(data => {
-                 Object.keys(data)
-                       .forEach(dataKey => {
-                         hashToPopulate.push({
-                           id: parseInt(dataKey, 10),
-                           label: data[dataKey]
-                         })
-                       })
+      .do(() => notifier.next(true))
+       .subscribe(data => {
+         Object.keys(data)
+               .forEach(dataKey => {
+                 hashToPopulate.push({
+                   id: parseInt(dataKey, 10),
+                   label: data[dataKey]
+                 })
                })
+       })
   }
 }
diff --git a/client/src/app/videos/+video-edit/video-add.component.html b/client/src/app/videos/+video-edit/video-add.component.html
index f8355f3db..78e5bb70e 100644
--- a/client/src/app/videos/+video-edit/video-add.component.html
+++ b/client/src/app/videos/+video-edit/video-add.component.html
@@ -5,13 +5,13 @@
 
   <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
 
-  <div class="upload-video-container">
+  <div *ngIf="!isUploadingVideo" class="upload-video-container">
     <div class="upload-video">
       <div class="icon icon-upload"></div>
 
       <div class="button-file">
         <span>Select the file to upload</span>
-        <input #videofileInput type="file" name="videofile" id="videofile" (change)="fileChange($event)" />
+        <input #videofileInput type="file" name="videofile" id="videofile" (change)="fileChange()" />
       </div>
 
       <div class="form-group">
@@ -26,20 +26,20 @@
         </select>
       </div>
     </div>
+  </div>
 
-
-    <form *ngIf="isUploadingVideo" novalidate [formGroup]="form">
-      <my-video-edit
-          [form]="form" [formErrors]="formErrors"
-          [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies"
-      ></my-video-edit>
-
-      <div class="submit-container">
-        <div class="submit-button" [ngClass]="{ disabled: !form.valid }">
-          <span class="icon icon-validate"></span>
-          <input type="button" value="Publish" (click)="upload()" />
-        </div>
+  <!-- Hidden because we need to load the component -->
+  <form [hidden]="!isUploadingVideo" novalidate [formGroup]="form">
+    <my-video-edit
+        [form]="form" [formErrors]="formErrors"
+        [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies"
+    ></my-video-edit>
+
+    <div class="submit-container">
+      <div class="submit-button" [ngClass]="{ disabled: !form.valid }">
+        <span class="icon icon-validate"></span>
+        <input type="button" value="Publish" (click)="upload()" />
       </div>
-    </form>
-  </div>
+    </div>
+  </form>
 </div>
diff --git a/client/src/app/videos/+video-edit/video-add.component.ts b/client/src/app/videos/+video-edit/video-add.component.ts
index 071f9a28b..c2ee4ae2e 100644
--- a/client/src/app/videos/+video-edit/video-add.component.ts
+++ b/client/src/app/videos/+video-edit/video-add.component.ts
@@ -23,12 +23,14 @@ export class VideoAddComponent extends FormReactive implements OnInit {
   @ViewChild('videofileInput') videofileInput
 
   isUploadingVideo = false
+  videoUploaded = false
   progressPercent = 0
 
   error: string = null
   form: FormGroup
   formErrors: { [ id: string ]: string } = {}
   validationMessages: ValidatorMessage = {}
+
   userVideoChannels = []
   videoPrivacies = []
   firstStepPrivacy = 0
@@ -53,8 +55,12 @@ export class VideoAddComponent extends FormReactive implements OnInit {
   ngOnInit () {
     this.buildForm()
 
-    this.videoPrivacies = this.serverService.getVideoPrivacies()
-    this.firstStepPrivacy = this.videoPrivacies[0].id
+    this.serverService.videoCategoriesLoaded
+      .subscribe(
+        () => {
+          this.videoPrivacies = this.serverService.getVideoPrivacies()
+          this.firstStepPrivacy = this.videoPrivacies[0].id
+        })
 
     this.authService.userInformationLoaded
       .subscribe(
@@ -71,8 +77,8 @@ export class VideoAddComponent extends FormReactive implements OnInit {
       )
   }
 
-  fileChange ($event) {
-    console.log('uploading file ?')
+  fileChange () {
+    this.uploadFirstStep()
   }
 
   checkForm () {
@@ -82,38 +88,26 @@ export class VideoAddComponent extends FormReactive implements OnInit {
   }
 
   uploadFirstStep () {
-    const formValue: VideoCreate = this.form.value
-
-    const name = formValue.name
-    const privacy = formValue.privacy
-    const nsfw = formValue.nsfw
-    const category = formValue.category
-    const licence = formValue.licence
-    const language = formValue.language
-    const channelId = formValue.channelId
-    const description = formValue.description
-    const tags = formValue.tags
     const videofile = this.videofileInput.nativeElement.files[0]
+    const name = videofile.name
+    const privacy = this.firstStepPrivacy.toString()
+    const nsfw = false
+    const channelId = this.firstStepChannel.toString()
 
     const formData = new FormData()
     formData.append('name', name)
     formData.append('privacy', privacy.toString())
-    formData.append('category', '' + category)
     formData.append('nsfw', '' + nsfw)
-    formData.append('licence', '' + licence)
     formData.append('channelId', '' + channelId)
     formData.append('videofile', videofile)
 
-    // Language is optional
-    if (language) {
-      formData.append('language', '' + language)
-    }
-
-    formData.append('description', description)
-
-    for (let i = 0; i < tags.length; i++) {
-      formData.append(`tags[${i}]`, tags[i])
-    }
+    this.isUploadingVideo = true
+    this.form.patchValue({
+      name,
+      privacy,
+      nsfw,
+      channelId
+    })
 
     this.videoService.uploadVideo(formData).subscribe(
       event => {
@@ -121,10 +115,8 @@ export class VideoAddComponent extends FormReactive implements OnInit {
           this.progressPercent = Math.round(100 * event.loaded / event.total)
         } else if (event instanceof HttpResponse) {
           console.log('Video uploaded.')
-          this.notificationsService.success('Success', 'Video uploaded.')
 
-          // Display all the videos once it's finished
-          this.router.navigate([ '/videos/trending' ])
+          this.videoUploaded = true
         }
       },
 
-- 
cgit v1.2.3


From c182778e26b8478fae9d7dd0bf0687baf7b72fd1 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Thu, 7 Dec 2017 17:56:59 +0100
Subject: Add progress bar for video upload

---
 client/.bootstraprc                                | 10 +++++-----
 client/src/app/shared/shared.module.ts             |  3 ---
 .../videos/+video-edit/video-add.component.html    |  2 ++
 .../videos/+video-edit/video-add.component.scss    | 23 ++++++++++++++++++++++
 .../app/videos/+video-edit/video-add.component.ts  |  8 ++++----
 .../src/app/videos/+video-edit/video-add.module.ts |  4 +++-
 6 files changed, 37 insertions(+), 13 deletions(-)

(limited to 'client')

diff --git a/client/.bootstraprc b/client/.bootstraprc
index 6ceef4fe9..cc6768d43 100644
--- a/client/.bootstraprc
+++ b/client/.bootstraprc
@@ -84,19 +84,19 @@ styles:
   navs: true
   navbar: false
   breadcrumbs: false
-  pagination: true
+  pagination: false
   pager: false
-  labels: true
+  labels: false
   badges: false
   jumbotron: false
-  thumbnails: true
+  thumbnails: false
   alerts: true
-  progress-bars: true
+  progress-bars: false
   media: true
   list-group: false
   panels: true
   wells: false
-  responsive-embed: true
+  responsive-embed: false
   close: true
 
   # Components w/ JavaScript
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index bd9aee345..74f6f579d 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -6,7 +6,6 @@ import { RouterModule } from '@angular/router'
 
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown'
 import { ModalModule } from 'ngx-bootstrap/modal'
-import { ProgressbarModule } from 'ngx-bootstrap/progressbar'
 import { InfiniteScrollModule } from 'ngx-infinite-scroll'
 import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes'
 import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared'
@@ -34,7 +33,6 @@ import { VideoService } from './video/video.service'
 
     BsDropdownModule.forRoot(),
     ModalModule.forRoot(),
-    ProgressbarModule.forRoot(),
 
     DataTableModule,
     PrimeSharedModule,
@@ -59,7 +57,6 @@ import { VideoService } from './video/video.service'
 
     BsDropdownModule,
     ModalModule,
-    ProgressbarModule,
     DataTableModule,
     PrimeSharedModule,
     InfiniteScrollModule,
diff --git a/client/src/app/videos/+video-edit/video-add.component.html b/client/src/app/videos/+video-edit/video-add.component.html
index 78e5bb70e..6883f8280 100644
--- a/client/src/app/videos/+video-edit/video-add.component.html
+++ b/client/src/app/videos/+video-edit/video-add.component.html
@@ -28,6 +28,8 @@
     </div>
   </div>
 
+  <p-progressBar *ngIf="isUploadingVideo" [value]="videoUploadPercents"></p-progressBar>
+
   <!-- Hidden because we need to load the component -->
   <form [hidden]="!isUploadingVideo" novalidate [formGroup]="form">
     <my-video-edit
diff --git a/client/src/app/videos/+video-edit/video-add.component.scss b/client/src/app/videos/+video-edit/video-add.component.scss
index 25dfd40d2..dfdf7ff73 100644
--- a/client/src/app/videos/+video-edit/video-add.component.scss
+++ b/client/src/app/videos/+video-edit/video-add.component.scss
@@ -57,4 +57,27 @@
   }
 }
 
+p-progressBar {
+  margin-top: 50px;
+  margin-bottom: 40px;
+
+  /deep/ .ui-progressbar {
+    font-size: 15px !important;
+    color: #fff !important;
+    height: 30px !important;
+    line-height: 30px !important;
+    border-radius: 3px !important;
+    background-color: rgba(11, 204, 41, 0.16) !important;
+
+    .ui-progressbar-value {
+      background-color: #0BCC29 !important;
+    }
+
+    .ui-progressbar-label {
+      text-align: left;
+      padding-left: 18px;
+    }
+  }
+}
+
 
diff --git a/client/src/app/videos/+video-edit/video-add.component.ts b/client/src/app/videos/+video-edit/video-add.component.ts
index c2ee4ae2e..2409acfda 100644
--- a/client/src/app/videos/+video-edit/video-add.component.ts
+++ b/client/src/app/videos/+video-edit/video-add.component.ts
@@ -24,7 +24,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
 
   isUploadingVideo = false
   videoUploaded = false
-  progressPercent = 0
+  videoUploadPercents = 0
 
   error: string = null
   form: FormGroup
@@ -55,7 +55,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
   ngOnInit () {
     this.buildForm()
 
-    this.serverService.videoCategoriesLoaded
+    this.serverService.videoPrivaciesLoaded
       .subscribe(
         () => {
           this.videoPrivacies = this.serverService.getVideoPrivacies()
@@ -112,7 +112,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
     this.videoService.uploadVideo(formData).subscribe(
       event => {
         if (event.type === HttpEventType.UploadProgress) {
-          this.progressPercent = Math.round(100 * event.loaded / event.total)
+          this.videoUploadPercents = Math.round(100 * event.loaded / event.total)
         } else if (event instanceof HttpResponse) {
           console.log('Video uploaded.')
 
@@ -122,7 +122,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
 
       err => {
         // Reset progress
-        this.progressPercent = 0
+        this.videoUploadPercents = 0
         this.error = err.message
       }
     )
diff --git a/client/src/app/videos/+video-edit/video-add.module.ts b/client/src/app/videos/+video-edit/video-add.module.ts
index f58d12dac..1efecdf4d 100644
--- a/client/src/app/videos/+video-edit/video-add.module.ts
+++ b/client/src/app/videos/+video-edit/video-add.module.ts
@@ -1,4 +1,5 @@
 import { NgModule } from '@angular/core'
+import { ProgressBarModule } from 'primeng/primeng'
 import { SharedModule } from '../../shared'
 import { VideoEditModule } from './shared/video-edit.module'
 import { VideoAddRoutingModule } from './video-add-routing.module'
@@ -8,7 +9,8 @@ import { VideoAddComponent } from './video-add.component'
   imports: [
     VideoAddRoutingModule,
     VideoEditModule,
-    SharedModule
+    SharedModule,
+    ProgressBarModule
   ],
 
   declarations: [
-- 
cgit v1.2.3


From cadb46d832724ea1a17b085b992142aa32e212be Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Fri, 8 Dec 2017 08:39:15 +0100
Subject: Design second video upload step

---
 client/src/app/core/auth/auth.service.ts           |  3 +-
 client/src/app/core/server/server.service.ts       |  3 +-
 client/src/app/menu/menu.component.scss            |  4 ++
 .../src/app/shared/forms/form-validators/video.ts  | 22 ++-----
 client/src/app/shared/video/video-edit.model.ts    | 26 +++++----
 client/src/app/shared/video/video.service.ts       | 11 ++--
 .../shared/video-description.component.html        |  9 +++
 .../shared/video-description.component.scss        | 41 +++++++++++++
 .../shared/video-description.component.ts          | 66 +++++++++++++++++++++
 .../+video-edit/shared/video-edit.component.scss   |  9 +++
 .../videos/+video-edit/shared/video-edit.module.ts |  3 +-
 .../videos/+video-edit/video-add.component.html    | 16 +++--
 .../videos/+video-edit/video-add.component.scss    | 19 +++++-
 .../app/videos/+video-edit/video-add.component.ts  | 30 ++++++----
 .../videos/+video-edit/video-update.component.html |  4 +-
 .../videos/+video-watch/video-watch.component.html |  2 +-
 .../videos/+video-watch/video-watch.component.ts   |  5 ++
 client/src/app/videos/shared/index.ts              |  1 -
 .../videos/shared/video-description.component.html |  9 ---
 .../videos/shared/video-description.component.scss | 19 ------
 .../videos/shared/video-description.component.ts   | 68 ----------------------
 client/src/sass/_mixins.scss                       |  6 +-
 client/src/sass/application.scss                   |  4 ++
 23 files changed, 224 insertions(+), 156 deletions(-)
 create mode 100644 client/src/app/videos/+video-edit/shared/video-description.component.html
 create mode 100644 client/src/app/videos/+video-edit/shared/video-description.component.scss
 create mode 100644 client/src/app/videos/+video-edit/shared/video-description.component.ts
 delete mode 100644 client/src/app/videos/shared/video-description.component.html
 delete mode 100644 client/src/app/videos/shared/video-description.component.scss
 delete mode 100644 client/src/app/videos/shared/video-description.component.ts

(limited to 'client')

diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts
index fd2708c11..0db197f02 100644
--- a/client/src/app/core/auth/auth.service.ts
+++ b/client/src/app/core/auth/auth.service.ts
@@ -194,7 +194,6 @@ export class AuthService {
     }
 
     this.mergeUserInformation(obj)
-      .do(() => this.userInformationLoaded.next(true))
       .subscribe(
         res => {
           this.user.displayNSFW = res.displayNSFW
@@ -203,6 +202,8 @@ export class AuthService {
           this.user.account = res.account
 
           this.user.save()
+
+          this.userInformationLoaded.next(true)
         }
       )
   }
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts
index 43a836c5a..16e0595b6 100644
--- a/client/src/app/core/server/server.service.ts
+++ b/client/src/app/core/server/server.service.ts
@@ -77,7 +77,6 @@ export class ServerService {
     notifier: ReplaySubject<boolean>
   ) {
     return this.http.get(ServerService.BASE_VIDEO_URL + attributeName)
-      .do(() => notifier.next(true))
        .subscribe(data => {
          Object.keys(data)
                .forEach(dataKey => {
@@ -86,6 +85,8 @@ export class ServerService {
                    label: data[dataKey]
                  })
                })
+
+         notifier.next(true)
        })
   }
 }
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss
index eda3e1a85..63d63d287 100644
--- a/client/src/app/menu/menu.component.scss
+++ b/client/src/app/menu/menu.component.scss
@@ -43,6 +43,10 @@ menu {
       .logged-in-email {
         font-size: 13px;
         color: #C6C6C6;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        max-width: 140px;
       }
     }
 
diff --git a/client/src/app/shared/forms/form-validators/video.ts b/client/src/app/shared/forms/form-validators/video.ts
index 8e512e8c8..45da7df4a 100644
--- a/client/src/app/shared/forms/form-validators/video.ts
+++ b/client/src/app/shared/forms/form-validators/video.ts
@@ -23,17 +23,13 @@ export const VIDEO_PRIVACY = {
 }
 
 export const VIDEO_CATEGORY = {
-  VALIDATORS: [ Validators.required ],
-  MESSAGES: {
-    'required': 'Video category is required.'
-  }
+  VALIDATORS: [ ],
+  MESSAGES: {}
 }
 
 export const VIDEO_LICENCE = {
-  VALIDATORS: [ Validators.required ],
-  MESSAGES: {
-    'required': 'Video licence is required.'
-  }
+  VALIDATORS: [ ],
+  MESSAGES: {}
 }
 
 export const VIDEO_LANGUAGE = {
@@ -49,9 +45,8 @@ export const VIDEO_CHANNEL = {
 }
 
 export const VIDEO_DESCRIPTION = {
-  VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(3000) ],
+  VALIDATORS: [ Validators.minLength(3), Validators.maxLength(3000) ],
   MESSAGES: {
-    'required': 'Video description is required.',
     'minlength': 'Video description must be at least 3 characters long.',
     'maxlength': 'Video description cannot be more than 3000 characters long.'
   }
@@ -64,10 +59,3 @@ export const VIDEO_TAGS = {
     'maxlength': 'A tag should be less than 30 characters long.'
   }
 }
-
-export const VIDEO_FILE = {
-  VALIDATORS: [ Validators.required ],
-  MESSAGES: {
-    'required': 'Video file is required.'
-  }
-}
diff --git a/client/src/app/shared/video/video-edit.model.ts b/client/src/app/shared/video/video-edit.model.ts
index 88d23a59f..955255bfa 100644
--- a/client/src/app/shared/video/video-edit.model.ts
+++ b/client/src/app/shared/video/video-edit.model.ts
@@ -14,18 +14,20 @@ export class VideoEdit {
   uuid?: string
   id?: number
 
-  constructor (videoDetails: VideoDetails) {
-    this.id = videoDetails.id
-    this.uuid = videoDetails.uuid
-    this.category = videoDetails.category
-    this.licence = videoDetails.licence
-    this.language = videoDetails.language
-    this.description = videoDetails.description
-    this.name = videoDetails.name
-    this.tags = videoDetails.tags
-    this.nsfw = videoDetails.nsfw
-    this.channel = videoDetails.channel.id
-    this.privacy = videoDetails.privacy
+  constructor (videoDetails?: VideoDetails) {
+    if (videoDetails) {
+      this.id = videoDetails.id
+      this.uuid = videoDetails.uuid
+      this.category = videoDetails.category
+      this.licence = videoDetails.licence
+      this.language = videoDetails.language
+      this.description = videoDetails.description
+      this.name = videoDetails.name
+      this.tags = videoDetails.tags
+      this.nsfw = videoDetails.nsfw
+      this.channel = videoDetails.channel.id
+      this.privacy = videoDetails.privacy
+    }
   }
 
   patch (values: Object) {
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts
index 3f35b67c4..1a0644c3d 100644
--- a/client/src/app/shared/video/video.service.ts
+++ b/client/src/app/shared/video/video.service.ts
@@ -42,14 +42,17 @@ export class VideoService {
   }
 
   updateVideo (video: VideoEdit) {
-    const language = video.language ? video.language : null
+    const language = video.language || undefined
+    const licence = video.licence || undefined
+    const category = video.category || undefined
+    const description = video.description || undefined
 
     const body: VideoUpdate = {
       name: video.name,
-      category: video.category,
-      licence: video.licence,
+      category,
+      licence,
       language,
-      description: video.description,
+      description,
       privacy: video.privacy,
       tags: video.tags,
       nsfw: video.nsfw
diff --git a/client/src/app/videos/+video-edit/shared/video-description.component.html b/client/src/app/videos/+video-edit/shared/video-description.component.html
new file mode 100644
index 000000000..da66a9753
--- /dev/null
+++ b/client/src/app/videos/+video-edit/shared/video-description.component.html
@@ -0,0 +1,9 @@
+<textarea
+  [(ngModel)]="description" (ngModelChange)="onModelChange()"
+  id="description" placeholder="My super video">
+</textarea>
+
+<tabset #staticTabs class="previews">
+  <tab heading="Truncated description preview" [innerHTML]="truncatedDescriptionHTML"></tab>
+  <tab heading="Complete description preview" [innerHTML]="descriptionHTML"></tab>
+</tabset>
diff --git a/client/src/app/videos/+video-edit/shared/video-description.component.scss b/client/src/app/videos/+video-edit/shared/video-description.component.scss
new file mode 100644
index 000000000..38506bb46
--- /dev/null
+++ b/client/src/app/videos/+video-edit/shared/video-description.component.scss
@@ -0,0 +1,41 @@
+textarea {
+  @include peertube-input-text(100%);
+
+  padding: 5px 15px;
+  font-size: 15px;
+  height: 150px;
+}
+
+.previews /deep/ {
+  font-size: 15px !important;
+
+  .nav {
+    margin-top: 10px;
+    font-size: 16px !important;
+    border: none !important;
+
+    .nav-item .nav-link {
+      color: #000 !important;
+      height: 30px !important;
+      margin-right: 30px;
+      padding: 0 15px;
+      display: flex;
+      align-items: center;
+      border-radius: 3px;
+      border: none !important;
+
+      &.active, &:hover {
+        background-color: #F0F0F0;
+      }
+
+      &.active {
+        font-weight: $font-semibold !important;
+      }
+    }
+  }
+
+  .tab-content {
+    min-height: 75px;
+    padding: 15px;
+  }
+}
diff --git a/client/src/app/videos/+video-edit/shared/video-description.component.ts b/client/src/app/videos/+video-edit/shared/video-description.component.ts
new file mode 100644
index 000000000..8dfb74b2a
--- /dev/null
+++ b/client/src/app/videos/+video-edit/shared/video-description.component.ts
@@ -0,0 +1,66 @@
+import { Component, forwardRef, Input, OnInit } from '@angular/core'
+import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
+import { truncate } from 'lodash'
+import 'rxjs/add/operator/debounceTime'
+import 'rxjs/add/operator/distinctUntilChanged'
+import { Subject } from 'rxjs/Subject'
+import { MarkdownService } from '../../shared'
+
+@Component({
+  selector: 'my-video-description',
+  templateUrl: './video-description.component.html',
+  styleUrls: [ './video-description.component.scss' ],
+  providers: [
+    {
+      provide: NG_VALUE_ACCESSOR,
+      useExisting: forwardRef(() => VideoDescriptionComponent),
+      multi: true
+    }
+  ]
+})
+
+export class VideoDescriptionComponent implements ControlValueAccessor, OnInit {
+  @Input() description = ''
+  truncatedDescriptionHTML = ''
+  descriptionHTML = ''
+
+  private descriptionChanged = new Subject<string>()
+
+  constructor (private markdownService: MarkdownService) {}
+
+  ngOnInit () {
+    this.descriptionChanged
+      .debounceTime(150)
+      .distinctUntilChanged()
+      .subscribe(() => this.updateDescriptionPreviews())
+
+    this.descriptionChanged.next(this.description)
+  }
+
+  propagateChange = (_: any) => { /* empty */ }
+
+  writeValue (description: string) {
+    this.description = description
+
+    this.descriptionChanged.next(this.description)
+  }
+
+  registerOnChange (fn: (_: any) => void) {
+    this.propagateChange = fn
+  }
+
+  registerOnTouched () {
+    // Unused
+  }
+
+  onModelChange () {
+    this.propagateChange(this.description)
+
+    this.descriptionChanged.next(this.description)
+  }
+
+  private updateDescriptionPreviews () {
+    this.truncatedDescriptionHTML = this.markdownService.markdownToHTML(truncate(this.description, { length: 250 }))
+    this.descriptionHTML = this.markdownService.markdownToHTML(this.description)
+  }
+}
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.scss b/client/src/app/videos/+video-edit/shared/video-edit.component.scss
index 2d0bfc36e..d363499ce 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.component.scss
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.scss
@@ -43,6 +43,14 @@
   position: relative;
   bottom: $button-height;
 
+  .message-submit {
+    display: inline-block;
+    margin-right: 25px;
+
+    color: #585858;
+    font-size: 15px;
+  }
+
   .submit-button {
     @include peertube-button;
     @include orange-button;
@@ -54,6 +62,7 @@
       background-color: inherit;
       border: none;
       padding: 0;
+      outline: 0;
     }
 
     .icon.icon-validate {
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.module.ts b/client/src/app/videos/+video-edit/shared/video-edit.module.ts
index c7ed8c351..ce106d82f 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.module.ts
+++ b/client/src/app/videos/+video-edit/shared/video-edit.module.ts
@@ -3,8 +3,9 @@ import { NgModule } from '@angular/core'
 import { TagInputModule } from 'ngx-chips'
 import { TabsModule } from 'ngx-bootstrap/tabs'
 
-import { MarkdownService, VideoDescriptionComponent } from '../../shared'
+import { MarkdownService } from '../../shared'
 import { SharedModule } from '../../../shared'
+import { VideoDescriptionComponent } from './video-description.component'
 import { VideoEditComponent } from './video-edit.component'
 
 @NgModule({
diff --git a/client/src/app/videos/+video-edit/video-add.component.html b/client/src/app/videos/+video-edit/video-add.component.html
index 6883f8280..a6f2bf6f2 100644
--- a/client/src/app/videos/+video-edit/video-add.component.html
+++ b/client/src/app/videos/+video-edit/video-add.component.html
@@ -15,20 +15,23 @@
       </div>
 
       <div class="form-group">
-        <select [(ngModel)]="firstStepPrivacy">
+        <select [(ngModel)]="firstStepPrivacyId">
           <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option>
         </select>
       </div>
 
       <div class="form-group">
-        <select [(ngModel)]="firstStepChannel">
+        <select [(ngModel)]="firstStepChannelId">
           <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option>
         </select>
       </div>
     </div>
   </div>
 
-  <p-progressBar *ngIf="isUploadingVideo" [value]="videoUploadPercents"></p-progressBar>
+  <p-progressBar
+      *ngIf="isUploadingVideo" [value]="videoUploadPercents"
+      [ngClass]="{ processing: videoUploadPercents === 100 && videoUploaded === false }"
+  ></p-progressBar>
 
   <!-- Hidden because we need to load the component -->
   <form [hidden]="!isUploadingVideo" novalidate [formGroup]="form">
@@ -37,10 +40,13 @@
         [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies"
     ></my-video-edit>
 
+
     <div class="submit-container">
-      <div class="submit-button" [ngClass]="{ disabled: !form.valid }">
+      <div *ngIf="videoUploaded === false" class="message-submit">Publish will be available when upload is finished</div>
+
+      <div class="submit-button" (click)="updateSecondStep()" [ngClass]="{ disabled: !form.valid || videoUploaded !== true }">
         <span class="icon icon-validate"></span>
-        <input type="button" value="Publish" (click)="upload()" />
+        <input type="button" value="Publish" />
       </div>
     </div>
   </form>
diff --git a/client/src/app/videos/+video-edit/video-add.component.scss b/client/src/app/videos/+video-edit/video-add.component.scss
index dfdf7ff73..39673b4b7 100644
--- a/client/src/app/videos/+video-edit/video-add.component.scss
+++ b/client/src/app/videos/+video-edit/video-add.component.scss
@@ -18,6 +18,7 @@
     .icon.icon-upload {
       @include icon(90px);
       margin-bottom: 25px;
+      cursor: default;
 
       background-image: url('../../../assets/images/video/upload.svg');
     }
@@ -58,10 +59,9 @@
 }
 
 p-progressBar {
-  margin-top: 50px;
-  margin-bottom: 40px;
-
   /deep/ .ui-progressbar {
+    margin-top: 25px !important;
+    margin-bottom: 40px !important;
     font-size: 15px !important;
     color: #fff !important;
     height: 30px !important;
@@ -76,6 +76,19 @@ p-progressBar {
     .ui-progressbar-label {
       text-align: left;
       padding-left: 18px;
+      margin-top: 0 !important;
+    }
+  }
+
+  &.processing {
+    /deep/ .ui-progressbar-label {
+      // Same color as background to hide "100%"
+      color: rgba(11, 204, 41, 0.16) !important;
+
+      &::before {
+        content: 'Processing...';
+        color: #fff;
+      }
     }
   }
 }
diff --git a/client/src/app/videos/+video-edit/video-add.component.ts b/client/src/app/videos/+video-edit/video-add.component.ts
index 2409acfda..2bbc3de17 100644
--- a/client/src/app/videos/+video-edit/video-add.component.ts
+++ b/client/src/app/videos/+video-edit/video-add.component.ts
@@ -5,6 +5,7 @@ import { Router } from '@angular/router'
 import { NotificationsService } from 'angular2-notifications'
 import { VideoService } from 'app/shared/video/video.service'
 import { VideoCreate } from '../../../../../shared'
+import { VideoPrivacy } from '../../../../../shared/models/videos'
 import { AuthService, ServerService } from '../../core'
 import { FormReactive } from '../../shared'
 import { ValidatorMessage } from '../../shared/forms/form-validators'
@@ -25,6 +26,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
   isUploadingVideo = false
   videoUploaded = false
   videoUploadPercents = 0
+  videoUploadedId = 0
 
   error: string = null
   form: FormGroup
@@ -33,8 +35,8 @@ export class VideoAddComponent extends FormReactive implements OnInit {
 
   userVideoChannels = []
   videoPrivacies = []
-  firstStepPrivacy = 0
-  firstStepChannel = 0
+  firstStepPrivacyId = 0
+  firstStepChannelId = 0
 
   constructor (
     private formBuilder: FormBuilder,
@@ -59,7 +61,9 @@ export class VideoAddComponent extends FormReactive implements OnInit {
       .subscribe(
         () => {
           this.videoPrivacies = this.serverService.getVideoPrivacies()
-          this.firstStepPrivacy = this.videoPrivacies[0].id
+
+          // Public by default
+          this.firstStepPrivacyId = VideoPrivacy.PUBLIC
         })
 
     this.authService.userInformationLoaded
@@ -72,7 +76,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
           if (Array.isArray(videoChannels) === false) return
 
           this.userVideoChannels = videoChannels.map(v => ({ id: v.id, label: v.name }))
-          this.firstStepChannel = this.userVideoChannels[0].id
+          this.firstStepChannelId = this.userVideoChannels[0].id
         }
       )
   }
@@ -89,14 +93,15 @@ export class VideoAddComponent extends FormReactive implements OnInit {
 
   uploadFirstStep () {
     const videofile = this.videofileInput.nativeElement.files[0]
-    const name = videofile.name
-    const privacy = this.firstStepPrivacy.toString()
+    const name = videofile.name.replace(/\.[^/.]+$/, '')
+    const privacy = this.firstStepPrivacyId.toString()
     const nsfw = false
-    const channelId = this.firstStepChannel.toString()
+    const channelId = this.firstStepChannelId.toString()
 
     const formData = new FormData()
     formData.append('name', name)
-    formData.append('privacy', privacy.toString())
+    // Put the video "private" -> we wait he validates the second step
+    formData.append('privacy', VideoPrivacy.PRIVATE.toString())
     formData.append('nsfw', '' + nsfw)
     formData.append('channelId', '' + channelId)
     formData.append('videofile', videofile)
@@ -117,6 +122,8 @@ export class VideoAddComponent extends FormReactive implements OnInit {
           console.log('Video uploaded.')
 
           this.videoUploaded = true
+
+          this.videoUploadedId = event.body.video.id
         }
       },
 
@@ -133,13 +140,16 @@ export class VideoAddComponent extends FormReactive implements OnInit {
       return
     }
 
-    const video = new VideoEdit(this.form.value)
+    const video = new VideoEdit()
+    video.patch(this.form.value)
+    video.channel = this.firstStepChannelId
+    video.id = this.videoUploadedId
 
     this.videoService.updateVideo(video)
       .subscribe(
         () => {
           this.notificationsService.success('Success', 'Video published.')
-          this.router.navigate([ '/videos/watch', video.uuid ])
+          this.router.navigate([ '/videos/watch', video.id ])
         },
 
         err => {
diff --git a/client/src/app/videos/+video-edit/video-update.component.html b/client/src/app/videos/+video-edit/video-update.component.html
index 3163495bf..261b8a130 100644
--- a/client/src/app/videos/+video-edit/video-update.component.html
+++ b/client/src/app/videos/+video-edit/video-update.component.html
@@ -11,9 +11,9 @@
     ></my-video-edit>
 
     <div class="submit-container">
-      <div class="submit-button" [ngClass]="{ disabled: !form.valid }">
+      <div class="submit-button" (click)="update()" [ngClass]="{ disabled: !form.valid }">
         <span class="icon icon-validate"></span>
-        <input type="button" value="Update" (click)="update()" />
+        <input type="button" value="Update" />
       </div>
     </div>
   </form>
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html
index 583da4685..dfed4768c 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -78,7 +78,7 @@
     <div class="video-info-description">
       <div class="video-info-description-html" [innerHTML]="videoHTMLDescription"></div>
 
-      <div class="video-info-description-more" *ngIf="completeDescriptionShown === false && video.description.length === 250" (click)="showMoreDescription()">
+      <div class="video-info-description-more" *ngIf="completeDescriptionShown === false && video.description?.length === 250" (click)="showMoreDescription()">
         Show more
         <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-down"></span>
         <my-loader class="description-loading" [loading]="descriptionLoading"></my-loader>
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts
index 87db023bf..d4e3ec014 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -219,6 +219,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
   }
 
   private setVideoDescriptionHTML () {
+    if (!this.video.description) {
+      this.videoHTMLDescription = ''
+      return
+    }
+
     this.videoHTMLDescription = this.markdownService.markdownToHTML(this.video.description)
   }
 
diff --git a/client/src/app/videos/shared/index.ts b/client/src/app/videos/shared/index.ts
index 3c72ed895..7a66944b9 100644
--- a/client/src/app/videos/shared/index.ts
+++ b/client/src/app/videos/shared/index.ts
@@ -1,2 +1 @@
 export * from './markdown.service'
-export * from './video-description.component'
diff --git a/client/src/app/videos/shared/video-description.component.html b/client/src/app/videos/shared/video-description.component.html
deleted file mode 100644
index da66a9753..000000000
--- a/client/src/app/videos/shared/video-description.component.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<textarea
-  [(ngModel)]="description" (ngModelChange)="onModelChange()"
-  id="description" placeholder="My super video">
-</textarea>
-
-<tabset #staticTabs class="previews">
-  <tab heading="Truncated description preview" [innerHTML]="truncatedDescriptionHTML"></tab>
-  <tab heading="Complete description preview" [innerHTML]="descriptionHTML"></tab>
-</tabset>
diff --git a/client/src/app/videos/shared/video-description.component.scss b/client/src/app/videos/shared/video-description.component.scss
deleted file mode 100644
index 6ef81ae58..000000000
--- a/client/src/app/videos/shared/video-description.component.scss
+++ /dev/null
@@ -1,19 +0,0 @@
-textarea {
-  @include peertube-input-text(100%);
-
-  font-size: 15px;
-  height: 150px;
-}
-
-.previews /deep/ {
-  font-size: 15px !important;
-
-  .nav {
-    margin-top: 10px;
-  }
-
-  .tab-content {
-    min-height: 75px;
-    padding: 5px;
-  }
-}
diff --git a/client/src/app/videos/shared/video-description.component.ts b/client/src/app/videos/shared/video-description.component.ts
deleted file mode 100644
index d9ffb7800..000000000
--- a/client/src/app/videos/shared/video-description.component.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import { Component, forwardRef, Input, OnInit } from '@angular/core'
-import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
-import { Subject } from 'rxjs/Subject'
-import 'rxjs/add/operator/debounceTime'
-import 'rxjs/add/operator/distinctUntilChanged'
-
-import { truncate } from 'lodash'
-
-import { MarkdownService } from './markdown.service'
-
-@Component({
-  selector: 'my-video-description',
-  templateUrl: './video-description.component.html',
-  styleUrls: [ './video-description.component.scss' ],
-  providers: [
-    {
-      provide: NG_VALUE_ACCESSOR,
-      useExisting: forwardRef(() => VideoDescriptionComponent),
-      multi: true
-    }
-  ]
-})
-
-export class VideoDescriptionComponent implements ControlValueAccessor, OnInit {
-  @Input() description = ''
-  truncatedDescriptionHTML = ''
-  descriptionHTML = ''
-
-  private descriptionChanged = new Subject<string>()
-
-  constructor (private markdownService: MarkdownService) {}
-
-  ngOnInit () {
-    this.descriptionChanged
-      .debounceTime(150)
-      .distinctUntilChanged()
-      .subscribe(() => this.updateDescriptionPreviews())
-
-    this.descriptionChanged.next(this.description)
-  }
-
-  propagateChange = (_: any) => { /* empty */ }
-
-  writeValue (description: string) {
-    this.description = description
-
-    this.descriptionChanged.next(this.description)
-  }
-
-  registerOnChange (fn: (_: any) => void) {
-    this.propagateChange = fn
-  }
-
-  registerOnTouched () {
-    // Unused
-  }
-
-  onModelChange () {
-    this.propagateChange(this.description)
-
-    this.descriptionChanged.next(this.description)
-  }
-
-  private updateDescriptionPreviews () {
-    this.truncatedDescriptionHTML = this.markdownService.markdownToHTML(truncate(this.description, { length: 250 }))
-    this.descriptionHTML = this.markdownService.markdownToHTML(this.description)
-  }
-}
diff --git a/client/src/sass/_mixins.scss b/client/src/sass/_mixins.scss
index 121e16e10..d9c9e45ec 100644
--- a/client/src/sass/_mixins.scss
+++ b/client/src/sass/_mixins.scss
@@ -1,5 +1,5 @@
 @mixin disable-default-a-behaviour {
-  &:hover, &:focus {
+  &:hover, &:focus, &:active {
     text-decoration: none !important;
     outline: none !important;
   }
@@ -23,13 +23,15 @@
   color: #fff;
   background-color: $orange-color;
 
-  &:hover, &:active, &:focus, &[disabled], &.disabled {
+  &:hover, &:active, &:focus {
     color: #fff;
     background-color: $orange-hoover-color;
   }
 
   &[disabled], &.disabled {
     cursor: default;
+    color: #fff;
+    background-color: #C6C6C6;
   }
 }
 
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index 0c999d659..3c5a00309 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -86,6 +86,10 @@ label {
     margin-top: 30px;
     margin-bottom: 25px;
   }
+
+  &:hover, &:active, &:focus {
+    color: #000;
+}
 }
 
 // On small screen, menu is absolute and displayed over the page
-- 
cgit v1.2.3


From 04e0fc488826f505a8de3ce99113f3cb2fcec147 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Fri, 8 Dec 2017 10:41:49 +0100
Subject: Begin admin design

---
 client/src/app/+admin/admin.component.html         |  27 +++++
 client/src/app/+admin/admin.component.scss         |   0
 client/src/app/+admin/admin.component.ts           |  26 ++++-
 .../followers-list/followers-list.component.html   |  26 ++---
 .../following-add/following-add.component.html     |  53 ++++-----
 .../following-list/following-list.component.html   |  34 +++---
 .../src/app/+admin/follows/follows.component.scss  |  18 ---
 .../users/user-edit/user-edit.component.html       | 123 ++++++++++-----------
 .../users/user-edit/user-edit.component.scss       |  18 +++
 .../users/user-list/user-list.component.html       |  63 +++++------
 .../users/user-list/user-list.component.scss       |  22 +++-
 client/src/app/app.component.html                  |   3 +-
 client/src/app/app.module.ts                       |   3 +-
 client/src/app/header/header.component.scss        |   7 +-
 client/src/app/menu/index.ts                       |   1 -
 client/src/app/menu/menu-admin.component.html      |  35 ------
 client/src/app/menu/menu-admin.component.ts        |  33 ------
 .../shared/video-description.component.scss        |  34 ------
 client/src/assets/images/admin/add.svg             |  13 +++
 client/src/sass/application.scss                   |  42 ++++++-
 20 files changed, 288 insertions(+), 293 deletions(-)
 create mode 100644 client/src/app/+admin/admin.component.html
 create mode 100644 client/src/app/+admin/admin.component.scss
 delete mode 100644 client/src/app/menu/menu-admin.component.html
 delete mode 100644 client/src/app/menu/menu-admin.component.ts
 create mode 100644 client/src/assets/images/admin/add.svg

(limited to 'client')

diff --git a/client/src/app/+admin/admin.component.html b/client/src/app/+admin/admin.component.html
new file mode 100644
index 000000000..0bf4c8aac
--- /dev/null
+++ b/client/src/app/+admin/admin.component.html
@@ -0,0 +1,27 @@
+<div class="row">
+  <div class="sub-menu">
+    <a *ngIf="hasUsersRight()" routerLink="/admin/users" routerLinkActive="active" class="title-page">
+      Users
+    </a>
+
+    <a *ngIf="hasServerFollowRight()" routerLink="/admin/follows" routerLinkActive="active" class="title-page">
+      Manage follows
+    </a>
+
+    <a *ngIf="hasVideoAbusesRight()" routerLink="/admin/video-abuses" routerLinkActive="active" class="title-page">
+      Video abuses
+    </a>
+
+    <a *ngIf="hasVideoBlacklistRight()" routerLink="/admin/video-blacklist" routerLinkActive="active" class="title-page">
+      Video blacklist
+    </a>
+
+    <a *ngIf="hasJobsRight()" routerLink="/admin/jobs" routerLinkActive="active" class="title-page">
+      Jobs
+    </a>
+  </div>
+
+  <div class="margin-content">
+    <router-outlet></router-outlet>
+  </div>
+</div>
diff --git a/client/src/app/+admin/admin.component.scss b/client/src/app/+admin/admin.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/client/src/app/+admin/admin.component.ts b/client/src/app/+admin/admin.component.ts
index ecd62ee61..75cd50cc7 100644
--- a/client/src/app/+admin/admin.component.ts
+++ b/client/src/app/+admin/admin.component.ts
@@ -1,7 +1,31 @@
 import { Component } from '@angular/core'
+import { UserRight } from '../../../../shared'
+import { AuthService } from '../core/auth/auth.service'
 
 @Component({
-  template: '<router-outlet></router-outlet>'
+  templateUrl: './admin.component.html',
+  styleUrls: [ './admin.component.scss' ]
 })
 export class AdminComponent {
+  constructor (private auth: AuthService) {}
+
+  hasUsersRight () {
+    return this.auth.getUser().hasRight(UserRight.MANAGE_USERS)
+  }
+
+  hasServerFollowRight () {
+    return this.auth.getUser().hasRight(UserRight.MANAGE_SERVER_FOLLOW)
+  }
+
+  hasVideoAbusesRight () {
+    return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_ABUSES)
+  }
+
+  hasVideoBlacklistRight () {
+    return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)
+  }
+
+  hasJobsRight () {
+    return this.auth.getUser().hasRight(UserRight.MANAGE_JOBS)
+  }
 }
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.html b/client/src/app/+admin/follows/followers-list/followers-list.component.html
index 473801822..ea5380ff7 100644
--- a/client/src/app/+admin/follows/followers-list/followers-list.component.html
+++ b/client/src/app/+admin/follows/followers-list/followers-list.component.html
@@ -1,16 +1,12 @@
-<div class="row">
-  <div class="content-padding">
-    <h3>Followers list</h3>
+<h3>Followers list</h3>
 
-    <p-dataTable
-        [value]="followers" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
-        sortField="createdAt" (onLazyLoad)="loadLazy($event)"
-    >
-      <p-column field="id" header="ID"></p-column>
-      <p-column field="follower.host" header="Host"></p-column>
-      <p-column field="follower.score" header="Score"></p-column>
-      <p-column field="state" header="State"></p-column>
-      <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
-    </p-dataTable>
-  </div>
-</div>
+<p-dataTable
+    [value]="followers" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
+    sortField="createdAt" (onLazyLoad)="loadLazy($event)"
+>
+  <p-column field="id" header="ID"></p-column>
+  <p-column field="follower.host" header="Host"></p-column>
+  <p-column field="follower.score" header="Score"></p-column>
+  <p-column field="state" header="State"></p-column>
+  <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
+</p-dataTable>
diff --git a/client/src/app/+admin/follows/following-add/following-add.component.html b/client/src/app/+admin/follows/following-add/following-add.component.html
index 8e7dddc11..65c1eda0c 100644
--- a/client/src/app/+admin/follows/following-add/following-add.component.html
+++ b/client/src/app/+admin/follows/following-add/following-add.component.html
@@ -1,35 +1,30 @@
-<div class="row">
-  <div class="content-padding">
+<h3>Add following</h3>
 
-    <h3>Add following</h3>
+<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
 
-    <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
+<form (ngSubmit)="addFollowing()" [formGroup]="form">
+  <div class="form-group"  *ngFor="let host of hosts; let id = index; trackBy:customTrackBy">
+    <label [for]="'host-' + id">Host (so without "http://")</label>
 
-    <form (ngSubmit)="addFollowing()" [formGroup]="form">
-      <div class="form-group"  *ngFor="let host of hosts; let id = index; trackBy:customTrackBy">
-        <label [for]="'host-' + id">Host (so without "http://")</label>
+    <div class="input-group">
+      <input
+        type="text" class="form-control" placeholder="example.com"
+        [id]="'host-' + id" [formControlName]="'host-' + id"
+      />
+      <span class="input-group-btn">
+        <button *ngIf="displayAddField(id)" (click)="addField()" class="btn btn-default" type="button">+</button>
+        <button *ngIf="displayRemoveField(id)" (click)="removeField(id)" class="btn btn-default" type="button">-</button>
+      </span>
+    </div>
 
-        <div class="input-group">
-          <input
-            type="text" class="form-control" placeholder="example.com"
-            [id]="'host-' + id" [formControlName]="'host-' + id"
-          />
-          <span class="input-group-btn">
-            <button *ngIf="displayAddField(id)" (click)="addField()" class="btn btn-default" type="button">+</button>
-            <button *ngIf="displayRemoveField(id)" (click)="removeField(id)" class="btn btn-default" type="button">-</button>
-          </span>
-        </div>
-
-        <div [hidden]="form.controls['host-' + id].valid || form.controls['host-' + id].pristine" class="alert alert-warning">
-          It should be a valid host.
-        </div>
-      </div>
-
-      <div *ngIf="canMakeFriends() === false"  class="alert alert-warning">
-        It seems that you are not on a HTTPS server. Your webserver need to have TLS activated in order to follow servers.
-      </div>
+    <div [hidden]="form.controls['host-' + id].valid || form.controls['host-' + id].pristine" class="alert alert-warning">
+      It should be a valid host.
+    </div>
+  </div>
 
-      <input type="submit" value="Add following" class="btn btn-default" [disabled]="!isFormValid()">
-    </form>
+  <div *ngIf="canMakeFriends() === false"  class="alert alert-warning">
+    It seems that you are not on a HTTPS server. Your webserver need to have TLS activated in order to follow servers.
   </div>
-</div>
+
+  <input type="submit" value="Add following" class="btn btn-default" [disabled]="!isFormValid()">
+</form>
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.html b/client/src/app/+admin/follows/following-list/following-list.component.html
index a73084312..85c7c3af1 100644
--- a/client/src/app/+admin/follows/following-list/following-list.component.html
+++ b/client/src/app/+admin/follows/following-list/following-list.component.html
@@ -1,20 +1,16 @@
-<div class="row">
-  <div class="content-padding">
-    <h3>Following list</h3>
+<h3>Following list</h3>
 
-    <p-dataTable
-        [value]="following" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
-        sortField="createdAt" (onLazyLoad)="loadLazy($event)"
-    >
-      <p-column field="id" header="ID"></p-column>
-      <p-column field="following.host" header="Host"></p-column>
-      <p-column field="state" header="State"></p-column>
-      <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
-      <p-column header="Unfollow" styleClass="action-cell">
-        <ng-template pTemplate="body" let-following="rowData">
-          <span (click)="removeFollowing(following)" class="glyphicon glyphicon-remove glyphicon-black" title="Unfollow"></span>
-        </ng-template>
-      </p-column>
-    </p-dataTable>
-  </div>
-</div>
+<p-dataTable
+    [value]="following" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
+    sortField="createdAt" (onLazyLoad)="loadLazy($event)"
+>
+  <p-column field="id" header="ID"></p-column>
+  <p-column field="following.host" header="Host"></p-column>
+  <p-column field="state" header="State"></p-column>
+  <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
+  <p-column header="Unfollow" styleClass="action-cell">
+    <ng-template pTemplate="body" let-following="rowData">
+      <span (click)="removeFollowing(following)" class="glyphicon glyphicon-remove glyphicon-black" title="Unfollow"></span>
+    </ng-template>
+  </p-column>
+</p-dataTable>
diff --git a/client/src/app/+admin/follows/follows.component.scss b/client/src/app/+admin/follows/follows.component.scss
index d8ab41975..242effd85 100644
--- a/client/src/app/+admin/follows/follows.component.scss
+++ b/client/src/app/+admin/follows/follows.component.scss
@@ -1,21 +1,3 @@
 .follows-menu {
   margin-top: 20px;
 }
-
-tabset /deep/ {
-  .nav-link {
-    padding: 0;
-  }
-
-  .tab-link {
-    display: block;
-    text-align: center;
-    height: 40px;
-    width: 120px;
-    line-height: 40px;
-
-    &:hover, &:active, &:focus {
-      text-decoration: none !important;
-    }
-  }
-}
diff --git a/client/src/app/+admin/users/user-edit/user-edit.component.html b/client/src/app/+admin/users/user-edit/user-edit.component.html
index 349be13c1..ed27ea745 100644
--- a/client/src/app/+admin/users/user-edit/user-edit.component.html
+++ b/client/src/app/+admin/users/user-edit/user-edit.component.html
@@ -1,73 +1,68 @@
-<div class="row">
-  <div class="content-padding">
+<div class="admin-sub-title" *ngIf="isCreation() === true">Add user</div>
+<div class="admin-sub-title" *ngIf="isCreation() === false">Edit user {{ username }}</div>
 
-    <h3 *ngIf="isCreation() === true">Add user</h3>
-    <h3 *ngIf="isCreation() === false">Edit user {{ username }}</h3>
+<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
 
-    <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
-
-    <form role="form" (ngSubmit)="formValidated()" [formGroup]="form">
-      <div class="form-group" *ngIf="isCreation()">
-        <label for="username">Username</label>
-        <input
-          type="text" class="form-control" id="username" placeholder="john"
-          formControlName="username"
-        >
-        <div *ngIf="formErrors.username" class="alert alert-danger">
-          {{ formErrors.username }}
-        </div>
-      </div>
-
-      <div class="form-group">
-        <label for="email">Email</label>
-        <input
-          type="text" class="form-control" id="email" placeholder="mail@example.com"
-          formControlName="email"
-        >
-        <div *ngIf="formErrors.email" class="alert alert-danger">
-          {{ formErrors.email }}
-        </div>
-      </div>
+<form role="form" (ngSubmit)="formValidated()" [formGroup]="form">
+  <div class="form-group" *ngIf="isCreation()">
+    <label for="username">Username</label>
+    <input
+      type="text" class="form-control" id="username" placeholder="john"
+      formControlName="username" [ngClass]="{ 'input-error': formErrors['username'] }"
+    >
+    <div *ngIf="formErrors.username" class="form-error">
+      {{ formErrors.username }}
+    </div>
+  </div>
 
-      <div class="form-group" *ngIf="isCreation()">
-        <label for="password">Password</label>
-        <input
-          type="password" class="form-control" id="password"
-          formControlName="password"
-        >
-        <div *ngIf="formErrors.password" class="alert alert-danger">
-          {{ formErrors.password }}
-        </div>
-      </div>
+  <div class="form-group">
+    <label for="email">Email</label>
+    <input
+      type="text" class="form-control" id="email" placeholder="mail@example.com"
+      formControlName="email" [ngClass]="{ 'input-error': formErrors['email'] }"
+    >
+    <div *ngIf="formErrors.email" class="form-error">
+      {{ formErrors.email }}
+    </div>
+  </div>
 
-      <div class="form-group">
-        <label for="role">Role</label>
-        <select class="form-control" id="role" formControlName="role">
-          <option *ngFor="let role of roles" [value]="role.value">
-            {{ role.label }}
-          </option>
-        </select>
+  <div class="form-group" *ngIf="isCreation()">
+    <label for="password">Password</label>
+    <input
+      type="password" class="form-control" id="password"
+      formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }"
+    >
+    <div *ngIf="formErrors.password" class="form-error">
+      {{ formErrors.password }}
+    </div>
+  </div>
 
-        <div *ngIf="formErrors.role" class="alert alert-danger">
-          {{ formErrors.role }}
-        </div>
-      </div>
+  <div class="form-group">
+    <label for="role">Role</label>
+    <select class="form-control" id="role" formControlName="role">
+      <option *ngFor="let role of roles" [value]="role.value">
+        {{ role.label }}
+      </option>
+    </select>
 
-      <div class="form-group">
-        <label for="videoQuota">Video quota</label>
-        <select class="form-control" id="videoQuota" formControlName="videoQuota">
-          <option *ngFor="let videoQuotaOption of videoQuotaOptions" [value]="videoQuotaOption.value">
-            {{ videoQuotaOption.label }}
-          </option>
-        </select>
+    <div *ngIf="formErrors.role" class="form-error">
+      {{ formErrors.role }}
+    </div>
+  </div>
 
-        <div class="transcoding-information" *ngIf="isTranscodingInformationDisplayed()">
-          Transcoding is enabled on server. The video quota only take in account <strong>original</strong> video. <br />
-          In maximum, this user could use ~ {{ computeQuotaWithTranscoding() | bytes }}.
-        </div>
-      </div>
+  <div class="form-group">
+    <label for="videoQuota">Video quota</label>
+    <select class="form-control" id="videoQuota" formControlName="videoQuota">
+      <option *ngFor="let videoQuotaOption of videoQuotaOptions" [value]="videoQuotaOption.value">
+        {{ videoQuotaOption.label }}
+      </option>
+    </select>
 
-      <input type="submit" value="{{ getFormButtonTitle() }}" class="btn btn-default" [disabled]="!form.valid">
-    </form>
+    <div class="transcoding-information" *ngIf="isTranscodingInformationDisplayed()">
+      Transcoding is enabled on server. The video quota only take in account <strong>original</strong> video. <br />
+      In maximum, this user could use ~ {{ computeQuotaWithTranscoding() | bytes }}.
+    </div>
   </div>
-</div>
+
+  <input type="submit" value="{{ getFormButtonTitle() }}" class="btn btn-default" [disabled]="!form.valid">
+</form>
diff --git a/client/src/app/+admin/users/user-edit/user-edit.component.scss b/client/src/app/+admin/users/user-edit/user-edit.component.scss
index 401caa0c6..68d270c19 100644
--- a/client/src/app/+admin/users/user-edit/user-edit.component.scss
+++ b/client/src/app/+admin/users/user-edit/user-edit.component.scss
@@ -1,3 +1,21 @@
+.admin-sub-title {
+  margin-bottom: 30px;
+}
+
+input:not([type=submit]) {
+  @include peertube-input-text(340px);
+  display: block;
+}
+
+select {
+  @include peertube-select(340px);
+}
+
+input[type=submit] {
+  @include peertube-button;
+  @include orange-button;
+}
+
 .transcoding-information {
   margin-top: 5px;
   font-size: 11px;
diff --git a/client/src/app/+admin/users/user-list/user-list.component.html b/client/src/app/+admin/users/user-list/user-list.component.html
index 16a8a8033..a100ddfaa 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.html
+++ b/client/src/app/+admin/users/user-list/user-list.component.html
@@ -1,35 +1,32 @@
-<div class="row">
-  <div class="content-padding">
+<div class="sub-header">
+  <div class="admin-sub-title">Users list</div>
 
-    <h3>Users list</h3>
-
-    <p-dataTable
-        [value]="users" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
-        sortField="id" (onLazyLoad)="loadLazy($event)"
-    >
-      <p-column field="id" header="ID" [sortable]="true"></p-column>
-      <p-column field="username" header="Username" [sortable]="true"></p-column>
-      <p-column field="email" header="Email"></p-column>
-      <p-column field="videoQuota" header="Video quota"></p-column>
-      <p-column field="roleLabel" header="Role"></p-column>
-      <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
-      <p-column header="Edit" styleClass="action-cell">
-        <ng-template pTemplate="body" let-user="rowData">
-          <a [routerLink]="getRouterUserEditLink(user)" title="Edit this user">
-            <span class="glyphicon glyphicon-pencil glyphicon-black"></span>
-          </a>
-        </ng-template>
-      </p-column>
-      <p-column header="Delete" styleClass="action-cell">
-        <ng-template pTemplate="body" let-user="rowData">
-          <span (click)="removeUser(user)" class="glyphicon glyphicon-remove glyphicon-black" title="Remove this user"></span>
-        </ng-template>
-      </p-column>
-    </p-dataTable>
-
-    <a class="add-user btn btn-success pull-right" [routerLink]="['/admin/users/add']">
-      <span class="glyphicon glyphicon-plus"></span>
-      Add user
-    </a>
-  </div>
+  <a class="add-button" routerLink="/admin/users/add">
+    <span class="icon icon-add"></span>
+    Add user
+  </a>
 </div>
+
+<p-dataTable
+    [value]="users" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
+    sortField="id" (onLazyLoad)="loadLazy($event)"
+>
+  <p-column field="id" header="ID" [sortable]="true"></p-column>
+  <p-column field="username" header="Username" [sortable]="true"></p-column>
+  <p-column field="email" header="Email"></p-column>
+  <p-column field="videoQuota" header="Video quota"></p-column>
+  <p-column field="roleLabel" header="Role"></p-column>
+  <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
+  <p-column header="Edit" styleClass="action-cell">
+    <ng-template pTemplate="body" let-user="rowData">
+      <a [routerLink]="getRouterUserEditLink(user)" title="Edit this user">
+        <span class="glyphicon glyphicon-pencil glyphicon-black"></span>
+      </a>
+    </ng-template>
+  </p-column>
+  <p-column header="Delete" styleClass="action-cell">
+    <ng-template pTemplate="body" let-user="rowData">
+      <span (click)="removeUser(user)" class="glyphicon glyphicon-remove glyphicon-black" title="Remove this user"></span>
+    </ng-template>
+  </p-column>
+</p-dataTable>
diff --git a/client/src/app/+admin/users/user-list/user-list.component.scss b/client/src/app/+admin/users/user-list/user-list.component.scss
index 71adef653..54ecb61b4 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.scss
+++ b/client/src/app/+admin/users/user-list/user-list.component.scss
@@ -1,3 +1,21 @@
-.add-user {
-  margin-top: 10px;
+.sub-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 30px;
+
+  .admin-sub-title {
+    flex-grow: 1;
+  }
+
+  .add-button {
+    @include peertube-button-link;
+    @include orange-button;
+
+    .icon.icon-add {
+      @include icon(22px);
+
+      margin-right: 3px;
+      background-image: url('../../../../assets/images/admin/add.svg');
+    }
+  }
 }
diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html
index b095e44d6..cb1f4e4ef 100644
--- a/client/src/app/app.component.html
+++ b/client/src/app/app.component.html
@@ -19,8 +19,7 @@
     <div class="title-menu-left">
 
       <div class="title-menu-left-block menu">
-        <my-menu *ngIf="isMenuDisplayed && isInAdmin() === false"></my-menu>
-        <my-menu-admin *ngIf="isMenuDisplayed && isInAdmin() === true"></my-menu-admin>
+        <my-menu *ngIf="isMenuDisplayed"></my-menu>
       </div>
     </div>
 
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts
index ee7cb0c8a..1326e3411 100644
--- a/client/src/app/app.module.ts
+++ b/client/src/app/app.module.ts
@@ -20,7 +20,7 @@ import { LoginModule } from './login'
 import { SignupModule } from './signup'
 import { SharedModule } from './shared'
 import { VideosModule } from './videos'
-import { MenuComponent, MenuAdminComponent } from './menu'
+import { MenuComponent } from './menu'
 import { HeaderComponent } from './header'
 
 export function metaFactory (): MetaLoader {
@@ -52,7 +52,6 @@ const APP_PROVIDERS = [
     AppComponent,
 
     MenuComponent,
-    MenuAdminComponent,
     HeaderComponent
   ],
   imports: [
diff --git a/client/src/app/header/header.component.scss b/client/src/app/header/header.component.scss
index d1c59e8d1..ed8695eab 100644
--- a/client/src/app/header/header.component.scss
+++ b/client/src/app/header/header.component.scss
@@ -27,10 +27,9 @@
   margin-right: 25px;
 
   .icon.icon-upload {
-    display: inline-block;
-    background: url('../../assets/images/header/upload.svg') no-repeat;
-    background-size: contain;
-    width: 22px;
+    @include icon(22px);
+
+    background-image: url('../../assets/images/header/upload.svg');
     height: 24px;
     vertical-align: middle;
     margin-right: 6px;
diff --git a/client/src/app/menu/index.ts b/client/src/app/menu/index.ts
index c905ed20a..421271c12 100644
--- a/client/src/app/menu/index.ts
+++ b/client/src/app/menu/index.ts
@@ -1,2 +1 @@
 export * from './menu.component'
-export * from './menu-admin.component'
diff --git a/client/src/app/menu/menu-admin.component.html b/client/src/app/menu/menu-admin.component.html
deleted file mode 100644
index 9857b2e3e..000000000
--- a/client/src/app/menu/menu-admin.component.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<menu>
-  <div class="panel-block">
-    <a *ngIf="hasUsersRight()" routerLink="/admin/users" routerLinkActive="active">
-      <span class="hidden-xs glyphicon glyphicon-user"></span>
-      List users
-    </a>
-
-    <a *ngIf="hasServerFollowRight()" routerLink="/admin/follows" routerLinkActive="active">
-      <span class="hidden-xs glyphicon glyphicon-cloud"></span>
-      Manage follows
-    </a>
-
-    <a *ngIf="hasVideoAbusesRight()" routerLink="/admin/video-abuses" routerLinkActive="active">
-      <span class="hidden-xs glyphicon glyphicon-alert"></span>
-      Video abuses
-    </a>
-
-    <a *ngIf="hasVideoBlacklistRight()" routerLink="/admin/video-blacklist" routerLinkActive="active">
-      <span class="hidden-xs glyphicon glyphicon-eye-close"></span>
-      Video blacklist
-    </a>
-
-    <a *ngIf="hasJobsRight()" routerLink="/admin/jobs" routerLinkActive="active">
-      <span class="hidden-xs glyphicon glyphicon-tasks"></span>
-      Jobs
-    </a>
-  </div>
-
-  <div class="panel-block">
-    <a routerLink="/videos/list" routerLinkActive="active">
-      <span class="hidden-xs glyphicon glyphicon-cog"></span>
-      Quit admin.
-    </a>
-  </div>
-</menu>
diff --git a/client/src/app/menu/menu-admin.component.ts b/client/src/app/menu/menu-admin.component.ts
deleted file mode 100644
index 1babf5eb6..000000000
--- a/client/src/app/menu/menu-admin.component.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Component } from '@angular/core'
-
-import { AuthService } from '../core/auth/auth.service'
-import { UserRight } from '../../../../shared'
-
-@Component({
-  selector: 'my-menu-admin',
-  templateUrl: './menu-admin.component.html',
-  styleUrls: [ './menu.component.scss' ]
-})
-export class MenuAdminComponent {
-  constructor (private auth: AuthService) {}
-
-  hasUsersRight () {
-    return this.auth.getUser().hasRight(UserRight.MANAGE_USERS)
-  }
-
-  hasServerFollowRight () {
-    return this.auth.getUser().hasRight(UserRight.MANAGE_SERVER_FOLLOW)
-  }
-
-  hasVideoAbusesRight () {
-    return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_ABUSES)
-  }
-
-  hasVideoBlacklistRight () {
-    return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)
-  }
-
-  hasJobsRight () {
-    return this.auth.getUser().hasRight(UserRight.MANAGE_JOBS)
-  }
-}
diff --git a/client/src/app/videos/+video-edit/shared/video-description.component.scss b/client/src/app/videos/+video-edit/shared/video-description.component.scss
index 38506bb46..8155cbca7 100644
--- a/client/src/app/videos/+video-edit/shared/video-description.component.scss
+++ b/client/src/app/videos/+video-edit/shared/video-description.component.scss
@@ -5,37 +5,3 @@ textarea {
   font-size: 15px;
   height: 150px;
 }
-
-.previews /deep/ {
-  font-size: 15px !important;
-
-  .nav {
-    margin-top: 10px;
-    font-size: 16px !important;
-    border: none !important;
-
-    .nav-item .nav-link {
-      color: #000 !important;
-      height: 30px !important;
-      margin-right: 30px;
-      padding: 0 15px;
-      display: flex;
-      align-items: center;
-      border-radius: 3px;
-      border: none !important;
-
-      &.active, &:hover {
-        background-color: #F0F0F0;
-      }
-
-      &.active {
-        font-weight: $font-semibold !important;
-      }
-    }
-  }
-
-  .tab-content {
-    min-height: 75px;
-    padding: 15px;
-  }
-}
diff --git a/client/src/assets/images/admin/add.svg b/client/src/assets/images/admin/add.svg
new file mode 100644
index 000000000..42b269c43
--- /dev/null
+++ b/client/src/assets/images/admin/add.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Artboard-4" transform="translate(-92.000000, -115.000000)">
+            <g id="2" transform="translate(92.000000, 115.000000)">
+                <circle id="Oval-1" stroke="#ffffff" stroke-width="2" cx="12" cy="12" r="10"></circle>
+                <rect id="Rectangle-1" fill="#ffffff" x="11" y="7" width="2" height="10" rx="1"></rect>
+                <rect id="Rectangle-1" fill="#ffffff" x="7" y="11" width="10" height="2" rx="1"></rect>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index 3c5a00309..e7b4024a7 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -89,7 +89,12 @@ label {
 
   &:hover, &:active, &:focus {
     color: #000;
+  }
 }
+
+.admin-sub-title {
+  font-size: 20px;
+  font-weight: bold;
 }
 
 // On small screen, menu is absolute and displayed over the page
@@ -132,7 +137,7 @@ label {
   to { -moz-transform: rotate(360deg);}
 }
 
-/* ngprime data table customizations */
+// ngprime data table customizations
 p-datatable {
   .action-cell {
     text-align: center;
@@ -143,6 +148,7 @@ p-datatable {
   }
 }
 
+// Bootstrap customizations
 .dropdown-menu {
   border-radius: 3px;
   box-shadow: 0 3px 6px;
@@ -167,6 +173,40 @@ p-datatable {
   }
 }
 
+.nav {
+  margin-top: 10px;
+  font-size: 16px !important;
+  border: none !important;
+
+  .nav-item .nav-link {
+    height: 30px !important;
+    margin-right: 30px;
+    padding: 0 15px;
+    display: flex;
+    align-items: center;
+    border-radius: 3px;
+    border: none !important;
+
+    &, & a {
+      color: #000 !important;
+    }
+
+    &.active, &:hover {
+      background-color: #F0F0F0;
+    }
+
+    &.active {
+      font-weight: $font-semibold !important;
+    }
+  }
+
+  .tab-content {
+    min-height: 75px;
+    padding: 15px;
+  }
+}
+
+
 .orange-button {
   @include peertube-button;
   @include orange-button;
-- 
cgit v1.2.3


From cd83ea1b908efe594c1e03f886c0dc4742b91360 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Fri, 8 Dec 2017 14:34:17 +0100
Subject: Design admin data tables

---
 .../+admin/jobs/jobs-list/jobs-list.component.html | 36 ++++----
 .../+admin/jobs/jobs-list/jobs-list.component.scss |  3 +
 .../+admin/jobs/jobs-list/jobs-list.component.ts   | 24 +++---
 client/src/app/+admin/jobs/shared/job.service.ts   |  7 ++
 .../users/user-list/user-list.component.html       | 16 ++--
 .../users/user-list/user-list.component.scss       | 10 ---
 .../account-videos/account-videos.component.html   | 12 +--
 .../account-videos/account-videos.component.scss   | 27 +-----
 client/src/app/shared/misc/button.component.scss   | 27 ++++++
 .../app/shared/misc/delete-button.component.html   |  4 +
 .../src/app/shared/misc/delete-button.component.ts | 10 +++
 .../src/app/shared/misc/edit-button.component.html |  4 +
 .../src/app/shared/misc/edit-button.component.ts   | 11 +++
 client/src/app/shared/misc/utils.ts                |  5 ++
 client/src/app/shared/shared.module.ts             |  6 ++
 client/src/assets/images/account/delete-grey.svg   | 14 ---
 client/src/assets/images/account/delete-white.svg  | 14 ---
 client/src/assets/images/global/delete-grey.svg    | 14 +++
 client/src/assets/images/global/delete-white.svg   | 14 +++
 client/src/sass/application.scss                   | 99 +++++++++++++++++++++-
 20 files changed, 242 insertions(+), 115 deletions(-)
 create mode 100644 client/src/app/+admin/jobs/jobs-list/jobs-list.component.scss
 create mode 100644 client/src/app/shared/misc/button.component.scss
 create mode 100644 client/src/app/shared/misc/delete-button.component.html
 create mode 100644 client/src/app/shared/misc/delete-button.component.ts
 create mode 100644 client/src/app/shared/misc/edit-button.component.html
 create mode 100644 client/src/app/shared/misc/edit-button.component.ts
 delete mode 100644 client/src/assets/images/account/delete-grey.svg
 delete mode 100644 client/src/assets/images/account/delete-white.svg
 create mode 100644 client/src/assets/images/global/delete-grey.svg
 create mode 100644 client/src/assets/images/global/delete-white.svg

(limited to 'client')

diff --git a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.html b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.html
index a90267172..29103c06b 100644
--- a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.html
+++ b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.html
@@ -1,18 +1,20 @@
-<div class="row">
-  <div class="content-padding">
-    <h3>Jobs list</h3>
-
-    <p-dataTable
-        [value]="jobs" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
-        sortField="createdAt" (onLazyLoad)="loadLazy($event)"
-    >
-      <p-column field="id" header="ID"></p-column>
-      <p-column field="category" header="Category"></p-column>
-      <p-column field="handlerName" header="Handler name"></p-column>
-      <p-column field="handlerInputData" header="Input data"></p-column>
-      <p-column field="state" header="State"></p-column>
-      <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
-      <p-column field="updatedAt" header="Updated date"></p-column>
-    </p-dataTable>
-  </div>
+<div class="admin-sub-header">
+  <div class="admin-sub-title">Jobs list</div>
 </div>
+
+<p-dataTable
+    [value]="jobs" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
+    sortField="createdAt" (onLazyLoad)="loadLazy($event)" [scrollable]="true" [virtualScroll]="true" [scrollHeight]="scrollHeight"
+>
+  <p-column field="id" header="ID" [style]="{ width: '40px' }"></p-column>
+  <p-column field="category" header="Category" [style]="{ width: '100px' }"></p-column>
+  <p-column field="handlerName" header="Handler name" [style]="{ width: '150px' }"></p-column>
+  <p-column header="Input data">
+    <ng-template pTemplate="body" let-job="rowData">
+      <pre>{{ job.handlerInputData }}</pre>
+    </ng-template>
+  </p-column>
+  <p-column field="state" header="State" [style]="{ width: '100px' }"></p-column>
+  <p-column field="createdAt" header="Created date" [sortable]="true" [style]="{ width: '250px' }"></p-column>
+  <p-column field="updatedAt" header="Updated date" [style]="{ width: '250px' }"></p-column>
+</p-dataTable>
diff --git a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.scss b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.scss
new file mode 100644
index 000000000..9dde13216
--- /dev/null
+++ b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.scss
@@ -0,0 +1,3 @@
+pre {
+  font-size: 13px;
+}
diff --git a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts
index 88fe259fb..f93847f29 100644
--- a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts
+++ b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts
@@ -1,22 +1,24 @@
-import { Component } from '@angular/core'
+import { Component, OnInit } from '@angular/core'
 import { NotificationsService } from 'angular2-notifications'
 import { SortMeta } from 'primeng/primeng'
 import { Job } from '../../../../../../shared/index'
 import { RestPagination, RestTable } from '../../../shared'
+import { viewportHeight } from '../../../shared/misc/utils'
 import { JobService } from '../shared'
 import { RestExtractor } from '../../../shared/rest/rest-extractor.service'
 
 @Component({
   selector: 'my-jobs-list',
   templateUrl: './jobs-list.component.html',
-  styleUrls: [ ]
+  styleUrls: [ './jobs-list.component.scss' ]
 })
-export class JobsListComponent extends RestTable {
+export class JobsListComponent extends RestTable implements OnInit {
   jobs: Job[] = []
   totalRecords = 0
-  rowsPerPage = 10
+  rowsPerPage = 20
   sort: SortMeta = { field: 'createdAt', order: 1 }
   pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
+  scrollHeight = ''
 
   constructor (
     private notificationsService: NotificationsService,
@@ -26,10 +28,14 @@ export class JobsListComponent extends RestTable {
     super()
   }
 
+  ngOnInit () {
+    // 270 -> headers + footer...
+    this.scrollHeight = (viewportHeight() - 380) + 'px'
+  }
+
   protected loadData () {
     this.jobsService
       .getJobs(this.pagination, this.sort)
-      .map(res => this.restExtractor.applyToResultListData(res, this.formatJob.bind(this)))
       .subscribe(
         resultList => {
           this.jobs = resultList.data
@@ -39,12 +45,4 @@ export class JobsListComponent extends RestTable {
         err => this.notificationsService.error('Error', err.message)
       )
   }
-
-  private formatJob (job: Job) {
-    const handlerInputData = JSON.stringify(job.handlerInputData)
-
-    return Object.assign(job, {
-      handlerInputData
-    })
-  }
 }
diff --git a/client/src/app/+admin/jobs/shared/job.service.ts b/client/src/app/+admin/jobs/shared/job.service.ts
index 49f1ab6f5..0cfbdbbea 100644
--- a/client/src/app/+admin/jobs/shared/job.service.ts
+++ b/client/src/app/+admin/jobs/shared/job.service.ts
@@ -25,6 +25,13 @@ export class JobService {
 
     return this.authHttp.get<ResultList<Job>>(JobService.BASE_JOB_URL, { params })
       .map(res => this.restExtractor.convertResultListDateToHuman(res))
+      .map(res => this.restExtractor.applyToResultListData(res, this.prettyPrintData))
       .catch(err => this.restExtractor.handleError(err))
   }
+
+  private prettyPrintData (obj: Job) {
+    const handlerInputData = JSON.stringify(obj.handlerInputData, null, 2)
+
+    return Object.assign(obj, { handlerInputData })
+  }
 }
diff --git a/client/src/app/+admin/users/user-list/user-list.component.html b/client/src/app/+admin/users/user-list/user-list.component.html
index a100ddfaa..5a19edfde 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.html
+++ b/client/src/app/+admin/users/user-list/user-list.component.html
@@ -1,4 +1,4 @@
-<div class="sub-header">
+<div class="admin-sub-header">
   <div class="admin-sub-title">Users list</div>
 
   <a class="add-button" routerLink="/admin/users/add">
@@ -17,16 +17,10 @@
   <p-column field="videoQuota" header="Video quota"></p-column>
   <p-column field="roleLabel" header="Role"></p-column>
   <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
-  <p-column header="Edit" styleClass="action-cell">
-    <ng-template pTemplate="body" let-user="rowData">
-      <a [routerLink]="getRouterUserEditLink(user)" title="Edit this user">
-        <span class="glyphicon glyphicon-pencil glyphicon-black"></span>
-      </a>
-    </ng-template>
-  </p-column>
-  <p-column header="Delete" styleClass="action-cell">
-    <ng-template pTemplate="body" let-user="rowData">
-      <span (click)="removeUser(user)" class="glyphicon glyphicon-remove glyphicon-black" title="Remove this user"></span>
+  <p-column styleClass="action-cell">
+      <ng-template pTemplate="body" let-user="rowData">
+      <my-edit-button [routerLink]="getRouterUserEditLink(user)"></my-edit-button>
+      <my-delete-button (click)="removeUser(user)"></my-delete-button>
     </ng-template>
   </p-column>
 </p-dataTable>
diff --git a/client/src/app/+admin/users/user-list/user-list.component.scss b/client/src/app/+admin/users/user-list/user-list.component.scss
index 54ecb61b4..8b22f67ff 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.scss
+++ b/client/src/app/+admin/users/user-list/user-list.component.scss
@@ -1,12 +1,3 @@
-.sub-header {
-  display: flex;
-  align-items: center;
-  margin-bottom: 30px;
-
-  .admin-sub-title {
-    flex-grow: 1;
-  }
-
   .add-button {
     @include peertube-button-link;
     @include orange-button;
@@ -18,4 +9,3 @@
       background-image: url('../../../../assets/images/admin/add.svg');
     }
   }
-}
diff --git a/client/src/app/account/account-videos/account-videos.component.html b/client/src/app/account/account-videos/account-videos.component.html
index 030c2f19c..641fcb38a 100644
--- a/client/src/app/account/account-videos/account-videos.component.html
+++ b/client/src/app/account/account-videos/account-videos.component.html
@@ -22,7 +22,7 @@
           Cancel
         </span>
 
-            <span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()">
+        <span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()">
           <span class="icon icon-delete-white"></span>
           Delete
         </span>
@@ -30,15 +30,9 @@
     </div>
 
     <ng-template [ngIf]="isInSelectionMode() === false">
-      <span class="action-button action-button-delete" (click)="deleteVideo(video)">
-        <span class="icon icon-delete-grey"></span>
-        Delete
-      </span>
+      <my-delete-button (click)="deleteVideo(video)"></my-delete-button>
 
-      <a class="action-button" [routerLink]="[ '/videos', 'edit', video.uuid ]">
-        <span class="icon icon-edit"></span>
-        Edit
-      </a>
+      <my-edit-button [routerLink]="[ '/videos', 'edit', video.uuid ]"></my-edit-button>
     </ng-template>
   </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
index 083918e29..670fe992c 100644
--- a/client/src/app/account/account-videos/account-videos.component.scss
+++ b/client/src/app/account/account-videos/account-videos.component.scss
@@ -6,17 +6,7 @@
   }
 }
 
-.action-button {
-  @include peertube-button-link;
-
-  font-size: 15px;
-  font-weight: $font-semibold;
-  color: #585858;
-  background-color: #E5E5E5;
-
-  &:hover {
-    background-color: #EFEFEF;
-  }
+/deep/ .action-button {
 
   &.action-button-delete {
     margin-right: 10px;
@@ -32,21 +22,8 @@
   }
 
   .icon {
-    @include icon(21px);
-
-    position: relative;
-    top: -2px;
-
-    &.icon-edit {
-      background-image: url('../../../assets/images/global/edit.svg');
-    }
-
-    &.icon-delete-grey {
-      background-image: url('../../../assets/images/account/delete-grey.svg');
-    }
-
     &.icon-delete-white {
-      background-image: url('../../../assets/images/account/delete-white.svg');
+      background-image: url('../../../assets/images/global/delete-white.svg');
     }
   }
 }
diff --git a/client/src/app/shared/misc/button.component.scss b/client/src/app/shared/misc/button.component.scss
new file mode 100644
index 000000000..5fcae4f10
--- /dev/null
+++ b/client/src/app/shared/misc/button.component.scss
@@ -0,0 +1,27 @@
+.action-button {
+  @include peertube-button-link;
+
+  font-size: 15px;
+  font-weight: $font-semibold;
+  color: #585858;
+  background-color: #E5E5E5;
+
+  &:hover {
+    background-color: #EFEFEF;
+  }
+
+  .icon {
+    @include icon(21px);
+
+    position: relative;
+    top: -2px;
+
+    &.icon-edit {
+      background-image: url('../../../assets/images/global/edit.svg');
+    }
+
+    &.icon-delete-grey {
+      background-image: url('../../../assets/images/global/delete-grey.svg');
+    }
+  }
+}
diff --git a/client/src/app/shared/misc/delete-button.component.html b/client/src/app/shared/misc/delete-button.component.html
new file mode 100644
index 000000000..3db483882
--- /dev/null
+++ b/client/src/app/shared/misc/delete-button.component.html
@@ -0,0 +1,4 @@
+<span class="action-button action-button-delete" >
+  <span class="icon icon-delete-grey"></span>
+  Delete
+</span>
diff --git a/client/src/app/shared/misc/delete-button.component.ts b/client/src/app/shared/misc/delete-button.component.ts
new file mode 100644
index 000000000..e04039f69
--- /dev/null
+++ b/client/src/app/shared/misc/delete-button.component.ts
@@ -0,0 +1,10 @@
+import { Component } from '@angular/core'
+
+@Component({
+  selector: 'my-delete-button',
+  styleUrls: [ './button.component.scss' ],
+  templateUrl: './delete-button.component.html'
+})
+
+export class DeleteButtonComponent {
+}
diff --git a/client/src/app/shared/misc/edit-button.component.html b/client/src/app/shared/misc/edit-button.component.html
new file mode 100644
index 000000000..6e9564bd7
--- /dev/null
+++ b/client/src/app/shared/misc/edit-button.component.html
@@ -0,0 +1,4 @@
+<a class="action-button" [routerLink]="routerLink">
+  <span class="icon icon-edit"></span>
+  Edit
+</a>
diff --git a/client/src/app/shared/misc/edit-button.component.ts b/client/src/app/shared/misc/edit-button.component.ts
new file mode 100644
index 000000000..201a618ec
--- /dev/null
+++ b/client/src/app/shared/misc/edit-button.component.ts
@@ -0,0 +1,11 @@
+import { Component, Input } from '@angular/core'
+
+@Component({
+  selector: 'my-edit-button',
+  styleUrls: [ './button.component.scss' ],
+  templateUrl: './edit-button.component.html'
+})
+
+export class EditButtonComponent {
+  @Input() routerLink = []
+}
diff --git a/client/src/app/shared/misc/utils.ts b/client/src/app/shared/misc/utils.ts
index 2b5c3686e..df9e0381a 100644
--- a/client/src/app/shared/misc/utils.ts
+++ b/client/src/app/shared/misc/utils.ts
@@ -13,6 +13,11 @@ function getParameterByName (name: string, url: string) {
   return decodeURIComponent(results[2].replace(/\+/g, ' '))
 }
 
+function viewportHeight () {
+  return Math.max(document.documentElement.clientHeight, window.innerHeight || 0)
+}
+
 export {
+  viewportHeight,
   getParameterByName
 }
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 74f6f579d..d0e163f69 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -12,6 +12,8 @@ import { SharedModule as PrimeSharedModule } from 'primeng/components/common/sha
 import { DataTableModule } from 'primeng/components/datatable/datatable'
 
 import { AUTH_INTERCEPTOR_PROVIDER } from './auth'
+import { DeleteButtonComponent } from './misc/delete-button.component'
+import { EditButtonComponent } from './misc/edit-button.component'
 import { FromNowPipe } from './misc/from-now.pipe'
 import { LoaderComponent } from './misc/loader.component'
 import { NumberFormatterPipe } from './misc/number-formatter.pipe'
@@ -44,6 +46,8 @@ import { VideoService } from './video/video.service'
     LoaderComponent,
     VideoThumbnailComponent,
     VideoMiniatureComponent,
+    DeleteButtonComponent,
+    EditButtonComponent,
     NumberFormatterPipe,
     FromNowPipe
   ],
@@ -66,6 +70,8 @@ import { VideoService } from './video/video.service'
     LoaderComponent,
     VideoThumbnailComponent,
     VideoMiniatureComponent,
+    DeleteButtonComponent,
+    EditButtonComponent,
 
     NumberFormatterPipe,
     FromNowPipe
diff --git a/client/src/assets/images/account/delete-grey.svg b/client/src/assets/images/account/delete-grey.svg
deleted file mode 100644
index 67e9e2ce7..000000000
--- a/client/src/assets/images/account/delete-grey.svg
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-224.000000, -159.000000)">
-            <g id="25" transform="translate(224.000000, 159.000000)">
-                <path d="M5,7 L5,20.0081158 C5,21.1082031 5.89706013,22 7.00585866,22 L16.9941413,22 C18.1019465,22 19,21.1066027 19,20.0081158 L19,7" id="Path-296" stroke="#585858" stroke-width="2"></path>
-                <rect id="Rectangle-424" fill="#585858" x="2" y="4" width="20" height="2" rx="1"></rect>
-                <path d="M9,10.9970301 C9,10.4463856 9.44386482,10 10,10 C10.5522847,10 11,10.4530363 11,10.9970301 L11,17.0029699 C11,17.5536144 10.5561352,18 10,18 C9.44771525,18 9,17.5469637 9,17.0029699 L9,10.9970301 Z M13,10.9970301 C13,10.4463856 13.4438648,10 14,10 C14.5522847,10 15,10.4530363 15,10.9970301 L15,17.0029699 C15,17.5536144 14.5561352,18 14,18 C13.4477153,18 13,17.5469637 13,17.0029699 L13,10.9970301 Z" id="Combined-Shape" fill="#585858"></path>
-                <path d="M9,5 L9,2.99895656 C9,2.44724809 9.45097518,2 9.99077797,2 L14.009222,2 C14.5564136,2 15,2.44266033 15,2.99895656 L15,5" id="Path-33" stroke="#585858" stroke-width="2" stroke-linejoin="round"></path>
-            </g>
-        </g>
-    </g>
-</svg>
diff --git a/client/src/assets/images/account/delete-white.svg b/client/src/assets/images/account/delete-white.svg
deleted file mode 100644
index 9c52de557..000000000
--- a/client/src/assets/images/account/delete-white.svg
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-224.000000, -159.000000)">
-            <g id="25" transform="translate(224.000000, 159.000000)">
-                <path d="M5,7 L5,20.0081158 C5,21.1082031 5.89706013,22 7.00585866,22 L16.9941413,22 C18.1019465,22 19,21.1066027 19,20.0081158 L19,7" id="Path-296" stroke="#ffffff" stroke-width="2"></path>
-                <rect id="Rectangle-424" fill="#ffffff" x="2" y="4" width="20" height="2" rx="1"></rect>
-                <path d="M9,10.9970301 C9,10.4463856 9.44386482,10 10,10 C10.5522847,10 11,10.4530363 11,10.9970301 L11,17.0029699 C11,17.5536144 10.5561352,18 10,18 C9.44771525,18 9,17.5469637 9,17.0029699 L9,10.9970301 Z M13,10.9970301 C13,10.4463856 13.4438648,10 14,10 C14.5522847,10 15,10.4530363 15,10.9970301 L15,17.0029699 C15,17.5536144 14.5561352,18 14,18 C13.4477153,18 13,17.5469637 13,17.0029699 L13,10.9970301 Z" id="Combined-Shape" fill="#ffffff"></path>
-                <path d="M9,5 L9,2.99895656 C9,2.44724809 9.45097518,2 9.99077797,2 L14.009222,2 C14.5564136,2 15,2.44266033 15,2.99895656 L15,5" id="Path-33" stroke="#ffffff" stroke-width="2" stroke-linejoin="round"></path>
-            </g>
-        </g>
-    </g>
-</svg>
diff --git a/client/src/assets/images/global/delete-grey.svg b/client/src/assets/images/global/delete-grey.svg
new file mode 100644
index 000000000..67e9e2ce7
--- /dev/null
+++ b/client/src/assets/images/global/delete-grey.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Artboard-4" transform="translate(-224.000000, -159.000000)">
+            <g id="25" transform="translate(224.000000, 159.000000)">
+                <path d="M5,7 L5,20.0081158 C5,21.1082031 5.89706013,22 7.00585866,22 L16.9941413,22 C18.1019465,22 19,21.1066027 19,20.0081158 L19,7" id="Path-296" stroke="#585858" stroke-width="2"></path>
+                <rect id="Rectangle-424" fill="#585858" x="2" y="4" width="20" height="2" rx="1"></rect>
+                <path d="M9,10.9970301 C9,10.4463856 9.44386482,10 10,10 C10.5522847,10 11,10.4530363 11,10.9970301 L11,17.0029699 C11,17.5536144 10.5561352,18 10,18 C9.44771525,18 9,17.5469637 9,17.0029699 L9,10.9970301 Z M13,10.9970301 C13,10.4463856 13.4438648,10 14,10 C14.5522847,10 15,10.4530363 15,10.9970301 L15,17.0029699 C15,17.5536144 14.5561352,18 14,18 C13.4477153,18 13,17.5469637 13,17.0029699 L13,10.9970301 Z" id="Combined-Shape" fill="#585858"></path>
+                <path d="M9,5 L9,2.99895656 C9,2.44724809 9.45097518,2 9.99077797,2 L14.009222,2 C14.5564136,2 15,2.44266033 15,2.99895656 L15,5" id="Path-33" stroke="#585858" stroke-width="2" stroke-linejoin="round"></path>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/global/delete-white.svg b/client/src/assets/images/global/delete-white.svg
new file mode 100644
index 000000000..9c52de557
--- /dev/null
+++ b/client/src/assets/images/global/delete-white.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Artboard-4" transform="translate(-224.000000, -159.000000)">
+            <g id="25" transform="translate(224.000000, 159.000000)">
+                <path d="M5,7 L5,20.0081158 C5,21.1082031 5.89706013,22 7.00585866,22 L16.9941413,22 C18.1019465,22 19,21.1066027 19,20.0081158 L19,7" id="Path-296" stroke="#ffffff" stroke-width="2"></path>
+                <rect id="Rectangle-424" fill="#ffffff" x="2" y="4" width="20" height="2" rx="1"></rect>
+                <path d="M9,10.9970301 C9,10.4463856 9.44386482,10 10,10 C10.5522847,10 11,10.4530363 11,10.9970301 L11,17.0029699 C11,17.5536144 10.5561352,18 10,18 C9.44771525,18 9,17.5469637 9,17.0029699 L9,10.9970301 Z M13,10.9970301 C13,10.4463856 13.4438648,10 14,10 C14.5522847,10 15,10.4530363 15,10.9970301 L15,17.0029699 C15,17.5536144 14.5561352,18 14,18 C13.4477153,18 13,17.5469637 13,17.0029699 L13,10.9970301 Z" id="Combined-Shape" fill="#ffffff"></path>
+                <path d="M9,5 L9,2.99895656 C9,2.44724809 9.45097518,2 9.99077797,2 L14.009222,2 C14.5564136,2 15,2.44266033 15,2.99895656 L15,5" id="Path-33" stroke="#ffffff" stroke-width="2" stroke-linejoin="round"></path>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index e7b4024a7..5277e2070 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -92,6 +92,16 @@ label {
   }
 }
 
+.admin-sub-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 30px;
+
+  .admin-sub-title {
+    flex-grow: 1;
+  }
+}
+
 .admin-sub-title {
   font-size: 20px;
   font-weight: bold;
@@ -139,11 +149,96 @@ label {
 
 // ngprime data table customizations
 p-datatable {
+  font-size: 15px !important;
+
+  .ui-datatable-scrollable-header {
+    background-color: #fff !important;
+  }
+
+  .ui-widget-content {
+    border: none !important;
+  }
+
+  .ui-datatable-virtual-table {
+    border-top: none !important;
+  }
+
+  td {
+    border: 1px solid #E5E5E5 !important;
+    padding: 15px;
+  }
+
+  tr {
+    background-color: #fff !important;
+    height: 46px;
+
+    &:hover {
+      background-color: #f0f0f0 !important;
+    }
+
+    &:not(:hover) {
+      .action-cell * {
+        display: none !important;
+      }
+    }
+
+    &:first-child td {
+      border-top: none !important;
+    }
+  }
+
+  th {
+    border: none !important;
+    border-bottom: 1px solid #f0f0f0 !important;
+    text-align: left !important;
+    padding: 5px 0 5px 15px !important;
+    font-weight: $font-semibold !important;
+    color: #000 !important;
+
+    &.ui-state-active, &.ui-sortable-column:hover {
+      background-color: #f0f0f0 !important;
+      border: 1px solid #f0f0f0 !important;
+    }
+  }
+
   .action-cell {
+    width: 250px !important;
+    padding: 0 !important;
     text-align: center;
+  }
 
-    .glyphicon {
-      cursor: pointer;
+  p-paginator {
+    overflow: hidden;
+    display: block;
+    padding-top: 2px;
+    border: 1px solid #f0f0f0 !important;
+    border-top: none !important;
+
+    .ui-paginator-bottom {
+      position: relative;
+      border: none !important;
+      border-top: 1px solid #f0f0f0 !important;
+      box-shadow: 0 -1px 3px rgba(0, 0, 0, 0.16);
+      height: 40px;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+
+      a {
+        color: #000 !important;
+        font-weight: $font-semibold !important;
+        margin-right: 20px !important;
+        outline: 0 !important;
+        border-radius: 3px !important;
+        padding: 5px 2px !important;
+
+        &.ui-state-active {
+          &, &:hover, &:active, &:focus {
+            color: #fff !important;
+            background-color: $orange-color !important;
+          }
+        }
+      }
     }
   }
 }
-- 
cgit v1.2.3


From e600e1fea275c12f4420e23624804617e61a082c Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Fri, 8 Dec 2017 15:22:57 +0100
Subject: Design follow admin page

---
 .../followers-list/followers-list.component.html   |  2 -
 .../followers-list/followers-list.component.scss   |  3 -
 .../following-add/following-add.component.html     | 32 +++-----
 .../following-add/following-add.component.scss     | 10 ++-
 .../following-add/following-add.component.ts       | 94 +++++++---------------
 .../following-list/following-list.component.html   |  4 +-
 .../src/app/+admin/follows/follows.component.html  |  6 +-
 .../src/app/+admin/follows/follows.component.scss  |  5 +-
 client/src/app/+admin/follows/follows.component.ts |  2 +-
 .../users/user-edit/user-edit.component.html       |  2 +-
 .../users/user-list/user-list.component.html       |  2 +-
 .../shared/forms/form-validators/host.validator.ts | 10 +--
 client/src/sass/_mixins.scss                       |  1 +
 client/src/sass/application.scss                   | 14 ++--
 14 files changed, 73 insertions(+), 114 deletions(-)

(limited to 'client')

diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.html b/client/src/app/+admin/follows/followers-list/followers-list.component.html
index ea5380ff7..a24039fc6 100644
--- a/client/src/app/+admin/follows/followers-list/followers-list.component.html
+++ b/client/src/app/+admin/follows/followers-list/followers-list.component.html
@@ -1,5 +1,3 @@
-<h3>Followers list</h3>
-
 <p-dataTable
     [value]="followers" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
     sortField="createdAt" (onLazyLoad)="loadLazy($event)"
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.scss b/client/src/app/+admin/follows/followers-list/followers-list.component.scss
index 0a0f621c6..e69de29bb 100644
--- a/client/src/app/+admin/follows/followers-list/followers-list.component.scss
+++ b/client/src/app/+admin/follows/followers-list/followers-list.component.scss
@@ -1,3 +0,0 @@
-.btn {
-  margin-top: 10px;
-}
diff --git a/client/src/app/+admin/follows/following-add/following-add.component.html b/client/src/app/+admin/follows/following-add/following-add.component.html
index 65c1eda0c..25bab9d0d 100644
--- a/client/src/app/+admin/follows/following-add/following-add.component.html
+++ b/client/src/app/+admin/follows/following-add/following-add.component.html
@@ -1,30 +1,22 @@
-<h3>Add following</h3>
-
 <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
 
-<form (ngSubmit)="addFollowing()" [formGroup]="form">
-  <div class="form-group"  *ngFor="let host of hosts; let id = index; trackBy:customTrackBy">
-    <label [for]="'host-' + id">Host (so without "http://")</label>
+<form (ngSubmit)="addFollowing()">
+  <div class="form-group">
+    <label for="hosts">1 host (without "http://") per line</label>
 
-    <div class="input-group">
-      <input
-        type="text" class="form-control" placeholder="example.com"
-        [id]="'host-' + id" [formControlName]="'host-' + id"
-      />
-      <span class="input-group-btn">
-        <button *ngIf="displayAddField(id)" (click)="addField()" class="btn btn-default" type="button">+</button>
-        <button *ngIf="displayRemoveField(id)" (click)="removeField(id)" class="btn btn-default" type="button">-</button>
-      </span>
-    </div>
+    <textarea
+      type="text" class="form-control" placeholder="example.com" id="hosts" name="hosts"
+      [(ngModel)]="hostsString" (ngModelChange)="onHostsChanged()" [ngClass]="{ 'input-error': hostsError }"
+    ></textarea>
 
-    <div [hidden]="form.controls['host-' + id].valid || form.controls['host-' + id].pristine" class="alert alert-warning">
-      It should be a valid host.
+    <div *ngIf="hostsError" class="form-error">
+      {{ hostsError }}
     </div>
   </div>
 
-  <div *ngIf="canMakeFriends() === false"  class="alert alert-warning">
-    It seems that you are not on a HTTPS server. Your webserver need to have TLS activated in order to follow servers.
+  <div *ngIf="httpEnabled() === false"  class="alert alert-warning">
+    It seems that you are not on a HTTPS server. Your webserver needs to have TLS activated in order to follow servers.
   </div>
 
-  <input type="submit" value="Add following" class="btn btn-default" [disabled]="!isFormValid()">
+  <input type="submit" value="Add following" [disabled]="hostsError || !hostsString" class="btn btn-default">
 </form>
diff --git a/client/src/app/+admin/follows/following-add/following-add.component.scss b/client/src/app/+admin/follows/following-add/following-add.component.scss
index 5fde51636..2cb3efe28 100644
--- a/client/src/app/+admin/follows/following-add/following-add.component.scss
+++ b/client/src/app/+admin/follows/following-add/following-add.component.scss
@@ -1,7 +1,9 @@
-table {
-  margin-bottom: 40px;
+textarea {
+  height: 250px;
 }
 
-.input-group-btn button {
-  width: 35px;
+input[type=submit] {
+  @include peertube-button;
+  @include orange-button;
 }
+
diff --git a/client/src/app/+admin/follows/following-add/following-add.component.ts b/client/src/app/+admin/follows/following-add/following-add.component.ts
index 814c6f1a1..bf842129d 100644
--- a/client/src/app/+admin/follows/following-add/following-add.component.ts
+++ b/client/src/app/+admin/follows/following-add/following-add.component.ts
@@ -1,9 +1,6 @@
-import { Component, OnInit } from '@angular/core'
-import { FormControl, FormGroup } from '@angular/forms'
+import { Component } from '@angular/core'
 import { Router } from '@angular/router'
-
 import { NotificationsService } from 'angular2-notifications'
-
 import { ConfirmService } from '../../../core'
 import { validateHost } from '../../../shared'
 import { FollowService } from '../shared'
@@ -13,9 +10,9 @@ import { FollowService } from '../shared'
   templateUrl: './following-add.component.html',
   styleUrls: [ './following-add.component.scss' ]
 })
-export class FollowingAddComponent implements OnInit {
-  form: FormGroup
-  hosts: string[] = [ ]
+export class FollowingAddComponent {
+  hostsString = ''
+  hostsError: string = null
   error: string = null
 
   constructor (
@@ -25,76 +22,50 @@ export class FollowingAddComponent implements OnInit {
     private followService: FollowService
   ) {}
 
-  ngOnInit () {
-    this.form = new FormGroup({})
-    this.addField()
-  }
-
-  addField () {
-    this.form.addControl(`host-${this.hosts.length}`, new FormControl('', [ validateHost ]))
-    this.hosts.push('')
-  }
-
-  canMakeFriends () {
+  httpEnabled () {
     return window.location.protocol === 'https:'
   }
 
-  customTrackBy (index: number, obj: any): any {
-    return index
-  }
-
-  displayAddField (index: number) {
-    return index === (this.hosts.length - 1)
-  }
+  onHostsChanged () {
+    this.hostsError = null
 
-  displayRemoveField (index: number) {
-    return (index !== 0 || this.hosts.length > 1) && index !== (this.hosts.length - 1)
-  }
+    const newHostsErrors = []
+    const hosts = this.getNotEmptyHosts()
 
-  isFormValid () {
-    // Do not check the last input
-    for (let i = 0; i < this.hosts.length - 1; i++) {
-      if (!this.form.controls[`host-${i}`].valid) return false
+    for (const host of hosts) {
+      if (validateHost(host) === false) {
+        newHostsErrors.push(`${host} is not valid`)
+      }
     }
 
-    const lastIndex = this.hosts.length - 1
-    // If the last input (which is not the first) is empty, it's ok
-    if (this.hosts[lastIndex] === '' && lastIndex !== 0) {
-      return true
-    } else {
-      return this.form.controls[`host-${lastIndex}`].valid
+    if (newHostsErrors.length !== 0) {
+      this.hostsError = newHostsErrors.join('. ')
     }
   }
 
-  removeField (index: number) {
-    // Remove the last control
-    this.form.removeControl(`host-${this.hosts.length - 1}`)
-    this.hosts.splice(index, 1)
-  }
-
   addFollowing () {
     this.error = ''
 
-    const notEmptyHosts = this.getNotEmptyHosts()
-    if (notEmptyHosts.length === 0) {
-      this.error = 'You need to specify at least 1 host.'
-      return
+    const hosts = this.getNotEmptyHosts()
+    if (hosts.length === 0) {
+      this.error = 'You need to specify hosts to follow.'
     }
 
-    if (!this.isHostsUnique(notEmptyHosts)) {
+    if (!this.isHostsUnique(hosts)) {
       this.error = 'Hosts need to be unique.'
       return
     }
 
-    const confirmMessage = 'Are you sure to make friends with:<br /> - ' + notEmptyHosts.join('<br /> - ')
+    const confirmMessage = 'If you confirm, you will send a follow request to:<br /> - ' + hosts.join('<br /> - ')
     this.confirmService.confirm(confirmMessage, 'Follow new server(s)').subscribe(
       res => {
         if (res === false) return
 
-        this.followService.follow(notEmptyHosts).subscribe(
+        this.followService.follow(hosts).subscribe(
           status => {
             this.notificationsService.success('Success', 'Follow request(s) sent!')
-            this.router.navigate([ '/admin/follows/following-list' ])
+
+            setTimeout(() => this.router.navigate([ '/admin/follows/following-list' ]), 500)
           },
 
           err => this.notificationsService.error('Error', err.message)
@@ -103,18 +74,15 @@ export class FollowingAddComponent implements OnInit {
     )
   }
 
-  private getNotEmptyHosts () {
-    const notEmptyHosts = []
-
-    Object.keys(this.form.value).forEach((hostKey) => {
-      const host = this.form.value[hostKey]
-      if (host !== '') notEmptyHosts.push(host)
-    })
-
-    return notEmptyHosts
-  }
-
   private isHostsUnique (hosts: string[]) {
     return hosts.every(host => hosts.indexOf(host) === hosts.lastIndexOf(host))
   }
+
+  private getNotEmptyHosts () {
+    const hosts = this.hostsString
+      .split('\n')
+      .filter(host => host && host.length !== 0) // Eject empty hosts
+
+    return hosts
+  }
 }
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.html b/client/src/app/+admin/follows/following-list/following-list.component.html
index 85c7c3af1..3e70b418c 100644
--- a/client/src/app/+admin/follows/following-list/following-list.component.html
+++ b/client/src/app/+admin/follows/following-list/following-list.component.html
@@ -1,5 +1,3 @@
-<h3>Following list</h3>
-
 <p-dataTable
     [value]="following" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
     sortField="createdAt" (onLazyLoad)="loadLazy($event)"
@@ -10,7 +8,7 @@
   <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
   <p-column header="Unfollow" styleClass="action-cell">
     <ng-template pTemplate="body" let-following="rowData">
-      <span (click)="removeFollowing(following)" class="glyphicon glyphicon-remove glyphicon-black" title="Unfollow"></span>
+      <my-delete-button (click)="removeFollowing(following)"></my-delete-button>
     </ng-template>
   </p-column>
 </p-dataTable>
diff --git a/client/src/app/+admin/follows/follows.component.html b/client/src/app/+admin/follows/follows.component.html
index b67bc9736..1baba5a4d 100644
--- a/client/src/app/+admin/follows/follows.component.html
+++ b/client/src/app/+admin/follows/follows.component.html
@@ -1,4 +1,6 @@
-<div class="follows-menu">
+<div class="admin-sub-header">
+  <div class="admin-sub-title">Manage follows</div>
+
   <tabset #followsMenuTabs>
     <tab *ngFor="let link of links">
       <ng-template tabHeading>
@@ -8,4 +10,6 @@
   </tabset>
 </div>
 
+
+
 <router-outlet></router-outlet>
diff --git a/client/src/app/+admin/follows/follows.component.scss b/client/src/app/+admin/follows/follows.component.scss
index 242effd85..835fa3b78 100644
--- a/client/src/app/+admin/follows/follows.component.scss
+++ b/client/src/app/+admin/follows/follows.component.scss
@@ -1,3 +1,4 @@
-.follows-menu {
-  margin-top: 20px;
+.admin-sub-title {
+  flex-grow: 0;
+  margin-right: 30px;
 }
diff --git a/client/src/app/+admin/follows/follows.component.ts b/client/src/app/+admin/follows/follows.component.ts
index a1be82585..f29ad384f 100644
--- a/client/src/app/+admin/follows/follows.component.ts
+++ b/client/src/app/+admin/follows/follows.component.ts
@@ -47,7 +47,7 @@ export class FollowsComponent implements OnInit, AfterViewInit {
     for (let i = 0; i < this.links.length; i++) {
       const path = this.links[i].path
 
-      if (url.endsWith(path) === true) {
+      if (url.endsWith(path) === true && this.followsMenuTabs.tabs[i]) {
         this.followsMenuTabs.tabs[i].active = true
         return
       }
diff --git a/client/src/app/+admin/users/user-edit/user-edit.component.html b/client/src/app/+admin/users/user-edit/user-edit.component.html
index ed27ea745..963e2f39a 100644
--- a/client/src/app/+admin/users/user-edit/user-edit.component.html
+++ b/client/src/app/+admin/users/user-edit/user-edit.component.html
@@ -64,5 +64,5 @@
     </div>
   </div>
 
-  <input type="submit" value="{{ getFormButtonTitle() }}" class="btn btn-default" [disabled]="!form.valid">
+  <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid">
 </form>
diff --git a/client/src/app/+admin/users/user-list/user-list.component.html b/client/src/app/+admin/users/user-list/user-list.component.html
index 5a19edfde..b3d90ba1e 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.html
+++ b/client/src/app/+admin/users/user-list/user-list.component.html
@@ -18,7 +18,7 @@
   <p-column field="roleLabel" header="Role"></p-column>
   <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
   <p-column styleClass="action-cell">
-      <ng-template pTemplate="body" let-user="rowData">
+    <ng-template pTemplate="body" let-user="rowData">
       <my-edit-button [routerLink]="getRouterUserEditLink(user)"></my-edit-button>
       <my-delete-button (click)="removeUser(user)"></my-delete-button>
     </ng-template>
diff --git a/client/src/app/shared/forms/form-validators/host.validator.ts b/client/src/app/shared/forms/form-validators/host.validator.ts
index 03e810fdb..c18a35f9b 100644
--- a/client/src/app/shared/forms/form-validators/host.validator.ts
+++ b/client/src/app/shared/forms/form-validators/host.validator.ts
@@ -1,14 +1,8 @@
-import { FormControl } from '@angular/forms'
-
-export function validateHost (c: FormControl) {
+export function validateHost (value: string) {
   // Thanks to http://stackoverflow.com/a/106223
   const HOST_REGEXP = new RegExp(
     '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$'
   )
 
-  return HOST_REGEXP.test(c.value) ? null : {
-    validateHost: {
-      valid: false
-    }
-  }
+  return HOST_REGEXP.test(value)
 }
diff --git a/client/src/sass/_mixins.scss b/client/src/sass/_mixins.scss
index d9c9e45ec..2a7192fb2 100644
--- a/client/src/sass/_mixins.scss
+++ b/client/src/sass/_mixins.scss
@@ -59,6 +59,7 @@
   text-align: center;
   padding: 0 17px 0 13px;
   cursor: pointer;
+  outline: 0;
 }
 
 @mixin peertube-button-link {
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index 5277e2070..ecbb8dac5 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -269,21 +269,25 @@ p-datatable {
 }
 
 .nav {
-  margin-top: 10px;
   font-size: 16px !important;
   border: none !important;
 
   .nav-item .nav-link {
-    height: 30px !important;
     margin-right: 30px;
-    padding: 0 15px;
-    display: flex;
-    align-items: center;
+    padding: 0;
     border-radius: 3px;
     border: none !important;
 
+    .tab-link {
+      display: flex !important;
+      align-items: center;
+      height: 30px !important;
+      padding: 0 15px;
+    }
+
     &, & a {
       color: #000 !important;
+      @include disable-default-a-behaviour;
     }
 
     &.active, &:hover {
-- 
cgit v1.2.3


From f595d3947708114deeed4312cc5ffd285745b090 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Fri, 8 Dec 2017 17:31:21 +0100
Subject: Finish admin design

---
 .../following-list/following-list.component.html   |  2 +-
 .../+admin/jobs/jobs-list/jobs-list.component.html |  2 +-
 .../video-abuse-list.component.html                | 41 ++++++++++------------
 .../video-abuse-list.component.scss                |  6 ++++
 .../video-abuse-list/video-abuse-list.component.ts |  3 +-
 .../video-blacklist-list.component.html            |  2 +-
 client/src/app/core/auth/auth.service.ts           | 22 +++++-------
 client/src/app/shared/video/abstract-video-list.ts |  6 +++-
 .../shared/video-description.component.html        |  2 +-
 .../shared/video-description.component.scss        | 17 +++++++++
 .../shared/video-description.component.ts          |  2 ++
 client/src/sass/application.scss                   | 21 ++++-------
 12 files changed, 70 insertions(+), 56 deletions(-)
 create mode 100644 client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.scss

(limited to 'client')

diff --git a/client/src/app/+admin/follows/following-list/following-list.component.html b/client/src/app/+admin/follows/following-list/following-list.component.html
index 3e70b418c..2b6cc9113 100644
--- a/client/src/app/+admin/follows/following-list/following-list.component.html
+++ b/client/src/app/+admin/follows/following-list/following-list.component.html
@@ -6,7 +6,7 @@
   <p-column field="following.host" header="Host"></p-column>
   <p-column field="state" header="State"></p-column>
   <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
-  <p-column header="Unfollow" styleClass="action-cell">
+  <p-column styleClass="action-cell">
     <ng-template pTemplate="body" let-following="rowData">
       <my-delete-button (click)="removeFollowing(following)"></my-delete-button>
     </ng-template>
diff --git a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.html b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.html
index 29103c06b..7aa5f4254 100644
--- a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.html
+++ b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.html
@@ -8,7 +8,7 @@
 >
   <p-column field="id" header="ID" [style]="{ width: '40px' }"></p-column>
   <p-column field="category" header="Category" [style]="{ width: '100px' }"></p-column>
-  <p-column field="handlerName" header="Handler name" [style]="{ width: '150px' }"></p-column>
+  <p-column field="handlerName" header="Handler name" [style]="{ width: '200px' }"></p-column>
   <p-column header="Input data">
     <ng-template pTemplate="body" let-job="rowData">
       <pre>{{ job.handlerInputData }}</pre>
diff --git a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html
index ab0a9d99f..d655a5e9b 100644
--- a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html
+++ b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html
@@ -1,24 +1,19 @@
-<div class="row">
-  <div class="content-padding">
-
-  <h3>Video abuses list</h3>
-
-  <p-dataTable
-      [value]="videoAbuses" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
-      sortField="id" (onLazyLoad)="loadLazy($event)"
-  >
-    <p-column field="id" header="ID" [sortable]="true"></p-column>
-    <p-column field="reason" header="Reason"></p-column>
-    <p-column field="reporterServerHost" header="Reporter server host"></p-column>
-    <p-column field="reporterUsername" header="Reporter username"></p-column>
-    <p-column field="videoName" header="Video name"></p-column>
-    <p-column header="Video" styleClass="action-cell">
-      <ng-template pTemplate="body" let-videoAbuse="rowData">
-        <a [routerLink]="getRouterVideoLink(videoAbuse.videoId)" title="Go to the video">{{ videoAbuse.videoId }}</a>
-      </ng-template>
-    </p-column>
-    <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
-  </p-dataTable>
-
-  </div>
+<div class="admin-sub-header">
+  <div class="admin-sub-title">Video abuses list</div>
 </div>
+
+<p-dataTable
+    [value]="videoAbuses" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
+    sortField="id" (onLazyLoad)="loadLazy($event)"
+>
+  <p-column field="id" header="ID" [sortable]="true"></p-column>
+  <p-column field="reason" header="Reason"></p-column>
+  <p-column field="reporterServerHost" header="Reporter server host"></p-column>
+  <p-column field="reporterUsername" header="Reporter username"></p-column>
+  <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
+  <p-column header="Video">
+    <ng-template pTemplate="body" let-videoAbuse="rowData">
+      <a [routerLink]="getRouterVideoLink(videoAbuse.videoId)" title="Go to the video">{{ videoAbuse.videoName }}</a>
+    </ng-template>
+  </p-column>
+</p-dataTable>
diff --git a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.scss b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.scss
new file mode 100644
index 000000000..6a4762650
--- /dev/null
+++ b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.scss
@@ -0,0 +1,6 @@
+/deep/ a {
+
+  &, &:hover, &:active, &:focus {
+    color: #000;
+  }
+}
diff --git a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts
index 654603d01..b4d3bbd24 100644
--- a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts
+++ b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts
@@ -8,7 +8,8 @@ import { VideoAbuse } from '../../../../../../shared'
 
 @Component({
   selector: 'my-video-abuse-list',
-  templateUrl: './video-abuse-list.component.html'
+  templateUrl: './video-abuse-list.component.html',
+  styleUrls: [ './video-abuse-list.component.scss']
 })
 export class VideoAbuseListComponent extends RestTable implements OnInit {
   videoAbuses: VideoAbuse[] = []
diff --git a/client/src/app/+admin/video-blacklist/video-blacklist-list/video-blacklist-list.component.html b/client/src/app/+admin/video-blacklist/video-blacklist-list/video-blacklist-list.component.html
index 05d116798..1d813fa07 100644
--- a/client/src/app/+admin/video-blacklist/video-blacklist-list/video-blacklist-list.component.html
+++ b/client/src/app/+admin/video-blacklist/video-blacklist-list/video-blacklist-list.component.html
@@ -18,7 +18,7 @@
       <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
       <p-column header="Delete" styleClass="action-cell">
         <ng-template pTemplate="body" let-entry="rowData">
-          <span (click)="removeVideoFromBlacklist(entry)" class="glyphicon glyphicon-remove glyphicon-black" title="Remove this video from blacklist"></span>
+          <my-delete-button (click)="removeVideoFromBlacklist(entry)"></my-delete-button>
         </ng-template>
       </p-column>
     </p-dataTable>
diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts
index 0db197f02..e887dde1f 100644
--- a/client/src/app/core/auth/auth.service.ts
+++ b/client/src/app/core/auth/auth.service.ts
@@ -169,19 +169,15 @@ export class AuthService {
 
     return this.http.post<UserRefreshToken>(AuthService.BASE_TOKEN_URL, body, { headers })
                     .map(res => this.handleRefreshToken(res))
-                    .catch(res => {
-                      // The refresh token is invalid?
-                      if (res.status === 400 && res.error.error === 'invalid_grant') {
-                        console.error('Cannot refresh token -> logout...')
-                        this.logout()
-                        this.router.navigate(['/login'])
-
-                        return Observable.throw({
-                          error: 'You need to reconnect.'
-                        })
-                      }
-
-                      return this.restExtractor.handleError(res)
+                    .catch(err => {
+                      console.error(err)
+                      console.log('Cannot refresh token -> logout...')
+                      this.logout()
+                      this.router.navigate(['/login'])
+
+                      return Observable.throw({
+                        error: 'You need to reconnect.'
+                      })
                     })
   }
 
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts
index ee1ed2cb2..ba1635a18 100644
--- a/client/src/app/shared/video/abstract-video-list.ts
+++ b/client/src/app/shared/video/abstract-video-list.ts
@@ -62,7 +62,7 @@ export abstract class AbstractVideoList implements OnInit {
     observable.subscribe(
       ({ videos, totalVideos }) => {
         // Paging is too high, return to the first one
-        if (totalVideos <= ((this.pagination.currentPage - 1) * this.pagination.itemsPerPage)) {
+        if (this.pagination.currentPage > 1 && totalVideos <= ((this.pagination.currentPage - 1) * this.pagination.itemsPerPage)) {
           this.pagination.currentPage = 1
           this.setNewRouteParams()
           return this.reloadVideos()
@@ -82,6 +82,10 @@ export abstract class AbstractVideoList implements OnInit {
   }
 
   protected hasMoreVideos () {
+    // No results
+    if (this.pagination.totalItems === 0) return false
+
+    // Not loaded yet
     if (!this.pagination.totalItems) return true
 
     const maxPage = this.pagination.totalItems / this.pagination.itemsPerPage
diff --git a/client/src/app/videos/+video-edit/shared/video-description.component.html b/client/src/app/videos/+video-edit/shared/video-description.component.html
index da66a9753..5d05467be 100644
--- a/client/src/app/videos/+video-edit/shared/video-description.component.html
+++ b/client/src/app/videos/+video-edit/shared/video-description.component.html
@@ -1,6 +1,6 @@
 <textarea
   [(ngModel)]="description" (ngModelChange)="onModelChange()"
-  id="description" placeholder="My super video">
+  id="description" name="description">
 </textarea>
 
 <tabset #staticTabs class="previews">
diff --git a/client/src/app/videos/+video-edit/shared/video-description.component.scss b/client/src/app/videos/+video-edit/shared/video-description.component.scss
index 8155cbca7..2a4c8d189 100644
--- a/client/src/app/videos/+video-edit/shared/video-description.component.scss
+++ b/client/src/app/videos/+video-edit/shared/video-description.component.scss
@@ -4,4 +4,21 @@ textarea {
   padding: 5px 15px;
   font-size: 15px;
   height: 150px;
+  margin-bottom: 15px;
 }
+
+/deep/ {
+  .nav-link {
+    display: flex !important;
+    align-items: center;
+    height: 30px !important;
+    padding: 0 15px !important;
+  }
+
+  .tab-content {
+    min-height: 75px;
+    padding: 15px;
+    font-size: 15px;
+  }
+}
+
diff --git a/client/src/app/videos/+video-edit/shared/video-description.component.ts b/client/src/app/videos/+video-edit/shared/video-description.component.ts
index 8dfb74b2a..9b77a27e6 100644
--- a/client/src/app/videos/+video-edit/shared/video-description.component.ts
+++ b/client/src/app/videos/+video-edit/shared/video-description.component.ts
@@ -60,6 +60,8 @@ export class VideoDescriptionComponent implements ControlValueAccessor, OnInit {
   }
 
   private updateDescriptionPreviews () {
+    if (!this.description) return
+
     this.truncatedDescriptionHTML = this.markdownService.markdownToHTML(truncate(this.description, { length: 250 }))
     this.descriptionHTML = this.markdownService.markdownToHTML(this.description)
   }
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index ecbb8dac5..5a4aa4cd9 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -165,7 +165,7 @@ p-datatable {
 
   td {
     border: 1px solid #E5E5E5 !important;
-    padding: 15px;
+    padding-left: 15px !important;
   }
 
   tr {
@@ -185,6 +185,10 @@ p-datatable {
     &:first-child td {
       border-top: none !important;
     }
+
+    &:last-child td {
+      border-bottom: none !important;
+    }
   }
 
   th {
@@ -198,6 +202,7 @@ p-datatable {
     &.ui-state-active, &.ui-sortable-column:hover {
       background-color: #f0f0f0 !important;
       border: 1px solid #f0f0f0 !important;
+      border-width: 0 1px !important;
     }
   }
 
@@ -208,17 +213,10 @@ p-datatable {
   }
 
   p-paginator {
-    overflow: hidden;
-    display: block;
-    padding-top: 2px;
-    border: 1px solid #f0f0f0 !important;
-    border-top: none !important;
-
     .ui-paginator-bottom {
       position: relative;
       border: none !important;
-      border-top: 1px solid #f0f0f0 !important;
-      box-shadow: 0 -1px 3px rgba(0, 0, 0, 0.16);
+      border: 1px solid #f0f0f0 !important;
       height: 40px;
       display: flex;
       justify-content: center;
@@ -298,11 +296,6 @@ p-datatable {
       font-weight: $font-semibold !important;
     }
   }
-
-  .tab-content {
-    min-height: 75px;
-    padding: 15px;
-  }
 }
 
 
-- 
cgit v1.2.3


From 3daf400219fe8cc94025886ba14876bc59a45244 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Mon, 11 Dec 2017 08:50:43 +0100
Subject: Responsive homepage

---
 client/src/app/app.component.html                   |  7 ++-----
 client/src/app/app.component.scss                   |  2 ++
 client/src/app/header/header.component.html         |  2 +-
 client/src/app/header/header.component.scss         | 21 +++++++++++++++++++++
 .../src/app/shared/video/abstract-video-list.html   |  1 +
 .../src/app/shared/video/abstract-video-list.scss   |  7 +++++++
 client/src/sass/application.scss                    | 19 +++++++++++++------
 7 files changed, 47 insertions(+), 12 deletions(-)

(limited to 'client')

diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html
index cb1f4e4ef..da4273dda 100644
--- a/client/src/app/app.component.html
+++ b/client/src/app/app.component.html
@@ -16,11 +16,8 @@
   </div>
 
   <div class="sub-header-container">
-    <div class="title-menu-left">
-
-      <div class="title-menu-left-block menu">
-        <my-menu *ngIf="isMenuDisplayed"></my-menu>
-      </div>
+    <div *ngIf="isMenuDisplayed" class="title-menu-left">
+        <my-menu></my-menu>
     </div>
 
     <div class="main-col container-fluid" [ngClass]="getMainColClasses()">
diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss
index 10af9debe..008c6d1f0 100644
--- a/client/src/app/app.component.scss
+++ b/client/src/app/app.component.scss
@@ -61,6 +61,8 @@
     }
 
     @media screen and (max-width: 500px) {
+      width: 70px;
+
       #peertube-title {
         display: none;
       }
diff --git a/client/src/app/header/header.component.html b/client/src/app/header/header.component.html
index aa72fb68a..c853d2b1b 100644
--- a/client/src/app/header/header.component.html
+++ b/client/src/app/header/header.component.html
@@ -6,5 +6,5 @@
 
 <a class="upload-button" routerLink="/videos/upload">
   <span class="icon icon-upload"></span>
-  Upload
+  <span class="upload-button-label">Upload</span>
 </a>
diff --git a/client/src/app/header/header.component.scss b/client/src/app/header/header.component.scss
index ed8695eab..5f64ede98 100644
--- a/client/src/app/header/header.component.scss
+++ b/client/src/app/header/header.component.scss
@@ -6,6 +6,14 @@
   &::placeholder {
     color: #000;
   }
+
+  @media screen and (max-width: 800px) {
+    width: calc(100% - 150px);
+  }
+
+  @media screen and (max-width: 400px) {
+    width: calc(100% - 70px);
+  }
 }
 
 .icon.icon-search {
@@ -34,4 +42,17 @@
     vertical-align: middle;
     margin-right: 6px;
   }
+
+  @media screen and (max-width: 400px) {
+    margin-right: 10px;
+    padding: 0 10px;
+
+    .icon.icon-upload {
+      margin-right: 0;
+    }
+
+    .upload-button-label {
+      display: none;
+    }
+  }
 }
diff --git a/client/src/app/shared/video/abstract-video-list.html b/client/src/app/shared/video/abstract-video-list.html
index 5d07a276b..5761f2c81 100644
--- a/client/src/app/shared/video/abstract-video-list.html
+++ b/client/src/app/shared/video/abstract-video-list.html
@@ -4,6 +4,7 @@
   </div>
 
   <div
+    class="videos"
     infiniteScroll
     [infiniteScrollUpDistance]="1.5"
     [infiniteScrollDistance]="0.5"
diff --git a/client/src/app/shared/video/abstract-video-list.scss b/client/src/app/shared/video/abstract-video-list.scss
index e69de29bb..52797bc6c 100644
--- a/client/src/app/shared/video/abstract-video-list.scss
+++ b/client/src/app/shared/video/abstract-video-list.scss
@@ -0,0 +1,7 @@
+.videos {
+  text-align: center;
+
+  my-video-miniature {
+    text-align: left;
+  }
+}
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index 5a4aa4cd9..f7d83c5c3 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -107,8 +107,8 @@ label {
   font-weight: bold;
 }
 
-// On small screen, menu is absolute and displayed over the page
-@media screen and (max-width: 500px) {
+// On small screen, menu is absolute
+@media screen and (max-width: 800px) {
   .title-menu-left {
     width: 120px;
     position: absolute !important;
@@ -116,11 +116,18 @@ label {
   }
 
   .main-col {
-    width: 100% !important;
-  }
+    margin-left: 0;
 
-  .fake-menu {
-    display: none;
+    &, &.expanded {
+      .margin-content {
+        //display: flex;
+        //flex-direction: column;
+        //align-items: center;
+        //justify-content: center;
+        margin-left: auto;
+        margin-right: auto;
+      }
+    }
   }
 }
 
-- 
cgit v1.2.3


From b9828abe54723ebffd2aabdad870b157518472b1 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Mon, 11 Dec 2017 09:08:15 +0100
Subject: Videos watch responsive

---
 .../videos/+video-watch/video-watch.component.html |  2 +-
 .../videos/+video-watch/video-watch.component.scss | 31 ++++++++++++++++++++++
 2 files changed, 32 insertions(+), 1 deletion(-)

(limited to 'client')

diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html
index dfed4768c..43b175acc 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -1,7 +1,7 @@
 <div class="row">
   <!-- We need the video container for videojs so we just hide it -->
   <div [hidden]="videoNotFound" id="video-container">
-     <video id="video-element" class="video-js vjs-peertube-skin"></video>
+     <video id="video-element" class="video-js vjs-peertube-skin vjs-fluid"></video>
   </div>
 
   <div *ngIf="videoNotFound" id="video-not-found">Video not found :'(</div>
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss
index 2ccfd2749..fcf625d6c 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.scss
+++ b/client/src/app/videos/+video-watch/video-watch.component.scss
@@ -7,9 +7,14 @@
     width: 888px;
     height: 500px;
 
+    @media screen and (max-width: 800px) {
+      height: auto;
+    }
+
     // VideoJS create an inner video player
     video {
       outline: 0;
+      position: relative !important;
     }
   }
 }
@@ -210,3 +215,29 @@
   }
 }
 
+
+@media screen and (max-width: 800px) {
+  .other-videos {
+    display: none;
+  }
+
+  .video-bottom {
+    .video-info {
+      .video-info-name-actions {
+        align-items: left;
+        flex-direction: column;
+        margin-bottom: 30px;
+      }
+
+      .video-info-date-views-bar {
+        align-items: left;
+        flex-direction: column;
+        margin-bottom: 30px;
+
+        .video-info-likes-dislikes-bar {
+          margin-top: 0;
+        }
+      }
+    }
+  }
+}
-- 
cgit v1.2.3


From a86309b4af9ad8391822f628d74fc5c7d1a01974 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Mon, 11 Dec 2017 09:13:01 +0100
Subject: Responsive player

---
 client/src/assets/player/peertube-videojs-plugin.ts |  4 ++--
 client/src/sass/video-js-custom.scss                | 15 +++++++++++++++
 2 files changed, 17 insertions(+), 2 deletions(-)

(limited to 'client')

diff --git a/client/src/assets/player/peertube-videojs-plugin.ts b/client/src/assets/player/peertube-videojs-plugin.ts
index add4e521e..4ba37b7d9 100644
--- a/client/src/assets/player/peertube-videojs-plugin.ts
+++ b/client/src/assets/player/peertube-videojs-plugin.ts
@@ -156,7 +156,7 @@ const WebTorrentButton = videojsUntyped.extend(Button, {
 
     div.className = 'vjs-webtorrent'
     // Hide the stats before we get the info
-    subDiv.style.display = 'none'
+    subDiv.className = 'vjs-webtorrent-hidden'
 
     this.player_.on('torrentInfo', (event, data) => {
       const downloadSpeed = bytes(data.downloadSpeed)
@@ -171,7 +171,7 @@ const WebTorrentButton = videojsUntyped.extend(Button, {
 
       peersNumber.textContent = numPeers
 
-      subDiv.style.display = 'block'
+      subDiv.className = 'vjs-webtorrent-displayed'
     })
 
     return div
diff --git a/client/src/sass/video-js-custom.scss b/client/src/sass/video-js-custom.scss
index 1200c07a5..2fcfc6203 100644
--- a/client/src/sass/video-js-custom.scss
+++ b/client/src/sass/video-js-custom.scss
@@ -138,6 +138,14 @@ $control-bar-height: 34px;
       text-align: right;
       padding-right: 60px;
 
+      .vjs-webtorrent-displayed {
+        display: block;
+      }
+
+      .vjs-webtorrent-hidden {
+        display: none;
+      }
+
       .download-speed-number, .upload-speed-number, .peers-number {
         font-weight: $font-semibold;
       }
@@ -303,6 +311,12 @@ $control-bar-height: 34px;
       }
     }
   }
+
+  @media screen and (max-width: 450px) {
+    .vjs-webtorrent-displayed {
+      display: none !important;
+    }
+  }
 }
 
 // Thanks: https://projects.lukehaas.me/css-loaders/
@@ -340,3 +354,4 @@ $control-bar-height: 34px;
     }
   }
 }
+
-- 
cgit v1.2.3


From 9b7d1c723d7c11572d91d606954997e413f56a1f Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Mon, 11 Dec 2017 09:39:40 +0100
Subject: Responsive my account

---
 .../account-videos/account-videos.component.html   |  5 +-
 .../account-videos/account-videos.component.scss   | 20 +++++++
 client/src/app/menu/menu.component.scss            | 62 ++++++++++++++++++++--
 .../videos/+video-watch/video-watch.component.scss |  2 +
 client/src/sass/application.scss                   | 19 ++++---
 5 files changed, 94 insertions(+), 14 deletions(-)

(limited to 'client')

diff --git a/client/src/app/account/account-videos/account-videos.component.html b/client/src/app/account/account-videos/account-videos.component.html
index 641fcb38a..77f959fef 100644
--- a/client/src/app/account/account-videos/account-videos.component.html
+++ b/client/src/app/account/account-videos/account-videos.component.html
@@ -1,4 +1,5 @@
 <div
+  class="videos"
   infiniteScroll
   [infiniteScrollDistance]="0.5"
   [infiniteScrollUpDistance]="1.5"
@@ -29,10 +30,10 @@
       </div>
     </div>
 
-    <ng-template [ngIf]="isInSelectionMode() === false">
+    <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>
-    </ng-template>
+    </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
index 670fe992c..4c00431fa 100644
--- a/client/src/app/account/account-videos/account-videos.component.scss
+++ b/client/src/app/account/account-videos/account-videos.component.scss
@@ -64,3 +64,23 @@
     }
   }
 }
+
+@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/menu/menu.component.scss b/client/src/app/menu/menu.component.scss
index 63d63d287..97ceadde3 100644
--- a/client/src/app/menu/menu.component.scss
+++ b/client/src/app/menu/menu.component.scss
@@ -9,10 +9,6 @@ menu {
   z-index: 1000;
   color: $menu-color;
 
-  @media screen and (max-width: 550px) {
-    font-size: 90%;
-  }
-
   .logged-in-block {
     height: 100px;
     background-color: rgba(255, 255, 255, 0.15);
@@ -68,7 +64,7 @@ menu {
       font-size: 15px;
       height: $button-height;
       line-height: $button-height;
-      width: 190px;
+      width: 100%;
       border-radius: 3px;
       text-align: center;
       color: $menu-color;
@@ -139,3 +135,59 @@ menu {
     }
   }
 }
+
+@media screen and (max-width: 800px) {
+  menu {
+    .logged-in-block {
+      padding-left: 10px;
+
+      img {
+        display: none;
+      }
+
+      .logged-in-info {
+        .logged-in-username {
+          font-size: 14px;
+        }
+
+        .logged-in-email {
+          font-size: 11px;
+          max-width: 120px;
+        }
+      }
+
+      .logged-in-more {
+        margin-right: 5px;
+
+        .login-button, .create-account-button {
+          font-weight: $font-semibold;
+          font-size: 15px;
+          height: $button-height;
+          line-height: $button-height;
+          width: 190px;
+        }
+      }
+    }
+
+    .button-block {
+      margin: 20px 10px 25px 10px;
+
+      .login-button, .create-account-button {
+        font-size: 13px;
+      }
+    }
+
+    .panel-block {
+      margin-bottom: 30px;
+      margin-left: 10px;
+
+      a {
+        font-size: 14px;
+
+        .icon {
+          margin-right: 10px;
+        }
+      }
+    }
+  }
+}
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss
index fcf625d6c..83a7cc41d 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.scss
+++ b/client/src/app/videos/+video-watch/video-watch.component.scss
@@ -223,6 +223,8 @@
 
   .video-bottom {
     .video-info {
+      margin-right: 10px;
+
       .video-info-name-actions {
         align-items: left;
         flex-direction: column;
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index f7d83c5c3..6352dd4fb 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -110,7 +110,7 @@ label {
 // On small screen, menu is absolute
 @media screen and (max-width: 800px) {
   .title-menu-left {
-    width: 120px;
+    width: 150px !important;
     position: absolute !important;
     z-index: 10000;
   }
@@ -120,12 +120,17 @@ label {
 
     &, &.expanded {
       .margin-content {
-        //display: flex;
-        //flex-direction: column;
-        //align-items: center;
-        //justify-content: center;
-        margin-left: auto;
-        margin-right: auto;
+        margin-left: 10px;
+        margin-right: 10px;
+      }
+
+      .sub-menu {
+        padding-left: 10px;
+        margin-bottom: 10px;
+      }
+
+      input[type=text], input[type=password] {
+        width: 100% !important;
       }
     }
   }
-- 
cgit v1.2.3


From 20206dfb0bfe1537912ae0a5b99f2fa40c881d33 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Mon, 11 Dec 2017 10:02:17 +0100
Subject: Fix loading spinner in player

---
 client/src/app/header/header.component.scss        |   2 +-
 .../videos/+video-watch/video-watch.component.html | 230 ++++++++++-----------
 .../videos/+video-watch/video-watch.component.scss |   8 +-
 client/src/sass/application.scss                   |  58 +++---
 client/src/sass/video-js-custom.scss               |  14 +-
 5 files changed, 152 insertions(+), 160 deletions(-)

(limited to 'client')

diff --git a/client/src/app/header/header.component.scss b/client/src/app/header/header.component.scss
index 5f64ede98..fba70dd2f 100644
--- a/client/src/app/header/header.component.scss
+++ b/client/src/app/header/header.component.scss
@@ -7,7 +7,7 @@
     color: #000;
   }
 
-  @media screen and (max-width: 800px) {
+  @media screen and (max-width: 600px) {
     width: calc(100% - 150px);
   }
 
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html
index 43b175acc..f99e84caf 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -5,148 +5,148 @@
   </div>
 
   <div *ngIf="videoNotFound" id="video-not-found">Video not found :'(</div>
-</div>
-
-<!-- Video information -->
-<div *ngIf="video" class="margin-content video-bottom">
-  <div class="video-info">
-    <div class="video-info-name-actions">
-      <div class="video-info-name">{{ video.name }}</div>
 
-      <div class="video-info-actions">
-        <div *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'like' }" class="action-button">
-          <span class="icon icon-like" title="Like this video" (click)="setLike()"></span>
-        </div>
+  <!-- Video information -->
+  <div *ngIf="video" class="margin-content video-bottom">
+    <div class="video-info">
+      <div class="video-info-name-actions">
+        <div class="video-info-name">{{ video.name }}</div>
 
-        <div *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" class="action-button">
-          <span class="icon icon-dislike" title="Dislike this video" (click)="setDislike()"></span>
-        </div>
+        <div class="video-info-actions">
+          <div *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'like' }" class="action-button">
+            <span class="icon icon-like" title="Like this video" (click)="setLike()"></span>
+          </div>
 
-        <div (click)="showShareModal()" class="action-button">
-          <span class="icon icon-share"></span>
-          Share
-        </div>
+          <div *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" class="action-button">
+            <span class="icon icon-dislike" title="Dislike this video" (click)="setDislike()"></span>
+          </div>
 
-        <div class="action-more" dropdown dropup="true" placement="right">
-          <div class="action-button" dropdownToggle>
-            <span class="icon icon-more"></span>
+          <div (click)="showShareModal()" class="action-button">
+            <span class="icon icon-share"></span>
+            Share
           </div>
 
-          <ul *dropdownMenu class="dropdown-menu" id="more-menu" role="menu" aria-labelledby="single-button">
-            <li role="menuitem">
-              <a class="dropdown-item" title="Download the video" href="#" (click)="showDownloadModal($event)">
-                <span class="icon icon-download"></span> Download
-              </a>
-            </li>
-
-            <li *ngIf="isUserLoggedIn()" role="menuitem">
-              <a class="dropdown-item" title="Report this video" href="#" (click)="showReportModal($event)">
-                <span class="icon icon-alert"></span> Report
-              </a>
-            </li>
-
-            <li *ngIf="isVideoBlacklistable()" role="menuitem">
-              <a class="dropdown-item" title="Blacklist this video" href="#" (click)="blacklistVideo($event)">
-                <span class="icon icon-blacklist"></span> Blacklist
-              </a>
-            </li>
-          </ul>
+          <div class="action-more" dropdown dropup="true" placement="right">
+            <div class="action-button" dropdownToggle>
+              <span class="icon icon-more"></span>
+            </div>
+
+            <ul *dropdownMenu class="dropdown-menu" id="more-menu" role="menu" aria-labelledby="single-button">
+              <li role="menuitem">
+                <a class="dropdown-item" title="Download the video" href="#" (click)="showDownloadModal($event)">
+                  <span class="icon icon-download"></span> Download
+                </a>
+              </li>
+
+              <li *ngIf="isUserLoggedIn()" role="menuitem">
+                <a class="dropdown-item" title="Report this video" href="#" (click)="showReportModal($event)">
+                  <span class="icon icon-alert"></span> Report
+                </a>
+              </li>
+
+              <li *ngIf="isVideoBlacklistable()" role="menuitem">
+                <a class="dropdown-item" title="Blacklist this video" href="#" (click)="blacklistVideo($event)">
+                  <span class="icon icon-blacklist"></span> Blacklist
+                </a>
+              </li>
+            </ul>
+          </div>
         </div>
       </div>
-    </div>
 
-    <div class="video-info-date-views-bar">
-      <div class="video-info-date-views">
-        {{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views
-      </div>
+      <div class="video-info-date-views-bar">
+        <div class="video-info-date-views">
+          {{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views
+        </div>
 
-      <div *ngIf="video.likes !== 0 || video.dislikes !== 0" class="video-info-likes-dislikes-bar">
-        <div class="likes-bar" [ngStyle]="{ 'width.%': video.likesPercent }"></div>
+        <div *ngIf="video.likes !== 0 || video.dislikes !== 0" class="video-info-likes-dislikes-bar">
+          <div class="likes-bar" [ngStyle]="{ 'width.%': video.likesPercent }"></div>
+        </div>
       </div>
-    </div>
 
-    <div class="video-info-channel">
-      {{ video.channel.name }}
-      <!-- Here will be the subscribe button -->
-    </div>
+      <div class="video-info-channel">
+        {{ video.channel.name }}
+        <!-- Here will be the subscribe button -->
+      </div>
 
-    <div class="video-info-by">
-      By {{ video.by }}
-      <img [src]="getAvatarPath()" alt="Account avatar" />
-    </div>
+      <div class="video-info-by">
+        By {{ video.by }}
+        <img [src]="getAvatarPath()" alt="Account avatar" />
+      </div>
 
-    <div class="video-info-description">
-      <div class="video-info-description-html" [innerHTML]="videoHTMLDescription"></div>
+      <div class="video-info-description">
+        <div class="video-info-description-html" [innerHTML]="videoHTMLDescription"></div>
 
-      <div class="video-info-description-more" *ngIf="completeDescriptionShown === false && video.description?.length === 250" (click)="showMoreDescription()">
-        Show more
-        <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-down"></span>
-        <my-loader class="description-loading" [loading]="descriptionLoading"></my-loader>
-      </div>
+        <div class="video-info-description-more" *ngIf="completeDescriptionShown === false && video.description?.length === 250" (click)="showMoreDescription()">
+          Show more
+          <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-down"></span>
+          <my-loader class="description-loading" [loading]="descriptionLoading"></my-loader>
+        </div>
 
-      <div *ngIf="completeDescriptionShown === true" (click)="showLessDescription()" class="video-info-description-more">
-        Show less
-        <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-up"></span>
+        <div *ngIf="completeDescriptionShown === true" (click)="showLessDescription()" class="video-info-description-more">
+          Show less
+          <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-up"></span>
+        </div>
       </div>
-    </div>
 
-    <div class="video-attributes">
-      <div class="video-attribute">
-        <span class="video-attribute-label">
-          Privacy
-        </span>
-        <span class="video-attribute-value">
-          {{ video.privacyLabel }}
-        </span>
-      </div>
+      <div class="video-attributes">
+        <div class="video-attribute">
+          <span class="video-attribute-label">
+            Privacy
+          </span>
+          <span class="video-attribute-value">
+            {{ video.privacyLabel }}
+          </span>
+        </div>
 
-      <div class="video-attribute">
-        <span class="video-attribute-label">
-          Category
-        </span>
-        <span class="video-attribute-value">
-          {{ video.categoryLabel }}
-        </span>
-      </div>
+        <div class="video-attribute">
+          <span class="video-attribute-label">
+            Category
+          </span>
+          <span class="video-attribute-value">
+            {{ video.categoryLabel }}
+          </span>
+        </div>
 
-      <div class="video-attribute">
-        <span class="video-attribute-label">
-          Licence
-        </span>
-        <span class="video-attribute-value">
-          {{ video.licenceLabel }}
-        </span>
-      </div>
+        <div class="video-attribute">
+          <span class="video-attribute-label">
+            Licence
+          </span>
+          <span class="video-attribute-value">
+            {{ video.licenceLabel }}
+          </span>
+        </div>
 
-      <div class="video-attribute">
-        <span class="video-attribute-label">
-          Language
-        </span>
-        <span class="video-attribute-value">
-          {{ video.languageLabel }}
-        </span>
-      </div>
+        <div class="video-attribute">
+          <span class="video-attribute-label">
+            Language
+          </span>
+          <span class="video-attribute-value">
+            {{ video.languageLabel }}
+          </span>
+        </div>
 
-      <div class="video-attribute">
-        <span class="video-attribute-label">
-          Tags
-        </span>
+        <div class="video-attribute">
+          <span class="video-attribute-label">
+            Tags
+          </span>
 
-        <span class="video-attribute-value">
-          {{ getVideoTags() }}
-        </span>
+          <span class="video-attribute-value">
+            {{ getVideoTags() }}
+          </span>
+        </div>
       </div>
-    </div>
 
-  </div>
-
-  <div class="other-videos">
-    <div class="title-page title-page-single">
-      Other videos
     </div>
 
-    <div *ngFor="let video of otherVideos">
-      <my-video-miniature [video]="video" [user]="user"></my-video-miniature>
+    <div class="other-videos">
+      <div class="title-page title-page-single">
+        Other videos
+      </div>
+
+      <div *ngFor="let video of otherVideos">
+        <my-video-miniature [video]="video" [user]="user"></my-video-miniature>
+      </div>
     </div>
   </div>
 </div>
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss
index 83a7cc41d..9daa757b4 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.scss
+++ b/client/src/app/videos/+video-watch/video-watch.component.scss
@@ -216,14 +216,18 @@
 }
 
 
-@media screen and (max-width: 800px) {
+@media screen and (max-width: 1000px) {
   .other-videos {
     display: none;
   }
+}
 
+@media screen and (max-width: 800px) {
   .video-bottom {
+    margin: 20px 0 0 0;
+
     .video-info {
-      margin-right: 10px;
+      margin-right: 0;
 
       .video-info-name-actions {
         align-items: left;
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index 6352dd4fb..9a93411e9 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -107,35 +107,6 @@ label {
   font-weight: bold;
 }
 
-// On small screen, menu is absolute
-@media screen and (max-width: 800px) {
-  .title-menu-left {
-    width: 150px !important;
-    position: absolute !important;
-    z-index: 10000;
-  }
-
-  .main-col {
-    margin-left: 0;
-
-    &, &.expanded {
-      .margin-content {
-        margin-left: 10px;
-        margin-right: 10px;
-      }
-
-      .sub-menu {
-        padding-left: 10px;
-        margin-bottom: 10px;
-      }
-
-      input[type=text], input[type=password] {
-        width: 100% !important;
-      }
-    }
-  }
-}
-
 // Thanks https://gist.github.com/alexandrevicenzi/680147013e902a4eaa5d
 .glyphicon-refresh-animate {
   -animation: spin .7s infinite linear;
@@ -330,3 +301,32 @@ p-datatable {
   @include peertube-button-link;
   @include grey-button;
 }
+
+// On small screen, menu is absolute
+@media screen and (max-width: 800px) {
+  .title-menu-left {
+    width: 150px !important;
+    position: absolute !important;
+    z-index: 10000;
+  }
+
+  .main-col {
+    margin-left: 0;
+
+    &, &.expanded {
+      .margin-content {
+        margin-left: 10px;
+        margin-right: 10px;
+      }
+
+      .sub-menu {
+        padding-left: 10px;
+        margin-bottom: 10px;
+      }
+
+      input[type=text], input[type=password] {
+        width: 100% !important;
+      }
+    }
+  }
+}
diff --git a/client/src/sass/video-js-custom.scss b/client/src/sass/video-js-custom.scss
index 2fcfc6203..fe9495e77 100644
--- a/client/src/sass/video-js-custom.scss
+++ b/client/src/sass/video-js-custom.scss
@@ -28,10 +28,6 @@ $control-bar-height: 34px;
     $big-play-width: 3em;
     $big-play-height: 1.5em;
 
-    line-height: $big-play-height;
-    height: $big-play-height;
-    width: $big-play-width;
-
     border: 0;
     border-radius: 0.3em;
 
@@ -39,10 +35,7 @@ $control-bar-height: 34px;
     top: 50%;
     margin-left: -($big-play-width / 2);
     margin-top: -($big-play-height / 2);
-  }
-
-  &:hover .vjs-big-play-button {
-    background-color: transparent;
+    background-color: transparent !important;
   }
 
   .vjs-control-bar,
@@ -321,13 +314,8 @@ $control-bar-height: 34px;
 
 // Thanks: https://projects.lukehaas.me/css-loaders/
 .vjs-loading-spinner {
-  margin: 0 !important;
-  //position: absolute;
-  // 15px is the nav bar height
-  top: calc(50% - 15px);
   left: 50%;
   font-size: 10px;
-  position: relative;
   text-indent: -9999em;
   border: 0.7em solid rgba(255, 255, 255, 0.2);
   border-left-color: #ffffff;
-- 
cgit v1.2.3


From 9e6b41cc1d825182bcfdf51364fdf3eae8001244 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Mon, 11 Dec 2017 10:26:29 +0100
Subject: Embed player responsive

---
 client/src/sass/video-js-custom.scss    |  1 +
 client/src/standalone/videos/embed.html |  2 +-
 client/src/standalone/videos/embed.scss | 32 ++++++++++++++++++++++----------
 3 files changed, 24 insertions(+), 11 deletions(-)

(limited to 'client')

diff --git a/client/src/sass/video-js-custom.scss b/client/src/sass/video-js-custom.scss
index fe9495e77..1c5701bea 100644
--- a/client/src/sass/video-js-custom.scss
+++ b/client/src/sass/video-js-custom.scss
@@ -23,6 +23,7 @@ $control-bar-height: 34px;
   }
 
   .vjs-big-play-button {
+    outline: 0;
     font-size: 8em;
 
     $big-play-width: 3em;
diff --git a/client/src/standalone/videos/embed.html b/client/src/standalone/videos/embed.html
index 0a35bc362..fa4d0bdba 100644
--- a/client/src/standalone/videos/embed.html
+++ b/client/src/standalone/videos/embed.html
@@ -11,7 +11,7 @@
 
   <body>
 
-    <video id="video-container" class="video-js vjs-sublime-skin vjs-big-play-centered">
+    <video id="video-container" class="video-js vjs-peertube-skin">
     </video>
 
   </body>
diff --git a/client/src/standalone/videos/embed.scss b/client/src/standalone/videos/embed.scss
index b76f09677..9140cd37c 100644
--- a/client/src/standalone/videos/embed.scss
+++ b/client/src/standalone/videos/embed.scss
@@ -23,17 +23,13 @@ html, body {
 }
 
 .vjs-peertube-link {
-  color: white;
+  color: #fff;
   text-decoration: none;
-  font-size: 1.3em;
-  line-height: 2.20;
+  font-size: $font-size;
+  line-height: $control-bar-height;
   transition: all .4s;
-  position: relative;
-  right: 8px;
-}
-
-.vjs-resolution-button-label {
-  left: -7px;
+  font-weight: $font-semibold;
+  margin-right: 3px;
 }
 
 .vjs-peertube-link:hover {
@@ -42,5 +38,21 @@ html, body {
 
 // Fix volume panel because we added a new component (PeerTube link)
 .vjs-volume-panel {
-  margin-right: 130px !important;
+  margin-right: 121px !important;
+}
+
+@media screen and (max-width: 350px) {
+  .vjs-play-control {
+    padding: 0 5px !important;
+    width: 25px !important;
+  }
+
+  .vjs-volume-control {
+    display: none !important;
+  }
+
+  .vjs-volume-panel {
+    width: 26px !important;
+    margin-right: 140px !important;
+  }
 }
-- 
cgit v1.2.3


From dc595ab7caf407ab83cd55a641eadab0bbe778b3 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Mon, 11 Dec 2017 10:38:17 +0100
Subject: Fix selection buttons in my videos

---
 .../account-videos/account-videos.component.html   |  2 +-
 .../account-videos/account-videos.component.scss   | 38 ++++++++++++++--------
 2 files changed, 25 insertions(+), 15 deletions(-)

(limited to 'client')

diff --git a/client/src/app/account/account-videos/account-videos.component.html b/client/src/app/account/account-videos/account-videos.component.html
index 77f959fef..f69c0487d 100644
--- a/client/src/app/account/account-videos/account-videos.component.html
+++ b/client/src/app/account/account-videos/account-videos.component.html
@@ -19,7 +19,7 @@
     <!-- Display only once -->
     <div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0">
       <div class="action-selection-mode-child">
-        <span class="action-button" (click)="abortSelectionMode()">
+        <span class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
           Cancel
         </span>
 
diff --git a/client/src/app/account/account-videos/account-videos.component.scss b/client/src/app/account/account-videos/account-videos.component.scss
index 4c00431fa..5459014a6 100644
--- a/client/src/app/account/account-videos/account-videos.component.scss
+++ b/client/src/app/account/account-videos/account-videos.component.scss
@@ -1,33 +1,43 @@
 .action-selection-mode {
   width: 174px;
+  display: flex;
+  justify-content: flex-end;
 
   .action-selection-mode-child {
     position: fixed;
-  }
-}
 
-/deep/ .action-button {
+    .action-button {
+      display: inline-block;
+    }
 
-  &.action-button-delete {
-    margin-right: 10px;
-  }
+    .action-button-cancel-selection {
+      @include peertube-button;
+      @include grey-button;
 
-  &.action-button-delete-selection {
-    background-color: $orange-color;
-    color: #fff;
+      margin-right: 10px;
+    }
 
-    &:hover {
-      background-color: $orange-hoover-color;
+    .action-button-delete-selection {
+      @include peertube-button;
+      @include orange-button;
     }
-  }
 
-  .icon {
-    &.icon-delete-white {
+    .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;
   height: 130px;
-- 
cgit v1.2.3


From c2830fa8f84f61462098bf36add824f89436dfa9 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Mon, 11 Dec 2017 10:44:40 +0100
Subject: Background orange on sorted columns

---
 client/src/sass/application.scss | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

(limited to 'client')

diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index 9a93411e9..9d347d566 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -182,11 +182,18 @@ p-datatable {
     font-weight: $font-semibold !important;
     color: #000 !important;
 
-    &.ui-state-active, &.ui-sortable-column:hover {
+    &.ui-sortable-column:hover:not(.ui-state-active) {
       background-color: #f0f0f0 !important;
       border: 1px solid #f0f0f0 !important;
       border-width: 0 1px !important;
     }
+
+    &.ui-state-active {
+      color: #fff !important;
+      background-color: $orange-color !important;
+      border: 1px solid $orange-color !important;
+      border-width: 0 1px !important;
+    }
   }
 
   .action-cell {
@@ -281,7 +288,6 @@ p-datatable {
   }
 }
 
-
 .orange-button {
   @include peertube-button;
   @include orange-button;
-- 
cgit v1.2.3