]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Client: add basic support for updating a video
authorChocobozzz <florian.bigard@gmail.com>
Mon, 10 Apr 2017 19:15:28 +0000 (21:15 +0200)
committerChocobozzz <florian.bigard@gmail.com>
Mon, 10 Apr 2017 19:16:36 +0000 (21:16 +0200)
14 files changed:
client/src/app/videos/index.ts
client/src/app/videos/shared/video.model.ts
client/src/app/videos/shared/video.service.ts
client/src/app/videos/video-add/index.ts [deleted file]
client/src/app/videos/video-edit/index.ts [new file with mode: 0644]
client/src/app/videos/video-edit/video-add.component.html [moved from client/src/app/videos/video-add/video-add.component.html with 100% similarity]
client/src/app/videos/video-edit/video-add.component.ts [moved from client/src/app/videos/video-add/video-add.component.ts with 99% similarity]
client/src/app/videos/video-edit/video-edit.component.scss [moved from client/src/app/videos/video-add/video-add.component.scss with 100% similarity]
client/src/app/videos/video-edit/video-update.component.html [new file with mode: 0644]
client/src/app/videos/video-edit/video-update.component.ts [new file with mode: 0644]
client/src/app/videos/video-watch/video-watch.component.html
client/src/app/videos/video-watch/video-watch.component.ts
client/src/app/videos/videos-routing.module.ts
client/src/app/videos/videos.module.ts

index ca386a51df51e63f80adbcd2ee233f1859466c57..5158a23f835032de6c81d8aa6fc542ea2479bfd6 100644 (file)
@@ -1,5 +1,5 @@
 export * from './shared';
-export * from './video-add';
+export * from './video-edit';
 export * from './video-list';
 export * from './video-watch';
 export * from './videos-routing.module';
index f135ca707e77f795214bb22f3ee9dd1a3275f882..404e3bf45fea496ac0ca3e29dac42190085a3e9a 100644 (file)
@@ -5,8 +5,11 @@ export class Video {
   by: string;
   createdAt: Date;
   categoryLabel: string;
+  category: string;
   licenceLabel: string;
+  licence: string;
   languageLabel: string;
+  language: string;
   description: string;
   duration: string;
   id: string;
@@ -38,8 +41,11 @@ export class Video {
     author: string,
     createdAt: string,
     categoryLabel: string,
+    category: string,
     licenceLabel: string,
+    licence: string,
     languageLabel: string;
+    language: string;
     description: string,
     duration: number;
     id: string,
@@ -57,8 +63,11 @@ export class Video {
     this.author  = hash.author;
     this.createdAt = new Date(hash.createdAt);
     this.categoryLabel = hash.categoryLabel;
+    this.category = hash.category;
     this.licenceLabel = hash.licenceLabel;
+    this.licence = hash.licence;
     this.languageLabel = hash.languageLabel;
+    this.language = hash.language;
     this.description = hash.description;
     this.duration = Video.createDurationString(hash.duration);
     this.id = hash.id;
@@ -84,4 +93,33 @@ export class Video {
     // If the video is NSFW and the user is not logged in, or the user does not want to display NSFW videos...
     return (this.nsfw && (!user || user.displayNSFW === false));
   }
+
+  patch(values: Object) {
+    Object.keys(values).forEach((key) => {
+      this[key] = values[key];
+    });
+  }
+
+  toJSON() {
+    return {
+      author: this.author,
+      createdAt: this.createdAt,
+      category: this.category,
+      licence: this.licence,
+      language: this.language,
+      description: this.description,
+      duration: this.duration,
+      id: this.id,
+      isLocal: this.isLocal,
+      magnetUri: this.magnetUri,
+      name: this.name,
+      podHost: this.podHost,
+      tags: this.tags,
+      thumbnailPath: this.thumbnailPath,
+      views: this.views,
+      likes: this.likes,
+      dislikes: this.dislikes,
+      nsfw: this.nsfw
+    };
+  }
 }
index 13d4ca246a11db03c8c2108fbb6308bcb1c7bbfc..ee67bc1ae7f3935b90c71f519f523e3d82edbef8 100644 (file)
@@ -1,5 +1,5 @@
 import { Injectable } from '@angular/core';
-import { Http } from '@angular/http';
+import { Http, Headers, RequestOptions } from '@angular/http';
 import { Observable } from 'rxjs/Observable';
 import 'rxjs/add/operator/catch';
 import 'rxjs/add/operator/map';
@@ -80,6 +80,23 @@ export class VideoService {
                     .catch((res) => this.restExtractor.handleError(res));
   }
 
+  updateVideo(video: Video) {
+    const body = {
+      name: video.name,
+      category: video.category,
+      licence: video.licence,
+      language: video.language,
+      description: video.description,
+      tags: video.tags
+    };
+    const headers = new Headers({ 'Content-Type': 'application/json' });
+    const options = new RequestOptions({ headers: headers });
+
+    return this.authHttp.put(`${VideoService.BASE_VIDEO_URL}/${video.id}`, body, options)
+                        .map(this.restExtractor.extractDataBool)
+                        .catch(this.restExtractor.handleError);
+  }
+
   getVideos(pagination: RestPagination, sort: SortField) {
     const params = this.restService.buildRestGetParams(pagination, sort);
 
diff --git a/client/src/app/videos/video-add/index.ts b/client/src/app/videos/video-add/index.ts
deleted file mode 100644 (file)
index 79488e8..0000000
+++ /dev/null
@@ -1 +0,0 @@
-export * from './video-add.component';
diff --git a/client/src/app/videos/video-edit/index.ts b/client/src/app/videos/video-edit/index.ts
new file mode 100644 (file)
index 0000000..5ce4fb9
--- /dev/null
@@ -0,0 +1,2 @@
+export * from './video-add.component';
+export * from './video-update.component';
similarity index 99%
rename from client/src/app/videos/video-add/video-add.component.ts
rename to client/src/app/videos/video-edit/video-add.component.ts
index da556f48d75a452d8f304de0d229e64d5e77a62c..e3cf0e9d8f1146f8559e2f52823f3cb7deb66488 100644 (file)
@@ -19,7 +19,7 @@ import { VideoService } from '../shared';
 
 @Component({
   selector: 'my-videos-add',
-  styleUrls: [ './video-add.component.scss' ],
+  styleUrls: [ './video-edit.component.scss' ],
   templateUrl: './video-add.component.html'
 })
 
diff --git a/client/src/app/videos/video-edit/video-update.component.html b/client/src/app/videos/video-edit/video-update.component.html
new file mode 100644 (file)
index 0000000..665a952
--- /dev/null
@@ -0,0 +1,101 @@
+<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">Tags</label> <span class="little-information">(press enter to add the tag)</span>
+    <input
+      type="text" class="form-control" id="currentTag"
+      formControlName="currentTag" (keyup)="onTagKeyPress($event)"
+    >
+    <div *ngIf="formErrors.currentTag" class="alert alert-danger">
+      {{ formErrors.currentTag }}
+    </div>
+  </div>
+
+  <div class="tags">
+    <div class="label label-primary tag" *ngFor="let tag of tags">
+      {{ tag }}
+      <span class="remove" (click)="removeTag(tag)">x</span>
+    </div>
+  </div>
+
+  <div *ngIf="tagsError" class="alert alert-danger">
+    {{ tagsError }}
+  </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>
diff --git a/client/src/app/videos/video-edit/video-update.component.ts b/client/src/app/videos/video-edit/video-update.component.ts
new file mode 100644 (file)
index 0000000..b45780a
--- /dev/null
@@ -0,0 +1,170 @@
+import { Component, ElementRef, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup } from '@angular/forms';
+import { ActivatedRoute, Router } from '@angular/router';
+
+import { FileUploader } from 'ng2-file-upload/ng2-file-upload';
+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;
+
+  error: string = null;
+  form: FormGroup;
+  formErrors = {
+    name: '',
+    category: '',
+    licence: '',
+    language: '',
+    description: '',
+    currentTag: ''
+  };
+  validationMessages = {
+    name: VIDEO_NAME.MESSAGES,
+    category: VIDEO_CATEGORY.MESSAGES,
+    licence: VIDEO_LICENCE.MESSAGES,
+    language: VIDEO_LANGUAGE.MESSAGES,
+    description: VIDEO_DESCRIPTION.MESSAGES,
+    currentTag: VIDEO_TAGS.MESSAGES
+  };
+
+  // Special error messages
+  tagsError = '';
+  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 ],
+      currentTag: [ '', VIDEO_TAGS.VALIDATORS ]
+    });
+
+    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 id = this.route.snapshot.params['id'];
+    this.videoService.getVideo(id)
+                     .subscribe(
+                       video => {
+                         this.video = video;
+
+                         this.hydrateFormFromVideo();
+                       },
+
+                       err => this.error = 'Cannot fetch video.'
+                     );
+  }
+
+  checkForm() {
+    this.forceCheck();
+
+    return this.form.valid === true && this.tagsError === '' && this.fileError === '';
+  }
+
+
+  onTagKeyPress(event: KeyboardEvent) {
+    // Enter press
+    if (event.keyCode === 13) {
+      this.addTagIfPossible();
+    }
+  }
+
+  removeTag(tag: string) {
+    this.tags.splice(this.tags.indexOf(tag), 1);
+    this.form.get('currentTag').enable();
+  }
+
+  update() {
+    // Maybe the user forgot to press "enter" when he filled the field
+    this.addTagIfPossible();
+
+    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.id ]);
+                       },
+
+                       err => {
+                         this.error = 'Cannot update the video.';
+                         console.error(err);
+                       }
+                      );
+
+  }
+
+  private addTagIfPossible() {
+    const currentTag = this.form.value['currentTag'];
+    if (currentTag === undefined) return;
+
+    // Check if the tag is valid and does not already exist
+    if (
+      currentTag.length >= 2 &&
+      this.form.controls['currentTag'].valid &&
+      this.tags.indexOf(currentTag) === -1
+    ) {
+      this.tags.push(currentTag);
+      this.form.patchValue({ currentTag: '' });
+
+      if (this.tags.length >= 3) {
+        this.form.get('currentTag').disable();
+      }
+
+      this.tagsError = '';
+    }
+  }
+
+  private hydrateFormFromVideo() {
+    this.form.patchValue(this.video.toJSON());
+  }
+}
index a6ec7b20fda6158604a65d2039d8173931410f3d..2a6b15dc99f72c4a44826ff398759b8e948c116b 100644 (file)
         </button>
 
         <ul dropdownMenu 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.id ]">
+              <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
index 37ed70a99f3f62017cf0a9c2b75a9ec1522ec52d..e04626a677f9c70225c20bc6b898c5cd06f70f08 100644 (file)
@@ -187,6 +187,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     return this.authService.isLoggedIn();
   }
 
+  canUserUpdateVideo() {
+    return this.authService.getUser() !== null &&
+           this.authService.getUser().username === this.video.author;
+  }
+
   private checkUserRating() {
     // Unlogged users do not have ratings
     if (this.isUserLoggedIn() === false) return;
index 005e9def6548bf39e41c50935dc67f3c81e17d3c..70968b4d1d90681142ce34838c23ab40c8cfa91e 100644 (file)
@@ -1,7 +1,7 @@
 import { NgModule } from '@angular/core';
 import { RouterModule, Routes } from '@angular/router';
 
-import { VideoAddComponent } from './video-add';
+import { VideoAddComponent, VideoUpdateComponent } from './video-edit';
 import { VideoListComponent } from './video-list';
 import { VideosComponent } from './videos.component';
 import { VideoWatchComponent } from './video-watch';
@@ -29,6 +29,15 @@ const videosRoutes: Routes = [
           }
         }
       },
+      {
+        path: 'edit/:id',
+        component: VideoUpdateComponent,
+        data: {
+          meta: {
+            title: 'Edit a video'
+          }
+        }
+      },
       {
         path: ':id',
         redirectTo: 'watch/:id'
index 03dea17b5b433e27cda9d6362e6fc6e7fb03e58e..fa37ad966b318b643368c21dffc3b60eb2a4bde7 100644 (file)
@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
 
 import { VideosRoutingModule } from './videos-routing.module';
 import { VideosComponent } from './videos.component';
-import { VideoAddComponent } from './video-add';
+import { VideoAddComponent, VideoUpdateComponent } from './video-edit';
 import { VideoListComponent, VideoMiniatureComponent, VideoSortComponent } from './video-list';
 import {
   VideoWatchComponent,
@@ -24,6 +24,7 @@ import { SharedModule } from '../shared';
     VideosComponent,
 
     VideoAddComponent,
+    VideoUpdateComponent,
 
     VideoListComponent,
     VideoMiniatureComponent,