-<div i18n *ngIf="pagination.totalItems === 0">No results.</div>
+<my-videos-selection
+ [(selection)]="selection"
+ [(videosModel)]="videos"
+ [miniatureDisplayOptions]="miniatureDisplayOptions"
+ [titlePage]="titlePage"
+ [getVideosObservableFunction]="getVideosObservableFunction"
+>
+ <ng-template ptTemplate="globalButtons">
+ <span class="action-button action-button-unblacklist-selection" (click)="removeSelectedVideosFromBlacklist()">
+ <my-global-icon iconName="tick"></my-global-icon>
+ <ng-container i18n>Unblacklist</ng-container>
+ </span>
+ </ng-template>
-<div myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" class="videos">
- <div class="video" *ngFor="let video of videos; let i = index">
- <div class="checkbox-container">
- <my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="checkedVideos[video.id]"></my-peertube-checkbox>
- </div>
+ <ng-template ptTemplate="rowButtons" let-video>
+ <my-button i18n-label label="Unblacklist" icon="tick" (click)="removeVideoFromBlacklist(video)"></my-button>
+ </ng-template>
- <my-video-miniature [video]="video" [displayAsRow]="true" [displayOptions]="miniatureDisplayOptions"></my-video-miniature>
-
- <!-- Display only once -->
- <div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0">
- <div class="action-selection-mode-child">
- <span i18n class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
- Cancel
- </span>
-
- <span class="action-button action-button-unblacklist-selection" (click)="removeSelectedVideosFromBlacklist()">
- <my-global-icon iconName="tick"></my-global-icon>
- <ng-container i18n>Unblacklist</ng-container>
- </span>
- </div>
- </div>
-
- <my-button
- *ngIf="isInSelectionMode() === false"
- i18n-label
- label="Unblacklist"
- icon="tick"
- (click)="removeVideoFromBlacklist(video)"
- ></my-button>
- </div>
-</div>
+</my-videos-selection>
@import '_variables';
@import '_mixins';
-.action-selection-mode {
- width: 194px;
- display: flex;
- justify-content: flex-end;
+.action-button-unblacklist-selection {
+ display: inline-block;
- .action-selection-mode-child {
- position: fixed;
+ @include peertube-button;
+ @include orange-button;
+ @include button-with-icon(21px);
- .action-button {
- display: inline-block;
- }
-
- .action-button-cancel-selection {
- @include peertube-button;
- @include grey-button;
-
- margin-right: 10px;
- }
-
- .action-button-unblacklist-selection {
- @include peertube-button;
- @include orange-button;
- @include button-with-icon(21px);
-
- my-global-icon {
- @include apply-svg-color(#fff);
- }
- }
- }
-}
-
-.video {
- @include row-blocks;
-
- &:first-child {
- margin-top: 47px;
- }
-
- .checkbox-container {
- display: flex;
- align-items: center;
- margin-right: 20px;
- margin-left: 12px;
- }
-
- my-video-miniature {
- flex-grow: 1;
- }
-}
-
-@media screen and (max-width: $small-view) {
- .video {
- flex-direction: column;
- height: auto;
-
- .checkbox-container {
- display: none;
- }
-
- my-button {
- margin-top: 10px;
- }
+ my-global-icon {
+ @include apply-svg-color(#fff);
}
}
-import { Component, OnDestroy, OnInit } from '@angular/core'
+import { Component } from '@angular/core'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { ActivatedRoute, Router } from '@angular/router'
-import { AbstractVideoList } from '@app/shared/video/abstract-video-list'
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
import { AuthService, Notifier, ServerService } from '@app/core'
-import { Video } from '@shared/models'
import { VideoBlacklistService } from '@app/shared'
import { immutableAssign } from '@app/shared/misc/utils'
import { ScreenService } from '@app/shared/misc/screen.service'
import { MiniatureDisplayOptions } from '@app/shared/video/video-miniature.component'
+import { SelectionType } from '@app/shared/video/videos-selection.component'
+import { Video } from '@app/shared/video/video.model'
@Component({
selector: 'my-video-auto-blacklist-list',
templateUrl: './video-auto-blacklist-list.component.html',
styleUrls: [ './video-auto-blacklist-list.component.scss' ]
})
-export class VideoAutoBlacklistListComponent extends AbstractVideoList implements OnInit, OnDestroy {
+export class VideoAutoBlacklistListComponent {
titlePage: string
- checkedVideos: { [ id: number ]: boolean } = {}
- pagination: ComponentPagination = {
- currentPage: 1,
- itemsPerPage: 5,
- totalItems: null
- }
-
+ selection: SelectionType = {}
miniatureDisplayOptions: MiniatureDisplayOptions = {
date: true,
views: false,
blacklistInfo: false,
nsfw: true
}
+ pagination: ComponentPagination = {
+ currentPage: 1,
+ itemsPerPage: 5,
+ totalItems: null
+ }
+ videos: Video[] = []
+ getVideosObservableFunction = this.getVideosObservable.bind(this)
constructor (
protected router: Router,
private i18n: I18n,
private videoBlacklistService: VideoBlacklistService
) {
- super()
-
this.titlePage = this.i18n('Auto-blacklisted videos')
}
- ngOnInit () {
- super.ngOnInit()
- }
-
- ngOnDestroy () {
- super.ngOnDestroy()
- }
-
- abortSelectionMode () {
- this.checkedVideos = {}
- }
-
- isInSelectionMode () {
- return Object.keys(this.checkedVideos).some(k => this.checkedVideos[k] === true)
- }
-
getVideosObservable (page: number) {
const newPagination = immutableAssign(this.pagination, { currentPage: page })
return this.videoBlacklistService.getAutoBlacklistedAsVideoList(newPagination)
}
- generateSyndicationList () {
- throw new Error('Method not implemented.')
- }
-
removeVideoFromBlacklist (entry: Video) {
this.videoBlacklistService.removeVideoFromBlacklist(entry.id).subscribe(
() => {
this.notifier.success(this.i18n('Video {{name}} removed from blacklist.', { name: entry.name }))
- this.reloadVideos()
+
+ this.videos = this.videos.filter(v => v.id !== entry.id)
},
error => this.notifier.error(error.message)
}
removeSelectedVideosFromBlacklist () {
- const toReleaseVideosIds = Object.keys(this.checkedVideos)
- .filter(k => this.checkedVideos[ k ] === true)
+ const toReleaseVideosIds = Object.keys(this.selection)
+ .filter(k => this.selection[ k ] === true)
.map(k => parseInt(k, 10))
this.videoBlacklistService.removeVideoFromBlacklist(toReleaseVideosIds).subscribe(
() => {
this.notifier.success(this.i18n('{{num}} videos removed from blacklist.', { num: toReleaseVideosIds.length }))
- this.abortSelectionMode()
- this.reloadVideos()
+ this.selection = {}
+ this.videos = this.videos.filter(v => toReleaseVideosIds.includes(v.id) === false)
},
error => this.notifier.error(error.message)
-<div i18n *ngIf="pagination.totalItems === 0">No results.</div>
+<my-videos-selection
+ [(selection)]="selection"
+ [(videosModel)]="videos"
+ [miniatureDisplayOptions]="miniatureDisplayOptions"
+ [titlePage]="titlePage"
+ [getVideosObservableFunction]="getVideosObservableFunction"
+ #videosSelection
+>
+ <ng-template ptTemplate="globalButtons">
+ <span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()">
+ <my-global-icon iconName="delete"></my-global-icon>
+ <ng-container i18n>Delete</ng-container>
+ </span>
+ </ng-template>
+
+ <ng-template ptTemplate="rowButtons" let-video>
+ <my-delete-button (click)="deleteVideo(video)"></my-delete-button>
+
+ <my-edit-button [routerLink]="[ '/videos', 'update', video.uuid ]"></my-edit-button>
+
+ <my-button i18n-label label="Change ownership"
+ className="action-button-change-ownership"
+ icon="im-with-her"
+ (click)="changeOwnership($event, video)"
+ ></my-button>
+ </ng-template>
+</my-videos-selection>
-<div myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" class="videos">
- <div class="video" *ngFor="let video of videos; let i = index">
- <div class="checkbox-container">
- <my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="checkedVideos[video.id]"></my-peertube-checkbox>
- </div>
-
- <my-video-miniature [video]="video" [displayOptions]="miniatureDisplayOptions" [displayAsRow]="true"></my-video-miniature>
-
- <!-- Display only once -->
- <div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0">
- <div class="action-selection-mode-child">
- <span i18n class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
- Cancel
- </span>
-
- <span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()">
- <my-global-icon iconName="delete"></my-global-icon>
- <ng-container i18n>Delete</ng-container>
- </span>
- </div>
- </div>
-
- <div class="video-buttons" *ngIf="isInSelectionMode() === false">
- <my-delete-button (click)="deleteVideo(video)"></my-delete-button>
-
- <my-edit-button [routerLink]="[ '/videos', 'update', video.uuid ]"></my-edit-button>
-
- <my-button i18n-label label="Change ownership"
- className="action-button-change-ownership"
- icon="im-with-her"
- (click)="changeOwnership($event, video)"
- ></my-button>
- </div>
- </div>
-</div>
<my-video-change-ownership #videoChangeOwnershipModal></my-video-change-ownership>
@import '_variables';
@import '_mixins';
-.action-selection-mode {
- width: 174px;
- display: flex;
- justify-content: flex-end;
+.action-button-delete-selection {
+ display: inline-block;
- .action-selection-mode-child {
- position: fixed;
+ @include peertube-button;
+ @include orange-button;
+ @include button-with-icon(21px);
- .action-button {
- display: inline-block;
- }
-
- .action-button-cancel-selection {
- @include peertube-button;
- @include grey-button;
-
- margin-right: 10px;
- }
-
- .action-button-delete-selection {
- @include peertube-button;
- @include orange-button;
- @include button-with-icon(21px);
-
- my-global-icon {
- @include apply-svg-color(#fff);
- }
- }
- }
-}
-
-.video {
- @include row-blocks;
-
- &:first-child {
- margin-top: 47px;
- }
-
- .checkbox-container {
- display: flex;
- align-items: center;
- margin-right: 20px;
- margin-left: 12px;
- }
-
- my-video-miniature {
- flex-grow: 1;
- }
-
- .video-buttons {
- min-width: 190px;
-
- *:not(:last-child) {
- margin-right: 10px;
- }
+ my-global-icon {
+ @include apply-svg-color(#fff);
}
}
-@media screen and (max-width: $small-view) {
- .video {
- flex-direction: column;
- height: auto;
-
- .checkbox-container {
- display: none;
- }
-
- .video-buttons {
- margin-top: 10px;
- }
- }
+my-delete-button,
+my-edit-button {
+ margin-right: 10px;
}
import { concat, Observable } from 'rxjs'
import { tap, toArray } from 'rxjs/operators'
-import { Component, Inject, LOCALE_ID, OnDestroy, OnInit, ViewChild } from '@angular/core'
+import { Component, ViewChild } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { immutableAssign } from '@app/shared/misc/utils'
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
import { Notifier, ServerService } from '@app/core'
import { AuthService } from '../../core/auth'
import { ConfirmService } from '../../core/confirm'
-import { AbstractVideoList } from '../../shared/video/abstract-video-list'
import { Video } from '../../shared/video/video.model'
import { VideoService } from '../../shared/video/video.service'
import { I18n } from '@ngx-translate/i18n-polyfill'
-import { VideoPrivacy, VideoState } from '../../../../../shared/models/videos'
import { ScreenService } from '@app/shared/misc/screen.service'
import { VideoChangeOwnershipComponent } from './video-change-ownership/video-change-ownership.component'
import { MiniatureDisplayOptions } from '@app/shared/video/video-miniature.component'
+import { SelectionType, VideosSelectionComponent } from '@app/shared/video/videos-selection.component'
+import { VideoSortField } from '@app/shared/video/sort-field.type'
+import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
@Component({
selector: 'my-account-videos',
templateUrl: './my-account-videos.component.html',
styleUrls: [ './my-account-videos.component.scss' ]
})
-export class MyAccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy {
+export class MyAccountVideosComponent implements DisableForReuseHook {
+ @ViewChild('videosSelection') videosSelection: VideosSelectionComponent
@ViewChild('videoChangeOwnershipModal') videoChangeOwnershipModal: VideoChangeOwnershipComponent
titlePage: string
- checkedVideos: { [ id: number ]: boolean } = {}
+ selection: SelectionType = {}
pagination: ComponentPagination = {
currentPage: 1,
itemsPerPage: 5,
state: true,
blacklistInfo: true
}
+ videos: Video[] = []
+ getVideosObservableFunction = this.getVideosObservable.bind(this)
constructor (
protected router: Router,
protected screenService: ScreenService,
private i18n: I18n,
private confirmService: ConfirmService,
- private videoService: VideoService,
- @Inject(LOCALE_ID) private localeId: string
+ private videoService: VideoService
) {
- super()
-
this.titlePage = this.i18n('My videos')
}
- ngOnInit () {
- super.ngOnInit()
- }
-
- ngOnDestroy () {
- super.ngOnDestroy()
+ disableForReuse () {
+ this.videosSelection.disableForReuse()
}
- abortSelectionMode () {
- this.checkedVideos = {}
+ enabledForReuse () {
+ this.videosSelection.enabledForReuse()
}
- isInSelectionMode () {
- return Object.keys(this.checkedVideos).some(k => this.checkedVideos[ k ] === true)
- }
-
- getVideosObservable (page: number) {
+ getVideosObservable (page: number, sort: VideoSortField) {
const newPagination = immutableAssign(this.pagination, { currentPage: page })
- return this.videoService.getMyVideos(newPagination, this.sort)
- }
-
- generateSyndicationList () {
- throw new Error('Method not implemented.')
+ return this.videoService.getMyVideos(newPagination, sort)
}
async deleteSelectedVideos () {
- const toDeleteVideosIds = Object.keys(this.checkedVideos)
- .filter(k => this.checkedVideos[ k ] === true)
+ const toDeleteVideosIds = Object.keys(this.selection)
+ .filter(k => this.selection[ k ] === true)
.map(k => parseInt(k, 10))
const res = await this.confirmService.confirm(
() => {
this.notifier.success(this.i18n('{{deleteLength}} videos deleted.', { deleteLength: toDeleteVideosIds.length }))
- this.abortSelectionMode()
+ this.selection = {}
},
err => this.notifier.error(err.message)
.subscribe(
() => {
this.notifier.success(this.i18n('Video {{videoName}} deleted.', { videoName: video.name }))
- this.reloadVideos()
+ this.removeVideoFromArray(video.id)
},
error => this.notifier.error(error.message)
this.videoChangeOwnershipModal.show(video)
}
- getStateLabel (video: Video) {
- let suffix: string
-
- if (video.privacy.id !== VideoPrivacy.PRIVATE && video.state.id === VideoState.PUBLISHED) {
- suffix = this.i18n('Published')
- } else if (video.scheduledUpdate) {
- const updateAt = new Date(video.scheduledUpdate.updateAt.toString()).toLocaleString(this.localeId)
- suffix = this.i18n('Publication scheduled on ') + updateAt
- } else if (video.state.id === VideoState.TO_TRANSCODE && video.waitTranscoding === true) {
- suffix = this.i18n('Waiting transcoding')
- } else if (video.state.id === VideoState.TO_TRANSCODE) {
- suffix = this.i18n('To transcode')
- } else if (video.state.id === VideoState.TO_IMPORT) {
- suffix = this.i18n('To import')
- } else {
- return ''
- }
-
- return ' - ' + suffix
- }
-
private removeVideoFromArray (id: number) {
this.videos = this.videos.filter(v => v.id !== id)
}
--- /dev/null
+import { Pipe, PipeTransform } from '@angular/core'
+import { I18n } from '@ngx-translate/i18n-polyfill'
+
+// Thanks: https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site
+@Pipe({ name: 'myFromNow' })
+export class FromNowPipe implements PipeTransform {
+
+ constructor (private i18n: I18n) { }
+
+ transform (arg: number | Date | string) {
+ const argDate = new Date(arg)
+ const seconds = Math.floor((Date.now() - argDate.getTime()) / 1000)
+
+ let interval = Math.floor(seconds / 31536000)
+ if (interval > 1) {
+ return this.i18n('{{interval}} years ago', { interval })
+ }
+
+ interval = Math.floor(seconds / 2592000)
+ if (interval > 1) return this.i18n('{{interval}} months ago', { interval })
+ if (interval === 1) return this.i18n('{{interval}} month ago', { interval })
+
+ interval = Math.floor(seconds / 604800)
+ if (interval > 1) return this.i18n('{{interval}} weeks ago', { interval })
+ if (interval === 1) return this.i18n('{{interval}} week ago', { interval })
+
+ interval = Math.floor(seconds / 86400)
+ if (interval > 1) return this.i18n('{{interval}} days ago', { interval })
+ if (interval === 1) return this.i18n('{{interval}} day ago', { interval })
+
+ interval = Math.floor(seconds / 3600)
+ if (interval > 1) return this.i18n('{{interval}} hours ago', { interval })
+ if (interval === 1) return this.i18n('{{interval}} hour ago', { interval })
+
+ interval = Math.floor(seconds / 60)
+ if (interval >= 1) return this.i18n('{{interval}} min ago', { interval })
+
+ return this.i18n('{{interval}} sec ago', { interval: Math.max(0, seconds) })
+ }
+}
--- /dev/null
+import { Pipe, PipeTransform } from '@angular/core'
+
+// Thanks: https://github.com/danrevah/ngx-pipes/blob/master/src/pipes/math/bytes.ts
+
+@Pipe({ name: 'myNumberFormatter' })
+export class NumberFormatterPipe implements PipeTransform {
+ private dictionary: Array<{max: number, type: string}> = [
+ { max: 1000, type: '' },
+ { max: 1000000, type: 'K' },
+ { max: 1000000000, type: 'M' }
+ ]
+
+ transform (value: number) {
+ const format = this.dictionary.find(d => value < d.max) || this.dictionary[this.dictionary.length - 1]
+ const calc = Math.floor(value / (format.max / 1000))
+
+ return `${calc}${format.type}`
+ }
+}
--- /dev/null
+import { Pipe, PipeTransform } from '@angular/core'
+
+@Pipe({ name: 'myObjectLength' })
+export class ObjectLengthPipe implements PipeTransform {
+ transform (value: Object) {
+ return Object.keys(value).length
+ }
+}
--- /dev/null
+import { Directive, Input, TemplateRef } from '@angular/core'
+
+@Directive({
+ selector: '[ptTemplate]'
+})
+export class PeerTubeTemplateDirective {
+ @Input('ptTemplate') name: string
+
+ constructor (public template: TemplateRef<any>) {
+ // empty
+ }
+}
+++ /dev/null
-import { Pipe, PipeTransform } from '@angular/core'
-import { I18n } from '@ngx-translate/i18n-polyfill'
-
-// Thanks: https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site
-@Pipe({ name: 'myFromNow' })
-export class FromNowPipe implements PipeTransform {
-
- constructor (private i18n: I18n) { }
-
- transform (arg: number | Date | string) {
- const argDate = new Date(arg)
- const seconds = Math.floor((Date.now() - argDate.getTime()) / 1000)
-
- let interval = Math.floor(seconds / 31536000)
- if (interval > 1) {
- return this.i18n('{{interval}} years ago', { interval })
- }
-
- interval = Math.floor(seconds / 2592000)
- if (interval > 1) return this.i18n('{{interval}} months ago', { interval })
- if (interval === 1) return this.i18n('{{interval}} month ago', { interval })
-
- interval = Math.floor(seconds / 604800)
- if (interval > 1) return this.i18n('{{interval}} weeks ago', { interval })
- if (interval === 1) return this.i18n('{{interval}} week ago', { interval })
-
- interval = Math.floor(seconds / 86400)
- if (interval > 1) return this.i18n('{{interval}} days ago', { interval })
- if (interval === 1) return this.i18n('{{interval}} day ago', { interval })
-
- interval = Math.floor(seconds / 3600)
- if (interval > 1) return this.i18n('{{interval}} hours ago', { interval })
- if (interval === 1) return this.i18n('{{interval}} hour ago', { interval })
-
- interval = Math.floor(seconds / 60)
- if (interval >= 1) return this.i18n('{{interval}} min ago', { interval })
-
- return this.i18n('{{interval}} sec ago', { interval: Math.max(0, seconds) })
- }
-}
+++ /dev/null
-import { Pipe, PipeTransform } from '@angular/core'
-
-// Thanks: https://github.com/danrevah/ngx-pipes/blob/master/src/pipes/math/bytes.ts
-
-@Pipe({ name: 'myNumberFormatter' })
-export class NumberFormatterPipe implements PipeTransform {
- private dictionary: Array<{max: number, type: string}> = [
- { max: 1000, type: '' },
- { max: 1000000, type: 'K' },
- { max: 1000000000, type: 'M' }
- ]
-
- transform (value: number) {
- const format = this.dictionary.find(d => value < d.max) || this.dictionary[this.dictionary.length - 1]
- const calc = Math.floor(value / (format.max / 1000))
-
- return `${calc}${format.type}`
- }
-}
+++ /dev/null
-import { Pipe, PipeTransform } from '@angular/core'
-
-@Pipe({ name: 'myObjectLength' })
-export class ObjectLengthPipe implements PipeTransform {
- transform (value: Object) {
- return Object.keys(value).length
- }
-}
import { ButtonComponent } from './buttons/button.component'
import { DeleteButtonComponent } from './buttons/delete-button.component'
import { EditButtonComponent } from './buttons/edit-button.component'
-import { FromNowPipe } from './misc/from-now.pipe'
import { LoaderComponent } from './misc/loader.component'
-import { NumberFormatterPipe } from './misc/number-formatter.pipe'
-import { ObjectLengthPipe } from './misc/object-length.pipe'
import { RestExtractor, RestService } from './rest'
import { UserService } from './users'
import { VideoAbuseService } from './video-abuse'
import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component'
import { TimestampInputComponent } from '@app/shared/forms/timestamp-input.component'
import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playlist/video-playlist-element-miniature.component'
+import { VideosSelectionComponent } from '@app/shared/video/videos-selection.component'
+import { NumberFormatterPipe } from '@app/shared/angular/number-formatter.pipe'
+import { ObjectLengthPipe } from '@app/shared/angular/object-length.pipe'
+import { FromNowPipe } from '@app/shared/angular/from-now.pipe'
+import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive'
@NgModule({
imports: [
VideoPlaylistMiniatureComponent,
VideoAddToPlaylistComponent,
VideoPlaylistElementMiniatureComponent,
+ VideosSelectionComponent,
FeedComponent,
DeleteButtonComponent,
EditButtonComponent,
- ActionDropdownComponent,
NumberFormatterPipe,
ObjectLengthPipe,
FromNowPipe,
+ PeerTubeTemplateDirective,
+
+ ActionDropdownComponent,
MarkdownTextareaComponent,
InfiniteScrollerDirective,
TextareaAutoResizeDirective,
VideoPlaylistMiniatureComponent,
VideoAddToPlaylistComponent,
VideoPlaylistElementMiniatureComponent,
+ VideosSelectionComponent,
FeedComponent,
NumberFormatterPipe,
ObjectLengthPipe,
- FromNowPipe
+ FromNowPipe,
+ PeerTubeTemplateDirective
],
providers: [
({ videos, totalVideos }) => {
this.pagination.totalItems = totalVideos
this.videos = this.videos.concat(videos)
+
+ this.onMoreVideos()
},
error => this.notifier.error(error.message)
throw new Error('toggleModerationDisplay is not implemented')
}
+ // On videos hook for children that want to do something
+ protected onMoreVideos () { /* empty */ }
+
protected loadRouteParams (routeParams: { [ key: string ]: any }) {
this.sort = routeParams[ 'sort' ] as VideoSortField || this.defaultSort
this.categoryOneOf = routeParams[ 'categoryOneOf' ]
--- /dev/null
+<div class="no-results" i18n *ngIf="pagination.totalItems === 0">No results.</div>
+
+<div myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" class="videos">
+ <div class="video" *ngFor="let video of videos; let i = index">
+ <div class="checkbox-container">
+ <my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="_selection[video.id]"></my-peertube-checkbox>
+ </div>
+
+ <my-video-miniature [video]="video" [displayAsRow]="true" [displayOptions]="miniatureDisplayOptions"></my-video-miniature>
+
+ <!-- Display only once -->
+ <div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0">
+ <div class="action-selection-mode-child">
+ <span i18n class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
+ Cancel
+ </span>
+
+ <ng-container *ngTemplateOutlet="globalButtonsTemplate"></ng-container>
+ </div>
+ </div>
+
+ <ng-container *ngIf="isInSelectionMode() === false">
+ <ng-container *ngTemplateOutlet="rowButtonsTemplate; context: {$implicit: video}"></ng-container>
+ </ng-container>
+ </div>
+</div>
--- /dev/null
+@import '_variables';
+@import '_mixins';
+
+.action-selection-mode {
+ display: flex;
+ justify-content: flex-end;
+ flex-grow: 1;
+
+ .action-selection-mode-child {
+ position: fixed;
+
+ .action-button {
+ display: inline-block;
+ }
+
+ .action-button-cancel-selection {
+ @include peertube-button;
+ @include grey-button;
+
+ margin-right: 10px;
+ }
+ }
+}
+
+.video {
+ @include row-blocks;
+
+ &:first-child {
+ margin-top: 47px;
+ }
+
+ .checkbox-container {
+ display: flex;
+ align-items: center;
+ margin-right: 20px;
+ margin-left: 12px;
+ }
+
+ my-video-miniature {
+ flex-grow: 1;
+ }
+}
+
+@media screen and (max-width: $small-view) {
+ .video {
+ flex-direction: column;
+ height: auto;
+
+ .checkbox-container {
+ display: none;
+ }
+
+ my-button {
+ margin-top: 10px;
+ }
+ }
+}
--- /dev/null
+import {
+ AfterContentInit,
+ Component,
+ ContentChildren,
+ EventEmitter,
+ Input,
+ OnDestroy,
+ OnInit,
+ Output,
+ QueryList,
+ TemplateRef
+} from '@angular/core'
+import { ActivatedRoute, Router } from '@angular/router'
+import { AbstractVideoList } from '@app/shared/video/abstract-video-list'
+import { AuthService, Notifier, ServerService } from '@app/core'
+import { ScreenService } from '@app/shared/misc/screen.service'
+import { MiniatureDisplayOptions } from '@app/shared/video/video-miniature.component'
+import { Observable } from 'rxjs'
+import { Video } from '@app/shared/video/video.model'
+import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive'
+import { VideoSortField } from '@app/shared/video/sort-field.type'
+
+export type SelectionType = { [ id: number ]: boolean }
+
+@Component({
+ selector: 'my-videos-selection',
+ templateUrl: './videos-selection.component.html',
+ styleUrls: [ './videos-selection.component.scss' ]
+})
+export class VideosSelectionComponent extends AbstractVideoList implements OnInit, OnDestroy, AfterContentInit {
+ @Input() titlePage: string
+ @Input() miniatureDisplayOptions: MiniatureDisplayOptions
+ @Input() getVideosObservableFunction: (page: number, sort?: VideoSortField) => Observable<{ videos: Video[], totalVideos: number }>
+ @ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective>
+
+ @Output() selectionChange = new EventEmitter<SelectionType>()
+ @Output() videosModelChange = new EventEmitter<Video[]>()
+
+ _selection: SelectionType = {}
+
+ rowButtonsTemplate: TemplateRef<any>
+ globalButtonsTemplate: TemplateRef<any>
+
+ constructor (
+ protected router: Router,
+ protected route: ActivatedRoute,
+ protected notifier: Notifier,
+ protected authService: AuthService,
+ protected screenService: ScreenService,
+ protected serverService: ServerService
+ ) {
+ super()
+ }
+
+ ngAfterContentInit () {
+ {
+ const t = this.templates.find(t => t.name === 'rowButtons')
+ if (t) this.rowButtonsTemplate = t.template
+ }
+
+ {
+ const t = this.templates.find(t => t.name === 'globalButtons')
+ if (t) this.globalButtonsTemplate = t.template
+ }
+ }
+
+ @Input() get selection () {
+ return this._selection
+ }
+
+ set selection (selection: SelectionType) {
+ this._selection = selection
+ this.selectionChange.emit(this._selection)
+ }
+
+ @Input() get videosModel () {
+ return this.videos
+ }
+
+ set videosModel (videos: Video[]) {
+ this.videos = videos
+ this.videosModelChange.emit(this.videos)
+ }
+
+ ngOnInit () {
+ super.ngOnInit()
+ }
+
+ ngOnDestroy () {
+ super.ngOnDestroy()
+ }
+
+ getVideosObservable (page: number) {
+ return this.getVideosObservableFunction(page, this.sort)
+ }
+
+ abortSelectionMode () {
+ this._selection = {}
+ }
+
+ isInSelectionMode () {
+ return Object.keys(this._selection).some(k => this._selection[ k ] === true)
+ }
+
+ generateSyndicationList () {
+ throw new Error('Method not implemented.')
+ }
+
+ protected onMoreVideos () {
+ this.videosModel = this.videos
+ }
+}