# 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
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')
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
}
),
- /*
- * 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.
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 }),
/**
new ngcWebpack.NgcWebpackPlugin({
disabled: !AOT,
tsConfig: helpers.root('tsconfig.webpack.json')
- })
+ }),
+
+ new InlineManifestWebpackPlugin(),
],
/*
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
*/
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
*
* 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: {
},
"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",
"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",
"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",
"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",
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 = [
{
imports: [
RouterModule.forRoot(routes, {
useHash: Boolean(history.pushState) === false,
- preloadingStrategy: PreloadAllModules
+ preloadingStrategy: PreloadSelectedModulesList
})
],
+ providers: [ PreloadSelectedModulesList ],
exports: [ RouterModule ]
})
export class AppRoutingModule {}
import { Router } from '@angular/router'
import { AuthService, ConfigService } from './core'
-import { VideoService } from './videos'
import { UserService } from './shared'
@Component({
private router: Router,
private authService: AuthService,
private configService: ConfigService,
- private userService: UserService,
- private videoService: VideoService
+ private userService: UserService
) {}
ngOnInit () {
}
this.configService.loadConfig()
- this.videoService.loadVideoCategories()
- this.videoService.loadVideoLicences()
- this.videoService.loadVideoLanguages()
// Do not display menu on small screens
if (window.innerWidth < 600) {
export * from './config'
export * from './confirm'
export * from './menu'
+export * from './routing'
export * from './core.module'
--- /dev/null
+export * from './preload-selected-modules-list'
--- /dev/null
+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<any> {
+ 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();
+ }
+}
--- /dev/null
+export * from './video-add.module'
+export * from './video-update.module'
--- /dev/null
+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 {}
--- /dev/null
+<div class="row">
+ <div class="content-padding">
+
+ <h3>Upload a video</h3>
+
+ <div *ngIf="error !== undefined" class="alert alert-danger">{{ error }}</div>
+
+ <form novalidate [formGroup]="form">
+ <div class="form-group">
+ <label for="name">Name</label>
+ <input
+ type="text" class="form-control" id="name"
+ formControlName="name"
+ >
+ <div *ngIf="formErrors.name" class="alert alert-danger">
+ {{ formErrors.name }}
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="nsfw">NSFW</label>
+ <input
+ type="checkbox" id="nsfw"
+ formControlName="nsfw"
+ >
+ </div>
+
+ <div class="form-group">
+ <label for="category">Category</label>
+ <select class="form-control" id="category" formControlName="category">
+ <option></option>
+ <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option>
+ </select>
+
+ <div *ngIf="formErrors.category" class="alert alert-danger">
+ {{ formErrors.category }}
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="licence">Licence</label>
+ <select class="form-control" id="licence" formControlName="licence">
+ <option></option>
+ <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option>
+ </select>
+
+ <div *ngIf="formErrors.licence" class="alert alert-danger">
+ {{ formErrors.licence }}
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="language">Language</label>
+ <select class="form-control" id="language" formControlName="language">
+ <option></option>
+ <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option>
+ </select>
+
+ <div *ngIf="formErrors.language" class="alert alert-danger">
+ {{ formErrors.language }}
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label class="label-tags">Tags</label> <span class="little-information">(press enter to add the tag)</span>
+ <tag-input
+ [ngModel]="tags" [validators]="tagValidators" [errorMessages]="tagValidatorsMessages"
+ formControlName="tags" maxItems="3" modelAsStrings="true"
+ ></tag-input>
+ </div>
+
+ <div class="form-group">
+ <label for="videofile">File</label>
+ <div class="btn btn-default btn-file">
+ <span>Select the video...</span>
+ <input #videofileInput type="file" name="videofile" id="videofile" (change)="fileChange($event)" />
+ <input type="hidden" name="videofileHidden" formControlName="videofile"/>
+ </div>
+ </div>
+
+ <div class="file-to-upload">
+ <div class="file" *ngIf="filename">
+ <span class="filename">{{ filename }}</span>
+ <span class="glyphicon glyphicon-remove" (click)="removeFile()"></span>
+ </div>
+ </div>
+
+ <div *ngIf="formErrors.videofile" class="alert alert-danger">
+ {{ formErrors.videofile }}
+ </div>
+
+ <div class="form-group">
+ <label for="description">Description</label>
+ <textarea
+ id="description" class="form-control" placeholder="Description..."
+ formControlName="description"
+ >
+ </textarea>
+ <div *ngIf="formErrors.description" class="alert alert-danger">
+ {{ formErrors.description }}
+ </div>
+ </div>
+
+ <div class="progress">
+ <progressbar [value]="progressPercent" max="100">
+ <ng-template [ngIf]="progressPercent === 100">
+ <span class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></span>
+ Server is processing the video
+ </ng-template>
+ </progressbar>
+ </div>
+
+ <div class="form-group">
+ <input
+ type="button" value="Upload" class="btn btn-default form-control"
+ (click)="upload()"
+ >
+ </div>
+ </form>
+ </div>
+</div>
--- /dev/null
+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
+ }
+ )
+ }
+}
--- /dev/null
+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 { }
--- /dev/null
+.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;
+}
--- /dev/null
+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 {}
--- /dev/null
+<div class="row">
+ <div class="content-padding">
+
+ <h3>Update {{ video?.name }}</h3>
+
+ <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
+
+ <form novalidate [formGroup]="form">
+ <div class="form-group">
+ <label for="name">Name</label>
+ <input
+ type="text" class="form-control" id="name"
+ formControlName="name"
+ >
+ <div *ngIf="formErrors.name" class="alert alert-danger">
+ {{ formErrors.name }}
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="nsfw">NSFW</label>
+ <input
+ type="checkbox" id="nsfw"
+ formControlName="nsfw"
+ >
+ </div>
+
+ <div class="form-group">
+ <label for="category">Category</label>
+ <select class="form-control" id="category" formControlName="category">
+ <option></option>
+ <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option>
+ </select>
+
+ <div *ngIf="formErrors.category" class="alert alert-danger">
+ {{ formErrors.category }}
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="licence">Licence</label>
+ <select class="form-control" id="licence" formControlName="licence">
+ <option></option>
+ <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option>
+ </select>
+
+ <div *ngIf="formErrors.licence" class="alert alert-danger">
+ {{ formErrors.licence }}
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="language">Language</label>
+ <select class="form-control" id="language" formControlName="language">
+ <option></option>
+ <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option>
+ </select>
+
+ <div *ngIf="formErrors.language" class="alert alert-danger">
+ {{ formErrors.language }}
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="tags" class="label-tags">Tags</label> <span class="little-information">(press enter to add the tag)</span>
+ <tag-input
+ [ngModel]="tags" [validators]="tagValidators" [errorMessages]="tagValidatorsMessages"
+ formControlName="tags" maxItems="3" modelAsStrings="true"
+ ></tag-input>
+ </div>
+
+ <div class="form-group">
+ <label for="description">Description</label>
+ <textarea
+ id="description" class="form-control" placeholder="Description..."
+ formControlName="description"
+ >
+ </textarea>
+ <div *ngIf="formErrors.description" class="alert alert-danger">
+ {{ formErrors.description }}
+ </div>
+ </div>
+
+ <div class="form-group">
+ <input
+ type="button" value="Update" class="btn btn-default form-control"
+ (click)="update()"
+ >
+ </div>
+ </form>
+ </div>
+</div>
--- /dev/null
+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())
+ }
+}
--- /dev/null
+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 { }
--- /dev/null
+export * from './video-watch.module'
--- /dev/null
+<div bsModal #modal="bs-modal" class="modal" tabindex="-1">
+ <div class="modal-dialog">
+ <div class="modal-content modal-lg">
+
+ <div class="modal-header">
+ <button type="button" class="close" aria-label="Close" (click)="hide()">
+ <span aria-hidden="true">×</span>
+ </button>
+ <h4 class="modal-title">Magnet Uri</h4>
+ </div>
+
+ <div class="modal-body">
+ <div *ngFor="let file of video.files">
+ <label>{{ file.resolutionLabel }}</label>
+ <input #magnetUriInput (click)="magnetUriInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="file.magnetUri" />
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
--- /dev/null
+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()
+ }
+}
--- /dev/null
+<div bsModal #modal="bs-modal" class="modal" tabindex="-1">
+ <div class="modal-dialog">
+ <div class="modal-content modal-lg">
+
+ <div class="modal-header">
+ <button type="button" class="close" aria-label="Close" (click)="hide()">
+ <span aria-hidden="true">×</span>
+ </button>
+ <h4 class="modal-title">Report video</h4>
+ </div>
+
+ <div class="modal-body">
+
+ <form novalidate [formGroup]="form">
+ <div class="form-group">
+ <label for="description">Reason</label>
+ <textarea
+ id="reason" class="form-control" placeholder="Reason..."
+ formControlName="reason"
+ >
+ </textarea>
+ <div *ngIf="formErrors.reason" class="alert alert-danger">
+ {{ formErrors.reason }}
+ </div>
+ </div>
+
+ <div class="form-group">
+ <input
+ type="button" value="Report" class="btn btn-default form-control"
+ [disabled]="!form.valid" (click)="report()"
+ >
+ </div>
+ </form>
+
+ </div>
+ </div>
+ </div>
+</div>
--- /dev/null
+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)
+ )
+ }
+}
--- /dev/null
+<div bsModal #modal="bs-modal" class="modal" tabindex="-1">
+ <div class="modal-dialog modal-lg">
+ <div class="modal-content">
+
+ <div class="modal-header">
+ <button type="button" class="close" aria-label="Close" (click)="hide()">
+ <span aria-hidden="true">×</span>
+ </button>
+ <h4 class="modal-title">Share</h4>
+ </div>
+
+ <div class="modal-body">
+ <div class="form-group">
+ <label>URL</label>
+ <input #urlInput (click)="urlInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="getVideoUrl()" />
+ </div>
+
+ <div class="form-group">
+ <label>Embed</label>
+ <input #shareInput (click)="shareInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="getVideoIframeCode()" />
+ </div>
+
+ <div *ngIf="notSecure()" class="alert alert-warning">
+ The url is not secured (no HTTPS), so the embed video won't work on HTTPS websites (web browsers block non secured HTTP requests on HTTPS websites).
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
--- /dev/null
+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 '<iframe width="560" height="315" ' +
+ 'src="' + window.location.origin + '/videos/embed/' + this.video.uuid + '" ' +
+ 'frameborder="0" allowfullscreen>' +
+ '</iframe>'
+ }
+
+ getVideoUrl () {
+ return window.location.href
+ }
+
+ notSecure () {
+ return window.location.protocol === 'http:'
+ }
+}
--- /dev/null
+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 {}
--- /dev/null
+<div *ngIf="error" class="row">
+ <div class="alert alert-danger">
+ The video load seems to be abnormally long.
+ <ul>
+ <li>Maybe the server {{ video.podHost }} is down :(</li>
+ <li>
+ If not, you can report an issue on
+ <a href="https://github.com/Chocobozzz/PeerTube/issues" title="Report an issue">
+ https://github.com/Chocobozzz/PeerTube/issues
+ </a>
+ </li>
+ </ul>
+ </div>
+</div>
+
+<div class="row">
+ <!-- We need the video container for videojs so we just hide it -->
+ <div [hidden]="videoNotFound" class="embed-responsive embed-responsive-19by9">
+ <video id="video-container" class="video-js vjs-sublime-skin"></video>
+ </div>
+
+ <div *ngIf="videoNotFound" id="video-not-found">Video not found :'(</div>
+</div>
+
+<!-- P2P informations -->
+<div id="torrent-info" class="row">
+ <div id="torrent-info-download" class="col-md-4 col-sm-4 col-xs-4">Download: {{ downloadSpeed | bytes }}/s</div>
+ <div id="torrent-info-upload" class="col-md-4 col-sm-4 col-xs-4">Upload: {{ uploadSpeed | bytes }}/s</div>
+ <div id="torrent-info-peers" class="col-md-4 col-sm-4 col-xs-4">Number of peers: {{ numPeers }}</div>
+</div>
+
+<!-- Video informations -->
+<div *ngIf="video !== null" id="video-info">
+ <div class="row video-name-views">
+ <div class="col-xs-8 col-md-8 video-name">
+ {{ video.name }}
+ </div>
+
+ <div class="col-xs-4 col-md-4 pull-right video-views">
+ {{ video.views}} views
+ </div>
+ </div>
+
+ <div class="row video-small-blocks">
+ <div class="col-xs-5 col-xs-3 col-md-3 video-small-block video-small-block-author">
+ <a class="option" title="Access to all videos of this user" [routerLink]="['/videos/list', { field: 'author', search: video.author }]">
+ <span class="glyphicon glyphicon-user"></span>
+ <span class="video-small-block-text">{{ video.by }}</span>
+ </a>
+ </div>
+
+ <div class="col-xs-2 col-md-3 video-small-block video-small-block-share">
+ <a class="option" (click)="showShareModal()" title="Share the video">
+ <span class="glyphicon glyphicon-share"></span>
+ <span class="video-small-block-text">Share</span>
+ </a>
+ </div>
+
+ <div class="col-xs-2 col-md-3 video-small-block video-small-block-more">
+ <div class="video-small-block-dropdown" dropdown dropup="true" placement="right">
+ <a class="option" title="Access to more options" dropdownToggle>
+ <span class="glyphicon glyphicon-option-horizontal"></span>
+ <span class="video-small-block-text">More</span>
+ </a>
+
+ <ul *dropdownMenu class="dropdown-menu" id="more-menu" role="menu" aria-labelledby="single-button">
+ <li *ngIf="canUserUpdateVideo()" role="menuitem">
+ <a class="dropdown-item" title="Update this video" href="#" [routerLink]="[ '/videos/edit', video.uuid ]">
+ <span class="glyphicon glyphicon-pencil"></span> Update
+ </a>
+ </li>
+
+ <li role="menuitem">
+ <a class="dropdown-item" title="Get magnet URI" href="#" (click)="showMagnetUriModal($event)">
+ <span class="glyphicon glyphicon-magnet"></span> Magnet
+ </a>
+ </li>
+
+ <li *ngIf="isUserLoggedIn()" role="menuitem">
+ <a class="dropdown-item" title="Report this video" href="#" (click)="showReportModal($event)">
+ <span class="glyphicon glyphicon-alert"></span> Report
+ </a>
+ </li>
+
+ <li *ngIf="isVideoRemovable()" role="menuitem">
+ <a class="dropdown-item" title="Delete this video" href="#" (click)="removeVideo($event)">
+ <span class="glyphicon glyphicon-remove"></span> Delete
+ </a>
+ </li>
+
+ <li *ngIf="isVideoBlacklistable()" role="menuitem">
+ <a class="dropdown-item" title="Blacklist this video" href="#" (click)="blacklistVideo($event)">
+ <span class="glyphicon glyphicon-eye-close"></span> Blacklist
+ </a>
+ </li>
+ </ul>
+ </div>
+ </div>
+
+ <div class="col-xs-3 col-md-3 video-small-block video-small-block-rating">
+ <div class="video-small-block-like">
+ <span
+ class="glyphicon glyphicon-thumbs-up" title="Like this video"
+ [ngClass]="{ 'interactive': isUserLoggedIn(), 'activated': userRating === 'like' }" (click)="setLike()"
+ ></span>
+
+ <span class="video-small-block-text">
+ {{ video.likes }}
+ </span>
+ </div>
+
+ <div class="video-small-block-dislike">
+ <span
+ class="glyphicon glyphicon-thumbs-down" title="Dislike this video"
+ [ngClass]="{ 'interactive': isUserLoggedIn(), 'activated': userRating === 'dislike' }" (click)="setDislike()"
+ ></span>
+
+ <span class="video-small-block-text">
+ {{ video.dislikes }}
+ </span>
+ </div>
+ </div>
+ </div>
+
+ <div class="row video-details">
+ <div class="video-details-date-description col-xs-8 col-md-9">
+ <div class="video-details-date">
+ Published on {{ video.createdAt | date:'short' }}
+ </div>
+
+ <div class="video-details-description">
+ {{ video.description }}
+ </div>
+ </div>
+
+ <div class="video-details-attributes col-xs-4 col-md-3">
+ <div class="video-details-attribute">
+ <span class="video-details-attribute-label">
+ Category:
+ </span>
+ <span class="video-details-attribute-value">
+ {{ video.categoryLabel }}
+ </span>
+ </div>
+
+ <div class="video-details-attribute">
+ <span class="video-details-attribute-label">
+ Licence:
+ </span>
+ <span class="video-details-attribute-value">
+ {{ video.licenceLabel }}
+ </span>
+ </div>
+
+ <div class="video-details-attribute">
+ <span class="video-details-attribute-label">
+ Language:
+ </span>
+ <span class="video-details-attribute-value">
+ {{ video.languageLabel }}
+ </span>
+ </div>
+
+ <div class="video-details-attribute">
+ <span class="video-details-attribute-label">
+ Tags:
+ </span>
+
+ <div class="video-details-tags">
+ <a *ngFor="let tag of video.tags" [routerLink]="['/videos/list', { field: 'tags', search: tag }]" class="label label-primary">
+ {{ tag }}
+ </a>
+ </div>
+ </div>
+
+ </div>
+ </div>
+</div>
+
+<ng-template [ngIf]="video !== null">
+ <my-video-share #videoShareModal [video]="video"></my-video-share>
+ <my-video-magnet #videoMagnetModal [video]="video"></my-video-magnet>
+ <my-video-report #videoReportModal [video]="video"></my-video-report>
+</ng-template>
--- /dev/null
+#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;
+ }
+ }
+ }
+ }
+}
--- /dev/null
+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)
+ }
+}
--- /dev/null
+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 { }
-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'
+++ /dev/null
-export * from './video-add.component'
-export * from './video-update.component'
+++ /dev/null
-<div class="row">
- <div class="content-padding">
-
- <h3>Upload a video</h3>
-
- <div *ngIf="error !== undefined" class="alert alert-danger">{{ error }}</div>
-
- <form novalidate [formGroup]="form">
- <div class="form-group">
- <label for="name">Name</label>
- <input
- type="text" class="form-control" id="name"
- formControlName="name"
- >
- <div *ngIf="formErrors.name" class="alert alert-danger">
- {{ formErrors.name }}
- </div>
- </div>
-
- <div class="form-group">
- <label for="nsfw">NSFW</label>
- <input
- type="checkbox" id="nsfw"
- formControlName="nsfw"
- >
- </div>
-
- <div class="form-group">
- <label for="category">Category</label>
- <select class="form-control" id="category" formControlName="category">
- <option></option>
- <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option>
- </select>
-
- <div *ngIf="formErrors.category" class="alert alert-danger">
- {{ formErrors.category }}
- </div>
- </div>
-
- <div class="form-group">
- <label for="licence">Licence</label>
- <select class="form-control" id="licence" formControlName="licence">
- <option></option>
- <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option>
- </select>
-
- <div *ngIf="formErrors.licence" class="alert alert-danger">
- {{ formErrors.licence }}
- </div>
- </div>
-
- <div class="form-group">
- <label for="language">Language</label>
- <select class="form-control" id="language" formControlName="language">
- <option></option>
- <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option>
- </select>
-
- <div *ngIf="formErrors.language" class="alert alert-danger">
- {{ formErrors.language }}
- </div>
- </div>
-
- <div class="form-group">
- <label class="label-tags">Tags</label> <span class="little-information">(press enter to add the tag)</span>
- <tag-input
- [ngModel]="tags" [validators]="tagValidators" [errorMessages]="tagValidatorsMessages"
- formControlName="tags" maxItems="3" modelAsStrings="true"
- ></tag-input>
- </div>
-
- <div class="form-group">
- <label for="videofile">File</label>
- <div class="btn btn-default btn-file">
- <span>Select the video...</span>
- <input #videofileInput type="file" name="videofile" id="videofile" (change)="fileChange($event)" />
- <input type="hidden" name="videofileHidden" formControlName="videofile"/>
- </div>
- </div>
-
- <div class="file-to-upload">
- <div class="file" *ngIf="filename">
- <span class="filename">{{ filename }}</span>
- <span class="glyphicon glyphicon-remove" (click)="removeFile()"></span>
- </div>
- </div>
-
- <div *ngIf="formErrors.videofile" class="alert alert-danger">
- {{ formErrors.videofile }}
- </div>
-
- <div class="form-group">
- <label for="description">Description</label>
- <textarea
- id="description" class="form-control" placeholder="Description..."
- formControlName="description"
- >
- </textarea>
- <div *ngIf="formErrors.description" class="alert alert-danger">
- {{ formErrors.description }}
- </div>
- </div>
-
- <div class="progress">
- <progressbar [value]="progressPercent" max="100">
- <ng-template [ngIf]="progressPercent === 100">
- <span class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></span>
- Server is processing the video
- </ng-template>
- </progressbar>
- </div>
-
- <div class="form-group">
- <input
- type="button" value="Upload" class="btn btn-default form-control"
- (click)="upload()"
- >
- </div>
- </form>
- </div>
-</div>
+++ /dev/null
-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
- }
- )
- }
-}
+++ /dev/null
-.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;
-}
+++ /dev/null
-<div class="row">
- <div class="content-padding">
-
- <h3>Update {{ video?.name }}</h3>
-
- <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
-
- <form novalidate [formGroup]="form">
- <div class="form-group">
- <label for="name">Name</label>
- <input
- type="text" class="form-control" id="name"
- formControlName="name"
- >
- <div *ngIf="formErrors.name" class="alert alert-danger">
- {{ formErrors.name }}
- </div>
- </div>
-
- <div class="form-group">
- <label for="nsfw">NSFW</label>
- <input
- type="checkbox" id="nsfw"
- formControlName="nsfw"
- >
- </div>
-
- <div class="form-group">
- <label for="category">Category</label>
- <select class="form-control" id="category" formControlName="category">
- <option></option>
- <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option>
- </select>
-
- <div *ngIf="formErrors.category" class="alert alert-danger">
- {{ formErrors.category }}
- </div>
- </div>
-
- <div class="form-group">
- <label for="licence">Licence</label>
- <select class="form-control" id="licence" formControlName="licence">
- <option></option>
- <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option>
- </select>
-
- <div *ngIf="formErrors.licence" class="alert alert-danger">
- {{ formErrors.licence }}
- </div>
- </div>
-
- <div class="form-group">
- <label for="language">Language</label>
- <select class="form-control" id="language" formControlName="language">
- <option></option>
- <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option>
- </select>
-
- <div *ngIf="formErrors.language" class="alert alert-danger">
- {{ formErrors.language }}
- </div>
- </div>
-
- <div class="form-group">
- <label for="tags" class="label-tags">Tags</label> <span class="little-information">(press enter to add the tag)</span>
- <tag-input
- [ngModel]="tags" [validators]="tagValidators" [errorMessages]="tagValidatorsMessages"
- formControlName="tags" maxItems="3" modelAsStrings="true"
- ></tag-input>
- </div>
-
- <div class="form-group">
- <label for="description">Description</label>
- <textarea
- id="description" class="form-control" placeholder="Description..."
- formControlName="description"
- >
- </textarea>
- <div *ngIf="formErrors.description" class="alert alert-danger">
- {{ formErrors.description }}
- </div>
- </div>
-
- <div class="form-group">
- <input
- type="button" value="Update" class="btn btn-default form-control"
- (click)="update()"
- >
- </div>
- </form>
- </div>
-</div>
+++ /dev/null
-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())
- }
-}
+++ /dev/null
-export * from './video-magnet.component'
-export * from './video-share.component'
-export * from './video-report.component'
-export * from './video-watch.component'
+++ /dev/null
-<div bsModal #modal="bs-modal" class="modal" tabindex="-1">
- <div class="modal-dialog">
- <div class="modal-content modal-lg">
-
- <div class="modal-header">
- <button type="button" class="close" aria-label="Close" (click)="hide()">
- <span aria-hidden="true">×</span>
- </button>
- <h4 class="modal-title">Magnet Uri</h4>
- </div>
-
- <div class="modal-body">
- <div *ngFor="let file of video.files">
- <label>{{ file.resolutionLabel }}</label>
- <input #magnetUriInput (click)="magnetUriInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="file.magnetUri" />
- </div>
- </div>
- </div>
- </div>
-</div>
+++ /dev/null
-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()
- }
-}
+++ /dev/null
-<div bsModal #modal="bs-modal" class="modal" tabindex="-1">
- <div class="modal-dialog">
- <div class="modal-content modal-lg">
-
- <div class="modal-header">
- <button type="button" class="close" aria-label="Close" (click)="hide()">
- <span aria-hidden="true">×</span>
- </button>
- <h4 class="modal-title">Report video</h4>
- </div>
-
- <div class="modal-body">
-
- <form novalidate [formGroup]="form">
- <div class="form-group">
- <label for="description">Reason</label>
- <textarea
- id="reason" class="form-control" placeholder="Reason..."
- formControlName="reason"
- >
- </textarea>
- <div *ngIf="formErrors.reason" class="alert alert-danger">
- {{ formErrors.reason }}
- </div>
- </div>
-
- <div class="form-group">
- <input
- type="button" value="Report" class="btn btn-default form-control"
- [disabled]="!form.valid" (click)="report()"
- >
- </div>
- </form>
-
- </div>
- </div>
- </div>
-</div>
+++ /dev/null
-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)
- )
- }
-}
+++ /dev/null
-<div bsModal #modal="bs-modal" class="modal" tabindex="-1">
- <div class="modal-dialog modal-lg">
- <div class="modal-content">
-
- <div class="modal-header">
- <button type="button" class="close" aria-label="Close" (click)="hide()">
- <span aria-hidden="true">×</span>
- </button>
- <h4 class="modal-title">Share</h4>
- </div>
-
- <div class="modal-body">
- <div class="form-group">
- <label>URL</label>
- <input #urlInput (click)="urlInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="getVideoUrl()" />
- </div>
-
- <div class="form-group">
- <label>Embed</label>
- <input #shareInput (click)="shareInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="getVideoIframeCode()" />
- </div>
-
- <div *ngIf="notSecure()" class="alert alert-warning">
- The url is not secured (no HTTPS), so the embed video won't work on HTTPS websites (web browsers block non secured HTTP requests on HTTPS websites).
- </div>
- </div>
- </div>
- </div>
-</div>
+++ /dev/null
-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 '<iframe width="560" height="315" ' +
- 'src="' + window.location.origin + '/videos/embed/' + this.video.uuid + '" ' +
- 'frameborder="0" allowfullscreen>' +
- '</iframe>'
- }
-
- getVideoUrl () {
- return window.location.href
- }
-
- notSecure () {
- return window.location.protocol === 'http:'
- }
-}
+++ /dev/null
-<div *ngIf="error" class="row">
- <div class="alert alert-danger">
- The video load seems to be abnormally long.
- <ul>
- <li>Maybe the server {{ video.podHost }} is down :(</li>
- <li>
- If not, you can report an issue on
- <a href="https://github.com/Chocobozzz/PeerTube/issues" title="Report an issue">
- https://github.com/Chocobozzz/PeerTube/issues
- </a>
- </li>
- </ul>
- </div>
-</div>
-
-<div class="row">
- <!-- We need the video container for videojs so we just hide it -->
- <div [hidden]="videoNotFound" class="embed-responsive embed-responsive-19by9">
- <video id="video-container" class="video-js vjs-sublime-skin"></video>
- </div>
-
- <div *ngIf="videoNotFound" id="video-not-found">Video not found :'(</div>
-</div>
-
-<!-- P2P informations -->
-<div id="torrent-info" class="row">
- <div id="torrent-info-download" class="col-md-4 col-sm-4 col-xs-4">Download: {{ downloadSpeed | bytes }}/s</div>
- <div id="torrent-info-upload" class="col-md-4 col-sm-4 col-xs-4">Upload: {{ uploadSpeed | bytes }}/s</div>
- <div id="torrent-info-peers" class="col-md-4 col-sm-4 col-xs-4">Number of peers: {{ numPeers }}</div>
-</div>
-
-<!-- Video informations -->
-<div *ngIf="video !== null" id="video-info">
- <div class="row video-name-views">
- <div class="col-xs-8 col-md-8 video-name">
- {{ video.name }}
- </div>
-
- <div class="col-xs-4 col-md-4 pull-right video-views">
- {{ video.views}} views
- </div>
- </div>
-
- <div class="row video-small-blocks">
- <div class="col-xs-5 col-xs-3 col-md-3 video-small-block video-small-block-author">
- <a class="option" title="Access to all videos of this user" [routerLink]="['/videos/list', { field: 'author', search: video.author }]">
- <span class="glyphicon glyphicon-user"></span>
- <span class="video-small-block-text">{{ video.by }}</span>
- </a>
- </div>
-
- <div class="col-xs-2 col-md-3 video-small-block video-small-block-share">
- <a class="option" (click)="showShareModal()" title="Share the video">
- <span class="glyphicon glyphicon-share"></span>
- <span class="video-small-block-text">Share</span>
- </a>
- </div>
-
- <div class="col-xs-2 col-md-3 video-small-block video-small-block-more">
- <div class="video-small-block-dropdown" dropdown dropup="true" placement="right">
- <a class="option" title="Access to more options" dropdownToggle>
- <span class="glyphicon glyphicon-option-horizontal"></span>
- <span class="video-small-block-text">More</span>
- </a>
-
- <ul *dropdownMenu class="dropdown-menu" id="more-menu" role="menu" aria-labelledby="single-button">
- <li *ngIf="canUserUpdateVideo()" role="menuitem">
- <a class="dropdown-item" title="Update this video" href="#" [routerLink]="[ '/videos/edit', video.uuid ]">
- <span class="glyphicon glyphicon-pencil"></span> Update
- </a>
- </li>
-
- <li role="menuitem">
- <a class="dropdown-item" title="Get magnet URI" href="#" (click)="showMagnetUriModal($event)">
- <span class="glyphicon glyphicon-magnet"></span> Magnet
- </a>
- </li>
-
- <li *ngIf="isUserLoggedIn()" role="menuitem">
- <a class="dropdown-item" title="Report this video" href="#" (click)="showReportModal($event)">
- <span class="glyphicon glyphicon-alert"></span> Report
- </a>
- </li>
-
- <li *ngIf="isVideoRemovable()" role="menuitem">
- <a class="dropdown-item" title="Delete this video" href="#" (click)="removeVideo($event)">
- <span class="glyphicon glyphicon-remove"></span> Delete
- </a>
- </li>
-
- <li *ngIf="isVideoBlacklistable()" role="menuitem">
- <a class="dropdown-item" title="Blacklist this video" href="#" (click)="blacklistVideo($event)">
- <span class="glyphicon glyphicon-eye-close"></span> Blacklist
- </a>
- </li>
- </ul>
- </div>
- </div>
-
- <div class="col-xs-3 col-md-3 video-small-block video-small-block-rating">
- <div class="video-small-block-like">
- <span
- class="glyphicon glyphicon-thumbs-up" title="Like this video"
- [ngClass]="{ 'interactive': isUserLoggedIn(), 'activated': userRating === 'like' }" (click)="setLike()"
- ></span>
-
- <span class="video-small-block-text">
- {{ video.likes }}
- </span>
- </div>
-
- <div class="video-small-block-dislike">
- <span
- class="glyphicon glyphicon-thumbs-down" title="Dislike this video"
- [ngClass]="{ 'interactive': isUserLoggedIn(), 'activated': userRating === 'dislike' }" (click)="setDislike()"
- ></span>
-
- <span class="video-small-block-text">
- {{ video.dislikes }}
- </span>
- </div>
- </div>
- </div>
-
- <div class="row video-details">
- <div class="video-details-date-description col-xs-8 col-md-9">
- <div class="video-details-date">
- Published on {{ video.createdAt | date:'short' }}
- </div>
-
- <div class="video-details-description">
- {{ video.description }}
- </div>
- </div>
-
- <div class="video-details-attributes col-xs-4 col-md-3">
- <div class="video-details-attribute">
- <span class="video-details-attribute-label">
- Category:
- </span>
- <span class="video-details-attribute-value">
- {{ video.categoryLabel }}
- </span>
- </div>
-
- <div class="video-details-attribute">
- <span class="video-details-attribute-label">
- Licence:
- </span>
- <span class="video-details-attribute-value">
- {{ video.licenceLabel }}
- </span>
- </div>
-
- <div class="video-details-attribute">
- <span class="video-details-attribute-label">
- Language:
- </span>
- <span class="video-details-attribute-value">
- {{ video.languageLabel }}
- </span>
- </div>
-
- <div class="video-details-attribute">
- <span class="video-details-attribute-label">
- Tags:
- </span>
-
- <div class="video-details-tags">
- <a *ngFor="let tag of video.tags" [routerLink]="['/videos/list', { field: 'tags', search: tag }]" class="label label-primary">
- {{ tag }}
- </a>
- </div>
- </div>
-
- </div>
- </div>
-</div>
-
-<ng-template [ngIf]="video !== null">
- <my-video-share #videoShareModal [video]="video"></my-video-share>
- <my-video-magnet #videoMagnetModal [video]="video"></my-video-magnet>
- <my-video-report #videoReportModal [video]="video"></my-video-report>
-</ng-template>
+++ /dev/null
-#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;
- }
- }
- }
- }
-}
+++ /dev/null
-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)
- }
-}
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 = [
{
},
{
path: 'add',
- component: VideoAddComponent,
+ loadChildren: 'app/videos/+video-edit#VideoAddModule',
data: {
meta: {
title: 'Add a video'
},
{
path: 'edit/:uuid',
- component: VideoUpdateComponent,
+ loadChildren: 'app/videos/+video-edit#VideoUpdateModule',
data: {
meta: {
title: 'Edit a video'
},
{
path: 'watch/:uuid',
- component: VideoWatchComponent
+ loadChildren: 'app/videos/+video-watch#VideoWatchModule',
+ data: {
+ preload: 3000
+ }
}
]
}
-import { Component } from '@angular/core'
+import { Component, OnInit } from '@angular/core'
+
+import { VideoService } from './shared'
@Component({
template: '<router-outlet></router-outlet>'
})
+export class VideosComponent implements OnInit {
+ constructor(private videoService: VideoService) {}
-export class VideosComponent {
+ ngOnInit () {
+ this.videoService.loadVideoCategories()
+ this.videoService.loadVideoLicences()
+ this.videoService.loadVideoLanguages()
+ }
}
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
],
declarations: [
VideosComponent,
- VideoAddComponent,
- VideoUpdateComponent,
-
VideoListComponent,
VideoMiniatureComponent,
VideoSortComponent,
- VideoWatchComponent,
- VideoMagnetComponent,
- VideoShareComponent,
- VideoReportComponent,
-
LoaderComponent
],
# 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"
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"
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" "*"
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"
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"
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"
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"
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:
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"
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"
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"
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"
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:
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"
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"
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"
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"
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"
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"
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"
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"
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:
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"
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:
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"