Follow the angular styleguide for the directories structure
authorChocobozzz <florian.bigard@gmail.com>
Fri, 27 May 2016 14:23:10 +0000 (16:23 +0200)
committerChocobozzz <florian.bigard@gmail.com>
Fri, 27 May 2016 14:23:10 +0000 (16:23 +0200)
95 files changed:
client/.gitignore
client/angular/app/app.component.html [deleted file]
client/angular/app/app.component.scss [deleted file]
client/angular/app/app.component.ts [deleted file]
client/angular/app/search.component.html [deleted file]
client/angular/app/search.component.ts [deleted file]
client/angular/app/search.ts [deleted file]
client/angular/friends/services/friends.service.ts [deleted file]
client/angular/main.ts [deleted file]
client/angular/users/components/login/login.component.html [deleted file]
client/angular/users/components/login/login.component.scss [deleted file]
client/angular/users/components/login/login.component.ts [deleted file]
client/angular/users/models/authStatus.ts [deleted file]
client/angular/users/models/token.ts [deleted file]
client/angular/users/models/user.ts [deleted file]
client/angular/users/services/auth.service.ts [deleted file]
client/angular/videos/components/add/videos-add.component.html [deleted file]
client/angular/videos/components/add/videos-add.component.scss [deleted file]
client/angular/videos/components/add/videos-add.component.ts [deleted file]
client/angular/videos/components/list/sort.ts [deleted file]
client/angular/videos/components/list/video-miniature.component.html [deleted file]
client/angular/videos/components/list/video-miniature.component.scss [deleted file]
client/angular/videos/components/list/video-miniature.component.ts [deleted file]
client/angular/videos/components/list/video-sort.component.html [deleted file]
client/angular/videos/components/list/video-sort.component.ts [deleted file]
client/angular/videos/components/list/videos-list.component.html [deleted file]
client/angular/videos/components/list/videos-list.component.scss [deleted file]
client/angular/videos/components/list/videos-list.component.ts [deleted file]
client/angular/videos/components/watch/videos-watch.component.html [deleted file]
client/angular/videos/components/watch/videos-watch.component.scss [deleted file]
client/angular/videos/components/watch/videos-watch.component.ts [deleted file]
client/angular/videos/loader.component.html [deleted file]
client/angular/videos/loader.component.scss [deleted file]
client/angular/videos/loader.component.ts [deleted file]
client/angular/videos/pagination.ts [deleted file]
client/angular/videos/video.ts [deleted file]
client/angular/videos/videos.service.ts [deleted file]
client/app/app.component.html [new file with mode: 0644]
client/app/app.component.scss [new file with mode: 0644]
client/app/app.component.ts [new file with mode: 0644]
client/app/friends/friend.service.ts [new file with mode: 0644]
client/app/friends/index.ts [new file with mode: 0644]
client/app/shared/index.ts [new file with mode: 0644]
client/app/shared/search-field.type.ts [new file with mode: 0644]
client/app/shared/search.component.html [new file with mode: 0644]
client/app/shared/search.component.ts [new file with mode: 0644]
client/app/shared/search.model.ts [new file with mode: 0644]
client/app/users/index.ts [new file with mode: 0644]
client/app/users/login/index.ts [new file with mode: 0644]
client/app/users/login/login.component.html [new file with mode: 0644]
client/app/users/login/login.component.scss [new file with mode: 0644]
client/app/users/login/login.component.ts [new file with mode: 0644]
client/app/users/shared/auth-status.model.ts [new file with mode: 0644]
client/app/users/shared/auth.service.ts [new file with mode: 0644]
client/app/users/shared/index.ts [new file with mode: 0644]
client/app/users/shared/token.model.ts [new file with mode: 0644]
client/app/users/shared/user.model.ts [new file with mode: 0644]
client/app/videos/index.ts [new file with mode: 0644]
client/app/videos/shared/index.ts [new file with mode: 0644]
client/app/videos/shared/loader/index.ts [new file with mode: 0644]
client/app/videos/shared/loader/loader.component.html [new file with mode: 0644]
client/app/videos/shared/loader/loader.component.scss [new file with mode: 0644]
client/app/videos/shared/loader/loader.component.ts [new file with mode: 0644]
client/app/videos/shared/pagination.model.ts [new file with mode: 0644]
client/app/videos/shared/sort-field.type.ts [new file with mode: 0644]
client/app/videos/shared/video.model.ts [new file with mode: 0644]
client/app/videos/shared/video.service.ts [new file with mode: 0644]
client/app/videos/video-add/index.ts [new file with mode: 0644]
client/app/videos/video-add/video-add.component.html [new file with mode: 0644]
client/app/videos/video-add/video-add.component.scss [new file with mode: 0644]
client/app/videos/video-add/video-add.component.ts [new file with mode: 0644]
client/app/videos/video-list/index.ts [new file with mode: 0644]
client/app/videos/video-list/video-list.component.html [new file with mode: 0644]
client/app/videos/video-list/video-list.component.scss [new file with mode: 0644]
client/app/videos/video-list/video-list.component.ts [new file with mode: 0644]
client/app/videos/video-list/video-miniature.component.html [new file with mode: 0644]
client/app/videos/video-list/video-miniature.component.scss [new file with mode: 0644]
client/app/videos/video-list/video-miniature.component.ts [new file with mode: 0644]
client/app/videos/video-list/video-sort.component.html [new file with mode: 0644]
client/app/videos/video-list/video-sort.component.ts [new file with mode: 0644]
client/app/videos/video-watch/index.ts [new file with mode: 0644]
client/app/videos/video-watch/video-watch.component.html [new file with mode: 0644]
client/app/videos/video-watch/video-watch.component.scss [new file with mode: 0644]
client/app/videos/video-watch/video-watch.component.ts [new file with mode: 0644]
client/index.html
client/main.ts [new file with mode: 0644]
client/stylesheets/application.scss
client/systemjs.config.js
client/tsconfig.json
scripts/build/client/sass.sh
scripts/clean/client/sass.sh
scripts/clean/client/tsc.sh
scripts/watch/client/livereload.sh
scripts/watch/client/sass.sh
server.js

index 0ab218d7183c25806f52edb6ee412e23ece66143..81e4a1cf7defb1b796eec84483f384f045254171 100644 (file)
@@ -1,6 +1,8 @@
 typings
-angular/**/*.js
-angular/**/*.map
-angular/**/*.css
+app/**/*.js
+app/**/*.map
+app/**/*.css
 stylesheets/index.css
 bundles
+main.js
+main.js.map
diff --git a/client/angular/app/app.component.html b/client/angular/app/app.component.html
deleted file mode 100644 (file)
index 48e97d5..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-<div class="container">
-
-  <header class="row">
-    <div class="col-md-2">
-      <h4>PeerTube</h4>
-    </div>
-
-    <div class="col-md-9">
-      <my-search (search)="onSearch($event)"></my-search>
-    </div>
-  </header>
-
-
-  <div class="row">
-
-    <menu class="col-md-2 col-xs-3">
-      <div class="panel_block">
-        <div id="panel_user_login" class="panel_button">
-          <span class="glyphicon glyphicon-user"></span>
-          <a *ngIf="!isLoggedIn" [routerLink]="['UserLogin']">Login</a>
-          <a *ngIf="isLoggedIn" (click)="logout()">Logout</a>
-        </div>
-      </div>
-
-      <div class="panel_block">
-        <div id="panel_get_videos" class="panel_button">
-          <span class="glyphicon glyphicon-list"></span>
-          <a [routerLink]="['VideosList']">Get videos</a>
-        </div>
-
-        <div id="panel_upload_video" class="panel_button" *ngIf="isLoggedIn">
-          <span class="glyphicon glyphicon-cloud-upload"></span>
-          <a [routerLink]="['VideosAdd']">Upload a video</a>
-        </div>
-      </div>
-
-      <div class="panel_block" *ngIf="isLoggedIn">
-        <div id="panel_make_friends" class="panel_button">
-          <span class="glyphicon glyphicon-cloud"></span>
-          <a (click)='makeFriends()'>Make friends</a>
-        </div>
-
-        <div id="panel_quit_friends" class="panel_button">
-          <span class="glyphicon glyphicon-plane"></span>
-          <a (click)='quitFriends()'>Quit friends</a>
-        </div>
-      </div>
-    </menu>
-
-    <div class="col-md-9 col-xs-8 router_outler_container">
-      <router-outlet></router-outlet>
-    </div>
-
-  </div>
-
-
-  <footer>
-    PeerTube, CopyLeft 2015-2016
-  </footer>
-</div>
diff --git a/client/angular/app/app.component.scss b/client/angular/app/app.component.scss
deleted file mode 100644 (file)
index e02c2d5..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-header div {
-  line-height: 25px;
-  margin-bottom: 30px;
-}
-
-menu {
-  min-height: 600px;
-  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;
-  }
-}
-
-.panel_block:not(:last-child) {
-  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
-}
diff --git a/client/angular/app/app.component.ts b/client/angular/app/app.component.ts
deleted file mode 100644 (file)
index 722d0dc..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-import { Component } from '@angular/core';
-import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS, Router } from '@angular/router-deprecated';
-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/videos.service';
-import { FriendsService } from '../friends/services/friends.service';
-import { UserLoginComponent } from '../users/components/login/login.component';
-import { AuthService } from '../users/services/auth.service';
-import { AuthStatus } from '../users/models/authStatus';
-import { SearchComponent } from './search.component';
-import { Search } from './search';
-
-@RouteConfig([
-  {
-    path: '/users/login',
-    name: 'UserLogin',
-    component: UserLoginComponent
-  },
-  {
-    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, SearchComponent ],
-    providers: [ ROUTER_PROVIDERS, HTTP_PROVIDERS, VideosService, FriendsService, AuthService ]
-})
-
-export class AppComponent {
-  isLoggedIn: boolean;
-  search_field: string = name;
-  choices = [  ];
-
-  constructor(private _friendsService: FriendsService,
-              private _authService: AuthService,
-              private _router: Router
-
-  ) {
-    this.isLoggedIn = this._authService.isLoggedIn();
-
-    this._authService.loginChanged$.subscribe(
-      status => {
-        if (status === AuthStatus.LoggedIn) {
-          this.isLoggedIn = true;
-        }
-      }
-    );
-  }
-
-  onSearch(search: Search) {
-    if (search.value !== '') {
-      const params = {
-        search: search.value,
-        field: search.field
-      };
-      this._router.navigate(['VideosList', params]);
-    } else {
-      this._router.navigate(['VideosList']);
-    }
-  }
-
-  logout() {
-    // this._authService.logout();
-  }
-
-  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/angular/app/search.component.html b/client/angular/app/search.component.html
deleted file mode 100644 (file)
index fb13ac7..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-<div class="input-group">
-  <div class="input-group-btn" dropdown>
-    <button id="simple-btn-keyboard-nav" type="button" class="btn btn-default" dropdownToggle>
-      {{ getStringChoice(searchCriterias.field) }} <span class="caret"></span>
-    </button>
-    <ul class="dropdown-menu" role="menu" aria-labelledby="simple-btn-keyboard-nav">
-      <li *ngFor="let choice of choiceKeys" class="dropdown-item">
-        <a class="dropdown-item" href="#" (click)="choose($event, choice)">{{ getStringChoice(choice) }}</a>
-      </li>
-    </ul>
-  </div>
-
-  <input
-    type="text" id="search-video" name="search-video" class="form-control" placeholder="Search a video..." class="form-control"
-    [(ngModel)]="searchCriterias.value" (keyup.enter)="doSearch()"
-  >
-</div>
diff --git a/client/angular/app/search.component.ts b/client/angular/app/search.component.ts
deleted file mode 100644 (file)
index e21b91f..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-import { Component, EventEmitter, Output } from '@angular/core';
-
-import { DROPDOWN_DIRECTIVES} from  'ng2-bootstrap/components/dropdown';
-
-import { Search, SearchField } from './search';
-
-@Component({
-    selector: 'my-search',
-    templateUrl: 'app/angular/app/search.component.html',
-    directives: [ DROPDOWN_DIRECTIVES ]
-})
-
-export class SearchComponent {
-  @Output() search: EventEmitter<Search> = new EventEmitter<Search>();
-
-  searchCriterias: Search = {
-    field: 'name',
-    value: ''
-  };
-  fieldChoices = {
-    name: 'Name',
-    author: 'Author',
-    podUrl: 'Pod Url',
-    magnetUri: 'Magnet Uri'
-  };
-
-  get choiceKeys() {
-    return Object.keys(this.fieldChoices);
-  }
-
-  getStringChoice(choiceKey: SearchField): string {
-    return this.fieldChoices[choiceKey];
-  }
-
-  choose($event:MouseEvent, choice: SearchField) {
-    $event.preventDefault();
-    $event.stopPropagation();
-
-    this.searchCriterias.field = choice;
-  }
-
-  doSearch(): void {
-    this.search.emit(this.searchCriterias);
-  }
-
-}
diff --git a/client/angular/app/search.ts b/client/angular/app/search.ts
deleted file mode 100644 (file)
index c4e771b..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-export type SearchField = "name" | "author" | "podUrl" | "magnetUri";
-
-export interface Search {
-  field: SearchField;
-  value: string;
-}
diff --git a/client/angular/friends/services/friends.service.ts b/client/angular/friends/services/friends.service.ts
deleted file mode 100644 (file)
index cb34323..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-import { Injectable } from '@angular/core';
-import { Http, Response } from '@angular/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 => <number> res.status)
-                    .catch(this.handleError);
-  }
-
-  quitFriends() {
-    return this.http.get(this._baseFriendsUrl + 'quitfriends')
-                    .map(res => <number> 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/main.ts b/client/angular/main.ts
deleted file mode 100644 (file)
index e35f7db..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-import { bootstrap }    from '@angular/platform-browser-dynamic';
-import { AppComponent } from './app/app.component';
-
-bootstrap(AppComponent);
diff --git a/client/angular/users/components/login/login.component.html b/client/angular/users/components/login/login.component.html
deleted file mode 100644 (file)
index 9406945..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<h3>Login</h3>
-<form role="form" (submit)="login(username.value, password.value)">
-  <div class="form-group">
-    <label for="username">Username</label>
-    <input type="text" #username class="form-control" id="username" placeholder="Username">
-  </div>
-
-  <div class="form-group">
-    <label for="password">Password</label>
-    <input type="password" #password class="form-control" id="password" placeholder="Password">
-  </div>
-
-  <input type="submit" value="Login" class="btn btn-default">
-</form>
diff --git a/client/angular/users/components/login/login.component.scss b/client/angular/users/components/login/login.component.scss
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/client/angular/users/components/login/login.component.ts b/client/angular/users/components/login/login.component.ts
deleted file mode 100644 (file)
index d339353..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-import { Component } from '@angular/core';
-import { Router } from '@angular/router-deprecated';
-
-import { AuthService } from '../../services/auth.service';
-import { AuthStatus } from '../../models/authStatus';
-import { User } from '../../models/user';
-
-@Component({
-  selector: 'my-user-login',
-  styleUrls: [ 'app/angular/users/components/login/login.component.css' ],
-  templateUrl: 'app/angular/users/components/login/login.component.html'
-})
-
-export class UserLoginComponent {
-  constructor(private _authService: AuthService, private _router: Router) {}
-
-  login(username: string, password: string) {
-    this._authService.login(username, password).subscribe(
-      result => {
-        const user = new User(username, result);
-        user.save();
-
-        this._authService.setStatus(AuthStatus.LoggedIn);
-
-        this._router.navigate(['VideosList']);
-      },
-      error => {
-        if (error.error === 'invalid_grant') {
-          alert('Credentials are invalid.');
-        } else {
-          alert(`${error.error}: ${error.error_description}`);
-        }
-      }
-    );
-  }
-}
diff --git a/client/angular/users/models/authStatus.ts b/client/angular/users/models/authStatus.ts
deleted file mode 100644 (file)
index f646bd4..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-export enum AuthStatus {
-  LoggedIn,
-  LoggedOut
-}
diff --git a/client/angular/users/models/token.ts b/client/angular/users/models/token.ts
deleted file mode 100644 (file)
index b7872e7..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-export class Token {
-  access_token: string;
-  refresh_token: string;
-  token_type: string;
-
-  static load(): Token {
-    return new Token({
-      access_token: localStorage.getItem('access_token'),
-      refresh_token: localStorage.getItem('refresh_token'),
-      token_type: localStorage.getItem('token_type')
-    });
-  }
-
-  constructor (hash?: any) {
-    if (hash) {
-      this.access_token = hash.access_token;
-      this.refresh_token = hash.refresh_token;
-      if (hash.token_type === 'bearer') {
-        this.token_type = 'Bearer';
-      } else {
-        this.token_type = hash.token_type;
-      }
-    }
-  }
-
-  save():void {
-    localStorage.setItem('access_token', this.access_token);
-    localStorage.setItem('refresh_token', this.refresh_token);
-    localStorage.setItem('token_type', this.token_type);
-  }
-}
diff --git a/client/angular/users/models/user.ts b/client/angular/users/models/user.ts
deleted file mode 100644 (file)
index 3367e3b..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Token } from './token';
-
-export class User {
-  username: string;
-  token: Token;
-
-  static load(): User {
-    return new User(localStorage.getItem('username'), Token.load());
-  }
-
-  constructor (username: string, hash_token: any) {
-    this.username = username;
-    this.token = new Token(hash_token);
-  }
-
-  save(): void {
-    localStorage.setItem('username', this.username);
-    this.token.save();
-  }
-}
diff --git a/client/angular/users/services/auth.service.ts b/client/angular/users/services/auth.service.ts
deleted file mode 100644 (file)
index 099563d..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-import { Injectable } from '@angular/core';
-import { Http, Response, Headers, URLSearchParams, RequestOptions } from '@angular/http';
-import { Observable, Subject } from 'rxjs/Rx';
-
-import { AuthStatus } from '../models/authStatus';
-import { User } from '../models/user';
-
-@Injectable()
-export class AuthService {
-  loginChanged$;
-
-  private _loginChanged;
-  private _baseLoginUrl = '/api/v1/users/token';
-  private _baseClientUrl = '/api/v1/users/client';
-  private _clientId = '';
-  private _clientSecret = '';
-
-  constructor (private http: Http) {
-    this._loginChanged = new Subject<AuthStatus>();
-    this.loginChanged$ = this._loginChanged.asObservable();
-
-    // Fetch the client_id/client_secret
-    // FIXME: save in local storage?
-    this.http.get(this._baseClientUrl)
-      .map(res => res.json())
-      .catch(this.handleError)
-      .subscribe(
-        result => {
-          this._clientId = result.client_id;
-          this._clientSecret = result.client_secret;
-          console.log('Client credentials loaded.');
-        },
-        error => {
-          alert(error);
-        }
-      );
-  }
-
-  login(username: string, password: string) {
-    let body = new URLSearchParams();
-    body.set('client_id', this._clientId);
-    body.set('client_secret', this._clientSecret);
-    body.set('response_type', 'code');
-    body.set('grant_type', 'password');
-    body.set('scope', 'upload');
-    body.set('username', username);
-    body.set('password', password);
-
-    let headers = new Headers();
-    headers.append('Content-Type', 'application/x-www-form-urlencoded');
-
-    let options = {
-      headers: headers
-    };
-
-    return this.http.post(this._baseLoginUrl, body.toString(), options)
-                    .map(res => res.json())
-                    .catch(this.handleError);
-  }
-
-  logout() {
-    // TODO make HTTP request
-  }
-
-  getRequestHeader(): Headers {
-    return new Headers({ 'Authorization': `${this.getTokenType()} ${this.getToken()}` });
-  }
-
-  getAuthRequestOptions(): RequestOptions {
-    return new RequestOptions({ headers: this.getRequestHeader() });
-  }
-
-  getToken(): string {
-    return localStorage.getItem('access_token');
-  }
-
-  getTokenType(): string {
-    return localStorage.getItem('token_type');
-  }
-
-  getUser(): User {
-    if (this.isLoggedIn() === false) {
-      return null;
-    }
-
-    const user = User.load();
-
-    return user;
-  }
-
-  isLoggedIn(): boolean {
-    if (this.getToken()) {
-      return true;
-    } else {
-      return false;
-    }
-  }
-
-  setStatus(status: AuthStatus) {
-    this._loginChanged.next(status);
-  }
-
-  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
deleted file mode 100644 (file)
index 80d229c..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-<h3>Upload a video</h3>
-
-<form (ngSubmit)="uploadFile()" #videoForm="ngForm">
-  <div class="form-group">
-    <label for="name">Video name</label>
-    <input
-      type="text" class="form-control" name="name" id="name" required
-      ngControl="name"  #name="ngForm"
-    >
-    <div [hidden]="name.valid || name.pristine" class="alert alert-danger">
-      Name is required
-    </div>
-  </div>
-
-  <div class="form-group">
-    <div class="btn btn-default btn-file">
-      <span>Select the video...</span>
-      <input type="file" name="videofile" id="videofile">
-    </div>
-
-    <span *ngIf="fileToUpload">{{ fileToUpload.name }}</span>
-  </div>
-
-  <div class="form-group">
-    <label for="description">Description</label>
-    <textarea
-      name="description" id="description" class="form-control" placeholder="Description..." required
-      ngControl="description"  #description="ngForm"
-    >
-    </textarea>
-    <div [hidden]="description.valid || description.pristine" class="alert alert-danger">
-        A description is required
-    </div>
-  </div>
-
-  <div id="progress" *ngIf="progressBar.max !== 0">
-    <progressbar [value]="progressBar.value" [max]="progressBar.max">{{ progressBar.value | bytes }} / {{ progressBar.max | bytes }}</progressbar>
-  </div>
-
-  <input type="submit" value="Upload" class="btn btn-default" [disabled]="!videoForm.form.valid || !fileToUpload">
-</form>
diff --git a/client/angular/videos/components/add/videos-add.component.scss b/client/angular/videos/components/add/videos-add.component.scss
deleted file mode 100644 (file)
index 01195f0..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-.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;
-}
-
-.form-group {
-  margin-bottom: 10px;
-}
-
-#progress {
-  margin-bottom: 10px;
-}
diff --git a/client/angular/videos/components/add/videos-add.component.ts b/client/angular/videos/components/add/videos-add.component.ts
deleted file mode 100644 (file)
index f1652be..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-import { Component, ElementRef, OnInit } from '@angular/core';
-import { Router } from '@angular/router-deprecated';
-
-import { PROGRESSBAR_DIRECTIVES } from 'ng2-bootstrap/components/progressbar';
-import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe';
-
-import { AuthService } from '../../../users/services/auth.service';
-import { User } from '../../../users/models/user';
-
-// TODO: import it with systemjs
-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',
-  directives: [ PROGRESSBAR_DIRECTIVES ],
-  pipes: [ BytesPipe ]
-})
-
-export class VideosAddComponent implements OnInit {
-  user: User;
-  fileToUpload: any;
-  progressBar: { value: number; max: number; } = { value: 0, max: 0 };
-
-  private _form: any;
-
-  constructor(
-    private _router: Router, private _elementRef: ElementRef,
-    private _authService: AuthService
-  ) {}
-
-  ngOnInit() {
-    this.user = User.load();
-    jQuery(this._elementRef.nativeElement).find('#videofile').fileupload({
-      url: '/api/v1/videos',
-      dataType: 'json',
-      singleFileUploads: true,
-      multipart: true,
-      autoupload: false,
-
-      add: (e, data) => {
-        this._form = data;
-        this.fileToUpload = data['files'][0];
-      },
-
-      progressall: (e, data) => {
-        this.progressBar.value = data.loaded;
-        // The server is a little bit slow to answer (has to seed the video)
-        // So we add more time to the progress bar (+10%)
-        this.progressBar.max = data.total + (0.1 * data.total);
-      },
-
-      done: (e, data) => {
-        this.progressBar.value = this.progressBar.max;
-        console.log('Video uploaded.');
-
-        // Print all the videos once it's finished
-        this._router.navigate(['VideosList']);
-      }
-    });
-  }
-
-  uploadFile() {
-    this._form.headers = this._authService.getRequestHeader().toJSON();
-    this._form.formData = jQuery(this._elementRef.nativeElement).find('form').serializeArray();
-    this._form.submit();
-  }
-}
diff --git a/client/angular/videos/components/list/sort.ts b/client/angular/videos/components/list/sort.ts
deleted file mode 100644 (file)
index 6e8cc79..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-export type SortField = "name" | "-name"
-                      | "duration" | "-duration"
-                      | "createdDate" | "-createdDate";
diff --git a/client/angular/videos/components/list/video-miniature.component.html b/client/angular/videos/components/list/video-miniature.component.html
deleted file mode 100644 (file)
index 244254b..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-<div class="video-miniature col-md-4" (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>
diff --git a/client/angular/videos/components/list/video-miniature.component.scss b/client/angular/videos/components/list/video-miniature.component.scss
deleted file mode 100644 (file)
index 4488abe..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-.video-miniature {
-  height: 200px;
-  display: inline-block;
-  position: relative;
-
-  .video-miniature-thumbnail {
-    display: block;
-    position: relative;
-
-    .video-miniature-duration {
-      position: absolute;
-      right: 60px;
-      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: 16px;
-    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
deleted file mode 100644 (file)
index 383c2c6..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-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)
-      );
-    }
-  }
-}
diff --git a/client/angular/videos/components/list/video-sort.component.html b/client/angular/videos/components/list/video-sort.component.html
deleted file mode 100644 (file)
index 3bece0b..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<select class="form-control input-sm" [(ngModel)]="currentSort" (ngModelChange)="onSortChange()">
-  <option *ngFor="let choice of choiceKeys" [value]="choice">
-    {{ getStringChoice(choice) }}
-  </option>
-</select>
diff --git a/client/angular/videos/components/list/video-sort.component.ts b/client/angular/videos/components/list/video-sort.component.ts
deleted file mode 100644 (file)
index 0373cea..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-import { Component, Input, Output, EventEmitter } from '@angular/core';
-
-import { SortField } from './sort';
-
-@Component({
-  selector: 'my-video-sort',
-  // styleUrls: [ 'app/angular/videos/components/list/video-sort.component.css' ],
-  templateUrl: 'app/angular/videos/components/list/video-sort.component.html'
-})
-
-export class VideoSortComponent {
-  @Output() sort = new EventEmitter<any>();
-
-  @Input() currentSort: SortField;
-
-  sortChoices = {
-    'name': 'Name - Asc',
-    '-name': 'Name - Desc',
-    'duration': 'Duration - Asc',
-    '-duration': 'Duration - Desc',
-    'createdDate': 'Created Date - Asc',
-    '-createdDate': 'Created Date - Desc'
-  };
-
-  get choiceKeys() {
-    return Object.keys(this.sortChoices);
-  }
-
-  getStringChoice(choiceKey: SortField): string {
-    return this.sortChoices[choiceKey];
-  }
-
-  onSortChange() {
-    this.sort.emit(this.currentSort);
-  }
-}
diff --git a/client/angular/videos/components/list/videos-list.component.html b/client/angular/videos/components/list/videos-list.component.html
deleted file mode 100644 (file)
index edbbaf3..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-<div class="row videos-info">
-  <div class="col-md-9 videos-total-results"> {{ pagination.total }} videos</div>
-  <my-video-sort class="col-md-3" [currentSort]="sort" (sort)="onSort($event)"></my-video-sort>
-</div>
-
-<div class="videos-miniatures">
-  <my-loader [loading]="loading"></my-loader>
-
-  <div class="col-md-12 no-video" *ngIf="!loading && videos.length === 0">There is no video.</div>
-
-  <my-video-miniature *ngFor="let video of videos" [video]="video" [user]="user" (removed)="onRemoved(video)">
-  </my-video-miniature>
-</div>
-
-<pagination
-  [totalItems]="pagination.total" [itemsPerPage]="pagination.itemsPerPage" [(ngModel)]="pagination.currentPage"
-  (ngModelChange)="getVideos()"
-></pagination>
diff --git a/client/angular/videos/components/list/videos-list.component.scss b/client/angular/videos/components/list/videos-list.component.scss
deleted file mode 100644 (file)
index 9441d80..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-.videos-info {
-
-  padding-bottom: 20px;
-  margin-bottom: 20px;
-  border-bottom: 1px solid #f1f1f1;
-  height: 40px;
-  line-height: 40px;
-  width: 765px;
-  margin-left: 15px;
-
-  my-video-sort {
-    padding-right: 0;
-  }
-
-  .videos-total-results {
-    font-size: 13px;
-    padding-left: 0;
-  }
-}
-
-.videos-miniatures {
-  min-height: 600px;
-
-  my-videos-miniature {
-    display: inline-block;
-  }
-
-  .no-video {
-    margin-top: 50px;
-    text-align: center;
-  }
-}
-
-pagination {
-  display: block;
-  text-align: center;
-}
diff --git a/client/angular/videos/components/list/videos-list.component.ts b/client/angular/videos/components/list/videos-list.component.ts
deleted file mode 100644 (file)
index 56230e3..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-import { Component, OnInit } from '@angular/core';
-import { ROUTER_DIRECTIVES, RouteParams, Router } from '@angular/router-deprecated';
-
-import { PAGINATION_DIRECTIVES } from 'ng2-bootstrap/components/pagination';
-
-import { AuthService } from '../../../users/services/auth.service';
-import { Pagination } from '../../pagination';
-import { User } from '../../../users/models/user';
-import { VideosService } from '../../videos.service';
-import { Video } from '../../video';
-import { VideoMiniatureComponent } from './video-miniature.component';
-import { Search, SearchField } from '../../../app/search';
-import { VideoSortComponent } from './video-sort.component';
-import { SortField } from './sort';
-import { LoaderComponent } from '../../loader.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, PAGINATION_DIRECTIVES, VideoMiniatureComponent, VideoSortComponent, LoaderComponent ]
-})
-
-export class VideosListComponent implements OnInit {
-  user: User = null;
-  videos: Video[] = [];
-  pagination: Pagination = {
-    currentPage: 1,
-    itemsPerPage: 9,
-    total: 0
-  };
-  sort: SortField;
-  loading: boolean = false;
-
-  private search: Search;
-
-  constructor(
-    private _authService: AuthService,
-    private _videosService: VideosService,
-    private _routeParams: RouteParams,
-    private _router: Router
-  ) {
-    this.search = {
-      value: this._routeParams.get('search'),
-      field: <SearchField>this._routeParams.get('field')
-    };
-
-    this.sort = <SortField>this._routeParams.get('sort') || '-createdDate';
-  }
-
-  ngOnInit() {
-    if (this._authService.isLoggedIn()) {
-      this.user = User.load();
-    }
-
-    this.getVideos();
-  }
-
-  getVideos() {
-    this.loading = true;
-    this.videos = [];
-
-    let observable = null;
-
-    if (this.search.value !== null) {
-      observable = this._videosService.searchVideos(this.search, this.pagination, this.sort);
-    } else {
-      observable = this._videosService.getVideos(this.pagination, this.sort);
-    }
-
-    observable.subscribe(
-      ({ videos, totalVideos }) => {
-        this.videos = videos;
-        this.pagination.total = totalVideos;
-        this.loading = false;
-      },
-      error => alert(error)
-    );
-  }
-
-  onRemoved(video: Video): void {
-    this.videos.splice(this.videos.indexOf(video), 1);
-  }
-
-  onSort(sort: SortField) {
-    this.sort = sort;
-
-    const params: any = {
-      sort: this.sort
-    };
-
-    if (this.search.value) {
-      params.search = this.search.value;
-      params.field = this.search.field;
-    }
-
-    this._router.navigate(['VideosList', params]);
-    this.getVideos();
-  }
-}
diff --git a/client/angular/videos/components/watch/videos-watch.component.html b/client/angular/videos/components/watch/videos-watch.component.html
deleted file mode 100644 (file)
index 6c36b27..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<my-loader [loading]="loading"></my-loader>
-
-<div class="embed-responsive embed-responsive-19by9">
-</div>
-
-<div id="torrent-info">
-  <div id="torrent-info-download">Download: {{ downloadSpeed | bytes }}/s</div>
-  <div id="torrent-info-upload">Upload: {{ uploadSpeed | bytes }}/s</div>
-  <div id="torrent-info-peers">Number of peers: {{ numPeers }}</div>
-<div>
diff --git a/client/angular/videos/components/watch/videos-watch.component.scss b/client/angular/videos/components/watch/videos-watch.component.scss
deleted file mode 100644 (file)
index 1228d42..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-.embed-responsive {
-  height: 500px;
-}
-
-#torrent-info {
-  font-size: 10px;
-
-  div {
-    display: inline-block;
-    width: 33%;
-    text-align: center;
-  }
-}
diff --git a/client/angular/videos/components/watch/videos-watch.component.ts b/client/angular/videos/components/watch/videos-watch.component.ts
deleted file mode 100644 (file)
index e551e1f..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-import { Component, OnInit, ElementRef } from '@angular/core';
-import { RouteParams, CanDeactivate, ComponentInstruction } from '@angular/router-deprecated';
-
-import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe';
-
-import { LoaderComponent } from '../../loader.component';
-
-// TODO import it with systemjs
-declare var WebTorrent: any;
-
-import { Video } from '../../video';
-import { VideosService } from '../../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' ],
-  directives: [ LoaderComponent ],
-  pipes: [ BytesPipe ]
-})
-
-export class VideosWatchComponent implements OnInit, CanDeactivate {
-  video: Video;
-  downloadSpeed: number;
-  uploadSpeed: number;
-  numPeers: number;
-  loading: boolean = false;
-
-  private _interval: NodeJS.Timer;
-  private client: any;
-
-  constructor(
-    private _videosService: VideosService,
-    private _routeParams: RouteParams,
-    private _elementRef: ElementRef
-  ) {
-    // TODO: use a service
-    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.loading = true;
-    this.video = video;
-    console.log('Adding ' + this.video.magnetUri + '.');
-    this.client.add(this.video.magnetUri, (torrent) => {
-      this.loading = false;
-      console.log('Added ' + this.video.magnetUri + '.');
-      torrent.files[0].appendTo(this._elementRef.nativeElement.querySelector('.embed-responsive'), (err) => {
-        if (err) {
-          alert('Cannot append the file.');
-          console.error(err);
-        }
-      });
-
-      // Refresh each second
-      this._interval = setInterval(() => {
-        this.downloadSpeed = torrent.downloadSpeed;
-        this.uploadSpeed = torrent.uploadSpeed;
-        this.numPeers = torrent.numPeers;
-      }, 1000);
-    });
-  }
-
-  routerCanDeactivate(next: ComponentInstruction, prev: ComponentInstruction) : any {
-    console.log('Removing video from webtorrent.');
-    clearInterval(this._interval);
-    this.client.remove(this.video.magnetUri);
-    return true;
-  }
-}
diff --git a/client/angular/videos/loader.component.html b/client/angular/videos/loader.component.html
deleted file mode 100644 (file)
index d02296a..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-<div id="video-loading" class="col-md-12 text-center" *ngIf="loading">
-  <div class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></div>
-</div>
diff --git a/client/angular/videos/loader.component.scss b/client/angular/videos/loader.component.scss
deleted file mode 100644 (file)
index 4541958..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-div {
-  margin-top: 150px;
-}
-
-// Thanks https://gist.github.com/alexandrevicenzi/680147013e902a4eaa5d
-.glyphicon-refresh-animate {
-    -animation: spin .7s infinite linear;
-    -ms-animation: spin .7s infinite linear;
-    -webkit-animation: spinw .7s infinite linear;
-    -moz-animation: spinm .7s infinite linear;
-}
-
-@keyframes spin {
-    from { transform: scale(1) rotate(0deg);}
-    to { transform: scale(1) rotate(360deg);}
-}
-
-@-webkit-keyframes spinw {
-    from { -webkit-transform: rotate(0deg);}
-    to { -webkit-transform: rotate(360deg);}
-}
-
-@-moz-keyframes spinm {
-    from { -moz-transform: rotate(0deg);}
-    to { -moz-transform: rotate(360deg);}
-}
diff --git a/client/angular/videos/loader.component.ts b/client/angular/videos/loader.component.ts
deleted file mode 100644 (file)
index 2432943..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-import { Component, Input } from '@angular/core';
-
-@Component({
-  selector: 'my-loader',
-  styleUrls: [ 'app/angular/videos/loader.component.css' ],
-  templateUrl: 'app/angular/videos/loader.component.html'
-})
-
-export class LoaderComponent {
-  @Input() loading: boolean;
-}
diff --git a/client/angular/videos/pagination.ts b/client/angular/videos/pagination.ts
deleted file mode 100644 (file)
index 06f7a78..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-export interface Pagination {
-  currentPage: number;
-  itemsPerPage: number;
-  total: number;
-}
diff --git a/client/angular/videos/video.ts b/client/angular/videos/video.ts
deleted file mode 100644 (file)
index eec537c..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-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;
-
-  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;
-  }
-
-  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;
-  }
-}
diff --git a/client/angular/videos/videos.service.ts b/client/angular/videos/videos.service.ts
deleted file mode 100644 (file)
index d5438fd..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-import { Injectable } from '@angular/core';
-import { Http, Response, URLSearchParams } from '@angular/http';
-import { Observable } from 'rxjs/Rx';
-
-import { Pagination } from './pagination';
-import { Video } from './video';
-import { AuthService } from '../users/services/auth.service';
-import { Search } from '../app/search';
-import { SortField } from './components/list/sort';
-
-@Injectable()
-export class VideosService {
-  private _baseVideoUrl = '/api/v1/videos/';
-
-  constructor (private http: Http, private _authService: AuthService) {}
-
-  getVideos(pagination: Pagination, sort: SortField) {
-    const params = this.createPaginationParams(pagination);
-
-    if (sort) params.set('sort', sort);
-
-    return this.http.get(this._baseVideoUrl, { search: params })
-                    .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: Search, pagination: Pagination, sort: SortField) {
-    const params = this.createPaginationParams(pagination);
-
-    if (search.field) params.set('field', search.field);
-    if (sort) params.set('sort', sort);
-
-    return this.http.get(this._baseVideoUrl + 'search/' + encodeURIComponent(search.value), { search: params })
-                    .map(res => res.json())
-                    .map(this.extractVideos)
-                    .catch(this.handleError);
-  }
-
-  private extractVideos (body: any) {
-    const videos_json = body.data;
-    const totalVideos = body.total;
-    const videos = [];
-    for (const video_json of videos_json) {
-      videos.push(new Video(video_json));
-    }
-
-    return { videos, totalVideos };
-  }
-
-  private handleError (error: Response) {
-    console.error(error);
-    return Observable.throw(error.json().error || 'Server error');
-  }
-
-  private createPaginationParams(pagination: Pagination) {
-    const params = new URLSearchParams();
-    const start: number = (pagination.currentPage - 1) * pagination.itemsPerPage;
-    const count: number = pagination.itemsPerPage;
-
-    params.set('start', start.toString());
-    params.set('count', count.toString());
-
-    return params;
-  }
-}
diff --git a/client/app/app.component.html b/client/app/app.component.html
new file mode 100644 (file)
index 0000000..48e97d5
--- /dev/null
@@ -0,0 +1,60 @@
+<div class="container">
+
+  <header class="row">
+    <div class="col-md-2">
+      <h4>PeerTube</h4>
+    </div>
+
+    <div class="col-md-9">
+      <my-search (search)="onSearch($event)"></my-search>
+    </div>
+  </header>
+
+
+  <div class="row">
+
+    <menu class="col-md-2 col-xs-3">
+      <div class="panel_block">
+        <div id="panel_user_login" class="panel_button">
+          <span class="glyphicon glyphicon-user"></span>
+          <a *ngIf="!isLoggedIn" [routerLink]="['UserLogin']">Login</a>
+          <a *ngIf="isLoggedIn" (click)="logout()">Logout</a>
+        </div>
+      </div>
+
+      <div class="panel_block">
+        <div id="panel_get_videos" class="panel_button">
+          <span class="glyphicon glyphicon-list"></span>
+          <a [routerLink]="['VideosList']">Get videos</a>
+        </div>
+
+        <div id="panel_upload_video" class="panel_button" *ngIf="isLoggedIn">
+          <span class="glyphicon glyphicon-cloud-upload"></span>
+          <a [routerLink]="['VideosAdd']">Upload a video</a>
+        </div>
+      </div>
+
+      <div class="panel_block" *ngIf="isLoggedIn">
+        <div id="panel_make_friends" class="panel_button">
+          <span class="glyphicon glyphicon-cloud"></span>
+          <a (click)='makeFriends()'>Make friends</a>
+        </div>
+
+        <div id="panel_quit_friends" class="panel_button">
+          <span class="glyphicon glyphicon-plane"></span>
+          <a (click)='quitFriends()'>Quit friends</a>
+        </div>
+      </div>
+    </menu>
+
+    <div class="col-md-9 col-xs-8 router_outler_container">
+      <router-outlet></router-outlet>
+    </div>
+
+  </div>
+
+
+  <footer>
+    PeerTube, CopyLeft 2015-2016
+  </footer>
+</div>
diff --git a/client/app/app.component.scss b/client/app/app.component.scss
new file mode 100644 (file)
index 0000000..e02c2d5
--- /dev/null
@@ -0,0 +1,32 @@
+header div {
+  line-height: 25px;
+  margin-bottom: 30px;
+}
+
+menu {
+  min-height: 600px;
+  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;
+  }
+}
+
+.panel_block:not(:last-child) {
+  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+}
diff --git a/client/app/app.component.ts b/client/app/app.component.ts
new file mode 100644 (file)
index 0000000..c94ff79
--- /dev/null
@@ -0,0 +1,109 @@
+import { Component } from '@angular/core';
+import { HTTP_PROVIDERS } from '@angular/http';
+import { RouteConfig, Router, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from '@angular/router-deprecated';
+
+import { FriendService } from './friends/index';
+import { Search, SearchComponent } from './shared/index';
+import {
+  UserLoginComponent,
+  AuthService,
+  AuthStatus
+} from './users/index';
+import {
+  VideoAddComponent,
+  VideoListComponent,
+  VideoWatchComponent,
+  VideoService
+} from './videos/index';
+
+@RouteConfig([
+  {
+    path: '/users/login',
+    name: 'UserLogin',
+    component: UserLoginComponent
+  },
+  {
+    path: '/videos/list',
+    name: 'VideosList',
+    component: VideoListComponent,
+    useAsDefault: true
+  },
+  {
+    path: '/videos/watch/:id',
+    name: 'VideosWatch',
+    component: VideoWatchComponent
+  },
+  {
+    path: '/videos/add',
+    name: 'VideosAdd',
+    component: VideoAddComponent
+  }
+])
+
+@Component({
+    selector: 'my-app',
+    templateUrl: 'client/app/app.component.html',
+    styleUrls: [ 'client/app/app.component.css' ],
+    directives: [ ROUTER_DIRECTIVES, SearchComponent ],
+    providers: [ ROUTER_PROVIDERS, HTTP_PROVIDERS, VideoService, FriendService, AuthService ]
+})
+
+export class AppComponent {
+  isLoggedIn: boolean;
+  search_field: string = name;
+  choices = [ ];
+
+  constructor(private _friendService: FriendService,
+              private _authService: AuthService,
+              private _router: Router
+
+  ) {
+    this.isLoggedIn = this._authService.isLoggedIn();
+
+    this._authService.loginChanged$.subscribe(
+      status => {
+        if (status === AuthStatus.LoggedIn) {
+          this.isLoggedIn = true;
+        }
+      }
+    );
+  }
+
+  onSearch(search: Search) {
+    if (search.value !== '') {
+      const params = {
+        search: search.value,
+        field: search.field
+      };
+      this._router.navigate(['VideosList', params]);
+    } else {
+      this._router.navigate(['VideosList']);
+    }
+  }
+
+  logout() {
+    // this._authService.logout();
+  }
+
+  makeFriends() {
+    this._friendService.makeFriends().subscribe(
+      status => {
+        if (status === 409) {
+          alert('Already made friends!');
+        } else {
+          alert('Made friends!');
+        }
+      },
+      error => alert(error)
+    );
+  }
+
+  quitFriends() {
+    this._friendService.quitFriends().subscribe(
+      status => {
+          alert('Quit friends!');
+      },
+      error => alert(error)
+    );
+  }
+}
diff --git a/client/app/friends/friend.service.ts b/client/app/friends/friend.service.ts
new file mode 100644 (file)
index 0000000..d143ec4
--- /dev/null
@@ -0,0 +1,27 @@
+import { Injectable } from '@angular/core';
+import { Http, Response } from '@angular/http';
+import { Observable } from 'rxjs/Rx';
+
+@Injectable()
+export class FriendService {
+  private _baseFriendsUrl = '/api/v1/pods/';
+
+  constructor (private http: Http) {}
+
+  makeFriends() {
+    return this.http.get(this._baseFriendsUrl + 'makefriends')
+                    .map(res => <number> res.status)
+                    .catch(this.handleError);
+  }
+
+  quitFriends() {
+    return this.http.get(this._baseFriendsUrl + 'quitfriends')
+                    .map(res => <number> res.status)
+                    .catch(this.handleError);
+  }
+
+  private handleError (error: Response) {
+    console.error(error);
+    return Observable.throw(error.json().error || 'Server error');
+  }
+}
diff --git a/client/app/friends/index.ts b/client/app/friends/index.ts
new file mode 100644 (file)
index 0000000..0adc256
--- /dev/null
@@ -0,0 +1 @@
+export * from './friend.service';
diff --git a/client/app/shared/index.ts b/client/app/shared/index.ts
new file mode 100644 (file)
index 0000000..a49a4f1
--- /dev/null
@@ -0,0 +1,3 @@
+export * from './search-field.type';
+export * from './search.component';
+export * from './search.model';
diff --git a/client/app/shared/search-field.type.ts b/client/app/shared/search-field.type.ts
new file mode 100644 (file)
index 0000000..8462362
--- /dev/null
@@ -0,0 +1 @@
+export type SearchField = "name" | "author" | "podUrl" | "magnetUri";
diff --git a/client/app/shared/search.component.html b/client/app/shared/search.component.html
new file mode 100644 (file)
index 0000000..fb13ac7
--- /dev/null
@@ -0,0 +1,17 @@
+<div class="input-group">
+  <div class="input-group-btn" dropdown>
+    <button id="simple-btn-keyboard-nav" type="button" class="btn btn-default" dropdownToggle>
+      {{ getStringChoice(searchCriterias.field) }} <span class="caret"></span>
+    </button>
+    <ul class="dropdown-menu" role="menu" aria-labelledby="simple-btn-keyboard-nav">
+      <li *ngFor="let choice of choiceKeys" class="dropdown-item">
+        <a class="dropdown-item" href="#" (click)="choose($event, choice)">{{ getStringChoice(choice) }}</a>
+      </li>
+    </ul>
+  </div>
+
+  <input
+    type="text" id="search-video" name="search-video" class="form-control" placeholder="Search a video..." class="form-control"
+    [(ngModel)]="searchCriterias.value" (keyup.enter)="doSearch()"
+  >
+</div>
diff --git a/client/app/shared/search.component.ts b/client/app/shared/search.component.ts
new file mode 100644 (file)
index 0000000..519810f
--- /dev/null
@@ -0,0 +1,47 @@
+import { Component, EventEmitter, Output } from '@angular/core';
+
+import { DROPDOWN_DIRECTIVES} from  'ng2-bootstrap/components/dropdown';
+
+import { Search } from './search.model';
+import { SearchField } from './search-field.type';
+
+@Component({
+    selector: 'my-search',
+    templateUrl: 'client/app/shared/search.component.html',
+    directives: [ DROPDOWN_DIRECTIVES ]
+})
+
+export class SearchComponent {
+  @Output() search: EventEmitter<Search> = new EventEmitter<Search>();
+
+  searchCriterias: Search = {
+    field: 'name',
+    value: ''
+  };
+  fieldChoices = {
+    name: 'Name',
+    author: 'Author',
+    podUrl: 'Pod Url',
+    magnetUri: 'Magnet Uri'
+  };
+
+  get choiceKeys() {
+    return Object.keys(this.fieldChoices);
+  }
+
+  getStringChoice(choiceKey: SearchField): string {
+    return this.fieldChoices[choiceKey];
+  }
+
+  choose($event:MouseEvent, choice: SearchField) {
+    $event.preventDefault();
+    $event.stopPropagation();
+
+    this.searchCriterias.field = choice;
+  }
+
+  doSearch(): void {
+    this.search.emit(this.searchCriterias);
+  }
+
+}
diff --git a/client/app/shared/search.model.ts b/client/app/shared/search.model.ts
new file mode 100644 (file)
index 0000000..932a656
--- /dev/null
@@ -0,0 +1,6 @@
+import { SearchField } from './search-field.type';
+
+export interface Search {
+  field: SearchField;
+  value: string;
+}
diff --git a/client/app/users/index.ts b/client/app/users/index.ts
new file mode 100644 (file)
index 0000000..4f08b8b
--- /dev/null
@@ -0,0 +1,2 @@
+export * from './login/index';
+export * from './shared/index';
diff --git a/client/app/users/login/index.ts b/client/app/users/login/index.ts
new file mode 100644 (file)
index 0000000..69c1644
--- /dev/null
@@ -0,0 +1 @@
+export * from './login.component';
diff --git a/client/app/users/login/login.component.html b/client/app/users/login/login.component.html
new file mode 100644 (file)
index 0000000..9406945
--- /dev/null
@@ -0,0 +1,14 @@
+<h3>Login</h3>
+<form role="form" (submit)="login(username.value, password.value)">
+  <div class="form-group">
+    <label for="username">Username</label>
+    <input type="text" #username class="form-control" id="username" placeholder="Username">
+  </div>
+
+  <div class="form-group">
+    <label for="password">Password</label>
+    <input type="password" #password class="form-control" id="password" placeholder="Password">
+  </div>
+
+  <input type="submit" value="Login" class="btn btn-default">
+</form>
diff --git a/client/app/users/login/login.component.scss b/client/app/users/login/login.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/client/app/users/login/login.component.ts b/client/app/users/login/login.component.ts
new file mode 100644 (file)
index 0000000..33590ad
--- /dev/null
@@ -0,0 +1,34 @@
+import { Component } from '@angular/core';
+import { Router } from '@angular/router-deprecated';
+
+import { AuthService, AuthStatus, User } from '../shared/index';
+
+@Component({
+  selector: 'my-user-login',
+  styleUrls: [ 'client/app/users/login/login.component.css' ],
+  templateUrl: 'client/app/users/login/login.component.html'
+})
+
+export class UserLoginComponent {
+  constructor(private _authService: AuthService, private _router: Router) {}
+
+  login(username: string, password: string) {
+    this._authService.login(username, password).subscribe(
+      result => {
+        const user = new User(username, result);
+        user.save();
+
+        this._authService.setStatus(AuthStatus.LoggedIn);
+
+        this._router.navigate(['VideosList']);
+      },
+      error => {
+        if (error.error === 'invalid_grant') {
+          alert('Credentials are invalid.');
+        } else {
+          alert(`${error.error}: ${error.error_description}`);
+        }
+      }
+    );
+  }
+}
diff --git a/client/app/users/shared/auth-status.model.ts b/client/app/users/shared/auth-status.model.ts
new file mode 100644 (file)
index 0000000..f646bd4
--- /dev/null
@@ -0,0 +1,4 @@
+export enum AuthStatus {
+  LoggedIn,
+  LoggedOut
+}
diff --git a/client/app/users/shared/auth.service.ts b/client/app/users/shared/auth.service.ts
new file mode 100644 (file)
index 0000000..1cb042d
--- /dev/null
@@ -0,0 +1,107 @@
+import { Injectable } from '@angular/core';
+import { Headers, Http, RequestOptions, Response, URLSearchParams } from '@angular/http';
+import { Observable, Subject } from 'rxjs/Rx';
+
+import { AuthStatus } from './auth-status.model';
+import { User } from './user.model';
+
+@Injectable()
+export class AuthService {
+  loginChanged$;
+
+  private _loginChanged;
+  private _baseLoginUrl = '/api/v1/users/token';
+  private _baseClientUrl = '/api/v1/users/client';
+  private _clientId = '';
+  private _clientSecret = '';
+
+  constructor (private http: Http) {
+    this._loginChanged = new Subject<AuthStatus>();
+    this.loginChanged$ = this._loginChanged.asObservable();
+
+    // Fetch the client_id/client_secret
+    // FIXME: save in local storage?
+    this.http.get(this._baseClientUrl)
+      .map(res => res.json())
+      .catch(this.handleError)
+      .subscribe(
+        result => {
+          this._clientId = result.client_id;
+          this._clientSecret = result.client_secret;
+          console.log('Client credentials loaded.');
+        },
+        error => {
+          alert(error);
+        }
+      );
+  }
+
+  login(username: string, password: string) {
+    let body = new URLSearchParams();
+    body.set('client_id', this._clientId);
+    body.set('client_secret', this._clientSecret);
+    body.set('response_type', 'code');
+    body.set('grant_type', 'password');
+    body.set('scope', 'upload');
+    body.set('username', username);
+    body.set('password', password);
+
+    let headers = new Headers();
+    headers.append('Content-Type', 'application/x-www-form-urlencoded');
+
+    let options = {
+      headers: headers
+    };
+
+    return this.http.post(this._baseLoginUrl, body.toString(), options)
+                    .map(res => res.json())
+                    .catch(this.handleError);
+  }
+
+  logout() {
+    // TODO make HTTP request
+  }
+
+  getRequestHeader(): Headers {
+    return new Headers({ 'Authorization': `${this.getTokenType()} ${this.getToken()}` });
+  }
+
+  getAuthRequestOptions(): RequestOptions {
+    return new RequestOptions({ headers: this.getRequestHeader() });
+  }
+
+  getToken(): string {
+    return localStorage.getItem('access_token');
+  }
+
+  getTokenType(): string {
+    return localStorage.getItem('token_type');
+  }
+
+  getUser(): User {
+    if (this.isLoggedIn() === false) {
+      return null;
+    }
+
+    const user = User.load();
+
+    return user;
+  }
+
+  isLoggedIn(): boolean {
+    if (this.getToken()) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  setStatus(status: AuthStatus) {
+    this._loginChanged.next(status);
+  }
+
+  private handleError (error: Response) {
+    console.error(error);
+    return Observable.throw(error.json() || { error: 'Server error' });
+  }
+}
diff --git a/client/app/users/shared/index.ts b/client/app/users/shared/index.ts
new file mode 100644 (file)
index 0000000..c6816b3
--- /dev/null
@@ -0,0 +1,4 @@
+export * from './auth-status.model';
+export * from './auth.service';
+export * from './token.model';
+export * from './user.model';
diff --git a/client/app/users/shared/token.model.ts b/client/app/users/shared/token.model.ts
new file mode 100644 (file)
index 0000000..b7872e7
--- /dev/null
@@ -0,0 +1,31 @@
+export class Token {
+  access_token: string;
+  refresh_token: string;
+  token_type: string;
+
+  static load(): Token {
+    return new Token({
+      access_token: localStorage.getItem('access_token'),
+      refresh_token: localStorage.getItem('refresh_token'),
+      token_type: localStorage.getItem('token_type')
+    });
+  }
+
+  constructor (hash?: any) {
+    if (hash) {
+      this.access_token = hash.access_token;
+      this.refresh_token = hash.refresh_token;
+      if (hash.token_type === 'bearer') {
+        this.token_type = 'Bearer';
+      } else {
+        this.token_type = hash.token_type;
+      }
+    }
+  }
+
+  save():void {
+    localStorage.setItem('access_token', this.access_token);
+    localStorage.setItem('refresh_token', this.refresh_token);
+    localStorage.setItem('token_type', this.token_type);
+  }
+}
diff --git a/client/app/users/shared/user.model.ts b/client/app/users/shared/user.model.ts
new file mode 100644 (file)
index 0000000..73fd4dd
--- /dev/null
@@ -0,0 +1,20 @@
+import { Token } from './token.model';
+
+export class User {
+  username: string;
+  token: Token;
+
+  static load(): User {
+    return new User(localStorage.getItem('username'), Token.load());
+  }
+
+  constructor (username: string, hash_token: any) {
+    this.username = username;
+    this.token = new Token(hash_token);
+  }
+
+  save(): void {
+    localStorage.setItem('username', this.username);
+    this.token.save();
+  }
+}
diff --git a/client/app/videos/index.ts b/client/app/videos/index.ts
new file mode 100644 (file)
index 0000000..1c80ac5
--- /dev/null
@@ -0,0 +1,4 @@
+export * from './shared/index';
+export * from './video-add/index';
+export * from './video-list/index';
+export * from './video-watch/index';
diff --git a/client/app/videos/shared/index.ts b/client/app/videos/shared/index.ts
new file mode 100644 (file)
index 0000000..c535c46
--- /dev/null
@@ -0,0 +1,5 @@
+export * from './loader/index';
+export * from './pagination.model';
+export * from './sort-field.type';
+export * from './video.model';
+export * from './video.service';
diff --git a/client/app/videos/shared/loader/index.ts b/client/app/videos/shared/loader/index.ts
new file mode 100644 (file)
index 0000000..ab22584
--- /dev/null
@@ -0,0 +1 @@
+export * from './loader.component';
diff --git a/client/app/videos/shared/loader/loader.component.html b/client/app/videos/shared/loader/loader.component.html
new file mode 100644 (file)
index 0000000..d02296a
--- /dev/null
@@ -0,0 +1,3 @@
+<div id="video-loading" class="col-md-12 text-center" *ngIf="loading">
+  <div class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></div>
+</div>
diff --git a/client/app/videos/shared/loader/loader.component.scss b/client/app/videos/shared/loader/loader.component.scss
new file mode 100644 (file)
index 0000000..4541958
--- /dev/null
@@ -0,0 +1,26 @@
+div {
+  margin-top: 150px;
+}
+
+// Thanks https://gist.github.com/alexandrevicenzi/680147013e902a4eaa5d
+.glyphicon-refresh-animate {
+    -animation: spin .7s infinite linear;
+    -ms-animation: spin .7s infinite linear;
+    -webkit-animation: spinw .7s infinite linear;
+    -moz-animation: spinm .7s infinite linear;
+}
+
+@keyframes spin {
+    from { transform: scale(1) rotate(0deg);}
+    to { transform: scale(1) rotate(360deg);}
+}
+
+@-webkit-keyframes spinw {
+    from { -webkit-transform: rotate(0deg);}
+    to { -webkit-transform: rotate(360deg);}
+}
+
+@-moz-keyframes spinm {
+    from { -moz-transform: rotate(0deg);}
+    to { -moz-transform: rotate(360deg);}
+}
diff --git a/client/app/videos/shared/loader/loader.component.ts b/client/app/videos/shared/loader/loader.component.ts
new file mode 100644 (file)
index 0000000..666d43b
--- /dev/null
@@ -0,0 +1,11 @@
+import { Component, Input } from '@angular/core';
+
+@Component({
+  selector: 'my-loader',
+  styleUrls: [ 'client/app/videos/shared/loader/loader.component.css' ],
+  templateUrl: 'client/app/videos/shared/loader/loader.component.html'
+})
+
+export class LoaderComponent {
+  @Input() loading: boolean;
+}
diff --git a/client/app/videos/shared/pagination.model.ts b/client/app/videos/shared/pagination.model.ts
new file mode 100644 (file)
index 0000000..06f7a78
--- /dev/null
@@ -0,0 +1,5 @@
+export interface Pagination {
+  currentPage: number;
+  itemsPerPage: number;
+  total: number;
+}
diff --git a/client/app/videos/shared/sort-field.type.ts b/client/app/videos/shared/sort-field.type.ts
new file mode 100644 (file)
index 0000000..6e8cc79
--- /dev/null
@@ -0,0 +1,3 @@
+export type SortField = "name" | "-name"
+                      | "duration" | "-duration"
+                      | "createdDate" | "-createdDate";
diff --git a/client/app/videos/shared/video.model.ts b/client/app/videos/shared/video.model.ts
new file mode 100644 (file)
index 0000000..eec537c
--- /dev/null
@@ -0,0 +1,63 @@
+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;
+
+  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;
+  }
+
+  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;
+  }
+}
diff --git a/client/app/videos/shared/video.service.ts b/client/app/videos/shared/video.service.ts
new file mode 100644 (file)
index 0000000..78789c3
--- /dev/null
@@ -0,0 +1,79 @@
+import { Injectable } from '@angular/core';
+import { Http, Response, URLSearchParams } from '@angular/http';
+import { Observable } from 'rxjs/Rx';
+
+import { Pagination } from './pagination.model';
+import { Search } from '../../shared/index';
+import { SortField } from './sort-field.type';
+import { AuthService } from '../../users/index';
+import { Video } from './video.model';
+
+@Injectable()
+export class VideoService {
+  private _baseVideoUrl = '/api/v1/videos/';
+
+  constructor (private http: Http, private _authService: AuthService) {}
+
+  getVideos(pagination: Pagination, sort: SortField) {
+    const params = this.createPaginationParams(pagination);
+
+    if (sort) params.set('sort', sort);
+
+    return this.http.get(this._baseVideoUrl, { search: params })
+                    .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: Search, pagination: Pagination, sort: SortField) {
+    const params = this.createPaginationParams(pagination);
+
+    if (search.field) params.set('field', search.field);
+    if (sort) params.set('sort', sort);
+
+    return this.http.get(this._baseVideoUrl + 'search/' + encodeURIComponent(search.value), { search: params })
+                    .map(res => res.json())
+                    .map(this.extractVideos)
+                    .catch(this.handleError);
+  }
+
+  private extractVideos (body: any) {
+    const videos_json = body.data;
+    const totalVideos = body.total;
+    const videos = [];
+    for (const video_json of videos_json) {
+      videos.push(new Video(video_json));
+    }
+
+    return { videos, totalVideos };
+  }
+
+  private handleError (error: Response) {
+    console.error(error);
+    return Observable.throw(error.json().error || 'Server error');
+  }
+
+  private createPaginationParams(pagination: Pagination) {
+    const params = new URLSearchParams();
+    const start: number = (pagination.currentPage - 1) * pagination.itemsPerPage;
+    const count: number = pagination.itemsPerPage;
+
+    params.set('start', start.toString());
+    params.set('count', count.toString());
+
+    return params;
+  }
+}
diff --git a/client/app/videos/video-add/index.ts b/client/app/videos/video-add/index.ts
new file mode 100644 (file)
index 0000000..79488e8
--- /dev/null
@@ -0,0 +1 @@
+export * from './video-add.component';
diff --git a/client/app/videos/video-add/video-add.component.html b/client/app/videos/video-add/video-add.component.html
new file mode 100644 (file)
index 0000000..80d229c
--- /dev/null
@@ -0,0 +1,41 @@
+<h3>Upload a video</h3>
+
+<form (ngSubmit)="uploadFile()" #videoForm="ngForm">
+  <div class="form-group">
+    <label for="name">Video name</label>
+    <input
+      type="text" class="form-control" name="name" id="name" required
+      ngControl="name"  #name="ngForm"
+    >
+    <div [hidden]="name.valid || name.pristine" class="alert alert-danger">
+      Name is required
+    </div>
+  </div>
+
+  <div class="form-group">
+    <div class="btn btn-default btn-file">
+      <span>Select the video...</span>
+      <input type="file" name="videofile" id="videofile">
+    </div>
+
+    <span *ngIf="fileToUpload">{{ fileToUpload.name }}</span>
+  </div>
+
+  <div class="form-group">
+    <label for="description">Description</label>
+    <textarea
+      name="description" id="description" class="form-control" placeholder="Description..." required
+      ngControl="description"  #description="ngForm"
+    >
+    </textarea>
+    <div [hidden]="description.valid || description.pristine" class="alert alert-danger">
+        A description is required
+    </div>
+  </div>
+
+  <div id="progress" *ngIf="progressBar.max !== 0">
+    <progressbar [value]="progressBar.value" [max]="progressBar.max">{{ progressBar.value | bytes }} / {{ progressBar.max | bytes }}</progressbar>
+  </div>
+
+  <input type="submit" value="Upload" class="btn btn-default" [disabled]="!videoForm.form.valid || !fileToUpload">
+</form>
diff --git a/client/app/videos/video-add/video-add.component.scss b/client/app/videos/video-add/video-add.component.scss
new file mode 100644 (file)
index 0000000..01195f0
--- /dev/null
@@ -0,0 +1,33 @@
+.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;
+}
+
+.form-group {
+  margin-bottom: 10px;
+}
+
+#progress {
+  margin-bottom: 10px;
+}
diff --git a/client/app/videos/video-add/video-add.component.ts b/client/app/videos/video-add/video-add.component.ts
new file mode 100644 (file)
index 0000000..ca583a1
--- /dev/null
@@ -0,0 +1,68 @@
+import { Component, ElementRef, OnInit } from '@angular/core';
+import { Router } from '@angular/router-deprecated';
+
+import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe';
+import { PROGRESSBAR_DIRECTIVES } from 'ng2-bootstrap/components/progressbar';
+
+import { AuthService, User } from '../../users/index';
+
+// TODO: import it with systemjs
+declare var jQuery:any;
+
+@Component({
+  selector: 'my-videos-add',
+  styleUrls: [ 'client/app/videos/video-add/video-add.component.css' ],
+  templateUrl: 'client/app/videos/video-add/video-add.component.html',
+  directives: [ PROGRESSBAR_DIRECTIVES ],
+  pipes: [ BytesPipe ]
+})
+
+export class VideoAddComponent implements OnInit {
+  user: User;
+  fileToUpload: any;
+  progressBar: { value: number; max: number; } = { value: 0, max: 0 };
+
+  private _form: any;
+
+  constructor(
+    private _router: Router, private _elementRef: ElementRef,
+    private _authService: AuthService
+  ) {}
+
+  ngOnInit() {
+    this.user = User.load();
+    jQuery(this._elementRef.nativeElement).find('#videofile').fileupload({
+      url: '/api/v1/videos',
+      dataType: 'json',
+      singleFileUploads: true,
+      multipart: true,
+      autoupload: false,
+
+      add: (e, data) => {
+        this._form = data;
+        this.fileToUpload = data['files'][0];
+      },
+
+      progressall: (e, data) => {
+        this.progressBar.value = data.loaded;
+        // The server is a little bit slow to answer (has to seed the video)
+        // So we add more time to the progress bar (+10%)
+        this.progressBar.max = data.total + (0.1 * data.total);
+      },
+
+      done: (e, data) => {
+        this.progressBar.value = this.progressBar.max;
+        console.log('Video uploaded.');
+
+        // Print all the videos once it's finished
+        this._router.navigate(['VideosList']);
+      }
+    });
+  }
+
+  uploadFile() {
+    this._form.headers = this._authService.getRequestHeader().toJSON();
+    this._form.formData = jQuery(this._elementRef.nativeElement).find('form').serializeArray();
+    this._form.submit();
+  }
+}
diff --git a/client/app/videos/video-list/index.ts b/client/app/videos/video-list/index.ts
new file mode 100644 (file)
index 0000000..1f6d6a4
--- /dev/null
@@ -0,0 +1,3 @@
+export * from './video-list.component';
+export * from './video-miniature.component';
+export * from './video-sort.component';
diff --git a/client/app/videos/video-list/video-list.component.html b/client/app/videos/video-list/video-list.component.html
new file mode 100644 (file)
index 0000000..edbbaf3
--- /dev/null
@@ -0,0 +1,18 @@
+<div class="row videos-info">
+  <div class="col-md-9 videos-total-results"> {{ pagination.total }} videos</div>
+  <my-video-sort class="col-md-3" [currentSort]="sort" (sort)="onSort($event)"></my-video-sort>
+</div>
+
+<div class="videos-miniatures">
+  <my-loader [loading]="loading"></my-loader>
+
+  <div class="col-md-12 no-video" *ngIf="!loading && videos.length === 0">There is no video.</div>
+
+  <my-video-miniature *ngFor="let video of videos" [video]="video" [user]="user" (removed)="onRemoved(video)">
+  </my-video-miniature>
+</div>
+
+<pagination
+  [totalItems]="pagination.total" [itemsPerPage]="pagination.itemsPerPage" [(ngModel)]="pagination.currentPage"
+  (ngModelChange)="getVideos()"
+></pagination>
diff --git a/client/app/videos/video-list/video-list.component.scss b/client/app/videos/video-list/video-list.component.scss
new file mode 100644 (file)
index 0000000..9441d80
--- /dev/null
@@ -0,0 +1,37 @@
+.videos-info {
+
+  padding-bottom: 20px;
+  margin-bottom: 20px;
+  border-bottom: 1px solid #f1f1f1;
+  height: 40px;
+  line-height: 40px;
+  width: 765px;
+  margin-left: 15px;
+
+  my-video-sort {
+    padding-right: 0;
+  }
+
+  .videos-total-results {
+    font-size: 13px;
+    padding-left: 0;
+  }
+}
+
+.videos-miniatures {
+  min-height: 600px;
+
+  my-videos-miniature {
+    display: inline-block;
+  }
+
+  .no-video {
+    margin-top: 50px;
+    text-align: center;
+  }
+}
+
+pagination {
+  display: block;
+  text-align: center;
+}
diff --git a/client/app/videos/video-list/video-list.component.ts b/client/app/videos/video-list/video-list.component.ts
new file mode 100644 (file)
index 0000000..a88fb37
--- /dev/null
@@ -0,0 +1,101 @@
+import { Component, OnInit } from '@angular/core';
+import { Router, ROUTER_DIRECTIVES, RouteParams } from '@angular/router-deprecated';
+
+import { PAGINATION_DIRECTIVES } from 'ng2-bootstrap/components/pagination';
+
+import {
+  LoaderComponent,
+  Pagination,
+  SortField,
+  Video,
+  VideoService
+} from '../shared/index';
+import { Search, SearchField } from '../../shared/index';
+import { AuthService, User } from '../../users/index';
+import { VideoMiniatureComponent } from './video-miniature.component';
+import { VideoSortComponent } from './video-sort.component';
+
+@Component({
+  selector: 'my-videos-list',
+  styleUrls: [ 'client/app/videos/video-list/video-list.component.css' ],
+  templateUrl: 'client/app/videos/video-list/video-list.component.html',
+  directives: [ ROUTER_DIRECTIVES, PAGINATION_DIRECTIVES, VideoMiniatureComponent, VideoSortComponent, LoaderComponent ]
+})
+
+export class VideoListComponent implements OnInit {
+  user: User = null;
+  videos: Video[] = [];
+  pagination: Pagination = {
+    currentPage: 1,
+    itemsPerPage: 9,
+    total: 0
+  };
+  sort: SortField;
+  loading: boolean = false;
+
+  private search: Search;
+
+  constructor(
+    private _authService: AuthService,
+    private _videoService: VideoService,
+    private _routeParams: RouteParams,
+    private _router: Router
+  ) {
+    this.search = {
+      value: this._routeParams.get('search'),
+      field: <SearchField>this._routeParams.get('field')
+    };
+
+    this.sort = <SortField>this._routeParams.get('sort') || '-createdDate';
+  }
+
+  ngOnInit() {
+    if (this._authService.isLoggedIn()) {
+      this.user = User.load();
+    }
+
+    this.getVideos();
+  }
+
+  getVideos() {
+    this.loading = true;
+    this.videos = [];
+
+    let observable = null;
+
+    if (this.search.value !== null) {
+      observable = this._videoService.searchVideos(this.search, this.pagination, this.sort);
+    } else {
+      observable = this._videoService.getVideos(this.pagination, this.sort);
+    }
+
+    observable.subscribe(
+      ({ videos, totalVideos }) => {
+        this.videos = videos;
+        this.pagination.total = totalVideos;
+        this.loading = false;
+      },
+      error => alert(error)
+    );
+  }
+
+  onRemoved(video: Video): void {
+    this.videos.splice(this.videos.indexOf(video), 1);
+  }
+
+  onSort(sort: SortField) {
+    this.sort = sort;
+
+    const params: any = {
+      sort: this.sort
+    };
+
+    if (this.search.value) {
+      params.search = this.search.value;
+      params.field = this.search.field;
+    }
+
+    this._router.navigate(['VideosList', params]);
+    this.getVideos();
+  }
+}
diff --git a/client/app/videos/video-list/video-miniature.component.html b/client/app/videos/video-list/video-miniature.component.html
new file mode 100644 (file)
index 0000000..244254b
--- /dev/null
@@ -0,0 +1,22 @@
+<div class="video-miniature col-md-4" (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>
diff --git a/client/app/videos/video-list/video-miniature.component.scss b/client/app/videos/video-list/video-miniature.component.scss
new file mode 100644 (file)
index 0000000..4488abe
--- /dev/null
@@ -0,0 +1,55 @@
+.video-miniature {
+  height: 200px;
+  display: inline-block;
+  position: relative;
+
+  .video-miniature-thumbnail {
+    display: block;
+    position: relative;
+
+    .video-miniature-duration {
+      position: absolute;
+      right: 60px;
+      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: 16px;
+    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/app/videos/video-list/video-miniature.component.ts b/client/app/videos/video-list/video-miniature.component.ts
new file mode 100644 (file)
index 0000000..8176367
--- /dev/null
@@ -0,0 +1,46 @@
+import { DatePipe } from '@angular/common';
+import { Component, Input, Output, EventEmitter } from '@angular/core';
+import { ROUTER_DIRECTIVES } from '@angular/router-deprecated';
+
+import { Video, VideoService } from '../shared/index';
+import { User } from '../../users/index';
+
+@Component({
+  selector: 'my-video-miniature',
+  styleUrls: [ 'client/app/videos/video-list/video-miniature.component.css' ],
+  templateUrl: 'client/app/videos/video-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 _videoService: VideoService) {}
+
+  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._videoService.removeVideo(id).subscribe(
+        status => this.removed.emit(true),
+        error => alert(error)
+      );
+    }
+  }
+}
diff --git a/client/app/videos/video-list/video-sort.component.html b/client/app/videos/video-list/video-sort.component.html
new file mode 100644 (file)
index 0000000..3bece0b
--- /dev/null
@@ -0,0 +1,5 @@
+<select class="form-control input-sm" [(ngModel)]="currentSort" (ngModelChange)="onSortChange()">
+  <option *ngFor="let choice of choiceKeys" [value]="choice">
+    {{ getStringChoice(choice) }}
+  </option>
+</select>
diff --git a/client/app/videos/video-list/video-sort.component.ts b/client/app/videos/video-list/video-sort.component.ts
new file mode 100644 (file)
index 0000000..d00d7ed
--- /dev/null
@@ -0,0 +1,36 @@
+import { Component, EventEmitter, Input, Output } from '@angular/core';
+
+import { SortField } from '../shared/index';
+
+@Component({
+  selector: 'my-video-sort',
+  // styleUrls: [ 'app/angular/videos/components/list/video-sort.component.css' ],
+  templateUrl: 'client/app/videos/video-list/video-sort.component.html'
+})
+
+export class VideoSortComponent {
+  @Output() sort = new EventEmitter<any>();
+
+  @Input() currentSort: SortField;
+
+  sortChoices = {
+    'name': 'Name - Asc',
+    '-name': 'Name - Desc',
+    'duration': 'Duration - Asc',
+    '-duration': 'Duration - Desc',
+    'createdDate': 'Created Date - Asc',
+    '-createdDate': 'Created Date - Desc'
+  };
+
+  get choiceKeys() {
+    return Object.keys(this.sortChoices);
+  }
+
+  getStringChoice(choiceKey: SortField): string {
+    return this.sortChoices[choiceKey];
+  }
+
+  onSortChange() {
+    this.sort.emit(this.currentSort);
+  }
+}
diff --git a/client/app/videos/video-watch/index.ts b/client/app/videos/video-watch/index.ts
new file mode 100644 (file)
index 0000000..2228b6e
--- /dev/null
@@ -0,0 +1 @@
+export * from './video-watch.component';
diff --git a/client/app/videos/video-watch/video-watch.component.html b/client/app/videos/video-watch/video-watch.component.html
new file mode 100644 (file)
index 0000000..6c36b27
--- /dev/null
@@ -0,0 +1,10 @@
+<my-loader [loading]="loading"></my-loader>
+
+<div class="embed-responsive embed-responsive-19by9">
+</div>
+
+<div id="torrent-info">
+  <div id="torrent-info-download">Download: {{ downloadSpeed | bytes }}/s</div>
+  <div id="torrent-info-upload">Upload: {{ uploadSpeed | bytes }}/s</div>
+  <div id="torrent-info-peers">Number of peers: {{ numPeers }}</div>
+<div>
diff --git a/client/app/videos/video-watch/video-watch.component.scss b/client/app/videos/video-watch/video-watch.component.scss
new file mode 100644 (file)
index 0000000..1228d42
--- /dev/null
@@ -0,0 +1,13 @@
+.embed-responsive {
+  height: 500px;
+}
+
+#torrent-info {
+  font-size: 10px;
+
+  div {
+    display: inline-block;
+    width: 33%;
+    text-align: center;
+  }
+}
diff --git a/client/app/videos/video-watch/video-watch.component.ts b/client/app/videos/video-watch/video-watch.component.ts
new file mode 100644 (file)
index 0000000..891e656
--- /dev/null
@@ -0,0 +1,75 @@
+import { Component, ElementRef, OnInit } from '@angular/core';
+import { CanDeactivate, ComponentInstruction, RouteParams } from '@angular/router-deprecated';
+
+import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe';
+
+import { LoaderComponent, Video, VideoService } from '../shared/index';
+
+// TODO import it with systemjs
+declare var WebTorrent: any;
+
+@Component({
+  selector: 'my-video-watch',
+  templateUrl: 'client/app/videos/video-watch/video-watch.component.html',
+  styleUrls: [ 'client/app/videos/video-watch/video-watch.component.css' ],
+  directives: [ LoaderComponent ],
+  pipes: [ BytesPipe ]
+})
+
+export class VideoWatchComponent implements OnInit, CanDeactivate {
+  video: Video;
+  downloadSpeed: number;
+  uploadSpeed: number;
+  numPeers: number;
+  loading: boolean = false;
+
+  private _interval: NodeJS.Timer;
+  private client: any;
+
+  constructor(
+    private _videoService: VideoService,
+    private _routeParams: RouteParams,
+    private _elementRef: ElementRef
+  ) {
+    // TODO: use a service
+    this.client = new WebTorrent({ dht: false });
+  }
+
+  ngOnInit() {
+    let id = this._routeParams.get('id');
+    this._videoService.getVideo(id).subscribe(
+      video => this.loadVideo(video),
+      error => alert(error)
+    );
+  }
+
+  loadVideo(video: Video) {
+    this.loading = true;
+    this.video = video;
+    console.log('Adding ' + this.video.magnetUri + '.');
+    this.client.add(this.video.magnetUri, (torrent) => {
+      this.loading = false;
+      console.log('Added ' + this.video.magnetUri + '.');
+      torrent.files[0].appendTo(this._elementRef.nativeElement.querySelector('.embed-responsive'), (err) => {
+        if (err) {
+          alert('Cannot append the file.');
+          console.error(err);
+        }
+      });
+
+      // Refresh each second
+      this._interval = setInterval(() => {
+        this.downloadSpeed = torrent.downloadSpeed;
+        this.uploadSpeed = torrent.uploadSpeed;
+        this.numPeers = torrent.numPeers;
+      }, 1000);
+    });
+  }
+
+  routerCanDeactivate(next: ComponentInstruction, prev: ComponentInstruction) : any {
+    console.log('Removing video from webtorrent.');
+    clearInterval(this._interval);
+    this.client.remove(this.video.magnetUri);
+    return true;
+  }
+}
index db4b76613c5a0707b4f6ab5fb9dd44acbab7d000..bc750dde7a2a98769940608116f7d6ab8b65b3c7 100644 (file)
@@ -7,27 +7,27 @@
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1">
 
-    <link rel="stylesheet" href="/app/stylesheets/index.css">
+    <link rel="stylesheet" href="client/stylesheets/index.css">
 
     <!-- 1. Load libraries -->
     <!-- IE required polyfills, in this exact order -->
-    <script src="/app/node_modules/es6-shim/es6-shim.min.js"></script>
-    <script src="/app/node_modules/zone.js/dist/zone.js"></script>
-    <script src="/app/node_modules/reflect-metadata/Reflect.js"></script>
-    <script src="/app/node_modules/systemjs/dist/system.src.js"></script>
+    <script src="client/node_modules/es6-shim/es6-shim.min.js"></script>
+    <script src="client/node_modules/zone.js/dist/zone.js"></script>
+    <script src="client/node_modules/reflect-metadata/Reflect.js"></script>
+    <script src="client/node_modules/systemjs/dist/system.src.js"></script>
 
-    <script src="/app/node_modules/jquery/dist/jquery.js"></script>
-    <script src="/app/node_modules/jquery.ui.widget/jquery.ui.widget.js"></script>
-    <script src="/app/node_modules/blueimp-file-upload/js/jquery.fileupload.js"></script>
+    <script src="client/node_modules/jquery/dist/jquery.js"></script>
+    <script src="client/node_modules/jquery.ui.widget/jquery.ui.widget.js"></script>
+    <script src="client/node_modules/blueimp-file-upload/js/jquery.fileupload.js"></script>
 
-    <script src="/app/node_modules/webtorrent/webtorrent.min.js"></script>
+    <script src="client/node_modules/webtorrent/webtorrent.min.js"></script>
 
-    <script src="/app/node_modules/ng2-bootstrap/bundles/ng2-bootstrap.min.js"></script>
+    <script src="client/node_modules/ng2-bootstrap/bundles/ng2-bootstrap.min.js"></script>
 
     <!-- 2. Configure SystemJS -->
-    <script src="/app/systemjs.config.js"></script>
+    <script src="client/systemjs.config.js"></script>
     <script>
-      System.import('app').catch(function(err){ console.error(err); });
+      System.import('client').catch(function(err){ console.error(err); });
     </script>
   </head>
 
diff --git a/client/main.ts b/client/main.ts
new file mode 100644 (file)
index 0000000..5e2ea0d
--- /dev/null
@@ -0,0 +1,5 @@
+import { bootstrap }    from '@angular/platform-browser-dynamic';
+
+import { AppComponent } from './app/app.component';
+
+bootstrap(AppComponent);
index b91698056e0100058680886f97af506b48d11fee..98c1e37ad746d5d080989631980dd467e95b2f0b 100644 (file)
@@ -1,4 +1,4 @@
-$icon-font-path: "/app/node_modules/bootstrap-sass/assets/fonts/bootstrap/";
+$icon-font-path: "/client/node_modules/bootstrap-sass/assets/fonts/bootstrap/";
 
 @import "bootstrap-variables";
 @import "_bootstrap";
index 82ca5bb704f8cf6e6fbb22860f9de41c7e3d2cd3..d04bc4107711445288f001a3b3334c401f04abb0 100644 (file)
@@ -1,13 +1,12 @@
 ;(function (global) {
   var map = {
-    'app': 'app/angular',
-    'angular-pipes': 'app/node_modules/angular-pipes',
-    'ng2-bootstrap': 'app/node_modules/ng2-bootstrap',
-    'angular-rxjs.bundle': 'app/bundles/angular-rxjs.bundle.js'
+    'angular-pipes': 'client/node_modules/angular-pipes',
+    'ng2-bootstrap': 'client/node_modules/ng2-bootstrap',
+    'angular-rxjs.bundle': 'client/bundles/angular-rxjs.bundle.js'
   }
 
   var packages = {
-    'app': { main: 'main.js', defaultExtension: 'js' },
+    'client': { main: 'main.js', defaultExtension: 'js' },
     'ng2-bootstrap': { defaultExtension: 'js' },
     'rxjs': { defaultExtension: 'js' }
   }
index 8e786ca28cc7ffa671fe820493cbf905016bb80a..d48bd392009b206313a4e51e2cd6b41a422cd91d 100644 (file)
   ],
   "compileOnSave": false,
   "files": [
-    "angular/app/app.component.ts",
-    "angular/app/search.component.ts",
-    "angular/app/search.ts",
-    "angular/friends/services/friends.service.ts",
-    "angular/main.ts",
-    "angular/users/components/login/login.component.ts",
-    "angular/users/models/authStatus.ts",
-    "angular/users/models/token.ts",
-    "angular/users/models/user.ts",
-    "angular/users/services/auth.service.ts",
-    "angular/videos/components/add/videos-add.component.ts",
-    "angular/videos/components/list/sort.ts",
-    "angular/videos/components/list/video-miniature.component.ts",
-    "angular/videos/components/list/video-sort.component.ts",
-    "angular/videos/components/list/videos-list.component.ts",
-    "angular/videos/components/watch/videos-watch.component.ts",
-    "angular/videos/loader.component.ts",
-    "angular/videos/pagination.ts",
-    "angular/videos/video.ts",
-    "angular/videos/videos.service.ts",
+    "app/app.component.ts",
+    "app/friends/friend.service.ts",
+    "app/friends/index.ts",
+    "app/shared/index.ts",
+    "app/shared/search-field.type.ts",
+    "app/shared/search.component.ts",
+    "app/shared/search.model.ts",
+    "app/users/index.ts",
+    "app/users/login/index.ts",
+    "app/users/login/login.component.ts",
+    "app/users/shared/auth-status.model.ts",
+    "app/users/shared/auth.service.ts",
+    "app/users/shared/index.ts",
+    "app/users/shared/token.model.ts",
+    "app/users/shared/user.model.ts",
+    "app/videos/index.ts",
+    "app/videos/shared/index.ts",
+    "app/videos/shared/loader/index.ts",
+    "app/videos/shared/loader/loader.component.ts",
+    "app/videos/shared/pagination.model.ts",
+    "app/videos/shared/sort-field.type.ts",
+    "app/videos/shared/video.model.ts",
+    "app/videos/shared/video.service.ts",
+    "app/videos/video-add/index.ts",
+    "app/videos/video-add/video-add.component.ts",
+    "app/videos/video-list/index.ts",
+    "app/videos/video-list/video-list.component.ts",
+    "app/videos/video-list/video-miniature.component.ts",
+    "app/videos/video-list/video-sort.component.ts",
+    "app/videos/video-watch/index.ts",
+    "app/videos/video-watch/video-watch.component.ts",
+    "main.ts",
     "typings/globals/es6-shim/index.d.ts",
     "typings/globals/jasmine/index.d.ts",
     "typings/globals/node/index.d.ts",
index 0caa0df20a227b65f391d9d4b9f4aa126fd433f0..d8dfedca362d7f2b9c774e7c993845fa200740bf 100755 (executable)
@@ -6,4 +6,4 @@ cd client || exit -1
 # Compile index and angular files
 concurrently \
   "node-sass --include-path node_modules/bootstrap-sass/assets/stylesheets/ stylesheets/application.scss stylesheets/index.css" \
-  "node-sass angular/ --output angular/"
+  "node-sass app/ --output app/"
index 82c079f28637fb0a75e385ef7267d96de278aff0..04d239ffc64392ea2c762d5ddd2b5e52b0b30d88 100755 (executable)
@@ -2,4 +2,4 @@
 
 cd client || exit -1
 rm -f stylesheets/index.css
-find angular -regextype posix-egrep -regex ".*\.(css)$" -exec rm -f {} \;
+find app -regextype posix-egrep -regex ".*\.(css)$" -exec rm -f {} \;
index 3ea6e78d5b8cc5353094877d3de2b241ea6629b9..b17888640bb87ec3ff3619a74c77d67f4404cfa4 100755 (executable)
@@ -1,5 +1,6 @@
 #!/usr/bin/env sh
 
 cd client || exit -1
-find angular -regextype posix-egrep -regex ".*\.(js|map)$" -exec rm -f {} \;
+find app -regextype posix-egrep -regex ".*\.(js|map)$" -exec rm -f {} \;
 rm -rf ./bundles
+rm -f main.js main.js.map
index a4acc439c90da77806a0b3458d53525ea15ed671..5f095265ef954ee3156f4e4fc3a3d4e37305a842 100755 (executable)
@@ -1,3 +1,3 @@
 #!/usr/bin/env sh
 
-livereload client/angular -e scss 
+livereload client/app -e scss 
index 22c536e383f3ddba85057c00767ea6988d509d1c..f7a8c8a2b9820defb1bf4df7b85f3165995d081d 100755 (executable)
@@ -4,4 +4,4 @@ cd client || exit -1
 
 concurrently \
   "node-sass -w --include-path node_modules/bootstrap-sass/assets/stylesheets/ stylesheets/application.scss stylesheets/index.css" \
-  "node-sass -w angular/ --output angular/"
+  "node-sass -w app/ --output app/"
index 024ce10f86c87246b97a9e5d875ce859a652d729..02c0d53cd7ef949fe0592aac77ce3bffbcc9c984 100644 (file)
--- a/server.js
+++ b/server.js
@@ -64,9 +64,9 @@ const apiRoute = '/api/' + constants.API_VERSION
 app.use(apiRoute, routes.api)
 
 // Static files
-app.use('/app', express.static(path.join(__dirname, '/client'), { maxAge: 0 }))
+app.use('/client', express.static(path.join(__dirname, '/client'), { maxAge: 0 }))
 // 404 for static files not found
-app.use('/app/*', function (req, res, next) {
+app.use('/client/*', function (req, res, next) {
   res.sendStatus(404)
 })