Client: Handle NSFW video

This commit is contained in:
Chocobozzz 2017-04-04 21:37:03 +02:00
parent 1d49e1e27d
commit 92fb909c9b
15 changed files with 157 additions and 22 deletions

View File

@ -1,7 +1,7 @@
import { Component, OnInit, ViewContainerRef } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from './core';
import { AuthService, ConfigService } from './core';
import { VideoService } from './videos';
import { UserService } from './shared';
@ -27,6 +27,7 @@ export class AppComponent implements OnInit {
constructor(
private router: Router,
private authService: AuthService,
private configService: ConfigService,
private userService: UserService,
private videoService: VideoService,
viewContainerRef: ViewContainerRef
@ -38,6 +39,7 @@ export class AppComponent implements OnInit {
this.userService.checkTokenValidity();
}
this.configService.loadConfig();
this.videoService.loadVideoCategories();
this.videoService.loadVideoLicences();
}

View File

@ -5,7 +5,8 @@ export class AuthUser extends User {
private static KEYS = {
ID: 'id',
ROLE: 'role',
USERNAME: 'username'
USERNAME: 'username',
DISPLAY_NSFW: 'display_nsfw'
};
tokens: Tokens;
@ -17,7 +18,8 @@ export class AuthUser extends User {
{
id: parseInt(localStorage.getItem(this.KEYS.ID)),
username: localStorage.getItem(this.KEYS.USERNAME),
role: localStorage.getItem(this.KEYS.ROLE)
role: localStorage.getItem(this.KEYS.ROLE),
displayNSFW: localStorage.getItem(this.KEYS.DISPLAY_NSFW) === 'true'
},
Tokens.load()
);
@ -30,10 +32,16 @@ export class AuthUser extends User {
localStorage.removeItem(this.KEYS.USERNAME);
localStorage.removeItem(this.KEYS.ID);
localStorage.removeItem(this.KEYS.ROLE);
localStorage.removeItem(this.KEYS.DISPLAY_NSFW);
Tokens.flush();
}
constructor(userHash: { id: number, username: string, role: string }, hashTokens: any) {
constructor(userHash: {
id: number,
username: string,
role: string,
displayNSFW: boolean
}, hashTokens: any) {
super(userHash);
this.tokens = new Tokens(hashTokens);
}
@ -59,6 +67,7 @@ export class AuthUser extends User {
localStorage.setItem(AuthUser.KEYS.ID, this.id.toString());
localStorage.setItem(AuthUser.KEYS.USERNAME, this.username);
localStorage.setItem(AuthUser.KEYS.ROLE, this.role);
localStorage.setItem(AuthUser.KEYS.DISPLAY_NSFW, JSON.stringify(this.displayNSFW);
this.tokens.save();
}
}

View File

@ -0,0 +1,36 @@
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { RestExtractor } from '../../shared/rest';
@Injectable()
export class ConfigService {
private static BASE_CONFIG_URL = '/api/v1/config/';
private config: {
signup: {
enabled: boolean
}
} = {
signup: {
enabled: false
}
};
constructor(
private http: Http,
private restExtractor: RestExtractor,
) {}
loadConfig() {
this.http.get(ConfigService.BASE_CONFIG_URL)
.map(this.restExtractor.extractDataGet)
.subscribe(data => {
this.config = data;
});
}
getConfig() {
return this.config;
}
}

View File

@ -0,0 +1 @@
export * from './config.service';

View File

@ -7,6 +7,7 @@ import { SimpleNotificationsModule } from 'angular2-notifications';
import { ModalModule } from 'ng2-bootstrap/modal';
import { AuthService } from './auth';
import { ConfigService } from './config';
import { ConfirmComponent, ConfirmService } from './confirm';
import { MenuComponent, MenuAdminComponent } from './menu';
import { throwIfAlreadyLoaded } from './module-import-guard';
@ -37,7 +38,8 @@ import { throwIfAlreadyLoaded } from './module-import-guard';
providers: [
AuthService,
ConfirmService
ConfirmService,
ConfigService
]
})
export class CoreModule {

View File

@ -1,4 +1,5 @@
export * from './auth';
export * from './config';
export * from './confirm';
export * from './menu';
export * from './core.module'

View File

@ -2,12 +2,20 @@ export class User {
id: number;
username: string;
role: string;
displayNSFW: boolean;
createdAt: Date;
constructor(hash: { id: number, username: string, role: string, createdAt?: Date }) {
constructor(hash: {
id: number,
username: string,
role: string,
displayNSFW?: boolean,
createdAt?: Date,
}) {
this.id = hash.id;
this.username = hash.username;
this.role = hash.role;
this.displayNSFW = hash.displayNSFW;
if (hash.createdAt) {
this.createdAt = hash.createdAt;

View File

@ -1,3 +1,5 @@
import { User } from '../../shared';
export class Video {
author: string;
by: string;
@ -16,6 +18,7 @@ export class Video {
views: number;
likes: number;
dislikes: number;
nsfw: boolean;
private static createByString(author: string, podHost: string) {
return author + '@' + podHost;
@ -47,6 +50,7 @@ export class Video {
views: number,
likes: number,
dislikes: number,
nsfw: boolean
}) {
this.author = hash.author;
this.createdAt = new Date(hash.createdAt);
@ -64,11 +68,17 @@ export class Video {
this.views = hash.views;
this.likes = hash.likes;
this.dislikes = hash.dislikes;
this.nsfw = hash.nsfw;
this.by = Video.createByString(hash.author, hash.podHost);
}
isRemovableBy(user) {
isRemovableBy(user: User) {
return this.isLocal === true && user && this.author === user.username;
}
isVideoNSFWForUser(user: User) {
// 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));
}
}

View File

@ -14,6 +14,14 @@
</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">

View File

@ -71,6 +71,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
buildForm() {
this.form = this.formBuilder.group({
name: [ '', VIDEO_NAME.VALIDATORS ],
nsfw: [ false ],
category: [ '', VIDEO_CATEGORY.VALIDATORS ],
licence: [ '', VIDEO_LICENCE.VALIDATORS ],
description: [ '', VIDEO_DESCRIPTION.VALIDATORS ],
@ -93,12 +94,14 @@ export class VideoAddComponent extends FormReactive implements OnInit {
this.uploader.onBuildItemForm = (item, form) => {
const name = this.form.value['name'];
const nsfw = this.form.value['nsfw'];
const category = this.form.value['category'];
const licence = this.form.value['licence'];
const description = this.form.value['description'];
form.append('name', name);
form.append('category', category);
form.append('nsfw', nsfw);
form.append('licence', licence);
form.append('description', description);

View File

@ -3,7 +3,11 @@
[routerLink]="['/videos/watch', video.id]" [attr.title]="video.description"
class="video-miniature-thumbnail"
>
<img [attr.src]="video.thumbnailPath" alt="video thumbnail" />
<img *ngIf="isVideoNSFWForThisUser() === false" [attr.src]="video.thumbnailPath" alt="video thumbnail" />
<div *ngIf="isVideoNSFWForThisUser()" class="thumbnail-nsfw">
NSFW
</div>
<span class="video-miniature-duration">{{ video.duration }}</span>
</a>
<span
@ -13,7 +17,7 @@
<div class="video-miniature-informations">
<span class="video-miniature-name-tags">
<a [routerLink]="['/videos/watch', video.id]" [attr.title]="video.name" class="video-miniature-name">{{ video.name }}</a>
<a [routerLink]="['/videos/watch', video.id]" [attr.title]="getVideoName()" class="video-miniature-name">{{ getVideoName() }}</a>
<div class="video-miniature-tags">
<span *ngFor="let tag of video.tags" class="video-miniature-tag">

View File

@ -15,6 +15,21 @@
display: inline-block;
position: relative;
&:hover {
text-decoration: none !important;
}
.thumbnail-nsfw {
background-color: #000;
color: #fff;
text-align: center;
font-size: 30px;
line-height: 110px;
width: 200px;
height: 110px;
}
.video-miniature-duration {
position: absolute;
right: 5px;

View File

@ -2,7 +2,7 @@ import { Component, Input, Output, EventEmitter } from '@angular/core';
import { NotificationsService } from 'angular2-notifications';
import { ConfirmService } from '../../core';
import { ConfirmService, ConfigService } from '../../core';
import { SortField, Video, VideoService } from '../shared';
import { User } from '../../shared';
@ -24,6 +24,7 @@ export class VideoMiniatureComponent {
constructor(
private notificationsService: NotificationsService,
private confirmService: ConfirmService,
private configService: ConfigService,
private videoService: VideoService
) {}
@ -31,6 +32,13 @@ export class VideoMiniatureComponent {
return this.hovering && this.video.isRemovableBy(this.user);
}
getVideoName() {
if (this.isVideoNSFWForThisUser())
return 'NSFW';
return this.video.name;
}
onBlur() {
this.hovering = false;
}
@ -52,4 +60,8 @@ export class VideoMiniatureComponent {
}
);
}
isVideoNSFWForThisUser() {
return this.video.isVideoNSFWForUser(this.user);
}
}

View File

@ -1,12 +1,13 @@
import { Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import * as videojs from 'video.js';
import { MetaService } from '@nglibs/meta';
import { NotificationsService } from 'angular2-notifications';
import { AuthService } from '../../core';
import { AuthService, ConfirmService } from '../../core';
import { VideoMagnetComponent } from './video-magnet.component';
import { VideoShareComponent } from './video-share.component';
import { VideoReportComponent } from './video-report.component';
@ -47,7 +48,9 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
private elementRef: ElementRef,
private ngZone: NgZone,
private route: ActivatedRoute,
private router: Router,
private videoService: VideoService,
private confirmService: ConfirmService,
private metaService: MetaService,
private webTorrentService: WebTorrentService,
private authService: AuthService,
@ -58,15 +61,9 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
this.paramsSub = this.route.params.subscribe(routeParams => {
let id = routeParams['id'];
this.videoService.getVideo(id).subscribe(
video => {
this.video = video;
this.setOpenGraphTags();
this.loadVideo();
this.checkUserRating();
},
error => {
this.videoNotFound = true;
}
video => this.onVideoFetched(video),
error => this.videoNotFound = true
);
});
@ -92,7 +89,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
window.clearInterval(this.torrentInfosInterval);
window.clearTimeout(this.errorTimer);
if (this.video !== null) {
if (this.video !== null && this.webTorrentService.has(this.video.magnetUri)) {
this.webTorrentService.remove(this.video.magnetUri);
}
@ -206,6 +203,29 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
);
}
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.setOpenGraphTags();
this.loadVideo();
this.checkUserRating();
}
);
}
private updateVideoRating(oldRating: RateType, newRating: RateType) {
let likesToIncrement = 0;
let dislikesToIncrement = 0;

View File

@ -26,4 +26,8 @@ export class WebTorrentService {
remove(magnetUri: string) {
return this.client.remove(magnetUri);
}
has(magnetUri: string) {
return this.client.get(magnetUri) !== null;
}
}