diff options
author | Chocobozzz <me@florianbigard.com> | 2018-08-30 14:58:00 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-08-31 09:19:58 +0200 |
commit | 2d3741d6d92e9bd1f41694c7442a6d1da434e1f2 (patch) | |
tree | 93a1e609e14bc14ca9e77a6661ddc9c0e461d6f3 /client | |
parent | d9eaee3939bf2e93e5d775d32bce77842201faba (diff) | |
download | PeerTube-2d3741d6d92e9bd1f41694c7442a6d1da434e1f2.tar.gz PeerTube-2d3741d6d92e9bd1f41694c7442a6d1da434e1f2.tar.zst PeerTube-2d3741d6d92e9bd1f41694c7442a6d1da434e1f2.zip |
Videos overview page: first version
Diffstat (limited to 'client')
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 | |||
5 | import { RestExtractor } from '@app/shared' | 5 | import { RestExtractor } from '@app/shared' |
6 | import { catchError, distinctUntilChanged, map, switchMap } from 'rxjs/operators' | 6 | import { catchError, distinctUntilChanged, map, switchMap } from 'rxjs/operators' |
7 | import { Subscription } from 'rxjs' | 7 | import { Subscription } from 'rxjs' |
8 | import { 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 @@ | |||
1 | import { catchError, map, switchMap, tap } from 'rxjs/operators' | ||
2 | import { HttpClient } from '@angular/common/http' | ||
3 | import { Injectable } from '@angular/core' | ||
4 | import { forkJoin, Observable, of } from 'rxjs' | ||
5 | import { VideosOverview as VideosOverviewServer, peertubeTranslate } from '../../../../../shared/models' | ||
6 | import { environment } from '../../../environments/environment' | ||
7 | import { RestExtractor } from '../rest/rest-extractor.service' | ||
8 | import { RestService } from '../rest/rest.service' | ||
9 | import { VideosOverview } from '@app/shared/overview/videos-overview.model' | ||
10 | import { VideoService } from '@app/shared/video/video.service' | ||
11 | import { ServerService } from '@app/core' | ||
12 | import { immutableAssign } from '@app/shared/misc/utils' | ||
13 | |||
14 | @Injectable() | ||
15 | export 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 @@ | |||
1 | import { VideoChannelAttribute, VideoConstant, VideosOverview as VideosOverviewServer } from '../../../../../shared/models' | ||
2 | import { Video } from '@app/shared/video/video.model' | ||
3 | |||
4 | export 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 | |||
52 | import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' | 52 | import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' |
53 | import { SubscribeButtonComponent, UserSubscriptionService } from '@app/shared/user-subscription' | 53 | import { SubscribeButtonComponent, UserSubscriptionService } from '@app/shared/user-subscription' |
54 | import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-features-table.component' | 54 | import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-features-table.component' |
55 | import { 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 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | ||
2 | import { AuthService } from '@app/core' | ||
3 | import { NotificationsService } from 'angular2-notifications' | ||
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
5 | import { VideosOverview } from '@app/shared/overview/videos-overview.model' | ||
6 | import { OverviewService } from '@app/shared/overview' | ||
7 | import { 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 | }) | ||
14 | export 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 | |||
6 | import { VideoTrendingComponent } from './video-list/video-trending.component' | 6 | import { VideoTrendingComponent } from './video-list/video-trending.component' |
7 | import { VideosComponent } from './videos.component' | 7 | import { VideosComponent } from './videos.component' |
8 | import { VideoUserSubscriptionsComponent } from '@app/videos/video-list/video-user-subscriptions.component' | 8 | import { VideoUserSubscriptionsComponent } from '@app/videos/video-list/video-user-subscriptions.component' |
9 | import { VideoOverviewComponent } from '@app/videos/video-list/video-overview.component' | ||
9 | 10 | ||
10 | const videosRoutes: Routes = [ | 11 | const 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' | |||
6 | import { VideosRoutingModule } from './videos-routing.module' | 6 | import { VideosRoutingModule } from './videos-routing.module' |
7 | import { VideosComponent } from './videos.component' | 7 | import { VideosComponent } from './videos.component' |
8 | import { VideoUserSubscriptionsComponent } from '@app/videos/video-list/video-user-subscriptions.component' | 8 | import { VideoUserSubscriptionsComponent } from '@app/videos/video-list/video-user-subscriptions.component' |
9 | import { 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 { |