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';
--- /dev/null
+<div class="video-miniature" (mouseenter)="onHover()" (mouseleave)="onBlur()">
+ <a
+ [routerLink]="['VideosWatch', { id: video.id }]" [attr.title]="video.description"
+ class="video-miniature-thumbnail"
+ >
+ <img [attr.src]="video.thumbnailPath" alt="video thumbnail" />
+ <span class="video-miniature-duration">{{ video.duration }}</span>
+ </a>
+ <span
+ *ngIf="displayRemoveIcon()" (click)="removeVideo(video.id)"
+ class="video-miniature-remove glyphicon glyphicon-remove"
+ ></span>
+
+ <div class="video-miniature-informations">
+ <a [routerLink]="['VideosWatch', { id: video.id }]" class="video-miniature-name">
+ <span>{{ video.name }}</span>
+ </a>
+
+ <span class="video-miniature-author">by {{ video.by }}</span>
+ <span class="video-miniature-created-date">on {{ video.createdDate | date:'short' }}</span>
+ </div>
+</div>
--- /dev/null
+.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);
+ }
+ }
+}
--- /dev/null
+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<any>();
+
+ @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)
+ );
+ }
+ }
+}
<div *ngIf="videos.length === 0">There is no video.</div>
-<div *ngFor="let video of videos" class="video">
- <div>
- <a [routerLink]="['VideosWatch', { id: video.id }]" class="video_name">{{ video.name }}</a>
- <span class="video_pod_url">{{ video.podUrl }}</span>
- <span *ngIf="video.isLocal === true && user && video.author === user.username" (click)="removeVideo(video.id)" class="video_remove glyphicon glyphicon-remove"></span>
- </div>
-
- <div class="video_description">
- {{ video.description }}
- </div>
-</div>
+<my-video-miniature *ngFor="let video of videos" [video]="video" [user]="user" (removed)="onRemoved(video)">
+</my-video-miniature>
-.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;
+}
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 {
);
}
- 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);
}
}
// 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',
+++ /dev/null
-export interface Video {
- id: string;
- name: string;
- description: string;
- magnetUri: string;
- podUrl: string;
- isLocal: boolean;
-}
+++ /dev/null
-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';
-
-@Injectable()
-export class VideosService {
- private _baseVideoUrl = '/api/v1/videos/';
-
- constructor (private http: Http, private _authService: AuthService) {}
-
- getVideos() {
- return this.http.get(this._baseVideoUrl)
- .map(res => <Video[]> res.json())
- .catch(this.handleError);
- }
-
- getVideo(id: string) {
- return this.http.get(this._baseVideoUrl + id)
- .map(res => <Video> res.json())
- .catch(this.handleError);
- }
-
- removeVideo(id: string) {
- if (confirm('Are you sure?')) {
- const options = this._authService.getAuthRequestOptions();
- return this.http.delete(this._baseVideoUrl + id, options)
- .map(res => <number> res.status)
- .catch(this.handleError);
- }
- }
-
- searchVideos(search: string) {
- return this.http.get(this._baseVideoUrl + 'search/' + search)
- .map(res => <Video> res.json())
- .catch(this.handleError);
- }
-
- private handleError (error: Response) {
- console.error(error);
- return Observable.throw(error.json().error || 'Server error');
- }
-}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+import { Injectable } from '@angular/core';
+import { Http, Response } from '@angular/http';
+import { Observable } from 'rxjs/Rx';
+
+import { Video } from './video';
+import { AuthService } from '../users/services/auth.service';
+
+@Injectable()
+export class VideosService {
+ private _baseVideoUrl = '/api/v1/videos/';
+
+ constructor (private http: Http, private _authService: AuthService) {}
+
+ getVideos() {
+ return this.http.get(this._baseVideoUrl)
+ .map(res => res.json())
+ .map(this.extractVideos)
+ .catch(this.handleError);
+ }
+
+ getVideo(id: string) {
+ return this.http.get(this._baseVideoUrl + id)
+ .map(res => <Video> res.json())
+ .catch(this.handleError);
+ }
+
+ removeVideo(id: string) {
+ const options = this._authService.getAuthRequestOptions();
+ return this.http.delete(this._baseVideoUrl + id, options)
+ .map(res => <number> res.status)
+ .catch(this.handleError);
+ }
+
+ searchVideos(search: string) {
+ return this.http.get(this._baseVideoUrl + 'search/' + search)
+ .map(res => res.json())
+ .map(this.extractVideos)
+ .catch(this.handleError);
+ }
+
+ private extractVideos (body: any[]) {
+ const videos = [];
+ for (const video_json of body) {
+ videos.push(new Video(video_json));
+ }
+
+ return videos;
+ }
+
+ private handleError (error: Response) {
+ console.error(error);
+ return Observable.throw(error.json().error || 'Server error');
+ }
+}
}
if (duration > constants.MAXIMUM_VIDEO_DURATION) {
- return res.status(400).send('Duration of the video file is too big.')
+ return res.status(400).send('Duration of the video file is too big (' + constants.MAXIMUM_VIDEO_DURATION + ').')
}
videoFile.duration = duration