Angular application :first draft

pull/10/head
Chocobozzz 2016-03-14 13:50:19 +01:00
parent bd324a6692
commit dc8bc31be5
34 changed files with 509 additions and 192 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ test5/
test6/
public/stylesheets/global.css
public/stylesheets/vendor
uploads

6
client/.gitignore vendored
View File

@ -1,5 +1,5 @@
typings
components/**/*.js
components/**/*.map
angular/**/*.js
angular/**/*.map
angular/**/*.css
stylesheets/index.css
components/**/*.css

View File

@ -0,0 +1,37 @@
<div class="container">
<div class="row">
<menu class="col-md-2">
<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">
<span class="glyphicon glyphicon-cloud-upload"></span>
<a [routerLink]="['VideosAdd']">Upload a video</a>
</div>
<div id="panel_make_friends" class="panel_button">
<span class="glyphicon glyphicon-user"></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>
</menu>
<div class="col-md-9 router_outler_container">
<router-outlet></router-outlet>
</div>
</div>
<footer>
PeerTube, CopyLeft 2015-2016
</footer>
</div>

View File

@ -0,0 +1,28 @@
menu {
min-height: 300px;
height: 100%;
margin-right: 20px;
border-right: 1px solid rgba(0, 0, 0, 0.2);
.panel_button {
margin: 8px;
cursor: pointer;
transition: margin 0.2s;
&:hover {
margin-left: 15px;
}
a {
color: #333333;
}
}
.glyphicon {
margin: 5px;
}
}
footer {
margin-top: 30px;
}

View File

@ -0,0 +1,63 @@
import { Component, ElementRef } from 'angular2/core';
import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from 'angular2/router';
import {HTTP_PROVIDERS} from 'angular2/http';
import { VideosAddComponent } from '../videos/components/add/videos-add.component';
import { VideosListComponent } from '../videos/components/list/videos-list.component';
import { VideosWatchComponent } from '../videos/components/watch/videos-watch.component';
import { VideosService } from '../videos/services/videos.service';
import { FriendsService } from '../friends/services/friends.service';
@RouteConfig([
{
path: '/videos/list',
name: 'VideosList',
component: VideosListComponent,
useAsDefault: true
},
{
path: '/videos/watch/:id',
name: 'VideosWatch',
component: VideosWatchComponent
},
{
path: '/videos/add',
name: 'VideosAdd',
component: VideosAddComponent
}
])
@Component({
selector: 'my-app',
templateUrl: 'app/angular/app/app.component.html',
styleUrls: [ 'app/angular/app/app.component.css' ],
directives: [ ROUTER_DIRECTIVES ],
providers: [ ROUTER_PROVIDERS, HTTP_PROVIDERS, ElementRef, VideosService, FriendsService ]
})
export class AppComponent {
constructor(private _friendsService: FriendsService) {}
makeFriends() {
this._friendsService.makeFriends().subscribe(
status => {
if (status === 409) {
alert('Already made friends!');
}
else {
alert('Made friends!');
}
},
error => alert(error)
)
}
quitFriends() {
this._friendsService.quitFriends().subscribe(
status => {
alert('Quit friends!');
},
error => alert(error)
)
}
}

View File

@ -0,0 +1,27 @@
import {Injectable} from 'angular2/core';
import {Http, Response, Headers, RequestOptions} from 'angular2/http';
import {Observable} from 'rxjs/Rx';
@Injectable()
export class FriendsService {
private _baseFriendsUrl = '/api/v1/pods/';
constructor (private http: Http) {}
makeFriends() {
return this.http.get(this._baseFriendsUrl + 'makefriends')
.map(res => <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');
}
}

View File

@ -0,0 +1,39 @@
<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="btn btn-default btn-file">
<span>Select the video...</span>
<input type="file" name="input_video" id="input_video">
</div>
<span *ngIf="fileToUpload">{{ fileToUpload.name }}</span>
<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">
<progress [value]="progressBar.value" [max]="progressBar.max"></progress>
</div>
<input type="submit" value="Upload" class="btn btn-default" [disabled]="!videoForm.form.valid || !fileToUpload">
</form>

View File

@ -0,0 +1,25 @@
.btn-file {
position: relative;
overflow: hidden;
}
.btn-file input[type=file] {
position: absolute;
top: 0;
right: 0;
min-width: 100%;
min-height: 100%;
font-size: 100px;
text-align: right;
filter: alpha(opacity=0);
opacity: 0;
outline: none;
background: white;
cursor: inherit;
display: block;
}
.name_file {
display: inline-block;
margin-left: 10px;
}

View File

@ -0,0 +1,52 @@
import {Component, ElementRef, Inject, OnInit} from 'angular2/core';
import {Router} from 'angular2/router';
import {NgForm} from 'angular2/common';
import {Video} from '../../models/video';
declare var jQuery:any;
@Component({
selector: 'my-videos-add',
styleUrls: [ 'app/angular/videos/components/add/videos-add.component.css' ],
templateUrl: 'app/angular/videos/components/add/videos-add.component.html'
})
export class VideosAddComponent implements OnInit {
fileToUpload: any;
progressBar: { value: number; max: number; } = { value: 0, max: 0 };
private _form: any;
constructor(private _router: Router, private _elementRef: ElementRef) {}
ngOnInit() {
jQuery(this._elementRef.nativeElement).find('#input_video').fileupload({
singleFileUploads: true,
multipart: true,
url: '/api/v1/videos',
autoupload: false,
add: (e, data) => {
this._form = data;
this.fileToUpload = data['files'][0];
},
progressall: (e, data) => {
this.progressBar.value = data.loaded;
this.progressBar.max= data.total;
},
done: (e, data) => {
console.log('finished');
// Print all the videos once it's finished
this._router.navigate(['VideosList']);
}
});
}
uploadFile() {
this._form.formData = jQuery(this._elementRef.nativeElement).find('form').serializeArray();
this._form.submit();
}
}

View File

@ -0,0 +1,11 @@
<div *ngFor="#video of videos" class="video">
<div>
<a [routerLink]="['VideosWatch', { id: video._id }]" class="video_name">{{ video.name }}</a>
<span class="video_pod_url">{{ video.podUrl }}</span>
<span *ngIf="video.namePath !== null" (click)="removeVideo(video._id)" class="video_remove glyphicon glyphicon-remove"></span>
</div>
<div class="video_description">
{{ video.description }}
</div>
</div>

View File

@ -0,0 +1,34 @@
.video {
margin-bottom: 10px;
transition: margin 0.5s ease;
&:hover {
margin-left: 5px;
}
a.video_name {
color: #333333;
margin-right: 5px;
}
.video_pod_url {
font-size: small;
color: rgba(0, 0, 0, 0.5);
}
.video_description {
font-size: small;
font-style: italic;
margin-left: 7px;
}
.video_remove {
margin: 5px;
cursor: pointer;
}
}
.loading {
display: inline-block;
margin-top: 100px;
}

View File

@ -0,0 +1,39 @@
import {Component, OnInit} from 'angular2/core';
import {ROUTER_DIRECTIVES} from 'angular2/router';
import {VideosService} from '../../services/videos.service';
import {Video} from '../../models/video';
@Component({
selector: 'my-videos-list',
styleUrls: [ 'app/angular/videos/components/list/videos-list.component.css' ],
templateUrl: 'app/angular/videos/components/list/videos-list.component.html',
directives: [ ROUTER_DIRECTIVES ]
})
export class VideosListComponent implements OnInit {
videos: Video[];
constructor(
private _videosService: VideosService
) { }
ngOnInit() {
this.getVideos();
}
getVideos() {
this._videosService.getVideos().subscribe(
videos => this.videos = videos,
error => alert(error)
);
}
removeVideo(id: string) {
this._videosService.removeVideo(id).subscribe(
status => this.getVideos(),
error => alert(error)
)
}
}

View File

@ -0,0 +1,2 @@
<div class="embed-responsive embed-responsive-19by9">
</div>

View File

@ -0,0 +1,50 @@
/// <reference path='../../../../typings/browser/ambient/webtorrent/webtorrent.d.ts' />
import { Component, OnInit, ElementRef } from 'angular2/core';
import { RouteParams } from 'angular2/router';
declare var WebTorrent: any;
import { Video } from '../../models/video';
import { VideosService } from '../../services/videos.service';
@Component({
selector: 'my-video-watch',
templateUrl: 'app/angular/videos/components/watch/videos-watch.component.html',
styleUrls: [ 'app/angular/videos/components/watch/videos-watch.component.css' ]
})
export class VideosWatchComponent {
video: Video;
private client: any;
constructor(
private _videosService: VideosService,
private _routeParams: RouteParams,
private _elementRef: ElementRef
) {
this.client = new WebTorrent({ dht: false });
}
ngOnInit() {
let id = this._routeParams.get('id');
this._videosService.getVideo(id).subscribe(
video => this.loadVideo(video),
error => alert(error)
);
}
loadVideo(video: Video) {
this.video = video;
this.client.add(this.video.magnetUri, (torrent) => {
torrent.files[0].appendTo(this._elementRef.nativeElement, (err) => {
if (err) {
alert('Cannot append the file.');
console.error(err);
}
})
})
}
}

View File

@ -0,0 +1,6 @@
export interface Video {
_id: string;
name: string;
description: string;
magnetUri: string;
}

View File

@ -0,0 +1,37 @@
import {Injectable} from 'angular2/core';
import {Http, Response} from 'angular2/http';
import {Observable} from 'rxjs/Rx';
import {Video} from '../models/video';
@Injectable()
export class VideosService {
private _baseVideoUrl = '/api/v1/videos/';
constructor (private http: Http) {}
getVideos() {
return this.http.get(this._baseVideoUrl)
.map(res => <Video[]> res.json())
.catch(this.handleError);
}
getVideo(id: string) {
return this.http.get(this._baseVideoUrl + id)
.map(res => <Video> res.json())
.catch(this.handleError);
}
removeVideo(id: string) {
if (confirm('Are you sure?')) {
return this.http.delete(this._baseVideoUrl + id)
.map(res => <number> res.status)
.catch(this.handleError);
}
}
private handleError (error: Response) {
console.error(error);
return Observable.throw(error.json().error || 'Server error');
}
}

View File

@ -1,23 +0,0 @@
<div class="container">
<div class="row">
<menu class="col-md-2">
<div id="panel_get_videos" class="panel_button">
<a [routerLink]="['VideosList']" class="glyphicon glyphicon-list">Get videos</a>
</div>
<div id="panel_upload_video" class="panel_button">
<a [routerLink]="['VideosAdd']" class="glyphicon glyphicon-cloud-upload">Upload a video</a>
</div>
<div id="panel_make_friends" class="panel_button">
<a (click)='makeFriends()' class="glyphicon glyphicon-user">Make friends</a>
</div>
<div id="panel_quit_friends" class="panel_button">
<a (click)='quitFriends()' class="glyphicon glyphicon-plane">Quit friends</a>
</div>
</menu>
<router-outlet class="col-md-9"></router-outlet>
</div>
</div>

View File

@ -1,43 +0,0 @@
import {Component} from 'angular2/core';
import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from 'angular2/router';
import { VideosAddComponent } from '../videos/add/videos-add.component';
import { VideosListComponent } from '../videos/list/videos-list.component';
import { VideosWatchComponent } from '../videos/watch/videos-watch.component';
@RouteConfig([
{
path: '/videos/list',
name: 'VideosList',
component: VideosListComponent,
useAsDefault: true
},
{
path: '/videos/watch/:id',
name: 'VideosWatch',
component: VideosWatchComponent
},
{
path: '/videos/add',
name: 'VideosAdd',
component: VideosAddComponent
}
])
@Component({
selector: 'my-app',
templateUrl: 'app/components/app/app.component.html',
styleUrls: [ 'app/components/app/app.component.css' ],
directives: [ ROUTER_DIRECTIVES ],
providers: [ ROUTER_PROVIDERS ]
})
export class AppComponent {
makeFriends() {
alert('make Friends');
}
quitFriends() {
alert('quit Friends');
}
}

View File

@ -1 +0,0 @@
export class VideosAddComponent {}

View File

@ -1 +0,0 @@
export class VideosListComponent {}

View File

@ -1 +0,0 @@
export class VideosWatchComponent {}

View File

@ -1,34 +1,40 @@
<html>
<head>
<title>Angular 2 QuickStart</title>
<title>PeerTube</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/stylesheets/index.css">
<link rel="stylesheet" href="/app/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/systemjs/dist/system-polyfills.js"></script>
<script src="app/node_modules/angular2/es6/dev/src/testing/shims_for_IE.js"></script>
<script src="/app/node_modules/es6-shim/es6-shim.min.js"></script>
<script src="/app/node_modules/systemjs/dist/system-polyfills.js"></script>
<script src="/app/node_modules/angular2/es6/dev/src/testing/shims_for_IE.js"></script>
<script src="/app/node_modules/angular2/bundles/angular2-polyfills.js"></script>
<script src="/app/node_modules/systemjs/dist/system.src.js"></script>
<script src="/app/node_modules/rxjs/bundles/Rx.js"></script>
<script src="/app/node_modules/angular2/bundles/angular2.dev.js"></script>
<script src="/app/node_modules/angular2/bundles/router.dev.js"></script>
<script src="/app/node_modules/angular2/bundles/http.dev.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="/app/node_modules/webtorrent/webtorrent.min.js"></script>
<script src="app/node_modules/angular2/bundles/angular2-polyfills.js"></script>
<script src="app/node_modules/systemjs/dist/system.src.js"></script>
<script src="app/node_modules/rxjs/bundles/Rx.js"></script>
<script src="app/node_modules/angular2/bundles/angular2.dev.js"></script>
<script src="app/node_modules/angular2/bundles/router.dev.js"></script>
<!-- 2. Configure SystemJS -->
<script>
System.config({
packages: {
app: {
'/app': {
components: {
format: 'register',
defaultExtension: 'js'
}
}
},
}
});
System.import('app/components/bootstrap')
System.import('/app/angular/bootstrap')
.then(null, console.error.bind(console));
</script>

View File

@ -20,13 +20,17 @@
},
"license": "ISC",
"dependencies": {
"angular2": "2.0.0-beta.8",
"angular2": "2.0.0-beta.9",
"blueimp-file-upload": "^9.12.1",
"bootstrap-sass": "^3.3.6",
"es6-promise": "^3.0.2",
"es6-shim": "^0.33.3",
"jquery": "^2.2.1",
"jquery.ui.widget": "^1.10.3",
"reflect-metadata": "0.1.2",
"rxjs": "5.0.0-beta.2",
"systemjs": "0.19.22",
"webtorrent": "^0.85.1",
"zone.js": "0.5.15"
},
"devDependencies": {

View File

@ -1,6 +1,6 @@
$icon-font-path: "/stylesheets/vendor/fonts/bootstrap/";
$icon-font-path: "/app/node_modules/bootstrap-sass/assets/fonts/bootstrap/";
@import "bootstrap-variables";
@import "_bootstrap";
@import "base";
@import "index";
@import "base.scss";
@import "index.scss";

View File

@ -108,9 +108,9 @@
// $line-height-large: 1.3333333 // extra decimals for Win 8.1 Chrome
// $line-height-small: 1.5
$border-radius-base: 0;
$border-radius-large: 0;
$border-radius-small: 0;
// $border-radius-base: 0;
// $border-radius-large: 0;
// $border-radius-small: 0;
//** Global color for active items (e.g., navs or dropdowns).
// $component-active-color: #fff

View File

@ -1,88 +1,5 @@
.span_action {
margin: 5px;
cursor: pointer;
}
header div {
height: 50px;
line-height: 25px;
margin-bottom: 50px;
}
menu {
margin-right: 20px;
border-right: 1px solid rgba(0, 0, 0, 0.2);
}
menu .panel_button {
margin: 8px;
cursor: pointer;
transition: margin 0.2s;
}
menu .panel_button:hover {
margin-left: 15px;
}
menu .glyphicon {
margin: 5px;
}
#ajax_load {
min-height: 500px;
}
.loading {
display: inline-block;
margin-top: 100px;
}
.video {
margin-bottom: 10px;
transition: margin 0.5s ease;
}
.video:hover {
margin-left: 5px;
}
.video_name {
cursor: pointer;
margin-right: 5px;
}
.video_pod_url {
font-size: small;
color: rgba(0, 0, 0, 0.5);
}
.video_description {
font-size: small;
font-style: italic;
margin-left: 7px;
}
.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;
}

View File

@ -1,6 +1,9 @@
{
"ambientDependencies": {
"es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#4de74cb527395c13ba20b438c3a7a419ad931f1c",
"jasmine": "github:DefinitelyTyped/DefinitelyTyped/jasmine/jasmine.d.ts#bc92442c075929849ec41d28ab618892ba493504"
"jasmine": "github:DefinitelyTyped/DefinitelyTyped/jasmine/jasmine.d.ts#bc92442c075929849ec41d28ab618892ba493504",
"node": "github:DefinitelyTyped/DefinitelyTyped/node/node.d.ts#abc2bcfb8524b1e027e6298d3348012b5b06eda5",
"parse-torrent": "github:DefinitelyTyped/DefinitelyTyped/parse-torrent/parse-torrent.d.ts#abc2bcfb8524b1e027e6298d3348012b5b06eda5",
"webtorrent": "github:DefinitelyTyped/DefinitelyTyped/webtorrent/webtorrent.d.ts#abc2bcfb8524b1e027e6298d3348012b5b06eda5"
}
}

View File

@ -18,19 +18,25 @@
},
"scripts": {
"build": "concurrently \"npm run client:sass\" \"npm run client:tsc\"",
"client:clean": "concurrently \"npm run client:tsc:clean\" \"npm run client:sass:clean\"",
"client:sass:index": "npm run client:sass:index:clean && cd client && node-sass --include-path node_modules/bootstrap-sass/assets/stylesheets/ stylesheets/application.scss stylesheets/index.css",
"client:sass:index:watch": "cd client && node-sass -w --include-path node_modules/bootstrap-sass/assets/stylesheets/ stylesheets/application.scss stylesheets/index.css",
"client:sass:index:clean": "cd client && rm -f stylesheets/index.css",
"client:sass:components": "cd client && node-sass components/ --output components/",
"client:sass:components:watch": "cd client && node-sass -w components/ --output components/",
"client:sass:components:clean": "cd client && rm -f components/**/*.css",
"client:sass": "concurrently \"npm run client:sass:index\" \"npm run client:sass:components\"",
"client:sass:watch": "concurrently \"npm run client:sass:index:watch\" \"npm run client:sass:components:watch\"",
"client:sass:clean": "concurrently \"npm run client:sass:index:clean\" \"npm run client:sass:components:clean\"",
"client:sass:angular": "cd client && node-sass angular/ --output angular/",
"client:sass:angular:watch": "cd client && node-sass -w angular/ --output angular/",
"client:sass:angular:clean": "cd client && rm -f angular/**/*.css",
"client:sass": "concurrently \"npm run client:sass:index\" \"npm run client:sass:angular\"",
"client:sass:watch": "concurrently \"npm run client:sass:index:watch\" \"npm run client:sass:angular:watch\"",
"client:sass:clean": "concurrently \"npm run client:sass:index:clean\" \"npm run client:sass:angular:clean\"",
"client:tsc": "cd client && npm run tsc",
"client:tsc:watch": "cd client && npm run tsc:w",
"client:tsc:clean": "cd client && rm -f components/**/*.js components/**/*.js.map",
"client:tsc:clean": "cd client && find angular -regextype posix-egrep -regex \".*\\.(js|map)$\" -exec rm -f {} \\;",
"dev": "npm run build && concurrently \"npm run livereload\" \"npm run client:tsc:watch\" \"npm run client:sass:watch\" \"npm start\"",
"livereload": "livereload ./client",
"start": "node server",
@ -47,7 +53,6 @@
"electron-spawn": "https://github.com/Chocobozzz/electron-spawn",
"express": "^4.12.4",
"express-validator": "^2.11.0",
"jquery": "^2.1.4",
"lodash-node": "^3.10.2",
"mkdirp": "^0.5.1",
"mongoose": "^4.0.5",

View File

@ -20,5 +20,5 @@ module.exports = router
// ---------------------------------------------------------------------------
function badRequest (req, res, next) {
res.sendStatus(400)
res.type('json').status(400).end()
}

View File

@ -66,7 +66,7 @@ function makeFriends (req, res, next) {
friends.makeFriends(function (err) {
if (err) return next(err)
res.sendStatus(204)
res.type('json').status(204).end()
})
}
@ -79,7 +79,7 @@ function removePods (req, res, next) {
if (err) logger.error('Cannot remove all remote videos of %s.', url)
else logger.info('%s pod removed.', url)
res.sendStatus(204)
res.type('json').status(204).end()
})
})
}
@ -88,6 +88,6 @@ function quitFriends (req, res, next) {
friends.quitFriends(function (err) {
if (err) return next(err)
res.sendStatus(204)
res.type('json').status(204).end()
})
}

View File

@ -48,6 +48,6 @@ function removeRemoteVideo (req, res, next) {
videos.removeRemotesOfByMagnetUris(url, magnetUris, function (err) {
if (err) return next(err)
res.sendStatus(204)
res.type('json').status(204).end()
})
}

View File

@ -77,7 +77,7 @@ function addVideo (req, res, next) {
friends.addVideoToFriends(video_data)
// TODO : include Location of the new video
res.sendStatus(201)
res.type('json').status(201).end()
})
})
}
@ -87,7 +87,7 @@ function getVideos (req, res, next) {
if (err) return next(err)
if (video === null) {
return res.sendStatus(404)
res.type('json').status(204).end()
}
res.json(video)
@ -117,7 +117,7 @@ function removeVideo (req, res, next) {
}
friends.removeVideoToFriends(params)
res.sendStatus(204)
res.type('json').status(204).end()
})
})
})