diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2016-05-21 18:03:34 +0200 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2016-05-21 18:08:23 +0200 |
commit | 501bc6c2b186f6a724a5b619d15aa44791f13995 (patch) | |
tree | 4a6e1d244c5f94305a25b6ec6f50f1a71ce9295d /client/angular/videos | |
parent | 295ba044afc394ef51dac22263063670362787ec (diff) | |
download | PeerTube-501bc6c2b186f6a724a5b619d15aa44791f13995.tar.gz PeerTube-501bc6c2b186f6a724a5b619d15aa44791f13995.tar.zst PeerTube-501bc6c2b186f6a724a5b619d15aa44791f13995.zip |
Thumbnail, author and duration support in client
Diffstat (limited to 'client/angular/videos')
-rw-r--r-- | client/angular/videos/components/list/video-miniature.component.html | 22 | ||||
-rw-r--r-- | client/angular/videos/components/list/video-miniature.component.scss | 57 | ||||
-rw-r--r-- | client/angular/videos/components/list/video-miniature.component.ts | 47 | ||||
-rw-r--r-- | client/angular/videos/components/list/videos-list.component.html | 13 | ||||
-rw-r--r-- | client/angular/videos/components/list/videos-list.component.scss | 34 | ||||
-rw-r--r-- | client/angular/videos/components/list/videos-list.component.ts | 14 | ||||
-rw-r--r-- | client/angular/videos/components/watch/videos-watch.component.ts | 4 | ||||
-rw-r--r-- | client/angular/videos/models/video.ts | 8 | ||||
-rw-r--r-- | client/angular/videos/video.ts | 60 | ||||
-rw-r--r-- | client/angular/videos/videos.service.ts (renamed from client/angular/videos/services/videos.service.ts) | 29 |
10 files changed, 219 insertions, 69 deletions
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 @@ | |||
1 | <div class="video-miniature" (mouseenter)="onHover()" (mouseleave)="onBlur()"> | ||
2 | <a | ||
3 | [routerLink]="['VideosWatch', { id: video.id }]" [attr.title]="video.description" | ||
4 | class="video-miniature-thumbnail" | ||
5 | > | ||
6 | <img [attr.src]="video.thumbnailPath" alt="video thumbnail" /> | ||
7 | <span class="video-miniature-duration">{{ video.duration }}</span> | ||
8 | </a> | ||
9 | <span | ||
10 | *ngIf="displayRemoveIcon()" (click)="removeVideo(video.id)" | ||
11 | class="video-miniature-remove glyphicon glyphicon-remove" | ||
12 | ></span> | ||
13 | |||
14 | <div class="video-miniature-informations"> | ||
15 | <a [routerLink]="['VideosWatch', { id: video.id }]" class="video-miniature-name"> | ||
16 | <span>{{ video.name }}</span> | ||
17 | </a> | ||
18 | |||
19 | <span class="video-miniature-author">by {{ video.by }}</span> | ||
20 | <span class="video-miniature-created-date">on {{ video.createdDate | date:'short' }}</span> | ||
21 | </div> | ||
22 | </div> | ||
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 @@ | |||
1 | .video-miniature { | ||
2 | width: 200px; | ||
3 | height: 200px; | ||
4 | display: inline-block; | ||
5 | margin-right: 40px; | ||
6 | position: relative; | ||
7 | |||
8 | .video-miniature-thumbnail { | ||
9 | display: block; | ||
10 | position: relative; | ||
11 | |||
12 | .video-miniature-duration { | ||
13 | position: absolute; | ||
14 | right: 2px; | ||
15 | bottom: 2px; | ||
16 | display: inline-block; | ||
17 | background-color: rgba(0, 0, 0, 0.8); | ||
18 | color: rgba(255, 255, 255, 0.8); | ||
19 | padding: 2px; | ||
20 | font-size: 11px; | ||
21 | } | ||
22 | } | ||
23 | |||
24 | .video-miniature-remove { | ||
25 | display: inline-block; | ||
26 | position: absolute; | ||
27 | left: 2px; | ||
28 | background-color: rgba(0, 0, 0, 0.8); | ||
29 | color: rgba(255, 255, 255, 0.8); | ||
30 | padding: 2px; | ||
31 | cursor: pointer; | ||
32 | |||
33 | &:hover { | ||
34 | color: rgba(255, 255, 255, 0.9); | ||
35 | } | ||
36 | } | ||
37 | |||
38 | .video-miniature-informations { | ||
39 | margin-left: 3px; | ||
40 | |||
41 | .video-miniature-name { | ||
42 | display: block; | ||
43 | font-weight: bold; | ||
44 | |||
45 | &:hover { | ||
46 | text-decoration: none; | ||
47 | } | ||
48 | } | ||
49 | |||
50 | .video-miniature-author, .video-miniature-created-date { | ||
51 | display: block; | ||
52 | margin-left: 1px; | ||
53 | font-size: 11px; | ||
54 | color: rgba(0, 0, 0, 0.5); | ||
55 | } | ||
56 | } | ||
57 | } | ||
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 @@ | |||
1 | import { Component, Input, Output, EventEmitter } from '@angular/core'; | ||
2 | import { DatePipe } from '@angular/common'; | ||
3 | import { ROUTER_DIRECTIVES } from '@angular/router-deprecated'; | ||
4 | |||
5 | import { Video } from '../../video'; | ||
6 | import { VideosService } from '../../videos.service'; | ||
7 | import { User } from '../../../users/models/user'; | ||
8 | |||
9 | @Component({ | ||
10 | selector: 'my-video-miniature', | ||
11 | styleUrls: [ 'app/angular/videos/components/list/video-miniature.component.css' ], | ||
12 | templateUrl: 'app/angular/videos/components/list/video-miniature.component.html', | ||
13 | directives: [ ROUTER_DIRECTIVES ], | ||
14 | pipes: [ DatePipe ] | ||
15 | }) | ||
16 | |||
17 | export class VideoMiniatureComponent { | ||
18 | @Output() removed = new EventEmitter<any>(); | ||
19 | |||
20 | @Input() video: Video; | ||
21 | @Input() user: User; | ||
22 | |||
23 | hovering: boolean = false; | ||
24 | |||
25 | constructor(private _videosService: VideosService) {} | ||
26 | |||
27 | onHover() { | ||
28 | this.hovering = true; | ||
29 | } | ||
30 | |||
31 | onBlur() { | ||
32 | this.hovering = false; | ||
33 | } | ||
34 | |||
35 | displayRemoveIcon(): boolean { | ||
36 | return this.hovering && this.video.isRemovableBy(this.user); | ||
37 | } | ||
38 | |||
39 | removeVideo(id: string) { | ||
40 | if (confirm('Do you really want to remove this video?')) { | ||
41 | this._videosService.removeVideo(id).subscribe( | ||
42 | status => this.removed.emit(true), | ||
43 | error => alert(error) | ||
44 | ); | ||
45 | } | ||
46 | } | ||
47 | } | ||
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 @@ | |||
1 | <div *ngIf="videos.length === 0">There is no video.</div> | 1 | <div *ngIf="videos.length === 0">There is no video.</div> |
2 | <div *ngFor="let video of videos" class="video"> | 2 | <my-video-miniature *ngFor="let video of videos" [video]="video" [user]="user" (removed)="onRemoved(video)"> |
3 | <div> | 3 | </my-video-miniature> |
4 | <a [routerLink]="['VideosWatch', { id: video.id }]" class="video_name">{{ video.name }}</a> | ||
5 | <span class="video_pod_url">{{ video.podUrl }}</span> | ||
6 | <span *ngIf="video.isLocal === true && user && video.author === user.username" (click)="removeVideo(video.id)" class="video_remove glyphicon glyphicon-remove"></span> | ||
7 | </div> | ||
8 | |||
9 | <div class="video_description"> | ||
10 | {{ video.description }} | ||
11 | </div> | ||
12 | </div> | ||
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 @@ | |||
1 | .video { | ||
2 | margin-bottom: 10px; | ||
3 | transition: margin 0.5s ease; | ||
4 | |||
5 | &:hover { | ||
6 | margin-left: 5px; | ||
7 | } | ||
8 | |||
9 | a.video_name { | ||
10 | color: #333333; | ||
11 | margin-right: 5px; | ||
12 | } | ||
13 | |||
14 | .video_pod_url { | ||
15 | font-size: small; | ||
16 | color: rgba(0, 0, 0, 0.5); | ||
17 | } | ||
18 | |||
19 | .video_description { | ||
20 | font-size: small; | ||
21 | font-style: italic; | ||
22 | margin-left: 7px; | ||
23 | } | ||
24 | |||
25 | .video_remove { | ||
26 | margin: 5px; | ||
27 | cursor: pointer; | ||
28 | } | ||
29 | } | ||
30 | |||
31 | .loading { | 1 | .loading { |
32 | display: inline-block; | 2 | display: inline-block; |
33 | margin-top: 100px; | 3 | margin-top: 100px; |
34 | } | 4 | } |
5 | |||
6 | my-videos-miniature { | ||
7 | display: inline-block; | ||
8 | } | ||
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'; | |||
3 | 3 | ||
4 | import { AuthService } from '../../../users/services/auth.service'; | 4 | import { AuthService } from '../../../users/services/auth.service'; |
5 | import { User } from '../../../users/models/user'; | 5 | import { User } from '../../../users/models/user'; |
6 | import { VideosService } from '../../services/videos.service'; | 6 | import { VideosService } from '../../videos.service'; |
7 | import { Video } from '../../models/video'; | 7 | import { Video } from '../../video'; |
8 | import { VideoMiniatureComponent } from './video-miniature.component'; | ||
8 | 9 | ||
9 | @Component({ | 10 | @Component({ |
10 | selector: 'my-videos-list', | 11 | selector: 'my-videos-list', |
11 | styleUrls: [ 'app/angular/videos/components/list/videos-list.component.css' ], | 12 | styleUrls: [ 'app/angular/videos/components/list/videos-list.component.css' ], |
12 | templateUrl: 'app/angular/videos/components/list/videos-list.component.html', | 13 | templateUrl: 'app/angular/videos/components/list/videos-list.component.html', |
13 | directives: [ ROUTER_DIRECTIVES ] | 14 | directives: [ ROUTER_DIRECTIVES, VideoMiniatureComponent ] |
14 | }) | 15 | }) |
15 | 16 | ||
16 | export class VideosListComponent implements OnInit { | 17 | export class VideosListComponent implements OnInit { |
@@ -50,11 +51,8 @@ export class VideosListComponent implements OnInit { | |||
50 | ); | 51 | ); |
51 | } | 52 | } |
52 | 53 | ||
53 | removeVideo(id: string) { | 54 | onRemoved(video: Video): void { |
54 | this._videosService.removeVideo(id).subscribe( | 55 | this.videos.splice(this.videos.indexOf(video), 1); |
55 | status => this.getVideos(), | ||
56 | error => alert(error) | ||
57 | ); | ||
58 | } | 56 | } |
59 | 57 | ||
60 | } | 58 | } |
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'; | |||
5 | // TODO import it with systemjs | 5 | // TODO import it with systemjs |
6 | declare var WebTorrent: any; | 6 | declare var WebTorrent: any; |
7 | 7 | ||
8 | import { Video } from '../../models/video'; | 8 | import { Video } from '../../video'; |
9 | import { VideosService } from '../../services/videos.service'; | 9 | import { VideosService } from '../../videos.service'; |
10 | 10 | ||
11 | @Component({ | 11 | @Component({ |
12 | selector: 'my-video-watch', | 12 | 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 @@ | |||
1 | export interface Video { | ||
2 | id: string; | ||
3 | name: string; | ||
4 | description: string; | ||
5 | magnetUri: string; | ||
6 | podUrl: string; | ||
7 | isLocal: boolean; | ||
8 | } | ||
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 @@ | |||
1 | export class Video { | ||
2 | id: string; | ||
3 | name: string; | ||
4 | description: string; | ||
5 | magnetUri: string; | ||
6 | podUrl: string; | ||
7 | isLocal: boolean; | ||
8 | thumbnailPath: string; | ||
9 | author: string; | ||
10 | createdDate: Date; | ||
11 | by: string; | ||
12 | duration: string; | ||
13 | |||
14 | constructor(hash: { | ||
15 | id: string, | ||
16 | name: string, | ||
17 | description: string, | ||
18 | magnetUri: string, | ||
19 | podUrl: string, | ||
20 | isLocal: boolean, | ||
21 | thumbnailPath: string, | ||
22 | author: string, | ||
23 | createdDate: string, | ||
24 | duration: number; | ||
25 | }) { | ||
26 | this.id = hash.id; | ||
27 | this.name = hash.name; | ||
28 | this.description = hash.description; | ||
29 | this.magnetUri = hash.magnetUri; | ||
30 | this.podUrl = hash.podUrl; | ||
31 | this.isLocal = hash.isLocal; | ||
32 | this.thumbnailPath = hash.thumbnailPath; | ||
33 | this.author = hash.author; | ||
34 | this.createdDate = new Date(hash.createdDate); | ||
35 | this.duration = Video.createDurationString(hash.duration); | ||
36 | this.by = Video.createByString(hash.author, hash.podUrl); | ||
37 | } | ||
38 | |||
39 | isRemovableBy(user): boolean { | ||
40 | return this.isLocal === true && user && this.author === user.username; | ||
41 | } | ||
42 | |||
43 | private static createDurationString(duration: number): string { | ||
44 | const minutes = Math.floor(duration / 60); | ||
45 | const seconds = duration % 60; | ||
46 | const minutes_padding = minutes >= 10 ? '' : '0'; | ||
47 | const seconds_padding = seconds >= 10 ? '' : '0' | ||
48 | |||
49 | return minutes_padding + minutes.toString() + ':' + seconds_padding + seconds.toString(); | ||
50 | } | ||
51 | |||
52 | private static createByString(author: string, podUrl: string): string { | ||
53 | let [ host, port ] = podUrl.replace(/^https?:\/\//, '').split(':'); | ||
54 | |||
55 | if (port === '80' || port === '443') port = ''; | ||
56 | else port = ':' + port; | ||
57 | |||
58 | return author + '@' + host + port; | ||
59 | } | ||
60 | } | ||
diff --git a/client/angular/videos/services/videos.service.ts b/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'; | |||
2 | import { Http, Response } from '@angular/http'; | 2 | import { Http, Response } from '@angular/http'; |
3 | import { Observable } from 'rxjs/Rx'; | 3 | import { Observable } from 'rxjs/Rx'; |
4 | 4 | ||
5 | import { Video } from '../models/video'; | 5 | import { Video } from './video'; |
6 | import { AuthService } from '../../users/services/auth.service'; | 6 | import { AuthService } from '../users/services/auth.service'; |
7 | 7 | ||
8 | @Injectable() | 8 | @Injectable() |
9 | export class VideosService { | 9 | export class VideosService { |
@@ -13,7 +13,8 @@ export class VideosService { | |||
13 | 13 | ||
14 | getVideos() { | 14 | getVideos() { |
15 | return this.http.get(this._baseVideoUrl) | 15 | return this.http.get(this._baseVideoUrl) |
16 | .map(res => <Video[]> res.json()) | 16 | .map(res => res.json()) |
17 | .map(this.extractVideos) | ||
17 | .catch(this.handleError); | 18 | .catch(this.handleError); |
18 | } | 19 | } |
19 | 20 | ||
@@ -24,20 +25,28 @@ export class VideosService { | |||
24 | } | 25 | } |
25 | 26 | ||
26 | removeVideo(id: string) { | 27 | removeVideo(id: string) { |
27 | if (confirm('Are you sure?')) { | 28 | const options = this._authService.getAuthRequestOptions(); |
28 | const options = this._authService.getAuthRequestOptions(); | 29 | return this.http.delete(this._baseVideoUrl + id, options) |
29 | return this.http.delete(this._baseVideoUrl + id, options) | 30 | .map(res => <number> res.status) |
30 | .map(res => <number> res.status) | 31 | .catch(this.handleError); |
31 | .catch(this.handleError); | ||
32 | } | ||
33 | } | 32 | } |
34 | 33 | ||
35 | searchVideos(search: string) { | 34 | searchVideos(search: string) { |
36 | return this.http.get(this._baseVideoUrl + 'search/' + search) | 35 | return this.http.get(this._baseVideoUrl + 'search/' + search) |
37 | .map(res => <Video> res.json()) | 36 | .map(res => res.json()) |
37 | .map(this.extractVideos) | ||
38 | .catch(this.handleError); | 38 | .catch(this.handleError); |
39 | } | 39 | } |
40 | 40 | ||
41 | private extractVideos (body: any[]) { | ||
42 | const videos = []; | ||
43 | for (const video_json of body) { | ||
44 | videos.push(new Video(video_json)); | ||
45 | } | ||
46 | |||
47 | return videos; | ||
48 | } | ||
49 | |||
41 | private handleError (error: Response) { | 50 | private handleError (error: Response) { |
42 | console.error(error); | 51 | console.error(error); |
43 | return Observable.throw(error.json().error || 'Server error'); | 52 | return Observable.throw(error.json().error || 'Server error'); |