diff --git a/.gitignore b/.gitignore
index aaa4db4fb..6c0165c41 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ test5/
test6/
public/stylesheets/global.css
public/stylesheets/vendor
+uploads
diff --git a/client/.gitignore b/client/.gitignore
index 439f0c025..4bb21ebab 100644
--- a/client/.gitignore
+++ b/client/.gitignore
@@ -1,5 +1,5 @@
typings
-components/**/*.js
-components/**/*.map
+angular/**/*.js
+angular/**/*.map
+angular/**/*.css
stylesheets/index.css
-components/**/*.css
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+menu {
+ min-height: 300px;
+ height: 100%;
+ margin-right: 20px;
+ border-right: 1px solid rgba(0, 0, 0, 0.2);
+
+ .panel_button {
+ margin: 8px;
+ cursor: pointer;
+ transition: margin 0.2s;
+
+ &:hover {
+ margin-left: 15px;
+ }
+
+ a {
+ color: #333333;
+ }
+ }
+
+ .glyphicon {
+ margin: 5px;
+ }
+}
+
+footer {
+ margin-top: 30px;
+}
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 @@
+import { Component, ElementRef } from 'angular2/core';
+import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from 'angular2/router';
+import {HTTP_PROVIDERS} from 'angular2/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 { FriendsService } from '../friends/services/friends.service';
+
+@RouteConfig([
+ {
+ path: '/videos/list',
+ name: 'VideosList',
+ component: VideosListComponent,
+ useAsDefault: true
+ },
+ {
+ path: '/videos/watch/:id',
+ name: 'VideosWatch',
+ component: VideosWatchComponent
+ },
+ {
+ path: '/videos/add',
+ name: 'VideosAdd',
+ component: VideosAddComponent
+ }
+])
+
+@Component({
+ selector: 'my-app',
+ templateUrl: 'app/angular/app/app.component.html',
+ styleUrls: [ 'app/angular/app/app.component.css' ],
+ directives: [ ROUTER_DIRECTIVES ],
+ providers: [ ROUTER_PROVIDERS, HTTP_PROVIDERS, ElementRef, VideosService, FriendsService ]
+})
+
+export class AppComponent {
+ constructor(private _friendsService: FriendsService) {}
+
+ makeFriends() {
+ this._friendsService.makeFriends().subscribe(
+ status => {
+ if (status === 409) {
+ alert('Already made friends!');
+ }
+ else {
+ alert('Made friends!');
+ }
+ },
+ error => alert(error)
+ )
+ }
+
+ quitFriends() {
+ this._friendsService.quitFriends().subscribe(
+ status => {
+ alert('Quit friends!');
+ },
+ error => alert(error)
+ )
+ }
+}
diff --git a/client/components/bootstrap.ts b/client/angular/bootstrap.ts
similarity index 100%
rename from client/components/bootstrap.ts
rename to client/angular/bootstrap.ts
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 @@
+import {Injectable} from 'angular2/core';
+import {Http, Response, Headers, RequestOptions} from 'angular2/http';
+import {Observable} from 'rxjs/Rx';
+
+@Injectable()
+export class FriendsService {
+ private _baseFriendsUrl = '/api/v1/pods/';
+
+ constructor (private http: Http) {}
+
+ makeFriends() {
+ return this.http.get(this._baseFriendsUrl + 'makefriends')
+ .map(res => res.status)
+ .catch(this.handleError);
+ }
+
+ quitFriends() {
+ return this.http.get(this._baseFriendsUrl + 'quitfriends')
+ .map(res => res.status)
+ .catch(this.handleError);
+ }
+
+ private handleError (error: Response) {
+ console.error(error);
+ return Observable.throw(error.json().error || 'Server error');
+ }
+}
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 @@
+Upload a video
+
+
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 @@
+.btn-file {
+ position: relative;
+ overflow: hidden;
+}
+
+.btn-file input[type=file] {
+ position: absolute;
+ top: 0;
+ right: 0;
+ min-width: 100%;
+ min-height: 100%;
+ font-size: 100px;
+ text-align: right;
+ filter: alpha(opacity=0);
+ opacity: 0;
+ outline: none;
+ background: white;
+ cursor: inherit;
+ display: block;
+}
+
+.name_file {
+ display: inline-block;
+ margin-left: 10px;
+}
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 @@
+import {Component, ElementRef, Inject, OnInit} from 'angular2/core';
+import {Router} from 'angular2/router';
+import {NgForm} from 'angular2/common';
+
+import {Video} from '../../models/video';
+
+declare var jQuery:any;
+
+@Component({
+ selector: 'my-videos-add',
+ styleUrls: [ 'app/angular/videos/components/add/videos-add.component.css' ],
+ templateUrl: 'app/angular/videos/components/add/videos-add.component.html'
+})
+
+export class VideosAddComponent implements OnInit {
+ fileToUpload: any;
+ progressBar: { value: number; max: number; } = { value: 0, max: 0 };
+
+ private _form: any;
+
+ constructor(private _router: Router, private _elementRef: ElementRef) {}
+
+ ngOnInit() {
+ jQuery(this._elementRef.nativeElement).find('#input_video').fileupload({
+ singleFileUploads: true,
+ multipart: true,
+ url: '/api/v1/videos',
+ autoupload: false,
+
+ add: (e, data) => {
+ this._form = data;
+ this.fileToUpload = data['files'][0];
+ },
+
+ progressall: (e, data) => {
+ this.progressBar.value = data.loaded;
+ this.progressBar.max= data.total;
+ },
+
+ done: (e, data) => {
+ console.log('finished');
+ // Print all the videos once it's finished
+ this._router.navigate(['VideosList']);
+ }
+ });
+ }
+
+ uploadFile() {
+ this._form.formData = jQuery(this._elementRef.nativeElement).find('form').serializeArray();
+ this._form.submit();
+ }
+}
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 @@
+
+
+
+
+ {{ video.description }}
+
+
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 @@
+.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;
+}
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 @@
+import {Component, OnInit} from 'angular2/core';
+import {ROUTER_DIRECTIVES} from 'angular2/router';
+
+import {VideosService} from '../../services/videos.service';
+import {Video} from '../../models/video';
+
+@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 ]
+})
+
+export class VideosListComponent implements OnInit {
+ videos: Video[];
+
+ constructor(
+ private _videosService: VideosService
+ ) { }
+
+ ngOnInit() {
+ this.getVideos();
+ }
+
+ getVideos() {
+ this._videosService.getVideos().subscribe(
+ videos => this.videos = videos,
+ error => alert(error)
+ );
+ }
+
+ removeVideo(id: string) {
+ this._videosService.removeVideo(id).subscribe(
+ status => this.getVideos(),
+ error => alert(error)
+ )
+ }
+
+}
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 @@
+
+
diff --git a/client/components/app/app.component.scss b/client/angular/videos/components/watch/videos-watch.component.scss
similarity index 100%
rename from client/components/app/app.component.scss
rename to 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 @@
+///
+
+import { Component, OnInit, ElementRef } from 'angular2/core';
+import { RouteParams } from 'angular2/router';
+
+declare var WebTorrent: any;
+
+import { Video } from '../../models/video';
+import { VideosService } from '../../services/videos.service';
+
+@Component({
+ selector: 'my-video-watch',
+ templateUrl: 'app/angular/videos/components/watch/videos-watch.component.html',
+ styleUrls: [ 'app/angular/videos/components/watch/videos-watch.component.css' ]
+})
+
+export class VideosWatchComponent {
+ video: Video;
+
+ private client: any;
+
+ constructor(
+ private _videosService: VideosService,
+ private _routeParams: RouteParams,
+ private _elementRef: ElementRef
+ ) {
+ this.client = new WebTorrent({ dht: false });
+ }
+
+ ngOnInit() {
+ let id = this._routeParams.get('id');
+ this._videosService.getVideo(id).subscribe(
+ video => this.loadVideo(video),
+ error => alert(error)
+ );
+ }
+
+ loadVideo(video: Video) {
+ this.video = video;
+
+ this.client.add(this.video.magnetUri, (torrent) => {
+ torrent.files[0].appendTo(this._elementRef.nativeElement, (err) => {
+ if (err) {
+ alert('Cannot append the file.');
+ console.error(err);
+ }
+ })
+ })
+ }
+}
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 @@
+export interface Video {
+ _id: string;
+ name: string;
+ description: string;
+ magnetUri: string;
+}
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 @@
+import {Injectable} from 'angular2/core';
+import {Http, Response} from 'angular2/http';
+import {Observable} from 'rxjs/Rx';
+
+import {Video} from '../models/video';
+
+@Injectable()
+export class VideosService {
+ private _baseVideoUrl = '/api/v1/videos/';
+
+ constructor (private http: Http) {}
+
+ getVideos() {
+ return this.http.get(this._baseVideoUrl)
+ .map(res =>