Client: add basic support for updating a video

pull/61/head
Chocobozzz 2017-04-10 21:15:28 +02:00
parent a184c71b52
commit d8e689b864
14 changed files with 354 additions and 6 deletions

View 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';

View 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
};
}
}

View 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);

View File

@ -1 +0,0 @@
export * from './video-add.component';

View File

@ -0,0 +1,2 @@
export * from './video-add.component';
export * from './video-update.component';

View 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'
})

View File

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

View File

@ -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());
}
}

View File

@ -79,6 +79,12 @@
</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

View 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;

View 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'

View 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,