mirror of https://github.com/Chocobozzz/PeerTube
Add search bars for a user's videos and playlist library
parent
71810d0bcb
commit
bf64ed4196
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
border: none;
|
||||
transform: translateY(-7%);
|
||||
}
|
||||
|
||||
my-feed {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue