diff options
author | Chocobozzz <me@florianbigard.com> | 2018-05-09 09:26:41 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-05-09 09:32:26 +0200 |
commit | 62e62f118d5da57acd3494fece2e8ed357564ffe (patch) | |
tree | ff5b7c6ec5708d71a76a2eb5744d810015ae29a5 /client/src/app/+my-account/my-account-videos | |
parent | 1952a538baa330b13bd11631b3975f7ef1c37e70 (diff) | |
download | PeerTube-62e62f118d5da57acd3494fece2e8ed357564ffe.tar.gz PeerTube-62e62f118d5da57acd3494fece2e8ed357564ffe.tar.zst PeerTube-62e62f118d5da57acd3494fece2e8ed357564ffe.zip |
Load my-account module lazily
Diffstat (limited to 'client/src/app/+my-account/my-account-videos')
3 files changed, 290 insertions, 0 deletions
diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.html b/client/src/app/+my-account/my-account-videos/my-account-videos.component.html new file mode 100644 index 000000000..66ce3a77b --- /dev/null +++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.html | |||
@@ -0,0 +1,45 @@ | |||
1 | <div *ngIf="pagination.totalItems === 0">No results.</div> | ||
2 | |||
3 | <div | ||
4 | myInfiniteScroller | ||
5 | [pageHeight]="pageHeight" | ||
6 | (nearOfTop)="onNearOfTop()" (nearOfBottom)="onNearOfBottom()" (pageChanged)="onPageChanged($event)" | ||
7 | class="videos" #videosElement | ||
8 | > | ||
9 | <div *ngFor="let videos of videoPages; let i = index" class="videos-page"> | ||
10 | <div class="video" *ngFor="let video of videos; let j = index"> | ||
11 | <div class="checkbox-container"> | ||
12 | <input [id]="'video-check-' + video.id" type="checkbox" [(ngModel)]="checkedVideos[video.id]" /> | ||
13 | <label [for]="'video-check-' + video.id"></label> | ||
14 | </div> | ||
15 | |||
16 | <my-video-thumbnail [video]="video"></my-video-thumbnail> | ||
17 | |||
18 | <div class="video-info"> | ||
19 | <a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a> | ||
20 | <span class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span> | ||
21 | <div class="video-info-private">{{ video.privacy.label }}</div> | ||
22 | </div> | ||
23 | |||
24 | <!-- Display only once --> | ||
25 | <div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0 && j === 0"> | ||
26 | <div class="action-selection-mode-child"> | ||
27 | <span class="action-button action-button-cancel-selection" (click)="abortSelectionMode()"> | ||
28 | Cancel | ||
29 | </span> | ||
30 | |||
31 | <span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()"> | ||
32 | <span class="icon icon-delete-white"></span> | ||
33 | Delete | ||
34 | </span> | ||
35 | </div> | ||
36 | </div> | ||
37 | |||
38 | <div class="video-buttons" *ngIf="isInSelectionMode() === false"> | ||
39 | <my-delete-button (click)="deleteVideo(video)"></my-delete-button> | ||
40 | |||
41 | <my-edit-button [routerLink]="[ '/videos', 'edit', video.uuid ]"></my-edit-button> | ||
42 | </div> | ||
43 | </div> | ||
44 | </div> | ||
45 | </div> | ||
diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss b/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss new file mode 100644 index 000000000..f276ea389 --- /dev/null +++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss | |||
@@ -0,0 +1,114 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .action-selection-mode { | ||
5 | width: 174px; | ||
6 | display: flex; | ||
7 | justify-content: flex-end; | ||
8 | |||
9 | .action-selection-mode-child { | ||
10 | position: fixed; | ||
11 | |||
12 | .action-button { | ||
13 | display: inline-block; | ||
14 | } | ||
15 | |||
16 | .action-button-cancel-selection { | ||
17 | @include peertube-button; | ||
18 | @include grey-button; | ||
19 | |||
20 | margin-right: 10px; | ||
21 | } | ||
22 | |||
23 | .action-button-delete-selection { | ||
24 | @include peertube-button; | ||
25 | @include orange-button; | ||
26 | } | ||
27 | |||
28 | .icon.icon-delete-white { | ||
29 | @include icon(21px); | ||
30 | |||
31 | position: relative; | ||
32 | top: -2px; | ||
33 | background-image: url('../../../assets/images/global/delete-white.svg'); | ||
34 | } | ||
35 | } | ||
36 | } | ||
37 | |||
38 | /deep/ .action-button { | ||
39 | &.action-button-delete { | ||
40 | margin-right: 10px; | ||
41 | } | ||
42 | } | ||
43 | |||
44 | .video { | ||
45 | display: flex; | ||
46 | min-height: 130px; | ||
47 | padding-bottom: 20px; | ||
48 | margin-bottom: 20px; | ||
49 | border-bottom: 1px solid #C6C6C6; | ||
50 | |||
51 | &:first-child { | ||
52 | margin-top: 47px; | ||
53 | } | ||
54 | |||
55 | .checkbox-container { | ||
56 | display: flex; | ||
57 | align-items: center; | ||
58 | margin-right: 20px; | ||
59 | margin-left: 12px; | ||
60 | |||
61 | input[type=checkbox] { | ||
62 | @include peertube-checkbox(2px); | ||
63 | } | ||
64 | } | ||
65 | |||
66 | my-video-thumbnail { | ||
67 | margin-right: 10px; | ||
68 | } | ||
69 | |||
70 | .video-info { | ||
71 | flex-grow: 1; | ||
72 | |||
73 | .video-info-name { | ||
74 | @include disable-default-a-behaviour; | ||
75 | |||
76 | color: #000; | ||
77 | display: block; | ||
78 | font-size: 16px; | ||
79 | font-weight: $font-semibold; | ||
80 | } | ||
81 | |||
82 | .video-info-date-views, .video-info-private { | ||
83 | font-size: 13px; | ||
84 | |||
85 | &.video-info-private { | ||
86 | font-weight: $font-semibold; | ||
87 | } | ||
88 | } | ||
89 | } | ||
90 | |||
91 | .video-buttons { | ||
92 | min-width: 190px; | ||
93 | } | ||
94 | } | ||
95 | |||
96 | @media screen and (max-width: 800px) { | ||
97 | .video { | ||
98 | flex-direction: column; | ||
99 | height: auto; | ||
100 | text-align: center; | ||
101 | |||
102 | input[type=checkbox] { | ||
103 | display: none; | ||
104 | } | ||
105 | |||
106 | my-video-thumbnail { | ||
107 | margin-right: 0; | ||
108 | } | ||
109 | |||
110 | .video-buttons { | ||
111 | margin-top: 10px; | ||
112 | } | ||
113 | } | ||
114 | } | ||
diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts b/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts new file mode 100644 index 000000000..c1b53bcd5 --- /dev/null +++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts | |||
@@ -0,0 +1,131 @@ | |||
1 | import { Component, OnInit, OnDestroy } from '@angular/core' | ||
2 | import { ActivatedRoute, Router } from '@angular/router' | ||
3 | import { Location } from '@angular/common' | ||
4 | import { immutableAssign } from '@app/shared/misc/utils' | ||
5 | import { ComponentPagination } from '@app/shared/rest/component-pagination.model' | ||
6 | import { NotificationsService } from 'angular2-notifications' | ||
7 | import 'rxjs/add/observable/from' | ||
8 | import 'rxjs/add/operator/concatAll' | ||
9 | import { Observable } from 'rxjs/Observable' | ||
10 | import { AuthService } from '../../core/auth' | ||
11 | import { ConfirmService } from '../../core/confirm' | ||
12 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' | ||
13 | import { Video } from '../../shared/video/video.model' | ||
14 | import { VideoService } from '../../shared/video/video.service' | ||
15 | |||
16 | @Component({ | ||
17 | selector: 'my-account-videos', | ||
18 | templateUrl: './my-account-videos.component.html', | ||
19 | styleUrls: [ './my-account-videos.component.scss' ] | ||
20 | }) | ||
21 | export class MyAccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy { | ||
22 | titlePage = 'My videos' | ||
23 | currentRoute = '/my-account/videos' | ||
24 | checkedVideos: { [ id: number ]: boolean } = {} | ||
25 | pagination: ComponentPagination = { | ||
26 | currentPage: 1, | ||
27 | itemsPerPage: 5, | ||
28 | totalItems: null | ||
29 | } | ||
30 | |||
31 | protected baseVideoWidth = -1 | ||
32 | protected baseVideoHeight = 155 | ||
33 | |||
34 | constructor (protected router: Router, | ||
35 | protected route: ActivatedRoute, | ||
36 | protected authService: AuthService, | ||
37 | protected notificationsService: NotificationsService, | ||
38 | protected confirmService: ConfirmService, | ||
39 | protected location: Location, | ||
40 | private videoService: VideoService) { | ||
41 | super() | ||
42 | } | ||
43 | |||
44 | ngOnInit () { | ||
45 | super.ngOnInit() | ||
46 | } | ||
47 | |||
48 | ngOnDestroy () { | ||
49 | super.ngOnDestroy() | ||
50 | } | ||
51 | |||
52 | abortSelectionMode () { | ||
53 | this.checkedVideos = {} | ||
54 | } | ||
55 | |||
56 | isInSelectionMode () { | ||
57 | return Object.keys(this.checkedVideos).some(k => this.checkedVideos[k] === true) | ||
58 | } | ||
59 | |||
60 | getVideosObservable (page: number) { | ||
61 | const newPagination = immutableAssign(this.pagination, { currentPage: page }) | ||
62 | |||
63 | return this.videoService.getMyVideos(newPagination, this.sort) | ||
64 | } | ||
65 | |||
66 | generateSyndicationList () { | ||
67 | throw new Error('Method not implemented.') | ||
68 | } | ||
69 | |||
70 | async deleteSelectedVideos () { | ||
71 | const toDeleteVideosIds = Object.keys(this.checkedVideos) | ||
72 | .filter(k => this.checkedVideos[k] === true) | ||
73 | .map(k => parseInt(k, 10)) | ||
74 | |||
75 | const res = await this.confirmService.confirm(`Do you really want to delete ${toDeleteVideosIds.length} videos?`, 'Delete') | ||
76 | if (res === false) return | ||
77 | |||
78 | const observables: Observable<any>[] = [] | ||
79 | for (const videoId of toDeleteVideosIds) { | ||
80 | const o = this.videoService | ||
81 | .removeVideo(videoId) | ||
82 | .do(() => this.spliceVideosById(videoId)) | ||
83 | |||
84 | observables.push(o) | ||
85 | } | ||
86 | |||
87 | Observable.from(observables) | ||
88 | .concatAll() | ||
89 | .subscribe( | ||
90 | res => { | ||
91 | this.notificationsService.success('Success', `${toDeleteVideosIds.length} videos deleted.`) | ||
92 | this.buildVideoPages() | ||
93 | }, | ||
94 | |||
95 | err => this.notificationsService.error('Error', err.message) | ||
96 | ) | ||
97 | } | ||
98 | |||
99 | async deleteVideo (video: Video) { | ||
100 | const res = await this.confirmService.confirm(`Do you really want to delete ${video.name}?`, 'Delete') | ||
101 | if (res === false) return | ||
102 | |||
103 | this.videoService.removeVideo(video.id) | ||
104 | .subscribe( | ||
105 | status => { | ||
106 | this.notificationsService.success('Success', `Video ${video.name} deleted.`) | ||
107 | this.spliceVideosById(video.id) | ||
108 | this.buildVideoPages() | ||
109 | }, | ||
110 | |||
111 | error => this.notificationsService.error('Error', error.message) | ||
112 | ) | ||
113 | } | ||
114 | |||
115 | protected buildVideoHeight () { | ||
116 | // In account videos, the video height is fixed | ||
117 | return this.baseVideoHeight | ||
118 | } | ||
119 | |||
120 | private spliceVideosById (id: number) { | ||
121 | for (const key of Object.keys(this.loadedPages)) { | ||
122 | const videos = this.loadedPages[key] | ||
123 | const index = videos.findIndex(v => v.id === id) | ||
124 | |||
125 | if (index !== -1) { | ||
126 | videos.splice(index, 1) | ||
127 | return | ||
128 | } | ||
129 | } | ||
130 | } | ||
131 | } | ||