diff options
Diffstat (limited to 'client/angular')
16 files changed, 454 insertions, 0 deletions
diff --git a/client/angular/app/app.component.html b/client/angular/app/app.component.html new file mode 100644 index 000000000..590efa0d6 --- /dev/null +++ b/client/angular/app/app.component.html | |||
@@ -0,0 +1,37 @@ | |||
1 | <div class="container"> | ||
2 | |||
3 | <div class="row"> | ||
4 | |||
5 | <menu class="col-md-2"> | ||
6 | <div id="panel_get_videos" class="panel_button"> | ||
7 | <span class="glyphicon glyphicon-list"></span> | ||
8 | <a [routerLink]="['VideosList']">Get videos</a> | ||
9 | </div> | ||
10 | |||
11 | <div id="panel_upload_video" class="panel_button"> | ||
12 | <span class="glyphicon glyphicon-cloud-upload"></span> | ||
13 | <a [routerLink]="['VideosAdd']">Upload a video</a> | ||
14 | </div> | ||
15 | |||
16 | <div id="panel_make_friends" class="panel_button"> | ||
17 | <span class="glyphicon glyphicon-user"></span> | ||
18 | <a (click)='makeFriends()'>Make friends</a> | ||
19 | </div> | ||
20 | |||
21 | <div id="panel_quit_friends" class="panel_button"> | ||
22 | <span class="glyphicon glyphicon-plane"></span> | ||
23 | <a (click)='quitFriends()'>Quit friends</a> | ||
24 | </div> | ||
25 | </menu> | ||
26 | |||
27 | <div class="col-md-9 router_outler_container"> | ||
28 | <router-outlet></router-outlet> | ||
29 | </div> | ||
30 | |||
31 | </div> | ||
32 | |||
33 | |||
34 | <footer> | ||
35 | PeerTube, CopyLeft 2015-2016 | ||
36 | </footer> | ||
37 | </div> | ||
diff --git a/client/angular/app/app.component.scss b/client/angular/app/app.component.scss new file mode 100644 index 000000000..03ecba8f2 --- /dev/null +++ b/client/angular/app/app.component.scss | |||
@@ -0,0 +1,28 @@ | |||
1 | menu { | ||
2 | min-height: 300px; | ||
3 | height: 100%; | ||
4 | margin-right: 20px; | ||
5 | border-right: 1px solid rgba(0, 0, 0, 0.2); | ||
6 | |||
7 | .panel_button { | ||
8 | margin: 8px; | ||
9 | cursor: pointer; | ||
10 | transition: margin 0.2s; | ||
11 | |||
12 | &:hover { | ||
13 | margin-left: 15px; | ||
14 | } | ||
15 | |||
16 | a { | ||
17 | color: #333333; | ||
18 | } | ||
19 | } | ||
20 | |||
21 | .glyphicon { | ||
22 | margin: 5px; | ||
23 | } | ||
24 | } | ||
25 | |||
26 | footer { | ||
27 | margin-top: 30px; | ||
28 | } | ||
diff --git a/client/angular/app/app.component.ts b/client/angular/app/app.component.ts new file mode 100644 index 000000000..3d41183f2 --- /dev/null +++ b/client/angular/app/app.component.ts | |||
@@ -0,0 +1,63 @@ | |||
1 | import { Component, ElementRef } from 'angular2/core'; | ||
2 | import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from 'angular2/router'; | ||
3 | import {HTTP_PROVIDERS} from 'angular2/http'; | ||
4 | |||
5 | import { VideosAddComponent } from '../videos/components/add/videos-add.component'; | ||
6 | import { VideosListComponent } from '../videos/components/list/videos-list.component'; | ||
7 | import { VideosWatchComponent } from '../videos/components/watch/videos-watch.component'; | ||
8 | import { VideosService } from '../videos/services/videos.service'; | ||
9 | import { FriendsService } from '../friends/services/friends.service'; | ||
10 | |||
11 | @RouteConfig([ | ||
12 | { | ||
13 | path: '/videos/list', | ||
14 | name: 'VideosList', | ||
15 | component: VideosListComponent, | ||
16 | useAsDefault: true | ||
17 | }, | ||
18 | { | ||
19 | path: '/videos/watch/:id', | ||
20 | name: 'VideosWatch', | ||
21 | component: VideosWatchComponent | ||
22 | }, | ||
23 | { | ||
24 | path: '/videos/add', | ||
25 | name: 'VideosAdd', | ||
26 | component: VideosAddComponent | ||
27 | } | ||
28 | ]) | ||
29 | |||
30 | @Component({ | ||
31 | selector: 'my-app', | ||
32 | templateUrl: 'app/angular/app/app.component.html', | ||
33 | styleUrls: [ 'app/angular/app/app.component.css' ], | ||
34 | directives: [ ROUTER_DIRECTIVES ], | ||
35 | providers: [ ROUTER_PROVIDERS, HTTP_PROVIDERS, ElementRef, VideosService, FriendsService ] | ||
36 | }) | ||
37 | |||
38 | export class AppComponent { | ||
39 | constructor(private _friendsService: FriendsService) {} | ||
40 | |||
41 | makeFriends() { | ||
42 | this._friendsService.makeFriends().subscribe( | ||
43 | status => { | ||
44 | if (status === 409) { | ||
45 | alert('Already made friends!'); | ||
46 | } | ||
47 | else { | ||
48 | alert('Made friends!'); | ||
49 | } | ||
50 | }, | ||
51 | error => alert(error) | ||
52 | ) | ||
53 | } | ||
54 | |||
55 | quitFriends() { | ||
56 | this._friendsService.quitFriends().subscribe( | ||
57 | status => { | ||
58 | alert('Quit friends!'); | ||
59 | }, | ||
60 | error => alert(error) | ||
61 | ) | ||
62 | } | ||
63 | } | ||
diff --git a/client/angular/bootstrap.ts b/client/angular/bootstrap.ts new file mode 100644 index 000000000..d0f524f4a --- /dev/null +++ b/client/angular/bootstrap.ts | |||
@@ -0,0 +1,4 @@ | |||
1 | import { bootstrap } from 'angular2/platform/browser'; | ||
2 | import { AppComponent } from './app/app.component'; | ||
3 | |||
4 | bootstrap(AppComponent); | ||
diff --git a/client/angular/friends/services/friends.service.ts b/client/angular/friends/services/friends.service.ts new file mode 100644 index 000000000..a34ef0d6f --- /dev/null +++ b/client/angular/friends/services/friends.service.ts | |||
@@ -0,0 +1,27 @@ | |||
1 | import {Injectable} from 'angular2/core'; | ||
2 | import {Http, Response, Headers, RequestOptions} from 'angular2/http'; | ||
3 | import {Observable} from 'rxjs/Rx'; | ||
4 | |||
5 | @Injectable() | ||
6 | export class FriendsService { | ||
7 | private _baseFriendsUrl = '/api/v1/pods/'; | ||
8 | |||
9 | constructor (private http: Http) {} | ||
10 | |||
11 | makeFriends() { | ||
12 | return this.http.get(this._baseFriendsUrl + 'makefriends') | ||
13 | .map(res => <number> res.status) | ||
14 | .catch(this.handleError); | ||
15 | } | ||
16 | |||
17 | quitFriends() { | ||
18 | return this.http.get(this._baseFriendsUrl + 'quitfriends') | ||
19 | .map(res => <number> res.status) | ||
20 | .catch(this.handleError); | ||
21 | } | ||
22 | |||
23 | private handleError (error: Response) { | ||
24 | console.error(error); | ||
25 | return Observable.throw(error.json().error || 'Server error'); | ||
26 | } | ||
27 | } | ||
diff --git a/client/angular/videos/components/add/videos-add.component.html b/client/angular/videos/components/add/videos-add.component.html new file mode 100644 index 000000000..5f28ae650 --- /dev/null +++ b/client/angular/videos/components/add/videos-add.component.html | |||
@@ -0,0 +1,39 @@ | |||
1 | <h3>Upload a video</h3> | ||
2 | |||
3 | <form (ngSubmit)="uploadFile()" #videoForm="ngForm"> | ||
4 | <div class="form-group"> | ||
5 | <label for="name">Video name</label> | ||
6 | <input | ||
7 | type="text" class="form-control" name="name" id="name" required | ||
8 | ngControl="name" #name="ngForm" | ||
9 | > | ||
10 | <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> | ||
11 | Name is required | ||
12 | </div> | ||
13 | </div> | ||
14 | |||
15 | <div class="btn btn-default btn-file"> | ||
16 | <span>Select the video...</span> | ||
17 | <input type="file" name="input_video" id="input_video"> | ||
18 | </div> | ||
19 | |||
20 | <span *ngIf="fileToUpload">{{ fileToUpload.name }}</span> | ||
21 | |||
22 | <div class="form-group"> | ||
23 | <label for="description">Description</label> | ||
24 | <textarea | ||
25 | name="description" id="description" class="form-control" placeholder="Description..." required | ||
26 | ngControl="description" #description="ngForm" | ||
27 | > | ||
28 | </textarea> | ||
29 | <div [hidden]="description.valid || description.pristine" class="alert alert-danger"> | ||
30 | A description is required | ||
31 | </div> | ||
32 | </div> | ||
33 | |||
34 | <div id="progress" *ngIf="progressBar.max !== 0"> | ||
35 | <progress [value]="progressBar.value" [max]="progressBar.max"></progress> | ||
36 | </div> | ||
37 | |||
38 | <input type="submit" value="Upload" class="btn btn-default" [disabled]="!videoForm.form.valid || !fileToUpload"> | ||
39 | </form> | ||
diff --git a/client/angular/videos/components/add/videos-add.component.scss b/client/angular/videos/components/add/videos-add.component.scss new file mode 100644 index 000000000..f4187088b --- /dev/null +++ b/client/angular/videos/components/add/videos-add.component.scss | |||
@@ -0,0 +1,25 @@ | |||
1 | .btn-file { | ||
2 | position: relative; | ||
3 | overflow: hidden; | ||
4 | } | ||
5 | |||
6 | .btn-file input[type=file] { | ||
7 | position: absolute; | ||
8 | top: 0; | ||
9 | right: 0; | ||
10 | min-width: 100%; | ||
11 | min-height: 100%; | ||
12 | font-size: 100px; | ||
13 | text-align: right; | ||
14 | filter: alpha(opacity=0); | ||
15 | opacity: 0; | ||
16 | outline: none; | ||
17 | background: white; | ||
18 | cursor: inherit; | ||
19 | display: block; | ||
20 | } | ||
21 | |||
22 | .name_file { | ||
23 | display: inline-block; | ||
24 | margin-left: 10px; | ||
25 | } | ||
diff --git a/client/angular/videos/components/add/videos-add.component.ts b/client/angular/videos/components/add/videos-add.component.ts new file mode 100644 index 000000000..97e3bb3b5 --- /dev/null +++ b/client/angular/videos/components/add/videos-add.component.ts | |||
@@ -0,0 +1,52 @@ | |||
1 | import {Component, ElementRef, Inject, OnInit} from 'angular2/core'; | ||
2 | import {Router} from 'angular2/router'; | ||
3 | import {NgForm} from 'angular2/common'; | ||
4 | |||
5 | import {Video} from '../../models/video'; | ||
6 | |||
7 | declare var jQuery:any; | ||
8 | |||
9 | @Component({ | ||
10 | selector: 'my-videos-add', | ||
11 | styleUrls: [ 'app/angular/videos/components/add/videos-add.component.css' ], | ||
12 | templateUrl: 'app/angular/videos/components/add/videos-add.component.html' | ||
13 | }) | ||
14 | |||
15 | export class VideosAddComponent implements OnInit { | ||
16 | fileToUpload: any; | ||
17 | progressBar: { value: number; max: number; } = { value: 0, max: 0 }; | ||
18 | |||
19 | private _form: any; | ||
20 | |||
21 | constructor(private _router: Router, private _elementRef: ElementRef) {} | ||
22 | |||
23 | ngOnInit() { | ||
24 | jQuery(this._elementRef.nativeElement).find('#input_video').fileupload({ | ||
25 | singleFileUploads: true, | ||
26 | multipart: true, | ||
27 | url: '/api/v1/videos', | ||
28 | autoupload: false, | ||
29 | |||
30 | add: (e, data) => { | ||
31 | this._form = data; | ||
32 | this.fileToUpload = data['files'][0]; | ||
33 | }, | ||
34 | |||
35 | progressall: (e, data) => { | ||
36 | this.progressBar.value = data.loaded; | ||
37 | this.progressBar.max= data.total; | ||
38 | }, | ||
39 | |||
40 | done: (e, data) => { | ||
41 | console.log('finished'); | ||
42 | // Print all the videos once it's finished | ||
43 | this._router.navigate(['VideosList']); | ||
44 | } | ||
45 | }); | ||
46 | } | ||
47 | |||
48 | uploadFile() { | ||
49 | this._form.formData = jQuery(this._elementRef.nativeElement).find('form').serializeArray(); | ||
50 | this._form.submit(); | ||
51 | } | ||
52 | } | ||
diff --git a/client/angular/videos/components/list/videos-list.component.html b/client/angular/videos/components/list/videos-list.component.html new file mode 100644 index 000000000..7ecdacee4 --- /dev/null +++ b/client/angular/videos/components/list/videos-list.component.html | |||
@@ -0,0 +1,11 @@ | |||
1 | <div *ngFor="#video of videos" class="video"> | ||
2 | <div> | ||
3 | <a [routerLink]="['VideosWatch', { id: video._id }]" class="video_name">{{ video.name }}</a> | ||
4 | <span class="video_pod_url">{{ video.podUrl }}</span> | ||
5 | <span *ngIf="video.namePath !== null" (click)="removeVideo(video._id)" class="video_remove glyphicon glyphicon-remove"></span> | ||
6 | </div> | ||
7 | |||
8 | <div class="video_description"> | ||
9 | {{ video.description }} | ||
10 | </div> | ||
11 | </div> | ||
diff --git a/client/angular/videos/components/list/videos-list.component.scss b/client/angular/videos/components/list/videos-list.component.scss new file mode 100644 index 000000000..82ddd80e5 --- /dev/null +++ b/client/angular/videos/components/list/videos-list.component.scss | |||
@@ -0,0 +1,34 @@ | |||
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 { | ||
32 | display: inline-block; | ||
33 | margin-top: 100px; | ||
34 | } | ||
diff --git a/client/angular/videos/components/list/videos-list.component.ts b/client/angular/videos/components/list/videos-list.component.ts new file mode 100644 index 000000000..e5af87448 --- /dev/null +++ b/client/angular/videos/components/list/videos-list.component.ts | |||
@@ -0,0 +1,39 @@ | |||
1 | import {Component, OnInit} from 'angular2/core'; | ||
2 | import {ROUTER_DIRECTIVES} from 'angular2/router'; | ||
3 | |||
4 | import {VideosService} from '../../services/videos.service'; | ||
5 | import {Video} from '../../models/video'; | ||
6 | |||
7 | @Component({ | ||
8 | selector: 'my-videos-list', | ||
9 | styleUrls: [ 'app/angular/videos/components/list/videos-list.component.css' ], | ||
10 | templateUrl: 'app/angular/videos/components/list/videos-list.component.html', | ||
11 | directives: [ ROUTER_DIRECTIVES ] | ||
12 | }) | ||
13 | |||
14 | export class VideosListComponent implements OnInit { | ||
15 | videos: Video[]; | ||
16 | |||
17 | constructor( | ||
18 | private _videosService: VideosService | ||
19 | ) { } | ||
20 | |||
21 | ngOnInit() { | ||
22 | this.getVideos(); | ||
23 | } | ||
24 | |||
25 | getVideos() { | ||
26 | this._videosService.getVideos().subscribe( | ||
27 | videos => this.videos = videos, | ||
28 | error => alert(error) | ||
29 | ); | ||
30 | } | ||
31 | |||
32 | removeVideo(id: string) { | ||
33 | this._videosService.removeVideo(id).subscribe( | ||
34 | status => this.getVideos(), | ||
35 | error => alert(error) | ||
36 | ) | ||
37 | } | ||
38 | |||
39 | } | ||
diff --git a/client/angular/videos/components/watch/videos-watch.component.html b/client/angular/videos/components/watch/videos-watch.component.html new file mode 100644 index 000000000..e47222751 --- /dev/null +++ b/client/angular/videos/components/watch/videos-watch.component.html | |||
@@ -0,0 +1,2 @@ | |||
1 | <div class="embed-responsive embed-responsive-19by9"> | ||
2 | </div> | ||
diff --git a/client/angular/videos/components/watch/videos-watch.component.scss b/client/angular/videos/components/watch/videos-watch.component.scss new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/client/angular/videos/components/watch/videos-watch.component.scss | |||
diff --git a/client/angular/videos/components/watch/videos-watch.component.ts b/client/angular/videos/components/watch/videos-watch.component.ts new file mode 100644 index 000000000..e3a973820 --- /dev/null +++ b/client/angular/videos/components/watch/videos-watch.component.ts | |||
@@ -0,0 +1,50 @@ | |||
1 | /// <reference path='../../../../typings/browser/ambient/webtorrent/webtorrent.d.ts' /> | ||
2 | |||
3 | import { Component, OnInit, ElementRef } from 'angular2/core'; | ||
4 | import { RouteParams } from 'angular2/router'; | ||
5 | |||
6 | declare var WebTorrent: any; | ||
7 | |||
8 | import { Video } from '../../models/video'; | ||
9 | import { VideosService } from '../../services/videos.service'; | ||
10 | |||
11 | @Component({ | ||
12 | selector: 'my-video-watch', | ||
13 | templateUrl: 'app/angular/videos/components/watch/videos-watch.component.html', | ||
14 | styleUrls: [ 'app/angular/videos/components/watch/videos-watch.component.css' ] | ||
15 | }) | ||
16 | |||
17 | export class VideosWatchComponent { | ||
18 | video: Video; | ||
19 | |||
20 | private client: any; | ||
21 | |||
22 | constructor( | ||
23 | private _videosService: VideosService, | ||
24 | private _routeParams: RouteParams, | ||
25 | private _elementRef: ElementRef | ||
26 | ) { | ||
27 | this.client = new WebTorrent({ dht: false }); | ||
28 | } | ||
29 | |||
30 | ngOnInit() { | ||
31 | let id = this._routeParams.get('id'); | ||
32 | this._videosService.getVideo(id).subscribe( | ||
33 | video => this.loadVideo(video), | ||
34 | error => alert(error) | ||
35 | ); | ||
36 | } | ||
37 | |||
38 | loadVideo(video: Video) { | ||
39 | this.video = video; | ||
40 | |||
41 | this.client.add(this.video.magnetUri, (torrent) => { | ||
42 | torrent.files[0].appendTo(this._elementRef.nativeElement, (err) => { | ||
43 | if (err) { | ||
44 | alert('Cannot append the file.'); | ||
45 | console.error(err); | ||
46 | } | ||
47 | }) | ||
48 | }) | ||
49 | } | ||
50 | } | ||
diff --git a/client/angular/videos/models/video.ts b/client/angular/videos/models/video.ts new file mode 100644 index 000000000..2f998c49a --- /dev/null +++ b/client/angular/videos/models/video.ts | |||
@@ -0,0 +1,6 @@ | |||
1 | export interface Video { | ||
2 | _id: string; | ||
3 | name: string; | ||
4 | description: string; | ||
5 | magnetUri: string; | ||
6 | } | ||
diff --git a/client/angular/videos/services/videos.service.ts b/client/angular/videos/services/videos.service.ts new file mode 100644 index 000000000..784eec68d --- /dev/null +++ b/client/angular/videos/services/videos.service.ts | |||
@@ -0,0 +1,37 @@ | |||
1 | import {Injectable} from 'angular2/core'; | ||
2 | import {Http, Response} from 'angular2/http'; | ||
3 | import {Observable} from 'rxjs/Rx'; | ||
4 | |||
5 | import {Video} from '../models/video'; | ||
6 | |||
7 | @Injectable() | ||
8 | export class VideosService { | ||
9 | private _baseVideoUrl = '/api/v1/videos/'; | ||
10 | |||
11 | constructor (private http: Http) {} | ||
12 | |||
13 | getVideos() { | ||
14 | return this.http.get(this._baseVideoUrl) | ||
15 | .map(res => <Video[]> res.json()) | ||
16 | .catch(this.handleError); | ||
17 | } | ||
18 | |||
19 | getVideo(id: string) { | ||
20 | return this.http.get(this._baseVideoUrl + id) | ||
21 | .map(res => <Video> res.json()) | ||
22 | .catch(this.handleError); | ||
23 | } | ||
24 | |||
25 | removeVideo(id: string) { | ||
26 | if (confirm('Are you sure?')) { | ||
27 | return this.http.delete(this._baseVideoUrl + id) | ||
28 | .map(res => <number> res.status) | ||
29 | .catch(this.handleError); | ||
30 | } | ||
31 | } | ||
32 | |||
33 | private handleError (error: Response) { | ||
34 | console.error(error); | ||
35 | return Observable.throw(error.json().error || 'Server error'); | ||
36 | } | ||
37 | } | ||