Add language filters in user preferences

This commit is contained in:
Chocobozzz
2019-06-19 14:55:58 +02:00
parent bbe078ba55
commit 3caf77d3b1
24 changed files with 443 additions and 150 deletions

View File

@@ -7,6 +7,9 @@
<div i18n class="account-title">Profile</div>
<my-account-profile [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-profile>
<div i18n class="account-title">Video settings</div>
<my-account-video-settings [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-video-settings>
<div i18n class="account-title" id="notifications">Notifications</div>
<my-account-notification-preferences [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-notification-preferences>
@@ -16,8 +19,5 @@
<div i18n class="account-title">Email</div>
<my-account-change-email></my-account-change-email>
<div i18n class="account-title">Video settings</div>
<my-account-video-settings [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-video-settings>
<div i18n class="account-title">Danger zone</div>
<my-account-danger-zone [user]="user"></my-account-danger-zone>

View File

@@ -15,6 +15,21 @@
</div>
</div>
<div class="form-group">
<label i18n for="videoLanguages">Only display videos in the following languages</label>
<my-help i18n-customHtml
customHtml="In Recently added, Trending, Local and Search pages"
></my-help>
<div>
<p-multiSelect
[options]="languageItems" formControlName="videoLanguages" showToggleAll="true"
[defaultLabel]="getDefaultVideoLanguageLabel()" [selectedItemsLabel]="getSelectedVideoLanguageLabel()"
emptyFilterMessage="No results found" i18n-emptyFilterMessage
></p-multiSelect>
</div>
</div>
<div class="form-group">
<my-peertube-checkbox
inputName="webTorrentEnabled" formControlName="webTorrentEnabled"

View File

@@ -1,11 +1,13 @@
import { Component, Input, OnInit } from '@angular/core'
import { Notifier } from '@app/core'
import { Notifier, ServerService } from '@app/core'
import { UserUpdateMe } from '../../../../../../shared'
import { AuthService } from '../../../core'
import { FormReactive, User, UserService } from '../../../shared'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
import { Subject } from 'rxjs'
import { SelectItem } from 'primeng/api'
import { switchMap } from 'rxjs/operators'
@Component({
selector: 'my-account-video-settings',
@@ -16,11 +18,14 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI
@Input() user: User = null
@Input() userInformationLoaded: Subject<any>
languageItems: SelectItem[] = []
constructor (
protected formValidatorService: FormValidatorService,
private authService: AuthService,
private notifier: Notifier,
private userService: UserService,
private serverService: ServerService,
private i18n: I18n
) {
super()
@@ -30,31 +35,60 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI
this.buildForm({
nsfwPolicy: null,
webTorrentEnabled: null,
autoPlayVideo: null
autoPlayVideo: null,
videoLanguages: null
})
this.userInformationLoaded.subscribe(() => {
this.form.patchValue({
nsfwPolicy: this.user.nsfwPolicy,
webTorrentEnabled: this.user.webTorrentEnabled,
autoPlayVideo: this.user.autoPlayVideo === true
})
})
this.serverService.videoLanguagesLoaded
.pipe(switchMap(() => this.userInformationLoaded))
.subscribe(() => {
const languages = this.serverService.getVideoLanguages()
this.languageItems = [ { label: this.i18n('Unknown language'), value: '_unknown' } ]
this.languageItems = this.languageItems
.concat(languages.map(l => ({ label: l.label, value: l.id })))
const videoLanguages = this.user.videoLanguages
? this.user.videoLanguages
: this.languageItems.map(l => l.value)
this.form.patchValue({
nsfwPolicy: this.user.nsfwPolicy,
webTorrentEnabled: this.user.webTorrentEnabled,
autoPlayVideo: this.user.autoPlayVideo === true,
videoLanguages
})
})
}
updateDetails () {
const nsfwPolicy = this.form.value['nsfwPolicy']
const webTorrentEnabled = this.form.value['webTorrentEnabled']
const autoPlayVideo = this.form.value['autoPlayVideo']
let videoLanguages: string[] = this.form.value['videoLanguages']
if (Array.isArray(videoLanguages)) {
if (videoLanguages.length === this.languageItems.length) {
videoLanguages = null // null means "All"
} else if (videoLanguages.length > 20) {
this.notifier.error('Too many languages are enabled. Please enable them all or stay below 20 enabled languages.')
return
} else if (videoLanguages.length === 0) {
this.notifier.error('You need to enabled at least 1 video language.')
return
}
}
const details: UserUpdateMe = {
nsfwPolicy,
webTorrentEnabled,
autoPlayVideo
autoPlayVideo,
videoLanguages
}
this.userService.updateMyProfile(details).subscribe(
() => {
this.notifier.success(this.i18n('Information updated.'))
this.notifier.success(this.i18n('Video settings updated.'))
this.authService.refreshUserInformation()
},
@@ -62,4 +96,12 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI
err => this.notifier.error(err.message)
)
}
getDefaultVideoLanguageLabel () {
return this.i18n('No language')
}
getSelectedVideoLanguageLabel () {
return this.i18n('{{\'{0} languages selected')
}
}

View File

@@ -25,18 +25,13 @@ import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-b
import { MyAccountHistoryComponent } from '@app/+my-account/my-account-history/my-account-history.component'
import { MyAccountNotificationsComponent } from '@app/+my-account/my-account-notifications/my-account-notifications.component'
import { MyAccountNotificationPreferencesComponent } from '@app/+my-account/my-account-settings/my-account-notification-preferences'
import {
MyAccountVideoPlaylistCreateComponent
} from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component'
import {
MyAccountVideoPlaylistUpdateComponent
} from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component'
import { MyAccountVideoPlaylistCreateComponent } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component'
import { MyAccountVideoPlaylistUpdateComponent } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component'
import { MyAccountVideoPlaylistsComponent } from '@app/+my-account/my-account-video-playlists/my-account-video-playlists.component'
import {
MyAccountVideoPlaylistElementsComponent
} from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component'
import { MyAccountVideoPlaylistElementsComponent } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component'
import { DragDropModule } from '@angular/cdk/drag-drop'
import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-settings/my-account-change-email'
import { MultiSelectModule } from 'primeng/primeng'
@NgModule({
imports: [
@@ -46,7 +41,8 @@ import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-setti
SharedModule,
TableModule,
InputSwitchModule,
DragDropModule
DragDropModule,
MultiSelectModule
],
declarations: [

View File

@@ -18,6 +18,7 @@ export class User implements UserServerModel {
webTorrentEnabled: boolean
autoPlayVideo: boolean
videosHistoryEnabled: boolean
videoLanguages: string[]
videoQuota: number
videoQuotaDaily: number

View File

@@ -1,7 +1,7 @@
import { debounceTime } from 'rxjs/operators'
import { debounceTime, first, tap } from 'rxjs/operators'
import { OnDestroy, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { fromEvent, Observable, Subscription } from 'rxjs'
import { fromEvent, Observable, of, Subscription } from 'rxjs'
import { AuthService } from '../../core/auth'
import { ComponentPagination } from '../rest/component-pagination.model'
import { VideoSortField } from './sort-field.type'
@@ -32,18 +32,20 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
sort: VideoSortField = '-publishedAt'
categoryOneOf?: number
languageOneOf?: string[]
defaultSort: VideoSortField = '-publishedAt'
syndicationItems: Syndication[] = []
loadOnInit = true
videos: Video[] = []
useUserVideoLanguagePreferences = false
ownerDisplayType: OwnerDisplayType = 'account'
displayModerationBlock = false
titleTooltip: string
displayVideoActions = true
groupByDate = false
videos: Video[] = []
disabled = false
displayOptions: MiniatureDisplayOptions = {
@@ -98,7 +100,12 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
.subscribe(() => this.calcPageSizes())
this.calcPageSizes()
if (this.loadOnInit === true) this.loadMoreVideos()
const loadUserObservable = this.loadUserVideoLanguagesIfNeeded()
if (this.loadOnInit === true) {
loadUserObservable.subscribe(() => this.loadMoreVideos())
}
}
ngOnDestroy () {
@@ -245,4 +252,16 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
this.router.navigate([ path ], { queryParams, replaceUrl: true, queryParamsHandling: 'merge' })
}
private loadUserVideoLanguagesIfNeeded () {
if (!this.authService.isLoggedIn() || !this.useUserVideoLanguagePreferences) {
return of(true)
}
return this.authService.userInformationLoaded
.pipe(
first(),
tap(() => this.languageOneOf = this.user.videoLanguages)
)
}
}

View File

@@ -35,12 +35,13 @@ import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
export interface VideosProvider {
getVideos (
getVideos (parameters: {
videoPagination: ComponentPagination,
sort: VideoSortField,
filter?: VideoFilter,
categoryOneOf?: number
): Observable<{ videos: Video[], totalVideos: number }>
categoryOneOf?: number,
languageOneOf?: string[]
}): Observable<{ videos: Video[], totalVideos: number }>
}
@Injectable()
@@ -206,12 +207,15 @@ export class VideoService implements VideosProvider {
)
}
getVideos (
getVideos (parameters: {
videoPagination: ComponentPagination,
sort: VideoSortField,
filter?: VideoFilter,
categoryOneOf?: number
): Observable<{ videos: Video[], totalVideos: number }> {
categoryOneOf?: number,
languageOneOf?: string[]
}): Observable<{ videos: Video[], totalVideos: number }> {
const { videoPagination, sort, filter, categoryOneOf, languageOneOf } = parameters
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
let params = new HttpParams()
@@ -225,6 +229,12 @@ export class VideoService implements VideosProvider {
params = params.set('categoryOneOf', categoryOneOf + '')
}
if (languageOneOf) {
for (const l of languageOneOf) {
params = params.append('languageOneOf[]', l)
}
}
return this.authHttp
.get<ResultList<Video>>(VideoService.BASE_VIDEO_URL, { params })
.pipe(

View File

@@ -32,7 +32,7 @@ export class RecentVideosRecommendationService implements RecommendationService
private fetchPage (page: number, recommendation: RecommendationInfo): Observable<Video[]> {
const pagination = { currentPage: page, itemsPerPage: this.pageSize + 1 }
const defaultSubscription = this.videos.getVideos(pagination, '-createdAt')
const defaultSubscription = this.videos.getVideos({ videoPagination: pagination, sort: '-createdAt' })
.pipe(map(v => v.videos))
if (!recommendation.tags || recommendation.tags.length === 0) return defaultSubscription

View File

@@ -21,6 +21,8 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On
sort = '-publishedAt' as VideoSortField
filter: VideoFilter = 'local'
useUserVideoLanguagePreferences = true
constructor (
protected i18n: I18n,
protected router: Router,
@@ -54,7 +56,13 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On
getVideosObservable (page: number) {
const newPagination = immutableAssign(this.pagination, { currentPage: page })
return this.videoService.getVideos(newPagination, this.sort, this.filter, this.categoryOneOf)
return this.videoService.getVideos({
videoPagination: newPagination,
sort: this.sort,
filter: this.filter,
categoryOneOf: this.categoryOneOf,
languageOneOf: this.languageOneOf
})
}
generateSyndicationList () {

View File

@@ -19,6 +19,8 @@ export class VideoRecentlyAddedComponent extends AbstractVideoList implements On
sort: VideoSortField = '-publishedAt'
groupByDate = true
useUserVideoLanguagePreferences = true
constructor (
protected i18n: I18n,
protected route: ActivatedRoute,
@@ -47,7 +49,13 @@ export class VideoRecentlyAddedComponent extends AbstractVideoList implements On
getVideosObservable (page: number) {
const newPagination = immutableAssign(this.pagination, { currentPage: page })
return this.videoService.getVideos(newPagination, this.sort, undefined, this.categoryOneOf)
return this.videoService.getVideos({
videoPagination: newPagination,
sort: this.sort,
filter: undefined,
categoryOneOf: this.categoryOneOf,
languageOneOf: this.languageOneOf
})
}
generateSyndicationList () {

View File

@@ -18,6 +18,8 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit,
titlePage: string
defaultSort: VideoSortField = '-trending'
useUserVideoLanguagePreferences = true
constructor (
protected i18n: I18n,
protected router: Router,
@@ -59,7 +61,13 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit,
getVideosObservable (page: number) {
const newPagination = immutableAssign(this.pagination, { currentPage: page })
return this.videoService.getVideos(newPagination, this.sort, undefined, this.categoryOneOf)
return this.videoService.getVideos({
videoPagination: newPagination,
sort: this.sort,
filter: undefined,
categoryOneOf: this.categoryOneOf,
languageOneOf: this.languageOneOf
})
}
generateSyndicationList () {

View File

@@ -224,6 +224,20 @@
cursor: pointer;
}
@mixin select-arrow-down {
top: 50%;
right: calc(0% + 15px);
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border: 5px solid rgba(0, 0, 0, 0);
border-top-color: #000;
margin-top: -2px;
z-index: 100;
}
@mixin peertube-select-container ($width) {
padding: 0;
margin: 0;
@@ -248,17 +262,7 @@
}
&:after {
top: 50%;
right: calc(0% + 15px);
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border: 5px solid rgba(0, 0, 0, 0);
border-top-color: #000;
margin-top: -2px;
z-index: 100;
@include select-arrow-down;
}
select {

View File

@@ -232,6 +232,43 @@ p-table {
}
}
// multiselect customizations
p-multiselect {
.ui-multiselect-label {
font-size: 15px !important;
padding: 4px 30px 4px 12px !important;
$width: 338px;
width: $width !important;
@media screen and (max-width: $width) {
width: 100% !important;
}
}
.pi.pi-chevron-down{
margin-left: 0 !important;
&::after {
@include select-arrow-down;
right: 0;
margin-top: 6px;
}
}
.ui-chkbox-icon {
//position: absolute !important;
width: 18px;
height: 18px;
//left: 0;
//&::after {
// left: -2px !important;
//}
}
}
// PrimeNG calendar tweaks
p-calendar .ui-datepicker {
a {