<div class="row">
<menu class="col-md-2 col-xs-3">
- <div class="panel_block">
- <div id="panel_user_login" class="panel_button">
+ <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" [routerLink]="['/users/login']">Login</a>
<a *ngIf="isLoggedIn" (click)="logout()">Logout</a>
</div>
</div>
- <div class="panel_block">
- <div id="panel_get_videos" class="panel_button">
+ <div class="panel-block">
+ <div id="panel-get-videos" class="panel-button">
<span class="glyphicon glyphicon-list"></span>
- <a [routerLink]="['VideosList']">Get videos</a>
+ <a [routerLink]="['/videos/list']">Get videos</a>
</div>
- <div id="panel_upload_video" class="panel_button" *ngIf="isLoggedIn">
+ <div id="panel-upload-video" class="panel-button" *ngIf="isLoggedIn">
<span class="glyphicon glyphicon-cloud-upload"></span>
- <a [routerLink]="['VideosAdd']">Upload a video</a>
+ <a [routerLink]="['/videos/add']">Upload a video</a>
</div>
</div>
- <div class="panel_block" *ngIf="isLoggedIn">
- <div id="panel_make_friends" class="panel_button">
+ <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">
+ <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">
+ <div class="col-md-9 col-xs-8 router-outler-container">
<router-outlet></router-outlet>
</div>
margin-right: 20px;
border-right: 1px solid rgba(0, 0, 0, 0.2);
- .panel_button {
+ .panel-button {
margin: 8px;
cursor: pointer;
transition: margin 0.2s;
}
}
-.panel_block:not(:last-child) {
+.panel-block:not(:last-child) {
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
import { Component } from '@angular/core';
import { HTTP_PROVIDERS } from '@angular/http';
-import { RouteConfig, Router, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from '@angular/router-deprecated';
+import { Router, ROUTER_DIRECTIVES, ROUTER_PROVIDERS, Routes } from '@angular/router';
import { FriendService } from './friends';
import { LoginComponent } from './login';
VideoWatchComponent,
VideoService
} from './videos';
+import { SearchService } from './shared'; // Temporary
-@RouteConfig([
+@Routes([
{
path: '/users/login',
- name: 'UserLogin',
component: LoginComponent
},
{
path: '/videos/list',
- name: 'VideosList',
- component: VideoListComponent,
- useAsDefault: true
+ component: VideoListComponent
},
{
path: '/videos/watch/:id',
- name: 'VideosWatch',
component: VideoWatchComponent
},
{
path: '/videos/add',
- name: 'VideosAdd',
component: VideoAddComponent
}
])
template: require('./app.component.html'),
styles: [ require('./app.component.scss') ],
directives: [ ROUTER_DIRECTIVES, SearchComponent ],
- providers: [ AuthService, FriendService, HTTP_PROVIDERS, ROUTER_PROVIDERS, VideoService ]
+ providers: [ AuthService, FriendService, HTTP_PROVIDERS, ROUTER_PROVIDERS, VideoService, SearchService ]
})
export class AppComponent {
field: search.field,
search: search.value
};
- this.router.navigate(['VideosList', params]);
+ this.router.navigate(['/videos/list', params]);
} else {
- this.router.navigate(['VideosList']);
+ this.router.navigate(['/videos/list']);
}
}
+ // FIXME
logout() {
// this._authService.logout();
}
import { Component } from '@angular/core';
-import { Router } from '@angular/router-deprecated';
+import { Router } from '@angular/router';
import { AuthService, AuthStatus, User } from '../shared';
this.authService.setStatus(AuthStatus.LoggedIn);
- this.router.navigate(['VideosList']);
+ this.router.navigate(['/videos/list']);
},
error => {
if (error.error === 'invalid_grant') {
export * from './search-field.type';
export * from './search.component';
export * from './search.model';
+export * from './search.service';
-import { Component, EventEmitter, Output } from '@angular/core';
+import { Component, EventEmitter, Output, OnInit } from '@angular/core';
import { DROPDOWN_DIRECTIVES} from 'ng2-bootstrap/components/dropdown';
import { Search } from './search.model';
import { SearchField } from './search-field.type';
+import { SearchService } from './search.service'; // Temporary
@Component({
selector: 'my-search',
directives: [ DROPDOWN_DIRECTIVES ]
})
-export class SearchComponent {
+export class SearchComponent implements OnInit {
@Output() search = new EventEmitter<Search>();
fieldChoices = {
value: ''
};
+ constructor(private searchService: SearchService) {}
+
+ ngOnInit() {
+ this.searchService.searchChanged.subscribe(
+ newSearchCriterias => {
+ // Put a field by default
+ if (!newSearchCriterias.field) {
+ newSearchCriterias.field = 'name';
+ }
+
+ this.searchCriterias = newSearchCriterias;
+ }
+ );
+ }
+
get choiceKeys() {
return Object.keys(this.fieldChoices);
}
$event.stopPropagation();
this.searchCriterias.field = choice;
+ this.doSearch();
}
doSearch() {
--- /dev/null
+import { Injectable } from '@angular/core';
+import { Subject } from 'rxjs/Subject';
+
+import { Search } from './search.model';
+
+// This class is needed to communicate between videos/list and search component
+// Remove it when we'll be able to subscribe to router changes
+@Injectable()
+export class SearchService {
+ searchChanged: Subject<Search>;
+
+ constructor() {
+ this.searchChanged = new Subject<Search>();
+ }
+}
magnetUri: string;
name: string;
podUrl: string;
+ tags: string[];
thumbnailPath: string;
private static createByString(author: string, podUrl: string) {
magnetUri: string,
name: string,
podUrl: string,
+ tags: string[],
thumbnailPath: string
}) {
this.author = hash.author;
this.magnetUri = hash.magnetUri;
this.name = hash.name;
this.podUrl = hash.podUrl;
+ this.tags = hash.tags;
this.thumbnailPath = hash.thumbnailPath;
this.by = Video.createByString(hash.author, hash.podUrl);
ngControl="tags" #tags="ngForm" [disabled]="isTagsInputDisabled" (keyup)="onTagKeyPress($event)" [(ngModel)]="currentTag"
>
<div [hidden]="tags.valid || tags.pristine" class="alert alert-warning">
- A tag should be between 2 and 10 characters long
+ A tag should be between 2 and 10 characters (alphanumeric) long
</div>
</div>
<div class="tags">
- <div class="label label-info tag" *ngFor="let tag of video.tags">
+ <div class="label label-primary tag" *ngFor="let tag of video.tags">
{{ tag }}
<span class="remove" (click)="removeTag(tag)">x</span>
</div>
import { Control, ControlGroup, Validators } from '@angular/common';
import { Component, ElementRef, OnInit } from '@angular/core';
-import { Router } from '@angular/router-deprecated';
+import { Router } from '@angular/router';
import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe';
import { PROGRESSBAR_DIRECTIVES } from 'ng2-bootstrap/components/progressbar';
import { Component, OnInit } from '@angular/core';
-import { Router, ROUTER_DIRECTIVES, RouteParams } from '@angular/router-deprecated';
+import { Router, ROUTER_DIRECTIVES, RouteSegment } from '@angular/router';
import { PAGINATION_DIRECTIVES } from 'ng2-bootstrap/components/pagination';
import { AuthService, Search, SearchField, User } from '../../shared';
import { VideoMiniatureComponent } from './video-miniature.component';
import { VideoSortComponent } from './video-sort.component';
+import { SearchService } from '../../shared';
@Component({
selector: 'my-videos-list',
constructor(
private authService: AuthService,
private router: Router,
- private routeParams: RouteParams,
- private videoService: VideoService
- ) {
- this.search = {
- value: this.routeParams.get('search'),
- field: <SearchField>this.routeParams.get('field')
- };
-
- this.sort = <SortField>this.routeParams.get('sort') || '-createdDate';
- }
+ private routeSegment: RouteSegment,
+ private videoService: VideoService,
+ private searchService: SearchService // Temporary
+ ) {}
ngOnInit() {
if (this.authService.isLoggedIn()) {
this.user = User.load();
}
+ this.search = {
+ value: this.routeSegment.getParam('search'),
+ field: <SearchField>this.routeSegment.getParam('field')
+ };
+
+ // Temporary
+ this.searchChanged(this.search);
+
+ this.sort = <SortField>this.routeSegment.getParam('sort') || '-createdDate';
+
this.getVideos();
}
let observable = null;
- if (this.search.value !== null) {
+ if (this.search.value) {
observable = this.videoService.searchVideos(this.search, this.pagination, this.sort);
} else {
observable = this.videoService.getVideos(this.pagination, this.sort);
params.search = this.search.value;
}
- this.router.navigate(['VideosList', params]);
- this.getVideos();
+ this.router.navigate(['/videos/list', params]);
+ }
+
+ searchChanged(search: Search) {
+ this.searchService.searchChanged.next(search);
}
}
<div class="video-miniature col-md-4" (mouseenter)="onHover()" (mouseleave)="onBlur()">
<a
- [routerLink]="['VideosWatch', { id: video.id }]" [attr.title]="video.description"
+ [routerLink]="['/videos/watch', video.id]" [attr.title]="video.description"
class="video-miniature-thumbnail"
>
<img [attr.src]="video.thumbnailPath" alt="video thumbnail" />
></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-name-tags">
+ <a [routerLink]="['/videos/watch', video.id]" class="video-miniature-name">{{ video.name }}</a>
- <span class="video-miniature-author">by {{ video.by }}</span>
+ <span *ngFor="let tag of video.tags" class="video-miniature-tag">
+ <a [routerLink]="['/videos/list', { field: 'tags', search: tag }]" class="label label-primary">{{ tag }}</a>
+ </span>
+ </span>
+
+ <a [routerLink]="['/videos/list', { field: 'author', search: video.author }]" class="video-miniature-author">by {{ video.by }}</a>
<span class="video-miniature-created-date">on {{ video.createdDate | date:'short' }}</span>
</div>
</div>
.video-miniature {
- height: 200px;
+ margin-top: 30px;
display: inline-block;
position: relative;
.video-miniature-informations {
margin-left: 3px;
+ width: 200px;
- .video-miniature-name {
+ .video-miniature-name-tags {
display: block;
- font-weight: bold;
- &:hover {
- text-decoration: none;
+ .video-miniature-name {
+ font-weight: bold;
+
+ &:hover {
+ text-decoration: none;
+ }
+
+ &::after {
+ content: '\002022';
+ margin-left: 3px;
+ }
+ }
+
+ .video-miniature-tag {
+ font-size: 12px;
+ cursor: pointer;
+ transition: opacity 0.5s;
+ position: relative;
+ top: -2px;
+
+ &:hover {
+ opacity: 0.9;
+ }
}
}
display: block;
margin-left: 1px;
font-size: 11px;
- color: rgba(0, 0, 0, 0.5);
+ color: rgb(54, 118, 173);
+ }
+
+ .video-miniature-author {
+ transition: opacity 0.5s;
+
+ &:hover {
+ text-decoration: none;
+ opacity: 0.9;
+ }
}
}
}
import { DatePipe } from '@angular/common';
import { Component, Input, Output, EventEmitter } from '@angular/core';
-import { ROUTER_DIRECTIVES } from '@angular/router-deprecated';
+import { ROUTER_DIRECTIVES } from '@angular/router';
import { Video, VideoService } from '../shared';
import { User } from '../../shared';
import { Component, ElementRef, OnInit } from '@angular/core';
-import { CanDeactivate, ComponentInstruction, RouteParams } from '@angular/router-deprecated';
+import { CanDeactivate, RouteSegment } from '@angular/router';
import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe';
constructor(
private elementRef: ElementRef,
- private routeParams: RouteParams,
+ private routeSegment: RouteSegment,
private videoService: VideoService,
private webTorrentService: WebTorrentService
) {}
}
ngOnInit() {
- let id = this.routeParams.get('id');
+ let id = this.routeSegment.getParam('id');
this.videoService.getVideo(id).subscribe(
video => {
this.video = video;
);
}
- routerCanDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
+ routerCanDeactivate() {
console.log('Removing video from webtorrent.');
clearInterval(this.torrentInfosInterval);
this.webTorrentService.remove(this.video.magnetUri);
- return true;
+ return Promise.resolve(true);
}
private loadTooLong() {
import '@angular/core';
import '@angular/common';
import '@angular/http';
-import '@angular/router-deprecated';
+import '@angular/router';
// RxJS
import 'rxjs/Observable';
"src/app/shared/search/search-field.type.ts",
"src/app/shared/search/search.component.ts",
"src/app/shared/search/search.model.ts",
+ "src/app/shared/search/search.service.ts",
"src/app/shared/users/auth-status.model.ts",
"src/app/shared/users/auth.service.ts",
"src/app/shared/users/index.ts",