Refactor videos selection components

This commit is contained in:
Chocobozzz 2019-04-04 10:44:18 +02:00
parent 9ba1d64b1a
commit 693263e936
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
15 changed files with 320 additions and 285 deletions

View File

@ -1,33 +1,19 @@
<div i18n *ngIf="pagination.totalItems === 0">No results.</div> <my-videos-selection
[(selection)]="selection"
<div myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" class="videos"> [(videosModel)]="videos"
<div class="video" *ngFor="let video of videos; let i = index"> [miniatureDisplayOptions]="miniatureDisplayOptions"
<div class="checkbox-container"> [titlePage]="titlePage"
<my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="checkedVideos[video.id]"></my-peertube-checkbox> [getVideosObservableFunction]="getVideosObservableFunction"
</div> >
<ng-template ptTemplate="globalButtons">
<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()"> <span class="action-button action-button-unblacklist-selection" (click)="removeSelectedVideosFromBlacklist()">
<my-global-icon iconName="tick"></my-global-icon> <my-global-icon iconName="tick"></my-global-icon>
<ng-container i18n>Unblacklist</ng-container> <ng-container i18n>Unblacklist</ng-container>
</span> </span>
</div> </ng-template>
</div>
<my-button <ng-template ptTemplate="rowButtons" let-video>
*ngIf="isInSelectionMode() === false" <my-button i18n-label label="Unblacklist" icon="tick" (click)="removeVideoFromBlacklist(video)"></my-button>
i18n-label </ng-template>
label="Unblacklist"
icon="tick" </my-videos-selection>
(click)="removeVideoFromBlacklist(video)"
></my-button>
</div>
</div>

View File

@ -1,26 +1,9 @@
@import '_variables'; @import '_variables';
@import '_mixins'; @import '_mixins';
.action-selection-mode { .action-button-unblacklist-selection {
width: 194px;
display: flex;
justify-content: flex-end;
.action-selection-mode-child {
position: fixed;
.action-button {
display: inline-block; display: inline-block;
}
.action-button-cancel-selection {
@include peertube-button;
@include grey-button;
margin-right: 10px;
}
.action-button-unblacklist-selection {
@include peertube-button; @include peertube-button;
@include orange-button; @include orange-button;
@include button-with-icon(21px); @include button-with-icon(21px);
@ -28,40 +11,4 @@
my-global-icon { my-global-icon {
@include apply-svg-color(#fff); @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;
}
}
} }

View File

@ -1,29 +1,23 @@
import { Component, OnDestroy, OnInit } from '@angular/core' import { Component } from '@angular/core'
import { I18n } from '@ngx-translate/i18n-polyfill' import { I18n } from '@ngx-translate/i18n-polyfill'
import { ActivatedRoute, Router } from '@angular/router' 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 { ComponentPagination } from '@app/shared/rest/component-pagination.model'
import { AuthService, Notifier, ServerService } from '@app/core' import { AuthService, Notifier, ServerService } from '@app/core'
import { Video } from '@shared/models'
import { VideoBlacklistService } from '@app/shared' import { VideoBlacklistService } from '@app/shared'
import { immutableAssign } from '@app/shared/misc/utils' import { immutableAssign } from '@app/shared/misc/utils'
import { ScreenService } from '@app/shared/misc/screen.service' import { ScreenService } from '@app/shared/misc/screen.service'
import { MiniatureDisplayOptions } from '@app/shared/video/video-miniature.component' 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({ @Component({
selector: 'my-video-auto-blacklist-list', selector: 'my-video-auto-blacklist-list',
templateUrl: './video-auto-blacklist-list.component.html', templateUrl: './video-auto-blacklist-list.component.html',
styleUrls: [ './video-auto-blacklist-list.component.scss' ] styleUrls: [ './video-auto-blacklist-list.component.scss' ]
}) })
export class VideoAutoBlacklistListComponent extends AbstractVideoList implements OnInit, OnDestroy { export class VideoAutoBlacklistListComponent {
titlePage: string titlePage: string
checkedVideos: { [ id: number ]: boolean } = {} selection: SelectionType = {}
pagination: ComponentPagination = {
currentPage: 1,
itemsPerPage: 5,
totalItems: null
}
miniatureDisplayOptions: MiniatureDisplayOptions = { miniatureDisplayOptions: MiniatureDisplayOptions = {
date: true, date: true,
views: false, views: false,
@ -34,6 +28,13 @@ export class VideoAutoBlacklistListComponent extends AbstractVideoList implement
blacklistInfo: false, blacklistInfo: false,
nsfw: true nsfw: true
} }
pagination: ComponentPagination = {
currentPage: 1,
itemsPerPage: 5,
totalItems: null
}
videos: Video[] = []
getVideosObservableFunction = this.getVideosObservable.bind(this)
constructor ( constructor (
protected router: Router, protected router: Router,
@ -45,42 +46,21 @@ export class VideoAutoBlacklistListComponent extends AbstractVideoList implement
private i18n: I18n, private i18n: I18n,
private videoBlacklistService: VideoBlacklistService private videoBlacklistService: VideoBlacklistService
) { ) {
super()
this.titlePage = this.i18n('Auto-blacklisted videos') 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) { getVideosObservable (page: number) {
const newPagination = immutableAssign(this.pagination, { currentPage: page }) const newPagination = immutableAssign(this.pagination, { currentPage: page })
return this.videoBlacklistService.getAutoBlacklistedAsVideoList(newPagination) return this.videoBlacklistService.getAutoBlacklistedAsVideoList(newPagination)
} }
generateSyndicationList () {
throw new Error('Method not implemented.')
}
removeVideoFromBlacklist (entry: Video) { removeVideoFromBlacklist (entry: Video) {
this.videoBlacklistService.removeVideoFromBlacklist(entry.id).subscribe( this.videoBlacklistService.removeVideoFromBlacklist(entry.id).subscribe(
() => { () => {
this.notifier.success(this.i18n('Video {{name}} removed from blacklist.', { name: entry.name })) 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) error => this.notifier.error(error.message)
@ -88,16 +68,16 @@ export class VideoAutoBlacklistListComponent extends AbstractVideoList implement
} }
removeSelectedVideosFromBlacklist () { removeSelectedVideosFromBlacklist () {
const toReleaseVideosIds = Object.keys(this.checkedVideos) const toReleaseVideosIds = Object.keys(this.selection)
.filter(k => this.checkedVideos[ k ] === true) .filter(k => this.selection[ k ] === true)
.map(k => parseInt(k, 10)) .map(k => parseInt(k, 10))
this.videoBlacklistService.removeVideoFromBlacklist(toReleaseVideosIds).subscribe( this.videoBlacklistService.removeVideoFromBlacklist(toReleaseVideosIds).subscribe(
() => { () => {
this.notifier.success(this.i18n('{{num}} videos removed from blacklist.', { num: toReleaseVideosIds.length })) this.notifier.success(this.i18n('{{num}} videos removed from blacklist.', { num: toReleaseVideosIds.length }))
this.abortSelectionMode() this.selection = {}
this.reloadVideos() this.videos = this.videos.filter(v => toReleaseVideosIds.includes(v.id) === false)
}, },
error => this.notifier.error(error.message) error => this.notifier.error(error.message)

View File

@ -1,28 +1,19 @@
<div i18n *ngIf="pagination.totalItems === 0">No results.</div> <my-videos-selection
[(selection)]="selection"
<div myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" class="videos"> [(videosModel)]="videos"
<div class="video" *ngFor="let video of videos; let i = index"> [miniatureDisplayOptions]="miniatureDisplayOptions"
<div class="checkbox-container"> [titlePage]="titlePage"
<my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="checkedVideos[video.id]"></my-peertube-checkbox> [getVideosObservableFunction]="getVideosObservableFunction"
</div> #videosSelection
>
<my-video-miniature [video]="video" [displayOptions]="miniatureDisplayOptions" [displayAsRow]="true"></my-video-miniature> <ng-template ptTemplate="globalButtons">
<!-- 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()"> <span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()">
<my-global-icon iconName="delete"></my-global-icon> <my-global-icon iconName="delete"></my-global-icon>
<ng-container i18n>Delete</ng-container> <ng-container i18n>Delete</ng-container>
</span> </span>
</div> </ng-template>
</div>
<div class="video-buttons" *ngIf="isInSelectionMode() === false"> <ng-template ptTemplate="rowButtons" let-video>
<my-delete-button (click)="deleteVideo(video)"></my-delete-button> <my-delete-button (click)="deleteVideo(video)"></my-delete-button>
<my-edit-button [routerLink]="[ '/videos', 'update', video.uuid ]"></my-edit-button> <my-edit-button [routerLink]="[ '/videos', 'update', video.uuid ]"></my-edit-button>
@ -32,8 +23,8 @@
icon="im-with-her" icon="im-with-her"
(click)="changeOwnership($event, video)" (click)="changeOwnership($event, video)"
></my-button> ></my-button>
</div> </ng-template>
</div> </my-videos-selection>
</div>
<my-video-change-ownership #videoChangeOwnershipModal></my-video-change-ownership> <my-video-change-ownership #videoChangeOwnershipModal></my-video-change-ownership>

View File

@ -1,26 +1,9 @@
@import '_variables'; @import '_variables';
@import '_mixins'; @import '_mixins';
.action-selection-mode { .action-button-delete-selection {
width: 174px;
display: flex;
justify-content: flex-end;
.action-selection-mode-child {
position: fixed;
.action-button {
display: inline-block; display: inline-block;
}
.action-button-cancel-selection {
@include peertube-button;
@include grey-button;
margin-right: 10px;
}
.action-button-delete-selection {
@include peertube-button; @include peertube-button;
@include orange-button; @include orange-button;
@include button-with-icon(21px); @include button-with-icon(21px);
@ -28,48 +11,9 @@
my-global-icon { my-global-icon {
@include apply-svg-color(#fff); @include apply-svg-color(#fff);
} }
}
}
} }
.video { my-delete-button,
@include row-blocks; my-edit-button {
&: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; margin-right: 10px;
}
}
}
@media screen and (max-width: $small-view) {
.video {
flex-direction: column;
height: auto;
.checkbox-container {
display: none;
}
.video-buttons {
margin-top: 10px;
}
}
} }

View File

@ -1,31 +1,33 @@
import { concat, Observable } from 'rxjs' import { concat, Observable } from 'rxjs'
import { tap, toArray } from 'rxjs/operators' 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 { ActivatedRoute, Router } from '@angular/router'
import { immutableAssign } from '@app/shared/misc/utils' import { immutableAssign } from '@app/shared/misc/utils'
import { ComponentPagination } from '@app/shared/rest/component-pagination.model' import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
import { Notifier, ServerService } from '@app/core' import { Notifier, ServerService } from '@app/core'
import { AuthService } from '../../core/auth' import { AuthService } from '../../core/auth'
import { ConfirmService } from '../../core/confirm' import { ConfirmService } from '../../core/confirm'
import { AbstractVideoList } from '../../shared/video/abstract-video-list'
import { Video } from '../../shared/video/video.model' import { Video } from '../../shared/video/video.model'
import { VideoService } from '../../shared/video/video.service' import { VideoService } from '../../shared/video/video.service'
import { I18n } from '@ngx-translate/i18n-polyfill' import { I18n } from '@ngx-translate/i18n-polyfill'
import { VideoPrivacy, VideoState } from '../../../../../shared/models/videos'
import { ScreenService } from '@app/shared/misc/screen.service' import { ScreenService } from '@app/shared/misc/screen.service'
import { VideoChangeOwnershipComponent } from './video-change-ownership/video-change-ownership.component' import { VideoChangeOwnershipComponent } from './video-change-ownership/video-change-ownership.component'
import { MiniatureDisplayOptions } from '@app/shared/video/video-miniature.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({ @Component({
selector: 'my-account-videos', selector: 'my-account-videos',
templateUrl: './my-account-videos.component.html', templateUrl: './my-account-videos.component.html',
styleUrls: [ './my-account-videos.component.scss' ] 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 @ViewChild('videoChangeOwnershipModal') videoChangeOwnershipModal: VideoChangeOwnershipComponent
titlePage: string titlePage: string
checkedVideos: { [ id: number ]: boolean } = {} selection: SelectionType = {}
pagination: ComponentPagination = { pagination: ComponentPagination = {
currentPage: 1, currentPage: 1,
itemsPerPage: 5, itemsPerPage: 5,
@ -40,6 +42,8 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
state: true, state: true,
blacklistInfo: true blacklistInfo: true
} }
videos: Video[] = []
getVideosObservableFunction = this.getVideosObservable.bind(this)
constructor ( constructor (
protected router: Router, protected router: Router,
@ -50,43 +54,28 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
protected screenService: ScreenService, protected screenService: ScreenService,
private i18n: I18n, private i18n: I18n,
private confirmService: ConfirmService, private confirmService: ConfirmService,
private videoService: VideoService, private videoService: VideoService
@Inject(LOCALE_ID) private localeId: string
) { ) {
super()
this.titlePage = this.i18n('My videos') this.titlePage = this.i18n('My videos')
} }
ngOnInit () { disableForReuse () {
super.ngOnInit() this.videosSelection.disableForReuse()
} }
ngOnDestroy () { enabledForReuse () {
super.ngOnDestroy() this.videosSelection.enabledForReuse()
} }
abortSelectionMode () { getVideosObservable (page: number, sort: VideoSortField) {
this.checkedVideos = {}
}
isInSelectionMode () {
return Object.keys(this.checkedVideos).some(k => this.checkedVideos[ k ] === true)
}
getVideosObservable (page: number) {
const newPagination = immutableAssign(this.pagination, { currentPage: page }) const newPagination = immutableAssign(this.pagination, { currentPage: page })
return this.videoService.getMyVideos(newPagination, this.sort) return this.videoService.getMyVideos(newPagination, sort)
}
generateSyndicationList () {
throw new Error('Method not implemented.')
} }
async deleteSelectedVideos () { async deleteSelectedVideos () {
const toDeleteVideosIds = Object.keys(this.checkedVideos) const toDeleteVideosIds = Object.keys(this.selection)
.filter(k => this.checkedVideos[ k ] === true) .filter(k => this.selection[ k ] === true)
.map(k => parseInt(k, 10)) .map(k => parseInt(k, 10))
const res = await this.confirmService.confirm( const res = await this.confirmService.confirm(
@ -109,7 +98,7 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
() => { () => {
this.notifier.success(this.i18n('{{deleteLength}} videos deleted.', { deleteLength: toDeleteVideosIds.length })) this.notifier.success(this.i18n('{{deleteLength}} videos deleted.', { deleteLength: toDeleteVideosIds.length }))
this.abortSelectionMode() this.selection = {}
}, },
err => this.notifier.error(err.message) err => this.notifier.error(err.message)
@ -127,7 +116,7 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
.subscribe( .subscribe(
() => { () => {
this.notifier.success(this.i18n('Video {{videoName}} deleted.', { videoName: video.name })) this.notifier.success(this.i18n('Video {{videoName}} deleted.', { videoName: video.name }))
this.reloadVideos() this.removeVideoFromArray(video.id)
}, },
error => this.notifier.error(error.message) error => this.notifier.error(error.message)
@ -139,27 +128,6 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
this.videoChangeOwnershipModal.show(video) 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) { private removeVideoFromArray (id: number) {
this.videos = this.videos.filter(v => v.id !== id) this.videos = this.videos.filter(v => v.id !== id)
} }

View File

@ -0,0 +1,12 @@
import { Directive, Input, TemplateRef } from '@angular/core'
@Directive({
selector: '[ptTemplate]'
})
export class PeerTubeTemplateDirective {
@Input('ptTemplate') name: string
constructor (public template: TemplateRef<any>) {
// empty
}
}

View File

@ -14,10 +14,7 @@ import { AUTH_INTERCEPTOR_PROVIDER } from './auth'
import { ButtonComponent } from './buttons/button.component' import { ButtonComponent } from './buttons/button.component'
import { DeleteButtonComponent } from './buttons/delete-button.component' import { DeleteButtonComponent } from './buttons/delete-button.component'
import { EditButtonComponent } from './buttons/edit-button.component' import { EditButtonComponent } from './buttons/edit-button.component'
import { FromNowPipe } from './misc/from-now.pipe'
import { LoaderComponent } from './misc/loader.component' 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 { RestExtractor, RestService } from './rest'
import { UserService } from './users' import { UserService } from './users'
import { VideoAbuseService } from './video-abuse' import { VideoAbuseService } from './video-abuse'
@ -78,6 +75,11 @@ import { VideoPlaylistMiniatureComponent } from '@app/shared/video-playlist/vide
import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component' import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component'
import { TimestampInputComponent } from '@app/shared/forms/timestamp-input.component' import { TimestampInputComponent } from '@app/shared/forms/timestamp-input.component'
import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playlist/video-playlist-element-miniature.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({ @NgModule({
imports: [ imports: [
@ -107,6 +109,7 @@ import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playli
VideoPlaylistMiniatureComponent, VideoPlaylistMiniatureComponent,
VideoAddToPlaylistComponent, VideoAddToPlaylistComponent,
VideoPlaylistElementMiniatureComponent, VideoPlaylistElementMiniatureComponent,
VideosSelectionComponent,
FeedComponent, FeedComponent,
@ -114,10 +117,12 @@ import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playli
DeleteButtonComponent, DeleteButtonComponent,
EditButtonComponent, EditButtonComponent,
ActionDropdownComponent,
NumberFormatterPipe, NumberFormatterPipe,
ObjectLengthPipe, ObjectLengthPipe,
FromNowPipe, FromNowPipe,
PeerTubeTemplateDirective,
ActionDropdownComponent,
MarkdownTextareaComponent, MarkdownTextareaComponent,
InfiniteScrollerDirective, InfiniteScrollerDirective,
TextareaAutoResizeDirective, TextareaAutoResizeDirective,
@ -166,6 +171,7 @@ import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playli
VideoPlaylistMiniatureComponent, VideoPlaylistMiniatureComponent,
VideoAddToPlaylistComponent, VideoAddToPlaylistComponent,
VideoPlaylistElementMiniatureComponent, VideoPlaylistElementMiniatureComponent,
VideosSelectionComponent,
FeedComponent, FeedComponent,
@ -197,7 +203,8 @@ import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playli
NumberFormatterPipe, NumberFormatterPipe,
ObjectLengthPipe, ObjectLengthPipe,
FromNowPipe FromNowPipe,
PeerTubeTemplateDirective
], ],
providers: [ providers: [

View File

@ -102,6 +102,8 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
({ videos, totalVideos }) => { ({ videos, totalVideos }) => {
this.pagination.totalItems = totalVideos this.pagination.totalItems = totalVideos
this.videos = this.videos.concat(videos) this.videos = this.videos.concat(videos)
this.onMoreVideos()
}, },
error => this.notifier.error(error.message) error => this.notifier.error(error.message)
@ -118,6 +120,9 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
throw new Error('toggleModerationDisplay is not implemented') 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 }) { protected loadRouteParams (routeParams: { [ key: string ]: any }) {
this.sort = routeParams[ 'sort' ] as VideoSortField || this.defaultSort this.sort = routeParams[ 'sort' ] as VideoSortField || this.defaultSort
this.categoryOneOf = routeParams[ 'categoryOneOf' ] this.categoryOneOf = routeParams[ 'categoryOneOf' ]

View File

@ -0,0 +1,26 @@
<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>

View File

@ -0,0 +1,57 @@
@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;
}
}
}

View File

@ -0,0 +1,112 @@
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
}
}