From 501bc6c2b186f6a724a5b619d15aa44791f13995 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Sat, 21 May 2016 18:03:34 +0200 Subject: [PATCH] Thumbnail, author and duration support in client --- client/angular/app/app.component.ts | 2 +- .../list/video-miniature.component.html | 22 +++++++ .../list/video-miniature.component.scss | 57 ++++++++++++++++++ .../list/video-miniature.component.ts | 47 +++++++++++++++ .../list/videos-list.component.html | 13 +--- .../list/videos-list.component.scss | 34 ++--------- .../components/list/videos-list.component.ts | 14 ++--- .../watch/videos-watch.component.ts | 4 +- client/angular/videos/models/video.ts | 8 --- client/angular/videos/video.ts | 60 +++++++++++++++++++ .../videos/{services => }/videos.service.ts | 29 +++++---- server/middlewares/reqValidators/videos.js | 2 +- 12 files changed, 221 insertions(+), 71 deletions(-) create mode 100644 client/angular/videos/components/list/video-miniature.component.html create mode 100644 client/angular/videos/components/list/video-miniature.component.scss create mode 100644 client/angular/videos/components/list/video-miniature.component.ts delete mode 100644 client/angular/videos/models/video.ts create mode 100644 client/angular/videos/video.ts rename client/angular/videos/{services => }/videos.service.ts (58%) diff --git a/client/angular/app/app.component.ts b/client/angular/app/app.component.ts index 359d7128e..a105ed26a 100644 --- a/client/angular/app/app.component.ts +++ b/client/angular/app/app.component.ts @@ -5,7 +5,7 @@ import { HTTP_PROVIDERS } from '@angular/http'; import { VideosAddComponent } from '../videos/components/add/videos-add.component'; import { VideosListComponent } from '../videos/components/list/videos-list.component'; import { VideosWatchComponent } from '../videos/components/watch/videos-watch.component'; -import { VideosService } from '../videos/services/videos.service'; +import { VideosService } from '../videos/videos.service'; import { FriendsService } from '../friends/services/friends.service'; import { UserLoginComponent } from '../users/components/login/login.component'; import { AuthService } from '../users/services/auth.service'; diff --git a/client/angular/videos/components/list/video-miniature.component.html b/client/angular/videos/components/list/video-miniature.component.html new file mode 100644 index 000000000..b88a19d5e --- /dev/null +++ b/client/angular/videos/components/list/video-miniature.component.html @@ -0,0 +1,22 @@ +
+ + video thumbnail + {{ video.duration }} + + + +
+ + {{ video.name }} + + + by {{ video.by }} + on {{ video.createdDate | date:'short' }} +
+
diff --git a/client/angular/videos/components/list/video-miniature.component.scss b/client/angular/videos/components/list/video-miniature.component.scss new file mode 100644 index 000000000..dbcd65820 --- /dev/null +++ b/client/angular/videos/components/list/video-miniature.component.scss @@ -0,0 +1,57 @@ +.video-miniature { + width: 200px; + height: 200px; + display: inline-block; + margin-right: 40px; + position: relative; + + .video-miniature-thumbnail { + display: block; + position: relative; + + .video-miniature-duration { + position: absolute; + right: 2px; + bottom: 2px; + display: inline-block; + background-color: rgba(0, 0, 0, 0.8); + color: rgba(255, 255, 255, 0.8); + padding: 2px; + font-size: 11px; + } + } + + .video-miniature-remove { + display: inline-block; + position: absolute; + left: 2px; + background-color: rgba(0, 0, 0, 0.8); + color: rgba(255, 255, 255, 0.8); + padding: 2px; + cursor: pointer; + + &:hover { + color: rgba(255, 255, 255, 0.9); + } + } + + .video-miniature-informations { + margin-left: 3px; + + .video-miniature-name { + display: block; + font-weight: bold; + + &:hover { + text-decoration: none; + } + } + + .video-miniature-author, .video-miniature-created-date { + display: block; + margin-left: 1px; + font-size: 11px; + color: rgba(0, 0, 0, 0.5); + } + } +} diff --git a/client/angular/videos/components/list/video-miniature.component.ts b/client/angular/videos/components/list/video-miniature.component.ts new file mode 100644 index 000000000..383c2c609 --- /dev/null +++ b/client/angular/videos/components/list/video-miniature.component.ts @@ -0,0 +1,47 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { DatePipe } from '@angular/common'; +import { ROUTER_DIRECTIVES } from '@angular/router-deprecated'; + +import { Video } from '../../video'; +import { VideosService } from '../../videos.service'; +import { User } from '../../../users/models/user'; + +@Component({ + selector: 'my-video-miniature', + styleUrls: [ 'app/angular/videos/components/list/video-miniature.component.css' ], + templateUrl: 'app/angular/videos/components/list/video-miniature.component.html', + directives: [ ROUTER_DIRECTIVES ], + pipes: [ DatePipe ] +}) + +export class VideoMiniatureComponent { + @Output() removed = new EventEmitter(); + + @Input() video: Video; + @Input() user: User; + + hovering: boolean = false; + + constructor(private _videosService: VideosService) {} + + onHover() { + this.hovering = true; + } + + onBlur() { + this.hovering = false; + } + + displayRemoveIcon(): boolean { + return this.hovering && this.video.isRemovableBy(this.user); + } + + removeVideo(id: string) { + if (confirm('Do you really want to remove this video?')) { + this._videosService.removeVideo(id).subscribe( + status => this.removed.emit(true), + error => alert(error) + ); + } + } +} diff --git a/client/angular/videos/components/list/videos-list.component.html b/client/angular/videos/components/list/videos-list.component.html index 4eeacbc77..776339d10 100644 --- a/client/angular/videos/components/list/videos-list.component.html +++ b/client/angular/videos/components/list/videos-list.component.html @@ -1,12 +1,3 @@
There is no video.
-
-
- {{ video.name }} - {{ video.podUrl }} - -
- -
- {{ video.description }} -
-
+ + diff --git a/client/angular/videos/components/list/videos-list.component.scss b/client/angular/videos/components/list/videos-list.component.scss index 82ddd80e5..ac930978c 100644 --- a/client/angular/videos/components/list/videos-list.component.scss +++ b/client/angular/videos/components/list/videos-list.component.scss @@ -1,34 +1,8 @@ -.video { - margin-bottom: 10px; - transition: margin 0.5s ease; - - &:hover { - margin-left: 5px; - } - - a.video_name { - color: #333333; - margin-right: 5px; - } - - .video_pod_url { - font-size: small; - color: rgba(0, 0, 0, 0.5); - } - - .video_description { - font-size: small; - font-style: italic; - margin-left: 7px; - } - - .video_remove { - margin: 5px; - cursor: pointer; - } -} - .loading { display: inline-block; margin-top: 100px; } + +my-videos-miniature { + display: inline-block; +} diff --git a/client/angular/videos/components/list/videos-list.component.ts b/client/angular/videos/components/list/videos-list.component.ts index 6ff0b2afb..6fc0c1f04 100644 --- a/client/angular/videos/components/list/videos-list.component.ts +++ b/client/angular/videos/components/list/videos-list.component.ts @@ -3,14 +3,15 @@ import { ROUTER_DIRECTIVES, RouteParams } from '@angular/router-deprecated'; import { AuthService } from '../../../users/services/auth.service'; import { User } from '../../../users/models/user'; -import { VideosService } from '../../services/videos.service'; -import { Video } from '../../models/video'; +import { VideosService } from '../../videos.service'; +import { Video } from '../../video'; +import { VideoMiniatureComponent } from './video-miniature.component'; @Component({ selector: 'my-videos-list', styleUrls: [ 'app/angular/videos/components/list/videos-list.component.css' ], templateUrl: 'app/angular/videos/components/list/videos-list.component.html', - directives: [ ROUTER_DIRECTIVES ] + directives: [ ROUTER_DIRECTIVES, VideoMiniatureComponent ] }) export class VideosListComponent implements OnInit { @@ -50,11 +51,8 @@ export class VideosListComponent implements OnInit { ); } - removeVideo(id: string) { - this._videosService.removeVideo(id).subscribe( - status => this.getVideos(), - error => alert(error) - ); + onRemoved(video: Video): void { + this.videos.splice(this.videos.indexOf(video), 1); } } diff --git a/client/angular/videos/components/watch/videos-watch.component.ts b/client/angular/videos/components/watch/videos-watch.component.ts index 3d1829b99..3eb005d07 100644 --- a/client/angular/videos/components/watch/videos-watch.component.ts +++ b/client/angular/videos/components/watch/videos-watch.component.ts @@ -5,8 +5,8 @@ import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe'; // TODO import it with systemjs declare var WebTorrent: any; -import { Video } from '../../models/video'; -import { VideosService } from '../../services/videos.service'; +import { Video } from '../../video'; +import { VideosService } from '../../videos.service'; @Component({ selector: 'my-video-watch', diff --git a/client/angular/videos/models/video.ts b/client/angular/videos/models/video.ts deleted file mode 100644 index e52c6d886..000000000 --- a/client/angular/videos/models/video.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface Video { - id: string; - name: string; - description: string; - magnetUri: string; - podUrl: string; - isLocal: boolean; -} diff --git a/client/angular/videos/video.ts b/client/angular/videos/video.ts new file mode 100644 index 000000000..32ff64eb3 --- /dev/null +++ b/client/angular/videos/video.ts @@ -0,0 +1,60 @@ +export class Video { + id: string; + name: string; + description: string; + magnetUri: string; + podUrl: string; + isLocal: boolean; + thumbnailPath: string; + author: string; + createdDate: Date; + by: string; + duration: string; + + constructor(hash: { + id: string, + name: string, + description: string, + magnetUri: string, + podUrl: string, + isLocal: boolean, + thumbnailPath: string, + author: string, + createdDate: string, + duration: number; + }) { + this.id = hash.id; + this.name = hash.name; + this.description = hash.description; + this.magnetUri = hash.magnetUri; + this.podUrl = hash.podUrl; + this.isLocal = hash.isLocal; + this.thumbnailPath = hash.thumbnailPath; + this.author = hash.author; + this.createdDate = new Date(hash.createdDate); + this.duration = Video.createDurationString(hash.duration); + this.by = Video.createByString(hash.author, hash.podUrl); + } + + isRemovableBy(user): boolean { + return this.isLocal === true && user && this.author === user.username; + } + + private static createDurationString(duration: number): string { + const minutes = Math.floor(duration / 60); + const seconds = duration % 60; + const minutes_padding = minutes >= 10 ? '' : '0'; + const seconds_padding = seconds >= 10 ? '' : '0' + + return minutes_padding + minutes.toString() + ':' + seconds_padding + seconds.toString(); + } + + private static createByString(author: string, podUrl: string): string { + let [ host, port ] = podUrl.replace(/^https?:\/\//, '').split(':'); + + if (port === '80' || port === '443') port = ''; + else port = ':' + port; + + return author + '@' + host + port; + } +} diff --git a/client/angular/videos/services/videos.service.ts b/client/angular/videos/videos.service.ts similarity index 58% rename from client/angular/videos/services/videos.service.ts rename to client/angular/videos/videos.service.ts index d08548339..f4790b511 100644 --- a/client/angular/videos/services/videos.service.ts +++ b/client/angular/videos/videos.service.ts @@ -2,8 +2,8 @@ import { Injectable } from '@angular/core'; import { Http, Response } from '@angular/http'; import { Observable } from 'rxjs/Rx'; -import { Video } from '../models/video'; -import { AuthService } from '../../users/services/auth.service'; +import { Video } from './video'; +import { AuthService } from '../users/services/auth.service'; @Injectable() export class VideosService { @@ -13,7 +13,8 @@ export class VideosService { getVideos() { return this.http.get(this._baseVideoUrl) - .map(res => res.json()) + .map(res => res.json()) + .map(this.extractVideos) .catch(this.handleError); } @@ -24,20 +25,28 @@ export class VideosService { } removeVideo(id: string) { - if (confirm('Are you sure?')) { - const options = this._authService.getAuthRequestOptions(); - return this.http.delete(this._baseVideoUrl + id, options) - .map(res => res.status) - .catch(this.handleError); - } + const options = this._authService.getAuthRequestOptions(); + return this.http.delete(this._baseVideoUrl + id, options) + .map(res => res.status) + .catch(this.handleError); } searchVideos(search: string) { return this.http.get(this._baseVideoUrl + 'search/' + search) - .map(res =>