aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.html48
-rw-r--r--client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.scss67
-rw-r--r--client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.ts56
-rw-r--r--client/src/app/+my-account/my-account-videos/my-account-videos.component.html63
-rw-r--r--client/src/app/+my-account/my-account-videos/my-account-videos.component.scss76
-rw-r--r--client/src/app/+my-account/my-account-videos/my-account-videos.component.ts72
-rw-r--r--client/src/app/shared/angular/from-now.pipe.ts (renamed from client/src/app/shared/misc/from-now.pipe.ts)0
-rw-r--r--client/src/app/shared/angular/number-formatter.pipe.ts (renamed from client/src/app/shared/misc/number-formatter.pipe.ts)0
-rw-r--r--client/src/app/shared/angular/object-length.pipe.ts (renamed from client/src/app/shared/misc/object-length.pipe.ts)0
-rw-r--r--client/src/app/shared/angular/peertube-template.directive.ts12
-rw-r--r--client/src/app/shared/shared.module.ts17
-rw-r--r--client/src/app/shared/video/abstract-video-list.ts5
-rw-r--r--client/src/app/shared/video/videos-selection.component.html26
-rw-r--r--client/src/app/shared/video/videos-selection.component.scss57
-rw-r--r--client/src/app/shared/video/videos-selection.component.ts112
15 files changed, 323 insertions, 288 deletions
diff --git a/client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.html b/client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.html
index 5ef497fa7..62dde60bb 100644
--- a/client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.html
+++ b/client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.html
@@ -1,33 +1,19 @@
1<div i18n *ngIf="pagination.totalItems === 0">No results.</div> 1<my-videos-selection
2 [(selection)]="selection"
3 [(videosModel)]="videos"
4 [miniatureDisplayOptions]="miniatureDisplayOptions"
5 [titlePage]="titlePage"
6 [getVideosObservableFunction]="getVideosObservableFunction"
7>
8 <ng-template ptTemplate="globalButtons">
9 <span class="action-button action-button-unblacklist-selection" (click)="removeSelectedVideosFromBlacklist()">
10 <my-global-icon iconName="tick"></my-global-icon>
11 <ng-container i18n>Unblacklist</ng-container>
12 </span>
13 </ng-template>
2 14
3<div myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" class="videos"> 15 <ng-template ptTemplate="rowButtons" let-video>
4 <div class="video" *ngFor="let video of videos; let i = index"> 16 <my-button i18n-label label="Unblacklist" icon="tick" (click)="removeVideoFromBlacklist(video)"></my-button>
5 <div class="checkbox-container"> 17 </ng-template>
6 <my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="checkedVideos[video.id]"></my-peertube-checkbox>
7 </div>
8 18
9 <my-video-miniature [video]="video" [displayAsRow]="true" [displayOptions]="miniatureDisplayOptions"></my-video-miniature> 19</my-videos-selection>
10
11 <!-- Display only once -->
12 <div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0">
13 <div class="action-selection-mode-child">
14 <span i18n class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
15 Cancel
16 </span>
17
18 <span class="action-button action-button-unblacklist-selection" (click)="removeSelectedVideosFromBlacklist()">
19 <my-global-icon iconName="tick"></my-global-icon>
20 <ng-container i18n>Unblacklist</ng-container>
21 </span>
22 </div>
23 </div>
24
25 <my-button
26 *ngIf="isInSelectionMode() === false"
27 i18n-label
28 label="Unblacklist"
29 icon="tick"
30 (click)="removeVideoFromBlacklist(video)"
31 ></my-button>
32 </div>
33</div>
diff --git a/client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.scss b/client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.scss
index e43a2aa7b..85ebc6041 100644
--- a/client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.scss
+++ b/client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.scss
@@ -1,67 +1,14 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3 3
4.action-selection-mode { 4.action-button-unblacklist-selection {
5 width: 194px; 5 display: inline-block;
6 display: flex;
7 justify-content: flex-end;
8 6
9 .action-selection-mode-child { 7 @include peertube-button;
10 position: fixed; 8 @include orange-button;
9 @include button-with-icon(21px);
11 10
12 .action-button { 11 my-global-icon {
13 display: inline-block; 12 @include apply-svg-color(#fff);
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-unblacklist-selection {
24 @include peertube-button;
25 @include orange-button;
26 @include button-with-icon(21px);
27
28 my-global-icon {
29 @include apply-svg-color(#fff);
30 }
31 }
32 }
33}
34
35.video {
36 @include row-blocks;
37
38 &:first-child {
39 margin-top: 47px;
40 }
41
42 .checkbox-container {
43 display: flex;
44 align-items: center;
45 margin-right: 20px;
46 margin-left: 12px;
47 }
48
49 my-video-miniature {
50 flex-grow: 1;
51 }
52}
53
54@media screen and (max-width: $small-view) {
55 .video {
56 flex-direction: column;
57 height: auto;
58
59 .checkbox-container {
60 display: none;
61 }
62
63 my-button {
64 margin-top: 10px;
65 }
66 } 13 }
67} 14}
diff --git a/client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.ts b/client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.ts
index d66a6dcae..fb2962b47 100644
--- a/client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.ts
+++ b/client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.ts
@@ -1,29 +1,23 @@
1import { Component, OnDestroy, OnInit } from '@angular/core' 1import { Component } from '@angular/core'
2import { I18n } from '@ngx-translate/i18n-polyfill' 2import { I18n } from '@ngx-translate/i18n-polyfill'
3import { ActivatedRoute, Router } from '@angular/router' 3import { ActivatedRoute, Router } from '@angular/router'
4import { AbstractVideoList } from '@app/shared/video/abstract-video-list'
5import { ComponentPagination } from '@app/shared/rest/component-pagination.model' 4import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
6import { AuthService, Notifier, ServerService } from '@app/core' 5import { AuthService, Notifier, ServerService } from '@app/core'
7import { Video } from '@shared/models'
8import { VideoBlacklistService } from '@app/shared' 6import { VideoBlacklistService } from '@app/shared'
9import { immutableAssign } from '@app/shared/misc/utils' 7import { immutableAssign } from '@app/shared/misc/utils'
10import { ScreenService } from '@app/shared/misc/screen.service' 8import { ScreenService } from '@app/shared/misc/screen.service'
11import { MiniatureDisplayOptions } from '@app/shared/video/video-miniature.component' 9import { MiniatureDisplayOptions } from '@app/shared/video/video-miniature.component'
10import { SelectionType } from '@app/shared/video/videos-selection.component'
11import { Video } from '@app/shared/video/video.model'
12 12
13@Component({ 13@Component({
14 selector: 'my-video-auto-blacklist-list', 14 selector: 'my-video-auto-blacklist-list',
15 templateUrl: './video-auto-blacklist-list.component.html', 15 templateUrl: './video-auto-blacklist-list.component.html',
16 styleUrls: [ './video-auto-blacklist-list.component.scss' ] 16 styleUrls: [ './video-auto-blacklist-list.component.scss' ]
17}) 17})
18export class VideoAutoBlacklistListComponent extends AbstractVideoList implements OnInit, OnDestroy { 18export class VideoAutoBlacklistListComponent {
19 titlePage: string 19 titlePage: string
20 checkedVideos: { [ id: number ]: boolean } = {} 20 selection: SelectionType = {}
21 pagination: ComponentPagination = {
22 currentPage: 1,
23 itemsPerPage: 5,
24 totalItems: null
25 }
26
27 miniatureDisplayOptions: MiniatureDisplayOptions = { 21 miniatureDisplayOptions: MiniatureDisplayOptions = {
28 date: true, 22 date: true,
29 views: false, 23 views: false,
@@ -34,6 +28,13 @@ export class VideoAutoBlacklistListComponent extends AbstractVideoList implement
34 blacklistInfo: false, 28 blacklistInfo: false,
35 nsfw: true 29 nsfw: true
36 } 30 }
31 pagination: ComponentPagination = {
32 currentPage: 1,
33 itemsPerPage: 5,
34 totalItems: null
35 }
36 videos: Video[] = []
37 getVideosObservableFunction = this.getVideosObservable.bind(this)
37 38
38 constructor ( 39 constructor (
39 protected router: Router, 40 protected router: Router,
@@ -45,42 +46,21 @@ export class VideoAutoBlacklistListComponent extends AbstractVideoList implement
45 private i18n: I18n, 46 private i18n: I18n,
46 private videoBlacklistService: VideoBlacklistService 47 private videoBlacklistService: VideoBlacklistService
47 ) { 48 ) {
48 super()
49
50 this.titlePage = this.i18n('Auto-blacklisted videos') 49 this.titlePage = this.i18n('Auto-blacklisted videos')
51 } 50 }
52 51
53 ngOnInit () {
54 super.ngOnInit()
55 }
56
57 ngOnDestroy () {
58 super.ngOnDestroy()
59 }
60
61 abortSelectionMode () {
62 this.checkedVideos = {}
63 }
64
65 isInSelectionMode () {
66 return Object.keys(this.checkedVideos).some(k => this.checkedVideos[k] === true)
67 }
68
69 getVideosObservable (page: number) { 52 getVideosObservable (page: number) {
70 const newPagination = immutableAssign(this.pagination, { currentPage: page }) 53 const newPagination = immutableAssign(this.pagination, { currentPage: page })
71 54
72 return this.videoBlacklistService.getAutoBlacklistedAsVideoList(newPagination) 55 return this.videoBlacklistService.getAutoBlacklistedAsVideoList(newPagination)
73 } 56 }
74 57
75 generateSyndicationList () {
76 throw new Error('Method not implemented.')
77 }
78
79 removeVideoFromBlacklist (entry: Video) { 58 removeVideoFromBlacklist (entry: Video) {
80 this.videoBlacklistService.removeVideoFromBlacklist(entry.id).subscribe( 59 this.videoBlacklistService.removeVideoFromBlacklist(entry.id).subscribe(
81 () => { 60 () => {
82 this.notifier.success(this.i18n('Video {{name}} removed from blacklist.', { name: entry.name })) 61 this.notifier.success(this.i18n('Video {{name}} removed from blacklist.', { name: entry.name }))
83 this.reloadVideos() 62
63 this.videos = this.videos.filter(v => v.id !== entry.id)
84 }, 64 },
85 65
86 error => this.notifier.error(error.message) 66 error => this.notifier.error(error.message)
@@ -88,16 +68,16 @@ export class VideoAutoBlacklistListComponent extends AbstractVideoList implement
88 } 68 }
89 69
90 removeSelectedVideosFromBlacklist () { 70 removeSelectedVideosFromBlacklist () {
91 const toReleaseVideosIds = Object.keys(this.checkedVideos) 71 const toReleaseVideosIds = Object.keys(this.selection)
92 .filter(k => this.checkedVideos[ k ] === true) 72 .filter(k => this.selection[ k ] === true)
93 .map(k => parseInt(k, 10)) 73 .map(k => parseInt(k, 10))
94 74
95 this.videoBlacklistService.removeVideoFromBlacklist(toReleaseVideosIds).subscribe( 75 this.videoBlacklistService.removeVideoFromBlacklist(toReleaseVideosIds).subscribe(
96 () => { 76 () => {
97 this.notifier.success(this.i18n('{{num}} videos removed from blacklist.', { num: toReleaseVideosIds.length })) 77 this.notifier.success(this.i18n('{{num}} videos removed from blacklist.', { num: toReleaseVideosIds.length }))
98 78
99 this.abortSelectionMode() 79 this.selection = {}
100 this.reloadVideos() 80 this.videos = this.videos.filter(v => toReleaseVideosIds.includes(v.id) === false)
101 }, 81 },
102 82
103 error => this.notifier.error(error.message) 83 error => this.notifier.error(error.message)
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
index 3a4054de8..d7993fdc2 100644
--- 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
@@ -1,39 +1,30 @@
1<div i18n *ngIf="pagination.totalItems === 0">No results.</div> 1<my-videos-selection
2 [(selection)]="selection"
3 [(videosModel)]="videos"
4 [miniatureDisplayOptions]="miniatureDisplayOptions"
5 [titlePage]="titlePage"
6 [getVideosObservableFunction]="getVideosObservableFunction"
7 #videosSelection
8>
9 <ng-template ptTemplate="globalButtons">
10 <span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()">
11 <my-global-icon iconName="delete"></my-global-icon>
12 <ng-container i18n>Delete</ng-container>
13 </span>
14 </ng-template>
15
16 <ng-template ptTemplate="rowButtons" let-video>
17 <my-delete-button (click)="deleteVideo(video)"></my-delete-button>
18
19 <my-edit-button [routerLink]="[ '/videos', 'update', video.uuid ]"></my-edit-button>
20
21 <my-button i18n-label label="Change ownership"
22 className="action-button-change-ownership"
23 icon="im-with-her"
24 (click)="changeOwnership($event, video)"
25 ></my-button>
26 </ng-template>
27</my-videos-selection>
2 28
3<div myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" class="videos">
4 <div class="video" *ngFor="let video of videos; let i = index">
5 <div class="checkbox-container">
6 <my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="checkedVideos[video.id]"></my-peertube-checkbox>
7 </div>
8
9 <my-video-miniature [video]="video" [displayOptions]="miniatureDisplayOptions" [displayAsRow]="true"></my-video-miniature>
10
11 <!-- Display only once -->
12 <div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0">
13 <div class="action-selection-mode-child">
14 <span i18n class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
15 Cancel
16 </span>
17
18 <span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()">
19 <my-global-icon iconName="delete"></my-global-icon>
20 <ng-container i18n>Delete</ng-container>
21 </span>
22 </div>
23 </div>
24
25 <div class="video-buttons" *ngIf="isInSelectionMode() === false">
26 <my-delete-button (click)="deleteVideo(video)"></my-delete-button>
27
28 <my-edit-button [routerLink]="[ '/videos', 'update', video.uuid ]"></my-edit-button>
29
30 <my-button i18n-label label="Change ownership"
31 className="action-button-change-ownership"
32 icon="im-with-her"
33 (click)="changeOwnership($event, video)"
34 ></my-button>
35 </div>
36 </div>
37</div>
38 29
39<my-video-change-ownership #videoChangeOwnershipModal></my-video-change-ownership> 30<my-video-change-ownership #videoChangeOwnershipModal></my-video-change-ownership>
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
index 405ded3f8..87398e7c8 100644
--- 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
@@ -1,75 +1,19 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3 3
4.action-selection-mode { 4.action-button-delete-selection {
5 width: 174px; 5 display: inline-block;
6 display: flex;
7 justify-content: flex-end;
8 6
9 .action-selection-mode-child { 7 @include peertube-button;
10 position: fixed; 8 @include orange-button;
9 @include button-with-icon(21px);
11 10
12 .action-button { 11 my-global-icon {
13 display: inline-block; 12 @include apply-svg-color(#fff);
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 @include button-with-icon(21px);
27
28 my-global-icon {
29 @include apply-svg-color(#fff);
30 }
31 }
32 }
33}
34
35.video {
36 @include row-blocks;
37
38 &:first-child {
39 margin-top: 47px;
40 }
41
42 .checkbox-container {
43 display: flex;
44 align-items: center;
45 margin-right: 20px;
46 margin-left: 12px;
47 }
48
49 my-video-miniature {
50 flex-grow: 1;
51 }
52
53 .video-buttons {
54 min-width: 190px;
55
56 *:not(:last-child) {
57 margin-right: 10px;
58 }
59 } 13 }
60} 14}
61 15
62@media screen and (max-width: $small-view) { 16my-delete-button,
63 .video { 17my-edit-button {
64 flex-direction: column; 18 margin-right: 10px;
65 height: auto;
66
67 .checkbox-container {
68 display: none;
69 }
70
71 .video-buttons {
72 margin-top: 10px;
73 }
74 }
75} 19}
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
index bbe86af73..5f29364a8 100644
--- 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
@@ -1,31 +1,33 @@
1import { concat, Observable } from 'rxjs' 1import { concat, Observable } from 'rxjs'
2import { tap, toArray } from 'rxjs/operators' 2import { tap, toArray } from 'rxjs/operators'
3import { Component, Inject, LOCALE_ID, OnDestroy, OnInit, ViewChild } from '@angular/core' 3import { Component, ViewChild } from '@angular/core'
4import { ActivatedRoute, Router } from '@angular/router' 4import { ActivatedRoute, Router } from '@angular/router'
5import { immutableAssign } from '@app/shared/misc/utils' 5import { immutableAssign } from '@app/shared/misc/utils'
6import { ComponentPagination } from '@app/shared/rest/component-pagination.model' 6import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
7import { Notifier, ServerService } from '@app/core' 7import { Notifier, ServerService } from '@app/core'
8import { AuthService } from '../../core/auth' 8import { AuthService } from '../../core/auth'
9import { ConfirmService } from '../../core/confirm' 9import { ConfirmService } from '../../core/confirm'
10import { AbstractVideoList } from '../../shared/video/abstract-video-list'
11import { Video } from '../../shared/video/video.model' 10import { Video } from '../../shared/video/video.model'
12import { VideoService } from '../../shared/video/video.service' 11import { VideoService } from '../../shared/video/video.service'
13import { I18n } from '@ngx-translate/i18n-polyfill' 12import { I18n } from '@ngx-translate/i18n-polyfill'
14import { VideoPrivacy, VideoState } from '../../../../../shared/models/videos'
15import { ScreenService } from '@app/shared/misc/screen.service' 13import { ScreenService } from '@app/shared/misc/screen.service'
16import { VideoChangeOwnershipComponent } from './video-change-ownership/video-change-ownership.component' 14import { VideoChangeOwnershipComponent } from './video-change-ownership/video-change-ownership.component'
17import { MiniatureDisplayOptions } from '@app/shared/video/video-miniature.component' 15import { MiniatureDisplayOptions } from '@app/shared/video/video-miniature.component'
16import { SelectionType, VideosSelectionComponent } from '@app/shared/video/videos-selection.component'
17import { VideoSortField } from '@app/shared/video/sort-field.type'
18import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
18 19
19@Component({ 20@Component({
20 selector: 'my-account-videos', 21 selector: 'my-account-videos',
21 templateUrl: './my-account-videos.component.html', 22 templateUrl: './my-account-videos.component.html',
22 styleUrls: [ './my-account-videos.component.scss' ] 23 styleUrls: [ './my-account-videos.component.scss' ]
23}) 24})
24export class MyAccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy { 25export class MyAccountVideosComponent implements DisableForReuseHook {
26 @ViewChild('videosSelection') videosSelection: VideosSelectionComponent
25 @ViewChild('videoChangeOwnershipModal') videoChangeOwnershipModal: VideoChangeOwnershipComponent 27 @ViewChild('videoChangeOwnershipModal') videoChangeOwnershipModal: VideoChangeOwnershipComponent
26 28
27 titlePage: string 29 titlePage: string
28 checkedVideos: { [ id: number ]: boolean } = {} 30 selection: SelectionType = {}
29 pagination: ComponentPagination = { 31 pagination: ComponentPagination = {
30 currentPage: 1, 32 currentPage: 1,
31 itemsPerPage: 5, 33 itemsPerPage: 5,
@@ -40,6 +42,8 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
40 state: true, 42 state: true,
41 blacklistInfo: true 43 blacklistInfo: true
42 } 44 }
45 videos: Video[] = []
46 getVideosObservableFunction = this.getVideosObservable.bind(this)
43 47
44 constructor ( 48 constructor (
45 protected router: Router, 49 protected router: Router,
@@ -50,43 +54,28 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
50 protected screenService: ScreenService, 54 protected screenService: ScreenService,
51 private i18n: I18n, 55 private i18n: I18n,
52 private confirmService: ConfirmService, 56 private confirmService: ConfirmService,
53 private videoService: VideoService, 57 private videoService: VideoService
54 @Inject(LOCALE_ID) private localeId: string
55 ) { 58 ) {
56 super()
57
58 this.titlePage = this.i18n('My videos') 59 this.titlePage = this.i18n('My videos')
59 } 60 }
60 61
61 ngOnInit () { 62 disableForReuse () {
62 super.ngOnInit() 63 this.videosSelection.disableForReuse()
63 }
64
65 ngOnDestroy () {
66 super.ngOnDestroy()
67 } 64 }
68 65
69 abortSelectionMode () { 66 enabledForReuse () {
70 this.checkedVideos = {} 67 this.videosSelection.enabledForReuse()
71 } 68 }
72 69
73 isInSelectionMode () { 70 getVideosObservable (page: number, sort: VideoSortField) {
74 return Object.keys(this.checkedVideos).some(k => this.checkedVideos[ k ] === true)
75 }
76
77 getVideosObservable (page: number) {
78 const newPagination = immutableAssign(this.pagination, { currentPage: page }) 71 const newPagination = immutableAssign(this.pagination, { currentPage: page })
79 72
80 return this.videoService.getMyVideos(newPagination, this.sort) 73 return this.videoService.getMyVideos(newPagination, sort)
81 }
82
83 generateSyndicationList () {
84 throw new Error('Method not implemented.')
85 } 74 }
86 75
87 async deleteSelectedVideos () { 76 async deleteSelectedVideos () {
88 const toDeleteVideosIds = Object.keys(this.checkedVideos) 77 const toDeleteVideosIds = Object.keys(this.selection)
89 .filter(k => this.checkedVideos[ k ] === true) 78 .filter(k => this.selection[ k ] === true)
90 .map(k => parseInt(k, 10)) 79 .map(k => parseInt(k, 10))
91 80
92 const res = await this.confirmService.confirm( 81 const res = await this.confirmService.confirm(
@@ -109,7 +98,7 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
109 () => { 98 () => {
110 this.notifier.success(this.i18n('{{deleteLength}} videos deleted.', { deleteLength: toDeleteVideosIds.length })) 99 this.notifier.success(this.i18n('{{deleteLength}} videos deleted.', { deleteLength: toDeleteVideosIds.length }))
111 100
112 this.abortSelectionMode() 101 this.selection = {}
113 }, 102 },
114 103
115 err => this.notifier.error(err.message) 104 err => this.notifier.error(err.message)
@@ -127,7 +116,7 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
127 .subscribe( 116 .subscribe(
128 () => { 117 () => {
129 this.notifier.success(this.i18n('Video {{videoName}} deleted.', { videoName: video.name })) 118 this.notifier.success(this.i18n('Video {{videoName}} deleted.', { videoName: video.name }))
130 this.reloadVideos() 119 this.removeVideoFromArray(video.id)
131 }, 120 },
132 121
133 error => this.notifier.error(error.message) 122 error => this.notifier.error(error.message)
@@ -139,27 +128,6 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
139 this.videoChangeOwnershipModal.show(video) 128 this.videoChangeOwnershipModal.show(video)
140 } 129 }
141 130
142 getStateLabel (video: Video) {
143 let suffix: string
144
145 if (video.privacy.id !== VideoPrivacy.PRIVATE && video.state.id === VideoState.PUBLISHED) {
146 suffix = this.i18n('Published')
147 } else if (video.scheduledUpdate) {
148 const updateAt = new Date(video.scheduledUpdate.updateAt.toString()).toLocaleString(this.localeId)
149 suffix = this.i18n('Publication scheduled on ') + updateAt
150 } else if (video.state.id === VideoState.TO_TRANSCODE && video.waitTranscoding === true) {
151 suffix = this.i18n('Waiting transcoding')
152 } else if (video.state.id === VideoState.TO_TRANSCODE) {
153 suffix = this.i18n('To transcode')
154 } else if (video.state.id === VideoState.TO_IMPORT) {
155 suffix = this.i18n('To import')
156 } else {
157 return ''
158 }
159
160 return ' - ' + suffix
161 }
162
163 private removeVideoFromArray (id: number) { 131 private removeVideoFromArray (id: number) {
164 this.videos = this.videos.filter(v => v.id !== id) 132 this.videos = this.videos.filter(v => v.id !== id)
165 } 133 }
diff --git a/client/src/app/shared/misc/from-now.pipe.ts b/client/src/app/shared/angular/from-now.pipe.ts
index 3a9a76411..3a9a76411 100644
--- a/client/src/app/shared/misc/from-now.pipe.ts
+++ b/client/src/app/shared/angular/from-now.pipe.ts
diff --git a/client/src/app/shared/misc/number-formatter.pipe.ts b/client/src/app/shared/angular/number-formatter.pipe.ts
index 8a0756a36..8a0756a36 100644
--- a/client/src/app/shared/misc/number-formatter.pipe.ts
+++ b/client/src/app/shared/angular/number-formatter.pipe.ts
diff --git a/client/src/app/shared/misc/object-length.pipe.ts b/client/src/app/shared/angular/object-length.pipe.ts
index 84d182052..84d182052 100644
--- a/client/src/app/shared/misc/object-length.pipe.ts
+++ b/client/src/app/shared/angular/object-length.pipe.ts
diff --git a/client/src/app/shared/angular/peertube-template.directive.ts b/client/src/app/shared/angular/peertube-template.directive.ts
new file mode 100644
index 000000000..a514b6057
--- /dev/null
+++ b/client/src/app/shared/angular/peertube-template.directive.ts
@@ -0,0 +1,12 @@
1import { Directive, Input, TemplateRef } from '@angular/core'
2
3@Directive({
4 selector: '[ptTemplate]'
5})
6export class PeerTubeTemplateDirective {
7 @Input('ptTemplate') name: string
8
9 constructor (public template: TemplateRef<any>) {
10 // empty
11 }
12}
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 3647fc786..68225b457 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -14,10 +14,7 @@ import { AUTH_INTERCEPTOR_PROVIDER } from './auth'
14import { ButtonComponent } from './buttons/button.component' 14import { ButtonComponent } from './buttons/button.component'
15import { DeleteButtonComponent } from './buttons/delete-button.component' 15import { DeleteButtonComponent } from './buttons/delete-button.component'
16import { EditButtonComponent } from './buttons/edit-button.component' 16import { EditButtonComponent } from './buttons/edit-button.component'
17import { FromNowPipe } from './misc/from-now.pipe'
18import { LoaderComponent } from './misc/loader.component' 17import { LoaderComponent } from './misc/loader.component'
19import { NumberFormatterPipe } from './misc/number-formatter.pipe'
20import { ObjectLengthPipe } from './misc/object-length.pipe'
21import { RestExtractor, RestService } from './rest' 18import { RestExtractor, RestService } from './rest'
22import { UserService } from './users' 19import { UserService } from './users'
23import { VideoAbuseService } from './video-abuse' 20import { VideoAbuseService } from './video-abuse'
@@ -78,6 +75,11 @@ import { VideoPlaylistMiniatureComponent } from '@app/shared/video-playlist/vide
78import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component' 75import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component'
79import { TimestampInputComponent } from '@app/shared/forms/timestamp-input.component' 76import { TimestampInputComponent } from '@app/shared/forms/timestamp-input.component'
80import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playlist/video-playlist-element-miniature.component' 77import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playlist/video-playlist-element-miniature.component'
78import { VideosSelectionComponent } from '@app/shared/video/videos-selection.component'
79import { NumberFormatterPipe } from '@app/shared/angular/number-formatter.pipe'
80import { ObjectLengthPipe } from '@app/shared/angular/object-length.pipe'
81import { FromNowPipe } from '@app/shared/angular/from-now.pipe'
82import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive'
81 83
82@NgModule({ 84@NgModule({
83 imports: [ 85 imports: [
@@ -107,6 +109,7 @@ import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playli
107 VideoPlaylistMiniatureComponent, 109 VideoPlaylistMiniatureComponent,
108 VideoAddToPlaylistComponent, 110 VideoAddToPlaylistComponent,
109 VideoPlaylistElementMiniatureComponent, 111 VideoPlaylistElementMiniatureComponent,
112 VideosSelectionComponent,
110 113
111 FeedComponent, 114 FeedComponent,
112 115
@@ -114,10 +117,12 @@ import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playli
114 DeleteButtonComponent, 117 DeleteButtonComponent,
115 EditButtonComponent, 118 EditButtonComponent,
116 119
117 ActionDropdownComponent,
118 NumberFormatterPipe, 120 NumberFormatterPipe,
119 ObjectLengthPipe, 121 ObjectLengthPipe,
120 FromNowPipe, 122 FromNowPipe,
123 PeerTubeTemplateDirective,
124
125 ActionDropdownComponent,
121 MarkdownTextareaComponent, 126 MarkdownTextareaComponent,
122 InfiniteScrollerDirective, 127 InfiniteScrollerDirective,
123 TextareaAutoResizeDirective, 128 TextareaAutoResizeDirective,
@@ -166,6 +171,7 @@ import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playli
166 VideoPlaylistMiniatureComponent, 171 VideoPlaylistMiniatureComponent,
167 VideoAddToPlaylistComponent, 172 VideoAddToPlaylistComponent,
168 VideoPlaylistElementMiniatureComponent, 173 VideoPlaylistElementMiniatureComponent,
174 VideosSelectionComponent,
169 175
170 FeedComponent, 176 FeedComponent,
171 177
@@ -197,7 +203,8 @@ import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playli
197 203
198 NumberFormatterPipe, 204 NumberFormatterPipe,
199 ObjectLengthPipe, 205 ObjectLengthPipe,
200 FromNowPipe 206 FromNowPipe,
207 PeerTubeTemplateDirective
201 ], 208 ],
202 209
203 providers: [ 210 providers: [
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts
index 467f629ea..099650129 100644
--- a/client/src/app/shared/video/abstract-video-list.ts
+++ b/client/src/app/shared/video/abstract-video-list.ts
@@ -102,6 +102,8 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
102 ({ videos, totalVideos }) => { 102 ({ videos, totalVideos }) => {
103 this.pagination.totalItems = totalVideos 103 this.pagination.totalItems = totalVideos
104 this.videos = this.videos.concat(videos) 104 this.videos = this.videos.concat(videos)
105
106 this.onMoreVideos()
105 }, 107 },
106 108
107 error => this.notifier.error(error.message) 109 error => this.notifier.error(error.message)
@@ -118,6 +120,9 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
118 throw new Error('toggleModerationDisplay is not implemented') 120 throw new Error('toggleModerationDisplay is not implemented')
119 } 121 }
120 122
123 // On videos hook for children that want to do something
124 protected onMoreVideos () { /* empty */ }
125
121 protected loadRouteParams (routeParams: { [ key: string ]: any }) { 126 protected loadRouteParams (routeParams: { [ key: string ]: any }) {
122 this.sort = routeParams[ 'sort' ] as VideoSortField || this.defaultSort 127 this.sort = routeParams[ 'sort' ] as VideoSortField || this.defaultSort
123 this.categoryOneOf = routeParams[ 'categoryOneOf' ] 128 this.categoryOneOf = routeParams[ 'categoryOneOf' ]
diff --git a/client/src/app/shared/video/videos-selection.component.html b/client/src/app/shared/video/videos-selection.component.html
new file mode 100644
index 000000000..6f3401b4b
--- /dev/null
+++ b/client/src/app/shared/video/videos-selection.component.html
@@ -0,0 +1,26 @@
1<div class="no-results" i18n *ngIf="pagination.totalItems === 0">No results.</div>
2
3<div myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" class="videos">
4 <div class="video" *ngFor="let video of videos; let i = index">
5 <div class="checkbox-container">
6 <my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="_selection[video.id]"></my-peertube-checkbox>
7 </div>
8
9 <my-video-miniature [video]="video" [displayAsRow]="true" [displayOptions]="miniatureDisplayOptions"></my-video-miniature>
10
11 <!-- Display only once -->
12 <div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0">
13 <div class="action-selection-mode-child">
14 <span i18n class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
15 Cancel
16 </span>
17
18 <ng-container *ngTemplateOutlet="globalButtonsTemplate"></ng-container>
19 </div>
20 </div>
21
22 <ng-container *ngIf="isInSelectionMode() === false">
23 <ng-container *ngTemplateOutlet="rowButtonsTemplate; context: {$implicit: video}"></ng-container>
24 </ng-container>
25 </div>
26</div>
diff --git a/client/src/app/shared/video/videos-selection.component.scss b/client/src/app/shared/video/videos-selection.component.scss
new file mode 100644
index 000000000..d3cbabf23
--- /dev/null
+++ b/client/src/app/shared/video/videos-selection.component.scss
@@ -0,0 +1,57 @@
1@import '_variables';
2@import '_mixins';
3
4.action-selection-mode {
5 display: flex;
6 justify-content: flex-end;
7 flex-grow: 1;
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}
24
25.video {
26 @include row-blocks;
27
28 &:first-child {
29 margin-top: 47px;
30 }
31
32 .checkbox-container {
33 display: flex;
34 align-items: center;
35 margin-right: 20px;
36 margin-left: 12px;
37 }
38
39 my-video-miniature {
40 flex-grow: 1;
41 }
42}
43
44@media screen and (max-width: $small-view) {
45 .video {
46 flex-direction: column;
47 height: auto;
48
49 .checkbox-container {
50 display: none;
51 }
52
53 my-button {
54 margin-top: 10px;
55 }
56 }
57}
diff --git a/client/src/app/shared/video/videos-selection.component.ts b/client/src/app/shared/video/videos-selection.component.ts
new file mode 100644
index 000000000..b6bedafd8
--- /dev/null
+++ b/client/src/app/shared/video/videos-selection.component.ts
@@ -0,0 +1,112 @@
1import {
2 AfterContentInit,
3 Component,
4 ContentChildren,
5 EventEmitter,
6 Input,
7 OnDestroy,
8 OnInit,
9 Output,
10 QueryList,
11 TemplateRef
12} from '@angular/core'
13import { ActivatedRoute, Router } from '@angular/router'
14import { AbstractVideoList } from '@app/shared/video/abstract-video-list'
15import { AuthService, Notifier, ServerService } from '@app/core'
16import { ScreenService } from '@app/shared/misc/screen.service'
17import { MiniatureDisplayOptions } from '@app/shared/video/video-miniature.component'
18import { Observable } from 'rxjs'
19import { Video } from '@app/shared/video/video.model'
20import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive'
21import { VideoSortField } from '@app/shared/video/sort-field.type'
22
23export type SelectionType = { [ id: number ]: boolean }
24
25@Component({
26 selector: 'my-videos-selection',
27 templateUrl: './videos-selection.component.html',
28 styleUrls: [ './videos-selection.component.scss' ]
29})
30export class VideosSelectionComponent extends AbstractVideoList implements OnInit, OnDestroy, AfterContentInit {
31 @Input() titlePage: string
32 @Input() miniatureDisplayOptions: MiniatureDisplayOptions
33 @Input() getVideosObservableFunction: (page: number, sort?: VideoSortField) => Observable<{ videos: Video[], totalVideos: number }>
34 @ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective>
35
36 @Output() selectionChange = new EventEmitter<SelectionType>()
37 @Output() videosModelChange = new EventEmitter<Video[]>()
38
39 _selection: SelectionType = {}
40
41 rowButtonsTemplate: TemplateRef<any>
42 globalButtonsTemplate: TemplateRef<any>
43
44 constructor (
45 protected router: Router,
46 protected route: ActivatedRoute,
47 protected notifier: Notifier,
48 protected authService: AuthService,
49 protected screenService: ScreenService,
50 protected serverService: ServerService
51 ) {
52 super()
53 }
54
55 ngAfterContentInit () {
56 {
57 const t = this.templates.find(t => t.name === 'rowButtons')
58 if (t) this.rowButtonsTemplate = t.template
59 }
60
61 {
62 const t = this.templates.find(t => t.name === 'globalButtons')
63 if (t) this.globalButtonsTemplate = t.template
64 }
65 }
66
67 @Input() get selection () {
68 return this._selection
69 }
70
71 set selection (selection: SelectionType) {
72 this._selection = selection
73 this.selectionChange.emit(this._selection)
74 }
75
76 @Input() get videosModel () {
77 return this.videos
78 }
79
80 set videosModel (videos: Video[]) {
81 this.videos = videos
82 this.videosModelChange.emit(this.videos)
83 }
84
85 ngOnInit () {
86 super.ngOnInit()
87 }
88
89 ngOnDestroy () {
90 super.ngOnDestroy()
91 }
92
93 getVideosObservable (page: number) {
94 return this.getVideosObservableFunction(page, this.sort)
95 }
96
97 abortSelectionMode () {
98 this._selection = {}
99 }
100
101 isInSelectionMode () {
102 return Object.keys(this._selection).some(k => this._selection[ k ] === true)
103 }
104
105 generateSyndicationList () {
106 throw new Error('Method not implemented.')
107 }
108
109 protected onMoreVideos () {
110 this.videosModel = this.videos
111 }
112}