diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2017-12-01 14:46:22 +0100 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2017-12-01 14:46:22 +0100 |
commit | 9bf9d2a5c223bf006496ae7adf0c0bd7a7975108 (patch) | |
tree | 7e72814af43176ad96841046a9310af001d23a14 | |
parent | 26c6ee80d0fecfce595e8970f15717560b4f4ceb (diff) | |
download | PeerTube-9bf9d2a5c223bf006496ae7adf0c0bd7a7975108.tar.gz PeerTube-9bf9d2a5c223bf006496ae7adf0c0bd7a7975108.tar.zst PeerTube-9bf9d2a5c223bf006496ae7adf0c0bd7a7975108.zip |
Begin videos list new design
23 files changed, 213 insertions, 296 deletions
diff --git a/client/package.json b/client/package.json index c551c995a..310860fec 100644 --- a/client/package.json +++ b/client/package.json | |||
@@ -43,7 +43,6 @@ | |||
43 | "@types/webpack": "^3.0.0", | 43 | "@types/webpack": "^3.0.0", |
44 | "@types/webtorrent": "^0.98.4", | 44 | "@types/webtorrent": "^0.98.4", |
45 | "add-asset-html-webpack-plugin": "^2.0.1", | 45 | "add-asset-html-webpack-plugin": "^2.0.1", |
46 | "angular-pipes": "^6.0.0", | ||
47 | "angular2-notifications": "^0.7.7", | 46 | "angular2-notifications": "^0.7.7", |
48 | "angular2-template-loader": "^0.6.0", | 47 | "angular2-template-loader": "^0.6.0", |
49 | "assets-webpack-plugin": "^3.4.0", | 48 | "assets-webpack-plugin": "^3.4.0", |
@@ -72,6 +71,7 @@ | |||
72 | "ngc-webpack": "3.2.2", | 71 | "ngc-webpack": "3.2.2", |
73 | "ngx-bootstrap": "2.0.0-beta.9", | 72 | "ngx-bootstrap": "2.0.0-beta.9", |
74 | "ngx-chips": "1.5.3", | 73 | "ngx-chips": "1.5.3", |
74 | "ngx-pipes": "^2.0.5", | ||
75 | "node-sass": "^4.1.1", | 75 | "node-sass": "^4.1.1", |
76 | "normalize.css": "^7.0.0", | 76 | "normalize.css": "^7.0.0", |
77 | "optimize-js-plugin": "0.0.4", | 77 | "optimize-js-plugin": "0.0.4", |
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html index fb31c0734..21f8d8ba4 100644 --- a/client/src/app/menu/menu.component.html +++ b/client/src/app/menu/menu.component.html | |||
@@ -26,12 +26,12 @@ | |||
26 | <div class="panel-block"> | 26 | <div class="panel-block"> |
27 | <div class="block-title">Videos</div> | 27 | <div class="block-title">Videos</div> |
28 | 28 | ||
29 | <a routerLink="/videos/list" routerLinkActive="active"> | 29 | <a routerLink="/videos/trending" routerLinkActive="active"> |
30 | <span class="icon icon-videos-trending"></span> | 30 | <span class="icon icon-videos-trending"></span> |
31 | Trending | 31 | Trending |
32 | </a> | 32 | </a> |
33 | 33 | ||
34 | <a routerLink="/videos/list" routerLinkActive="active"> | 34 | <a routerLink="/videos/recently-added" routerLinkActive="active"> |
35 | <span class="icon icon-videos-recently-added"></span> | 35 | <span class="icon icon-videos-recently-added"></span> |
36 | Recently added | 36 | Recently added |
37 | </a> | 37 | </a> |
diff --git a/client/src/app/shared/misc/from-now.pipe.ts b/client/src/app/shared/misc/from-now.pipe.ts new file mode 100644 index 000000000..25e5d6a85 --- /dev/null +++ b/client/src/app/shared/misc/from-now.pipe.ts | |||
@@ -0,0 +1,37 @@ | |||
1 | import { Pipe, PipeTransform } from '@angular/core' | ||
2 | |||
3 | // Thanks: https://github.com/danrevah/ngx-pipes/blob/master/src/pipes/math/bytes.ts | ||
4 | |||
5 | @Pipe({name: 'fromNow'}) | ||
6 | export class FromNowPipe implements PipeTransform { | ||
7 | |||
8 | transform (value: number) { | ||
9 | const seconds = Math.floor((Date.now() - value) / 1000) | ||
10 | |||
11 | let interval = Math.floor(seconds / 31536000) | ||
12 | if (interval > 1) { | ||
13 | return interval + ' years ago' | ||
14 | } | ||
15 | |||
16 | interval = Math.floor(seconds / 2592000) | ||
17 | if (interval > 1) return interval + ' months ago' | ||
18 | if (interval === 1) return interval + ' month ago' | ||
19 | |||
20 | interval = Math.floor(seconds / 604800) | ||
21 | if (interval > 1) return interval + ' weeks ago' | ||
22 | if (interval === 1) return interval + ' week ago' | ||
23 | |||
24 | interval = Math.floor(seconds / 86400) | ||
25 | if (interval > 1) return interval + ' days ago' | ||
26 | if (interval === 1) return interval + ' day ago' | ||
27 | |||
28 | interval = Math.floor(seconds / 3600) | ||
29 | if (interval > 1) return interval + ' hours ago' | ||
30 | if (interval === 1) return interval + ' hour ago' | ||
31 | |||
32 | interval = Math.floor(seconds / 60) | ||
33 | if (interval >= 1) return interval + ' min ago' | ||
34 | |||
35 | return Math.floor(seconds) + ' sec ago' | ||
36 | } | ||
37 | } | ||
diff --git a/client/src/app/shared/misc/number-formatter.pipe.ts b/client/src/app/shared/misc/number-formatter.pipe.ts new file mode 100644 index 000000000..2491fb1d6 --- /dev/null +++ b/client/src/app/shared/misc/number-formatter.pipe.ts | |||
@@ -0,0 +1,19 @@ | |||
1 | import { Pipe, PipeTransform } from '@angular/core' | ||
2 | |||
3 | // Thanks: https://github.com/danrevah/ngx-pipes/blob/master/src/pipes/math/bytes.ts | ||
4 | |||
5 | @Pipe({name: 'numberFormatter'}) | ||
6 | export class NumberFormatterPipe implements PipeTransform { | ||
7 | private dictionary: Array<{max: number, type: string}> = [ | ||
8 | { max: 1000, type: '' }, | ||
9 | { max: 1000000, type: 'K' }, | ||
10 | { max: 1000000000, type: 'M' } | ||
11 | ] | ||
12 | |||
13 | transform (value: number) { | ||
14 | const format = this.dictionary.find(d => value < d.max) || this.dictionary[this.dictionary.length - 1] | ||
15 | const calc = Math.floor(value / (format.max / 1000)) | ||
16 | |||
17 | return `${calc}${format.type}` | ||
18 | } | ||
19 | } | ||
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index 456ce851e..c7ea6e603 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts | |||
@@ -1,25 +1,26 @@ | |||
1 | import { NgModule } from '@angular/core' | ||
2 | import { HttpClientModule } from '@angular/common/http' | ||
3 | import { CommonModule } from '@angular/common' | 1 | import { CommonModule } from '@angular/common' |
2 | import { HttpClientModule } from '@angular/common/http' | ||
3 | import { NgModule } from '@angular/core' | ||
4 | import { FormsModule, ReactiveFormsModule } from '@angular/forms' | 4 | import { FormsModule, ReactiveFormsModule } from '@angular/forms' |
5 | import { RouterModule } from '@angular/router' | 5 | import { RouterModule } from '@angular/router' |
6 | 6 | ||
7 | import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe' | ||
8 | import { KeysPipe } from 'angular-pipes/src/object/keys.pipe' | ||
9 | import { BsDropdownModule } from 'ngx-bootstrap/dropdown' | 7 | import { BsDropdownModule } from 'ngx-bootstrap/dropdown' |
10 | import { ProgressbarModule } from 'ngx-bootstrap/progressbar' | ||
11 | import { PaginationModule } from 'ngx-bootstrap/pagination' | ||
12 | import { ModalModule } from 'ngx-bootstrap/modal' | 8 | import { ModalModule } from 'ngx-bootstrap/modal' |
13 | import { DataTableModule } from 'primeng/components/datatable/datatable' | 9 | import { PaginationModule } from 'ngx-bootstrap/pagination' |
10 | import { ProgressbarModule } from 'ngx-bootstrap/progressbar' | ||
11 | import { BytesPipe, KeysPipe } from 'ngx-pipes' | ||
14 | import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared' | 12 | import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared' |
13 | import { DataTableModule } from 'primeng/components/datatable/datatable' | ||
15 | 14 | ||
16 | import { AUTH_INTERCEPTOR_PROVIDER } from './auth' | 15 | import { AUTH_INTERCEPTOR_PROVIDER } from './auth' |
16 | import { LoaderComponent } from './misc/loader.component' | ||
17 | import { RestExtractor, RestService } from './rest' | 17 | import { RestExtractor, RestService } from './rest' |
18 | import { SearchComponent, SearchService } from './search' | 18 | import { SearchComponent, SearchService } from './search' |
19 | import { UserService } from './users' | 19 | import { UserService } from './users' |
20 | import { VideoAbuseService } from './video-abuse' | 20 | import { VideoAbuseService } from './video-abuse' |
21 | import { VideoBlacklistService } from './video-blacklist' | 21 | import { VideoBlacklistService } from './video-blacklist' |
22 | import { LoaderComponent } from './misc/loader.component' | 22 | import { NumberFormatterPipe } from './misc/number-formatter.pipe' |
23 | import { FromNowPipe } from './misc/from-now.pipe' | ||
23 | 24 | ||
24 | @NgModule({ | 25 | @NgModule({ |
25 | imports: [ | 26 | imports: [ |
@@ -42,7 +43,9 @@ import { LoaderComponent } from './misc/loader.component' | |||
42 | BytesPipe, | 43 | BytesPipe, |
43 | KeysPipe, | 44 | KeysPipe, |
44 | SearchComponent, | 45 | SearchComponent, |
45 | LoaderComponent | 46 | LoaderComponent, |
47 | NumberFormatterPipe, | ||
48 | FromNowPipe | ||
46 | ], | 49 | ], |
47 | 50 | ||
48 | exports: [ | 51 | exports: [ |
@@ -62,7 +65,10 @@ import { LoaderComponent } from './misc/loader.component' | |||
62 | KeysPipe, | 65 | KeysPipe, |
63 | 66 | ||
64 | SearchComponent, | 67 | SearchComponent, |
65 | LoaderComponent | 68 | LoaderComponent, |
69 | |||
70 | NumberFormatterPipe, | ||
71 | FromNowPipe | ||
66 | ], | 72 | ], |
67 | 73 | ||
68 | providers: [ | 74 | providers: [ |
diff --git a/client/src/app/videos/+video-edit/video-add.component.ts b/client/src/app/videos/+video-edit/video-add.component.ts index 1704cf486..76bfbb515 100644 --- a/client/src/app/videos/+video-edit/video-add.component.ts +++ b/client/src/app/videos/+video-edit/video-add.component.ts | |||
@@ -184,7 +184,7 @@ export class VideoAddComponent extends FormReactive implements OnInit { | |||
184 | this.notificationsService.success('Success', 'Video uploaded.') | 184 | this.notificationsService.success('Success', 'Video uploaded.') |
185 | 185 | ||
186 | // Display all the videos once it's finished | 186 | // Display all the videos once it's finished |
187 | this.router.navigate([ '/videos/list' ]) | 187 | this.router.navigate([ '/videos/trending' ]) |
188 | } | 188 | } |
189 | }, | 189 | }, |
190 | 190 | ||
diff --git a/client/src/app/videos/video-list/index.ts b/client/src/app/videos/video-list/index.ts index ed2bb1657..a5a60364a 100644 --- a/client/src/app/videos/video-list/index.ts +++ b/client/src/app/videos/video-list/index.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | export * from './my-videos.component' | 1 | export * from './my-videos.component' |
2 | export * from './video-list.component' | 2 | export * from './video-recently-added.component' |
3 | export * from './video-trending.component' | ||
3 | export * from './shared' | 4 | export * from './shared' |
diff --git a/client/src/app/videos/video-list/my-videos.component.ts b/client/src/app/videos/video-list/my-videos.component.ts index 648741a40..146db8262 100644 --- a/client/src/app/videos/video-list/my-videos.component.ts +++ b/client/src/app/videos/video-list/my-videos.component.ts | |||
@@ -27,7 +27,7 @@ export class MyVideosComponent extends AbstractVideoList implements OnInit, OnDe | |||
27 | } | 27 | } |
28 | 28 | ||
29 | ngOnDestroy () { | 29 | ngOnDestroy () { |
30 | this.subActivatedRoute.unsubscribe() | 30 | super.ngOnDestroy() |
31 | } | 31 | } |
32 | 32 | ||
33 | getVideosObservable () { | 33 | getVideosObservable () { |
diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.html b/client/src/app/videos/video-list/shared/abstract-video-list.html index 680fba3f5..ab5530e68 100644 --- a/client/src/app/videos/video-list/shared/abstract-video-list.html +++ b/client/src/app/videos/video-list/shared/abstract-video-list.html | |||
@@ -1,20 +1,8 @@ | |||
1 | <div class="row"> | 1 | <div class="title-page"> |
2 | <div class="content-padding"> | 2 | {{ titlePage }} |
3 | <div class="videos-info"> | ||
4 | <div class="col-md-9 col-xs-5 videos-total-results"> | ||
5 | <span *ngIf="pagination.totalItems !== null">{{ pagination.totalItems }} videos</span> | ||
6 | |||
7 | <my-loader [loading]="loading | async"></my-loader> | ||
8 | </div> | ||
9 | |||
10 | <my-video-sort class="col-md-3 col-xs-7" [currentSort]="sort" (sort)="onSort($event)"></my-video-sort> | ||
11 | </div> | ||
12 | </div> | ||
13 | </div> | 3 | </div> |
14 | 4 | ||
15 | <div class="content-padding videos-miniatures"> | 5 | <div class="videos-miniatures"> |
16 | <div class="no-video" *ngIf="isThereNoVideo()">There is no video.</div> | ||
17 | |||
18 | <my-video-miniature | 6 | <my-video-miniature |
19 | class="ng-animate" | 7 | class="ng-animate" |
20 | *ngFor="let video of videos" [video]="video" [user]="user" [currentSort]="sort" | 8 | *ngFor="let video of videos" [video]="video" [user]="user" [currentSort]="sort" |
diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.scss b/client/src/app/videos/video-list/shared/abstract-video-list.scss index 4b4409602..de174802b 100644 --- a/client/src/app/videos/video-list/shared/abstract-video-list.scss +++ b/client/src/app/videos/video-list/shared/abstract-video-list.scss | |||
@@ -17,20 +17,6 @@ | |||
17 | } | 17 | } |
18 | } | 18 | } |
19 | 19 | ||
20 | .videos-miniatures { | ||
21 | text-align: center; | ||
22 | padding-top: 0; | ||
23 | |||
24 | my-video-miniature { | ||
25 | text-align: left; | ||
26 | } | ||
27 | |||
28 | .no-video { | ||
29 | margin-top: 50px; | ||
30 | text-align: center; | ||
31 | } | ||
32 | } | ||
33 | |||
34 | pagination { | 20 | pagination { |
35 | display: block; | 21 | display: block; |
36 | text-align: center; | 22 | text-align: center; |
diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.ts b/client/src/app/videos/video-list/shared/abstract-video-list.ts index 87d5bc48a..262ea4e21 100644 --- a/client/src/app/videos/video-list/shared/abstract-video-list.ts +++ b/client/src/app/videos/video-list/shared/abstract-video-list.ts | |||
@@ -1,25 +1,19 @@ | |||
1 | import { OnDestroy, OnInit } from '@angular/core' | 1 | import { OnDestroy, OnInit } from '@angular/core' |
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { Subscription } from 'rxjs/Subscription' | ||
4 | import { BehaviorSubject } from 'rxjs/BehaviorSubject' | ||
5 | import { Observable } from 'rxjs/Observable' | ||
6 | 3 | ||
7 | import { NotificationsService } from 'angular2-notifications' | 4 | import { NotificationsService } from 'angular2-notifications' |
5 | import { Observable } from 'rxjs/Observable' | ||
6 | import { Subscription } from 'rxjs/Subscription' | ||
8 | 7 | ||
9 | import { | 8 | import { SortField, Video, VideoPagination } from '../../shared' |
10 | SortField, | ||
11 | Video, | ||
12 | VideoPagination | ||
13 | } from '../../shared' | ||
14 | 9 | ||
15 | export abstract class AbstractVideoList implements OnInit, OnDestroy { | 10 | export abstract class AbstractVideoList implements OnInit, OnDestroy { |
16 | loading: BehaviorSubject<boolean> = new BehaviorSubject(false) | ||
17 | pagination: VideoPagination = { | 11 | pagination: VideoPagination = { |
18 | currentPage: 1, | 12 | currentPage: 1, |
19 | itemsPerPage: 25, | 13 | itemsPerPage: 25, |
20 | totalItems: null | 14 | totalItems: null |
21 | } | 15 | } |
22 | sort: SortField | 16 | sort: SortField = '-createdAt' |
23 | videos: Video[] = [] | 17 | videos: Video[] = [] |
24 | 18 | ||
25 | protected notificationsService: NotificationsService | 19 | protected notificationsService: NotificationsService |
@@ -28,6 +22,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
28 | 22 | ||
29 | protected subActivatedRoute: Subscription | 23 | protected subActivatedRoute: Subscription |
30 | 24 | ||
25 | abstract titlePage: string | ||
31 | abstract getVideosObservable (): Observable<{ videos: Video[], totalVideos: number}> | 26 | abstract getVideosObservable (): Observable<{ videos: Video[], totalVideos: number}> |
32 | 27 | ||
33 | ngOnInit () { | 28 | ngOnInit () { |
@@ -44,7 +39,6 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
44 | } | 39 | } |
45 | 40 | ||
46 | getVideos () { | 41 | getVideos () { |
47 | this.loading.next(true) | ||
48 | this.videos = [] | 42 | this.videos = [] |
49 | 43 | ||
50 | const observable = this.getVideosObservable() | 44 | const observable = this.getVideosObservable() |
@@ -53,17 +47,11 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
53 | ({ videos, totalVideos }) => { | 47 | ({ videos, totalVideos }) => { |
54 | this.videos = videos | 48 | this.videos = videos |
55 | this.pagination.totalItems = totalVideos | 49 | this.pagination.totalItems = totalVideos |
56 | |||
57 | this.loading.next(false) | ||
58 | }, | 50 | }, |
59 | error => this.notificationsService.error('Error', error.text) | 51 | error => this.notificationsService.error('Error', error.text) |
60 | ) | 52 | ) |
61 | } | 53 | } |
62 | 54 | ||
63 | isThereNoVideo () { | ||
64 | return !this.loading.getValue() && this.videos.length === 0 | ||
65 | } | ||
66 | |||
67 | onPageChanged (event: { page: number }) { | 55 | onPageChanged (event: { page: number }) { |
68 | // Be sure the current page is set | 56 | // Be sure the current page is set |
69 | this.pagination.currentPage = event.page | 57 | this.pagination.currentPage = event.page |
@@ -71,12 +59,6 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
71 | this.navigateToNewParams() | 59 | this.navigateToNewParams() |
72 | } | 60 | } |
73 | 61 | ||
74 | onSort (sort: SortField) { | ||
75 | this.sort = sort | ||
76 | |||
77 | this.navigateToNewParams() | ||
78 | } | ||
79 | |||
80 | protected buildRouteParams () { | 62 | protected buildRouteParams () { |
81 | // There is always a sort and a current page | 63 | // There is always a sort and a current page |
82 | const params = { | 64 | const params = { |
diff --git a/client/src/app/videos/video-list/shared/index.ts b/client/src/app/videos/video-list/shared/index.ts index d8f73bcda..170ca4832 100644 --- a/client/src/app/videos/video-list/shared/index.ts +++ b/client/src/app/videos/video-list/shared/index.ts | |||
@@ -1,3 +1,2 @@ | |||
1 | export * from './abstract-video-list' | 1 | export * from './abstract-video-list' |
2 | export * from './video-miniature.component' | 2 | export * from './video-miniature.component' |
3 | export * from './video-sort.component' | ||
diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.html b/client/src/app/videos/video-list/shared/video-miniature.component.html index 6bbd29666..aea85b6c6 100644 --- a/client/src/app/videos/video-list/shared/video-miniature.component.html +++ b/client/src/app/videos/video-list/shared/video-miniature.component.html | |||
@@ -6,8 +6,7 @@ | |||
6 | <img [attr.src]="video.thumbnailUrl" alt="video thumbnail" [ngClass]="{ 'blur-filter': isVideoNSFWForThisUser() }" /> | 6 | <img [attr.src]="video.thumbnailUrl" alt="video thumbnail" [ngClass]="{ 'blur-filter': isVideoNSFWForThisUser() }" /> |
7 | 7 | ||
8 | <div class="video-miniature-thumbnail-overlay"> | 8 | <div class="video-miniature-thumbnail-overlay"> |
9 | <span class="video-miniature-thumbnail-overlay-views">{{ video.views }} views</span> | 9 | {{ video.durationLabel }} |
10 | <span class="video-miniature-thumbnail-overlay-duration">{{ video.durationLabel }}</span> | ||
11 | </div> | 10 | </div> |
12 | </a> | 11 | </a> |
13 | 12 | ||
@@ -21,13 +20,7 @@ | |||
21 | </a> | 20 | </a> |
22 | </span> | 21 | </span> |
23 | 22 | ||
24 | <div class="video-miniature-tags"> | 23 | <span class="video-miniature-created-at-views">{{ video.createdAt | fromNow }} - {{ video.views | numberFormatter }} views</span> |
25 | <span *ngFor="let tag of video.tags" class="video-miniature-tag"> | 24 | <span class="video-miniature-account">{{ video.by }}</span> |
26 | <a [routerLink]="['/videos/list', { field: 'tags', search: tag, sort: currentSort }]" class="label label-primary">{{ tag }}</a> | ||
27 | </span> | ||
28 | </div> | ||
29 | |||
30 | <a [routerLink]="['/videos/list', { field: 'account', search: video.account, sort: currentSort }]" class="video-miniature-account">{{ video.by }}</a> | ||
31 | <span class="video-miniature-created-at">{{ video.createdAt | date:'short' }}</span> | ||
32 | </div> | 25 | </div> |
33 | </div> | 26 | </div> |
diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.scss b/client/src/app/videos/video-list/shared/video-miniature.component.scss index 507ace098..ed15864d9 100644 --- a/client/src/app/videos/video-list/shared/video-miniature.component.scss +++ b/client/src/app/videos/video-list/shared/video-miniature.component.scss | |||
@@ -1,14 +1,14 @@ | |||
1 | .video-miniature { | 1 | .video-miniature { |
2 | margin: 15px 10px; | ||
3 | display: inline-block; | 2 | display: inline-block; |
4 | position: relative; | 3 | padding-right: 15px; |
5 | height: 190px; | 4 | margin-bottom: 30px; |
5 | height: 175px; | ||
6 | vertical-align: top; | 6 | vertical-align: top; |
7 | 7 | ||
8 | .video-miniature-thumbnail { | 8 | .video-miniature-thumbnail { |
9 | display: inline-block; | 9 | display: inline-block; |
10 | position: relative; | 10 | position: relative; |
11 | border-radius: 3px; | 11 | border-radius: 4px; |
12 | overflow: hidden; | 12 | overflow: hidden; |
13 | 13 | ||
14 | &:hover { | 14 | &:hover { |
@@ -22,38 +22,33 @@ | |||
22 | 22 | ||
23 | .video-miniature-thumbnail-overlay { | 23 | .video-miniature-thumbnail-overlay { |
24 | position: absolute; | 24 | position: absolute; |
25 | right: 0px; | 25 | right: 5px; |
26 | bottom: 0px; | 26 | bottom: 5px; |
27 | display: inline-block; | 27 | display: inline-block; |
28 | background-color: rgba(0, 0, 0, 0.7); | 28 | background-color: rgba(0, 0, 0, 0.7); |
29 | color: #fff; | 29 | color: #fff; |
30 | padding: 3px 5px; | 30 | font-size: 12px; |
31 | font-size: 11px; | 31 | font-weight: $font-bold; |
32 | font-weight: bold; | 32 | border-radius: 3px; |
33 | width: 100%; | 33 | padding: 0 5px; |
34 | |||
35 | .video-miniature-thumbnail-overlay-views { | ||
36 | |||
37 | } | ||
38 | |||
39 | .video-miniature-thumbnail-overlay-duration { | ||
40 | float: right; | ||
41 | } | ||
42 | } | 34 | } |
43 | } | 35 | } |
44 | 36 | ||
45 | .video-miniature-information { | 37 | .video-miniature-information { |
46 | width: 200px; | 38 | width: 200px; |
39 | margin-top: 2px; | ||
40 | line-height: normal; | ||
47 | 41 | ||
48 | .video-miniature-name { | 42 | .video-miniature-name { |
49 | height: 23px; | ||
50 | display: block; | 43 | display: block; |
51 | overflow: hidden; | 44 | overflow: hidden; |
52 | text-overflow: ellipsis; | 45 | text-overflow: ellipsis; |
53 | white-space: nowrap; | 46 | white-space: nowrap; |
54 | font-weight: bold; | 47 | font-weight: bold; |
55 | transition: color 0.2s; | 48 | transition: color 0.2s; |
56 | font-size: 15px; | 49 | font-size: 16px; |
50 | font-weight: $font-semibold; | ||
51 | color: #000; | ||
57 | 52 | ||
58 | &:hover { | 53 | &:hover { |
59 | text-decoration: none; | 54 | text-decoration: none; |
@@ -63,39 +58,16 @@ | |||
63 | filter: blur(3px); | 58 | filter: blur(3px); |
64 | padding-left: 4px; | 59 | padding-left: 4px; |
65 | } | 60 | } |
66 | |||
67 | .video-miniature-tags { | ||
68 | // Fix for chrome when tags are long | ||
69 | width: 201px; | ||
70 | |||
71 | .video-miniature-tag { | ||
72 | font-size: 13px; | ||
73 | cursor: pointer; | ||
74 | position: relative; | ||
75 | top: -2px; | ||
76 | |||
77 | .label { | ||
78 | transition: background-color 0.2s; | ||
79 | } | ||
80 | } | ||
81 | } | ||
82 | } | 61 | } |
83 | 62 | ||
84 | .video-miniature-account, .video-miniature-created-at { | 63 | .video-miniature-created-at-views { |
85 | display: block; | 64 | display: block; |
86 | margin-left: 1px; | 65 | font-size: 13px; |
87 | font-size: 11px; | ||
88 | color: $video-miniature-other-infos; | ||
89 | opacity: 0.9; | ||
90 | } | 66 | } |
91 | 67 | ||
92 | .video-miniature-account { | 68 | .video-miniature-account { |
93 | transition: color 0.2s; | 69 | font-size: 12px; |
94 | 70 | color: #585858; | |
95 | &:hover { | ||
96 | color: #23527c; | ||
97 | text-decoration: none; | ||
98 | } | ||
99 | } | 71 | } |
100 | } | 72 | } |
101 | } | 73 | } |
diff --git a/client/src/app/videos/video-list/shared/video-sort.component.html b/client/src/app/videos/video-list/shared/video-sort.component.html deleted file mode 100644 index 3bece0b22..000000000 --- a/client/src/app/videos/video-list/shared/video-sort.component.html +++ /dev/null | |||
@@ -1,5 +0,0 @@ | |||
1 | <select class="form-control input-sm" [(ngModel)]="currentSort" (ngModelChange)="onSortChange()"> | ||
2 | <option *ngFor="let choice of choiceKeys" [value]="choice"> | ||
3 | {{ getStringChoice(choice) }} | ||
4 | </option> | ||
5 | </select> | ||
diff --git a/client/src/app/videos/video-list/shared/video-sort.component.ts b/client/src/app/videos/video-list/shared/video-sort.component.ts deleted file mode 100644 index 8aa89d32b..000000000 --- a/client/src/app/videos/video-list/shared/video-sort.component.ts +++ /dev/null | |||
@@ -1,39 +0,0 @@ | |||
1 | import { Component, EventEmitter, Input, Output } from '@angular/core' | ||
2 | |||
3 | import { SortField } from '../../shared' | ||
4 | |||
5 | @Component({ | ||
6 | selector: 'my-video-sort', | ||
7 | templateUrl: './video-sort.component.html' | ||
8 | }) | ||
9 | |||
10 | export class VideoSortComponent { | ||
11 | @Output() sort = new EventEmitter<any>() | ||
12 | |||
13 | @Input() currentSort: SortField | ||
14 | |||
15 | sortChoices: { [ P in SortField ]: string } = { | ||
16 | 'name': 'Name - Asc', | ||
17 | '-name': 'Name - Desc', | ||
18 | 'duration': 'Duration - Asc', | ||
19 | '-duration': 'Duration - Desc', | ||
20 | 'createdAt': 'Created Date - Asc', | ||
21 | '-createdAt': 'Created Date - Desc', | ||
22 | 'views': 'Views - Asc', | ||
23 | '-views': 'Views - Desc', | ||
24 | 'likes': 'Likes - Asc', | ||
25 | '-likes': 'Likes - Desc' | ||
26 | } | ||
27 | |||
28 | get choiceKeys () { | ||
29 | return Object.keys(this.sortChoices) | ||
30 | } | ||
31 | |||
32 | getStringChoice (choiceKey: SortField) { | ||
33 | return this.sortChoices[choiceKey] | ||
34 | } | ||
35 | |||
36 | onSortChange () { | ||
37 | this.sort.emit(this.currentSort) | ||
38 | } | ||
39 | } | ||
diff --git a/client/src/app/videos/video-list/video-list.component.ts b/client/src/app/videos/video-list/video-list.component.ts deleted file mode 100644 index 784162679..000000000 --- a/client/src/app/videos/video-list/video-list.component.ts +++ /dev/null | |||
@@ -1,94 +0,0 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | ||
2 | import { ActivatedRoute, Router } from '@angular/router' | ||
3 | import { Subscription } from 'rxjs/Subscription' | ||
4 | |||
5 | import { NotificationsService } from 'angular2-notifications' | ||
6 | |||
7 | import { VideoService } from '../shared' | ||
8 | import { Search, SearchField, SearchService } from '../../shared' | ||
9 | import { AbstractVideoList } from './shared' | ||
10 | |||
11 | @Component({ | ||
12 | selector: 'my-videos-list', | ||
13 | styleUrls: [ './shared/abstract-video-list.scss' ], | ||
14 | templateUrl: './shared/abstract-video-list.html' | ||
15 | }) | ||
16 | export class VideoListComponent extends AbstractVideoList implements OnInit, OnDestroy { | ||
17 | private search: Search | ||
18 | private subSearch: Subscription | ||
19 | |||
20 | constructor ( | ||
21 | protected router: Router, | ||
22 | protected route: ActivatedRoute, | ||
23 | protected notificationsService: NotificationsService, | ||
24 | private videoService: VideoService, | ||
25 | private searchService: SearchService | ||
26 | ) { | ||
27 | super() | ||
28 | } | ||
29 | |||
30 | ngOnInit () { | ||
31 | // Subscribe to route changes | ||
32 | this.subActivatedRoute = this.route.params.subscribe(routeParams => { | ||
33 | this.loadRouteParams(routeParams) | ||
34 | |||
35 | // Update the search service component | ||
36 | this.searchService.updateSearch.next(this.search) | ||
37 | this.getVideos() | ||
38 | }) | ||
39 | |||
40 | // Subscribe to search changes | ||
41 | this.subSearch = this.searchService.searchUpdated.subscribe(search => { | ||
42 | this.search = search | ||
43 | // Reset pagination | ||
44 | this.pagination.currentPage = 1 | ||
45 | |||
46 | this.navigateToNewParams() | ||
47 | }) | ||
48 | } | ||
49 | |||
50 | ngOnDestroy () { | ||
51 | super.ngOnDestroy() | ||
52 | |||
53 | this.subSearch.unsubscribe() | ||
54 | } | ||
55 | |||
56 | getVideosObservable () { | ||
57 | let observable = null | ||
58 | if (this.search.value) { | ||
59 | observable = this.videoService.searchVideos(this.search, this.pagination, this.sort) | ||
60 | } else { | ||
61 | observable = this.videoService.getVideos(this.pagination, this.sort) | ||
62 | } | ||
63 | |||
64 | return observable | ||
65 | } | ||
66 | |||
67 | protected buildRouteParams () { | ||
68 | const params = super.buildRouteParams() | ||
69 | |||
70 | // Maybe there is a search | ||
71 | if (this.search.value) { | ||
72 | params['field'] = this.search.field | ||
73 | params['search'] = this.search.value | ||
74 | } | ||
75 | |||
76 | return params | ||
77 | } | ||
78 | |||
79 | protected loadRouteParams (routeParams: { [ key: string ]: any }) { | ||
80 | super.loadRouteParams(routeParams) | ||
81 | |||
82 | if (routeParams['search'] !== undefined) { | ||
83 | this.search = { | ||
84 | value: routeParams['search'], | ||
85 | field: routeParams['field'] as SearchField | ||
86 | } | ||
87 | } else { | ||
88 | this.search = { | ||
89 | value: '', | ||
90 | field: 'name' | ||
91 | } | ||
92 | } | ||
93 | } | ||
94 | } | ||
diff --git a/client/src/app/videos/video-list/video-recently-added.component.ts b/client/src/app/videos/video-list/video-recently-added.component.ts new file mode 100644 index 000000000..dbba264df --- /dev/null +++ b/client/src/app/videos/video-list/video-recently-added.component.ts | |||
@@ -0,0 +1,33 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | ||
2 | import { ActivatedRoute, Router } from '@angular/router' | ||
3 | import { NotificationsService } from 'angular2-notifications' | ||
4 | import { VideoService } from '../shared' | ||
5 | import { AbstractVideoList } from './shared' | ||
6 | |||
7 | @Component({ | ||
8 | selector: 'my-videos-recently-added', | ||
9 | styleUrls: [ './shared/abstract-video-list.scss' ], | ||
10 | templateUrl: './shared/abstract-video-list.html' | ||
11 | }) | ||
12 | export class VideoRecentlyAddedComponent extends AbstractVideoList implements OnInit, OnDestroy { | ||
13 | titlePage = 'Recently added' | ||
14 | |||
15 | constructor (protected router: Router, | ||
16 | protected route: ActivatedRoute, | ||
17 | protected notificationsService: NotificationsService, | ||
18 | private videoService: VideoService) { | ||
19 | super() | ||
20 | } | ||
21 | |||
22 | ngOnInit () { | ||
23 | super.ngOnInit() | ||
24 | } | ||
25 | |||
26 | ngOnDestroy () { | ||
27 | super.ngOnDestroy() | ||
28 | } | ||
29 | |||
30 | getVideosObservable () { | ||
31 | return this.videoService.getVideos(this.pagination, this.sort) | ||
32 | } | ||
33 | } | ||
diff --git a/client/src/app/videos/video-list/video-trending.component.ts b/client/src/app/videos/video-list/video-trending.component.ts new file mode 100644 index 000000000..b97966c12 --- /dev/null +++ b/client/src/app/videos/video-list/video-trending.component.ts | |||
@@ -0,0 +1,33 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | ||
2 | import { ActivatedRoute, Router } from '@angular/router' | ||
3 | import { NotificationsService } from 'angular2-notifications' | ||
4 | import { VideoService } from '../shared' | ||
5 | import { AbstractVideoList } from './shared' | ||
6 | |||
7 | @Component({ | ||
8 | selector: 'my-videos-trending', | ||
9 | styleUrls: [ './shared/abstract-video-list.scss' ], | ||
10 | templateUrl: './shared/abstract-video-list.html' | ||
11 | }) | ||
12 | export class VideoTrendingComponent extends AbstractVideoList implements OnInit, OnDestroy { | ||
13 | titlePage = 'Trending' | ||
14 | |||
15 | constructor (protected router: Router, | ||
16 | protected route: ActivatedRoute, | ||
17 | protected notificationsService: NotificationsService, | ||
18 | private videoService: VideoService) { | ||
19 | super() | ||
20 | } | ||
21 | |||
22 | ngOnInit () { | ||
23 | super.ngOnInit() | ||
24 | } | ||
25 | |||
26 | ngOnDestroy () { | ||
27 | super.ngOnDestroy() | ||
28 | } | ||
29 | |||
30 | getVideosObservable () { | ||
31 | return this.videoService.getVideos(this.pagination, this.sort) | ||
32 | } | ||
33 | } | ||
diff --git a/client/src/app/videos/videos-routing.module.ts b/client/src/app/videos/videos-routing.module.ts index 3ca3e5486..1f894df7a 100644 --- a/client/src/app/videos/videos-routing.module.ts +++ b/client/src/app/videos/videos-routing.module.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | import { RouterModule, Routes } from '@angular/router' | 2 | import { RouterModule, Routes } from '@angular/router' |
3 | |||
4 | import { MetaGuard } from '@ngx-meta/core' | 3 | import { MetaGuard } from '@ngx-meta/core' |
5 | 4 | import { MyVideosComponent } from './video-list' | |
6 | import { VideoListComponent, MyVideosComponent } from './video-list' | 5 | import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' |
6 | import { VideoTrendingComponent } from './video-list/video-trending.component' | ||
7 | import { VideosComponent } from './videos.component' | 7 | import { VideosComponent } from './videos.component' |
8 | 8 | ||
9 | const videosRoutes: Routes = [ | 9 | const videosRoutes: Routes = [ |
@@ -13,6 +13,11 @@ const videosRoutes: Routes = [ | |||
13 | canActivateChild: [ MetaGuard ], | 13 | canActivateChild: [ MetaGuard ], |
14 | children: [ | 14 | children: [ |
15 | { | 15 | { |
16 | path: 'list', | ||
17 | pathMatch: 'full', | ||
18 | redirectTo: 'recently-added' | ||
19 | }, | ||
20 | { | ||
16 | path: 'mine', | 21 | path: 'mine', |
17 | component: MyVideosComponent, | 22 | component: MyVideosComponent, |
18 | data: { | 23 | data: { |
@@ -22,11 +27,20 @@ const videosRoutes: Routes = [ | |||
22 | } | 27 | } |
23 | }, | 28 | }, |
24 | { | 29 | { |
25 | path: 'list', | 30 | path: 'trending', |
26 | component: VideoListComponent, | 31 | component: VideoTrendingComponent, |
32 | data: { | ||
33 | meta: { | ||
34 | title: 'Trending videos' | ||
35 | } | ||
36 | } | ||
37 | }, | ||
38 | { | ||
39 | path: 'recently-added', | ||
40 | component: VideoRecentlyAddedComponent, | ||
27 | data: { | 41 | data: { |
28 | meta: { | 42 | meta: { |
29 | title: 'Videos list' | 43 | title: 'Recently added videos' |
30 | } | 44 | } |
31 | } | 45 | } |
32 | }, | 46 | }, |
diff --git a/client/src/app/videos/videos.module.ts b/client/src/app/videos/videos.module.ts index 4f3054c3a..93193000c 100644 --- a/client/src/app/videos/videos.module.ts +++ b/client/src/app/videos/videos.module.ts | |||
@@ -1,7 +1,9 @@ | |||
1 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | import { SharedModule } from '../shared' | 2 | import { SharedModule } from '../shared' |
3 | import { VideoService } from './shared' | 3 | import { VideoService } from './shared' |
4 | import { MyVideosComponent, VideoListComponent, VideoMiniatureComponent, VideoSortComponent } from './video-list' | 4 | import { MyVideosComponent, VideoMiniatureComponent } from './video-list' |
5 | import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' | ||
6 | import { VideoTrendingComponent } from './video-list/video-trending.component' | ||
5 | import { VideosRoutingModule } from './videos-routing.module' | 7 | import { VideosRoutingModule } from './videos-routing.module' |
6 | import { VideosComponent } from './videos.component' | 8 | import { VideosComponent } from './videos.component' |
7 | 9 | ||
@@ -14,10 +16,10 @@ import { VideosComponent } from './videos.component' | |||
14 | declarations: [ | 16 | declarations: [ |
15 | VideosComponent, | 17 | VideosComponent, |
16 | 18 | ||
17 | VideoListComponent, | 19 | VideoTrendingComponent, |
20 | VideoRecentlyAddedComponent, | ||
18 | MyVideosComponent, | 21 | MyVideosComponent, |
19 | VideoMiniatureComponent, | 22 | VideoMiniatureComponent |
20 | VideoSortComponent | ||
21 | ], | 23 | ], |
22 | 24 | ||
23 | exports: [ | 25 | exports: [ |
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss index 58f07612b..fc61a22da 100644 --- a/client/src/sass/application.scss +++ b/client/src/sass/application.scss | |||
@@ -33,24 +33,14 @@ input.readonly { | |||
33 | } | 33 | } |
34 | 34 | ||
35 | .main-col { | 35 | .main-col { |
36 | .content-padding { | 36 | padding: 30px; |
37 | padding: 15px 30px; | 37 | |
38 | 38 | .title-page { | |
39 | @media screen and (max-width: 800px) { | 39 | font-size: 16px; |
40 | padding: 15px 10px; | 40 | font-weight: $font-bold; |
41 | } | 41 | display: inline-block; |
42 | 42 | border-bottom: 2px solid $orange-color; | |
43 | @media screen and (min-width: 1400px) { | 43 | margin-bottom: 25px; |
44 | padding: 15px 40px; | ||
45 | } | ||
46 | |||
47 | @media screen and (min-width: 1600px) { | ||
48 | padding: 15px 50px; | ||
49 | } | ||
50 | |||
51 | @media screen and (min-width: 1800px) { | ||
52 | padding: 15px 60px; | ||
53 | } | ||
54 | } | 44 | } |
55 | } | 45 | } |
56 | 46 | ||
diff --git a/client/yarn.lock b/client/yarn.lock index 8f148e431..fa1802a29 100644 --- a/client/yarn.lock +++ b/client/yarn.lock | |||
@@ -264,10 +264,6 @@ amdefine@>=0.0.4: | |||
264 | version "1.0.1" | 264 | version "1.0.1" |
265 | resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" | 265 | resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" |
266 | 266 | ||
267 | angular-pipes@^6.0.0: | ||
268 | version "6.5.3" | ||
269 | resolved "https://registry.yarnpkg.com/angular-pipes/-/angular-pipes-6.5.3.tgz#6bed37c51ebc2adaf3412663bfe25179d0489b02" | ||
270 | |||
271 | angular2-notifications@^0.7.7: | 267 | angular2-notifications@^0.7.7: |
272 | version "0.7.8" | 268 | version "0.7.8" |
273 | resolved "https://registry.yarnpkg.com/angular2-notifications/-/angular2-notifications-0.7.8.tgz#ecbcb95a8d2d402af94a9a080d6664c70d33a029" | 269 | resolved "https://registry.yarnpkg.com/angular2-notifications/-/angular2-notifications-0.7.8.tgz#ecbcb95a8d2d402af94a9a080d6664c70d33a029" |
@@ -4718,6 +4714,10 @@ ngx-chips@1.5.3: | |||
4718 | dependencies: | 4714 | dependencies: |
4719 | ng2-material-dropdown "0.7.10" | 4715 | ng2-material-dropdown "0.7.10" |
4720 | 4716 | ||
4717 | ngx-pipes@^2.0.5: | ||
4718 | version "2.0.5" | ||
4719 | resolved "https://registry.yarnpkg.com/ngx-pipes/-/ngx-pipes-2.0.5.tgz#743b827e350b1e66f5bdae49e90a02fa631d4c54" | ||
4720 | |||
4721 | no-case@^2.2.0: | 4721 | no-case@^2.2.0: |
4722 | version "2.3.2" | 4722 | version "2.3.2" |
4723 | resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" | 4723 | resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" |