Highlight lives when listing videos

This commit is contained in:
Chocobozzz 2024-06-20 12:07:16 +02:00
parent 9b56fcb500
commit e3161f4105
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
9 changed files with 153 additions and 57 deletions

View File

@ -4,20 +4,22 @@
*ngIf="account"
[title]="title"
[displayTitle]="false"
displayTitle="false"
[getVideosObservableFunction]="getVideosObservableFunction"
[getSyndicationItemsFunction]="getSyndicationItemsFunction"
[defaultSort]="defaultSort"
[displayFilters]="true"
[displayModerationBlock]="true"
displayFilters="true"
displayModerationBlock="true"
[displayAsRow]="displayAsRow()"
[hideScopeFilter]="true"
hideScopeFilter="true"
[loadUserVideoPreferences]="true"
loadUserVideoPreferences="true"
highlightLives="true"
[disabled]="disabled"
>

View File

@ -4,21 +4,24 @@
*ngIf="videoChannel"
[title]="title"
[displayTitle]="false"
displayTitle="false"
[getVideosObservableFunction]="getVideosObservableFunction"
[getSyndicationItemsFunction]="getSyndicationItemsFunction"
[defaultSort]="defaultSort"
[displayFilters]="true"
[displayModerationBlock]="true"
displayFilters="true"
displayModerationBlock="true"
[displayOptions]="displayOptions"
[displayAsRow]="displayAsRow()"
[hideScopeFilter]="true"
hideScopeFilter="true"
[loadUserVideoPreferences]="true"
loadUserVideoPreferences="true"
highlightLives="true"
[disabled]="disabled"

View File

@ -6,11 +6,13 @@
[defaultSort]="defaultSort"
[displayFilters]="false"
[displayModerationBlock]="false"
displayFilters="false"
displayModerationBlock="false"
[loadUserVideoPreferences]="false"
[groupByDate]="true"
loadUserVideoPreferences="false"
groupByDate="true"
highlightLives="false"
[disabled]="disabled"

View File

@ -9,12 +9,14 @@
[defaultSort]="defaultSort"
[defaultScope]="defaultScope"
[displayFilters]="true"
[displayModerationBlock]="true"
displayFilters="true"
displayModerationBlock="true"
[loadUserVideoPreferences]="true"
loadUserVideoPreferences="true"
[groupByDate]="groupByDate"
highlightLives="true"
[disabled]="disabled"
(filtersChanged)="onFiltersChanged($event)"

View File

@ -86,7 +86,7 @@
.owner-moderator-privilege {
margin: 0 !important;
@include margin-left(1rem !important);
@include margin-left(0.25rem !important);
width: 13px !important;
}

View File

@ -57,6 +57,8 @@ export class VideoFilters {
this.reset()
}
// ---------------------------------------------------------------------------
onChange (cb: () => void) {
this.onChangeCallbacks.push(cb)
}
@ -73,6 +75,8 @@ export class VideoFilters {
}
}
// ---------------------------------------------------------------------------
setDefaultScope (scope: VideoFilterScope) {
this.defaultValues.set('scope', scope)
}
@ -85,6 +89,8 @@ export class VideoFilters {
this.updateDefaultNSFW(nsfwPolicy)
}
// ---------------------------------------------------------------------------
reset (specificKey?: string) {
for (const [ key, value ] of this.defaultValues) {
if (specificKey && specificKey !== key) continue
@ -95,6 +101,8 @@ export class VideoFilters {
this.buildActiveFilters()
}
// ---------------------------------------------------------------------------
load (obj: Partial<AttributesOnly<VideoFilters>>) {
// FIXME: We may use <ng-option> that doesn't escape HTML so prefer to escape things
// https://github.com/ng-select/ng-select/issues/1363
@ -122,6 +130,16 @@ export class VideoFilters {
this.buildActiveFilters()
}
clone () {
const cloned = new VideoFilters(this.defaultValues.get('sort'), this.defaultValues.get('scope'), this.hiddenFields)
cloned.load(this.toUrlObject())
return cloned
}
// ---------------------------------------------------------------------------
buildActiveFilters () {
this.activeFilters = []
@ -189,6 +207,8 @@ export class VideoFilters {
return this.activeFilters
}
// ---------------------------------------------------------------------------
toFormObject (): VideoFiltersKeys {
const result: Partial<VideoFiltersKeys> = {}
@ -242,6 +262,8 @@ export class VideoFilters {
}
}
// ---------------------------------------------------------------------------
getNSFWDisplayLabel () {
if (this.defaultNSFWPolicy === 'blur') return $localize`Blurred`

View File

@ -40,10 +40,31 @@
<div class="no-results" i18n *ngIf="hasDoneFirstQuery && videos.length === 0">No results.</div>
<div
myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()"
myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onVideosDataSubject.asObservable()"
[setAngularState]="true" [parentDisabled]="disabled"
class="videos" [ngClass]="{ 'display-as-row': displayAsRow }"
>
<ng-container *ngIf="highlightedLives.length !== 0">
<h2 class="date-title">
<my-global-icon iconName="live"></my-global-icon> Current lives
</h2>
<ng-container *ngFor="let live of highlightedLives; trackBy: videoById;">
<div class="video-wrapper">
<my-video-miniature
[video]="live" [user]="userMiniature" [displayAsRow]="displayAsRow"
[displayVideoActions]="displayVideoActions" [displayOptions]="displayOptions"
(videoBlocked)="removeVideoFromArray(live)" (liveRemoved)="removeVideoFromArray(live)"
>
</my-video-miniature>
</div>
</ng-container>
<h2 *ngIf="!groupByDate" class="date-title">
<my-global-icon iconName="videos"></my-global-icon> Videos
</h2>
</ng-container>
<ng-container *ngFor="let video of videos; trackBy: videoById;">
<h2 class="date-title" *ngIf="getCurrentGroupedDateLabel(video)">
{{ getCurrentGroupedDateLabel(video) }}

View File

@ -76,6 +76,11 @@ $margin-top: 2rem;
}
}
my-global-icon[iconname=live] {
position: relative;
top: -1px;
}
@media screen and (max-width: $mobile-view) {
my-video-filters-header {
@include margin-left(1rem);

View File

@ -1,8 +1,6 @@
import * as debug from 'debug'
import { fromEvent, Observable, Subject, Subscription } from 'rxjs'
import { concatMap, debounceTime, map, switchMap } from 'rxjs/operators'
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges } from '@angular/core'
import { ActivatedRoute, RouterLinkActive, RouterLink } from '@angular/router'
import { NgClass, NgFor, NgIf, NgTemplateOutlet } from '@angular/common'
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, booleanAttribute } from '@angular/core'
import { ActivatedRoute, RouterLink, RouterLinkActive } from '@angular/router'
import {
AuthService,
ComponentPaginationLight,
@ -13,20 +11,22 @@ import {
User,
UserService
} from '@app/core'
import { GlobalIconName } from '@app/shared/shared-icons/global-icon.component'
import { logger } from '@root-helpers/logger'
import { GlobalIconComponent, GlobalIconName } from '@app/shared/shared-icons/global-icon.component'
import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'
import { isLastMonth, isLastWeek, isThisMonth, isToday, isYesterday } from '@peertube/peertube-core-utils'
import { ResultList, UserRight, VideoSortField } from '@peertube/peertube-models'
import { VideoFilters, VideoFilterScope } from './video-filters.model'
import { MiniatureDisplayOptions, VideoMiniatureComponent } from './video-miniature.component'
import { logger } from '@root-helpers/logger'
import * as debug from 'debug'
import { Observable, Subject, Subscription, forkJoin, fromEvent, of } from 'rxjs'
import { concatMap, debounceTime, map, switchMap } from 'rxjs/operators'
import { InfiniteScrollerDirective } from '../shared-main/angular/infinite-scroller.directive'
import { VideoFiltersHeaderComponent } from './video-filters-header.component'
import { ButtonComponent } from '../shared-main/buttons/button.component'
import { FeedComponent } from '../shared-main/feeds/feed.component'
import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'
import { NgIf, NgClass, NgFor, NgTemplateOutlet } from '@angular/common'
import { Video } from '../shared-main/video/video.model'
import { Syndication } from '../shared-main/feeds/syndication.model'
import { Video } from '../shared-main/video/video.model'
import { VideoFiltersHeaderComponent } from './video-filters-header.component'
import { VideoFilterScope, VideoFilters } from './video-filters.model'
import { MiniatureDisplayOptions, VideoMiniatureComponent } from './video-miniature.component'
const debugLogger = debug('peertube:videos:VideosListComponent')
@ -66,7 +66,8 @@ enum GroupDate {
ButtonComponent,
VideoFiltersHeaderComponent,
InfiniteScrollerDirective,
VideoMiniatureComponent
VideoMiniatureComponent,
GlobalIconComponent
]
})
export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
@ -76,35 +77,38 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
@Input() title: string
@Input() titleTooltip: string
@Input() displayTitle = true
@Input({ transform: booleanAttribute }) displayTitle = true
@Input() defaultSort: VideoSortField
@Input() defaultScope: VideoFilterScope = 'federated'
@Input() displayFilters = false
@Input() displayModerationBlock = false
@Input({ transform: booleanAttribute }) displayFilters = false
@Input({ transform: booleanAttribute }) displayModerationBlock = false
@Input() loadUserVideoPreferences = false
@Input({ transform: booleanAttribute }) loadUserVideoPreferences = false
@Input() displayAsRow = false
@Input() displayVideoActions = true
@Input() groupByDate = false
@Input({ transform: booleanAttribute }) displayAsRow = false
@Input({ transform: booleanAttribute }) displayVideoActions = true
@Input({ transform: booleanAttribute }) groupByDate = false
@Input({ transform: booleanAttribute }) highlightLives = false
@Input() headerActions: HeaderAction[] = []
@Input() hideScopeFilter = false
@Input({ transform: booleanAttribute }) hideScopeFilter = false
@Input() displayOptions: MiniatureDisplayOptions
@Input() disabled = false
@Input({ transform: booleanAttribute }) disabled = false
@Output() filtersChanged = new EventEmitter<VideoFilters>()
@Output() videosLoaded = new EventEmitter<Video[]>()
videos: Video[] = []
highlightedLives: Video[] = []
filters: VideoFilters
syndicationItems: Syndication[]
onDataSubject = new Subject<any[]>()
onVideosDataSubject = new Subject<any[]>()
hasDoneFirstQuery = false
userMiniature: User
@ -133,7 +137,11 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
private lastQueryLength: number
private videoRequests = new Subject<{ reset: boolean, obs: Observable<ResultList<Video>> }>()
private videoRequests = new Subject<{
reset: boolean
obsVideos: Observable<Pick<ResultList<Video>, 'data'>>
obsHighlightedLives: Observable<Pick<ResultList<Video>, 'data'>>
}>()
private alreadyDoneSearch = false
@ -161,12 +169,12 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
this.groupedDateLabels = {
[GroupDate.UNKNOWN]: null,
[GroupDate.TODAY]: $localize`Today`,
[GroupDate.YESTERDAY]: $localize`Yesterday`,
[GroupDate.THIS_WEEK]: $localize`This week`,
[GroupDate.THIS_MONTH]: $localize`This month`,
[GroupDate.LAST_MONTH]: $localize`Last month`,
[GroupDate.OLDER]: $localize`Older`
[GroupDate.TODAY]: $localize`Today's videos`,
[GroupDate.YESTERDAY]: $localize`Yesterday's videos`,
[GroupDate.THIS_WEEK]: $localize`This week's videos`,
[GroupDate.THIS_MONTH]: $localize`This month's videos`,
[GroupDate.LAST_MONTH]: $localize`Last month's videos`,
[GroupDate.OLDER]: $localize`Older videos`
}
this.resizeSub = fromEvent(window, 'resize')
@ -256,12 +264,31 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
}
loadMoreVideos (reset = false) {
let liveFilters: VideoFilters
let videoFilters: VideoFilters
if (reset) {
this.hasDoneFirstQuery = false
this.videos = []
this.highlightedLives = []
if (this.highlightLives && (!this.filters.live || this.filters.live === 'both')) {
liveFilters = this.filters.clone()
liveFilters.live = 'true'
videoFilters = this.filters.clone()
videoFilters.live = 'false'
}
}
this.videoRequests.next({ reset, obs: this.getVideosObservableFunction(this.pagination, this.filters) })
this.videoRequests.next({
reset,
obsVideos: this.getVideosObservableFunction(this.pagination, videoFilters ?? this.filters),
obsHighlightedLives: liveFilters
? this.getVideosObservableFunction(this.pagination, liveFilters)
: of(({ data: this.highlightedLives }))
})
}
reloadVideos () {
@ -271,6 +298,7 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
removeVideoFromArray (video: Video) {
this.videos = this.videos.filter(v => v.id !== video.id)
this.highlightedLives = this.highlightedLives.filter(v => v.id !== video.id)
}
buildGroupedDateLabels () {
@ -338,6 +366,7 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
scheduleOnFiltersChanged (customizedByUser: boolean) {
// We'll reload videos, but avoid weird UI effect
this.videos = []
this.highlightedLives = []
setTimeout(() => this.onFiltersChanged(customizedByUser))
}
@ -444,19 +473,29 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
private subscribeToVideoRequests () {
this.videoRequests
.pipe(concatMap(({ reset, obs }) => obs.pipe(map(({ data }) => ({ data, reset })))))
.pipe(
concatMap(({ reset, obsHighlightedLives, obsVideos }) => {
return forkJoin([ obsHighlightedLives, obsVideos ])
.pipe(
map(([ resHighlightedLives, resVideos ]) => ({ highlightedLives: resHighlightedLives.data, videos: resVideos.data, reset }))
)
})
)
.subscribe({
next: ({ data, reset }) => {
next: ({ videos, highlightedLives, reset }) => {
this.hasDoneFirstQuery = true
this.lastQueryLength = data.length
this.lastQueryLength = videos.length
if (reset) this.videos = []
if (reset) {
this.videos = []
this.highlightedLives = highlightedLives
}
this.videos = this.videos.concat(data)
this.videos = this.videos.concat(videos)
if (this.groupByDate) this.buildGroupedDateLabels()
this.onDataSubject.next(data)
this.onVideosDataSubject.next(videos)
this.videosLoaded.emit(this.videos)
},