From b33f657c304b77938c1f68164d8e754787f5aae5 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 1 Dec 2017 09:20:19 +0100 Subject: [PATCH 01/49] 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/menu.component.html | 55 -------- client/src/app/core/menu/menu.component.scss | 51 ------- client/src/app/{core => }/menu/index.ts | 0 .../{core => }/menu/menu-admin.component.html | 0 .../{core => }/menu/menu-admin.component.ts | 4 +- client/src/app/menu/menu.component.html | 48 +++++++ client/src/app/menu/menu.component.scss | 131 ++++++++++++++++++ .../src/app/{core => }/menu/menu.component.ts | 15 +- .../app/shared/search/search.component.html | 26 +--- .../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 | 17 ++- client/src/sass/application.scss | 8 ++ client/yarn.lock | 10 +- 26 files changed, 432 insertions(+), 263 deletions(-) delete mode 100644 client/src/app/core/menu/menu.component.html delete mode 100644 client/src/app/core/menu/menu.component.scss rename client/src/app/{core => }/menu/index.ts (100%) rename client/src/app/{core => }/menu/menu-admin.component.html (100%) rename client/src/app/{core => }/menu/menu-admin.component.ts (87%) create mode 100644 client/src/app/menu/menu.component.html create mode 100644 client/src/app/menu/menu.component.scss rename client/src/app/{core => }/menu/menu.component.ts (84%) 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 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 @@
-
+
@@ -11,15 +11,13 @@
- -
- - - +
+ +
-
-
+
+
- -
-
-
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) - } + if (this.isMenuDisplayed === false) return [ 'expanded' ] - const classes = [] - Object.keys(colSizes).forEach(col => classes.push(`col-${col}-${colSizes[col]}`)) - - 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/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 @@ - - - - - - - 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/index.ts b/client/src/app/menu/index.ts similarity index 100% rename from client/src/app/core/menu/index.ts rename to client/src/app/menu/index.ts diff --git a/client/src/app/core/menu/menu-admin.component.html b/client/src/app/menu/menu-admin.component.html similarity index 100% rename from client/src/app/core/menu/menu-admin.component.html rename to client/src/app/menu/menu-admin.component.html diff --git a/client/src/app/core/menu/menu-admin.component.ts b/client/src/app/menu/menu-admin.component.ts similarity index 87% rename from client/src/app/core/menu/menu-admin.component.ts rename to client/src/app/menu/menu-admin.component.ts index ea8d5f57c..1babf5eb6 100644 --- a/client/src/app/core/menu/menu-admin.component.ts +++ b/client/src/app/menu/menu-admin.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core' -import { AuthService } from '../auth/auth.service' -import { UserRight } from '../../../../../shared' +import { AuthService } from '../core/auth/auth.service' +import { UserRight } from '../../../../shared' @Component({ selector: 'my-menu-admin', 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 @@ + +
+
+
{{ user.username }}
+
{{ user.email }}
+
+ +
+ + + +
+
+ + + + + + +
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/core/menu/menu.component.ts b/client/src/app/menu/menu.component.ts similarity index 84% rename from client/src/app/core/menu/menu.component.ts rename to client/src/app/menu/menu.component.ts index d2bd71534..4c35bb3a5 100644 --- a/client/src/app/core/menu/menu.component.ts +++ b/client/src/app/menu/menu.component.ts @@ -1,9 +1,8 @@ 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' +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', @@ -11,6 +10,7 @@ import { UserRight } from '../../../../../shared/models/users/user-right.enum' styleUrls: [ './menu.component.scss' ] }) export class MenuComponent implements OnInit { + user: User isLoggedIn: boolean userHasAdminAccess = false @@ -29,16 +29,19 @@ export class MenuComponent implements OnInit { 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 { @@ -78,7 +81,9 @@ export class MenuComponent implements OnInit { return this.routesPerRight[right] } - logout () { + logout (event: Event) { + event.preventDefault() + this.authService.logout() // Redirect to home page this.router.navigate(['/videos/list']) 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 @@ -
+ - - - - -
- - -
-
+Upload 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 c1d77a24c5ef25b02dae68383983d76eb40843e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 838 zcmV-M1G)T(P)AL_t(o!`0VIZ=)~}0ANG1gkyMw?J-<>4y$g?L>pB- zW?O0Z8dO!ig4Ev0Y5(>H8=LWrNx&-m@S##D8GiwLX3W@M7_%S<686uH|ChB3(D-RL zRS_6RY{|+M34OQH!yv!LNuR36*|@p%|DNDdqi8gVs2Hqy8~vilVm;Pr{}@v zdj7oFnICY++6`a@!Kx%Xc|}-GJ4b={fWFg0oIiV5F}unV`P)I!%q)-W0o}R6Rm;pu z$|0sZ77{}tJH0R+r48KHNd_2-q z;O0|fJurJ#!C8fICn8(USztFF`S-%^LIPR(uwZTY-!CL@A#BZAGnj{)JC+&vIUmp3 z5SE^V5|JwxFe!0Xg)_}WtfZ}pJ}L&BrOL~;Y*|tty-vcC^W7a~sYa#KBXbtG4V!>P z`z=^(j%`b)O?I+oHIF!AAWP}2OeKgVWr3Qsm7V4qvqEcD^HV`i2P|KtQxZNih3Bh1 zDe>v_)rRHKuarBT^z$*{3l_ z4D&)g;N>=krB7K)mJ6ZG%?7dH@wGkucgNR}EauBYXY>Oke(^#K$JaCKHx#7o^cn2$ Q8vp + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + filter + Created with Sketch. + + + + + + + + + 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 @@ + + + + + + + + + + + + + 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 @@ + + + + graph + Created with Sketch. + + + + + + + + + + + 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 @@ -$grey-color: #555; +$font-regular: 400; +$font-semibold: 600; +$font-bold: 700; -$black-background: #1d2125; +$grey-color: #555; +$orange-color: #F1680D; + +$black-background: #000; $grey-background: #f6f2f2; -$menu-color-link: #9cabb8; -$menu-color-block: #686f77; +$button-height: 30px; -$header-height: 65px; +$menu-color: #fff; +$menu-width: 240px; + +$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" From 26c6ee80d0fecfce595e8970f15717560b4f4ceb Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 1 Dec 2017 13:08:46 +0100 Subject: [PATCH 02/49] 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 | 99 +++++-------------- client/src/app/menu/menu.component.html | 2 +- client/src/app/menu/menu.component.scss | 5 +- .../app/shared/search/search.component.html | 10 +- .../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, 157 insertions(+), 95 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 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 @@ -
-
+
+
-
- -
+ -
- -
+ + + PeerTube +
@@ -16,7 +15,7 @@
-
+
-
+
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; + align-items: center; - .hamburger-block { - margin-right: 10px; - margin-left: 25px; + .icon { + cursor: pointer; + width: 22px; + height: 22px; + display: inline-block; + background-size: contain; - .glyphicon { - cursor: pointer; + &.icon-menu { + background-image: url('../assets/header/menu.svg'); + margin: 0 18px 0 24px; } } #peertube-title { - a { - color: inherit !important; - display: block; - background: url('../assets/logo.svg') no-repeat; - width: 24px; - height: 24px; + font-size: 20px; + font-weight: $font-bold; + color: inherit !important; + display: flex; + align-items: center; - &:hover { - color: inherit !important; - text-decoration: none !important; - } + @include disable-default-a-behaviour; + + .icon.icon-logo { + display: inline-block; + background: url('../assets/logo.svg') no-repeat; + width: 20px; + height: 24px; } } @@ -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 @@
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 @@ + -Upload + + + Upload + 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 @@ + + + + menu + Created with Sketch. + + + + + + + + + \ 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 @@ + + + + + + + + + + + + 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 @@ + + + + cloud-upload + Created with Sketch. + + + + + + + + + + + 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; From 9bf9d2a5c223bf006496ae7adf0c0bd7a7975108 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 1 Dec 2017 14:46:22 +0100 Subject: [PATCH 03/49] 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 ++++++++ .../app/shared/misc/number-formatter.pipe.ts | 19 ++++ client/src/app/shared/shared.module.ts | 26 +++-- .../videos/+video-edit/video-add.component.ts | 2 +- client/src/app/videos/video-list/index.ts | 3 +- .../videos/video-list/my-videos.component.ts | 2 +- .../shared/abstract-video-list.html | 18 +--- .../shared/abstract-video-list.scss | 14 --- .../video-list/shared/abstract-video-list.ts | 28 +----- .../src/app/videos/video-list/shared/index.ts | 1 - .../shared/video-miniature.component.html | 13 +-- .../shared/video-miniature.component.scss | 66 ++++--------- .../shared/video-sort.component.html | 5 - .../video-list/shared/video-sort.component.ts | 39 -------- .../videos/video-list/video-list.component.ts | 94 ------------------- .../video-recently-added.component.ts | 33 +++++++ .../video-list/video-trending.component.ts | 33 +++++++ .../src/app/videos/videos-routing.module.ts | 26 +++-- client/src/app/videos/videos.module.ts | 10 +- client/src/sass/application.scss | 24 ++--- client/yarn.lock | 8 +- 23 files changed, 212 insertions(+), 295 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 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 @@
Videos
- + Trending - + Recently added 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 @@ -
-
-
-
- {{ pagination.totalItems }} videos - - -
- - -
-
+
+ {{ titlePage }}
-
-
There is no video.
- +
= 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 @@ video thumbnail
- {{ video.views }} views - {{ video.durationLabel }} + {{ video.durationLabel }}
@@ -21,13 +20,7 @@ -
- - {{ tag }} - -
- - - {{ video.createdAt | date:'short' }} + {{ video.createdAt | fromNow }} - {{ video.views | numberFormatter }} views +
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 @@ - 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() - - @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: 'Videos list' + title: 'Trending videos' + } + } + }, + { + path: 'recently-added', + component: VideoRecentlyAddedComponent, + data: { + meta: { + 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; + padding: 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; - } + .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" From 2bbb34127fccd187ed690949b6791e49fdd77194 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 1 Dec 2017 16:17:32 +0100 Subject: [PATCH 04/49] 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 +- .../src/app/shared/search/search.component.ts | 33 +--------- client/src/app/shared/shared.module.ts | 7 +-- .../shared/abstract-video-list.html | 14 +++-- .../video-list/shared/abstract-video-list.ts | 63 ++++++++++++++----- .../video-recently-added.component.ts | 1 + .../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(-) 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 }}
-
+
- - 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 - this.navigateToNewParams() + 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 () { @@ -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" From c30745f342480b59fb0856a059c8c2fbffbcfc6a Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 1 Dec 2017 17:38:26 +0100 Subject: [PATCH 05/49] Add account settings new design --- .../app/+admin/users/shared/user.service.ts | 14 +++--- .../account-change-password.component.html | 24 ---------- .../account-details.component.html | 16 ------- .../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 | 9 ++-- .../account-change-password/index.ts | 0 .../account-details.component.html | 14 ++++++ .../account-details.component.scss | 11 +++++ .../account-details.component.ts | 19 +++----- .../account-details/index.ts | 0 .../account-settings.component.html | 9 ++++ .../account-settings.component.scss | 13 ++++++ .../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 | 13 +++--- client/src/app/app.component.scss | 8 ---- client/src/app/menu/menu.component.html | 2 +- client/src/app/menu/menu.component.scss | 29 +++++++----- .../app/shared/search/search.component.scss | 22 ++-------- client/src/app/videos/video-list/index.ts | 1 - .../videos/video-list/my-videos.component.ts | 36 --------------- .../shared/abstract-video-list.html | 31 ++++++------- .../shared/abstract-video-list.scss | 23 ---------- .../video-list/shared/abstract-video-list.ts | 4 +- .../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 + 34 files changed, 263 insertions(+), 256 deletions(-) delete mode 100644 client/src/app/account/account-change-password/account-change-password.component.html delete mode 100644 client/src/app/account/account-details/account-details.component.html 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 rename client/src/app/account/{ => account-settings}/account-change-password/account-change-password.component.ts (88%) rename client/src/app/account/{ => account-settings}/account-change-password/index.ts (100%) 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 rename client/src/app/account/{ => account-settings}/account-details/account-details.component.ts (78%) rename client/src/app/account/{ => account-settings}/account-details/index.ts (100%) 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 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 @@ -
{{ error }}
- -
-
- - -
- {{ formErrors['new-password'] }} -
-
- -
- - -
- - -
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 @@ -
{{ error }}
- -
-
- - -
- {{ formErrors['displayNSFW'] }} -
-
- - -
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 @@ +
{{ error }}
+ +
+ +
+ {{ formErrors['new-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 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-change-password/account-change-password.component.ts b/client/src/app/account/account-settings/account-change-password/account-change-password.component.ts similarity index 88% rename from client/src/app/account/account-change-password/account-change-password.component.ts rename to client/src/app/account/account-settings/account-change-password/account-change-password.component.ts index 69edec54b..8979e1734 100644 --- a/client/src/app/account/account-change-password/account-change-password.component.ts +++ b/client/src/app/account/account-settings/account-change-password/account-change-password.component.ts @@ -1,16 +1,13 @@ 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' +import { FormReactive, USER_PASSWORD, UserService } from '../../../shared' @Component({ selector: 'my-account-change-password', - templateUrl: './account-change-password.component.html' + templateUrl: './account-change-password.component.html', + styleUrls: [ './account-change-password.component.scss' ] }) - export class AccountChangePasswordComponent extends FormReactive implements OnInit { error: string = null diff --git a/client/src/app/account/account-change-password/index.ts b/client/src/app/account/account-settings/account-change-password/index.ts similarity index 100% rename from client/src/app/account/account-change-password/index.ts rename to client/src/app/account/account-settings/account-change-password/index.ts 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 @@ +
{{ error }}
+ +
+ + +
+ {{ formErrors['displayNSFW'] }} +
+ + +
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-details/account-details.component.ts b/client/src/app/account/account-settings/account-details/account-details.component.ts similarity index 78% rename from client/src/app/account/account-details/account-details.component.ts rename to client/src/app/account/account-settings/account-details/account-details.component.ts index d7a6e6871..d835c53e5 100644 --- a/client/src/app/account/account-details/account-details.component.ts +++ b/client/src/app/account/account-settings/account-details/account-details.component.ts @@ -1,21 +1,14 @@ -import { Component, OnInit, Input } from '@angular/core' +import { Component, Input, 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 { UserUpdateMe } from '../../../../../shared' +import { UserUpdateMe } from '../../../../../../shared' +import { AuthService } from '../../../core' +import { FormReactive, User, UserService } from '../../../shared' @Component({ selector: 'my-account-details', - templateUrl: './account-details.component.html' + templateUrl: './account-details.component.html', + styleUrls: [ './account-details.component.scss' ] }) export class AccountDetailsComponent extends FormReactive implements OnInit { diff --git a/client/src/app/account/account-details/index.ts b/client/src/app/account/account-settings/account-details/index.ts similarity index 100% rename from client/src/app/account/account-details/index.ts rename to client/src/app/account/account-settings/account-details/index.ts 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 @@ + + + + + + + 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 @@
-
-

Account

+ - -
-
-
Update my informations
- -
- -
-
-
+
+
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 { AccountRoutingModule } from './account-routing.module' -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' +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 { AccountService } from './account.service' @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 @@
-
{{ user.username }}
+ {{ user.username }}
{{ user.email }}
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 @@ -
- {{ titlePage }} -
+
+
+ {{ titlePage }} +
-
- - + + +
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; // From 202f6b6c9dcc9b0aec4b0c1b15e455c22a7952a7 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 1 Dec 2017 18:56:26 +0100 Subject: [PATCH 06/49] Begin videos of an account --- .../src/app/account/account-routing.module.ts | 19 +++++----- .../account-videos.component.html | 9 +++++ .../account-videos.component.scss} | 0 .../account-videos.component.ts | 35 +++++++++++++++++++ client/src/app/account/account.module.ts | 4 ++- client/src/app/shared/shared.module.ts | 12 +++++-- .../video}/abstract-video-list.html | 0 .../app/shared/video/abstract-video-list.scss | 0 .../video}/abstract-video-list.ts | 8 ++--- .../video}/sort-field.type.ts | 0 .../video}/video-details.model.ts | 2 +- .../video}/video-edit.model.ts | 0 .../video}/video-pagination.model.ts | 0 .../video/video-thumbnail.component.html | 10 ++++++ .../video/video-thumbnail.component.scss | 28 +++++++++++++++ .../shared/video/video-thumbnail.component.ts | 12 +++++++ .../shared => shared/video}/video.model.ts | 2 +- .../shared => shared/video}/video.service.ts | 32 +++++++---------- .../+video-edit/shared/video-edit.module.ts | 3 +- .../videos/+video-edit/video-add.component.ts | 22 ++++++------ .../+video-edit/video-update.component.ts | 19 +++++----- .../+video-watch/video-download.component.ts | 4 +-- .../+video-watch/video-report.component.ts | 8 ++--- .../+video-watch/video-share.component.ts | 4 +-- .../+video-watch/video-watch.component.ts | 4 ++- .../videos/+video-watch/video-watch.module.ts | 5 ++- client/src/app/videos/shared/index.ts | 6 ---- .../src/app/videos/video-list/shared/index.ts | 1 - .../shared/video-miniature.component.html | 11 +----- .../shared/video-miniature.component.scss | 29 --------------- .../shared/video-miniature.component.ts | 4 +-- .../video-recently-added.component.ts | 8 ++--- .../video-list/video-trending.component.ts | 8 ++--- client/src/app/videos/videos.module.ts | 9 ++--- 34 files changed, 179 insertions(+), 139 deletions(-) create mode 100644 client/src/app/account/account-videos/account-videos.component.html rename client/src/app/{videos/video-list/shared/abstract-video-list.scss => account/account-videos/account-videos.component.scss} (100%) create mode 100644 client/src/app/account/account-videos/account-videos.component.ts rename client/src/app/{videos/video-list/shared => shared/video}/abstract-video-list.html (100%) create mode 100644 client/src/app/shared/video/abstract-video-list.scss rename client/src/app/{videos/video-list/shared => shared/video}/abstract-video-list.ts (93%) rename client/src/app/{videos/shared => shared/video}/sort-field.type.ts (100%) rename client/src/app/{videos/shared => shared/video}/video-details.model.ts (97%) rename client/src/app/{videos/shared => shared/video}/video-edit.model.ts (100%) rename client/src/app/{videos/shared => shared/video}/video-pagination.model.ts (100%) 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 rename client/src/app/{videos/shared => shared/video}/video.model.ts (98%) rename client/src/app/{videos/shared => shared/video}/video.service.ts (87%) 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 @@ +
+
+ +
+
diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.scss b/client/src/app/account/account-videos/account-videos.component.scss similarity index 100% rename from client/src/app/videos/video-list/shared/abstract-video-list.scss rename to client/src/app/account/account-videos/account-videos.component.scss 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/videos/video-list/shared/abstract-video-list.html b/client/src/app/shared/video/abstract-video-list.html similarity index 100% rename from client/src/app/videos/video-list/shared/abstract-video-list.html rename to client/src/app/shared/video/abstract-video-list.html 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/videos/video-list/shared/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts similarity index 93% rename from client/src/app/videos/video-list/shared/abstract-video-list.ts rename to client/src/app/shared/video/abstract-video-list.ts index a684ffef4..cf717cf4c 100644 --- a/client/src/app/videos/video-list/shared/abstract-video-list.ts +++ b/client/src/app/shared/video/abstract-video-list.ts @@ -1,11 +1,11 @@ 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' +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 = { @@ -76,7 +76,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { protected hasMoreVideos () { if (!this.pagination.totalItems) return true - const maxPage = this.pagination.totalItems/this.pagination.itemsPerPage + const maxPage = this.pagination.totalItems / this.pagination.itemsPerPage return maxPage > this.pagination.currentPage } diff --git a/client/src/app/videos/shared/sort-field.type.ts b/client/src/app/shared/video/sort-field.type.ts similarity index 100% rename from client/src/app/videos/shared/sort-field.type.ts rename to client/src/app/shared/video/sort-field.type.ts diff --git a/client/src/app/videos/shared/video-details.model.ts b/client/src/app/shared/video/video-details.model.ts similarity index 97% rename from client/src/app/videos/shared/video-details.model.ts rename to client/src/app/shared/video/video-details.model.ts index 64cb4f847..93c380b73 100644 --- a/client/src/app/videos/shared/video-details.model.ts +++ b/client/src/app/shared/video/video-details.model.ts @@ -1,4 +1,4 @@ -import { Video } from './video.model' +import { Video } from '../../shared/video/video.model' import { AuthUser } from '../../core' import { VideoDetails as VideoDetailsServerModel, diff --git a/client/src/app/videos/shared/video-edit.model.ts b/client/src/app/shared/video/video-edit.model.ts similarity index 100% rename from client/src/app/videos/shared/video-edit.model.ts rename to client/src/app/shared/video/video-edit.model.ts diff --git a/client/src/app/videos/shared/video-pagination.model.ts b/client/src/app/shared/video/video-pagination.model.ts similarity index 100% rename from client/src/app/videos/shared/video-pagination.model.ts rename to client/src/app/shared/video/video-pagination.model.ts 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 @@ + +video thumbnail + +
+ {{ video.durationLabel }} +
+
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/videos/shared/video.model.ts b/client/src/app/shared/video/video.model.ts similarity index 98% rename from client/src/app/videos/shared/video.model.ts rename to client/src/app/shared/video/video.model.ts index 0dd41d71b..6929c8755 100644 --- a/client/src/app/videos/shared/video.model.ts +++ b/client/src/app/shared/video/video.model.ts @@ -1,5 +1,5 @@ import { Video as VideoServerModel } from '../../../../../shared' -import { User } from '../../shared' +import { User } from '../' export class Video implements VideoServerModel { account: string diff --git a/client/src/app/videos/shared/video.service.ts b/client/src/app/shared/video/video.service.ts similarity index 87% rename from client/src/app/videos/shared/video.service.ts rename to client/src/app/shared/video/video.service.ts index 5d25a26d4..b2a26417c 100644 --- a/client/src/app/videos/shared/video.service.ts +++ b/client/src/app/shared/video/video.service.ts @@ -1,29 +1,23 @@ -import { Injectable } from '@angular/core' -import { Observable } from 'rxjs/Observable' 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 { - 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' +import { Video } from './video.model' @Injectable() export class VideoService { 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/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 @@
- - video thumbnail - -
- {{ video.durationLabel }} -
-
+
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 { } From 2295ce6c4e7ba805cc100ff961527bebc2cd89e5 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 4 Dec 2017 10:34:40 +0100 Subject: [PATCH 07/49] Add account avatar --- .../account-settings.component.html | 10 ++++- .../account-settings.component.scss | 21 ++++++++-- .../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 +++ config/default.yaml | 1 + config/production.yaml.example | 1 + config/test-1.yaml | 1 + config/test-2.yaml | 1 + config/test-3.yaml | 1 + config/test-4.yaml | 1 + config/test-5.yaml | 1 + config/test-6.yaml | 1 + server/initializers/constants.ts | 7 +++- server/initializers/database.ts | 2 + .../migrations/0115-account-avatar.ts | 31 +++++++++++++++ server/models/account/account-interface.ts | 3 ++ server/models/account/account.ts | 27 ++++++++++++- server/models/account/user.ts | 5 +-- server/models/avatar/avatar-interface.ts | 16 ++++++++ server/models/avatar/avatar.ts | 24 ++++++++++++ server/models/avatar/index.ts | 1 + server/models/index.ts | 1 + shared/models/accounts/account.model.ts | 8 ++++ shared/models/avatars/avatar.model.ts | 5 +++ shared/models/users/user.model.ts | 8 ++-- 31 files changed, 207 insertions(+), 54 deletions(-) create mode 100644 client/src/assets/default-avatar.png create mode 100644 server/initializers/migrations/0115-account-avatar.ts create mode 100644 server/models/avatar/avatar-interface.ts create mode 100644 server/models/avatar/avatar.ts create mode 100644 server/models/avatar/index.ts create mode 100644 shared/models/avatars/avatar.model.ts 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 @@ - diff --git a/server/initializers/migrations/0115-account-avatar.ts b/server/initializers/migrations/0115-account-avatar.ts index e3531f5ce..2b947ceda 100644 --- a/server/initializers/migrations/0115-account-avatar.ts +++ b/server/initializers/migrations/0115-account-avatar.ts @@ -7,7 +7,7 @@ async function up (utils: { sequelize: Sequelize.Sequelize, db: PeerTubeDatabase }): Promise { - await db.Avatar.sync() + await utils.db.Avatar.sync() const data = { type: Sequelize.INTEGER, diff --git a/server/models/account/account.ts b/server/models/account/account.ts index 15be1126b..8b0819f39 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts @@ -13,7 +13,6 @@ import { isActivityPubUrlValid } from '../../helpers/custom-validators/activityp import { AVATARS_DIR } from '../../initializers' import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers/constants' import { sendDeleteAccount } from '../../lib/activitypub/send/send-delete' -import { AvatarModel } from '../avatar' import { addMethodsToModel } from '../utils' import { AccountAttributes, AccountInstance, AccountMethods } from './account-interface' diff --git a/server/models/avatar/avatar.ts b/server/models/avatar/avatar.ts index 3d329d888..96308fd5f 100644 --- a/server/models/avatar/avatar.ts +++ b/server/models/avatar/avatar.ts @@ -1,6 +1,6 @@ import * as Sequelize from 'sequelize' import { addMethodsToModel } from '../utils' -import { AvatarAttributes, AvatarInstance, AvatarMethods } from './avatar-interface' +import { AvatarAttributes, AvatarInstance } from './avatar-interface' let Avatar: Sequelize.Model From 62e23e40dad8fbd6148a9201fba2e73ced3f1877 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 4 Dec 2017 11:40:45 +0100 Subject: [PATCH 11/49] Fix client build --- client/src/app/shared/shared.module.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) 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, From be6a4802326b1748e85c0d6fdadf06e70e6ecbb0 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 5 Dec 2017 10:44:11 +0100 Subject: [PATCH 12/49] Design video player --- .../+video-watch/video-watch.component.html | 11 +- .../+video-watch/video-watch.component.scss | 18 +- .../+video-watch/video-watch.component.ts | 11 +- .../src/assets/player/images/arrow-down.svg | 14 + client/src/assets/player/images/arrow-up.svg | 14 + .../src/assets/player/images/fullscreen.svg | 18 + .../src/assets/player/images/volume-mute.svg | 16 + client/src/assets/player/images/volume.svg | 13 + .../assets/player/peertube-videojs-plugin.ts | 98 +++- client/src/sass/video-js-custom.scss | 539 +++++++++--------- 10 files changed, 435 insertions(+), 317 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 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 @@
-
- +
+
Video not found :'(
- -
-
Download: {{ downloadSpeed | bytes }}/s
-
Upload: {{ uploadSpeed | bytes }}/s
-
Number of peers: {{ numPeers }}
-
-
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 @@ + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + 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 @@ + + + + fullscreen + Created with Sketch. + + + + + + + + + + + + + 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 @@ + + + + volume-mute + Created with Sketch. + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + 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; - - .vjs-menu { - .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: 1em; - } - } -} - // Thanks: https://github.com/zanechua/videojs-sublime-inspired-skin +$primary-foreground-color: #fff; +$primary-background-color: #000; +$font-size: 13px; +$control-bar-height: 34px; -// 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. - -// The color of icons, text, and the big play button border. -// Try changing to #0f0 -$primary-foreground-color: #fff; // #fff default - -// 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 - -// Try changing to true -$center-big-play-button: true; // true default - -.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; - - /* The main font color changes the ICON COLORS as well as the text */ +.video-js.vjs-peertube-skin { + font-size: $font-size; color: $primary-foreground-color; -} -/* 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; + .vjs-button > .vjs-icon-placeholder::before { + line-height: $control-bar-height; + } - /* 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; + .vjs-mouse-display:before, + .vjs-play-progress:before, + .vjs-volume-level:before { + content: ''; /* Remove Circle From Progress Bar */ + } - line-height: $big-play-height; - height: $big-play-height; - width: $big-play-width; + .vjs-audio-button { + display: none; + } - /* 0.06666em = 2px default */ - border: 0; - /* 0.3em = 9px default */ - border-radius: 0.3em; + .vjs-big-play-button { + font-size: 8em; + + $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; - @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; -} + &:hover .vjs-big-play-button { + background-color: transparent; + } -// Make a slightly lighter version of the main background -// for the slider background. -$slider-bg-color: lighten($primary-background-color, 33%); + .vjs-control-bar, + .vjs-big-play-button, + .vjs-menu-button .vjs-menu-content { + background-color: rgba($primary-background-color, 0.5); + } -/* 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; -} + $slider-bg-color: lighten($primary-background-color, 33%); -/* 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; -} + .vjs-slider { + background-color: rgba(255, 255, 255, .3); + border-radius: 2px; + height: 5px; + } -/* 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 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; + } -/* 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); -} + .vjs-load-progress { + 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); -} + .vjs-load-progress div { + background: rgba($slider-bg-color, 0.75); + } -//Skin Style Starts -.vjs-sublime-skin .vjs-poster { + .vjs-poster { outline: none; /* Remove Blue Outline on Click*/ outline: 0; -} + } -.vjs-sublime-skin:hover .vjs-big-play-button { - background-color: transparent; -} + .vjs-control-bar { + height: $control-bar-height; -.vjs-sublime-skin .vjs-fullscreen-control:before, -.vjs-sublime-skin.vjs-fullscreen .vjs-fullscreen-control:before { - content: ''; /* Remove Fullscreen Exit Icon */ -} + .vjs-progress-control { + bottom: 34px; + width: 100%; + position: absolute; + height: 5px; -.vjs-sublime-skin.vjs-fullscreen .vjs-fullscreen-control { - background: #fff; -} + .vjs-progress-holder { + margin: 0; + border-radius: 0; + } + } -.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-play-control { + font-size: $font-size; + padding: 0 17px; + margin-right: 5px; + } -.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-time-control { + &.vjs-current-time { + font-size: $font-size; + display: inline-block; + padding: 0; -.vjs-sublime-skin .vjs-progress-holder { - margin: 0; -} + .vjs-current-time-display { + line-height: $control-bar-height; -.vjs-sublime-skin .vjs-progress-control .vjs-progress-holder:after { - border-radius: 2px; - display: block; - height: 6.5px; -} + &::after { + content: "/"; + margin: 0 1px 0 2px; + } + } + } -.vjs-sublime-skin .vjs-progress-control .vjs-load-progres, -.vjs-sublime-skin .vjs-progress-control .vjs-play-progress { - border-radius: 2px; - height: 6.5px; -} + &.vjs-duration { + font-size: $font-size; + display: inline-block; + padding: 0; -.vjs-sublime-skin .vjs-playback-rate { - display: none; /* Remove Playback Rate */ -} + .vjs-duration-display { + line-height: $control-bar-height; + } + } -.vjs-sublime-skin .vjs-progress-control { - margin-right: 50px; -} + &.vjs-remaining-time { + display: none; + } + } -.vjs-sublime-skin .vjs-time-control { - right: 55px; -} + .vjs-webtorrent { + width: 100%; + line-height: $control-bar-height; + text-align: right; + padding-right: 60px; -.vjs-sublime-skin .vjs-volume-menu-button:before { - width: 1.2em; - z-index: 1; -} + .download-speed-number, .upload-speed-number, .peers-number { + font-weight: $font-semibold; + } -.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; -} + .download-speed-text, .upload-speed-text, .peers-text { + margin-right: 15px; + } -.vjs-sublime-skin .vjs-volume-menu-button, -.vjs-sublime-skin .vjs-volume-panel { - width: 6em; - position: absolute; - right: 0; - margin-right: 65px; -} + .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; -.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; -} + &.icon-download { + background-image: url('../assets/player/images/arrow-down.svg'); + } -.vjs-sublime-skin .vjs-volume-menu-button .vjs-menu { - left: 23px; -} + &.icon-upload { + background-image: url('../assets/player/images/arrow-up.svg'); + } + } + } -.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-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; -.vjs-sublime-skin .vjs-mouse-display:after, -.vjs-sublime-skin .vjs-play-progress:after, -.vjs-sublime-skin .vjs-time-tooltip { - width: 5.5em; -} + &::before { + content: ''; + } + } -.vjs-sublime-skin .vjs-audio-button { - display: none; -} + &.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; -/* 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-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%; + } + } -.vjs-sublime-skin .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control.vjs-volume-horizontal { - width: 3em; - height: auto; -} + .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 { - transition-property: none; -} + .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control.vjs-volume-horizontal { + width: 3em; + height: auto; + } -.vjs-sublime-skin .vjs-fullscreen-control .vjs-icon-placeholder { - display: none; /* Remove Duplicate Fullscreen Icon */ -} + .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control { + transition-property: none; + } -.vjs-sublime-skin .vjs-volume-panel .vjs-mute-control { - width: 2em; - z-index: 1; - padding: 0; -} + .vjs-volume-panel { + .vjs-mute-control { + width: 2em; + z-index: 1; + padding: 0; + } -.vjs-sublime-skin .vjs-volume-panel .vjs-volume-control { - display: inline-block; - position: relative; - left: 5px; - opacity: 1; - width: 3em; - height: auto; + .vjs-volume-control { + display: inline-block; + position: relative; + left: 5px; + opacity: 1; + width: 3em; + height: auto; + } + } + + .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-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%; From 6bafac54bf375cd60f1c06f6afdc648e0e19743d Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 5 Dec 2017 15:01:47 +0100 Subject: [PATCH 13/49] 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 +- server/controllers/client.ts | 2 ++ 5 files changed, 16 insertions(+), 2 deletions(-) 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 bb57ee6b0200ec38a2e0205edcd80ca2e602c3b9..cef0f0d2e2d36017421b9e327ae46a11c1b6373f 100644 GIT binary patch literal 539 zcmV+$0_6RPP)12P+DJJvjgX0jNnt zK~y-)rIfo%LSY!jpYJ5pCxHqE(itieIuTj4yUN@_0LrBaxmox?@!!jwrzkJA}0m&*;s zQmKU0?JaV-e0a(;IKjB>(lkL$s;VM<5VpJ?`4&Fz&&afaEXxo#No?(GGa{-`F^~82 zO%$VkqTCDpYOtZIxPN@GxZQ5n+*p1Vcs-m%GmTWAx?N2*!lkzw&C)jwJB>zZEfne? zqR*d(3cG!TVz#BuxDB0)hmeL81^Pki3J8jdv?7R93Zm0Nr+7j|4OTl- zl{!U&b~HMpMNJ7JQtnF&5`++rq$K1bo9u2jo9y|!e{cV&o$07ZB0hh<$Go3;-)Fwh z_XVyhD+>Vcy!kKyV8Q&ybqBUSI_Ha;=k|PDy=E+~QS6l_Ha_5uyx0QK?6`fHMw_7#g5zOjJ`9J z^a;J8U_?jJRz~EltjhtrgCO7+5blXDmnf}>)@2x4o5RPJKl%2D*LVZ_=v|xN-WW}> zBTMrx%%F50=vB%vs=$)P8Xcf0283vk`EJPJASO@Ec}HVC07F6SZ7TtAZ2&bJK7V#{ z(dEZgS^(d#fZ~JD5gDw`1quu(LO>CK)2E|!h6k!30vf=m1Bh$-y+ic?{rv%r7OiJa z4hN@a;2c>1O%-7>a{#9Y1@hf00*Wxu6c#kaKnT^$wtG;iccb=5uXJ2fx zRBhjXATg*X$jub;2u*>acrXk=AOflk3OxLN2e$mJ4*3N)p>~^yKu-z)g&WIs5E8wB z7)8H7h~F%6th=?sqN+KfX7yOE4XAjJF#wqAxo2|?+pgMl4@O&^9PC&0*IAl?P%@MVc1)q%!sW3_Iq zq~5AJj9I=*C{1=DcNhc7U_p(x7>B=^fK1y~y#AU3A(lc_<#{Y!?1H9Vc{#zB}R@70iz3||R=g;(g`a$ikb62f|r`n5VyK`c@I-5tMJ$f4D6ZJ3*x4@7P z@Q_-CC98HKy`l`N>V{tD$1{~K?Ech%%yb9R9H&5t3q*jX!f ze^86719Ufq_hoTAPHm`MbVGcr0=vfy)@g#OV}O8!ueNq!<_H!#V;Xn~*k6+dZ@L@) zU;(6A>sXt5Md zx6vMHqi98igd+?x_;sXL48 zVjoPp81`?;KuLZV?7~SLZ^*p3>685z2OUOh)-5(%FUuti;hvX5fmz9DFXt=tAZNH9 z*<*Kv;@w6dl6bR0@j`bsi_^3q@@mD)J8nL>C51d(hvZq zPaN-fYPzRCJ3|L#q+`THFLdc?P-8)8N)n902Abjkh6ADgn-y}h2mRfb;l6$%zWK5t zG2y|lb0MDZA5IP@ef~v^K%a5b!>t1-XqF`)x(5wboQ(<5FzZC0JJo|oDO9lX<*k_jbh?&!p zAki+w6&EN?g%a_D5Ct^Hps-j&cfbYz2@W=67{f9x@jjiCkNJ~*Z`J0le(#IwuBQ5< z7*$p=bbwXsYuZXnA80ARK`ctiP8gLKE*#UNpfCc~#)2qr5EbBf9dwKjAO4dAV?yHV zR`1B8$KOi;!1`5-u6X#5#_{(xe#}khxPKhHI ze~0On&EbJ`QPGNuJZj0&v&|m^z@fUc_}NeJ`U}6sinUvgES)lG-ksyGdpFCaWI#?! zoWBs$QfgZK^u>TDBqy0B-jI3hU~O#WqPymK`F4!3>d7^?l<%YWA!w{;c%BE=_$4`M;CSa46^YU5VJ)(r`?trLsXF!{8HNY!H+k&&(A7* z_nrBLWaon;yPUY6UAOSolI{2BtLXR$Zf+M@0<$2%Xst~dia3on0hTwfX%9E~r2vg;=Su4uwF zt5;K-H&@Yby!z^!GnTB_(jDkFW>qZEjVz)(Q+=(zfrtQM)UQx7XAYHmSSJ z--4{k)1WFH44WXu%c}NQU(@x!vi_yow$^sicB!50*}adXQb{7pBB_66Jo)%f>quWD zLI@$`#Lla`mej+t@)T^Zh7v|Q?d z(cysJXo98y&CbPelud=%WCj3?zI_?=H_U_8W&!|sODhnFXW(*FYWmp#40tis+Y^Zf zx_ZxD4oOaX8U#U9Qqfpn2T%JIXds#jNfPD8hQ@wl;Vs`;ia-!V>0q>A``W+nta|y^ zvxaWM=PUn$4Qqb~0GK;vvT@uU_vVi)^reYHYM`dJwmr9S3{_m1KYB#Ahv~fB-TzVb z`~FGuAGs&jrp>iljsHsViO=s__(0QZ3n%00tw(+m;6DPe=Vb_Kp0@x1002ovPDHLk FV1g|HPU-*v 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 @@ - + diff --git a/server/controllers/client.ts b/server/controllers/client.ts index 64e5829ca..f474c4282 100644 --- a/server/controllers/client.ts +++ b/server/controllers/client.ts @@ -18,6 +18,7 @@ import { VideoInstance } from '../models' const clientsRouter = express.Router() const distPath = join(root(), 'client', 'dist') +const assetsImagesPath = join(root(), 'client', 'dist', 'assets', 'images') const embedPath = join(distPath, 'standalone', 'videos', 'embed.html') const indexPath = join(distPath, 'index.html') @@ -33,6 +34,7 @@ clientsRouter.use('/videos/embed', (req: express.Request, res: express.Response, // Static HTML/CSS/JS client files clientsRouter.use('/client', express.static(distPath, { maxAge: STATIC_MAX_AGE })) +clientsRouter.use('/client/assets/images', express.static(assetsImagesPath, { maxAge: STATIC_MAX_AGE })) // 404 for static files not found clientsRouter.use('/client/*', (req: express.Request, res: express.Response, next: express.NextFunction) => { From 35fb2b68ff9cd9b755dae6f073c32a97dc237e35 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 5 Dec 2017 16:30:03 +0100 Subject: [PATCH 14/49] Fix favicon ratio --- client/src/assets/images/favicon.png | Bin 539 -> 833 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/client/src/assets/images/favicon.png b/client/src/assets/images/favicon.png index cef0f0d2e2d36017421b9e327ae46a11c1b6373f..2e589cf6ce8877a7855966e11d179c776c425a7f 100644 GIT binary patch delta 820 zcmV-41IzrI1i=O&iBL{Q4GJ0x0000DNk~Le0000W0000W2nGNE0CReJ^pPPPe+}0F z01ejxLMWSf00007bV*G`2jL6_4*)MiZw1f*00PcQL_t(o!^M_MOcPNQhR+>}QNui- zMUCim(u5Ff(2@cXR9qOt&IPb=M@a~e#03e9vLPu8lCqeICI$qX_*!U1W#i6`iG$Ua zw<1Jk;j2M$YMGH@V9Ip?Bxop1e`#m(_?+S>4p`-aHiV88p6VVpZjXlZ#&y?&3xw*MNA3b?I z%d%|6rgl957`*Zvd1;{ux#&LmgDwPI^{zm0ICPk0+0zNOQ~+8XPlkpj0t~}MA3XF= z6%-Vl)li`a5eBb3mvtf1f0)^j)Bo#pN(8EFYeHia;}&h^{{*?1L8xz{Eo37GKuHsz zX?o%I-8+*GhodruPN5KnKDjxoT2kW;X5Z3RO$2IcYDdQ>C(J3DZ>CXYGMU~+TgXOD z0ZPhB5ddHv=KhMN0HIK*xTB*(3WvjDdaUs?l~9YQUgehNB8pCrkXJkRT9o=|OVZKXze{RP9y45H>tD53xW3IG5}MNUMnLSTZQ7;0$% delta 523 zcmV+m0`&dC2Ac#SiBL{Q4GJ0x0000DNk~Le0000G0000M2nGNE0LiKxe32m>e-7yY z01oK^4l_a=00007bV*G`2jL6_4hJg=fjv0@00F2;L_t(I%cYdNOG057#-Hyb)F*)o z2GSWS5;_rCwB!~IT|j6Eo0>AGtkZ3)#o~-Y6xiC7wd82Z4(Jq6$%CH z@9kkVuu8hF6HH2KF5IP3n4g`)MeM?qNk@;<87`O04aHKagw*XVa=CnX$}>2@xb4z3 zK~1WvB76|GydL=$KJL%Rw1F(k5I0F|?QAn5s!%bH_w!8@qkf{?3;k-af1#?le|)gG z-EP+0Sbi3GJ)A@{jZ~kyT}?H@rMDW*(l-q|jYerL6zU+N&!2`0yM2UWwx!Ov4V{aJ zkcJcm|B)q`OhVJNimvN9m&@faH8e7VuH_R*4w*>+kj-WhiA3x|2x5#G9xdZGbS)pl zx_-qZhs*%LQr{qiFr=wZVg2 Date: Tue, 5 Dec 2017 16:48:26 +0100 Subject: [PATCH 15/49] Design signup and login pages --- client/config/webpack.common.js | 1 + .../account-change-password.component.html | 2 + client/src/app/login/login.component.html | 63 +++++++++---------- 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, 63 insertions(+), 46 deletions(-) create mode 100644 client/src/app/login/login.component.scss create mode 100644 client/src/app/signup/signup.component.scss 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 @@
{{ error }}
+ + -
- -

Login

- -
{{ error }}
- - -
- - -
- {{ formErrors.username }} -
-
- -
- - -
- {{ formErrors.password }} -
-
- - - +
+
+ Login
+ +
{{ error }}
+ +
+
+ + +
+ {{ formErrors.username }} +
+
+ +
+ + +
+ {{ formErrors.password }} +
+
+ + +
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 @@ -
-
+
-

Signup

+
+ Signup +
{{ error }}
@@ -39,8 +40,7 @@
- + -
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 { From f3aaa9a95cc2b61f1f255472d7014d08faa66561 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 5 Dec 2017 17:46:33 +0100 Subject: [PATCH 16/49] Fix client search --- .../account-videos.component.ts | 8 +- client/src/app/app.component.html | 2 +- client/src/app/app.module.ts | 4 +- .../header.component.html} | 2 +- .../header.component.scss} | 0 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 - .../app/shared/search/search-field.type.ts | 1 - .../src/app/shared/search/search.component.ts | 42 ------- client/src/app/shared/search/search.model.ts | 6 - .../src/app/shared/search/search.service.ts | 18 --- client/src/app/shared/shared.module.ts | 4 - .../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-recently-added.component.ts | 12 +- .../video-list/video-search.component.ts | 51 ++++++++ .../video-list/video-trending.component.ts | 12 +- .../src/app/videos/videos-routing.module.ts | 11 ++ client/src/app/videos/videos.module.ts | 5 +- server/controllers/api/videos/index.ts | 22 ++-- server/initializers/constants.ts | 6 - server/middlewares/index.ts | 1 - server/middlewares/search.ts | 14 --- server/middlewares/validators/videos.ts | 5 +- server/models/video/video-interface.ts | 1 - server/models/video/video.ts | 47 +++----- server/tests/api/single-server.ts | 112 +++++++++--------- server/tests/utils/videos.ts | 20 ++-- 33 files changed, 245 insertions(+), 249 deletions(-) rename client/src/app/{shared/search/search.component.html => header/header.component.html} (79%) rename client/src/app/{shared/search/search.component.scss => header/header.component.scss} (100%) 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.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 delete mode 100644 server/middlewares/search.ts 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 @@
- +
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/shared/search/search.component.html b/client/src/app/header/header.component.html similarity index 79% rename from client/src/app/shared/search/search.component.html rename to client/src/app/header/header.component.html index 9bc9bafe4..aa72fb68a 100644 --- a/client/src/app/shared/search/search.component.html +++ b/client/src/app/header/header.component.html @@ -1,6 +1,6 @@ diff --git a/client/src/app/shared/search/search.component.scss b/client/src/app/header/header.component.scss similarity index 100% rename from client/src/app/shared/search/search.component.scss rename to client/src/app/header/header.component.scss 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.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 - updateSearch: Subject - - constructor () { - this.updateSearch = new Subject() - this.searchUpdated = new ReplaySubject(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>(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 @@
- Signup + Create an account
{{ error }}
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: [ diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index e2798830e..2b70d535e 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts @@ -26,7 +26,6 @@ import { authenticate, paginationValidator, setPagination, - setVideosSearch, setVideosSort, videosAddValidator, videosGetValidator, @@ -84,6 +83,14 @@ videosRouter.get('/', setPagination, asyncMiddleware(listVideos) ) +videosRouter.get('/search', + videosSearchValidator, + paginationValidator, + videosSortValidator, + setVideosSort, + setPagination, + asyncMiddleware(searchVideos) +) videosRouter.put('/:id', authenticate, asyncMiddleware(videosUpdateValidator), @@ -115,16 +122,6 @@ videosRouter.delete('/:id', asyncMiddleware(removeVideoRetryWrapper) ) -videosRouter.get('/search/:value', - videosSearchValidator, - paginationValidator, - videosSortValidator, - setVideosSort, - setPagination, - setVideosSearch, - asyncMiddleware(searchVideos) -) - // --------------------------------------------------------------------------- export { @@ -378,8 +375,7 @@ async function removeVideo (req: express.Request, res: express.Response) { async function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) { const resultList = await db.Video.searchAndPopulateAccountAndServerAndTags( - req.params.value, - req.query.field, + req.query.search, req.query.start, req.query.count, req.query.sort diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 144a4edbf..3e083fd92 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -24,11 +24,6 @@ const API_VERSION = 'v1' // Number of results by default for the pagination const PAGINATION_COUNT_DEFAULT = 15 -// Sortable columns per schema -const SEARCHABLE_COLUMNS = { - VIDEOS: [ 'name', 'magnetUri', 'host', 'account', 'tags' ] -} - // Sortable columns per schema const SORTABLE_COLUMNS = { USERS: [ 'id', 'username', 'createdAt' ], @@ -361,7 +356,6 @@ export { REMOTE_SCHEME, FOLLOW_STATES, AVATARS_DIR, - SEARCHABLE_COLUMNS, SERVER_ACCOUNT_NAME, PRIVATE_RSA_KEY_SIZE, SORTABLE_COLUMNS, diff --git a/server/middlewares/index.ts b/server/middlewares/index.ts index aafcad2d9..0cef26953 100644 --- a/server/middlewares/index.ts +++ b/server/middlewares/index.ts @@ -4,6 +4,5 @@ export * from './async' export * from './oauth' export * from './pagination' export * from './servers' -export * from './search' export * from './sort' export * from './user-right' diff --git a/server/middlewares/search.ts b/server/middlewares/search.ts deleted file mode 100644 index 6fe83d25b..000000000 --- a/server/middlewares/search.ts +++ /dev/null @@ -1,14 +0,0 @@ -import 'express-validator' -import * as express from 'express' - -function setVideosSearch (req: express.Request, res: express.Response, next: express.NextFunction) { - if (!req.query.field) req.query.field = 'name' - - return next() -} - -// --------------------------------------------------------------------------- - -export { - setVideosSearch -} diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts index f21680aa0..ee2ac50c8 100644 --- a/server/middlewares/validators/videos.ts +++ b/server/middlewares/validators/videos.ts @@ -18,7 +18,7 @@ import { } from '../../helpers/custom-validators/videos' import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils' import { logger } from '../../helpers/logger' -import { CONSTRAINTS_FIELDS, SEARCHABLE_COLUMNS } from '../../initializers' +import { CONSTRAINTS_FIELDS } from '../../initializers' import { database as db } from '../../initializers/database' import { UserInstance } from '../../models/account/user-interface' import { VideoInstance } from '../../models/video/video-interface' @@ -172,8 +172,7 @@ const videosRemoveValidator = [ ] const videosSearchValidator = [ - param('value').not().isEmpty().withMessage('Should have a valid search'), - query('field').optional().isIn(SEARCHABLE_COLUMNS.VIDEOS).withMessage('Should have correct searchable column'), + query('search').not().isEmpty().withMessage('Should have a valid search'), (req: express.Request, res: express.Response, next: express.NextFunction) => { logger.debug('Checking videosSearch parameters', { parameters: req.params }) diff --git a/server/models/video/video-interface.ts b/server/models/video/video-interface.ts index be140de86..2a63350af 100644 --- a/server/models/video/video-interface.ts +++ b/server/models/video/video-interface.ts @@ -50,7 +50,6 @@ export namespace VideoMethods { export type ListUserVideosForApi = (userId: number, start: number, count: number, sort: string) => Bluebird< ResultList > export type SearchAndPopulateAccountAndServerAndTags = ( value: string, - field: string, start: number, count: number, sort: string diff --git a/server/models/video/video.ts b/server/models/video/video.ts index f3469c1de..4dce8e2fc 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -1070,7 +1070,7 @@ loadByUUIDAndPopulateAccountAndServerAndTags = function (uuid: string) { return Video.findOne(options) } -searchAndPopulateAccountAndServerAndTags = function (value: string, field: string, start: number, count: number, sort: string) { +searchAndPopulateAccountAndServerAndTags = function (value: string, start: number, count: number, sort: string) { const serverInclude: Sequelize.IncludeOptions = { model: Video['sequelize'].models.Server, required: false @@ -1099,33 +1099,24 @@ searchAndPopulateAccountAndServerAndTags = function (value: string, field: strin order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ] } - if (field === 'tags') { - const escapedValue = Video['sequelize'].escape('%' + value + '%') - query.where['id'][Sequelize.Op.in] = Video['sequelize'].literal( - `(SELECT "VideoTags"."videoId" - FROM "Tags" - INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId" - WHERE name ILIKE ${escapedValue} - )` - ) - } else if (field === 'host') { - // FIXME: Include our server? (not stored in the database) - serverInclude.where = { - host: { - [Sequelize.Op.iLike]: '%' + value + '%' - } - } - serverInclude.required = true - } else if (field === 'account') { - accountInclude.where = { - name: { - [Sequelize.Op.iLike]: '%' + value + '%' - } - } - } else { - query.where[field] = { - [Sequelize.Op.iLike]: '%' + value + '%' - } + // TODO: search on tags too + // const escapedValue = Video['sequelize'].escape('%' + value + '%') + // query.where['id'][Sequelize.Op.in] = Video['sequelize'].literal( + // `(SELECT "VideoTags"."videoId" + // FROM "Tags" + // INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId" + // WHERE name ILIKE ${escapedValue} + // )` + // ) + + // TODO: search on account too + // accountInclude.where = { + // name: { + // [Sequelize.Op.iLike]: '%' + value + '%' + // } + // } + query.where['name'] = { + [Sequelize.Op.iLike]: '%' + value + '%' } query.include = [ diff --git a/server/tests/api/single-server.ts b/server/tests/api/single-server.ts index 041d13225..fe192d391 100644 --- a/server/tests/api/single-server.ts +++ b/server/tests/api/single-server.ts @@ -225,7 +225,7 @@ describe('Test a single server', function () { expect(video.views).to.equal(3) }) - it('Should search the video by name by default', async function () { + it('Should search the video by name', async function () { const res = await searchVideo(server.url, 'my') expect(res.body.total).to.equal(1) @@ -279,35 +279,36 @@ describe('Test a single server', function () { // }) // }) - it('Should search the video by tag', async function () { - const res = await searchVideo(server.url, 'tag1', 'tags') + // Not implemented yet + // it('Should search the video by tag', async function () { + // const res = await searchVideo(server.url, 'tag1') + // + // expect(res.body.total).to.equal(1) + // expect(res.body.data).to.be.an('array') + // expect(res.body.data.length).to.equal(1) + // + // const video = res.body.data[0] + // expect(video.name).to.equal('my super name') + // expect(video.category).to.equal(2) + // expect(video.categoryLabel).to.equal('Films') + // expect(video.licence).to.equal(6) + // expect(video.licenceLabel).to.equal('Attribution - Non Commercial - No Derivatives') + // expect(video.language).to.equal(3) + // expect(video.languageLabel).to.equal('Mandarin') + // expect(video.nsfw).to.be.ok + // expect(video.description).to.equal('my super description') + // expect(video.serverHost).to.equal('localhost:9001') + // expect(video.account).to.equal('root') + // expect(video.isLocal).to.be.true + // expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) + // expect(dateIsValid(video.createdAt)).to.be.true + // expect(dateIsValid(video.updatedAt)).to.be.true + // + // const test = await testVideoImage(server.url, 'video_short.webm', video.thumbnailPath) + // expect(test).to.equal(true) + // }) - expect(res.body.total).to.equal(1) - expect(res.body.data).to.be.an('array') - expect(res.body.data.length).to.equal(1) - - const video = res.body.data[0] - expect(video.name).to.equal('my super name') - expect(video.category).to.equal(2) - expect(video.categoryLabel).to.equal('Films') - expect(video.licence).to.equal(6) - expect(video.licenceLabel).to.equal('Attribution - Non Commercial - No Derivatives') - expect(video.language).to.equal(3) - expect(video.languageLabel).to.equal('Mandarin') - expect(video.nsfw).to.be.ok - expect(video.description).to.equal('my super description') - expect(video.serverHost).to.equal('localhost:9001') - expect(video.account).to.equal('root') - expect(video.isLocal).to.be.true - expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) - expect(dateIsValid(video.createdAt)).to.be.true - expect(dateIsValid(video.updatedAt)).to.be.true - - const test = await testVideoImage(server.url, 'video_short.webm', video.thumbnailPath) - expect(test).to.equal(true) - }) - - it('Should not find a search by name by default', async function () { + it('Should not find a search by name', async function () { const res = await searchVideo(server.url, 'hello') expect(res.body.total).to.equal(0) @@ -315,21 +316,23 @@ describe('Test a single server', function () { expect(res.body.data.length).to.equal(0) }) - it('Should not find a search by author', async function () { - const res = await searchVideo(server.url, 'hello', 'account') - - expect(res.body.total).to.equal(0) - expect(res.body.data).to.be.an('array') - expect(res.body.data.length).to.equal(0) - }) - - it('Should not find a search by tag', async function () { - const res = await searchVideo(server.url, 'hello', 'tags') - - expect(res.body.total).to.equal(0) - expect(res.body.data).to.be.an('array') - expect(res.body.data.length).to.equal(0) - }) + // Not implemented yet + // it('Should not find a search by author', async function () { + // const res = await searchVideo(server.url, 'hello') + // + // expect(res.body.total).to.equal(0) + // expect(res.body.data).to.be.an('array') + // expect(res.body.data.length).to.equal(0) + // }) + // + // Not implemented yet + // it('Should not find a search by tag', async function () { + // const res = await searchVideo(server.url, 'hello') + // + // expect(res.body.total).to.equal(0) + // expect(res.body.data).to.be.an('array') + // expect(res.body.data.length).to.equal(0) + // }) it('Should remove the video', async function () { await removeVideo(server.url, server.accessToken, videoId) @@ -443,7 +446,7 @@ describe('Test a single server', function () { }) it('Should search the first video', async function () { - const res = await searchVideoWithPagination(server.url, 'webm', 'name', 0, 1, 'name') + const res = await searchVideoWithPagination(server.url, 'webm', 0, 1, 'name') const videos = res.body.data expect(res.body.total).to.equal(4) @@ -452,7 +455,7 @@ describe('Test a single server', function () { }) it('Should search the last two videos', async function () { - const res = await searchVideoWithPagination(server.url, 'webm', 'name', 2, 2, 'name') + const res = await searchVideoWithPagination(server.url, 'webm', 2, 2, 'name') const videos = res.body.data expect(res.body.total).to.equal(4) @@ -462,20 +465,21 @@ describe('Test a single server', function () { }) it('Should search all the webm videos', async function () { - const res = await searchVideoWithPagination(server.url, 'webm', 'name', 0, 15) + const res = await searchVideoWithPagination(server.url, 'webm', 0, 15) const videos = res.body.data expect(res.body.total).to.equal(4) expect(videos.length).to.equal(4) }) - it('Should search all the root author videos', async function () { - const res = await searchVideoWithPagination(server.url, 'root', 'account', 0, 15) - - const videos = res.body.data - expect(res.body.total).to.equal(6) - expect(videos.length).to.equal(6) - }) + // Not implemented yet + // it('Should search all the root author videos', async function () { + // const res = await searchVideoWithPagination(server.url, 'root', 0, 15) + // + // const videos = res.body.data + // expect(res.body.total).to.equal(6) + // expect(videos.length).to.equal(6) + // }) // Not implemented yet // it('Should search all the 9001 port videos', async function () { diff --git a/server/tests/utils/videos.ts b/server/tests/utils/videos.ts index 73a9f1a0a..ff7da9bb2 100644 --- a/server/tests/utils/videos.ts +++ b/server/tests/utils/videos.ts @@ -145,26 +145,25 @@ function removeVideo (url: string, token: string, id: number, expectedStatus = 2 .expect(expectedStatus) } -function searchVideo (url: string, search: string, field?: string) { +function searchVideo (url: string, search: string) { const path = '/api/v1/videos' const req = request(url) - .get(path + '/search/' + search) - .set('Accept', 'application/json') - - if (field) req.query({ field }) + .get(path + '/search') + .query({ search }) + .set('Accept', 'application/json') return req.expect(200) - .expect('Content-Type', /json/) + .expect('Content-Type', /json/) } -function searchVideoWithPagination (url: string, search: string, field: string, start: number, count: number, sort?: string) { +function searchVideoWithPagination (url: string, search: string, start: number, count: number, sort?: string) { const path = '/api/v1/videos' const req = request(url) - .get(path + '/search/' + search) + .get(path + '/search') .query({ start }) + .query({ search }) .query({ count }) - .query({ field }) if (sort) req.query({ sort }) @@ -177,7 +176,8 @@ function searchVideoWithSort (url: string, search: string, sort: string) { const path = '/api/v1/videos' return request(url) - .get(path + '/search/' + search) + .get(path + '/search') + .query({ search }) .query({ sort }) .set('Accept', 'application/json') .expect(200) From a06a31c75c0cd4d337e3e193c670a77cabcd9507 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 5 Dec 2017 18:43:15 +0100 Subject: [PATCH 17/49] Fix player control bar when video is not loaded --- .../assets/player/peertube-videojs-plugin.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) 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 From a2b817d322ef4074bdaaf2589ada567f338323f4 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 6 Dec 2017 09:19:25 +0100 Subject: [PATCH 18/49] 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 + .../account-videos/account-videos.component.html | 2 ++ .../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(-) 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 @@ -
+
{{ formErrors['new-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'] }}
- + 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 @@
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 @@ -
+
{{ formErrors.username }}
@@ -21,9 +21,9 @@ -
+
{{ formErrors.password }}
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 @@ -
+
{{ formErrors.username }}
@@ -22,9 +22,9 @@ -
+
{{ formErrors.email }}
@@ -33,9 +33,9 @@ -
+
{{ formErrors.password }}
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; } From 332542bc6814bd16c2daf47dc776f9f4b126ec2e Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 6 Dec 2017 14:05:38 +0100 Subject: [PATCH 19/49] Add delete button to my videos --- .../account-videos.component.html | 7 +++++- .../account-videos.component.scss | 18 ++++++++++++--- .../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 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 @@ {{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views
- + + + Delete + + + Edit 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 @@ + + + + + + + + + + + + + + From 7d763d97497df1bbf7a01f61aa916d99a1338a33 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 6 Dec 2017 14:09:39 +0100 Subject: [PATCH 20/49] Add hover effect to buttons --- .../app/account/account-videos/account-videos.component.scss | 4 ++++ client/src/sass/_mixins.scss | 4 ++++ 2 files changed, 8 insertions(+) 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 { From ce0e281d46a7b574dcccb47958743656532bd312 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 6 Dec 2017 15:07:17 +0100 Subject: [PATCH 21/49] Client bulk delete --- .../account-videos.component.html | 36 +++++-- .../account-videos.component.scss | 102 +++++++++++------- .../account-videos.component.ts | 49 ++++++++- .../account/{delete.svg => delete-grey.svg} | 0 .../assets/images/account/delete-white.svg | 14 +++ client/src/sass/_mixins.scss | 4 +- client/src/sass/_variables.scss | 1 + 7 files changed, 157 insertions(+), 49 deletions(-) rename client/src/assets/images/account/{delete.svg => delete-grey.svg} (100%) create mode 100644 client/src/assets/images/account/delete-white.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 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()" > -
+
+ +
@@ -13,14 +15,30 @@ {{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views
- - - Delete - + +
+
+ + Cancel + - - - Edit - + + + Delete + +
+
+ + + + + Delete + + + + + Edit + +
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[] = [] + 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.svg b/client/src/assets/images/account/delete-grey.svg similarity index 100% rename from client/src/assets/images/account/delete.svg rename to client/src/assets/images/account/delete-grey.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 @@ + + + + + + + + + + + + + + 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; From b1fa3eba70dbd7d9e5b795ad251e293c88ebeee2 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 6 Dec 2017 17:15:59 +0100 Subject: [PATCH 22/49] Begin video watch design --- CREDITS.md | 9 +- .../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 +- .../app/shared/video/abstract-video-list.html | 2 +- .../app/shared/video/video-details.model.ts | 9 +- .../video}/video-miniature.component.html | 0 .../video}/video-miniature.component.scss | 0 .../video}/video-miniature.component.ts | 6 +- .../shared/video/video-pagination.model.ts | 2 +- client/src/app/shared/video/video.model.ts | 8 +- .../+video-watch/video-watch.component.html | 223 ++++++------ .../+video-watch/video-watch.component.scss | 324 +++++------------- .../+video-watch/video-watch.component.ts | 43 ++- client/src/app/videos/video-list/index.ts | 1 - .../src/app/videos/video-list/shared/index.ts | 1 - 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 + server/models/video/video.ts | 3 +- server/tests/api/follows.ts | 2 +- server/tests/api/multiple-servers.ts | 11 +- server/tests/api/services.ts | 4 +- server/tests/api/single-server.ts | 15 +- server/tests/api/users.ts | 4 +- server/tests/utils/servers.ts | 2 +- shared/models/videos/video.model.ts | 4 +- 29 files changed, 330 insertions(+), 431 deletions(-) create mode 100644 client/src/app/shared/account/account.model.ts rename client/src/app/{videos/video-list/shared => shared/video}/video-miniature.component.html (100%) rename client/src/app/{videos/video-list/shared => shared/video}/video-miniature.component.scss (100%) rename client/src/app/{videos/video-list/shared => shared/video}/video-miniature.component.ts (65%) delete mode 100644 client/src/app/videos/video-list/shared/index.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 diff --git a/CREDITS.md b/CREDITS.md index a7b2b5568..65017bbc1 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -8,14 +8,9 @@ # Design -Inspirations from: +By [Olivier Massain](https://twitter.com/omassain) - * [Aurélien Salomon](https://dribbble.com/shots/1338727-Youtube-Redesign) - * [Wojciech Zieliński](https://dribbble.com/shots/3000315-youtube-concept) - -Video.js theme: - - * [zanechua](https://github.com/zanechua/videojs-sublime-inspired-skin) +Icons from [Robbie Pearce](https://robbiepearce.com/softies/) # Fonts 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 @@ >
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/videos/video-list/shared/video-miniature.component.html b/client/src/app/shared/video/video-miniature.component.html similarity index 100% rename from client/src/app/videos/video-list/shared/video-miniature.component.html rename to client/src/app/shared/video/video-miniature.component.html diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.scss b/client/src/app/shared/video/video-miniature.component.scss similarity index 100% rename from client/src/app/videos/video-list/shared/video-miniature.component.scss rename to client/src/app/shared/video/video-miniature.component.scss diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.ts b/client/src/app/shared/video/video-miniature.component.ts similarity index 65% rename from client/src/app/videos/video-list/shared/video-miniature.component.ts rename to client/src/app/shared/video/video-miniature.component.ts index e8fc8e911..4d79a74bb 100644 --- a/client/src/app/videos/video-list/shared/video-miniature.component.ts +++ b/client/src/app/shared/video/video-miniature.component.ts @@ -1,7 +1,6 @@ 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' +import { User } from '../users' +import { Video } from './video.model' @Component({ selector: 'my-video-miniature', @@ -9,7 +8,6 @@ import { Video } from '../../../shared/video/video.model' templateUrl: './video-miniature.component.html' }) export class VideoMiniatureComponent { - @Input() currentSort: SortField @Input() user: User @Input() video: Video 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 @@ -
-
- The video load seems to be abnormally long. - -
-
-
@@ -23,167 +8,153 @@
-
-
-
- {{ video.name }} -
+
+
+
+
{{ video.name }}
-
- {{ video.views}} views -
-
+
+
+ +
-
- +
+ +
- +
+ + Share +
- -
-
- - - - {{ video.likes }} - -
- -
- - - - {{ video.dislikes }} - -
+
+ {{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views
-
-
-
-
- Published on {{ video.createdAt | date:'short' }} -
+
+ {{ video.channel.name }} + +
-
+
+ By {{ video.by }} + Account avatar +
-
+
+
+ +
Show more
-
+
Show less
-
-
- - Privacy: +
+
+ + Privacy - + {{ video.privacyLabel }}
-
- - Category: +
+ + Category - + {{ video.categoryLabel }}
-
- - Licence: +
+ + Licence - + {{ video.licenceLabel }}
-
- - Language: +
+ + Language - + {{ video.languageLabel }}
-
- - Tags: +
+ + Tags - + + {{ getVideoTags() }} +
+
+
+ +
+
+
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; +.video-bottom { + margin-top: 40px; + display: flex; - div { - min-width: 60px; - } -} + .video-info { + flex-grow: 1; + margin-right: 28px; -#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-views { - text-align: right; - // Keep a symmetry with the video name - padding-right: $video-watch-info-padding-left - } - - } - - .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-small-block { - height: $video-watch-info-height; + .video-info-name-actions { display: flex; - flex-direction: column; - justify-content: center; - text-align: center; + align-items: center; - a { - cursor: pointer; - transition: color 0.3s; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - - &, &:hover { - color: inherit; - text-decoration:none; - } - - &:hover { - color: #000 !important; - } - - &:hover > .glyphicon { - opacity: 1 !important; - } + .video-info-name { + font-size: 27px; + font-weight: $font-semibold; + flex-grow: 1; } - .option .glyphicon { - font-size: 22px; - color: inherit; - opacity: 0.15; - margin-bottom: 10px; - transition: opacity 0.3s; - } + .video-info-actions { + .action-button { + @include peertube-button; - .video-small-block-text { - font-size: 15px; - font-weight: bold; - } - } + font-size: 15px; + font-weight: $font-semibold; + color: #585858; + background-color: #E5E5E5; + display: inline-block; + padding: 0 10px 0 10px; - .video-small-block:not(:last-child) { - border-width: 0 1px 0 0; - border-color: $video-watch-border-color; - border-style: solid; - } + &:hover { + background-color: #EFEFEF; + } + } - .video-small-block-account, .video-small-block-more { - a.option { - display: block; + .action-more { + display: inline-block; + } - .glyphicon { - display: block; + .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-dislike { + background-image: url('../../../assets/images/video/dislike.svg'); + } + + &.icon-share { + background-image: url('../../../assets/images/video/share.svg'); + } + + &.icon-more { + background-image: url('../../../assets/images/video/more.svg'); + } } } } - .video-small-block-share, .video-small-block-more { - a.option { - display: block; + .video-info-date-views { + font-size: 16px; + margin-bottom: 10px; + } - .glyphicon { - display: block; - } + .video-info-channel { + font-weight: $font-semibold; + font-size: 15px; + } + + .video-info-by { + display: flex; + align-items: center; + font-size: 13px; + + img { + width: 16px; + height: 16px; + margin-left: 3px; } } - .video-small-block-more .video-small-block-dropdown { - position: relative; - - .dropdown-item .glyphicon { - margin-right: 5px; - } - } - - .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; - - &.activated, &:hover { - opacity: 1; - color: #000; - } - } - } - } - - .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-attributes { + .video-attribute { + font-size: 13px; + display: block; + margin-bottom: 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-attribute-label { + width: 86px; + display: inline-block; + color: #585858; + font-weight: $font-bold; } } } - .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-details-attribute-label { - width: 50px; - } - } - } - } - - @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/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 @@ + + + + + + + + + + + + + + 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 @@ + + + + thumbs-up + Created with Sketch. + + + + + + + + + + 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 @@ + + + + + + + + + + + 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 @@ + + + + share + Created with Sketch. + + + + + + + + + + + diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 4dce8e2fc..60023bc8c 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -486,7 +486,7 @@ toFormattedJSON = function (this: VideoInstance) { description: this.getTruncatedDescription(), serverHost, isLocal: this.isOwned(), - account: this.VideoChannel.Account.name, + accountName: this.VideoChannel.Account.name, duration: this.duration, views: this.views, likes: this.likes, @@ -514,6 +514,7 @@ toFormattedDetailsJSON = function (this: VideoInstance) { privacy: this.privacy, descriptionPath: this.getDescriptionPath(), channel: this.VideoChannel.toFormattedJSON(), + account: this.VideoChannel.Account.toFormattedJSON(), files: [] } diff --git a/server/tests/api/follows.ts b/server/tests/api/follows.ts index aadae3cce..dcb4c8bd9 100644 --- a/server/tests/api/follows.ts +++ b/server/tests/api/follows.ts @@ -227,7 +227,7 @@ describe('Test follows', function () { expect(videoDetails.nsfw).to.be.ok expect(videoDetails.description).to.equal('my super description') expect(videoDetails.serverHost).to.equal('localhost:9003') - expect(videoDetails.account).to.equal('root') + expect(videoDetails.accountName).to.equal('root') expect(videoDetails.likes).to.equal(1) expect(videoDetails.dislikes).to.equal(1) expect(videoDetails.isLocal).to.be.false diff --git a/server/tests/api/multiple-servers.ts b/server/tests/api/multiple-servers.ts index c80ded862..c7f19f261 100644 --- a/server/tests/api/multiple-servers.ts +++ b/server/tests/api/multiple-servers.ts @@ -111,13 +111,14 @@ describe('Test multiple servers', function () { expect(video.tags).to.deep.equal([ 'tag1p1', 'tag2p1' ]) expect(dateIsValid(video.createdAt)).to.be.true expect(dateIsValid(video.updatedAt)).to.be.true - expect(video.account).to.equal('root') + expect(video.accountName).to.equal('root') const res2 = await getVideo(server.url, video.uuid) const videoDetails = res2.body expect(videoDetails.channel.name).to.equal('my channel') expect(videoDetails.channel.description).to.equal('super channel') + expect(videoDetails.account.name).to.equal('root') expect(dateIsValid(videoDetails.channel.createdAt)).to.be.true expect(dateIsValid(videoDetails.channel.updatedAt)).to.be.true expect(videoDetails.files).to.have.lengthOf(1) @@ -201,7 +202,7 @@ describe('Test multiple servers', function () { expect(video.tags).to.deep.equal([ 'tag1p2', 'tag2p2', 'tag3p2' ]) expect(dateIsValid(video.createdAt)).to.be.true expect(dateIsValid(video.updatedAt)).to.be.true - expect(video.account).to.equal('user1') + expect(video.accountName).to.equal('user1') if (server.url !== 'http://localhost:9002') { expect(video.isLocal).to.be.false @@ -316,7 +317,7 @@ describe('Test multiple servers', function () { expect(video1.serverHost).to.equal('localhost:9003') expect(video1.duration).to.equal(5) expect(video1.tags).to.deep.equal([ 'tag1p3' ]) - expect(video1.account).to.equal('root') + expect(video1.accountName).to.equal('root') expect(dateIsValid(video1.createdAt)).to.be.true expect(dateIsValid(video1.updatedAt)).to.be.true @@ -342,7 +343,7 @@ describe('Test multiple servers', function () { expect(video2.serverHost).to.equal('localhost:9003') expect(video2.duration).to.equal(5) expect(video2.tags).to.deep.equal([ 'tag2p3', 'tag3p3', 'tag4p3' ]) - expect(video2.account).to.equal('root') + expect(video2.accountName).to.equal('root') expect(dateIsValid(video2.createdAt)).to.be.true expect(dateIsValid(video2.updatedAt)).to.be.true @@ -690,7 +691,7 @@ describe('Test multiple servers', function () { expect(baseVideo.licence).to.equal(video.licence) expect(baseVideo.category).to.equal(video.category) expect(baseVideo.nsfw).to.equal(video.nsfw) - expect(baseVideo.account).to.equal(video.account) + expect(baseVideo.accountName).to.equal(video.accountName) expect(baseVideo.tags).to.deep.equal(video.tags) } }) diff --git a/server/tests/api/services.ts b/server/tests/api/services.ts index 8d96ccc5e..4d480c305 100644 --- a/server/tests/api/services.ts +++ b/server/tests/api/services.ts @@ -46,7 +46,7 @@ describe('Test services', function () { expect(res.body.html).to.equal(expectedHtml) expect(res.body.title).to.equal(server.video.name) - expect(res.body.author_name).to.equal(server.video.account) + expect(res.body.author_name).to.equal(server.video.accountName) expect(res.body.width).to.equal(560) expect(res.body.height).to.equal(315) expect(res.body.thumbnail_url).to.equal(expectedThumbnailUrl) @@ -66,7 +66,7 @@ describe('Test services', function () { expect(res.body.html).to.equal(expectedHtml) expect(res.body.title).to.equal(server.video.name) - expect(res.body.author_name).to.equal(server.video.account) + expect(res.body.author_name).to.equal(server.video.accountName) expect(res.body.height).to.equal(50) expect(res.body.width).to.equal(50) expect(res.body).to.not.have.property('thumbnail_url') diff --git a/server/tests/api/single-server.ts b/server/tests/api/single-server.ts index fe192d391..d7e9ad41f 100644 --- a/server/tests/api/single-server.ts +++ b/server/tests/api/single-server.ts @@ -127,7 +127,7 @@ describe('Test a single server', function () { expect(video.nsfw).to.be.ok expect(video.description).to.equal('my super description') expect(video.serverHost).to.equal('localhost:9001') - expect(video.account).to.equal('root') + expect(video.accountName).to.equal('root') expect(video.isLocal).to.be.true expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) expect(dateIsValid(video.createdAt)).to.be.true @@ -176,7 +176,7 @@ describe('Test a single server', function () { expect(video.nsfw).to.be.ok expect(video.description).to.equal('my super description') expect(video.serverHost).to.equal('localhost:9001') - expect(video.account).to.equal('root') + expect(video.accountName).to.equal('root') expect(video.isLocal).to.be.true expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) expect(dateIsValid(video.createdAt)).to.be.true @@ -243,7 +243,7 @@ describe('Test a single server', function () { expect(video.nsfw).to.be.ok expect(video.description).to.equal('my super description') expect(video.serverHost).to.equal('localhost:9001') - expect(video.account).to.equal('root') + expect(video.accountName).to.equal('root') expect(video.isLocal).to.be.true expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) expect(dateIsValid(video.createdAt)).to.be.true @@ -298,7 +298,7 @@ describe('Test a single server', function () { // expect(video.nsfw).to.be.ok // expect(video.description).to.equal('my super description') // expect(video.serverHost).to.equal('localhost:9001') - // expect(video.account).to.equal('root') + // expect(video.accountName).to.equal('root') // expect(video.isLocal).to.be.true // expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) // expect(dateIsValid(video.createdAt)).to.be.true @@ -563,7 +563,8 @@ describe('Test a single server', function () { expect(video.nsfw).to.be.ok expect(video.description).to.equal('my super description updated') expect(video.serverHost).to.equal('localhost:9001') - expect(video.account).to.equal('root') + expect(video.accountName).to.equal('root') + expect(video.account.name).to.equal('root') expect(video.isLocal).to.be.true expect(video.tags).to.deep.equal([ 'tagup1', 'tagup2' ]) expect(dateIsValid(video.createdAt)).to.be.true @@ -612,7 +613,7 @@ describe('Test a single server', function () { expect(video.nsfw).to.be.ok expect(video.description).to.equal('my super description updated') expect(video.serverHost).to.equal('localhost:9001') - expect(video.account).to.equal('root') + expect(video.accountName).to.equal('root') expect(video.isLocal).to.be.true expect(video.tags).to.deep.equal([ 'supertag', 'tag1', 'tag2' ]) expect(dateIsValid(video.createdAt)).to.be.true @@ -652,7 +653,7 @@ describe('Test a single server', function () { expect(video.nsfw).to.be.ok expect(video.description).to.equal('hello everybody') expect(video.serverHost).to.equal('localhost:9001') - expect(video.account).to.equal('root') + expect(video.accountName).to.equal('root') expect(video.isLocal).to.be.true expect(video.tags).to.deep.equal([ 'supertag', 'tag1', 'tag2' ]) expect(dateIsValid(video.createdAt)).to.be.true diff --git a/server/tests/api/users.ts b/server/tests/api/users.ts index 33646e84f..5066e73fc 100644 --- a/server/tests/api/users.ts +++ b/server/tests/api/users.ts @@ -117,7 +117,7 @@ describe('Test users', function () { const res = await getVideosList(server.url) const video = res.body.data[ 0 ] - expect(video.account) + expect(video.accountName) .to .equal('root') videoId = video.id @@ -487,7 +487,7 @@ describe('Test users', function () { .equal(1) const video = res.body.data[ 0 ] - expect(video.account) + expect(video.accountName) .to .equal('root') }) diff --git a/server/tests/utils/servers.ts b/server/tests/utils/servers.ts index faa2f19ff..8340fbc18 100644 --- a/server/tests/utils/servers.ts +++ b/server/tests/utils/servers.ts @@ -24,7 +24,7 @@ interface ServerInfo { id: number uuid: string name: string - account: string + accountName: string } remoteVideo?: { diff --git a/shared/models/videos/video.model.ts b/shared/models/videos/video.model.ts index 08b29425c..dc12a05d9 100644 --- a/shared/models/videos/video.model.ts +++ b/shared/models/videos/video.model.ts @@ -1,3 +1,4 @@ +import { Account } from '../accounts' import { VideoChannel } from './video-channel.model' import { VideoPrivacy } from './video-privacy.enum' @@ -13,7 +14,7 @@ export interface VideoFile { export interface Video { id: number uuid: string - account: string + accountName: string createdAt: Date | string updatedAt: Date | string categoryLabel: string @@ -43,4 +44,5 @@ export interface VideoDetails extends Video { descriptionPath: string channel: VideoChannel files: VideoFile[] + account: Account } From 6a9e1d42f878c55ac5e2af8a1c98e6fe28a04f36 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 6 Dec 2017 18:04:40 +0100 Subject: [PATCH 23/49] Add likes/dislikes bar --- .../app/shared/video/video-details.model.ts | 5 ++++ .../+video-watch/video-watch.component.html | 10 ++++++-- .../+video-watch/video-watch.component.scss | 23 ++++++++++++++++--- 3 files changed, 33 insertions(+), 5 deletions(-) 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 @@
-
- {{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views +
+
+ {{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} 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 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 { From 41c3dfac99c41d2daec2a55554bb517ed5d59fc4 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 6 Dec 2017 18:10:57 +0100 Subject: [PATCH 24/49] Design other videos in watch video page --- .../+video-watch/video-watch.component.html | 4 ++++ .../+video-watch/video-watch.component.scss | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) 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 @@
+
+ Other videos +
+
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; + } + } } } + From 7b272fd73f1ea67e83c1924f2cc33503b8759811 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 7 Dec 2017 10:02:01 +0100 Subject: [PATCH 25/49] Fix dropdown menu in video watch --- .../account-change-password.component.scss | 2 + .../account-details.component.scss | 1 + .../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 + .../+video-watch/video-watch.component.html | 34 ++----- .../+video-watch/video-watch.component.scss | 88 +++++++++++++------ .../images/{account => global}/edit.svg | 0 client/src/assets/images/video/alert.svg | 16 ++++ .../video/{dislike.svg => dislike-grey.svg} | 0 .../src/assets/images/video/dislike-white.svg | 14 +++ client/src/assets/images/video/download.svg | 16 ++++ client/src/assets/images/video/eye-closed.svg | 18 ++++ .../images/video/{like.svg => like-grey.svg} | 0 client/src/assets/images/video/like-white.svg | 15 ++++ client/src/sass/_mixins.scss | 24 +++-- client/src/sass/_variables.scss | 12 +-- client/src/sass/application.scss | 14 +++ 19 files changed, 192 insertions(+), 71 deletions(-) rename client/src/assets/images/{account => global}/edit.svg (100%) create mode 100644 client/src/assets/images/video/alert.svg rename client/src/assets/images/video/{dislike.svg => dislike-grey.svg} (100%) create mode 100644 client/src/assets/images/video/dislike-white.svg create mode 100644 client/src/assets/images/video/download.svg create mode 100644 client/src/assets/images/video/eye-closed.svg rename client/src/assets/images/video/{like.svg => like-grey.svg} (100%) create mode 100644 client/src/assets/images/video/like-white.svg 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 @@
{{ video.name }}
-
- +
+
-
- +
+
@@ -39,33 +33,21 @@
@@ -78,7 +60,7 @@ {{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} 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 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'); + } + } + + &.activated { + @include orange-button; + + .icon-like { + background-image: url('../../../assets/images/video/like-white.svg'); + } + + .icon-dislike { + background-image: url('../../../assets/images/video/dislike-white.svg'); + } } } .action-more { display: inline-block; - } - .icon { - display: inline-block; - background-repeat: no-repeat; - background-size: contain; - width: 21px; - height: 21px; - vertical-align: middle; - position: relative; - top: -2px; + .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-like { - background-image: url('../../../assets/images/video/like.svg'); - } + &.icon-download { + background-image: url('../../../assets/images/video/download.svg'); + } - &.icon-dislike { - background-image: url('../../../assets/images/video/dislike.svg'); - } + &.icon-alert { + background-image: url('../../../assets/images/video/alert.svg'); + } - &.icon-share { - background-image: url('../../../assets/images/video/share.svg'); - } - - &.icon-more { - background-image: url('../../../assets/images/video/more.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/global/edit.svg similarity index 100% rename from client/src/assets/images/account/edit.svg rename to client/src/assets/images/global/edit.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 @@ + + + + alert + Created with Sketch. + + + + + + + + + + + diff --git a/client/src/assets/images/video/dislike.svg b/client/src/assets/images/video/dislike-grey.svg similarity index 100% rename from client/src/assets/images/video/dislike.svg rename to client/src/assets/images/video/dislike-grey.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 @@ + + + + + + + + + + + + + + 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 @@ + + + + download + Created with Sketch. + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + diff --git a/client/src/assets/images/video/like.svg b/client/src/assets/images/video/like-grey.svg similarity index 100% rename from client/src/assets/images/video/like.svg rename to client/src/assets/images/video/like-grey.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 @@ + + + + thumbs-up + Created with Sketch. + + + + + + + + + + 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; + } +} From 0727cab0dfd3d53e5e9c88bfbda6bc0e090d4f12 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 7 Dec 2017 10:27:33 +0100 Subject: [PATCH 26/49] Design video watch modals --- .../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 ++-- .../forms/form-validators/video-abuse.ts | 6 ++-- .../video-download.component.html | 11 +++---- .../video-download.component.scss | 23 ++++++++++++++ .../+video-watch/video-download.component.ts | 2 +- .../+video-watch/video-report.component.html | 12 ++++---- .../+video-watch/video-share.component.html | 2 +- .../+video-watch/video-watch.component.scss | 10 ++----- .../video/{download.svg => download-grey.svg} | 0 .../assets/images/video/download-white.svg | 16 ++++++++++ client/src/sass/_mixins.scss | 16 ++++++++-- client/src/sass/application.scss | 30 +++++++++++++++++++ 15 files changed, 113 insertions(+), 47 deletions(-) create mode 100644 client/src/app/videos/+video-watch/video-download.component.scss rename client/src/assets/images/video/{download.svg => download-grey.svg} (100%) create mode 100644 client/src/assets/images/video/download-white.svg 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 @@ - +

Download

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 { From ff249f499ccca2e37757f338384e7ba44c906a69 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 7 Dec 2017 11:15:19 +0100 Subject: [PATCH 28/49] Move video form inside a component --- .../app/shared/forms/form-validators/video.ts | 6 ++ .../shared/video-edit.component.html | 85 ++++++++++++++++++ .../shared/video-edit.component.ts | 83 +++++++++++++++++ .../+video-edit/shared/video-edit.module.ts | 7 +- .../+video-edit/video-update.component.html | 88 +------------------ .../+video-edit/video-update.component.ts | 46 ++-------- .../+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 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 @@ +
+
+ + +
+ {{ formErrors.name }} +
+
+ +
+ + + +
+ {{ formErrors.privacy }} +
+
+ +
+ + +
+ +
+ + + +
+ {{ formErrors.category }} +
+
+ +
+ + + +
+ {{ formErrors.licence }} +
+
+ +
+ + + +
+ {{ formErrors.language }} +
+
+ +
+ (press enter to add the tag) + +
+ +
+ + + +
+ {{ formErrors.description }} +
+
+
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 @@

Update {{ video?.name }}

-
{{ error }}
-
-
- - -
- {{ formErrors.name }} -
-
-
- - - -
- {{ formErrors.privacy }} -
-
- -
- - -
- -
- - - -
- {{ formErrors.category }} -
-
- -
- - - -
- {{ formErrors.licence }} -
-
- -
- - - -
- {{ formErrors.language }} -
-
- -
- (press enter to add the tag) - -
- -
- - - -
- {{ formErrors.description }} -
-
+
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()) } From 4cc66133abf1e37873316999b660c93ab92eb4a0 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 7 Dec 2017 14:48:47 +0100 Subject: [PATCH 29/49] Design video update --- .../shared/video-edit.component.html | 134 ++++++++--------- .../shared/video-edit.component.scss | 136 +++++++++++++++--- .../+video-edit/video-update.component.html | 19 ++- .../+video-edit/video-update.component.ts | 13 +- .../+video-watch/video-watch.component.scss | 1 + .../shared/video-description.component.html | 4 +- .../shared/video-description.component.scss | 6 +- client/src/assets/images/global/validate.svg | 14 ++ client/src/sass/_mixins.scss | 22 ++- 9 files changed, 238 insertions(+), 111 deletions(-) create mode 100644 client/src/assets/images/global/validate.svg 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 @@ -
-
- - -
- {{ formErrors.name }} +
+ +
+
+ + +
+ {{ formErrors.name }} +
+
+ +
+ (press enter to add the tag) + +
+ +
+ + + +
+ {{ formErrors.description }} +
-
- - +
+
+ + -
- {{ formErrors.privacy }} +
+ {{ formErrors.category }} +
-
-
- - -
+
+ + -
- - - -
- {{ formErrors.category }} +
+ {{ formErrors.licence }} +
-
-
- - +
+ + -
- {{ formErrors.licence }} +
+ {{ formErrors.language }} +
-
-
- - +
+ + -
- {{ formErrors.language }} +
+ {{ formErrors.privacy }} +
-
-
- (press enter to add the tag) - -
- -
- - - -
- {{ formErrors.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 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 @@ -
-
- -

Update {{ video?.name }}

+
+
+ Update {{ video?.name }} +
@@ -10,12 +10,11 @@ [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" > -
- +
+
+ + +
-
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 @@ 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 @@ + + + + + + + + + + + + + + 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; +} From 27e1a06c331278e5d37bc5172ee7e4fc968e4b5e Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 7 Dec 2017 16:32:06 +0100 Subject: [PATCH 30/49] First step upload with new design --- .../shared/video-edit.component.html | 1 + .../shared/video-edit.component.scss | 35 ----- .../+video-edit/video-add.component.html | 148 +++--------------- .../+video-edit/video-add.component.scss | 60 +++++++ .../videos/+video-edit/video-add.component.ts | 126 ++++++--------- client/src/assets/images/video/upload.svg | 16 ++ 6 files changed, 148 insertions(+), 238 deletions(-) create mode 100644 client/src/app/videos/+video-edit/video-add.component.scss create mode 100644 client/src/assets/images/video/upload.svg 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 @@
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 @@ -
-
+
+
+ Upload your video +
-

Upload a video

+
{{ error }}
-
{{ error }}
+
+
+
-
-
- - -
- {{ formErrors.name }} -
+
+ Select the file to upload +
- - - -
- {{ formErrors.privacy }} -
- - -
- -
- - +
+
-
- {{ formErrors.channelId }} + + + + +
+
+ +
- -
- - - -
- {{ formErrors.category }} -
-
- -
- - - -
- {{ formErrors.licence }} -
-
- -
- - - -
- {{ formErrors.language }} -
-
- -
- (press enter to add the tag) - -
- -
- -
- Select the video... - - -
-
- -
-
- {{ filename }} - -
-
- -
- {{ formErrors.videofile }} -
- -
- - - -
- {{ formErrors.description }} -
-
- -
- - - - Server is processing the video - - -
- -
- -
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 @@ + + + + cloud-upload + Created with Sketch. + + + + + + + + + + + From 8e7f08b5a5e65195ad6dd3d7850fda57021421f3 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 7 Dec 2017 17:03:56 +0100 Subject: [PATCH 31/49] Make some fields optional when uploading a video --- .../migrations/0120-video-null.ts | 46 ++++++++++ server/middlewares/validators/videos.ts | 6 +- server/models/video/video.ts | 16 ++-- server/tests/api/check-params/videos.ts | 24 ----- server/tests/api/single-server.ts | 88 +++++++++++++------ 5 files changed, 120 insertions(+), 60 deletions(-) create mode 100644 server/initializers/migrations/0120-video-null.ts diff --git a/server/initializers/migrations/0120-video-null.ts b/server/initializers/migrations/0120-video-null.ts new file mode 100644 index 000000000..3506a5046 --- /dev/null +++ b/server/initializers/migrations/0120-video-null.ts @@ -0,0 +1,46 @@ +import * as Sequelize from 'sequelize' +import { PeerTubeDatabase } from '../database' + +async function up (utils: { + transaction: Sequelize.Transaction, + queryInterface: Sequelize.QueryInterface, + sequelize: Sequelize.Sequelize, + db: PeerTubeDatabase +}): Promise { + + { + const data = { + type: Sequelize.INTEGER, + allowNull: true, + defaultValue: null + } + await utils.queryInterface.changeColumn('Videos', 'licence', data) + } + + { + const data = { + type: Sequelize.INTEGER, + allowNull: true, + defaultValue: null + } + await utils.queryInterface.changeColumn('Videos', 'category', data) + } + + { + const data = { + type: Sequelize.INTEGER, + allowNull: true, + defaultValue: null + } + await utils.queryInterface.changeColumn('Videos', 'description', data) + } +} + +function down (options) { + throw new Error('Not implemented.') +} + +export { + up, + down +} diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts index ee2ac50c8..10625e41d 100644 --- a/server/middlewares/validators/videos.ts +++ b/server/middlewares/validators/videos.ts @@ -31,11 +31,11 @@ const videosAddValidator = [ + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ') ), body('name').custom(isVideoNameValid).withMessage('Should have a valid name'), - body('category').custom(isVideoCategoryValid).withMessage('Should have a valid category'), - body('licence').custom(isVideoLicenceValid).withMessage('Should have a valid licence'), + body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'), + body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'), body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'), body('nsfw').custom(isVideoNSFWValid).withMessage('Should have a valid NSFW attribute'), - body('description').custom(isVideoDescriptionValid).withMessage('Should have a valid description'), + body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'), body('channelId').custom(isIdValid).withMessage('Should have correct video channel id'), body('privacy').custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'), body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'), diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 60023bc8c..8b1eb1f96 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -104,7 +104,8 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da }, category: { type: DataTypes.INTEGER, - allowNull: false, + allowNull: true, + defaultValue: null, validate: { categoryValid: value => { const res = isVideoCategoryValid(value) @@ -114,7 +115,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da }, licence: { type: DataTypes.INTEGER, - allowNull: false, + allowNull: true, defaultValue: null, validate: { licenceValid: value => { @@ -126,6 +127,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da language: { type: DataTypes.INTEGER, allowNull: true, + defaultValue: null, validate: { languageValid: value => { const res = isVideoLanguageValid(value) @@ -155,7 +157,8 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da }, description: { type: DataTypes.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max), - allowNull: false, + allowNull: true, + defaultValue: null, validate: { descriptionValid: value => { const res = isVideoDescriptionValid(value) @@ -664,6 +667,8 @@ toActivityPubObject = function (this: VideoInstance) { } getTruncatedDescription = function (this: VideoInstance) { + if (!this.description) return null + const options = { length: CONSTRAINTS_FIELDS.VIDEOS.TRUNCATED_DESCRIPTION.max } @@ -754,8 +759,6 @@ getDescriptionPath = function (this: VideoInstance) { getCategoryLabel = function (this: VideoInstance) { let categoryLabel = VIDEO_CATEGORIES[this.category] - - // Maybe our server is not up to date and there are new categories since our version if (!categoryLabel) categoryLabel = 'Misc' return categoryLabel @@ -763,15 +766,12 @@ getCategoryLabel = function (this: VideoInstance) { getLicenceLabel = function (this: VideoInstance) { let licenceLabel = VIDEO_LICENCES[this.licence] - - // Maybe our server is not up to date and there are new licences since our version if (!licenceLabel) licenceLabel = 'Unknown' return licenceLabel } getLanguageLabel = function (this: VideoInstance) { - // Language is an optional attribute let languageLabel = VIDEO_LANGUAGES[this.language] if (!languageLabel) languageLabel = 'Unknown' diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts index 2962f5640..00a209665 100644 --- a/server/tests/api/check-params/videos.ts +++ b/server/tests/api/check-params/videos.ts @@ -189,14 +189,6 @@ describe('Test videos API validator', function () { await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) }) - it('Should fail without a category', async function () { - const fields = getCompleteVideoUploadAttributes() - delete fields.category - - const attaches = getVideoUploadAttaches - await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) - }) - it('Should fail with a bad category', async function () { const fields = getCompleteVideoUploadAttributes() fields.category = 125 @@ -205,14 +197,6 @@ describe('Test videos API validator', function () { await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) }) - it('Should fail without a licence', async function () { - const fields = getCompleteVideoUploadAttributes() - delete fields.licence - - const attaches = getVideoUploadAttaches() - await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) - }) - it('Should fail with a bad licence', async function () { const fields = getCompleteVideoUploadAttributes() fields.licence = 125 @@ -245,14 +229,6 @@ describe('Test videos API validator', function () { await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) }) - it('Should fail without description', async function () { - const fields = getCompleteVideoUploadAttributes() - delete fields.description - - const attaches = getVideoUploadAttaches() - await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) - }) - it('Should fail with a long description', async function () { const fields = getCompleteVideoUploadAttributes() fields.description = 'my super description which is very very very very very very very very very very very very long'.repeat(35) diff --git a/server/tests/api/single-server.ts b/server/tests/api/single-server.ts index d7e9ad41f..fbb2dd1fb 100644 --- a/server/tests/api/single-server.ts +++ b/server/tests/api/single-server.ts @@ -1,40 +1,41 @@ /* tslint:disable:no-unused-expression */ -import { keyBy } from 'lodash' -import { join } from 'path' -import 'mocha' import * as chai from 'chai' -const expect = chai.expect - +import { keyBy } from 'lodash' +import 'mocha' +import { join } from 'path' +import * as request from 'supertest' import { - ServerInfo, - flushTests, - runServer, - uploadVideo, - getVideosList, - rateVideo, - removeVideo, - wait, - setAccessTokensToServers, - searchVideo, - killallServers, dateIsValid, - getVideoCategories, - getVideoLicences, - getVideoLanguages, - getVideoPrivacies, - testVideoImage, - webtorrentAdd, + flushTests, getVideo, - readdirPromise, + getVideoCategories, + getVideoLanguages, + getVideoLicences, + getVideoPrivacies, + getVideosList, getVideosListPagination, - searchVideoWithPagination, getVideosListSort, + killallServers, + rateVideo, + readdirPromise, + removeVideo, + runServer, + searchVideo, + searchVideoWithPagination, searchVideoWithSort, - updateVideo + ServerInfo, + setAccessTokensToServers, + testVideoImage, + updateVideo, + uploadVideo, + wait, + webtorrentAdd } from '../utils' import { viewVideo } from '../utils/videos' +const expect = chai.expect + describe('Test a single server', function () { let server: ServerInfo = null let videoId = -1 @@ -693,6 +694,43 @@ describe('Test a single server', function () { expect(video.dislikes).to.equal(1) }) + it('Should upload a video with minimum parameters', async function () { + const path = '/api/v1/videos/upload' + + const req = request(server.url) + .post(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + server.accessToken) + .field('name', 'minimum parameters') + .field('privacy', '1') + .field('nsfw', 'false') + .field('channelId', '1') + + const filePath = join(__dirname, '..', 'api', 'fixtures', 'video_short.webm') + + await req.attach('videofile', filePath) + .expect(204) + + const res = await getVideosList(server.url) + const video = res.body.data.find(v => v.name === 'minimum parameters') + + expect(video.name).to.equal('minimum parameters') + expect(video.category).to.equal(null) + expect(video.categoryLabel).to.equal('Misc') + expect(video.licence).to.equal(null) + expect(video.licenceLabel).to.equal('Unknown') + expect(video.language).to.equal(null) + expect(video.languageLabel).to.equal('Unknown') + expect(video.nsfw).to.not.be.ok + expect(video.description).to.equal(null) + expect(video.serverHost).to.equal('localhost:9001') + expect(video.accountName).to.equal('root') + expect(video.isLocal).to.be.true + expect(video.tags).to.deep.equal([ ]) + expect(dateIsValid(video.createdAt)).to.be.true + expect(dateIsValid(video.updatedAt)).to.be.true + }) + after(async function () { killallServers([ server ]) From baeefe22caf8ae6cb58dc40754e5db14b50168bf Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 7 Dec 2017 17:22:44 +0100 Subject: [PATCH 32/49] First upload step is ok --- client/src/app/core/server/server.service.ts | 37 ++++++++----- .../+video-edit/video-add.component.html | 34 ++++++------ .../videos/+video-edit/video-add.component.ts | 52 ++++++++----------- server/initializers/constants.ts | 2 +- .../migrations/0120-video-null.ts | 3 +- shared/models/videos/video-create.model.ts | 10 ++-- 6 files changed, 70 insertions(+), 68 deletions(-) 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(1) + videoCategoriesLoaded = new ReplaySubject(1) + videoLicencesLoaded = new ReplaySubject(1) + videoLanguagesLoaded = new ReplaySubject(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 ) { 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 @@
{{ error }}
-
+
Select the file to upload - +
@@ -26,20 +26,20 @@
- - -
- - -
-
- - -
-
-
+ + +
+ + +
+
+ + +
+
+
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 } }, diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 3e083fd92..7be7a5f95 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -14,7 +14,7 @@ import { FollowState } from '../../shared/models/accounts/follow.model' // --------------------------------------------------------------------------- -const LAST_MIGRATION_VERSION = 115 +const LAST_MIGRATION_VERSION = 120 // --------------------------------------------------------------------------- diff --git a/server/initializers/migrations/0120-video-null.ts b/server/initializers/migrations/0120-video-null.ts index 3506a5046..9130d10ee 100644 --- a/server/initializers/migrations/0120-video-null.ts +++ b/server/initializers/migrations/0120-video-null.ts @@ -1,4 +1,5 @@ import * as Sequelize from 'sequelize' +import { CONSTRAINTS_FIELDS } from '../constants' import { PeerTubeDatabase } from '../database' async function up (utils: { @@ -28,7 +29,7 @@ async function up (utils: { { const data = { - type: Sequelize.INTEGER, + type: Sequelize.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max), allowNull: true, defaultValue: null } diff --git a/shared/models/videos/video-create.model.ts b/shared/models/videos/video-create.model.ts index e537c38a8..8bc6a6639 100644 --- a/shared/models/videos/video-create.model.ts +++ b/shared/models/videos/video-create.model.ts @@ -1,13 +1,13 @@ import { VideoPrivacy } from './video-privacy.enum' export interface VideoCreate { - category: number - licence: number - language: number - description: string + category?: number + licence?: number + language?: number + description?: string channelId: number nsfw: boolean name: string - tags: string[] + tags?: string[] privacy: VideoPrivacy } From c182778e26b8478fae9d7dd0bf0687baf7b72fd1 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 7 Dec 2017 17:56:59 +0100 Subject: [PATCH 33/49] Add progress bar for video upload --- client/.bootstraprc | 10 ++++---- client/src/app/shared/shared.module.ts | 3 --- .../+video-edit/video-add.component.html | 2 ++ .../+video-edit/video-add.component.scss | 23 +++++++++++++++++++ .../videos/+video-edit/video-add.component.ts | 8 +++---- .../videos/+video-edit/video-add.module.ts | 4 +++- 6 files changed, 37 insertions(+), 13 deletions(-) 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 @@
+ +
{ 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: [ From cadb46d832724ea1a17b085b992142aa32e212be Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 8 Dec 2017 08:39:15 +0100 Subject: [PATCH 34/49] 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 ++ .../app/shared/forms/form-validators/video.ts | 22 +++------- .../src/app/shared/video/video-edit.model.ts | 26 ++++++------ client/src/app/shared/video/video.service.ts | 11 +++-- .../shared/video-description.component.html | 0 .../shared/video-description.component.scss | 41 +++++++++++++++++++ .../shared/video-description.component.ts | 8 ++-- .../shared/video-edit.component.scss | 9 ++++ .../+video-edit/shared/video-edit.module.ts | 3 +- .../+video-edit/video-add.component.html | 16 +++++--- .../+video-edit/video-add.component.scss | 19 +++++++-- .../videos/+video-edit/video-add.component.ts | 30 +++++++++----- .../+video-edit/video-update.component.html | 4 +- .../+video-watch/video-watch.component.html | 2 +- .../+video-watch/video-watch.component.ts | 5 +++ client/src/app/videos/shared/index.ts | 1 - .../shared/video-description.component.scss | 19 --------- client/src/sass/_mixins.scss | 6 ++- client/src/sass/application.scss | 4 ++ server/controllers/api/videos/index.ts | 28 +++++++------ server/tests/api/single-server.ts | 5 ++- server/tests/utils/videos.ts | 2 +- 24 files changed, 173 insertions(+), 98 deletions(-) rename client/src/app/videos/{ => +video-edit}/shared/video-description.component.html (100%) create mode 100644 client/src/app/videos/+video-edit/shared/video-description.component.scss rename client/src/app/videos/{ => +video-edit}/shared/video-description.component.ts (96%) delete mode 100644 client/src/app/videos/shared/video-description.component.scss 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 ) { 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/shared/video-description.component.html b/client/src/app/videos/+video-edit/shared/video-description.component.html similarity index 100% rename from client/src/app/videos/shared/video-description.component.html rename to client/src/app/videos/+video-edit/shared/video-description.component.html 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/shared/video-description.component.ts b/client/src/app/videos/+video-edit/shared/video-description.component.ts similarity index 96% rename from client/src/app/videos/shared/video-description.component.ts rename to client/src/app/videos/+video-edit/shared/video-description.component.ts index d9ffb7800..8dfb74b2a 100644 --- a/client/src/app/videos/shared/video-description.component.ts +++ b/client/src/app/videos/+video-edit/shared/video-description.component.ts @@ -1,12 +1,10 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core' import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' -import { Subject } from 'rxjs/Subject' +import { truncate } from 'lodash' import 'rxjs/add/operator/debounceTime' import 'rxjs/add/operator/distinctUntilChanged' - -import { truncate } from 'lodash' - -import { MarkdownService } from './markdown.service' +import { Subject } from 'rxjs/Subject' +import { MarkdownService } from '../../shared' @Component({ selector: 'my-video-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 @@
-
-
- + @@ -37,10 +40,13 @@ [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" > +
-
+
Publish will be available when upload is finished
+ +
- +
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 @@ >
-
+
- +
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 @@
-
+
Show more 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.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/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 diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 2b70d535e..f427a25c0 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts @@ -15,6 +15,7 @@ import { getServerAccount } from '../../../helpers/utils' import { CONFIG, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT, VIDEO_PRIVACIES } from '../../../initializers' import { database as db } from '../../../initializers/database' import { sendAddVideo } from '../../../lib/activitypub/send/send-add' +import { sendCreateViewToOrigin } from '../../../lib/activitypub/send/send-create' import { sendUpdateVideo } from '../../../lib/activitypub/send/send-update' import { shareVideoByServer } from '../../../lib/activitypub/share' import { getVideoActivityPubUrl } from '../../../lib/activitypub/url' @@ -39,7 +40,6 @@ import { abuseVideoRouter } from './abuse' import { blacklistRouter } from './blacklist' import { videoChannelRouter } from './channel' import { rateVideoRouter } from './rate' -import { sendCreateViewToOrigin } from '../../../lib/activitypub/send/send-create' const videosRouter = express.Router() @@ -154,17 +154,20 @@ async function addVideoRetryWrapper (req: express.Request, res: express.Response errorMessage: 'Cannot insert the video with many retries.' } - await retryTransactionWrapper(addVideo, options) + const video = await retryTransactionWrapper(addVideo, options) - // TODO : include Location of the new video -> 201 - res.type('json').status(204).end() + res.json({ + video: { + id: video.id, + uuid: video.uuid + } + }).end() } -async function addVideo (req: express.Request, res: express.Response, videoPhysicalFile: Express.Multer.File) { +function addVideo (req: express.Request, res: express.Response, videoPhysicalFile: Express.Multer.File) { const videoInfo: VideoCreate = req.body - let videoUUID = '' - await db.sequelize.transaction(async t => { + return db.sequelize.transaction(async t => { const sequelizeOptions = { transaction: t } const videoData = { @@ -223,7 +226,6 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi const videoCreated = await video.save(sequelizeOptions) // Do not forget to add video channel information to the created video videoCreated.VideoChannel = res.locals.videoChannel - videoUUID = videoCreated.uuid videoFile.videoId = video.id @@ -238,15 +240,17 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi } // Let transcoding job send the video to friends because the video file extension might change - if (CONFIG.TRANSCODING.ENABLED === true) return undefined + if (CONFIG.TRANSCODING.ENABLED === true) return videoCreated // Don't send video to remote servers, it is private - if (video.privacy === VideoPrivacy.PRIVATE) return undefined + if (video.privacy === VideoPrivacy.PRIVATE) return videoCreated await sendAddVideo(video, t) await shareVideoByServer(video, t) - }) - logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoUUID) + logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid) + + return videoCreated + }) } async function updateVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { diff --git a/server/tests/api/single-server.ts b/server/tests/api/single-server.ts index fbb2dd1fb..ed79f9e1c 100644 --- a/server/tests/api/single-server.ts +++ b/server/tests/api/single-server.ts @@ -104,7 +104,10 @@ describe('Test a single server', function () { licence: 6, tags: [ 'tag1', 'tag2', 'tag3' ] } - await uploadVideo(server.url, server.accessToken, videoAttributes) + const res = await uploadVideo(server.url, server.accessToken, videoAttributes) + expect(res.body.video).to.not.be.undefined + expect(res.body.video.id).to.equal(1) + expect(res.body.video.uuid).to.have.length.above(5) }) it('Should seed the uploaded video', async function () { diff --git a/server/tests/utils/videos.ts b/server/tests/utils/videos.ts index ff7da9bb2..bdf3368ac 100644 --- a/server/tests/utils/videos.ts +++ b/server/tests/utils/videos.ts @@ -201,7 +201,7 @@ async function testVideoImage (url: string, imageName: string, imagePath: string } } -async function uploadVideo (url: string, accessToken: string, videoAttributesArg: VideoAttributes, specialStatus = 204) { +async function uploadVideo (url: string, accessToken: string, videoAttributesArg: VideoAttributes, specialStatus = 201) { const path = '/api/v1/videos/upload' let defaultChannelId = '1' From e11f68a3562d2468480c396f47f1bdd2a306e17a Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 8 Dec 2017 10:08:36 +0100 Subject: [PATCH 35/49] Optimise transaction for video upload --- server/controllers/api/videos/index.ts | 95 +++++++++++++------------- server/tests/api/single-server.ts | 13 ++-- server/tests/utils/videos.ts | 2 +- 3 files changed, 53 insertions(+), 57 deletions(-) diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index f427a25c0..0f71a7f7f 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts @@ -164,73 +164,72 @@ async function addVideoRetryWrapper (req: express.Request, res: express.Response }).end() } -function addVideo (req: express.Request, res: express.Response, videoPhysicalFile: Express.Multer.File) { +async function addVideo (req: express.Request, res: express.Response, videoPhysicalFile: Express.Multer.File) { const videoInfo: VideoCreate = req.body + // Prepare data so we don't block the transaction + const videoData = { + name: videoInfo.name, + remote: false, + extname: extname(videoPhysicalFile.filename), + category: videoInfo.category, + licence: videoInfo.licence, + language: videoInfo.language, + nsfw: videoInfo.nsfw, + description: videoInfo.description, + privacy: videoInfo.privacy, + duration: videoPhysicalFile['duration'], // duration was added by a previous middleware + channelId: res.locals.videoChannel.id + } + const video = db.Video.build(videoData) + video.url = getVideoActivityPubUrl(video) + + const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename) + const videoFileHeight = await getVideoFileHeight(videoFilePath) + + const videoFileData = { + extname: extname(videoPhysicalFile.filename), + resolution: videoFileHeight, + size: videoPhysicalFile.size + } + const videoFile = db.VideoFile.build(videoFileData) + const videoDir = CONFIG.STORAGE.VIDEOS_DIR + const source = join(videoDir, videoPhysicalFile.filename) + const destination = join(videoDir, video.getVideoFilename(videoFile)) + + await renamePromise(source, destination) + // This is important in case if there is another attempt in the retry process + videoPhysicalFile.filename = video.getVideoFilename(videoFile) + + const tasks = [] + + tasks.push( + video.createTorrentAndSetInfoHash(videoFile), + video.createThumbnail(videoFile), + video.createPreview(videoFile) + ) + await Promise.all(tasks) + return db.sequelize.transaction(async t => { const sequelizeOptions = { transaction: t } - const videoData = { - name: videoInfo.name, - remote: false, - extname: extname(videoPhysicalFile.filename), - category: videoInfo.category, - licence: videoInfo.licence, - language: videoInfo.language, - nsfw: videoInfo.nsfw, - description: videoInfo.description, - privacy: videoInfo.privacy, - duration: videoPhysicalFile['duration'], // duration was added by a previous middleware - channelId: res.locals.videoChannel.id - } - const video = db.Video.build(videoData) - video.url = getVideoActivityPubUrl(video) - - const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename) - const videoFileHeight = await getVideoFileHeight(videoFilePath) - - const videoFileData = { - extname: extname(videoPhysicalFile.filename), - resolution: videoFileHeight, - size: videoPhysicalFile.size - } - const videoFile = db.VideoFile.build(videoFileData) - const videoDir = CONFIG.STORAGE.VIDEOS_DIR - const source = join(videoDir, videoPhysicalFile.filename) - const destination = join(videoDir, video.getVideoFilename(videoFile)) - - await renamePromise(source, destination) - // This is important in case if there is another attempt in the retry process - videoPhysicalFile.filename = video.getVideoFilename(videoFile) - - const tasks = [] - - tasks.push( - video.createTorrentAndSetInfoHash(videoFile), - video.createThumbnail(videoFile), - video.createPreview(videoFile) - ) - if (CONFIG.TRANSCODING.ENABLED === true) { // Put uuid because we don't have id auto incremented for now const dataInput = { videoUUID: video.uuid } - tasks.push( - transcodingJobScheduler.createJob(t, 'videoFileOptimizer', dataInput) - ) + await transcodingJobScheduler.createJob(t, 'videoFileOptimizer', dataInput) } - await Promise.all(tasks) const videoCreated = await video.save(sequelizeOptions) // Do not forget to add video channel information to the created video videoCreated.VideoChannel = res.locals.videoChannel videoFile.videoId = video.id - await videoFile.save(sequelizeOptions) - video.VideoFiles = [videoFile] + + video.VideoFiles = [ videoFile ] if (videoInfo.tags) { const tagInstances = await db.Tag.findOrCreateTags(videoInfo.tags, t) diff --git a/server/tests/api/single-server.ts b/server/tests/api/single-server.ts index ed79f9e1c..e99955ef4 100644 --- a/server/tests/api/single-server.ts +++ b/server/tests/api/single-server.ts @@ -364,7 +364,7 @@ describe('Test a single server', function () { 'video_short1.webm', 'video_short2.webm', 'video_short3.webm' ] - // const tasks: Promise[] = [] + const tasks: Promise[] = [] for (const video of videos) { const videoAttributes = { name: video + ' name', @@ -378,13 +378,10 @@ describe('Test a single server', function () { } const p = uploadVideo(server.url, server.accessToken, videoAttributes) - await p + tasks.push(p) } - // FIXME: concurrent uploads does not work :( - // tasks.push(p) - // } - // - // await Promise.all(tasks) + + await Promise.all(tasks) }) it('Should have the correct durations', async function () { @@ -712,7 +709,7 @@ describe('Test a single server', function () { const filePath = join(__dirname, '..', 'api', 'fixtures', 'video_short.webm') await req.attach('videofile', filePath) - .expect(204) + .expect(200) const res = await getVideosList(server.url) const video = res.body.data.find(v => v.name === 'minimum parameters') diff --git a/server/tests/utils/videos.ts b/server/tests/utils/videos.ts index bdf3368ac..fb758cf29 100644 --- a/server/tests/utils/videos.ts +++ b/server/tests/utils/videos.ts @@ -201,7 +201,7 @@ async function testVideoImage (url: string, imageName: string, imagePath: string } } -async function uploadVideo (url: string, accessToken: string, videoAttributesArg: VideoAttributes, specialStatus = 201) { +async function uploadVideo (url: string, accessToken: string, videoAttributesArg: VideoAttributes, specialStatus = 200) { const path = '/api/v1/videos/upload' let defaultChannelId = '1' From 18327bdf51ff534136d06906ba5c1f228718c371 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 8 Dec 2017 10:15:55 +0100 Subject: [PATCH 36/49] Fix test --- server/tests/api/check-params/videos.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts index 00a209665..0aaa6e7c9 100644 --- a/server/tests/api/check-params/videos.ts +++ b/server/tests/api/check-params/videos.ts @@ -321,7 +321,7 @@ describe('Test videos API validator', function () { token: server.accessToken, fields, attaches, - statusCodeExpected: 204 + statusCodeExpected: 200 }) attaches.videofile = join(__dirname, '..', 'fixtures', 'video_short.mp4') @@ -331,7 +331,7 @@ describe('Test videos API validator', function () { token: server.accessToken, fields, attaches, - statusCodeExpected: 204 + statusCodeExpected: 200 }) attaches.videofile = join(__dirname, '..', 'fixtures', 'video_short.ogv') @@ -341,7 +341,7 @@ describe('Test videos API validator', function () { token: server.accessToken, fields, attaches, - statusCodeExpected: 204 + statusCodeExpected: 200 }) }) }) From 04e0fc488826f505a8de3ce99113f3cb2fcec147 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 8 Dec 2017 10:41:49 +0100 Subject: [PATCH 37/49] 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.component.html | 26 ++-- .../following-add.component.html | 55 ++++--- .../following-list.component.html | 34 ++--- .../app/+admin/follows/follows.component.scss | 18 --- .../users/user-edit/user-edit.component.html | 135 +++++++++--------- .../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 +++++- server/tests/api/users.ts | 4 +- 21 files changed, 297 insertions(+), 302 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 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 @@ + 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: '' + 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 @@ -
-
-

Followers list

+

Followers list

- - - - - - - -
-
+ + + + + + + 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 @@ -
-
+

Add following

-

Add following

+
{{ error }}
-
{{ error }}
+
+
+ - -
- +
+ + + + + +
-
- - - - - -
- -
- It should be a valid host. -
-
- -
- It seems that you are not on a HTTPS server. Your webserver need to have TLS activated in order to follow servers. -
- - - +
+ It should be a valid host. +
-
+ +
+ It seems that you are not on a HTTPS server. Your webserver need to have TLS activated in order to follow servers. +
+ + + 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 @@ -
-
-

Following list

+

Following list

- - - - - - - - - - - -
-
+ + + + + + + + + + + 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 @@ -
-
+
Add user
+
Edit user {{ username }}
-

Add user

-

Edit user {{ username }}

+
{{ error }}
-
{{ error }}
- -
-
- - -
- {{ formErrors.username }} -
-
- -
- - -
- {{ formErrors.email }} -
-
- -
- - -
- {{ formErrors.password }} -
-
- -
- - - -
- {{ formErrors.role }} -
-
- -
- - - -
- Transcoding is enabled on server. The video quota only take in account original video.
- In maximum, this user could use ~ {{ computeQuotaWithTranscoding() | bytes }}. -
-
- - -
+
+
+ + +
+ {{ formErrors.username }} +
-
+ +
+ + +
+ {{ formErrors.email }} +
+
+ +
+ + +
+ {{ formErrors.password }} +
+
+ +
+ + + +
+ {{ formErrors.role }} +
+
+ +
+ + + +
+ Transcoding is enabled on server. The video quota only take in account original video.
+ In maximum, this user could use ~ {{ computeQuotaWithTranscoding() | bytes }}. +
+
+ + + 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 @@ -
-
+
+
Users list
-

Users list

- - - - - - - - - - - - - - - - - - - - - - - - - Add user - -
+ + + Add user +
+ + + + + + + + + + + + + + + + + + + + + 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 @@
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 @@ - - - - - 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 @@ + + + + + + + + + + + + + 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; diff --git a/server/tests/api/users.ts b/server/tests/api/users.ts index 5066e73fc..b3163b1e1 100644 --- a/server/tests/api/users.ts +++ b/server/tests/api/users.ts @@ -113,7 +113,7 @@ describe('Test users', function () { it('Should upload the video with the correct token', async function () { const videoAttributes = {} - await uploadVideo(server.url, accessToken, videoAttributes, 204) + await uploadVideo(server.url, accessToken, videoAttributes) const res = await getVideosList(server.url) const video = res.body.data[ 0 ] @@ -125,7 +125,7 @@ describe('Test users', function () { it('Should upload the video again with the correct token', async function () { const videoAttributes = {} - await uploadVideo(server.url, accessToken, videoAttributes, 204) + await uploadVideo(server.url, accessToken, videoAttributes) }) it('Should retrieve a video rating', async function () { From cd83ea1b908efe594c1e03f886c0dc4742b91360 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 8 Dec 2017 14:34:17 +0100 Subject: [PATCH 38/49] Design admin data tables --- .../jobs/jobs-list/jobs-list.component.html | 36 +++--- .../jobs/jobs-list/jobs-list.component.scss | 3 + .../jobs/jobs-list/jobs-list.component.ts | 24 ++-- .../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.component.html | 12 +- .../account-videos.component.scss | 27 +---- .../src/app/shared/misc/button.component.scss | 27 +++++ .../shared/misc/delete-button.component.html | 4 + .../shared/misc/delete-button.component.ts | 10 ++ .../shared/misc/edit-button.component.html | 4 + .../app/shared/misc/edit-button.component.ts | 11 ++ client/src/app/shared/misc/utils.ts | 5 + client/src/app/shared/shared.module.ts | 6 + .../{account => global}/delete-grey.svg | 0 .../{account => global}/delete-white.svg | 0 client/src/sass/application.scss | 103 +++++++++++++++++- 18 files changed, 216 insertions(+), 89 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 rename client/src/assets/images/{account => global}/delete-grey.svg (100%) rename client/src/assets/images/{account => global}/delete-white.svg (100%) 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 @@ -
-
-

Jobs list

- - - - - - - - - - -
+
+
Jobs list
+ + + + + + + +
{{ job.handlerInputData }}
+
+
+ + + +
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>(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 @@ -
+
Users list
@@ -17,16 +17,10 @@ - - - - - - - - - - + + + + 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 - + Delete @@ -30,15 +30,9 @@
- - - Delete - + - - - Edit - +
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 @@ + + + Delete + 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 @@ + + + Edit + 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/global/delete-grey.svg similarity index 100% rename from client/src/assets/images/account/delete-grey.svg rename to client/src/assets/images/global/delete-grey.svg diff --git a/client/src/assets/images/account/delete-white.svg b/client/src/assets/images/global/delete-white.svg similarity index 100% rename from client/src/assets/images/account/delete-white.svg rename to client/src/assets/images/global/delete-white.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 { - .action-cell { - text-align: center; + font-size: 15px !important; - .glyphicon { - cursor: pointer; + .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; + } + + 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; + } + } + } } } } From e600e1fea275c12f4420e23624804617e61a082c Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 8 Dec 2017 15:22:57 +0100 Subject: [PATCH 39/49] Design follow admin page --- .../followers-list.component.html | 2 - .../followers-list.component.scss | 3 - .../following-add.component.html | 32 +++---- .../following-add.component.scss | 10 +- .../following-add/following-add.component.ts | 94 ++++++------------- .../following-list.component.html | 4 +- .../app/+admin/follows/follows.component.html | 6 +- .../app/+admin/follows/follows.component.scss | 5 +- .../app/+admin/follows/follows.component.ts | 2 +- .../users/user-edit/user-edit.component.html | 2 +- .../users/user-list/user-list.component.html | 2 +- .../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(-) 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 @@ -

Followers list

- Add following -
{{ error }}
-
-
- + +
+ -
- - - - - -
+ -
- It should be a valid host. +
+ {{ hostsError }}
-
- It seems that you are not on a HTTPS server. Your webserver need to have TLS activated in order to follow servers. +
+ It seems that you are not on a HTTPS server. Your webserver needs to have TLS activated in order to follow servers.
- + 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 - } + onHostsChanged () { + this.hostsError = null - displayAddField (index: number) { - return index === (this.hosts.length - 1) - } + const newHostsErrors = [] + const hosts = this.getNotEmptyHosts() - displayRemoveField (index: number) { - return (index !== 0 || this.hosts.length > 1) && index !== (this.hosts.length - 1) - } - - 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:
- ' + notEmptyHosts.join('
- ') + const confirmMessage = 'If you confirm, you will send a follow request to:
- ' + hosts.join('
- ') 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 @@ -

Following list

- - + 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 @@ -
+
+
Manage follows
+ @@ -8,4 +10,6 @@
+ + 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 @@
- + 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 @@ - + 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 { From f595d3947708114deeed4312cc5ffd285745b090 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 8 Dec 2017 17:31:21 +0100 Subject: [PATCH 40/49] Finish admin design --- .../following-list.component.html | 2 +- .../jobs/jobs-list/jobs-list.component.html | 2 +- .../video-abuse-list.component.html | 41 ++++++++--------- .../video-abuse-list.component.scss | 6 +++ .../video-abuse-list.component.ts | 3 +- .../video-blacklist-list.component.html | 2 +- client/src/app/core/auth/auth.service.ts | 20 ++++---- .../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 +++------ server/controllers/api/videos/index.ts | 8 ++-- .../custom-validators/activitypub/videos.ts | 6 +-- server/helpers/custom-validators/videos.ts | 9 ++-- server/lib/activitypub/process/misc.ts | 21 +++++++-- server/models/video/video.ts | 26 +++++++---- server/tests/api/multiple-servers.ts | 46 +++++++++++++++++++ server/tests/api/single-server.ts | 37 --------------- 19 files changed, 163 insertions(+), 114 deletions(-) create mode 100644 client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.scss 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 @@ - + 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 @@ > - +
{{ job.handlerInputData }}
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 @@ -
-
- -

Video abuses list

- - - - - - - - - - {{ videoAbuse.videoId }} - - - - - -
+
+
Video abuses list
+ + + + + + + + + + {{ videoAbuse.videoName }} + + + 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 @@ - + 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(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']) + .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.' - }) - } - - return this.restExtractor.handleError(res) + 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 @@ 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; - } } diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 0f71a7f7f..63de662a7 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts @@ -280,7 +280,7 @@ async function updateVideo (req: express.Request, res: express.Response) { if (videoInfoToUpdate.licence !== undefined) videoInstance.set('licence', videoInfoToUpdate.licence) if (videoInfoToUpdate.language !== undefined) videoInstance.set('language', videoInfoToUpdate.language) if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw) - if (videoInfoToUpdate.privacy !== undefined) videoInstance.set('privacy', videoInfoToUpdate.privacy) + if (videoInfoToUpdate.privacy !== undefined) videoInstance.set('privacy', parseInt(videoInfoToUpdate.privacy.toString(), 10)) if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description) const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) @@ -298,9 +298,9 @@ async function updateVideo (req: express.Request, res: express.Response) { } // Video is not private anymore, send a create action to remote servers - if (wasPrivateVideo === true && videoInstance.privacy !== VideoPrivacy.PRIVATE) { - await sendAddVideo(videoInstance, t) - await shareVideoByServer(videoInstance, t) + if (wasPrivateVideo === true && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE) { + await sendAddVideo(videoInstanceUpdated, t) + await shareVideoByServer(videoInstanceUpdated, t) } }) diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts index 12c672fd2..2ed2988f5 100644 --- a/server/helpers/custom-validators/activitypub/videos.ts +++ b/server/helpers/custom-validators/activitypub/videos.ts @@ -49,14 +49,14 @@ function isVideoTorrentObjectValid (video: any) { isActivityPubVideoDurationValid(video.duration) && isUUIDValid(video.uuid) && setValidRemoteTags(video) && - isRemoteIdentifierValid(video.category) && - isRemoteIdentifierValid(video.licence) && + (!video.category || isRemoteIdentifierValid(video.category)) && + (!video.licence || isRemoteIdentifierValid(video.licence)) && (!video.language || isRemoteIdentifierValid(video.language)) && isVideoViewsValid(video.views) && isVideoNSFWValid(video.nsfw) && isDateValid(video.published) && isDateValid(video.updated) && - isRemoteVideoContentValid(video.mediaType, video.content) && + (!video.content || isRemoteVideoContentValid(video.mediaType, video.content)) && isRemoteVideoIconValid(video.icon) && setValidRemoteVideoUrls(video) && video.url.length !== 0 diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index f13178c54..4fc460699 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts @@ -9,16 +9,17 @@ import { VIDEO_PRIVACIES } from '../../initializers/constants' import { database as db } from '../../initializers/database' import { VideoInstance } from '../../models/video/video-interface' import { exists, isArray } from './misc' +import isInt = require('validator/lib/isInt') const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES function isVideoCategoryValid (value: number) { - return VIDEO_CATEGORIES[value] !== undefined + return value === null || VIDEO_CATEGORIES[value] !== undefined } function isVideoLicenceValid (value: number) { - return VIDEO_LICENCES[value] !== undefined + return value === null || VIDEO_LICENCES[value] !== undefined } function isVideoLanguageValid (value: number) { @@ -38,7 +39,7 @@ function isVideoTruncatedDescriptionValid (value: string) { } function isVideoDescriptionValid (value: string) { - return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION) + return value === null || (exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION)) } function isVideoNameValid (value: string) { @@ -84,7 +85,7 @@ function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } | } function isVideoPrivacyValid (value: string) { - return VIDEO_PRIVACIES[value] !== undefined + return validator.isInt(value + '') && VIDEO_PRIVACIES[value] !== undefined } function isVideoFileInfoHashValid (value: string) { diff --git a/server/lib/activitypub/process/misc.ts b/server/lib/activitypub/process/misc.ts index f20e588ab..0baa22c26 100644 --- a/server/lib/activitypub/process/misc.ts +++ b/server/lib/activitypub/process/misc.ts @@ -41,15 +41,30 @@ async function videoActivityObjectToDBAttributes ( language = parseInt(videoObject.language.identifier, 10) } + let category = null + if (videoObject.category) { + category = parseInt(videoObject.category.identifier, 10) + } + + let licence = null + if (videoObject.licence) { + licence = parseInt(videoObject.licence.identifier, 10) + } + + let description = null + if (videoObject.content) { + description = videoObject.content + } + const videoData: VideoAttributes = { name: videoObject.name, uuid: videoObject.uuid, url: videoObject.id, - category: parseInt(videoObject.category.identifier, 10), - licence: parseInt(videoObject.licence.identifier, 10), + category, + licence, language, + description, nsfw: videoObject.nsfw, - description: videoObject.content, channelId: videoChannel.id, duration: parseInt(duration, 10), createdAt: new Date(videoObject.published), diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 8b1eb1f96..d46fdeebe 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -564,6 +564,22 @@ toActivityPubObject = function (this: VideoInstance) { } } + let category + if (this.category) { + category = { + identifier: this.category + '', + name: this.getCategoryLabel() + } + } + + let licence + if (this.licence) { + licence = { + identifier: this.licence + '', + name: this.getLicenceLabel() + } + } + let likesObject let dislikesObject @@ -635,14 +651,8 @@ toActivityPubObject = function (this: VideoInstance) { duration: 'PT' + this.duration + 'S', uuid: this.uuid, tag, - category: { - identifier: this.category + '', - name: this.getCategoryLabel() - }, - licence: { - identifier: this.licence + '', - name: this.getLicenceLabel() - }, + category, + licence, language, views: this.views, nsfw: this.nsfw, diff --git a/server/tests/api/multiple-servers.ts b/server/tests/api/multiple-servers.ts index c7f19f261..c9f74cc8c 100644 --- a/server/tests/api/multiple-servers.ts +++ b/server/tests/api/multiple-servers.ts @@ -2,6 +2,8 @@ import 'mocha' import * as chai from 'chai' +import { join } from "path" +import * as request from 'supertest' import { dateIsValid, @@ -707,6 +709,50 @@ describe('Test multiple servers', function () { }) }) + describe('With minimum parameters', function () { + it('Should upload and propagate the video', async function () { + this.timeout(50000) + + const path = '/api/v1/videos/upload' + + const req = request(servers[1].url) + .post(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + servers[1].accessToken) + .field('name', 'minimum parameters') + .field('privacy', '1') + .field('nsfw', 'false') + .field('channelId', '1') + + const filePath = join(__dirname, '..', 'api', 'fixtures', 'video_short.webm') + + await req.attach('videofile', filePath) + .expect(200) + + await wait(25000) + + for (const server of servers) { + const res = await getVideosList(server.url) + const video = res.body.data.find(v => v.name === 'minimum parameters') + + expect(video.name).to.equal('minimum parameters') + expect(video.category).to.equal(null) + expect(video.categoryLabel).to.equal('Misc') + expect(video.licence).to.equal(null) + expect(video.licenceLabel).to.equal('Unknown') + expect(video.language).to.equal(null) + expect(video.languageLabel).to.equal('Unknown') + expect(video.nsfw).to.not.be.ok + expect(video.description).to.equal(null) + expect(video.serverHost).to.equal('localhost:9002') + expect(video.accountName).to.equal('root') + expect(video.tags).to.deep.equal([ ]) + expect(dateIsValid(video.createdAt)).to.be.true + expect(dateIsValid(video.updatedAt)).to.be.true + } + }) + }) + after(async function () { killallServers(servers) diff --git a/server/tests/api/single-server.ts b/server/tests/api/single-server.ts index e99955ef4..91460c7ae 100644 --- a/server/tests/api/single-server.ts +++ b/server/tests/api/single-server.ts @@ -694,43 +694,6 @@ describe('Test a single server', function () { expect(video.dislikes).to.equal(1) }) - it('Should upload a video with minimum parameters', async function () { - const path = '/api/v1/videos/upload' - - const req = request(server.url) - .post(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + server.accessToken) - .field('name', 'minimum parameters') - .field('privacy', '1') - .field('nsfw', 'false') - .field('channelId', '1') - - const filePath = join(__dirname, '..', 'api', 'fixtures', 'video_short.webm') - - await req.attach('videofile', filePath) - .expect(200) - - const res = await getVideosList(server.url) - const video = res.body.data.find(v => v.name === 'minimum parameters') - - expect(video.name).to.equal('minimum parameters') - expect(video.category).to.equal(null) - expect(video.categoryLabel).to.equal('Misc') - expect(video.licence).to.equal(null) - expect(video.licenceLabel).to.equal('Unknown') - expect(video.language).to.equal(null) - expect(video.languageLabel).to.equal('Unknown') - expect(video.nsfw).to.not.be.ok - expect(video.description).to.equal(null) - expect(video.serverHost).to.equal('localhost:9001') - expect(video.accountName).to.equal('root') - expect(video.isLocal).to.be.true - expect(video.tags).to.deep.equal([ ]) - expect(dateIsValid(video.createdAt)).to.be.true - expect(dateIsValid(video.updatedAt)).to.be.true - }) - after(async function () { killallServers([ server ]) From b1a134ee525bd23d7e359573eca24f133974de84 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 8 Dec 2017 17:40:54 +0100 Subject: [PATCH 41/49] Fix lint --- server/helpers/custom-validators/videos.ts | 1 - server/tests/api/multiple-servers.ts | 2 +- server/tests/api/single-server.ts | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index 4fc460699..37fa8b08a 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts @@ -9,7 +9,6 @@ import { VIDEO_PRIVACIES } from '../../initializers/constants' import { database as db } from '../../initializers/database' import { VideoInstance } from '../../models/video/video-interface' import { exists, isArray } from './misc' -import isInt = require('validator/lib/isInt') const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES diff --git a/server/tests/api/multiple-servers.ts b/server/tests/api/multiple-servers.ts index c9f74cc8c..2f17f017a 100644 --- a/server/tests/api/multiple-servers.ts +++ b/server/tests/api/multiple-servers.ts @@ -2,7 +2,7 @@ import 'mocha' import * as chai from 'chai' -import { join } from "path" +import { join } from 'path' import * as request from 'supertest' import { diff --git a/server/tests/api/single-server.ts b/server/tests/api/single-server.ts index 91460c7ae..174fb480d 100644 --- a/server/tests/api/single-server.ts +++ b/server/tests/api/single-server.ts @@ -4,7 +4,6 @@ import * as chai from 'chai' import { keyBy } from 'lodash' import 'mocha' import { join } from 'path' -import * as request from 'supertest' import { dateIsValid, flushTests, From 3daf400219fe8cc94025886ba14876bc59a45244 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 11 Dec 2017 08:50:43 +0100 Subject: [PATCH 42/49] 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 +++++++++++++++++++ .../app/shared/video/abstract-video-list.html | 1 + .../app/shared/video/abstract-video-list.scss | 7 +++++++ client/src/sass/application.scss | 19 +++++++++++------ 7 files changed, 47 insertions(+), 12 deletions(-) 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 @@
-
- - +
+
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 @@ - Upload + Upload 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 @@
Date: Mon, 11 Dec 2017 09:08:15 +0100 Subject: [PATCH 43/49] Videos watch responsive --- .../+video-watch/video-watch.component.html | 2 +- .../+video-watch/video-watch.component.scss | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) 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 @@
- +
Video not found :'(
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; + } + } + } + } +} From a86309b4af9ad8391822f628d74fc5c7d1a01974 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 11 Dec 2017 09:13:01 +0100 Subject: [PATCH 44/49] Responsive player --- .../src/assets/player/peertube-videojs-plugin.ts | 4 ++-- client/src/sass/video-js-custom.scss | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) 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; } } } + From 9b7d1c723d7c11572d91d606954997e413f56a1f Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 11 Dec 2017 09:39:40 +0100 Subject: [PATCH 45/49] Responsive my account --- .../account-videos.component.html | 5 +- .../account-videos.component.scss | 20 ++++++ client/src/app/menu/menu.component.scss | 62 +++++++++++++++++-- .../+video-watch/video-watch.component.scss | 2 + client/src/sass/application.scss | 19 +++--- 5 files changed, 94 insertions(+), 14 deletions(-) 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 @@
- +
- +
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; } } } From 20206dfb0bfe1537912ae0a5b99f2fa40c881d33 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 11 Dec 2017 10:02:17 +0100 Subject: [PATCH 46/49] Fix loading spinner in player --- client/src/app/header/header.component.scss | 2 +- .../+video-watch/video-watch.component.html | 258 +++++++++--------- .../+video-watch/video-watch.component.scss | 8 +- client/src/sass/application.scss | 58 ++-- client/src/sass/video-js-custom.scss | 14 +- 5 files changed, 166 insertions(+), 174 deletions(-) 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,149 +5,149 @@
Video not found :'(
-
- -
-
-
-
{{ video.name }}
+ +
+
+
+
{{ video.name }}
-
-
- -
- -
- -
- -
- - Share -
- -
-
- +
+
+
- +
+
+ +
+ + +
+ +
+
+ {{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views +
+ +
+ +
+
+ +
+ {{ video.channel.name }} + +
+ +
+ By {{ video.by }} + Account avatar +
+ +
+
+ +
+ Show more + + +
+ +
+ Show less + +
+
+ +
+
+ + Privacy + + + {{ video.privacyLabel }} + +
+ +
+ + Category + + + {{ video.categoryLabel }} + +
+ +
+ + Licence + + + {{ video.licenceLabel }} + +
+ +
+ + Language + + + {{ video.languageLabel }} + +
+ +
+ + Tags + + + + {{ getVideoTags() }} + +
+
+
-
-
- {{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views +
+
+ Other videos
-
- +
+
- -
- {{ video.channel.name }} - -
- -
- By {{ video.by }} - Account avatar -
- -
-
- -
- Show more - - -
- -
- Show less - -
-
- -
-
- - Privacy - - - {{ video.privacyLabel }} - -
- -
- - Category - - - {{ video.categoryLabel }} - -
- -
- - Licence - - - {{ video.licenceLabel }} - -
- -
- - Language - - - {{ video.languageLabel }} - -
- -
- - Tags - - - - {{ getVideoTags() }} - -
-
- -
- -
-
- Other videos -
- -
- -
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; From 9e6b41cc1d825182bcfdf51364fdf3eae8001244 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 11 Dec 2017 10:26:29 +0100 Subject: [PATCH 47/49] 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(-) 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 @@ -