aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-08-30 14:58:00 +0200
committerChocobozzz <me@florianbigard.com>2018-08-31 09:19:58 +0200
commit2d3741d6d92e9bd1f41694c7442a6d1da434e1f2 (patch)
tree93a1e609e14bc14ca9e77a6661ddc9c0e461d6f3 /client/src
parentd9eaee3939bf2e93e5d775d32bce77842201faba (diff)
downloadPeerTube-2d3741d6d92e9bd1f41694c7442a6d1da434e1f2.tar.gz
PeerTube-2d3741d6d92e9bd1f41694c7442a6d1da434e1f2.tar.zst
PeerTube-2d3741d6d92e9bd1f41694c7442a6d1da434e1f2.zip
Videos overview page: first version
Diffstat (limited to 'client/src')
-rw-r--r--client/src/app/+video-channels/video-channels.component.html2
-rw-r--r--client/src/app/+video-channels/video-channels.component.ts6
-rw-r--r--client/src/app/menu/menu.component.html5
-rw-r--r--client/src/app/menu/menu.component.scss5
-rw-r--r--client/src/app/search/search.component.html2
-rw-r--r--client/src/app/search/search.component.scss9
-rw-r--r--client/src/app/shared/overview/index.ts1
-rw-r--r--client/src/app/shared/overview/overview.service.ts76
-rw-r--r--client/src/app/shared/overview/videos-overview.model.ts19
-rw-r--r--client/src/app/shared/shared.module.ts2
-rw-r--r--client/src/app/shared/video/abstract-video-list.html8
-rw-r--r--client/src/app/shared/video/video.service.ts8
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.html28
-rw-r--r--client/src/app/videos/video-list/video-overview.component.html35
-rw-r--r--client/src/app/videos/video-list/video-overview.component.scss22
-rw-r--r--client/src/app/videos/video-list/video-overview.component.ts56
-rw-r--r--client/src/app/videos/videos-routing.module.ts10
-rw-r--r--client/src/app/videos/videos.module.ts4
-rw-r--r--client/src/assets/images/menu/globe.svg18
-rw-r--r--client/src/sass/application.scss9
20 files changed, 285 insertions, 40 deletions
diff --git a/client/src/app/+video-channels/video-channels.component.html b/client/src/app/+video-channels/video-channels.component.html
index 1941a2eab..e5a32dc92 100644
--- a/client/src/app/+video-channels/video-channels.component.html
+++ b/client/src/app/+video-channels/video-channels.component.html
@@ -9,7 +9,7 @@
9 <div class="actor-display-name">{{ videoChannel.displayName }}</div> 9 <div class="actor-display-name">{{ videoChannel.displayName }}</div>
10 <div class="actor-name">{{ videoChannel.nameWithHost }}</div> 10 <div class="actor-name">{{ videoChannel.nameWithHost }}</div>
11 11
12 <my-subscribe-button [videoChannel]="videoChannel"></my-subscribe-button> 12 <my-subscribe-button *ngIf="isUserLoggedIn()" [videoChannel]="videoChannel"></my-subscribe-button>
13 </div> 13 </div>
14 <div i18n class="actor-followers">{{ videoChannel.followersCount }} subscribers</div> 14 <div i18n class="actor-followers">{{ videoChannel.followersCount }} subscribers</div>
15 15
diff --git a/client/src/app/+video-channels/video-channels.component.ts b/client/src/app/+video-channels/video-channels.component.ts
index 57c55d286..ee2c86915 100644
--- a/client/src/app/+video-channels/video-channels.component.ts
+++ b/client/src/app/+video-channels/video-channels.component.ts
@@ -5,6 +5,7 @@ import { VideoChannelService } from '@app/shared/video-channel/video-channel.ser
5import { RestExtractor } from '@app/shared' 5import { RestExtractor } from '@app/shared'
6import { catchError, distinctUntilChanged, map, switchMap } from 'rxjs/operators' 6import { catchError, distinctUntilChanged, map, switchMap } from 'rxjs/operators'
7import { Subscription } from 'rxjs' 7import { Subscription } from 'rxjs'
8import { AuthService } from '@app/core'
8 9
9@Component({ 10@Component({
10 templateUrl: './video-channels.component.html', 11 templateUrl: './video-channels.component.html',
@@ -17,6 +18,7 @@ export class VideoChannelsComponent implements OnInit, OnDestroy {
17 18
18 constructor ( 19 constructor (
19 private route: ActivatedRoute, 20 private route: ActivatedRoute,
21 private authService: AuthService,
20 private videoChannelService: VideoChannelService, 22 private videoChannelService: VideoChannelService,
21 private restExtractor: RestExtractor 23 private restExtractor: RestExtractor
22 ) { } 24 ) { }
@@ -36,4 +38,8 @@ export class VideoChannelsComponent implements OnInit, OnDestroy {
36 ngOnDestroy () { 38 ngOnDestroy () {
37 if (this.routeSub) this.routeSub.unsubscribe() 39 if (this.routeSub) this.routeSub.unsubscribe()
38 } 40 }
41
42 isUserLoggedIn () {
43 return this.authService.isLoggedIn()
44 }
39} 45}
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html
index bd03af9b3..8fe6797d6 100644
--- a/client/src/app/menu/menu.component.html
+++ b/client/src/app/menu/menu.component.html
@@ -47,6 +47,11 @@
47 <ng-container i18n>Subscriptions</ng-container> 47 <ng-container i18n>Subscriptions</ng-container>
48 </a> 48 </a>
49 49
50 <a routerLink="/videos/overview" routerLinkActive="active">
51 <span class="icon icon-videos-overview"></span>
52 <ng-container i18n>Overview</ng-container>
53 </a>
54
50 <a routerLink="/videos/trending" routerLinkActive="active"> 55 <a routerLink="/videos/trending" routerLinkActive="active">
51 <span class="icon icon-videos-trending"></span> 56 <span class="icon icon-videos-trending"></span>
52 <ng-container i18n>Trending</ng-container> 57 <ng-container i18n>Trending</ng-container>
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss
index 606fea961..8539c0e56 100644
--- a/client/src/app/menu/menu.component.scss
+++ b/client/src/app/menu/menu.component.scss
@@ -141,6 +141,11 @@ menu {
141 background-image: url('../../assets/images/menu/subscriptions.svg'); 141 background-image: url('../../assets/images/menu/subscriptions.svg');
142 } 142 }
143 143
144 &.icon-videos-overview {
145 position: relative;
146 background-image: url('../../assets/images/menu/globe.svg');
147 }
148
144 &.icon-videos-trending { 149 &.icon-videos-trending {
145 position: relative; 150 position: relative;
146 top: -2px; 151 top: -2px;
diff --git a/client/src/app/search/search.component.html b/client/src/app/search/search.component.html
index d2ed1f881..b35a46ec9 100644
--- a/client/src/app/search/search.component.html
+++ b/client/src/app/search/search.component.html
@@ -22,7 +22,7 @@
22 </div> 22 </div>
23 </div> 23 </div>
24 24
25 <div i18n *ngIf="pagination.totalItems === 0 && results.length === 0" class="no-result"> 25 <div i18n *ngIf="pagination.totalItems === 0 && results.length === 0" class="no-results">
26 No results found 26 No results found
27 </div> 27 </div>
28 28
diff --git a/client/src/app/search/search.component.scss b/client/src/app/search/search.component.scss
index e5dfddcc5..f394099e2 100644
--- a/client/src/app/search/search.component.scss
+++ b/client/src/app/search/search.component.scss
@@ -1,15 +1,6 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3 3
4.no-result {
5 height: 40vh;
6 display: flex;
7 align-items: center;
8 justify-content: center;
9 font-size: 16px;
10 font-weight: $font-semibold;
11}
12
13.search-result { 4.search-result {
14 padding: 40px; 5 padding: 40px;
15 6
diff --git a/client/src/app/shared/overview/index.ts b/client/src/app/shared/overview/index.ts
new file mode 100644
index 000000000..2f7e41298
--- /dev/null
+++ b/client/src/app/shared/overview/index.ts
@@ -0,0 +1 @@
export * from './overview.service'
diff --git a/client/src/app/shared/overview/overview.service.ts b/client/src/app/shared/overview/overview.service.ts
new file mode 100644
index 000000000..4a4714af6
--- /dev/null
+++ b/client/src/app/shared/overview/overview.service.ts
@@ -0,0 +1,76 @@
1import { catchError, map, switchMap, tap } from 'rxjs/operators'
2import { HttpClient } from '@angular/common/http'
3import { Injectable } from '@angular/core'
4import { forkJoin, Observable, of } from 'rxjs'
5import { VideosOverview as VideosOverviewServer, peertubeTranslate } from '../../../../../shared/models'
6import { environment } from '../../../environments/environment'
7import { RestExtractor } from '../rest/rest-extractor.service'
8import { RestService } from '../rest/rest.service'
9import { VideosOverview } from '@app/shared/overview/videos-overview.model'
10import { VideoService } from '@app/shared/video/video.service'
11import { ServerService } from '@app/core'
12import { immutableAssign } from '@app/shared/misc/utils'
13
14@Injectable()
15export class OverviewService {
16 static BASE_OVERVIEW_URL = environment.apiUrl + '/api/v1/overviews/'
17
18 constructor (
19 private authHttp: HttpClient,
20 private restExtractor: RestExtractor,
21 private restService: RestService,
22 private videosService: VideoService,
23 private serverService: ServerService
24 ) {}
25
26 getVideosOverview (): Observable<VideosOverview> {
27 return this.authHttp
28 .get<VideosOverviewServer>(OverviewService.BASE_OVERVIEW_URL + 'videos')
29 .pipe(
30 switchMap(serverVideosOverview => this.updateVideosOverview(serverVideosOverview)),
31 catchError(err => this.restExtractor.handleError(err))
32 )
33 }
34
35 private updateVideosOverview (serverVideosOverview: VideosOverviewServer): Observable<VideosOverview> {
36 const observables: Observable<any>[] = []
37 const videosOverviewResult: VideosOverview = {
38 tags: [],
39 categories: [],
40 channels: []
41 }
42
43 // Build videos objects
44 for (const key of Object.keys(serverVideosOverview)) {
45 for (const object of serverVideosOverview[ key ]) {
46 observables.push(
47 of(object.videos)
48 .pipe(
49 switchMap(videos => this.videosService.extractVideos({ total: 0, data: videos })),
50 map(result => result.videos),
51 tap(videos => {
52 videosOverviewResult[key].push(immutableAssign(object, { videos }))
53 })
54 )
55 )
56 }
57 }
58
59 return forkJoin(observables)
60 .pipe(
61 // Translate categories
62 switchMap(() => {
63 return this.serverService.localeObservable
64 .pipe(
65 tap(translations => {
66 for (const c of videosOverviewResult.categories) {
67 c.category.label = peertubeTranslate(c.category.label, translations)
68 }
69 })
70 )
71 }),
72 map(() => videosOverviewResult)
73 )
74 }
75
76}
diff --git a/client/src/app/shared/overview/videos-overview.model.ts b/client/src/app/shared/overview/videos-overview.model.ts
new file mode 100644
index 000000000..cf02bdb3d
--- /dev/null
+++ b/client/src/app/shared/overview/videos-overview.model.ts
@@ -0,0 +1,19 @@
1import { VideoChannelAttribute, VideoConstant, VideosOverview as VideosOverviewServer } from '../../../../../shared/models'
2import { Video } from '@app/shared/video/video.model'
3
4export class VideosOverview implements VideosOverviewServer {
5 channels: {
6 channel: VideoChannelAttribute
7 videos: Video[]
8 }[]
9
10 categories: {
11 category: VideoConstant<number>
12 videos: Video[]
13 }[]
14
15 tags: {
16 tag: string
17 videos: Video[]
18 }[]
19}
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 2cbaaf4ae..b96a9aa41 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -52,6 +52,7 @@ import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.com
52import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' 52import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
53import { SubscribeButtonComponent, UserSubscriptionService } from '@app/shared/user-subscription' 53import { SubscribeButtonComponent, UserSubscriptionService } from '@app/shared/user-subscription'
54import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-features-table.component' 54import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-features-table.component'
55import { OverviewService } from '@app/shared/overview'
55 56
56@NgModule({ 57@NgModule({
57 imports: [ 58 imports: [
@@ -154,6 +155,7 @@ import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-fe
154 VideoValidatorsService, 155 VideoValidatorsService,
155 VideoCaptionsValidatorsService, 156 VideoCaptionsValidatorsService,
156 VideoBlacklistValidatorsService, 157 VideoBlacklistValidatorsService,
158 OverviewService,
157 159
158 I18nPrimengCalendarService, 160 I18nPrimengCalendarService,
159 ScreenService, 161 ScreenService,
diff --git a/client/src/app/shared/video/abstract-video-list.html b/client/src/app/shared/video/abstract-video-list.html
index d4b00c07c..0f48b9a64 100644
--- a/client/src/app/shared/video/abstract-video-list.html
+++ b/client/src/app/shared/video/abstract-video-list.html
@@ -4,7 +4,7 @@
4 </div> 4 </div>
5 <my-video-feed [syndicationItems]="syndicationItems"></my-video-feed> 5 <my-video-feed [syndicationItems]="syndicationItems"></my-video-feed>
6 6
7 <div i18n *ngIf="pagination.totalItems === 0">No results.</div> 7 <div class="no-results" i18n *ngIf="pagination.totalItems === 0">No results.</div>
8 <div 8 <div
9 myInfiniteScroller 9 myInfiniteScroller
10 [pageHeight]="pageHeight" 10 [pageHeight]="pageHeight"
@@ -12,11 +12,7 @@
12 class="videos" #videosElement 12 class="videos" #videosElement
13 > 13 >
14 <div *ngFor="let videos of videoPages" class="videos-page"> 14 <div *ngFor="let videos of videoPages" class="videos-page">
15 <my-video-miniature 15 <my-video-miniature *ngFor="let video of videos" [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType"></my-video-miniature>
16 class="ng-animate"
17 *ngFor="let video of videos" [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType"
18 >
19 </my-video-miniature>
20 </div> 16 </div>
21 </div> 17 </div>
22</div> 18</div>
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts
index 558db9543..7cc98c77a 100644
--- a/client/src/app/shared/video/video.service.ts
+++ b/client/src/app/shared/video/video.service.ts
@@ -51,14 +51,6 @@ export class VideoService {
51 ) 51 )
52 } 52 }
53 53
54 viewVideo (uuid: string): Observable<boolean> {
55 return this.authHttp.post(this.getVideoViewUrl(uuid), {})
56 .pipe(
57 map(this.restExtractor.extractDataBool),
58 catchError(err => this.restExtractor.handleError(err))
59 )
60 }
61
62 updateVideo (video: VideoEdit) { 54 updateVideo (video: VideoEdit) {
63 const language = video.language || null 55 const language = video.language || null
64 const licence = video.licence || null 56 const licence = video.licence || null
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html
index 333c9d11b..2c8305777 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -38,7 +38,7 @@
38 Published {{ video.publishedAt | myFromNow }} - {{ video.views | myNumberFormatter }} views 38 Published {{ video.publishedAt | myFromNow }} - {{ video.views | myNumberFormatter }} views
39 </div> 39 </div>
40 </div> 40 </div>
41 41
42 <div class="d-flex justify-content-between align-items-sm-end"> 42 <div class="d-flex justify-content-between align-items-sm-end">
43 <div class="d-none d-sm-block"> 43 <div class="d-none d-sm-block">
44 <div class="video-info-name">{{ video.name }}</div> 44 <div class="video-info-name">{{ video.name }}</div>
@@ -46,7 +46,7 @@
46 <div i18n class="video-info-date-views"> 46 <div i18n class="video-info-date-views">
47 Published {{ video.publishedAt | myFromNow }} - {{ video.views | myNumberFormatter }} views 47 Published {{ video.publishedAt | myFromNow }} - {{ video.views | myNumberFormatter }} views
48 </div> 48 </div>
49 </div> 49 </div>
50 50
51 <div class="video-actions-rates"> 51 <div class="video-actions-rates">
52 <div class="video-actions fullWidth justify-content-end"> 52 <div class="video-actions fullWidth justify-content-end">
@@ -56,57 +56,57 @@
56 > 56 >
57 <span class="icon icon-like" i18n-title title="Like this video" ></span> 57 <span class="icon icon-like" i18n-title title="Like this video" ></span>
58 </div> 58 </div>
59 59
60 <div 60 <div
61 *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()" 61 *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()"
62 class="action-button action-button-dislike" role="button" [attr.aria-pressed]="userRating === 'dislike'" 62 class="action-button action-button-dislike" role="button" [attr.aria-pressed]="userRating === 'dislike'"
63 > 63 >
64 <span class="icon icon-dislike" i18n-title title="Dislike this video"></span> 64 <span class="icon icon-dislike" i18n-title title="Dislike this video"></span>
65 </div> 65 </div>
66 66
67 <div *ngIf="video.support" (click)="showSupportModal()" class="action-button action-button-support"> 67 <div *ngIf="video.support" (click)="showSupportModal()" class="action-button action-button-support">
68 <span class="icon icon-support"></span> 68 <span class="icon icon-support"></span>
69 <span class="icon-text" i18n>Support</span> 69 <span class="icon-text" i18n>Support</span>
70 </div> 70 </div>
71 71
72 <div (click)="showShareModal()" class="action-button action-button-share" role="button"> 72 <div (click)="showShareModal()" class="action-button action-button-share" role="button">
73 <span class="icon icon-share"></span> 73 <span class="icon icon-share"></span>
74 <span class="icon-text" i18n>Share</span> 74 <span class="icon-text" i18n>Share</span>
75 </div> 75 </div>
76 76
77 <div class="action-more" ngbDropdown placement="top" role="button"> 77 <div class="action-more" ngbDropdown placement="top" role="button">
78 <div class="action-button" ngbDropdownToggle role="button"> 78 <div class="action-button" ngbDropdownToggle role="button">
79 <span class="icon icon-more"></span> 79 <span class="icon icon-more"></span>
80 </div> 80 </div>
81 81
82 <div ngbDropdownMenu> 82 <div ngbDropdownMenu>
83 <a class="dropdown-item" i18n-title title="Download the video" href="#" (click)="showDownloadModal($event)"> 83 <a class="dropdown-item" i18n-title title="Download the video" href="#" (click)="showDownloadModal($event)">
84 <span class="icon icon-download"></span> <ng-container i18n>Download</ng-container> 84 <span class="icon icon-download"></span> <ng-container i18n>Download</ng-container>
85 </a> 85 </a>
86 86
87 <a *ngIf="isUserLoggedIn()" class="dropdown-item" i18n-title title="Report this video" href="#" (click)="showReportModal($event)"> 87 <a *ngIf="isUserLoggedIn()" class="dropdown-item" i18n-title title="Report this video" href="#" (click)="showReportModal($event)">
88 <span class="icon icon-alert"></span> <ng-container i18n>Report</ng-container> 88 <span class="icon icon-alert"></span> <ng-container i18n>Report</ng-container>
89 </a> 89 </a>
90 90
91 <a *ngIf="isVideoUpdatable()" class="dropdown-item" i18n-title title="Update this video" href="#" [routerLink]="[ '/videos/update', video.uuid ]"> 91 <a *ngIf="isVideoUpdatable()" class="dropdown-item" i18n-title title="Update this video" href="#" [routerLink]="[ '/videos/update', video.uuid ]">
92 <span class="icon icon-edit"></span> <ng-container i18n>Update</ng-container> 92 <span class="icon icon-edit"></span> <ng-container i18n>Update</ng-container>
93 </a> 93 </a>
94 94
95 <a *ngIf="isVideoBlacklistable()" class="dropdown-item" i18n-title title="Blacklist this video" href="#" (click)="showBlacklistModal($event)"> 95 <a *ngIf="isVideoBlacklistable()" class="dropdown-item" i18n-title title="Blacklist this video" href="#" (click)="showBlacklistModal($event)">
96 <span class="icon icon-blacklist"></span> <ng-container i18n>Blacklist</ng-container> 96 <span class="icon icon-blacklist"></span> <ng-container i18n>Blacklist</ng-container>
97 </a> 97 </a>
98 98
99 <a *ngIf="isVideoUnblacklistable()" class="dropdown-item" i18n-title title="Unblacklist this video" href="#" (click)="unblacklistVideo($event)"> 99 <a *ngIf="isVideoUnblacklistable()" class="dropdown-item" i18n-title title="Unblacklist this video" href="#" (click)="unblacklistVideo($event)">
100 <span class="icon icon-unblacklist"></span> <ng-container i18n>Unblacklist</ng-container> 100 <span class="icon icon-unblacklist"></span> <ng-container i18n>Unblacklist</ng-container>
101 </a> 101 </a>
102 102
103 <a *ngIf="isVideoRemovable()" class="dropdown-item" i18n-title title="Delete this video" href="#" (click)="removeVideo($event)"> 103 <a *ngIf="isVideoRemovable()" class="dropdown-item" i18n-title title="Delete this video" href="#" (click)="removeVideo($event)">
104 <span class="icon icon-delete"></span> <ng-container i18n>Delete</ng-container> 104 <span class="icon icon-delete"></span> <ng-container i18n>Delete</ng-container>
105 </a> 105 </a>
106 </div> 106 </div>
107 </div> 107 </div>
108 </div> 108 </div>
109 109
110 <div 110 <div
111 class="video-info-likes-dislikes-bar" 111 class="video-info-likes-dislikes-bar"
112 *ngIf="video.likes !== 0 || video.dislikes !== 0" 112 *ngIf="video.likes !== 0 || video.dislikes !== 0"
@@ -125,7 +125,7 @@
125 <img [src]="video.videoChannelAvatarUrl" alt="Video channel avatar" /> 125 <img [src]="video.videoChannelAvatarUrl" alt="Video channel avatar" />
126 </a> 126 </a>
127 127
128 <my-subscribe-button [videoChannel]="video.channel" size="small"></my-subscribe-button> 128 <my-subscribe-button *ngIf="isUserLoggedIn()" [videoChannel]="video.channel" size="small"></my-subscribe-button>
129 </div> 129 </div>
130 130
131 <div class="video-info-by"> 131 <div class="video-info-by">
diff --git a/client/src/app/videos/video-list/video-overview.component.html b/client/src/app/videos/video-list/video-overview.component.html
new file mode 100644
index 000000000..9282dd59c
--- /dev/null
+++ b/client/src/app/videos/video-list/video-overview.component.html
@@ -0,0 +1,35 @@
1<div class="margin-content">
2
3 <div class="no-results" i18n *ngIf="notResults">No results.</div>
4
5 <div class="section" *ngFor="let object of overview.categories">
6 <div class="section-title" i18n>
7 <a routerLink="/search" [queryParams]="{ categoryOneOf: [ object.category.id ] }">Category {{ object.category.label }}</a>
8 </div>
9
10 <div>
11 <my-video-miniature *ngFor="let video of object.videos" [video]="video" [user]="user"></my-video-miniature>
12 </div>
13 </div>
14
15 <div class="section" *ngFor="let object of overview.tags">
16 <div class="section-title" i18n>
17 <a routerLink="/search" [queryParams]="{ categoryOneOf: [ object.category.id ] }">Tag {{ object.tag }}</a>
18 </div>
19
20 <div>
21 <my-video-miniature *ngFor="let video of object.videos" [video]="video" [user]="user"></my-video-miniature>
22 </div>
23 </div>
24
25 <div class="section" *ngFor="let object of overview.channels">
26 <div class="section-title" i18n>
27 <a [routerLink]="[ '/video-channels', buildVideoChannelBy(object) ]">Channel {{ object.channel.displayName }}</a>
28 </div>
29
30 <div>
31 <my-video-miniature *ngFor="let video of object.videos" [video]="video" [user]="user"></my-video-miniature>
32 </div>
33 </div>
34
35</div>
diff --git a/client/src/app/videos/video-list/video-overview.component.scss b/client/src/app/videos/video-list/video-overview.component.scss
new file mode 100644
index 000000000..8d66cf80a
--- /dev/null
+++ b/client/src/app/videos/video-list/video-overview.component.scss
@@ -0,0 +1,22 @@
1@import '_variables';
2@import '_mixins';
3
4.section {
5 padding-top: 10px;
6
7 &:first-child {
8 padding-top: 30px;
9 }
10}
11
12.section-title {
13 font-size: 17px;
14 font-weight: $font-semibold;
15 margin-bottom: 20px;
16
17 a {
18 @include disable-default-a-behaviour;
19
20 color: #000;
21 }
22} \ No newline at end of file
diff --git a/client/src/app/videos/video-list/video-overview.component.ts b/client/src/app/videos/video-list/video-overview.component.ts
new file mode 100644
index 000000000..c758e115c
--- /dev/null
+++ b/client/src/app/videos/video-list/video-overview.component.ts
@@ -0,0 +1,56 @@
1import { Component, OnInit } from '@angular/core'
2import { AuthService } from '@app/core'
3import { NotificationsService } from 'angular2-notifications'
4import { I18n } from '@ngx-translate/i18n-polyfill'
5import { VideosOverview } from '@app/shared/overview/videos-overview.model'
6import { OverviewService } from '@app/shared/overview'
7import { Video } from '@app/shared/video/video.model'
8
9@Component({
10 selector: 'my-video-overview',
11 templateUrl: './video-overview.component.html',
12 styleUrls: [ './video-overview.component.scss' ]
13})
14export class VideoOverviewComponent implements OnInit {
15 overview: VideosOverview = {
16 categories: [],
17 channels: [],
18 tags: []
19 }
20 notResults = false
21
22 constructor (
23 private i18n: I18n,
24 private notificationsService: NotificationsService,
25 private authService: AuthService,
26 private overviewService: OverviewService
27 ) { }
28
29 get user () {
30 return this.authService.getUser()
31 }
32
33 ngOnInit () {
34 this.overviewService.getVideosOverview()
35 .subscribe(
36 overview => {
37 this.overview = overview
38
39 if (
40 this.overview.categories.length === 0 &&
41 this.overview.channels.length === 0 &&
42 this.overview.tags.length === 0
43 ) this.notResults = true
44 },
45
46 err => {
47 console.log(err)
48 this.notificationsService.error('Error', err.text)
49 }
50 )
51 }
52
53 buildVideoChannelBy (object: { videos: Video[] }) {
54 return object.videos[0].byVideoChannel
55 }
56}
diff --git a/client/src/app/videos/videos-routing.module.ts b/client/src/app/videos/videos-routing.module.ts
index 18ed52570..58988ffd1 100644
--- a/client/src/app/videos/videos-routing.module.ts
+++ b/client/src/app/videos/videos-routing.module.ts
@@ -6,6 +6,7 @@ import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.c
6import { VideoTrendingComponent } from './video-list/video-trending.component' 6import { VideoTrendingComponent } from './video-list/video-trending.component'
7import { VideosComponent } from './videos.component' 7import { VideosComponent } from './videos.component'
8import { VideoUserSubscriptionsComponent } from '@app/videos/video-list/video-user-subscriptions.component' 8import { VideoUserSubscriptionsComponent } from '@app/videos/video-list/video-user-subscriptions.component'
9import { VideoOverviewComponent } from '@app/videos/video-list/video-overview.component'
9 10
10const videosRoutes: Routes = [ 11const videosRoutes: Routes = [
11 { 12 {
@@ -14,6 +15,15 @@ const videosRoutes: Routes = [
14 canActivateChild: [ MetaGuard ], 15 canActivateChild: [ MetaGuard ],
15 children: [ 16 children: [
16 { 17 {
18 path: 'overview',
19 component: VideoOverviewComponent,
20 data: {
21 meta: {
22 title: 'Videos overview'
23 }
24 }
25 },
26 {
17 path: 'trending', 27 path: 'trending',
18 component: VideoTrendingComponent, 28 component: VideoTrendingComponent,
19 data: { 29 data: {
diff --git a/client/src/app/videos/videos.module.ts b/client/src/app/videos/videos.module.ts
index 3c3877273..5cf1e944f 100644
--- a/client/src/app/videos/videos.module.ts
+++ b/client/src/app/videos/videos.module.ts
@@ -6,6 +6,7 @@ import { VideoTrendingComponent } from './video-list/video-trending.component'
6import { VideosRoutingModule } from './videos-routing.module' 6import { VideosRoutingModule } from './videos-routing.module'
7import { VideosComponent } from './videos.component' 7import { VideosComponent } from './videos.component'
8import { VideoUserSubscriptionsComponent } from '@app/videos/video-list/video-user-subscriptions.component' 8import { VideoUserSubscriptionsComponent } from '@app/videos/video-list/video-user-subscriptions.component'
9import { VideoOverviewComponent } from '@app/videos/video-list/video-overview.component'
9 10
10@NgModule({ 11@NgModule({
11 imports: [ 12 imports: [
@@ -19,7 +20,8 @@ import { VideoUserSubscriptionsComponent } from '@app/videos/video-list/video-us
19 VideoTrendingComponent, 20 VideoTrendingComponent,
20 VideoRecentlyAddedComponent, 21 VideoRecentlyAddedComponent,
21 VideoLocalComponent, 22 VideoLocalComponent,
22 VideoUserSubscriptionsComponent 23 VideoUserSubscriptionsComponent,
24 VideoOverviewComponent
23 ], 25 ],
24 26
25 exports: [ 27 exports: [
diff --git a/client/src/assets/images/menu/globe.svg b/client/src/assets/images/menu/globe.svg
new file mode 100644
index 000000000..a4b3db9c5
--- /dev/null
+++ b/client/src/assets/images/menu/globe.svg
@@ -0,0 +1,18 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3 <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
4 <title>globe</title>
5 <desc>Created with Sketch.</desc>
6 <defs></defs>
7 <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
8 <g id="Artboard-4" transform="translate(-224.000000, -687.000000)" stroke="#808080" stroke-width="2">
9 <g id="265" transform="translate(224.000000, 687.000000)">
10 <circle id="Oval-148" cx="12" cy="12" r="10"></circle>
11 <path d="M12,2 L12,22.006249" id="Path-199"></path>
12 <path d="M12,2 C12,2 17,4 17,12.0031245 C17,20.006249 12,22.006249 12,22.006249" id="Path-199"></path>
13 <path d="M7,2 C7,2 12,4 12,12.0031245 C12,20.006249 7,22.006249 7,22.006249" id="Path-199" transform="translate(9.500000, 12.003125) scale(-1, 1) translate(-9.500000, -12.003125) "></path>
14 <path d="M2,12 L22,12" id="Path-201"></path>
15 </g>
16 </g>
17 </g>
18</svg>
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index 21df23c18..38b7ea8d4 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -293,6 +293,15 @@ table {
293 } 293 }
294} 294}
295 295
296.no-results {
297 height: 40vh;
298 display: flex;
299 align-items: center;
300 justify-content: center;
301 font-size: 16px;
302 font-weight: $font-semibold;
303}
304
296@media screen and (max-width: 900px) { 305@media screen and (max-width: 900px) {
297 .main-col { 306 .main-col {
298 &, &.expanded { 307 &, &.expanded {