From a685e25ca05f08ad1b3f7fbaccc8744727bd8d27 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 9 Oct 2017 14:28:44 +0200 Subject: Try to optimize frontend --- client/.bootstraprc | 12 +- client/config/webpack.common.js | 37 +-- client/config/webpack.prod.js | 16 +- client/package.json | 27 +- client/src/app/app-routing.module.ts | 7 +- client/src/app/app.component.ts | 7 +- client/src/app/core/index.ts | 1 + client/src/app/core/routing/index.ts | 1 + .../core/routing/preload-selected-modules-list.ts | 16 ++ client/src/app/videos/+video-edit/index.ts | 2 + .../videos/+video-edit/video-add-routing.module.ts | 20 ++ .../videos/+video-edit/video-add.component.html | 121 +++++++++ .../app/videos/+video-edit/video-add.component.ts | 165 ++++++++++++ .../src/app/videos/+video-edit/video-add.module.ts | 30 +++ .../videos/+video-edit/video-edit.component.scss | 56 ++++ .../+video-edit/video-update-routing.module.ts | 20 ++ .../videos/+video-edit/video-update.component.html | 92 +++++++ .../videos/+video-edit/video-update.component.ts | 134 +++++++++ .../app/videos/+video-edit/video-update.module.ts | 30 +++ client/src/app/videos/+video-watch/index.ts | 1 + .../+video-watch/video-magnet.component.html | 20 ++ .../videos/+video-watch/video-magnet.component.ts | 27 ++ .../+video-watch/video-report.component.html | 38 +++ .../videos/+video-watch/video-report.component.ts | 69 +++++ .../videos/+video-watch/video-share.component.html | 29 ++ .../videos/+video-watch/video-share.component.ts | 42 +++ .../+video-watch/video-watch-routing.module.ts | 20 ++ .../videos/+video-watch/video-watch.component.html | 184 +++++++++++++ .../videos/+video-watch/video-watch.component.scss | 245 +++++++++++++++++ .../videos/+video-watch/video-watch.component.ts | 299 +++++++++++++++++++++ .../app/videos/+video-watch/video-watch.module.ts | 34 +++ client/src/app/videos/index.ts | 6 - client/src/app/videos/video-edit/index.ts | 2 - .../app/videos/video-edit/video-add.component.html | 121 --------- .../app/videos/video-edit/video-add.component.ts | 165 ------------ .../videos/video-edit/video-edit.component.scss | 56 ---- .../videos/video-edit/video-update.component.html | 92 ------- .../videos/video-edit/video-update.component.ts | 134 --------- client/src/app/videos/video-watch/index.ts | 4 - .../videos/video-watch/video-magnet.component.html | 20 -- .../videos/video-watch/video-magnet.component.ts | 27 -- .../videos/video-watch/video-report.component.html | 38 --- .../videos/video-watch/video-report.component.ts | 69 ----- .../videos/video-watch/video-share.component.html | 29 -- .../videos/video-watch/video-share.component.ts | 42 --- .../videos/video-watch/video-watch.component.html | 184 ------------- .../videos/video-watch/video-watch.component.scss | 245 ----------------- .../videos/video-watch/video-watch.component.ts | 299 --------------------- client/src/app/videos/videos-routing.module.ts | 11 +- client/src/app/videos/videos.component.ts | 12 +- client/src/app/videos/videos.module.ts | 19 -- client/yarn.lock | 266 +++++++++--------- 52 files changed, 1897 insertions(+), 1746 deletions(-) create mode 100644 client/src/app/core/routing/index.ts create mode 100644 client/src/app/core/routing/preload-selected-modules-list.ts create mode 100644 client/src/app/videos/+video-edit/index.ts create mode 100644 client/src/app/videos/+video-edit/video-add-routing.module.ts create mode 100644 client/src/app/videos/+video-edit/video-add.component.html create mode 100644 client/src/app/videos/+video-edit/video-add.component.ts create mode 100644 client/src/app/videos/+video-edit/video-add.module.ts create mode 100644 client/src/app/videos/+video-edit/video-edit.component.scss create mode 100644 client/src/app/videos/+video-edit/video-update-routing.module.ts create mode 100644 client/src/app/videos/+video-edit/video-update.component.html create mode 100644 client/src/app/videos/+video-edit/video-update.component.ts create mode 100644 client/src/app/videos/+video-edit/video-update.module.ts create mode 100644 client/src/app/videos/+video-watch/index.ts create mode 100644 client/src/app/videos/+video-watch/video-magnet.component.html create mode 100644 client/src/app/videos/+video-watch/video-magnet.component.ts create mode 100644 client/src/app/videos/+video-watch/video-report.component.html create mode 100644 client/src/app/videos/+video-watch/video-report.component.ts create mode 100644 client/src/app/videos/+video-watch/video-share.component.html create mode 100644 client/src/app/videos/+video-watch/video-share.component.ts create mode 100644 client/src/app/videos/+video-watch/video-watch-routing.module.ts create mode 100644 client/src/app/videos/+video-watch/video-watch.component.html create mode 100644 client/src/app/videos/+video-watch/video-watch.component.scss create mode 100644 client/src/app/videos/+video-watch/video-watch.component.ts create mode 100644 client/src/app/videos/+video-watch/video-watch.module.ts delete mode 100644 client/src/app/videos/video-edit/index.ts delete mode 100644 client/src/app/videos/video-edit/video-add.component.html delete mode 100644 client/src/app/videos/video-edit/video-add.component.ts delete mode 100644 client/src/app/videos/video-edit/video-edit.component.scss delete mode 100644 client/src/app/videos/video-edit/video-update.component.html delete mode 100644 client/src/app/videos/video-edit/video-update.component.ts delete mode 100644 client/src/app/videos/video-watch/index.ts delete mode 100644 client/src/app/videos/video-watch/video-magnet.component.html delete mode 100644 client/src/app/videos/video-watch/video-magnet.component.ts delete mode 100644 client/src/app/videos/video-watch/video-report.component.html delete mode 100644 client/src/app/videos/video-watch/video-report.component.ts delete mode 100644 client/src/app/videos/video-watch/video-share.component.html delete mode 100644 client/src/app/videos/video-watch/video-share.component.ts delete mode 100644 client/src/app/videos/video-watch/video-watch.component.html delete mode 100644 client/src/app/videos/video-watch/video-watch.component.scss delete mode 100644 client/src/app/videos/video-watch/video-watch.component.ts diff --git a/client/.bootstraprc b/client/.bootstraprc index d266656c1..e560cb5fb 100644 --- a/client/.bootstraprc +++ b/client/.bootstraprc @@ -19,12 +19,12 @@ styleLoaders: # It depends on value of NODE_ENV environment variable # This param can also be set in webpack config: # entry: 'bootstrap-loader/extractStyles' -extractStyles: false -# env: -# development: -# extractStyles: false -# production: -# extractStyles: true +# extractStyles: false +env: + development: + extractStyles: false + production: + extractStyles: true # Customize Bootstrap variables that get imported before the original Bootstrap variables. # Thus original Bootstrap variables can depend on values from here. All the bootstrap diff --git a/client/config/webpack.common.js b/client/config/webpack.common.js index 000699aa5..83bcbc240 100644 --- a/client/config/webpack.common.js +++ b/client/config/webpack.common.js @@ -11,6 +11,7 @@ const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin const HtmlWebpackPlugin = require('html-webpack-plugin') 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 WebpackNotifierPlugin = require('webpack-notifier') @@ -216,7 +217,9 @@ module.exports = function (options) { new CommonsChunkPlugin({ name: 'vendor', chunks: ['main'], - minChunks: module => /node_modules\//.test(module.resource) + minChunks: module => { + return /node_modules\//.test(module.resource) + } }), // Specify the correct order the scripts will be injected in @@ -244,20 +247,6 @@ module.exports = function (options) { } ), - /* - * Plugin: ScriptExtHtmlWebpackPlugin - * Description: Enhances html-webpack-plugin functionality - * with different deployment options for your scripts including: - * - * See: https://github.com/numical/script-ext-html-webpack-plugin - */ - new ScriptExtHtmlWebpackPlugin({ - sync: [ /polyfill|vendor/ ], - defaultAttribute: 'async', - preload: [/polyfill|vendor|main/], - prefetch: [/chunk/] - }), - /* * Plugin: HtmlWebpackPlugin * Description: Simplifies creation of HTML files to serve your webpack bundles. @@ -277,6 +266,20 @@ module.exports = function (options) { inject: 'body' }), + /* + * Plugin: ScriptExtHtmlWebpackPlugin + * Description: Enhances html-webpack-plugin functionality + * with different deployment options for your scripts including: + * + * See: https://github.com/numical/script-ext-html-webpack-plugin + */ + new ScriptExtHtmlWebpackPlugin({ + sync: [ /polyfill|vendor/ ], + defaultAttribute: 'async', + preload: [/polyfill|vendor|main/], + prefetch: [/chunk/] + }), + new WebpackNotifierPlugin({ alwaysNotify: true }), /** @@ -296,7 +299,9 @@ module.exports = function (options) { new ngcWebpack.NgcWebpackPlugin({ disabled: !AOT, tsConfig: helpers.root('tsconfig.webpack.json') - }) + }), + + new InlineManifestWebpackPlugin(), ], /* diff --git a/client/config/webpack.prod.js b/client/config/webpack.prod.js index 777c816e8..ecd7914c7 100644 --- a/client/config/webpack.prod.js +++ b/client/config/webpack.prod.js @@ -17,6 +17,8 @@ const NormalModuleReplacementPlugin = require('webpack/lib/NormalModuleReplaceme const OptimizeJsPlugin = require('optimize-js-plugin') const HashedModuleIdsPlugin = require('webpack/lib/HashedModuleIdsPlugin') const UglifyJsPlugin = require('uglifyjs-webpack-plugin') +const ExtractTextPlugin = require('extract-text-webpack-plugin') + /** * Webpack Constants */ @@ -120,15 +122,10 @@ module.exports = function (env) { sourceMap: false }), - /** - * Plugin: DedupePlugin - * Description: Prevents the inclusion of duplicate code into your bundle - * and instead applies a copy of the function at runtime. - * - * See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin - * See: https://github.com/webpack/docs/wiki/optimization#deduplication - */ - // new DedupePlugin(), + new ExtractTextPlugin({ + filename: '[name].[contenthash].css', + allChunks: true + }), /** * Plugin: DefinePlugin @@ -158,7 +155,6 @@ module.exports = function (env) { * * See: https://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin */ - // NOTE: To debug prod builds uncomment //debug lines and comment //prod lines new UglifyJsPlugin({ parallel: true, uglifyOptions: { diff --git a/client/package.json b/client/package.json index 6fb1da425..eaa31c22f 100644 --- a/client/package.json +++ b/client/package.json @@ -20,16 +20,16 @@ }, "license": "GPLv3", "dependencies": { - "@angular/animations": "~4.3.0", - "@angular/common": "~4.3.0", - "@angular/compiler": "~4.3.0", - "@angular/compiler-cli": "~4.3.0", - "@angular/core": "~4.3.0", - "@angular/forms": "~4.3.0", - "@angular/http": "~4.3.0", - "@angular/platform-browser": "~4.3.0", - "@angular/platform-browser-dynamic": "~4.3.0", - "@angular/router": "~4.3.0", + "@angular/animations": "~4.4.0", + "@angular/common": "~4.4.0", + "@angular/compiler": "~4.4.0", + "@angular/compiler-cli": "~4.4.0", + "@angular/core": "~4.4.0", + "@angular/forms": "~4.4.0", + "@angular/http": "~4.4.0", + "@angular/platform-browser": "~4.4.0", + "@angular/platform-browser-dynamic": "~4.4.0", + "@angular/router": "~4.4.0", "@angularclass/hmr": "^2.1.0", "@angularclass/hmr-loader": "^3.0.2", "@ngx-meta/core": "^0.4.0-rc.2", @@ -55,7 +55,7 @@ "css-loader": "^0.28.4", "css-to-string-loader": "^0.1.3", "es6-shim": "^0.35.0", - "file-loader": "^0.11.2", + "file-loader": "^1.1.5", "html-webpack-plugin": "^2.19.0", "ie-shim": "^0.1.0", "intl": "^1.2.4", @@ -76,12 +76,12 @@ "script-ext-html-webpack-plugin": "^1.3.2", "source-map-loader": "^0.2.1", "string-replace-loader": "^1.0.3", - "style-loader": "^0.18.2", + "style-loader": "^0.19.0", "tslib": "^1.5.0", "tslint": "^5.7.0", "tslint-loader": "^3.3.0", "typescript": "^2.5.2", - "url-loader": "^0.5.7", + "url-loader": "^0.6.2", "video.js": "^6.2.0", "videojs-dock": "^2.0.2", "webpack": "^3.3.0", @@ -95,6 +95,7 @@ "add-asset-html-webpack-plugin": "^2.0.1", "codelyzer": "^3.0.0-beta.4", "extract-text-webpack-plugin": "^3.0.0", + "inline-manifest-webpack-plugin": "^3.0.1", "primeng": "^4.2.0", "purify-css": "^1.2.5", "purifycss-webpack": "^0.7.0", diff --git a/client/src/app/app-routing.module.ts b/client/src/app/app-routing.module.ts index 191ae6974..0f9484344 100644 --- a/client/src/app/app-routing.module.ts +++ b/client/src/app/app-routing.module.ts @@ -1,5 +1,7 @@ import { NgModule } from '@angular/core' -import { Routes, RouterModule, PreloadAllModules } from '@angular/router' +import { Routes, RouterModule } from '@angular/router' + +import { PreloadSelectedModulesList } from './core' const routes: Routes = [ { @@ -17,9 +19,10 @@ const routes: Routes = [ imports: [ RouterModule.forRoot(routes, { useHash: Boolean(history.pushState) === false, - preloadingStrategy: PreloadAllModules + preloadingStrategy: PreloadSelectedModulesList }) ], + providers: [ PreloadSelectedModulesList ], exports: [ RouterModule ] }) export class AppRoutingModule {} diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index ae86bc96f..82e647c98 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -2,7 +2,6 @@ import { Component, OnInit, ViewContainerRef } from '@angular/core' import { Router } from '@angular/router' import { AuthService, ConfigService } from './core' -import { VideoService } from './videos' import { UserService } from './shared' @Component({ @@ -30,8 +29,7 @@ export class AppComponent implements OnInit { private router: Router, private authService: AuthService, private configService: ConfigService, - private userService: UserService, - private videoService: VideoService + private userService: UserService ) {} ngOnInit () { @@ -43,9 +41,6 @@ export class AppComponent implements OnInit { } this.configService.loadConfig() - this.videoService.loadVideoCategories() - this.videoService.loadVideoLicences() - this.videoService.loadVideoLanguages() // Do not display menu on small screens if (window.innerWidth < 600) { diff --git a/client/src/app/core/index.ts b/client/src/app/core/index.ts index 01b12ce7e..31322138f 100644 --- a/client/src/app/core/index.ts +++ b/client/src/app/core/index.ts @@ -2,4 +2,5 @@ export * from './auth' export * from './config' export * from './confirm' export * from './menu' +export * from './routing' export * from './core.module' diff --git a/client/src/app/core/routing/index.ts b/client/src/app/core/routing/index.ts new file mode 100644 index 000000000..17f3ee833 --- /dev/null +++ b/client/src/app/core/routing/index.ts @@ -0,0 +1 @@ +export * from './preload-selected-modules-list' diff --git a/client/src/app/core/routing/preload-selected-modules-list.ts b/client/src/app/core/routing/preload-selected-modules-list.ts new file mode 100644 index 000000000..dd5be6ad9 --- /dev/null +++ b/client/src/app/core/routing/preload-selected-modules-list.ts @@ -0,0 +1,16 @@ +import { Route, PreloadingStrategy } from '@angular/router'; +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/observable/timer'; +import 'rxjs/add/operator/switchMap'; + +export class PreloadSelectedModulesList implements PreloadingStrategy { + preload(route: Route, load: Function): Observable { + if (!route.data || !route.data.preload) return Observable.of(null); + + if (typeof route.data.preload === 'number') { + return Observable.timer(route.data.preload).switchMap(() => load()); + } + + return load(); + } +} diff --git a/client/src/app/videos/+video-edit/index.ts b/client/src/app/videos/+video-edit/index.ts new file mode 100644 index 000000000..63e0414dd --- /dev/null +++ b/client/src/app/videos/+video-edit/index.ts @@ -0,0 +1,2 @@ +export * from './video-add.module' +export * from './video-update.module' diff --git a/client/src/app/videos/+video-edit/video-add-routing.module.ts b/client/src/app/videos/+video-edit/video-add-routing.module.ts new file mode 100644 index 000000000..9e8fa4acc --- /dev/null +++ b/client/src/app/videos/+video-edit/video-add-routing.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core' +import { RouterModule, Routes } from '@angular/router' + +import { MetaGuard } from '@ngx-meta/core' + +import { VideoAddComponent } from './video-add.component' + +const videoAddRoutes: Routes = [ + { + path: '', + component: VideoAddComponent, + canActivateChild: [ MetaGuard ] + } +] + +@NgModule({ + imports: [ RouterModule.forChild(videoAddRoutes) ], + exports: [ RouterModule ] +}) +export class VideoAddRoutingModule {} diff --git a/client/src/app/videos/+video-edit/video-add.component.html b/client/src/app/videos/+video-edit/video-add.component.html new file mode 100644 index 000000000..698152ff9 --- /dev/null +++ b/client/src/app/videos/+video-edit/video-add.component.html @@ -0,0 +1,121 @@ +
+
+ +

Upload a video

+ +
{{ error }}
+ +
+
+ + +
+ {{ formErrors.name }} +
+
+ +
+ + +
+ +
+ + + +
+ {{ 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.ts b/client/src/app/videos/+video-edit/video-add.component.ts new file mode 100644 index 000000000..21311b184 --- /dev/null +++ b/client/src/app/videos/+video-edit/video-add.component.ts @@ -0,0 +1,165 @@ +import { Component, OnInit, ViewChild } from '@angular/core' +import { FormBuilder, FormGroup } from '@angular/forms' +import { Router } from '@angular/router' + +import { NotificationsService } from 'angular2-notifications' + +import { + FormReactive, + VIDEO_NAME, + VIDEO_CATEGORY, + VIDEO_LICENCE, + VIDEO_LANGUAGE, + VIDEO_DESCRIPTION, + VIDEO_TAGS +} from '../../shared' +import { VideoService } from '../shared' +import { VideoCreate } from '../../../../../shared' +import { HttpEventType, HttpResponse } from '@angular/common/http' +import { VIDEO_FILE } from '../../shared/forms/form-validators/video' + +@Component({ + selector: 'my-videos-add', + styleUrls: [ './video-edit.component.scss' ], + templateUrl: './video-add.component.html' +}) + +export class VideoAddComponent extends FormReactive implements OnInit { + @ViewChild('videofileInput') videofileInput + + progressPercent = 0 + tags: string[] = [] + videoCategories = [] + videoLicences = [] + videoLanguages = [] + + tagValidators = VIDEO_TAGS.VALIDATORS + tagValidatorsMessages = VIDEO_TAGS.MESSAGES + + error: string + form: FormGroup + formErrors = { + name: '', + category: '', + licence: '', + language: '', + description: '', + videofile: '' + } + validationMessages = { + name: VIDEO_NAME.MESSAGES, + category: VIDEO_CATEGORY.MESSAGES, + licence: VIDEO_LICENCE.MESSAGES, + language: VIDEO_LANGUAGE.MESSAGES, + description: VIDEO_DESCRIPTION.MESSAGES, + videofile: VIDEO_FILE.MESSAGES + } + + constructor ( + private formBuilder: FormBuilder, + private router: Router, + private notificationsService: NotificationsService, + private videoService: VideoService + ) { + super() + } + + get filename () { + return this.form.value['videofile'] + } + + buildForm () { + this.form = this.formBuilder.group({ + name: [ '', VIDEO_NAME.VALIDATORS ], + nsfw: [ false ], + category: [ '', VIDEO_CATEGORY.VALIDATORS ], + licence: [ '', VIDEO_LICENCE.VALIDATORS ], + language: [ '', VIDEO_LANGUAGE.VALIDATORS ], + description: [ '', VIDEO_DESCRIPTION.VALIDATORS ], + videofile: [ '', VIDEO_FILE.VALIDATORS ], + tags: [ '' ] + }) + + this.form.valueChanges.subscribe(data => this.onValueChanged(data)) + } + + ngOnInit () { + this.videoCategories = this.videoService.videoCategories + this.videoLicences = this.videoService.videoLicences + this.videoLanguages = this.videoService.videoLanguages + + this.buildForm() + } + + // 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('') + } + + checkForm () { + this.forceCheck() + + return this.form.valid + } + + upload () { + if (this.checkForm() === false) { + return + } + + const formValue: VideoCreate = this.form.value + + const name = formValue.name + const nsfw = formValue.nsfw + const category = formValue.category + const licence = formValue.licence + const language = formValue.language + const description = formValue.description + const tags = formValue.tags + const videofile = this.videofileInput.nativeElement.files[0] + + const formData = new FormData() + formData.append('name', name) + formData.append('category', '' + category) + formData.append('nsfw', '' + nsfw) + formData.append('licence', '' + licence) + 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.videoService.uploadVideo(formData).subscribe( + event => { + if (event.type === HttpEventType.UploadProgress) { + 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/list' ]) + } + }, + + err => { + // Reset progress + this.progressPercent = 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 new file mode 100644 index 000000000..141d33ad2 --- /dev/null +++ b/client/src/app/videos/+video-edit/video-add.module.ts @@ -0,0 +1,30 @@ +import { NgModule } from '@angular/core' + +import { TagInputModule } from 'ngx-chips' + +import { VideoAddRoutingModule } from './video-add-routing.module' +import { VideoAddComponent } from './video-add.component' +import { VideoService } from '../shared' +import { SharedModule } from '../../shared' + +@NgModule({ + imports: [ + TagInputModule, + + VideoAddRoutingModule, + SharedModule + ], + + declarations: [ + VideoAddComponent + ], + + exports: [ + VideoAddComponent + ], + + providers: [ + VideoService + ] +}) +export class VideoAddModule { } diff --git a/client/src/app/videos/+video-edit/video-edit.component.scss b/client/src/app/videos/+video-edit/video-edit.component.scss new file mode 100644 index 000000000..9ee0c520c --- /dev/null +++ b/client/src/app/videos/+video-edit/video-edit.component.scss @@ -0,0 +1,56 @@ +.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; +} + +.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; + + .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-update-routing.module.ts b/client/src/app/videos/+video-edit/video-update-routing.module.ts new file mode 100644 index 000000000..1d06a7ac3 --- /dev/null +++ b/client/src/app/videos/+video-edit/video-update-routing.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core' +import { RouterModule, Routes } from '@angular/router' + +import { MetaGuard } from '@ngx-meta/core' + +import { VideoUpdateComponent } from './video-update.component' + +const videoUpdateRoutes: Routes = [ + { + path: '', + component: VideoUpdateComponent, + canActivateChild: [ MetaGuard ] + } +] + +@NgModule({ + imports: [ RouterModule.forChild(videoUpdateRoutes) ], + exports: [ RouterModule ] +}) +export class VideoUpdateRoutingModule {} diff --git a/client/src/app/videos/+video-edit/video-update.component.html b/client/src/app/videos/+video-edit/video-update.component.html new file mode 100644 index 000000000..7f4faf21b --- /dev/null +++ b/client/src/app/videos/+video-edit/video-update.component.html @@ -0,0 +1,92 @@ +
+
+ +

Update {{ video?.name }}

+ +
{{ error }}
+ +
+
+ + +
+ {{ formErrors.name }} +
+
+ +
+ + +
+ +
+ + + +
+ {{ formErrors.category }} +
+
+ +
+ + + +
+ {{ formErrors.licence }} +
+
+ +
+ + + +
+ {{ formErrors.language }} +
+
+ +
+ (press enter to add the tag) + +
+ +
+ + +
+ {{ formErrors.description }} +
+
+ +
+ +
+
+
+
diff --git a/client/src/app/videos/+video-edit/video-update.component.ts b/client/src/app/videos/+video-edit/video-update.component.ts new file mode 100644 index 000000000..141ed3522 --- /dev/null +++ b/client/src/app/videos/+video-edit/video-update.component.ts @@ -0,0 +1,134 @@ +import { Component, ElementRef, OnInit } from '@angular/core' +import { FormBuilder, FormGroup } from '@angular/forms' +import { ActivatedRoute, Router } from '@angular/router' + +import { NotificationsService } from 'angular2-notifications' + +import { AuthService } from '../../core' +import { + FormReactive, + VIDEO_NAME, + VIDEO_CATEGORY, + VIDEO_LICENCE, + VIDEO_LANGUAGE, + VIDEO_DESCRIPTION, + VIDEO_TAGS +} from '../../shared' +import { Video, VideoService } from '../shared' + +@Component({ + selector: 'my-videos-update', + styleUrls: [ './video-edit.component.scss' ], + templateUrl: './video-update.component.html' +}) + +export class VideoUpdateComponent extends FormReactive implements OnInit { + tags: string[] = [] + videoCategories = [] + videoLicences = [] + videoLanguages = [] + video: Video + + tagValidators = VIDEO_TAGS.VALIDATORS + tagValidatorsMessages = VIDEO_TAGS.MESSAGES + + error: string = null + form: FormGroup + formErrors = { + name: '', + category: '', + licence: '', + language: '', + description: '' + } + validationMessages = { + name: VIDEO_NAME.MESSAGES, + category: VIDEO_CATEGORY.MESSAGES, + licence: VIDEO_LICENCE.MESSAGES, + language: VIDEO_LANGUAGE.MESSAGES, + description: VIDEO_DESCRIPTION.MESSAGES + } + + fileError = '' + + constructor ( + private authService: AuthService, + private elementRef: ElementRef, + private formBuilder: FormBuilder, + private route: ActivatedRoute, + private router: Router, + private notificationsService: NotificationsService, + private videoService: VideoService + ) { + super() + } + + buildForm () { + this.form = this.formBuilder.group({ + name: [ '', VIDEO_NAME.VALIDATORS ], + nsfw: [ false ], + category: [ '', VIDEO_CATEGORY.VALIDATORS ], + licence: [ '', VIDEO_LICENCE.VALIDATORS ], + language: [ '', VIDEO_LANGUAGE.VALIDATORS ], + description: [ '', VIDEO_DESCRIPTION.VALIDATORS ], + tags: [ '' ] + }) + + this.form.valueChanges.subscribe(data => this.onValueChanged(data)) + } + + ngOnInit () { + this.buildForm() + + this.videoCategories = this.videoService.videoCategories + this.videoLicences = this.videoService.videoLicences + this.videoLanguages = this.videoService.videoLanguages + + const uuid: string = this.route.snapshot.params['uuid'] + this.videoService.getVideo(uuid) + .subscribe( + video => { + this.video = video + + this.hydrateFormFromVideo() + }, + + err => { + console.error(err) + this.error = 'Cannot fetch video.' + } + ) + } + + checkForm () { + this.forceCheck() + + return this.form.valid + } + + update () { + if (this.checkForm() === false) { + return + } + + this.video.patch(this.form.value) + + this.videoService.updateVideo(this.video) + .subscribe( + () => { + this.notificationsService.success('Success', 'Video updated.') + this.router.navigate([ '/videos/watch', this.video.uuid ]) + }, + + err => { + this.error = 'Cannot update the video.' + console.error(err) + } + ) + + } + + private hydrateFormFromVideo () { + this.form.patchValue(this.video.toJSON()) + } +} diff --git a/client/src/app/videos/+video-edit/video-update.module.ts b/client/src/app/videos/+video-edit/video-update.module.ts new file mode 100644 index 000000000..eeb2e35e2 --- /dev/null +++ b/client/src/app/videos/+video-edit/video-update.module.ts @@ -0,0 +1,30 @@ +import { NgModule } from '@angular/core' + +import { TagInputModule } from 'ngx-chips' + +import { VideoUpdateRoutingModule } from './video-update-routing.module' +import { VideoUpdateComponent } from './video-update.component' +import { VideoService } from '../shared' +import { SharedModule } from '../../shared' + +@NgModule({ + imports: [ + TagInputModule, + + VideoUpdateRoutingModule, + SharedModule + ], + + declarations: [ + VideoUpdateComponent + ], + + exports: [ + VideoUpdateComponent + ], + + providers: [ + VideoService + ] +}) +export class VideoUpdateModule { } diff --git a/client/src/app/videos/+video-watch/index.ts b/client/src/app/videos/+video-watch/index.ts new file mode 100644 index 000000000..b19bfdb1e --- /dev/null +++ b/client/src/app/videos/+video-watch/index.ts @@ -0,0 +1 @@ +export * from './video-watch.module' diff --git a/client/src/app/videos/+video-watch/video-magnet.component.html b/client/src/app/videos/+video-watch/video-magnet.component.html new file mode 100644 index 000000000..484280c45 --- /dev/null +++ b/client/src/app/videos/+video-watch/video-magnet.component.html @@ -0,0 +1,20 @@ + diff --git a/client/src/app/videos/+video-watch/video-magnet.component.ts b/client/src/app/videos/+video-watch/video-magnet.component.ts new file mode 100644 index 000000000..f9432e92c --- /dev/null +++ b/client/src/app/videos/+video-watch/video-magnet.component.ts @@ -0,0 +1,27 @@ +import { Component, Input, ViewChild } from '@angular/core' + +import { ModalDirective } from 'ngx-bootstrap/modal' + +import { Video } from '../shared' + +@Component({ + selector: 'my-video-magnet', + templateUrl: './video-magnet.component.html' +}) +export class VideoMagnetComponent { + @Input() video: Video = null + + @ViewChild('modal') modal: ModalDirective + + constructor () { + // empty + } + + show () { + this.modal.show() + } + + hide () { + this.modal.hide() + } +} diff --git a/client/src/app/videos/+video-watch/video-report.component.html b/client/src/app/videos/+video-watch/video-report.component.html new file mode 100644 index 000000000..741080ead --- /dev/null +++ b/client/src/app/videos/+video-watch/video-report.component.html @@ -0,0 +1,38 @@ + diff --git a/client/src/app/videos/+video-watch/video-report.component.ts b/client/src/app/videos/+video-watch/video-report.component.ts new file mode 100644 index 000000000..d9c83a640 --- /dev/null +++ b/client/src/app/videos/+video-watch/video-report.component.ts @@ -0,0 +1,69 @@ +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 { Video, VideoService } from '../shared' + +@Component({ + selector: 'my-video-report', + templateUrl: './video-report.component.html' +}) +export class VideoReportComponent extends FormReactive implements OnInit { + @Input() video: Video = null + + @ViewChild('modal') modal: ModalDirective + + error: string = null + form: FormGroup + formErrors = { + reason: '' + } + validationMessages = { + reason: VIDEO_ABUSE_REASON.MESSAGES + } + + constructor ( + private formBuilder: FormBuilder, + private videoAbuseService: VideoAbuseService, + private notificationsService: NotificationsService + ) { + super() + } + + ngOnInit () { + this.buildForm() + } + + buildForm () { + this.form = this.formBuilder.group({ + reason: [ '', VIDEO_ABUSE_REASON.VALIDATORS ] + }) + + this.form.valueChanges.subscribe(data => this.onValueChanged(data)) + } + + show () { + this.modal.show() + } + + hide () { + this.modal.hide() + } + + report () { + const reason = this.form.value['reason'] + + this.videoAbuseService.reportVideo(this.video.id, reason) + .subscribe( + () => { + this.notificationsService.success('Success', 'Video reported.') + this.hide() + }, + + err => this.notificationsService.error('Error', err.message) + ) + } +} diff --git a/client/src/app/videos/+video-watch/video-share.component.html b/client/src/app/videos/+video-watch/video-share.component.html new file mode 100644 index 000000000..88f59c063 --- /dev/null +++ b/client/src/app/videos/+video-watch/video-share.component.html @@ -0,0 +1,29 @@ + diff --git a/client/src/app/videos/+video-watch/video-share.component.ts b/client/src/app/videos/+video-watch/video-share.component.ts new file mode 100644 index 000000000..133f93498 --- /dev/null +++ b/client/src/app/videos/+video-watch/video-share.component.ts @@ -0,0 +1,42 @@ +import { Component, Input, ViewChild } from '@angular/core' + +import { ModalDirective } from 'ngx-bootstrap/modal' + +import { Video } from '../shared' + +@Component({ + selector: 'my-video-share', + templateUrl: './video-share.component.html' +}) +export class VideoShareComponent { + @Input() video: Video = null + + @ViewChild('modal') modal: ModalDirective + + constructor () { + // empty + } + + show () { + this.modal.show() + } + + hide () { + this.modal.hide() + } + + getVideoIframeCode () { + return '' + } + + getVideoUrl () { + return window.location.href + } + + notSecure () { + return window.location.protocol === 'http:' + } +} diff --git a/client/src/app/videos/+video-watch/video-watch-routing.module.ts b/client/src/app/videos/+video-watch/video-watch-routing.module.ts new file mode 100644 index 000000000..97fa5c725 --- /dev/null +++ b/client/src/app/videos/+video-watch/video-watch-routing.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core' +import { RouterModule, Routes } from '@angular/router' + +import { MetaGuard } from '@ngx-meta/core' + +import { VideoWatchComponent } from './video-watch.component' + +const videoWatchRoutes: Routes = [ + { + path: '', + component: VideoWatchComponent, + canActivateChild: [ MetaGuard ] + } +] + +@NgModule({ + imports: [ RouterModule.forChild(videoWatchRoutes) ], + exports: [ RouterModule ] +}) +export class VideoWatchRoutingModule {} diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html new file mode 100644 index 000000000..88863131a --- /dev/null +++ b/client/src/app/videos/+video-watch/video-watch.component.html @@ -0,0 +1,184 @@ +
+
+ The video load seems to be abnormally long. + +
+
+ +
+ +
+ +
+ +
Video not found :'(
+
+ + +
+
Download: {{ downloadSpeed | bytes }}/s
+
Upload: {{ uploadSpeed | bytes }}/s
+
Number of peers: {{ numPeers }}
+
+ + +
+
+
+ {{ video.name }} +
+ +
+ {{ video.views}} views +
+
+ +
+ + + + + + +
+
+ + + + {{ video.likes }} + +
+ +
+ + + + {{ video.dislikes }} + +
+
+
+ +
+
+
+ Published on {{ video.createdAt | date:'short' }} +
+ +
+ {{ video.description }} +
+
+ +
+
+ + Category: + + + {{ video.categoryLabel }} + +
+ +
+ + Licence: + + + {{ video.licenceLabel }} + +
+ +
+ + Language: + + + {{ video.languageLabel }} + +
+ +
+ + Tags: + + + +
+ +
+
+
+ + + + + + diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss new file mode 100644 index 000000000..69661747c --- /dev/null +++ b/client/src/app/videos/+video-watch/video-watch.component.scss @@ -0,0 +1,245 @@ +#video-container { + width: 100%; + height: 100%; +} + +#video-not-found { + height: 300px; + line-height: 300px; + margin-top: 50px; + text-align: center; + font-weight: bold; +} + +.embed-responsive { + height: 500px; + + @media screen and (max-width: 600px) { + height: 300px; + } +} + +#torrent-info { + font-size: 10px; + margin-top: 10px; + text-align: center; + + div { + min-width: 60px; + } +} + +#video-info { + .video-name-views { + font-weight: bold; + font-size: 18px; + height: $video-watch-title-height; + line-height: $video-watch-title-height; + + .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; + display: flex; + flex-direction: column; + justify-content: center; + text-align: 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; + } + } + + .option .glyphicon { + font-size: 22px; + color: inherit; + opacity: 0.15; + margin-bottom: 10px; + transition: opacity 0.3s; + } + + .video-small-block-text { + font-size: 15px; + font-weight: bold; + } + } + + .video-small-block:not(:last-child) { + border-width: 0 1px 0 0; + border-color: $video-watch-border-color; + border-style: solid; + } + + .video-small-block-author, .video-small-block-more { + a.option { + display: block; + + .glyphicon { + display: block; + } + } + } + + .video-small-block-share, .video-small-block-more { + a.option { + display: block; + + .glyphicon { + display: block; + } + } + } + + .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-details-date { + font-weight: bold; + margin-bottom: 30px; + } + } + + .video-details-attributes { + font-weight: bold; + font-size: 12px; + + .video-details-attribute-label { + color: $video-watch-info-color; + display: inline-block; + width: 60px; + margin-right: 5px; + } + } + + .video-details-tags { + display: inline-block; + + a { + display: inline-block; + margin-right: 3px; + font-size: 11px; + } + } + } + + @media screen and (max-width: 400px) { + .video-name-views { + font-size: 16px !important; + } + } + + @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-author { + padding-left: 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; + } + } + } + } +} diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts new file mode 100644 index 000000000..874dd5997 --- /dev/null +++ b/client/src/app/videos/+video-watch/video-watch.component.ts @@ -0,0 +1,299 @@ +import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core' +import { ActivatedRoute, Router } from '@angular/router' +import { Observable } from 'rxjs/Observable' +import { Subscription } from 'rxjs/Subscription' + +import videojs from 'video.js' +import '../../../assets/player/peertube-videojs-plugin' + +import { MetaService } from '@ngx-meta/core' +import { NotificationsService } from 'angular2-notifications' + +import { AuthService, ConfirmService } from '../../core' +import { VideoMagnetComponent } from './video-magnet.component' +import { VideoShareComponent } from './video-share.component' +import { VideoReportComponent } from './video-report.component' +import { Video, VideoService } from '../shared' +import { WebTorrentService } from './webtorrent.service' +import { UserVideoRateType, VideoRateType } from '../../../../../shared' + +@Component({ + selector: 'my-video-watch', + templateUrl: './video-watch.component.html', + styleUrls: [ './video-watch.component.scss' ] +}) +export class VideoWatchComponent implements OnInit, OnDestroy { + @ViewChild('videoMagnetModal') videoMagnetModal: VideoMagnetComponent + @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: Video = null + videoNotFound = false + + private paramsSub: Subscription + + constructor ( + private elementRef: ElementRef, + private route: ActivatedRoute, + private router: Router, + private videoService: VideoService, + private confirmService: ConfirmService, + private metaService: MetaService, + private authService: AuthService, + private notificationsService: NotificationsService + ) {} + + ngOnInit () { + this.paramsSub = this.route.params.subscribe(routeParams => { + let uuid = routeParams['uuid'] + this.videoService.getVideo(uuid).subscribe( + video => this.onVideoFetched(video), + + error => { + console.error(error) + this.videoNotFound = true + } + ) + }) + } + + ngOnDestroy () { + // Remove player if it exists + if (this.videoNotFound === false) { + videojs(this.playerElement).dispose() + } + + // Unsubscribe subscriptions + this.paramsSub.unsubscribe() + } + + setLike () { + if (this.isUserLoggedIn() === false) return + // Already liked this video + if (this.userRating === 'like') return + + this.videoService.setVideoLike(this.video.id) + .subscribe( + () => { + // Update the video like attribute + this.updateVideoRating(this.userRating, 'like') + this.userRating = 'like' + }, + + err => this.notificationsService.error('Error', err.message) + ) + } + + setDislike () { + if (this.isUserLoggedIn() === false) return + // Already disliked this video + if (this.userRating === 'dislike') return + + this.videoService.setVideoDislike(this.video.id) + .subscribe( + () => { + // Update the video dislike attribute + this.updateVideoRating(this.userRating, 'dislike') + this.userRating = 'dislike' + }, + + err => this.notificationsService.error('Error', err.message) + ) + } + + 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() + + this.confirmService.confirm('Do you really want to blacklist this video ?', 'Blacklist').subscribe( + res => { + if (res === false) return + + this.videoService.blacklistVideo(this.video.id) + .subscribe( + status => { + this.notificationsService.success('Success', `Video ${this.video.name} had been blacklisted.`) + this.router.navigate(['/videos/list']) + }, + + error => this.notificationsService.error('Error', error.text) + ) + } + ) + } + + showReportModal (event: Event) { + event.preventDefault() + this.videoReportModal.show() + } + + showShareModal () { + this.videoShareModal.show() + } + + showMagnetUriModal (event: Event) { + event.preventDefault() + this.videoMagnetModal.show() + } + + isUserLoggedIn () { + 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()) + } + + private handleError (err: any) { + const errorMessage: string = typeof err === 'string' ? err : err.message + let message = '' + + if (errorMessage.indexOf('http error') !== -1) { + message = 'Cannot fetch video from server, maybe down.' + } else { + message = errorMessage + } + + this.notificationsService.error('Error', message) + } + + private checkUserRating () { + // Unlogged users do not have ratings + if (this.isUserLoggedIn() === false) return + + this.videoService.getUserVideoRating(this.video.id) + .subscribe( + ratingObject => { + if (ratingObject) { + this.userRating = ratingObject.rating + } + }, + + err => this.notificationsService.error('Error', err.message) + ) + } + + private onVideoFetched (video: Video) { + this.video = video + + let observable + if (this.video.isVideoNSFWForUser(this.authService.getUser())) { + observable = this.confirmService.confirm('This video is not safe for work. Are you sure you want to watch it?', 'NSFW') + } else { + observable = Observable.of(true) + } + + observable.subscribe( + res => { + if (res === false) { + return this.router.navigate([ '/videos/list' ]) + } + + this.playerElement = this.elementRef.nativeElement.querySelector('#video-container') + + const videojsOptions = { + controls: true, + autoplay: true, + plugins: { + peertube: { + videoFiles: this.video.files, + playerElement: this.playerElement, + autoplay: true, + peerTubeLink: false + } + } + } + + const self = this + videojs(this.playerElement, videojsOptions, function () { + self.player = this + 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.setOpenGraphTags() + this.checkUserRating() + } + ) + } + + private updateVideoRating (oldRating: UserVideoRateType, newRating: VideoRateType) { + let likesToIncrement = 0 + let dislikesToIncrement = 0 + + if (oldRating) { + if (oldRating === 'like') likesToIncrement-- + if (oldRating === 'dislike') dislikesToIncrement-- + } + + if (newRating === 'like') likesToIncrement++ + if (newRating === 'dislike') dislikesToIncrement++ + + this.video.likes += likesToIncrement + this.video.dislikes += dislikesToIncrement + } + + private setOpenGraphTags () { + this.metaService.setTitle(this.video.name) + + this.metaService.setTag('og:type', 'video') + + this.metaService.setTag('og:title', this.video.name) + this.metaService.setTag('name', this.video.name) + + this.metaService.setTag('og:description', this.video.description) + this.metaService.setTag('description', this.video.description) + + this.metaService.setTag('og:image', this.video.previewPath) + + this.metaService.setTag('og:duration', this.video.duration.toString()) + + this.metaService.setTag('og:site_name', 'PeerTube') + + this.metaService.setTag('og:url', window.location.href) + this.metaService.setTag('url', window.location.href) + } +} diff --git a/client/src/app/videos/+video-watch/video-watch.module.ts b/client/src/app/videos/+video-watch/video-watch.module.ts new file mode 100644 index 000000000..5f20b171e --- /dev/null +++ b/client/src/app/videos/+video-watch/video-watch.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core' + +import { VideoWatchRoutingModule } from './video-watch-routing.module' +import { VideoService } from '../shared' +import { SharedModule } from '../../shared' + +import { VideoWatchComponent } from './video-watch.component' +import { VideoReportComponent } from './video-report.component' +import { VideoShareComponent } from './video-share.component' +import { VideoMagnetComponent } from './video-magnet.component' + +@NgModule({ + imports: [ + VideoWatchRoutingModule, + SharedModule + ], + + declarations: [ + VideoWatchComponent, + + VideoMagnetComponent, + VideoShareComponent, + VideoReportComponent + ], + + exports: [ + VideoWatchComponent + ], + + providers: [ + VideoService + ] +}) +export class VideoWatchModule { } diff --git a/client/src/app/videos/index.ts b/client/src/app/videos/index.ts index 83edcc758..028a5854b 100644 --- a/client/src/app/videos/index.ts +++ b/client/src/app/videos/index.ts @@ -1,7 +1 @@ -export * from './shared' -export * from './video-edit' -export * from './video-list' -export * from './video-watch' -export * from './videos-routing.module' -export * from './videos.component' export * from './videos.module' diff --git a/client/src/app/videos/video-edit/index.ts b/client/src/app/videos/video-edit/index.ts deleted file mode 100644 index 3b4a9cb87..000000000 --- a/client/src/app/videos/video-edit/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './video-add.component' -export * from './video-update.component' diff --git a/client/src/app/videos/video-edit/video-add.component.html b/client/src/app/videos/video-edit/video-add.component.html deleted file mode 100644 index 698152ff9..000000000 --- a/client/src/app/videos/video-edit/video-add.component.html +++ /dev/null @@ -1,121 +0,0 @@ -
-
- -

Upload a video

- -
{{ error }}
- -
-
- - -
- {{ formErrors.name }} -
-
- -
- - -
- -
- - - -
- {{ 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.ts b/client/src/app/videos/video-edit/video-add.component.ts deleted file mode 100644 index 21311b184..000000000 --- a/client/src/app/videos/video-edit/video-add.component.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { Component, OnInit, ViewChild } from '@angular/core' -import { FormBuilder, FormGroup } from '@angular/forms' -import { Router } from '@angular/router' - -import { NotificationsService } from 'angular2-notifications' - -import { - FormReactive, - VIDEO_NAME, - VIDEO_CATEGORY, - VIDEO_LICENCE, - VIDEO_LANGUAGE, - VIDEO_DESCRIPTION, - VIDEO_TAGS -} from '../../shared' -import { VideoService } from '../shared' -import { VideoCreate } from '../../../../../shared' -import { HttpEventType, HttpResponse } from '@angular/common/http' -import { VIDEO_FILE } from '../../shared/forms/form-validators/video' - -@Component({ - selector: 'my-videos-add', - styleUrls: [ './video-edit.component.scss' ], - templateUrl: './video-add.component.html' -}) - -export class VideoAddComponent extends FormReactive implements OnInit { - @ViewChild('videofileInput') videofileInput - - progressPercent = 0 - tags: string[] = [] - videoCategories = [] - videoLicences = [] - videoLanguages = [] - - tagValidators = VIDEO_TAGS.VALIDATORS - tagValidatorsMessages = VIDEO_TAGS.MESSAGES - - error: string - form: FormGroup - formErrors = { - name: '', - category: '', - licence: '', - language: '', - description: '', - videofile: '' - } - validationMessages = { - name: VIDEO_NAME.MESSAGES, - category: VIDEO_CATEGORY.MESSAGES, - licence: VIDEO_LICENCE.MESSAGES, - language: VIDEO_LANGUAGE.MESSAGES, - description: VIDEO_DESCRIPTION.MESSAGES, - videofile: VIDEO_FILE.MESSAGES - } - - constructor ( - private formBuilder: FormBuilder, - private router: Router, - private notificationsService: NotificationsService, - private videoService: VideoService - ) { - super() - } - - get filename () { - return this.form.value['videofile'] - } - - buildForm () { - this.form = this.formBuilder.group({ - name: [ '', VIDEO_NAME.VALIDATORS ], - nsfw: [ false ], - category: [ '', VIDEO_CATEGORY.VALIDATORS ], - licence: [ '', VIDEO_LICENCE.VALIDATORS ], - language: [ '', VIDEO_LANGUAGE.VALIDATORS ], - description: [ '', VIDEO_DESCRIPTION.VALIDATORS ], - videofile: [ '', VIDEO_FILE.VALIDATORS ], - tags: [ '' ] - }) - - this.form.valueChanges.subscribe(data => this.onValueChanged(data)) - } - - ngOnInit () { - this.videoCategories = this.videoService.videoCategories - this.videoLicences = this.videoService.videoLicences - this.videoLanguages = this.videoService.videoLanguages - - this.buildForm() - } - - // 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('') - } - - checkForm () { - this.forceCheck() - - return this.form.valid - } - - upload () { - if (this.checkForm() === false) { - return - } - - const formValue: VideoCreate = this.form.value - - const name = formValue.name - const nsfw = formValue.nsfw - const category = formValue.category - const licence = formValue.licence - const language = formValue.language - const description = formValue.description - const tags = formValue.tags - const videofile = this.videofileInput.nativeElement.files[0] - - const formData = new FormData() - formData.append('name', name) - formData.append('category', '' + category) - formData.append('nsfw', '' + nsfw) - formData.append('licence', '' + licence) - 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.videoService.uploadVideo(formData).subscribe( - event => { - if (event.type === HttpEventType.UploadProgress) { - 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/list' ]) - } - }, - - err => { - // Reset progress - this.progressPercent = 0 - this.error = err.message - } - ) - } -} diff --git a/client/src/app/videos/video-edit/video-edit.component.scss b/client/src/app/videos/video-edit/video-edit.component.scss deleted file mode 100644 index 9ee0c520c..000000000 --- a/client/src/app/videos/video-edit/video-edit.component.scss +++ /dev/null @@ -1,56 +0,0 @@ -.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; -} - -.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; - - .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-update.component.html b/client/src/app/videos/video-edit/video-update.component.html deleted file mode 100644 index 7f4faf21b..000000000 --- a/client/src/app/videos/video-edit/video-update.component.html +++ /dev/null @@ -1,92 +0,0 @@ -
-
- -

Update {{ video?.name }}

- -
{{ error }}
- -
-
- - -
- {{ formErrors.name }} -
-
- -
- - -
- -
- - - -
- {{ formErrors.category }} -
-
- -
- - - -
- {{ formErrors.licence }} -
-
- -
- - - -
- {{ formErrors.language }} -
-
- -
- (press enter to add the tag) - -
- -
- - -
- {{ formErrors.description }} -
-
- -
- -
-
-
-
diff --git a/client/src/app/videos/video-edit/video-update.component.ts b/client/src/app/videos/video-edit/video-update.component.ts deleted file mode 100644 index 141ed3522..000000000 --- a/client/src/app/videos/video-edit/video-update.component.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { Component, ElementRef, OnInit } from '@angular/core' -import { FormBuilder, FormGroup } from '@angular/forms' -import { ActivatedRoute, Router } from '@angular/router' - -import { NotificationsService } from 'angular2-notifications' - -import { AuthService } from '../../core' -import { - FormReactive, - VIDEO_NAME, - VIDEO_CATEGORY, - VIDEO_LICENCE, - VIDEO_LANGUAGE, - VIDEO_DESCRIPTION, - VIDEO_TAGS -} from '../../shared' -import { Video, VideoService } from '../shared' - -@Component({ - selector: 'my-videos-update', - styleUrls: [ './video-edit.component.scss' ], - templateUrl: './video-update.component.html' -}) - -export class VideoUpdateComponent extends FormReactive implements OnInit { - tags: string[] = [] - videoCategories = [] - videoLicences = [] - videoLanguages = [] - video: Video - - tagValidators = VIDEO_TAGS.VALIDATORS - tagValidatorsMessages = VIDEO_TAGS.MESSAGES - - error: string = null - form: FormGroup - formErrors = { - name: '', - category: '', - licence: '', - language: '', - description: '' - } - validationMessages = { - name: VIDEO_NAME.MESSAGES, - category: VIDEO_CATEGORY.MESSAGES, - licence: VIDEO_LICENCE.MESSAGES, - language: VIDEO_LANGUAGE.MESSAGES, - description: VIDEO_DESCRIPTION.MESSAGES - } - - fileError = '' - - constructor ( - private authService: AuthService, - private elementRef: ElementRef, - private formBuilder: FormBuilder, - private route: ActivatedRoute, - private router: Router, - private notificationsService: NotificationsService, - private videoService: VideoService - ) { - super() - } - - buildForm () { - this.form = this.formBuilder.group({ - name: [ '', VIDEO_NAME.VALIDATORS ], - nsfw: [ false ], - category: [ '', VIDEO_CATEGORY.VALIDATORS ], - licence: [ '', VIDEO_LICENCE.VALIDATORS ], - language: [ '', VIDEO_LANGUAGE.VALIDATORS ], - description: [ '', VIDEO_DESCRIPTION.VALIDATORS ], - tags: [ '' ] - }) - - this.form.valueChanges.subscribe(data => this.onValueChanged(data)) - } - - ngOnInit () { - this.buildForm() - - this.videoCategories = this.videoService.videoCategories - this.videoLicences = this.videoService.videoLicences - this.videoLanguages = this.videoService.videoLanguages - - const uuid: string = this.route.snapshot.params['uuid'] - this.videoService.getVideo(uuid) - .subscribe( - video => { - this.video = video - - this.hydrateFormFromVideo() - }, - - err => { - console.error(err) - this.error = 'Cannot fetch video.' - } - ) - } - - checkForm () { - this.forceCheck() - - return this.form.valid - } - - update () { - if (this.checkForm() === false) { - return - } - - this.video.patch(this.form.value) - - this.videoService.updateVideo(this.video) - .subscribe( - () => { - this.notificationsService.success('Success', 'Video updated.') - this.router.navigate([ '/videos/watch', this.video.uuid ]) - }, - - err => { - this.error = 'Cannot update the video.' - console.error(err) - } - ) - - } - - private hydrateFormFromVideo () { - this.form.patchValue(this.video.toJSON()) - } -} diff --git a/client/src/app/videos/video-watch/index.ts b/client/src/app/videos/video-watch/index.ts deleted file mode 100644 index 105872469..000000000 --- a/client/src/app/videos/video-watch/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './video-magnet.component' -export * from './video-share.component' -export * from './video-report.component' -export * from './video-watch.component' diff --git a/client/src/app/videos/video-watch/video-magnet.component.html b/client/src/app/videos/video-watch/video-magnet.component.html deleted file mode 100644 index 484280c45..000000000 --- a/client/src/app/videos/video-watch/video-magnet.component.html +++ /dev/null @@ -1,20 +0,0 @@ - diff --git a/client/src/app/videos/video-watch/video-magnet.component.ts b/client/src/app/videos/video-watch/video-magnet.component.ts deleted file mode 100644 index f9432e92c..000000000 --- a/client/src/app/videos/video-watch/video-magnet.component.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Component, Input, ViewChild } from '@angular/core' - -import { ModalDirective } from 'ngx-bootstrap/modal' - -import { Video } from '../shared' - -@Component({ - selector: 'my-video-magnet', - templateUrl: './video-magnet.component.html' -}) -export class VideoMagnetComponent { - @Input() video: Video = null - - @ViewChild('modal') modal: ModalDirective - - constructor () { - // empty - } - - show () { - this.modal.show() - } - - hide () { - this.modal.hide() - } -} diff --git a/client/src/app/videos/video-watch/video-report.component.html b/client/src/app/videos/video-watch/video-report.component.html deleted file mode 100644 index 741080ead..000000000 --- a/client/src/app/videos/video-watch/video-report.component.html +++ /dev/null @@ -1,38 +0,0 @@ - diff --git a/client/src/app/videos/video-watch/video-report.component.ts b/client/src/app/videos/video-watch/video-report.component.ts deleted file mode 100644 index d9c83a640..000000000 --- a/client/src/app/videos/video-watch/video-report.component.ts +++ /dev/null @@ -1,69 +0,0 @@ -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 { Video, VideoService } from '../shared' - -@Component({ - selector: 'my-video-report', - templateUrl: './video-report.component.html' -}) -export class VideoReportComponent extends FormReactive implements OnInit { - @Input() video: Video = null - - @ViewChild('modal') modal: ModalDirective - - error: string = null - form: FormGroup - formErrors = { - reason: '' - } - validationMessages = { - reason: VIDEO_ABUSE_REASON.MESSAGES - } - - constructor ( - private formBuilder: FormBuilder, - private videoAbuseService: VideoAbuseService, - private notificationsService: NotificationsService - ) { - super() - } - - ngOnInit () { - this.buildForm() - } - - buildForm () { - this.form = this.formBuilder.group({ - reason: [ '', VIDEO_ABUSE_REASON.VALIDATORS ] - }) - - this.form.valueChanges.subscribe(data => this.onValueChanged(data)) - } - - show () { - this.modal.show() - } - - hide () { - this.modal.hide() - } - - report () { - const reason = this.form.value['reason'] - - this.videoAbuseService.reportVideo(this.video.id, reason) - .subscribe( - () => { - this.notificationsService.success('Success', 'Video reported.') - this.hide() - }, - - err => this.notificationsService.error('Error', err.message) - ) - } -} diff --git a/client/src/app/videos/video-watch/video-share.component.html b/client/src/app/videos/video-watch/video-share.component.html deleted file mode 100644 index 88f59c063..000000000 --- a/client/src/app/videos/video-watch/video-share.component.html +++ /dev/null @@ -1,29 +0,0 @@ - diff --git a/client/src/app/videos/video-watch/video-share.component.ts b/client/src/app/videos/video-watch/video-share.component.ts deleted file mode 100644 index 133f93498..000000000 --- a/client/src/app/videos/video-watch/video-share.component.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Component, Input, ViewChild } from '@angular/core' - -import { ModalDirective } from 'ngx-bootstrap/modal' - -import { Video } from '../shared' - -@Component({ - selector: 'my-video-share', - templateUrl: './video-share.component.html' -}) -export class VideoShareComponent { - @Input() video: Video = null - - @ViewChild('modal') modal: ModalDirective - - constructor () { - // empty - } - - show () { - this.modal.show() - } - - hide () { - this.modal.hide() - } - - getVideoIframeCode () { - return '' - } - - getVideoUrl () { - return window.location.href - } - - notSecure () { - return window.location.protocol === 'http:' - } -} diff --git a/client/src/app/videos/video-watch/video-watch.component.html b/client/src/app/videos/video-watch/video-watch.component.html deleted file mode 100644 index 88863131a..000000000 --- a/client/src/app/videos/video-watch/video-watch.component.html +++ /dev/null @@ -1,184 +0,0 @@ -
-
- The video load seems to be abnormally long. - -
-
- -
- -
- -
- -
Video not found :'(
-
- - -
-
Download: {{ downloadSpeed | bytes }}/s
-
Upload: {{ uploadSpeed | bytes }}/s
-
Number of peers: {{ numPeers }}
-
- - -
-
-
- {{ video.name }} -
- -
- {{ video.views}} views -
-
- -
- - - - - - -
-
- - - - {{ video.likes }} - -
- -
- - - - {{ video.dislikes }} - -
-
-
- -
-
-
- Published on {{ video.createdAt | date:'short' }} -
- -
- {{ video.description }} -
-
- -
-
- - Category: - - - {{ video.categoryLabel }} - -
- -
- - Licence: - - - {{ video.licenceLabel }} - -
- -
- - Language: - - - {{ video.languageLabel }} - -
- -
- - Tags: - - - -
- -
-
-
- - - - - - diff --git a/client/src/app/videos/video-watch/video-watch.component.scss b/client/src/app/videos/video-watch/video-watch.component.scss deleted file mode 100644 index 69661747c..000000000 --- a/client/src/app/videos/video-watch/video-watch.component.scss +++ /dev/null @@ -1,245 +0,0 @@ -#video-container { - width: 100%; - height: 100%; -} - -#video-not-found { - height: 300px; - line-height: 300px; - margin-top: 50px; - text-align: center; - font-weight: bold; -} - -.embed-responsive { - height: 500px; - - @media screen and (max-width: 600px) { - height: 300px; - } -} - -#torrent-info { - font-size: 10px; - margin-top: 10px; - text-align: center; - - div { - min-width: 60px; - } -} - -#video-info { - .video-name-views { - font-weight: bold; - font-size: 18px; - height: $video-watch-title-height; - line-height: $video-watch-title-height; - - .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; - display: flex; - flex-direction: column; - justify-content: center; - text-align: 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; - } - } - - .option .glyphicon { - font-size: 22px; - color: inherit; - opacity: 0.15; - margin-bottom: 10px; - transition: opacity 0.3s; - } - - .video-small-block-text { - font-size: 15px; - font-weight: bold; - } - } - - .video-small-block:not(:last-child) { - border-width: 0 1px 0 0; - border-color: $video-watch-border-color; - border-style: solid; - } - - .video-small-block-author, .video-small-block-more { - a.option { - display: block; - - .glyphicon { - display: block; - } - } - } - - .video-small-block-share, .video-small-block-more { - a.option { - display: block; - - .glyphicon { - display: block; - } - } - } - - .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-details-date { - font-weight: bold; - margin-bottom: 30px; - } - } - - .video-details-attributes { - font-weight: bold; - font-size: 12px; - - .video-details-attribute-label { - color: $video-watch-info-color; - display: inline-block; - width: 60px; - margin-right: 5px; - } - } - - .video-details-tags { - display: inline-block; - - a { - display: inline-block; - margin-right: 3px; - font-size: 11px; - } - } - } - - @media screen and (max-width: 400px) { - .video-name-views { - font-size: 16px !important; - } - } - - @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-author { - padding-left: 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; - } - } - } - } -} diff --git a/client/src/app/videos/video-watch/video-watch.component.ts b/client/src/app/videos/video-watch/video-watch.component.ts deleted file mode 100644 index db3e1cdd6..000000000 --- a/client/src/app/videos/video-watch/video-watch.component.ts +++ /dev/null @@ -1,299 +0,0 @@ -import { Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' -import { ActivatedRoute, Router } from '@angular/router' -import { Observable } from 'rxjs/Observable' -import { Subscription } from 'rxjs/Subscription' - -import videojs from 'video.js' -import '../../../assets/player/peertube-videojs-plugin' - -import { MetaService } from '@ngx-meta/core' -import { NotificationsService } from 'angular2-notifications' - -import { AuthService, ConfirmService } from '../../core' -import { VideoMagnetComponent } from './video-magnet.component' -import { VideoShareComponent } from './video-share.component' -import { VideoReportComponent } from './video-report.component' -import { Video, VideoService } from '../shared' -import { WebTorrentService } from './webtorrent.service' -import { UserVideoRateType, VideoRateType } from '../../../../../shared' - -@Component({ - selector: 'my-video-watch', - templateUrl: './video-watch.component.html', - styleUrls: [ './video-watch.component.scss' ] -}) -export class VideoWatchComponent implements OnInit, OnDestroy { - @ViewChild('videoMagnetModal') videoMagnetModal: VideoMagnetComponent - @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: Video = null - videoNotFound = false - - private paramsSub: Subscription - - constructor ( - private elementRef: ElementRef, - private route: ActivatedRoute, - private router: Router, - private videoService: VideoService, - private confirmService: ConfirmService, - private metaService: MetaService, - private authService: AuthService, - private notificationsService: NotificationsService - ) {} - - ngOnInit () { - this.paramsSub = this.route.params.subscribe(routeParams => { - let uuid = routeParams['uuid'] - this.videoService.getVideo(uuid).subscribe( - video => this.onVideoFetched(video), - - error => { - console.error(error) - this.videoNotFound = true - } - ) - }) - } - - ngOnDestroy () { - // Remove player if it exists - if (this.videoNotFound === false) { - videojs(this.playerElement).dispose() - } - - // Unsubscribe subscriptions - this.paramsSub.unsubscribe() - } - - setLike () { - if (this.isUserLoggedIn() === false) return - // Already liked this video - if (this.userRating === 'like') return - - this.videoService.setVideoLike(this.video.id) - .subscribe( - () => { - // Update the video like attribute - this.updateVideoRating(this.userRating, 'like') - this.userRating = 'like' - }, - - err => this.notificationsService.error('Error', err.message) - ) - } - - setDislike () { - if (this.isUserLoggedIn() === false) return - // Already disliked this video - if (this.userRating === 'dislike') return - - this.videoService.setVideoDislike(this.video.id) - .subscribe( - () => { - // Update the video dislike attribute - this.updateVideoRating(this.userRating, 'dislike') - this.userRating = 'dislike' - }, - - err => this.notificationsService.error('Error', err.message) - ) - } - - 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() - - this.confirmService.confirm('Do you really want to blacklist this video ?', 'Blacklist').subscribe( - res => { - if (res === false) return - - this.videoService.blacklistVideo(this.video.id) - .subscribe( - status => { - this.notificationsService.success('Success', `Video ${this.video.name} had been blacklisted.`) - this.router.navigate(['/videos/list']) - }, - - error => this.notificationsService.error('Error', error.text) - ) - } - ) - } - - showReportModal (event: Event) { - event.preventDefault() - this.videoReportModal.show() - } - - showShareModal () { - this.videoShareModal.show() - } - - showMagnetUriModal (event: Event) { - event.preventDefault() - this.videoMagnetModal.show() - } - - isUserLoggedIn () { - 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()) - } - - private handleError (err: any) { - const errorMessage: string = typeof err === 'string' ? err : err.message - let message = '' - - if (errorMessage.indexOf('http error') !== -1) { - message = 'Cannot fetch video from server, maybe down.' - } else { - message = errorMessage - } - - this.notificationsService.error('Error', message) - } - - private checkUserRating () { - // Unlogged users do not have ratings - if (this.isUserLoggedIn() === false) return - - this.videoService.getUserVideoRating(this.video.id) - .subscribe( - ratingObject => { - if (ratingObject) { - this.userRating = ratingObject.rating - } - }, - - err => this.notificationsService.error('Error', err.message) - ) - } - - private onVideoFetched (video: Video) { - this.video = video - - let observable - if (this.video.isVideoNSFWForUser(this.authService.getUser())) { - observable = this.confirmService.confirm('This video is not safe for work. Are you sure you want to watch it?', 'NSFW') - } else { - observable = Observable.of(true) - } - - observable.subscribe( - res => { - if (res === false) { - return this.router.navigate([ '/videos/list' ]) - } - - this.playerElement = this.elementRef.nativeElement.querySelector('#video-container') - - const videojsOptions = { - controls: true, - autoplay: true, - plugins: { - peertube: { - videoFiles: this.video.files, - playerElement: this.playerElement, - autoplay: true, - peerTubeLink: false - } - } - } - - const self = this - videojs(this.playerElement, videojsOptions, function () { - self.player = this - 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.setOpenGraphTags() - this.checkUserRating() - } - ) - } - - private updateVideoRating (oldRating: UserVideoRateType, newRating: VideoRateType) { - let likesToIncrement = 0 - let dislikesToIncrement = 0 - - if (oldRating) { - if (oldRating === 'like') likesToIncrement-- - if (oldRating === 'dislike') dislikesToIncrement-- - } - - if (newRating === 'like') likesToIncrement++ - if (newRating === 'dislike') dislikesToIncrement++ - - this.video.likes += likesToIncrement - this.video.dislikes += dislikesToIncrement - } - - private setOpenGraphTags () { - this.metaService.setTitle(this.video.name) - - this.metaService.setTag('og:type', 'video') - - this.metaService.setTag('og:title', this.video.name) - this.metaService.setTag('name', this.video.name) - - this.metaService.setTag('og:description', this.video.description) - this.metaService.setTag('description', this.video.description) - - this.metaService.setTag('og:image', this.video.previewPath) - - this.metaService.setTag('og:duration', this.video.duration.toString()) - - this.metaService.setTag('og:site_name', 'PeerTube') - - this.metaService.setTag('og:url', window.location.href) - this.metaService.setTag('url', window.location.href) - } -} diff --git a/client/src/app/videos/videos-routing.module.ts b/client/src/app/videos/videos-routing.module.ts index 715671ba7..225b6b018 100644 --- a/client/src/app/videos/videos-routing.module.ts +++ b/client/src/app/videos/videos-routing.module.ts @@ -3,10 +3,8 @@ import { RouterModule, Routes } from '@angular/router' import { MetaGuard } from '@ngx-meta/core' -import { VideoAddComponent, VideoUpdateComponent } from './video-edit' import { VideoListComponent } from './video-list' import { VideosComponent } from './videos.component' -import { VideoWatchComponent } from './video-watch' const videosRoutes: Routes = [ { @@ -25,7 +23,7 @@ const videosRoutes: Routes = [ }, { path: 'add', - component: VideoAddComponent, + loadChildren: 'app/videos/+video-edit#VideoAddModule', data: { meta: { title: 'Add a video' @@ -34,7 +32,7 @@ const videosRoutes: Routes = [ }, { path: 'edit/:uuid', - component: VideoUpdateComponent, + loadChildren: 'app/videos/+video-edit#VideoUpdateModule', data: { meta: { title: 'Edit a video' @@ -47,7 +45,10 @@ const videosRoutes: Routes = [ }, { path: 'watch/:uuid', - component: VideoWatchComponent + loadChildren: 'app/videos/+video-watch#VideoWatchModule', + data: { + preload: 3000 + } } ] } diff --git a/client/src/app/videos/videos.component.ts b/client/src/app/videos/videos.component.ts index 972c2221f..26d9d28d4 100644 --- a/client/src/app/videos/videos.component.ts +++ b/client/src/app/videos/videos.component.ts @@ -1,8 +1,16 @@ -import { Component } from '@angular/core' +import { Component, OnInit } from '@angular/core' + +import { VideoService } from './shared' @Component({ template: '' }) +export class VideosComponent implements OnInit { + constructor(private videoService: VideoService) {} -export class VideosComponent { + ngOnInit () { + this.videoService.loadVideoCategories() + this.videoService.loadVideoLicences() + this.videoService.loadVideoLanguages() + } } diff --git a/client/src/app/videos/videos.module.ts b/client/src/app/videos/videos.module.ts index bc86118cc..3a0c3feac 100644 --- a/client/src/app/videos/videos.module.ts +++ b/client/src/app/videos/videos.module.ts @@ -1,24 +1,13 @@ import { NgModule } from '@angular/core' -import { TagInputModule } from 'ngx-chips' - import { VideosRoutingModule } from './videos-routing.module' import { VideosComponent } from './videos.component' -import { VideoAddComponent, VideoUpdateComponent } from './video-edit' import { LoaderComponent, VideoListComponent, VideoMiniatureComponent, VideoSortComponent } from './video-list' -import { - VideoWatchComponent, - VideoMagnetComponent, - VideoReportComponent, - VideoShareComponent -} from './video-watch' import { VideoService } from './shared' import { SharedModule } from '../shared' @NgModule({ imports: [ - TagInputModule, - VideosRoutingModule, SharedModule ], @@ -26,18 +15,10 @@ import { SharedModule } from '../shared' declarations: [ VideosComponent, - VideoAddComponent, - VideoUpdateComponent, - VideoListComponent, VideoMiniatureComponent, VideoSortComponent, - VideoWatchComponent, - VideoMagnetComponent, - VideoShareComponent, - VideoReportComponent, - LoaderComponent ], diff --git a/client/yarn.lock b/client/yarn.lock index 011ecce68..f2ee71447 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -2,71 +2,71 @@ # yarn lockfile v1 -"@angular/animations@~4.3.0": - version "4.3.6" - resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-4.3.6.tgz#bf9283ec7c8c98b32f569d84dcda10890fdc0262" +"@angular/animations@~4.4.0": + version "4.4.4" + resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-4.4.4.tgz#a2f9353604347abe15df98292058842f52f08bc2" dependencies: tslib "^1.7.1" -"@angular/common@~4.3.0": - version "4.3.6" - resolved "https://registry.yarnpkg.com/@angular/common/-/common-4.3.6.tgz#ed37e9307c7506dd834797c1a6cf675e52b5b6ee" +"@angular/common@~4.4.0": + version "4.4.4" + resolved "https://registry.yarnpkg.com/@angular/common/-/common-4.4.4.tgz#ae0a818aaa0c6a3f0901e7b80bd94e1c22eb9365" dependencies: tslib "^1.7.1" -"@angular/compiler-cli@~4.3.0": - version "4.3.6" - resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-4.3.6.tgz#6afa6aef68dd681e61b398be4d6270e5c8680b12" +"@angular/compiler-cli@~4.4.0": + version "4.4.4" + resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-4.4.4.tgz#063080a497d9175396825050222c717da184f6cf" dependencies: - "@angular/tsc-wrapped" "4.3.6" + "@angular/tsc-wrapped" "4.4.4" minimist "^1.2.0" reflect-metadata "^0.1.2" -"@angular/compiler@~4.3.0": - version "4.3.6" - resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-4.3.6.tgz#be170df098b71e835ccedf168d5fb7b23e5045b8" +"@angular/compiler@~4.4.0": + version "4.4.4" + resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-4.4.4.tgz#326eb0029d9a3541aaca124def9adc51c36f2b41" dependencies: tslib "^1.7.1" -"@angular/core@~4.3.0": - version "4.3.6" - resolved "https://registry.yarnpkg.com/@angular/core/-/core-4.3.6.tgz#bbac63d68d0f7bcb389d12b34208652be3287e96" +"@angular/core@~4.4.0": + version "4.4.4" + resolved "https://registry.yarnpkg.com/@angular/core/-/core-4.4.4.tgz#bd37ecf54158f97489996c9386bd222f80a32f5c" dependencies: tslib "^1.7.1" -"@angular/forms@~4.3.0": - version "4.3.6" - resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-4.3.6.tgz#0f20c4597c16a152745d7cd95559855a0a5c6687" +"@angular/forms@~4.4.0": + version "4.4.4" + resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-4.4.4.tgz#4db3790509b6b10f1db8a7c1b7f52187cf64cfd4" dependencies: tslib "^1.7.1" -"@angular/http@~4.3.0": - version "4.3.6" - resolved "https://registry.yarnpkg.com/@angular/http/-/http-4.3.6.tgz#563827d1a7d5e89e3b7d86b77fbbd367b2c08591" +"@angular/http@~4.4.0": + version "4.4.4" + resolved "https://registry.yarnpkg.com/@angular/http/-/http-4.4.4.tgz#667faf616bb624168eafae6ee92e5eba23a9d1f2" dependencies: tslib "^1.7.1" -"@angular/platform-browser-dynamic@~4.3.0": - version "4.3.6" - resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-4.3.6.tgz#9eabf826f119c98f85c2a96edcb18ab00b4efb1c" +"@angular/platform-browser-dynamic@~4.4.0": + version "4.4.4" + resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-4.4.4.tgz#c3c9eb854a528556a07054127932e527fa932e14" dependencies: tslib "^1.7.1" -"@angular/platform-browser@~4.3.0": - version "4.3.6" - resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-4.3.6.tgz#6152b1f3b78d0246fc5e150e2f7b9ed4337e3ba6" +"@angular/platform-browser@~4.4.0": + version "4.4.4" + resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-4.4.4.tgz#a3898e2e7ba9d84ffa0d47144c6971179c75aee6" dependencies: tslib "^1.7.1" -"@angular/router@~4.3.0": - version "4.3.6" - resolved "https://registry.yarnpkg.com/@angular/router/-/router-4.3.6.tgz#64033edb4fcda08a323e7533b4a1820c0f28d130" +"@angular/router@~4.4.0": + version "4.4.4" + resolved "https://registry.yarnpkg.com/@angular/router/-/router-4.4.4.tgz#7be391096e843cb3e04f9f05d1d65a88df9bc7cf" dependencies: tslib "^1.7.1" -"@angular/tsc-wrapped@4.3.6": - version "4.3.6" - resolved "https://registry.yarnpkg.com/@angular/tsc-wrapped/-/tsc-wrapped-4.3.6.tgz#1aa66e0ab2c4799a4ad14b675e13953aa5fcd436" +"@angular/tsc-wrapped@4.4.4": + version "4.4.4" + resolved "https://registry.yarnpkg.com/@angular/tsc-wrapped/-/tsc-wrapped-4.4.4.tgz#9841821e55616b826ca160250fe85e15fc74ffc3" dependencies: tsickle "^0.21.0" @@ -107,8 +107,8 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.25.tgz#66ecaf4df93f5281b48427ee96fbcdfc4f0cdce1" "@types/node@^6.0.38": - version "6.0.88" - resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.88.tgz#f618f11a944f6a18d92b5c472028728a3e3d4b66" + version "6.0.89" + resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.89.tgz#154be0e6a823760cd6083aa8c48f952e2e63e0b0" "@types/parse-torrent-file@*": version "4.0.1" @@ -153,8 +153,8 @@ resolved "https://registry.yarnpkg.com/@types/video.js/-/video.js-5.16.0.tgz#b35085050b41b81b9425faa3616f925239685f88" "@types/webpack@^3.0.0": - version "3.0.10" - resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-3.0.10.tgz#1d27db07df32109f8c882535b547aae4252fd53e" + version "3.0.13" + resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-3.0.13.tgz#5a49ae51e784e73bc46830a6a20656e85b8af0e6" dependencies: "@types/node" "*" "@types/tapable" "*" @@ -205,12 +205,12 @@ acorn@^5.0.0, acorn@^5.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.1.tgz#53fe161111f912ab999ee887a90a0bc52822fd75" add-asset-html-webpack-plugin@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/add-asset-html-webpack-plugin/-/add-asset-html-webpack-plugin-2.1.1.tgz#bbcdfbfad657847d1b9424e9ed4476650975180c" + version "2.1.2" + resolved "https://registry.yarnpkg.com/add-asset-html-webpack-plugin/-/add-asset-html-webpack-plugin-2.1.2.tgz#b3e60192602cdc53f03f2b19b7de36b5c4a6c7fe" dependencies: - bluebird "^3.4.6" + bluebird "^3.5.0" globby "^6.1.0" - minimatch "^3.0.3" + minimatch "^3.0.4" addr-to-ip-port@^1.0.1, addr-to-ip-port@^1.4.2: version "1.4.2" @@ -273,8 +273,8 @@ angular-pipes@^6.0.0: resolved "https://registry.yarnpkg.com/angular-pipes/-/angular-pipes-6.5.3.tgz#6bed37c51ebc2adaf3412663bfe25179d0489b02" angular2-notifications@^0.7.7: - version "0.7.7" - resolved "https://registry.yarnpkg.com/angular2-notifications/-/angular2-notifications-0.7.7.tgz#50471d7325483cd0679eeb670c6d3a4618647a2f" + version "0.7.8" + resolved "https://registry.yarnpkg.com/angular2-notifications/-/angular2-notifications-0.7.8.tgz#ecbcb95a8d2d402af94a9a080d6664c70d33a029" angular2-template-loader@^0.6.0: version "0.6.2" @@ -1126,14 +1126,14 @@ block-stream@*: dependencies: inherits "~2.0.0" -bluebird@^2.10.2: - version "2.11.0" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" - -bluebird@^3.4.6, bluebird@^3.4.7: +bluebird@^3.4.7: version "3.5.0" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" +bluebird@^3.5.0, bluebird@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" + bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" @@ -1567,8 +1567,8 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" codelyzer@^3.0.0-beta.4: - version "3.2.0" - resolved "https://registry.yarnpkg.com/codelyzer/-/codelyzer-3.2.0.tgz#68eb0a67771ea73006b517053c3035c1838abf14" + version "3.2.1" + resolved "https://registry.yarnpkg.com/codelyzer/-/codelyzer-3.2.1.tgz#5b1ac75f7e0eb04647842ee29a322bf2167e7229" dependencies: app-root-path "^2.0.1" css-selector-tokenizer "^0.7.0" @@ -1730,16 +1730,16 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" copy-webpack-plugin@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.0.1.tgz#9728e383b94316050d0c7463958f2b85c0aa8200" + version "4.1.1" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.1.1.tgz#53ae69e04955ebfa9fda411f54cbb968531d71fd" dependencies: - bluebird "^2.10.2" - fs-extra "^0.26.4" - glob "^6.0.4" - is-glob "^3.1.0" + bluebird "^3.5.1" + fs-extra "^4.0.2" + glob "^7.1.2" + is-glob "^4.0.0" loader-utils "^0.2.15" lodash "^4.3.0" - minimatch "^3.0.0" + minimatch "^3.0.4" node-dir "^0.1.10" core-js@^2.4.0, core-js@^2.5.0: @@ -2003,13 +2003,6 @@ deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" -default-gateway@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-2.0.2.tgz#e365db05c50a4643cc1990c6178228c540a0b910" - dependencies: - execa "^0.7.0" - ip-regex "^2.1.0" - define-properties@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" @@ -2683,8 +2676,8 @@ extglob@^1.1.0: to-regex "^2.1.0" extract-text-webpack-plugin@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.0.tgz#90caa7907bc449f335005e3ac7532b41b00de612" + version "3.0.1" + resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.1.tgz#605a8893faca1dd49bb0d2ca87493f33fd43d102" dependencies: async "^2.4.1" loader-utils "^1.1.0" @@ -2733,11 +2726,12 @@ file-entry-cache@^2.0.0: flat-cache "^1.2.1" object-assign "^4.0.1" -file-loader@^0.11.2: - version "0.11.2" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-0.11.2.tgz#4ff1df28af38719a6098093b88c82c71d1794a34" +file-loader@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-1.1.5.tgz#91c25b6b6fbe56dae99f10a425fd64933b5c9daa" dependencies: loader-utils "^1.0.2" + schema-utils "^0.3.0" filename-regex@^2.0.0: version "2.0.1" @@ -2892,15 +2886,13 @@ fs-chunk-store@^1.6.2: run-parallel "^1.1.2" thunky "^1.0.1" -fs-extra@^0.26.4: - version "0.26.7" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.26.7.tgz#9ae1fdd94897798edab76d0918cf42d0c3184fa9" +fs-extra@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.2.tgz#f91704c53d1b461f893452b0c307d9997647ab6b" dependencies: graceful-fs "^4.1.2" - jsonfile "^2.1.0" - klaw "^1.0.0" - path-is-absolute "^1.0.0" - rimraf "^2.2.8" + jsonfile "^4.0.0" + universalify "^0.1.0" fs.realpath@^1.0.0: version "1.0.0" @@ -3010,17 +3002,7 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" -glob@^6.0.4: - version "6.0.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@~7.1.1: +glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@~7.1.1: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: @@ -3078,7 +3060,7 @@ globule@^1.0.0: lodash "~4.17.4" minimatch "~3.0.2" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: +graceful-fs@^4.1.2, graceful-fs@^4.1.6: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -3361,6 +3343,12 @@ ini@~1.3.0: version "1.3.4" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" +inline-manifest-webpack-plugin@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/inline-manifest-webpack-plugin/-/inline-manifest-webpack-plugin-3.0.1.tgz#ca2151063115298e2fd94b669ab76c7dd63e44ad" + dependencies: + source-map-url "0.4.0" + inquirer@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" @@ -3379,12 +3367,11 @@ inquirer@^0.12.0: strip-ansi "^3.0.0" through "^2.3.6" -internal-ip@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-2.0.2.tgz#bed2b35491e8b42aee087de7614e870908ee80f2" +internal-ip@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c" dependencies: - default-gateway "^2.0.2" - ipaddr.js "^1.5.1" + meow "^3.3.0" interpret@^1.0.0: version "1.0.3" @@ -3404,10 +3391,6 @@ invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" -ip-regex@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" - ip-set@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/ip-set/-/ip-set-1.0.1.tgz#633b66d0bd6c8d0de968d053263c9120d3b6727e" @@ -3422,7 +3405,7 @@ ipaddr.js@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.4.0.tgz#296aca878a821816e5b85d0a285a99bcff4582f0" -"ipaddr.js@>= 0.1.5", ipaddr.js@^1.0.1, ipaddr.js@^1.5.1: +"ipaddr.js@>= 0.1.5", ipaddr.js@^1.0.1: version "1.5.2" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0" @@ -3548,6 +3531,12 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" +is-glob@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" + dependencies: + is-extglob "^2.1.1" + is-my-json-valid@^2.10.0: version "2.16.1" resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz#5a846777e2c2620d1e69104e5d3a03b1f6088f11" @@ -3741,12 +3730,18 @@ json5@^0.5.0, json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" -jsonfile@^2.1.0, jsonfile@^2.4.0: +jsonfile@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" optionalDependencies: graceful-fs "^4.1.6" +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + optionalDependencies: + graceful-fs "^4.1.6" + jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" @@ -3818,12 +3813,6 @@ kind-of@^5.0.0: version "5.0.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.0.2.tgz#f57bec933d9a2209ffa96c5c08343607b7035fda" -klaw@^1.0.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" - optionalDependencies: - graceful-fs "^4.1.9" - lazy-cache@^0.2.3: version "0.2.7" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65" @@ -4232,7 +4221,7 @@ memory-fs@^0.4.0, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" -meow@^3.7.0: +meow@^3.3.0, meow@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" dependencies: @@ -4312,10 +4301,14 @@ mime@1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" -mime@1.3.x, mime@^1.3.4: +mime@^1.3.4: version "1.3.6" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0" +mime@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" + mimic-fn@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" @@ -4338,7 +4331,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" -"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2: +minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -5352,8 +5345,8 @@ pretty-error@^2.0.2: utila "~0.4" primeng@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/primeng/-/primeng-4.2.0.tgz#49c8c99de26d254f41d3fbb8759227fe1d269772" + version "4.2.2" + resolved "https://registry.yarnpkg.com/primeng/-/primeng-4.2.2.tgz#b76116c24505ddcad7f52a4bba76b584d8e7c527" private@^0.1.6, private@^0.1.7, private@~0.1.5: version "0.1.7" @@ -5920,8 +5913,8 @@ sass-loader@^6.0.3: pify "^3.0.0" sass-resources-loader@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/sass-resources-loader/-/sass-resources-loader-1.3.0.tgz#f061f8df7cc8ecda9ee7fd9b602ca8ee2ea73612" + version "1.3.1" + resolved "https://registry.yarnpkg.com/sass-resources-loader/-/sass-resources-loader-1.3.1.tgz#6784abca83818e4f7c96a4da86af187e8615357e" dependencies: async "^2.1.4" chalk "^1.1.3" @@ -6207,8 +6200,8 @@ source-list-map@~0.1.7: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" source-map-loader@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-0.2.1.tgz#48126be9230bd47fad05e46a8c3c2e3d2dabe507" + version "0.2.2" + resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-0.2.2.tgz#1249348ff6a66ea64a2957fc98f74cb6bba67505" dependencies: async "^0.9.0" loader-utils "~0.2.2" @@ -6238,7 +6231,7 @@ source-map-support@^0.4.0, source-map-support@^0.4.15, source-map-support@^0.4.2 dependencies: source-map "^0.5.6" -source-map-url@^0.4.0: +source-map-url@0.4.0, source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" @@ -6495,9 +6488,9 @@ strip-json-comments@^2.0.0, strip-json-comments@^2.0.1, strip-json-comments@~2.0 version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" -style-loader@^0.18.2: - version "0.18.2" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.18.2.tgz#cc31459afbcd6d80b7220ee54b291a9fd66ff5eb" +style-loader@^0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.19.0.tgz#7258e788f0fee6a42d710eaf7d6c2412a4c50759" dependencies: loader-utils "^1.0.2" schema-utils "^0.3.0" @@ -6822,8 +6815,8 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" typescript@^2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.5.2.tgz#038a95f7d9bbb420b1bf35ba31d4c5c1dd3ffe34" + version "2.5.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.5.3.tgz#df3dcdc38f3beb800d4bc322646b04a3f6ca7f0d" uglify-js@3.0.x, uglify-js@^3.0.6: version "3.0.28" @@ -6888,6 +6881,10 @@ uniqs@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" +universalify@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7" + unordered-array-remove@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unordered-array-remove/-/unordered-array-remove-1.0.2.tgz#c546e8f88e317a0cf2644c97ecb57dba66d250ef" @@ -6915,12 +6912,13 @@ urix@^0.1.0, urix@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" -url-loader@^0.5.7: - version "0.5.9" - resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.5.9.tgz#cc8fea82c7b906e7777019250869e569e995c295" +url-loader@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.6.2.tgz#a007a7109620e9d988d14bce677a1decb9a993f7" dependencies: loader-utils "^1.0.2" - mime "1.3.x" + mime "^1.4.1" + schema-utils "^0.3.0" url-parse@1.0.x: version "1.0.5" @@ -7055,8 +7053,8 @@ video.js@^5.19.2: xhr "2.2.2" video.js@^6.2.0: - version "6.2.8" - resolved "https://registry.yarnpkg.com/video.js/-/video.js-6.2.8.tgz#e449710bf8513f607456293ae1da97559a94fb97" + version "6.3.2" + resolved "https://registry.yarnpkg.com/video.js/-/video.js-6.3.2.tgz#53f7cd08e4219157e4053b795673c3a9fb3d3072" dependencies: babel-runtime "^6.9.2" global "4.3.2" @@ -7158,8 +7156,8 @@ webpack-dev-middleware@^1.11.0: time-stamp "^2.0.0" webpack-dev-server@^2.4.5: - version "2.8.2" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.8.2.tgz#abd61f410778cc4c843d7cebbf41465b1ab7734c" + version "2.9.1" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.9.1.tgz#7ac9320b61b00eb65b2109f15c82747fc5b93585" dependencies: ansi-html "0.0.7" array-includes "^3.0.3" @@ -7171,7 +7169,7 @@ webpack-dev-server@^2.4.5: express "^4.13.3" html-entities "^1.2.0" http-proxy-middleware "~0.17.4" - internal-ip "^2.0.2" + internal-ip "1.2.0" ip "^1.1.5" loglevel "^1.4.1" opn "^5.1.0" @@ -7222,8 +7220,8 @@ webpack-sources@^1.0.1: source-map "~0.5.3" webpack@^3.3.0: - version "3.5.6" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.5.6.tgz#a492fb6c1ed7f573816f90e00c8fbb5a20cc5c36" + version "3.6.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.6.0.tgz#a89a929fbee205d35a4fa2cc487be9cbec8898bc" dependencies: acorn "^5.0.0" acorn-dynamic-import "^2.0.0" @@ -7526,5 +7524,5 @@ zero-fill@^2.2.3: resolved "https://registry.yarnpkg.com/zero-fill/-/zero-fill-2.2.3.tgz#a3def06ba5e39ae644850bb4ca2ad4112b4855e9" zone.js@~0.8.5: - version "0.8.17" - resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.8.17.tgz#4c5e5185a857da8da793daf3919371c5a36b2a0b" + version "0.8.18" + resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.8.18.tgz#8cecb3977fcd1b3090562ff4570e2847e752b48d" -- cgit v1.2.3