Add search bars for a user's videos and playlist library

pull/2367/head
Rigel Kent 2019-12-28 01:10:26 +01:00
parent 71810d0bcb
commit bf64ed4196
No known key found for this signature in database
GPG Key ID: 5E53E96A494E452F
15 changed files with 98 additions and 17 deletions

View File

@ -1,4 +1,6 @@
<div class="video-playlists-header">
<input type="text" placeholder="Search your playlists" i18n-placeholder [(ngModel)]="videoPlaylistsSearch" (ngModelChange)="onVideoPlaylistSearchChanged()" />
<a class="create-button" routerLink="create">
<my-global-icon iconName="add"></my-global-icon>
<ng-container i18n>Create a new playlist</ng-container>

View File

@ -29,12 +29,19 @@
.video-playlist-buttons {
min-width: 190px;
height: max-content;
}
}
.video-playlists-header {
display: flex;
justify-content: space-between;
text-align: right;
margin: 20px 0 50px;
input[type=text] {
@include peertube-input-text(300px);
}
}
@media screen and (max-width: 800px) {

View File

@ -3,7 +3,7 @@ import { Notifier } from '@app/core'
import { AuthService } from '../../core/auth'
import { ConfirmService } from '../../core/confirm'
import { User } from '@app/shared'
import { flatMap } from 'rxjs/operators'
import { flatMap, debounceTime } from 'rxjs/operators'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
@ -17,7 +17,9 @@ import { Subject } from 'rxjs'
styleUrls: [ './my-account-video-playlists.component.scss' ]
})
export class MyAccountVideoPlaylistsComponent implements OnInit {
videoPlaylistsSearch: string
videoPlaylists: VideoPlaylist[] = []
videoPlaylistSearchChanged = new Subject<string>()
pagination: ComponentPagination = {
currentPage: 1,
@ -41,6 +43,13 @@ export class MyAccountVideoPlaylistsComponent implements OnInit {
this.user = this.authService.getUser()
this.loadVideoPlaylists()
this.videoPlaylistSearchChanged
.pipe(
debounceTime(500))
.subscribe(() => {
this.loadVideoPlaylists()
})
}
async deleteVideoPlaylist (videoPlaylist: VideoPlaylist) {
@ -80,12 +89,17 @@ export class MyAccountVideoPlaylistsComponent implements OnInit {
this.loadVideoPlaylists()
}
onVideoPlaylistSearchChanged () {
this.videoPlaylistSearchChanged.next()
}
private loadVideoPlaylists () {
this.authService.userInformationLoaded
.pipe(flatMap(() => {
return this.videoPlaylistService.listAccountPlaylists(this.user.account, this.pagination, '-updatedAt')
return this.videoPlaylistService.listAccountPlaylists(this.user.account, this.pagination, '-updatedAt', this.videoPlaylistsSearch)
}))
.subscribe(res => {
this.videoPlaylists = []
this.videoPlaylists = this.videoPlaylists.concat(res.data)
this.pagination.totalItems = res.total

View File

@ -1,3 +1,7 @@
<div class="videos-header">
<input type="text" placeholder="Search your videos" i18n-placeholder [(ngModel)]="videosSearch" (ngModelChange)="onVideosSearchChanged()" />
</div>
<my-videos-selection
[pagination]="pagination"
[(selection)]="selection"

View File

@ -1,6 +1,17 @@
@import '_variables';
@import '_mixins';
.videos-header {
display: flex;
justify-content: space-between;
text-align: right;
margin: 20px 0 50px;
input[type=text] {
@include peertube-input-text(300px);
}
}
.action-button-delete-selection {
display: inline-block;

View File

@ -1,6 +1,6 @@
import { concat, Observable } from 'rxjs'
import { tap, toArray } from 'rxjs/operators'
import { Component, ViewChild } from '@angular/core'
import { concat, Observable, Subject } from 'rxjs'
import { tap, toArray, debounceTime } from 'rxjs/operators'
import { Component, ViewChild, OnInit } 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'
@ -22,7 +22,7 @@ import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
templateUrl: './my-account-videos.component.html',
styleUrls: [ './my-account-videos.component.scss' ]
})
export class MyAccountVideosComponent implements DisableForReuseHook {
export class MyAccountVideosComponent implements OnInit, DisableForReuseHook {
@ViewChild('videosSelection', { static: true }) videosSelection: VideosSelectionComponent
@ViewChild('videoChangeOwnershipModal', { static: true }) videoChangeOwnershipModal: VideoChangeOwnershipComponent
@ -43,6 +43,8 @@ export class MyAccountVideosComponent implements DisableForReuseHook {
blacklistInfo: true
}
videos: Video[] = []
videosSearch: string
videosSearchChanged = new Subject<string>()
getVideosObservableFunction = this.getVideosObservable.bind(this)
constructor (
@ -59,6 +61,19 @@ export class MyAccountVideosComponent implements DisableForReuseHook {
this.titlePage = this.i18n('My videos')
}
ngOnInit () {
this.videosSearchChanged
.pipe(
debounceTime(500))
.subscribe(() => {
this.videosSelection.reloadVideos()
})
}
onVideosSearchChanged () {
this.videosSearchChanged.next()
}
disableForReuse () {
this.videosSelection.disableForReuse()
}
@ -70,7 +85,7 @@ export class MyAccountVideosComponent implements DisableForReuseHook {
getVideosObservable (page: number, sort: VideoSortField) {
const newPagination = immutableAssign(this.pagination, { currentPage: page })
return this.videoService.getMyVideos(newPagination, sort)
return this.videoService.getMyVideos(newPagination, sort, this.videosSearch)
}
async deleteSelectedVideos () {

View File

@ -88,7 +88,7 @@ export class UserService {
}
getMyVideoQuotaUsed () {
const url = UserService.BASE_USERS_URL + '/me/video-quota-used'
const url = UserService.BASE_USERS_URL + 'me/video-quota-used'
return this.authHttp.get<UserVideoQuota>(url)
.pipe(catchError(err => this.restExtractor.handleError(err)))

View File

@ -42,7 +42,7 @@
</div>
<div class="input-container">
<input type="text" placeholder="Search playlists" [(ngModel)]="videoPlaylistSearch" (ngModelChange)="onVideoPlaylistSearchChanged()" />
<input type="text" placeholder="Search playlists" i18n-placeholder [(ngModel)]="videoPlaylistSearch" (ngModelChange)="onVideoPlaylistSearchChanged()" />
</div>
<div class="playlists">

View File

@ -150,6 +150,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
this.getVideosObservable(this.pagination.currentPage).subscribe(
({ data, total }) => {
this.pagination.totalItems = total
this.videos = []
this.videos = this.videos.concat(data)
if (this.groupByDate) this.buildGroupedDateLabels()
@ -170,7 +171,6 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
reloadVideos () {
this.pagination.currentPage = 1
this.videos = []
this.loadMoreVideos()
}

View File

@ -100,7 +100,8 @@ $more-margin-right: 15px;
flex-direction: row;
margin-bottom: 0;
height: auto;
width: 100%;
display: flex;
flex-grow: 1;
my-video-thumbnail {
margin-right: 10px;

View File

@ -121,14 +121,15 @@ export class VideoService implements VideosProvider {
.pipe(catchError(err => this.restExtractor.handleError(err)))
}
getMyVideos (videoPagination: ComponentPagination, sort: VideoSortField): Observable<ResultList<Video>> {
getMyVideos (videoPagination: ComponentPagination, sort: VideoSortField, search?: string): Observable<ResultList<Video>> {
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
let params = new HttpParams()
params = this.restService.addRestGetParams(params, pagination, sort)
params = this.restService.addObjectParams(params, { search })
return this.authHttp
.get<ResultList<Video>>(UserService.BASE_USERS_URL + '/me/videos', { params })
.get<ResultList<Video>>(UserService.BASE_USERS_URL + 'me/videos', { params })
.pipe(
switchMap(res => this.extractVideos(res)),
catchError(err => this.restExtractor.handleError(err))

View File

@ -27,6 +27,7 @@
font-weight: 600;
text-transform: uppercase;
border: none;
transform: translateY(-7%);
}
my-feed {

View File

@ -10,6 +10,12 @@
color: var(--mainForegroundColor) !important;
}
my-edit-button,
my-delete-button,
my-button {
height: max-content;
}
// data table customizations
p-table {
.ui-table-caption {

View File

@ -99,7 +99,8 @@ async function getUserVideos (req: express.Request, res: express.Response) {
user.Account.id,
req.query.start as number,
req.query.count as number,
req.query.sort as VideoSortField
req.query.sort as VideoSortField,
req.query.search as string
)
const additionalAttributes = {

View File

@ -1196,9 +1196,15 @@ export class VideoModel extends Model<VideoModel> {
})
}
static listUserVideosForApi (accountId: number, start: number, count: number, sort: string) {
static listUserVideosForApi (
accountId: number,
start: number,
count: number,
sort: string,
search?: string
) {
function buildBaseQuery (): FindOptions {
return {
let baseQuery = {
offset: start,
limit: count,
order: getVideoSort(sort),
@ -1218,12 +1224,24 @@ export class VideoModel extends Model<VideoModel> {
}
]
}
if (search) {
baseQuery = Object.assign(baseQuery, {
where: {
name: {
[ Op.iLike ]: '%' + search + '%'
}
}
})
}
return baseQuery
}
const countQuery = buildBaseQuery()
const findQuery = buildBaseQuery()
const findScopes = [
const findScopes: (string | ScopeOptions)[] = [
ScopeNames.WITH_SCHEDULED_UPDATE,
ScopeNames.WITH_BLACKLISTED,
ScopeNames.WITH_THUMBNAILS