Restore videos list components
This commit is contained in:
parent
7ccddd7b52
commit
489290b8b1
|
@ -1,6 +1,5 @@
|
|||
import { Component, OnDestroy, OnInit } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { Location } from '@angular/common'
|
||||
import { immutableAssign } from '@app/shared/misc/utils'
|
||||
import { AuthService } from '../../core/auth'
|
||||
import { ConfirmService } from '../../core/confirm'
|
||||
|
@ -12,7 +11,7 @@ import { tap } from 'rxjs/operators'
|
|||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||
import { Notifier } from '@app/core'
|
||||
import { Notifier, ServerService } from '@app/core'
|
||||
|
||||
@Component({
|
||||
selector: 'my-account-videos',
|
||||
|
@ -25,7 +24,6 @@ import { Notifier } from '@app/core'
|
|||
export class AccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy {
|
||||
titlePage: string
|
||||
marginContent = false // Disable margin
|
||||
currentRoute = '/accounts/videos'
|
||||
loadOnInit = false
|
||||
|
||||
private account: Account
|
||||
|
@ -33,13 +31,13 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit,
|
|||
|
||||
constructor (
|
||||
protected router: Router,
|
||||
protected serverService: ServerService,
|
||||
protected route: ActivatedRoute,
|
||||
protected authService: AuthService,
|
||||
protected notifier: Notifier,
|
||||
protected confirmService: ConfirmService,
|
||||
protected location: Location,
|
||||
protected screenService: ScreenService,
|
||||
protected i18n: I18n,
|
||||
private i18n: I18n,
|
||||
private accountService: AccountService,
|
||||
private videoService: VideoService
|
||||
) {
|
||||
|
@ -55,7 +53,6 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit,
|
|||
this.accountSub = this.accountService.accountLoaded
|
||||
.subscribe(account => {
|
||||
this.account = account
|
||||
this.currentRoute = '/accounts/' + this.account.nameWithHost + '/videos'
|
||||
|
||||
this.reloadVideos()
|
||||
this.generateSyndicationList()
|
||||
|
|
|
@ -23,6 +23,10 @@ const accountsRoutes: Routes = [
|
|||
data: {
|
||||
meta: {
|
||||
title: 'Account videos'
|
||||
},
|
||||
reuse: {
|
||||
enabled: true,
|
||||
key: 'account-videos-list'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,49 +1,42 @@
|
|||
<div i18n *ngIf="pagination.totalItems === 0">No results.</div>
|
||||
<div
|
||||
myInfiniteScroller
|
||||
[pageHeight]="pageHeight"
|
||||
(nearOfTop)="onNearOfTop()"
|
||||
(nearOfBottom)="onNearOfBottom()"
|
||||
(pageChanged)="onPageChanged($event)"
|
||||
class="videos" #videosElement
|
||||
>
|
||||
<div *ngFor="let videos of videoPages; let i = index" class="videos-page">
|
||||
<div class="video" *ngFor="let video of videos; let j = index">
|
||||
<div class="checkbox-container">
|
||||
<my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="checkedVideos[video.id]"></my-peertube-checkbox>
|
||||
</div>
|
||||
<my-video-thumbnail [video]="video"></my-video-thumbnail>
|
||||
|
||||
<div class="video-info">
|
||||
<a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
|
||||
<div>{{ video.account.displayName }}</div>
|
||||
<div>{{ video.publishedAt | myFromNow }}</div>
|
||||
<div><span i18n>Privacy: </span><span>{{ video.privacy.label }}</span></div>
|
||||
<div><span i18n>Sensitve: </span><span> {{ video.nsfw }}</span></div>
|
||||
</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)]="checkedVideos[video.id]"></my-peertube-checkbox>
|
||||
</div>
|
||||
|
||||
<!-- Display only once -->
|
||||
<div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0 && j === 0">
|
||||
<div class="action-selection-mode-child">
|
||||
<span i18n class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
|
||||
Cancel
|
||||
</span>
|
||||
<my-video-thumbnail [video]="video"></my-video-thumbnail>
|
||||
|
||||
<span class="action-button action-button-unblacklist-selection" (click)="removeSelectedVideosFromBlacklist()">
|
||||
<my-global-icon iconName="tick"></my-global-icon>
|
||||
<ng-container i18n>Unblacklist</ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="video-info">
|
||||
<a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
|
||||
<div>{{ video.account.displayName }}</div>
|
||||
<div>{{ video.publishedAt | myFromNow }}</div>
|
||||
<div><span i18n>Privacy: </span><span>{{ video.privacy.label }}</span></div>
|
||||
<div><span i18n>Sensitive: </span><span> {{ video.nsfw }}</span></div>
|
||||
</div>
|
||||
|
||||
<div class="video-buttons" *ngIf="isInSelectionMode() === false">
|
||||
<my-button
|
||||
i18n-label
|
||||
label="Unblacklist"
|
||||
icon="tick"
|
||||
(click)="removeVideoFromBlacklist(video)"
|
||||
></my-button>
|
||||
<!-- Display only once -->
|
||||
<div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0 && j === 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()">
|
||||
<my-global-icon iconName="tick"></my-global-icon>
|
||||
<ng-container i18n>Unblacklist</ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="video-buttons" *ngIf="isInSelectionMode() === false">
|
||||
<my-button
|
||||
i18n-label
|
||||
label="Unblacklist"
|
||||
icon="tick"
|
||||
(click)="removeVideoFromBlacklist(video)"
|
||||
></my-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
|
|||
import { Router, ActivatedRoute } from '@angular/router'
|
||||
import { AbstractVideoList } from '@app/shared/video/abstract-video-list'
|
||||
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
|
||||
import { Notifier, AuthService } from '@app/core'
|
||||
import { Notifier, AuthService, ServerService } from '@app/core'
|
||||
import { Video } from '@shared/models'
|
||||
import { VideoBlacklistService } from '@app/shared'
|
||||
import { immutableAssign } from '@app/shared/misc/utils'
|
||||
|
@ -17,7 +17,6 @@ import { ScreenService } from '@app/shared/misc/screen.service'
|
|||
})
|
||||
export class VideoAutoBlacklistListComponent extends AbstractVideoList implements OnInit, OnDestroy {
|
||||
titlePage: string
|
||||
currentRoute = '/admin/moderation/video-auto-blacklist/list'
|
||||
checkedVideos: { [ id: number ]: boolean } = {}
|
||||
pagination: ComponentPagination = {
|
||||
currentPage: 1,
|
||||
|
@ -25,18 +24,15 @@ export class VideoAutoBlacklistListComponent extends AbstractVideoList implement
|
|||
totalItems: null
|
||||
}
|
||||
|
||||
protected baseVideoWidth = -1
|
||||
protected baseVideoHeight = 155
|
||||
|
||||
constructor (
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected i18n: I18n,
|
||||
protected notifier: Notifier,
|
||||
protected location: Location,
|
||||
protected authService: AuthService,
|
||||
protected screenService: ScreenService,
|
||||
private videoBlacklistService: VideoBlacklistService,
|
||||
protected serverService: ServerService,
|
||||
private i18n: I18n,
|
||||
private videoBlacklistService: VideoBlacklistService
|
||||
) {
|
||||
super()
|
||||
|
||||
|
@ -96,5 +92,4 @@ export class VideoAutoBlacklistListComponent extends AbstractVideoList implement
|
|||
error => this.notifier.error(error.message)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,16 +13,14 @@
|
|||
|
||||
<div class="no-history" i18n *ngIf="pagination.totalItems === 0">You don't have videos history yet.</div>
|
||||
|
||||
<div myInfiniteScroller (nearOfBottom)="onNearOfBottom()" class="videos" #videosElement>
|
||||
<div *ngFor="let videos of videoPages;" class="videos-page">
|
||||
<div class="video" *ngFor="let video of videos">
|
||||
<my-video-thumbnail [video]="video"></my-video-thumbnail>
|
||||
<div myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" class="videos">
|
||||
<div class="video" *ngFor="let video of videos">
|
||||
<my-video-thumbnail [video]="video"></my-video-thumbnail>
|
||||
|
||||
<div class="video-info">
|
||||
<a tabindex="-1" class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
|
||||
<span i18n class="video-info-date-views">{{ video.views | myNumberFormatter }} views</span>
|
||||
<a tabindex="-1" class="video-info-account" [routerLink]="[ '/accounts', video.byAccount ]">{{ video.byAccount }}</a>
|
||||
</div>
|
||||
<div class="video-info">
|
||||
<a tabindex="-1" class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
|
||||
<span i18n class="video-info-date-views">{{ video.views | myNumberFormatter }} views</span>
|
||||
<a tabindex="-1" class="video-info-account" [routerLink]="[ '/accounts', video.byAccount ]">{{ video.byAccount }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { Component, OnDestroy, OnInit } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { Location } from '@angular/common'
|
||||
import { immutableAssign } from '@app/shared/misc/utils'
|
||||
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
|
||||
import { AuthService } from '../../core/auth'
|
||||
|
@ -11,7 +10,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
|
|||
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||
import { UserHistoryService } from '@app/shared/users/user-history.service'
|
||||
import { UserService } from '@app/shared'
|
||||
import { Notifier } from '@app/core'
|
||||
import { Notifier, ServerService } from '@app/core'
|
||||
|
||||
@Component({
|
||||
selector: 'my-account-history',
|
||||
|
@ -20,7 +19,6 @@ import { Notifier } from '@app/core'
|
|||
})
|
||||
export class MyAccountHistoryComponent extends AbstractVideoList implements OnInit, OnDestroy {
|
||||
titlePage: string
|
||||
currentRoute = '/my-account/history/videos'
|
||||
pagination: ComponentPagination = {
|
||||
currentPage: 1,
|
||||
itemsPerPage: 5,
|
||||
|
@ -28,16 +26,13 @@ export class MyAccountHistoryComponent extends AbstractVideoList implements OnIn
|
|||
}
|
||||
videosHistoryEnabled: boolean
|
||||
|
||||
protected baseVideoWidth = -1
|
||||
protected baseVideoHeight = 155
|
||||
|
||||
constructor (
|
||||
protected router: Router,
|
||||
protected serverService: ServerService,
|
||||
protected route: ActivatedRoute,
|
||||
protected authService: AuthService,
|
||||
protected userService: UserService,
|
||||
protected notifier: Notifier,
|
||||
protected location: Location,
|
||||
protected screenService: ScreenService,
|
||||
protected i18n: I18n,
|
||||
private confirmService: ConfirmService,
|
||||
|
|
|
@ -118,6 +118,10 @@ const myAccountRoutes: Routes = [
|
|||
data: {
|
||||
meta: {
|
||||
title: 'Account videos'
|
||||
},
|
||||
reuse: {
|
||||
enabled: true,
|
||||
key: 'my-account-videos-list'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -172,6 +176,10 @@ const myAccountRoutes: Routes = [
|
|||
data: {
|
||||
meta: {
|
||||
title: 'Videos history'
|
||||
},
|
||||
reuse: {
|
||||
enabled: true,
|
||||
key: 'my-videos-history-list'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,54 +1,47 @@
|
|||
<div i18n *ngIf="pagination.totalItems === 0">No results.</div>
|
||||
|
||||
<div
|
||||
myInfiniteScroller
|
||||
[pageHeight]="pageHeight"
|
||||
(nearOfTop)="onNearOfTop()" (nearOfBottom)="onNearOfBottom()" (pageChanged)="onPageChanged($event)"
|
||||
class="videos" #videosElement
|
||||
>
|
||||
<div *ngFor="let videos of videoPages; let i = index" class="videos-page">
|
||||
<div class="video" *ngFor="let video of videos; let j = index">
|
||||
<div class="checkbox-container">
|
||||
<my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="checkedVideos[video.id]"></my-peertube-checkbox>
|
||||
<div myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" 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)]="checkedVideos[video.id]"></my-peertube-checkbox>
|
||||
</div>
|
||||
|
||||
<my-video-thumbnail [video]="video"></my-video-thumbnail>
|
||||
|
||||
<div class="video-info">
|
||||
<a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
|
||||
<span i18n class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
|
||||
<div class="video-info-privacy">{{ video.privacy.label }}{{ getStateLabel(video) }}</div>
|
||||
<div *ngIf="video.blacklisted" class="video-info-blacklisted">
|
||||
<span class="blacklisted-label" i18n>Blacklisted</span>
|
||||
<span class="blacklisted-reason" *ngIf="video.blacklistedReason">{{ video.blacklistedReason }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<my-video-thumbnail [video]="video"></my-video-thumbnail>
|
||||
<!-- 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>
|
||||
|
||||
<div class="video-info">
|
||||
<a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
|
||||
<span i18n class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
|
||||
<div class="video-info-privacy">{{ video.privacy.label }}{{ getStateLabel(video) }}</div>
|
||||
<div *ngIf="video.blacklisted" class="video-info-blacklisted">
|
||||
<span class="blacklisted-label" i18n>Blacklisted</span>
|
||||
<span class="blacklisted-reason" *ngIf="video.blacklistedReason">{{ video.blacklistedReason }}</span>
|
||||
</div>
|
||||
<span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()">
|
||||
<my-global-icon iconName="delete"></my-global-icon>
|
||||
<ng-container i18n>Delete</ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Display only once -->
|
||||
<div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0 && j === 0">
|
||||
<div class="action-selection-mode-child">
|
||||
<span i18n class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
|
||||
Cancel
|
||||
</span>
|
||||
<div class="video-buttons" *ngIf="isInSelectionMode() === false">
|
||||
<my-delete-button (click)="deleteVideo(video)"></my-delete-button>
|
||||
|
||||
<span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()">
|
||||
<my-global-icon iconName="delete"></my-global-icon>
|
||||
<ng-container i18n>Delete</ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<my-edit-button [routerLink]="[ '/videos', 'update', video.uuid ]"></my-edit-button>
|
||||
|
||||
<div class="video-buttons" *ngIf="isInSelectionMode() === false">
|
||||
<my-delete-button (click)="deleteVideo(video)"></my-delete-button>
|
||||
|
||||
<my-edit-button [routerLink]="[ '/videos', 'update', video.uuid ]"></my-edit-button>
|
||||
|
||||
<my-button i18n-label label="Change ownership"
|
||||
className="action-button-change-ownership"
|
||||
icon="im-with-her"
|
||||
(click)="changeOwnership($event, video)"
|
||||
></my-button>
|
||||
</div>
|
||||
<my-button i18n-label label="Change ownership"
|
||||
className="action-button-change-ownership"
|
||||
icon="im-with-her"
|
||||
(click)="changeOwnership($event, video)"
|
||||
></my-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { from as observableFrom, Observable } from 'rxjs'
|
||||
import { concatAll, tap } from 'rxjs/operators'
|
||||
import { Component, OnDestroy, OnInit, Inject, LOCALE_ID, ViewChild } from '@angular/core'
|
||||
import { concat, Observable } from 'rxjs'
|
||||
import { tap, toArray } from 'rxjs/operators'
|
||||
import { Component, Inject, LOCALE_ID, OnDestroy, OnInit, ViewChild } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { Location } from '@angular/common'
|
||||
import { immutableAssign } from '@app/shared/misc/utils'
|
||||
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
|
||||
import { Notifier } from '@app/core'
|
||||
import { Notifier, ServerService } from '@app/core'
|
||||
import { AuthService } from '../../core/auth'
|
||||
import { ConfirmService } from '../../core/confirm'
|
||||
import { AbstractVideoList } from '../../shared/video/abstract-video-list'
|
||||
|
@ -22,8 +21,9 @@ import { VideoChangeOwnershipComponent } from './video-change-ownership/video-ch
|
|||
styleUrls: [ './my-account-videos.component.scss' ]
|
||||
})
|
||||
export class MyAccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy {
|
||||
@ViewChild('videoChangeOwnershipModal') videoChangeOwnershipModal: VideoChangeOwnershipComponent
|
||||
|
||||
titlePage: string
|
||||
currentRoute = '/my-account/videos'
|
||||
checkedVideos: { [ id: number ]: boolean } = {}
|
||||
pagination: ComponentPagination = {
|
||||
currentPage: 1,
|
||||
|
@ -31,19 +31,14 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
|
|||
totalItems: null
|
||||
}
|
||||
|
||||
protected baseVideoWidth = -1
|
||||
protected baseVideoHeight = 155
|
||||
|
||||
@ViewChild('videoChangeOwnershipModal') videoChangeOwnershipModal: VideoChangeOwnershipComponent
|
||||
|
||||
constructor (
|
||||
protected router: Router,
|
||||
protected serverService: ServerService,
|
||||
protected route: ActivatedRoute,
|
||||
protected authService: AuthService,
|
||||
protected notifier: Notifier,
|
||||
protected location: Location,
|
||||
protected screenService: ScreenService,
|
||||
protected i18n: I18n,
|
||||
private i18n: I18n,
|
||||
private confirmService: ConfirmService,
|
||||
private videoService: VideoService,
|
||||
@Inject(LOCALE_ID) private localeId: string
|
||||
|
@ -93,19 +88,18 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
|
|||
const observables: Observable<any>[] = []
|
||||
for (const videoId of toDeleteVideosIds) {
|
||||
const o = this.videoService.removeVideo(videoId)
|
||||
.pipe(tap(() => this.spliceVideosById(videoId)))
|
||||
.pipe(tap(() => this.removeVideoFromArray(videoId)))
|
||||
|
||||
observables.push(o)
|
||||
}
|
||||
|
||||
observableFrom(observables)
|
||||
.pipe(concatAll())
|
||||
concat(...observables)
|
||||
.pipe(toArray())
|
||||
.subscribe(
|
||||
res => {
|
||||
() => {
|
||||
this.notifier.success(this.i18n('{{deleteLength}} videos deleted.', { deleteLength: toDeleteVideosIds.length }))
|
||||
|
||||
this.abortSelectionMode()
|
||||
this.reloadVideos()
|
||||
},
|
||||
|
||||
err => this.notifier.error(err.message)
|
||||
|
@ -156,20 +150,7 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
|
|||
return ' - ' + suffix
|
||||
}
|
||||
|
||||
protected buildVideoHeight () {
|
||||
// In account videos, the video height is fixed
|
||||
return this.baseVideoHeight
|
||||
}
|
||||
|
||||
private spliceVideosById (id: number) {
|
||||
for (const key of Object.keys(this.loadedPages)) {
|
||||
const videos: Video[] = this.loadedPages[ key ]
|
||||
const index = videos.findIndex(v => v.id === id)
|
||||
|
||||
if (index !== -1) {
|
||||
videos.splice(index, 1)
|
||||
return
|
||||
}
|
||||
}
|
||||
private removeVideoFromArray (id: number) {
|
||||
this.videos = this.videos.filter(v => v.id !== id)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { Component, OnDestroy, OnInit } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { Location } from '@angular/common'
|
||||
import { immutableAssign } from '@app/shared/misc/utils'
|
||||
import { AuthService } from '../../core/auth'
|
||||
import { ConfirmService } from '../../core/confirm'
|
||||
|
@ -12,7 +11,7 @@ import { tap } from 'rxjs/operators'
|
|||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||
import { Notifier } from '@app/core'
|
||||
import { Notifier, ServerService } from '@app/core'
|
||||
|
||||
@Component({
|
||||
selector: 'my-video-channel-videos',
|
||||
|
@ -25,7 +24,6 @@ import { Notifier } from '@app/core'
|
|||
export class VideoChannelVideosComponent extends AbstractVideoList implements OnInit, OnDestroy {
|
||||
titlePage: string
|
||||
marginContent = false // Disable margin
|
||||
currentRoute = '/video-channels/videos'
|
||||
loadOnInit = false
|
||||
|
||||
private videoChannel: VideoChannel
|
||||
|
@ -33,13 +31,13 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On
|
|||
|
||||
constructor (
|
||||
protected router: Router,
|
||||
protected serverService: ServerService,
|
||||
protected route: ActivatedRoute,
|
||||
protected authService: AuthService,
|
||||
protected notifier: Notifier,
|
||||
protected confirmService: ConfirmService,
|
||||
protected location: Location,
|
||||
protected screenService: ScreenService,
|
||||
protected i18n: I18n,
|
||||
private i18n: I18n,
|
||||
private videoChannelService: VideoChannelService,
|
||||
private videoService: VideoService
|
||||
) {
|
||||
|
@ -55,7 +53,6 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On
|
|||
this.videoChannelSub = this.videoChannelService.videoChannelLoaded
|
||||
.subscribe(videoChannel => {
|
||||
this.videoChannel = videoChannel
|
||||
this.currentRoute = '/video-channels/' + this.videoChannel.nameWithHost + '/videos'
|
||||
|
||||
this.reloadVideos()
|
||||
this.generateSyndicationList()
|
||||
|
|
|
@ -23,6 +23,10 @@ const videoChannelsRoutes: Routes = [
|
|||
data: {
|
||||
meta: {
|
||||
title: 'Video channel videos'
|
||||
},
|
||||
reuse: {
|
||||
enabled: true,
|
||||
key: 'video-channel-videos-list'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { NgModule } from '@angular/core'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { RouteReuseStrategy, RouterModule, Routes } from '@angular/router'
|
||||
|
||||
import { PreloadSelectedModulesList } from './core'
|
||||
import { AppComponent } from '@app/app.component'
|
||||
import { CustomReuseStrategy } from '@app/core/routing/custom-reuse-strategy'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
|
@ -43,12 +44,14 @@ const routes: Routes = [
|
|||
imports: [
|
||||
RouterModule.forRoot(routes, {
|
||||
useHash: Boolean(history.pushState) === false,
|
||||
scrollPositionRestoration: 'disabled',
|
||||
preloadingStrategy: PreloadSelectedModulesList,
|
||||
anchorScrolling: 'enabled'
|
||||
anchorScrolling: 'disabled'
|
||||
})
|
||||
],
|
||||
providers: [
|
||||
PreloadSelectedModulesList
|
||||
PreloadSelectedModulesList,
|
||||
{ provide: RouteReuseStrategy, useClass: CustomReuseStrategy }
|
||||
],
|
||||
exports: [ RouterModule ]
|
||||
})
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { Component, OnInit } from '@angular/core'
|
||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
|
||||
import { GuardsCheckStart, NavigationEnd, Router } from '@angular/router'
|
||||
import { Event, GuardsCheckStart, NavigationEnd, Router, Scroll } from '@angular/router'
|
||||
import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core'
|
||||
import { is18nPath } from '../../../shared/models/i18n'
|
||||
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||
import { skip, debounceTime } from 'rxjs/operators'
|
||||
import { HotkeysService, Hotkey } from 'angular2-hotkeys'
|
||||
import { debounceTime, filter, map, pairwise, skip } from 'rxjs/operators'
|
||||
import { Hotkey, HotkeysService } from 'angular2-hotkeys'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
import { fromEvent } from 'rxjs'
|
||||
import { ViewportScroller } from '@angular/common'
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
|
@ -22,6 +23,7 @@ export class AppComponent implements OnInit {
|
|||
|
||||
constructor (
|
||||
private i18n: I18n,
|
||||
private viewportScroller: ViewportScroller,
|
||||
private router: Router,
|
||||
private authService: AuthService,
|
||||
private serverService: ServerService,
|
||||
|
@ -52,15 +54,6 @@ export class AppComponent implements OnInit {
|
|||
ngOnInit () {
|
||||
document.getElementById('incompatible-browser').className += ' browser-ok'
|
||||
|
||||
this.router.events.subscribe(e => {
|
||||
if (e instanceof NavigationEnd) {
|
||||
const pathname = window.location.pathname
|
||||
if (!pathname || pathname === '/' || is18nPath(pathname)) {
|
||||
this.redirectService.redirectToHomepage(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.authService.loadClientCredentials()
|
||||
|
||||
if (this.isUserLoggedIn()) {
|
||||
|
@ -81,15 +74,94 @@ export class AppComponent implements OnInit {
|
|||
this.isMenuDisplayed = false
|
||||
}
|
||||
|
||||
this.router.events.subscribe(
|
||||
e => {
|
||||
// User clicked on a link in the menu, change the page
|
||||
if (e instanceof GuardsCheckStart && this.screenService.isInSmallView()) {
|
||||
this.isMenuDisplayed = false
|
||||
}
|
||||
}
|
||||
)
|
||||
this.initRouteEvents()
|
||||
this.injectJS()
|
||||
this.injectCSS()
|
||||
|
||||
this.initHotkeys()
|
||||
|
||||
fromEvent(window, 'resize')
|
||||
.pipe(debounceTime(200))
|
||||
.subscribe(() => this.onResize())
|
||||
}
|
||||
|
||||
isUserLoggedIn () {
|
||||
return this.authService.isLoggedIn()
|
||||
}
|
||||
|
||||
toggleMenu () {
|
||||
this.isMenuDisplayed = !this.isMenuDisplayed
|
||||
this.isMenuChangedByUser = true
|
||||
}
|
||||
|
||||
onResize () {
|
||||
this.isMenuDisplayed = window.innerWidth >= 800 && !this.isMenuChangedByUser
|
||||
}
|
||||
|
||||
private initRouteEvents () {
|
||||
let resetScroll = true
|
||||
const eventsObs = this.router.events
|
||||
|
||||
const scrollEvent = eventsObs.pipe(filter((e: Event): e is Scroll => e instanceof Scroll))
|
||||
const navigationEndEvent = eventsObs.pipe(filter((e: Event): e is NavigationEnd => e instanceof NavigationEnd))
|
||||
|
||||
scrollEvent.subscribe(e => {
|
||||
if (e.position) {
|
||||
return this.viewportScroller.scrollToPosition(e.position)
|
||||
}
|
||||
|
||||
if (e.anchor) {
|
||||
return this.viewportScroller.scrollToAnchor(e.anchor)
|
||||
}
|
||||
|
||||
if (resetScroll) {
|
||||
return this.viewportScroller.scrollToPosition([ 0, 0 ])
|
||||
}
|
||||
})
|
||||
|
||||
// When we add the a-state parameter, we don't want to alter the scroll
|
||||
navigationEndEvent.pipe(pairwise())
|
||||
.subscribe(([ e1, e2 ]) => {
|
||||
try {
|
||||
resetScroll = false
|
||||
|
||||
const previousUrl = new URL(window.location.origin + e1.url)
|
||||
const nextUrl = new URL(window.location.origin + e2.url)
|
||||
|
||||
if (previousUrl.pathname !== nextUrl.pathname) {
|
||||
resetScroll = true
|
||||
return
|
||||
}
|
||||
|
||||
const nextSearchParams = nextUrl.searchParams
|
||||
nextSearchParams.delete('a-state')
|
||||
|
||||
const previousSearchParams = previousUrl.searchParams
|
||||
|
||||
nextSearchParams.sort()
|
||||
previousSearchParams.sort()
|
||||
|
||||
if (nextSearchParams.toString() !== previousSearchParams.toString()) {
|
||||
resetScroll = true
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Cannot parse URL to check next scroll.', e)
|
||||
resetScroll = true
|
||||
}
|
||||
})
|
||||
|
||||
navigationEndEvent.pipe(
|
||||
map(() => window.location.pathname),
|
||||
filter(pathname => !pathname || pathname === '/' || is18nPath(pathname))
|
||||
).subscribe(() => this.redirectService.redirectToHomepage(true))
|
||||
|
||||
eventsObs.pipe(
|
||||
filter((e: Event): e is GuardsCheckStart => e instanceof GuardsCheckStart),
|
||||
filter(() => this.screenService.isInSmallView())
|
||||
).subscribe(() => this.isMenuDisplayed = false) // User clicked on a link in the menu, change the page
|
||||
}
|
||||
|
||||
private injectJS () {
|
||||
// Inject JS
|
||||
this.serverService.configLoaded
|
||||
.subscribe(() => {
|
||||
|
@ -104,7 +176,9 @@ export class AppComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private injectCSS () {
|
||||
// Inject CSS if modified (admin config settings)
|
||||
this.serverService.configLoaded
|
||||
.pipe(skip(1)) // We only want to subscribe to reloads, because the CSS is already injected by the server
|
||||
|
@ -120,7 +194,9 @@ export class AppComponent implements OnInit {
|
|||
this.customCSS = this.domSanitizer.bypassSecurityTrustHtml(styleTag)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private initHotkeys () {
|
||||
this.hotkeysService.add([
|
||||
new Hotkey(['/', 's'], (event: KeyboardEvent): boolean => {
|
||||
document.getElementById('search-video').focus()
|
||||
|
@ -155,22 +231,5 @@ export class AppComponent implements OnInit {
|
|||
return false
|
||||
}, undefined, this.i18n('Toggle Dark theme'))
|
||||
])
|
||||
|
||||
fromEvent(window, 'resize')
|
||||
.pipe(debounceTime(200))
|
||||
.subscribe(() => this.onResize())
|
||||
}
|
||||
|
||||
isUserLoggedIn () {
|
||||
return this.authService.isLoggedIn()
|
||||
}
|
||||
|
||||
toggleMenu () {
|
||||
this.isMenuDisplayed = !this.isMenuDisplayed
|
||||
this.isMenuChangedByUser = true
|
||||
}
|
||||
|
||||
onResize () {
|
||||
this.isMenuDisplayed = window.innerWidth >= 800 && !this.isMenuChangedByUser
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router'
|
||||
|
||||
export class CustomReuseStrategy implements RouteReuseStrategy {
|
||||
storedRouteHandles = new Map<string, DetachedRouteHandle>()
|
||||
recentlyUsed: string
|
||||
|
||||
private readonly MAX_SIZE = 2
|
||||
|
||||
// Decides if the route should be stored
|
||||
shouldDetach (route: ActivatedRouteSnapshot): boolean {
|
||||
return this.isReuseEnabled(route)
|
||||
}
|
||||
|
||||
// Store the information for the route we're destructing
|
||||
store (route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
|
||||
if (!handle) return
|
||||
|
||||
const key = this.generateKey(route)
|
||||
this.recentlyUsed = key
|
||||
|
||||
console.log('Storing component %s to reuse later.', key);
|
||||
|
||||
(handle as any).componentRef.instance.disableForReuse()
|
||||
|
||||
this.storedRouteHandles.set(key, handle)
|
||||
|
||||
this.gb()
|
||||
}
|
||||
|
||||
// Return true if we have a stored route object for the next route
|
||||
shouldAttach (route: ActivatedRouteSnapshot): boolean {
|
||||
const key = this.generateKey(route)
|
||||
return this.isReuseEnabled(route) && this.storedRouteHandles.has(key)
|
||||
}
|
||||
|
||||
// If we returned true in shouldAttach(), now return the actual route data for restoration
|
||||
retrieve (route: ActivatedRouteSnapshot): DetachedRouteHandle {
|
||||
if (!this.isReuseEnabled(route)) return undefined
|
||||
|
||||
const key = this.generateKey(route)
|
||||
this.recentlyUsed = key
|
||||
|
||||
console.log('Reusing component %s.', key)
|
||||
|
||||
const handle = this.storedRouteHandles.get(key)
|
||||
if (!handle) return handle;
|
||||
|
||||
(handle as any).componentRef.instance.enabledForReuse()
|
||||
|
||||
return handle
|
||||
}
|
||||
|
||||
// Reuse the route if we're going to and from the same route
|
||||
shouldReuseRoute (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
|
||||
return future.routeConfig === curr.routeConfig
|
||||
}
|
||||
|
||||
private gb () {
|
||||
if (this.storedRouteHandles.size >= this.MAX_SIZE) {
|
||||
this.storedRouteHandles.forEach((r, key) => {
|
||||
if (key === this.recentlyUsed) return
|
||||
|
||||
console.log('Removing stored component %s.', key);
|
||||
|
||||
(r as any).componentRef.destroy()
|
||||
this.storedRouteHandles.delete(key)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private generateKey (route: ActivatedRouteSnapshot) {
|
||||
const reuse = route.data.reuse
|
||||
if (!reuse) return undefined
|
||||
|
||||
return reuse.key + JSON.stringify(route.queryParams)
|
||||
}
|
||||
|
||||
private isReuseEnabled (route: ActivatedRouteSnapshot) {
|
||||
return route.data.reuse && route.data.reuse.enabled && route.queryParams['a-state']
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
export interface DisableForReuseHook {
|
||||
|
||||
disableForReuse (): void
|
||||
|
||||
enabledForReuse (): void
|
||||
|
||||
}
|
|
@ -19,13 +19,10 @@
|
|||
|
||||
<div class="no-results" i18n *ngIf="pagination.totalItems === 0">No results.</div>
|
||||
<div
|
||||
myInfiniteScroller
|
||||
[pageHeight]="pageHeight" [firstLoadedPage]="firstLoadedPage"
|
||||
(nearOfTop)="onNearOfTop()" (nearOfBottom)="onNearOfBottom()" (pageChanged)="onPageChanged($event)"
|
||||
class="videos" #videosElement
|
||||
myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true"
|
||||
class="videos"
|
||||
>
|
||||
<div *ngFor="let videos of videoPages; trackBy: pageByVideoId" class="videos-page">
|
||||
<my-video-miniature *ngFor="let video of videos; trackBy: videoById" [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType"></my-video-miniature>
|
||||
</div>
|
||||
<my-video-miniature *ngFor="let video of videos; trackBy: videoById" [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType">
|
||||
</my-video-miniature>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,66 +1,52 @@
|
|||
import { debounceTime } from 'rxjs/operators'
|
||||
import { ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core'
|
||||
import { OnDestroy, OnInit } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { Location } from '@angular/common'
|
||||
import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive'
|
||||
import { fromEvent, Observable, Subscription } from 'rxjs'
|
||||
import { AuthService } from '../../core/auth'
|
||||
import { ComponentPagination } from '../rest/component-pagination.model'
|
||||
import { VideoSortField } from './sort-field.type'
|
||||
import { Video } from './video.model'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||
import { OwnerDisplayType } from '@app/shared/video/video-miniature.component'
|
||||
import { Syndication } from '@app/shared/video/syndication.model'
|
||||
import { Notifier } from '@app/core'
|
||||
|
||||
export abstract class AbstractVideoList implements OnInit, OnDestroy {
|
||||
private static LINES_PER_PAGE = 4
|
||||
|
||||
@ViewChild('videosElement') videosElement: ElementRef
|
||||
@ViewChild(InfiniteScrollerDirective) infiniteScroller: InfiniteScrollerDirective
|
||||
import { Notifier, ServerService } from '@app/core'
|
||||
import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
|
||||
|
||||
export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableForReuseHook {
|
||||
pagination: ComponentPagination = {
|
||||
currentPage: 1,
|
||||
itemsPerPage: 10,
|
||||
itemsPerPage: 25,
|
||||
totalItems: null
|
||||
}
|
||||
sort: VideoSortField = '-publishedAt'
|
||||
|
||||
categoryOneOf?: number
|
||||
defaultSort: VideoSortField = '-publishedAt'
|
||||
|
||||
syndicationItems: Syndication[] = []
|
||||
|
||||
loadOnInit = true
|
||||
marginContent = true
|
||||
pageHeight: number
|
||||
videoWidth: number
|
||||
videoHeight: number
|
||||
videoPages: Video[][] = []
|
||||
videos: Video[] = []
|
||||
ownerDisplayType: OwnerDisplayType = 'account'
|
||||
firstLoadedPage: number
|
||||
displayModerationBlock = false
|
||||
titleTooltip: string
|
||||
|
||||
protected baseVideoWidth = 238
|
||||
protected baseVideoHeight = 225
|
||||
disabled = false
|
||||
|
||||
protected abstract notifier: Notifier
|
||||
protected abstract authService: AuthService
|
||||
protected abstract router: Router
|
||||
protected abstract route: ActivatedRoute
|
||||
protected abstract serverService: ServerService
|
||||
protected abstract screenService: ScreenService
|
||||
protected abstract i18n: I18n
|
||||
protected abstract location: Location
|
||||
protected abstract currentRoute: string
|
||||
protected abstract router: Router
|
||||
abstract titlePage: string
|
||||
|
||||
protected loadedPages: { [ id: number ]: Video[] } = {}
|
||||
protected loadingPage: { [ id: number ]: boolean } = {}
|
||||
protected otherRouteParams = {}
|
||||
|
||||
private resizeSubscription: Subscription
|
||||
private angularState: number
|
||||
|
||||
abstract getVideosObservable (page: number): Observable<{ videos: Video[], totalVideos: number }>
|
||||
|
||||
abstract getVideosObservable (page: number): Observable<{ videos: Video[], totalVideos: number}>
|
||||
abstract generateSyndicationList (): void
|
||||
|
||||
get user () {
|
||||
|
@ -77,207 +63,87 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
|
|||
.subscribe(() => this.calcPageSizes())
|
||||
|
||||
this.calcPageSizes()
|
||||
if (this.loadOnInit === true) this.loadMoreVideos(this.pagination.currentPage)
|
||||
if (this.loadOnInit === true) this.loadMoreVideos()
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
if (this.resizeSubscription) this.resizeSubscription.unsubscribe()
|
||||
}
|
||||
|
||||
pageByVideoId (index: number, page: Video[]) {
|
||||
// Video are unique in all pages
|
||||
return page.length !== 0 ? page[0].id : 0
|
||||
disableForReuse () {
|
||||
this.disabled = true
|
||||
}
|
||||
|
||||
enabledForReuse () {
|
||||
this.disabled = false
|
||||
}
|
||||
|
||||
videoById (index: number, video: Video) {
|
||||
return video.id
|
||||
}
|
||||
|
||||
onNearOfTop () {
|
||||
this.previousPage()
|
||||
}
|
||||
|
||||
onNearOfBottom () {
|
||||
if (this.hasMoreVideos()) {
|
||||
this.nextPage()
|
||||
}
|
||||
if (this.disabled) return
|
||||
|
||||
// Last page
|
||||
if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return
|
||||
|
||||
this.pagination.currentPage += 1
|
||||
|
||||
this.setScrollRouteParams()
|
||||
|
||||
this.loadMoreVideos()
|
||||
}
|
||||
|
||||
onPageChanged (page: number) {
|
||||
this.pagination.currentPage = page
|
||||
this.setNewRouteParams()
|
||||
}
|
||||
|
||||
reloadVideos () {
|
||||
this.loadedPages = {}
|
||||
this.loadMoreVideos(this.pagination.currentPage)
|
||||
}
|
||||
|
||||
loadMoreVideos (page: number, loadOnTop = false) {
|
||||
this.adjustVideoPageHeight()
|
||||
|
||||
const currentY = window.scrollY
|
||||
|
||||
if (this.loadedPages[page] !== undefined) return
|
||||
if (this.loadingPage[page] === true) return
|
||||
|
||||
this.loadingPage[page] = true
|
||||
const observable = this.getVideosObservable(page)
|
||||
loadMoreVideos () {
|
||||
const observable = this.getVideosObservable(this.pagination.currentPage)
|
||||
|
||||
observable.subscribe(
|
||||
({ videos, totalVideos }) => {
|
||||
this.loadingPage[page] = false
|
||||
|
||||
if (this.firstLoadedPage === undefined || this.firstLoadedPage > page) this.firstLoadedPage = page
|
||||
|
||||
// Paging is too high, return to the first one
|
||||
if (this.pagination.currentPage > 1 && totalVideos <= ((this.pagination.currentPage - 1) * this.pagination.itemsPerPage)) {
|
||||
this.pagination.currentPage = 1
|
||||
this.setNewRouteParams()
|
||||
return this.reloadVideos()
|
||||
}
|
||||
|
||||
this.loadedPages[page] = videos
|
||||
this.buildVideoPages()
|
||||
this.pagination.totalItems = totalVideos
|
||||
|
||||
// Initialize infinite scroller now we loaded the first page
|
||||
if (Object.keys(this.loadedPages).length === 1) {
|
||||
// Wait elements creation
|
||||
setTimeout(() => {
|
||||
this.infiniteScroller.initialize()
|
||||
|
||||
// At our first load, we did not load the first page
|
||||
// Load the previous page so the user can move on the top (and browser previous pages)
|
||||
if (this.pagination.currentPage > 1) this.loadMoreVideos(this.pagination.currentPage - 1, true)
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// Insert elements on the top but keep the scroll in the previous position
|
||||
if (loadOnTop) setTimeout(() => { window.scrollTo(0, currentY + this.pageHeight) }, 0)
|
||||
this.videos = this.videos.concat(videos)
|
||||
},
|
||||
error => {
|
||||
this.loadingPage[page] = false
|
||||
this.notifier.error(error.message)
|
||||
}
|
||||
|
||||
error => this.notifier.error(error.message)
|
||||
)
|
||||
}
|
||||
|
||||
reloadVideos () {
|
||||
this.pagination.currentPage = 1
|
||||
this.videos = []
|
||||
this.loadMoreVideos()
|
||||
}
|
||||
|
||||
toggleModerationDisplay () {
|
||||
throw new Error('toggleModerationDisplay is not implemented')
|
||||
}
|
||||
|
||||
protected hasMoreVideos () {
|
||||
// No results
|
||||
if (this.pagination.totalItems === 0) return false
|
||||
|
||||
// Not loaded yet
|
||||
if (!this.pagination.totalItems) return true
|
||||
|
||||
const maxPage = this.pagination.totalItems / this.pagination.itemsPerPage
|
||||
return maxPage > this.maxPageLoaded()
|
||||
}
|
||||
|
||||
protected previousPage () {
|
||||
const min = this.minPageLoaded()
|
||||
|
||||
if (min > 1) {
|
||||
this.loadMoreVideos(min - 1, true)
|
||||
}
|
||||
}
|
||||
|
||||
protected nextPage () {
|
||||
this.loadMoreVideos(this.maxPageLoaded() + 1)
|
||||
}
|
||||
|
||||
protected buildRouteParams () {
|
||||
// There is always a sort and a current page
|
||||
const params = {
|
||||
sort: this.sort,
|
||||
page: this.pagination.currentPage
|
||||
}
|
||||
|
||||
return Object.assign(params, this.otherRouteParams)
|
||||
}
|
||||
|
||||
protected loadRouteParams (routeParams: { [ key: string ]: any }) {
|
||||
this.sort = routeParams['sort'] as VideoSortField || this.defaultSort
|
||||
this.categoryOneOf = routeParams['categoryOneOf']
|
||||
if (routeParams['page'] !== undefined) {
|
||||
this.pagination.currentPage = parseInt(routeParams['page'], 10)
|
||||
} else {
|
||||
this.pagination.currentPage = 1
|
||||
}
|
||||
}
|
||||
|
||||
protected setNewRouteParams () {
|
||||
const paramsObject = this.buildRouteParams()
|
||||
|
||||
const queryParams = Object.keys(paramsObject)
|
||||
.map(p => p + '=' + paramsObject[p])
|
||||
.join('&')
|
||||
this.location.replaceState(this.currentRoute, queryParams)
|
||||
}
|
||||
|
||||
protected buildVideoPages () {
|
||||
this.videoPages = Object.values(this.loadedPages)
|
||||
}
|
||||
|
||||
protected adjustVideoPageHeight () {
|
||||
const numberOfPagesLoaded = Object.keys(this.loadedPages).length
|
||||
if (!numberOfPagesLoaded) return
|
||||
|
||||
this.pageHeight = this.videosElement.nativeElement.offsetHeight / numberOfPagesLoaded
|
||||
}
|
||||
|
||||
protected buildVideoHeight () {
|
||||
// Same ratios than base width/height
|
||||
return this.videosElement.nativeElement.offsetWidth * (this.baseVideoHeight / this.baseVideoWidth)
|
||||
}
|
||||
|
||||
private minPageLoaded () {
|
||||
return Math.min(...Object.keys(this.loadedPages).map(e => parseInt(e, 10)))
|
||||
}
|
||||
|
||||
private maxPageLoaded () {
|
||||
return Math.max(...Object.keys(this.loadedPages).map(e => parseInt(e, 10)))
|
||||
this.sort = routeParams[ 'sort' ] as VideoSortField || this.defaultSort
|
||||
this.categoryOneOf = routeParams[ 'categoryOneOf' ]
|
||||
this.angularState = routeParams[ 'a-state' ]
|
||||
}
|
||||
|
||||
private calcPageSizes () {
|
||||
if (this.screenService.isInMobileView() || this.baseVideoWidth === -1) {
|
||||
if (this.screenService.isInMobileView()) {
|
||||
this.pagination.itemsPerPage = 5
|
||||
}
|
||||
}
|
||||
|
||||
// Video takes all the width
|
||||
this.videoWidth = -1
|
||||
this.videoHeight = this.buildVideoHeight()
|
||||
this.pageHeight = this.pagination.itemsPerPage * this.videoHeight
|
||||
} else {
|
||||
this.videoWidth = this.baseVideoWidth
|
||||
this.videoHeight = this.baseVideoHeight
|
||||
private setScrollRouteParams () {
|
||||
// Already set
|
||||
if (this.angularState) return
|
||||
|
||||
const videosWidth = this.videosElement.nativeElement.offsetWidth
|
||||
this.pagination.itemsPerPage = Math.floor(videosWidth / this.videoWidth) * AbstractVideoList.LINES_PER_PAGE
|
||||
this.pageHeight = this.videoHeight * AbstractVideoList.LINES_PER_PAGE
|
||||
this.angularState = 42
|
||||
|
||||
const queryParams = {
|
||||
'a-state': this.angularState,
|
||||
categoryOneOf: this.categoryOneOf
|
||||
}
|
||||
|
||||
// Rebuild pages because maybe we modified the number of items per page
|
||||
const videos = [].concat(...this.videoPages)
|
||||
this.loadedPages = {}
|
||||
let path = this.router.url
|
||||
if (!path || path === '/') path = this.serverService.getConfig().instance.defaultClientRoute
|
||||
|
||||
let i = 1
|
||||
// Don't include the last page if it not complete
|
||||
while (videos.length >= this.pagination.itemsPerPage && i < 10000) { // 10000 -> Hard limit in case of infinite loop
|
||||
this.loadedPages[i] = videos.splice(0, this.pagination.itemsPerPage)
|
||||
i++
|
||||
}
|
||||
|
||||
// Re fetch the last page
|
||||
if (videos.length !== 0) {
|
||||
this.loadMoreVideos(i)
|
||||
} else {
|
||||
this.buildVideoPages()
|
||||
}
|
||||
|
||||
console.log('Rebuilt pages with %s elements per page.', this.pagination.itemsPerPage)
|
||||
this.router.navigate([ path ], { queryParams, replaceUrl: true, queryParamsHandling: 'merge' })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,24 +6,15 @@ import { fromEvent, Subscription } from 'rxjs'
|
|||
selector: '[myInfiniteScroller]'
|
||||
})
|
||||
export class InfiniteScrollerDirective implements OnInit, OnDestroy {
|
||||
@Input() containerHeight: number
|
||||
@Input() pageHeight: number
|
||||
@Input() firstLoadedPage = 1
|
||||
@Input() percentLimit = 70
|
||||
@Input() autoInit = false
|
||||
@Input() onItself = false
|
||||
|
||||
@Output() nearOfBottom = new EventEmitter<void>()
|
||||
@Output() nearOfTop = new EventEmitter<void>()
|
||||
@Output() pageChanged = new EventEmitter<number>()
|
||||
|
||||
private decimalLimit = 0
|
||||
private lastCurrentBottom = -1
|
||||
private lastCurrentTop = 0
|
||||
private scrollDownSub: Subscription
|
||||
private scrollUpSub: Subscription
|
||||
private pageChangeSub: Subscription
|
||||
private middleScreen: number
|
||||
private container: HTMLElement
|
||||
|
||||
constructor (private el: ElementRef) {
|
||||
|
@ -36,8 +27,6 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy {
|
|||
|
||||
ngOnDestroy () {
|
||||
if (this.scrollDownSub) this.scrollDownSub.unsubscribe()
|
||||
if (this.scrollUpSub) this.scrollUpSub.unsubscribe()
|
||||
if (this.pageChangeSub) this.pageChangeSub.unsubscribe()
|
||||
}
|
||||
|
||||
initialize () {
|
||||
|
@ -45,8 +34,6 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy {
|
|||
this.container = this.el.nativeElement
|
||||
}
|
||||
|
||||
this.middleScreen = window.innerHeight / 2
|
||||
|
||||
// Emit the last value
|
||||
const throttleOptions = { leading: true, trailing: true }
|
||||
|
||||
|
@ -72,40 +59,6 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy {
|
|||
filter(({ current, maximumScroll }) => maximumScroll <= 0 || (current / maximumScroll) > this.decimalLimit)
|
||||
)
|
||||
.subscribe(() => this.nearOfBottom.emit())
|
||||
|
||||
// Scroll up
|
||||
this.scrollUpSub = scrollObservable
|
||||
.pipe(
|
||||
// Check we scroll up
|
||||
filter(({ current }) => {
|
||||
const res = this.lastCurrentTop > current
|
||||
|
||||
this.lastCurrentTop = current
|
||||
return res
|
||||
}),
|
||||
filter(({ current, maximumScroll }) => {
|
||||
return current !== 0 && (1 - (current / maximumScroll)) > this.decimalLimit
|
||||
})
|
||||
)
|
||||
.subscribe(() => this.nearOfTop.emit())
|
||||
|
||||
// Page change
|
||||
this.pageChangeSub = scrollObservable
|
||||
.pipe(
|
||||
distinct(),
|
||||
map(({ current }) => this.calculateCurrentPage(current)),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
.subscribe(res => this.pageChanged.emit(res))
|
||||
}
|
||||
|
||||
private calculateCurrentPage (current: number) {
|
||||
const scrollY = current + this.middleScreen
|
||||
|
||||
const page = Math.max(1, Math.ceil(scrollY / this.pageHeight))
|
||||
|
||||
// Offset page
|
||||
return page + (this.firstLoadedPage - 1)
|
||||
}
|
||||
|
||||
private getScrollInfo () {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Component, OnDestroy, OnInit } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { immutableAssign } from '@app/shared/misc/utils'
|
||||
import { Location } from '@angular/common'
|
||||
import { AuthService } from '../../core/auth'
|
||||
import { AbstractVideoList } from '../../shared/video/abstract-video-list'
|
||||
import { VideoSortField } from '../../shared/video/sort-field.type'
|
||||
|
@ -10,7 +9,7 @@ import { VideoFilter } from '../../../../../shared/models/videos/video-query.typ
|
|||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||
import { UserRight } from '../../../../../shared/models/users'
|
||||
import { Notifier } from '@app/core'
|
||||
import { Notifier, ServerService } from '@app/core'
|
||||
|
||||
@Component({
|
||||
selector: 'my-videos-local',
|
||||
|
@ -19,18 +18,17 @@ import { Notifier } from '@app/core'
|
|||
})
|
||||
export class VideoLocalComponent extends AbstractVideoList implements OnInit, OnDestroy {
|
||||
titlePage: string
|
||||
currentRoute = '/videos/local'
|
||||
sort = '-publishedAt' as VideoSortField
|
||||
filter: VideoFilter = 'local'
|
||||
|
||||
constructor (
|
||||
protected router: Router,
|
||||
protected serverService: ServerService,
|
||||
protected route: ActivatedRoute,
|
||||
protected notifier: Notifier,
|
||||
protected authService: AuthService,
|
||||
protected location: Location,
|
||||
protected i18n: I18n,
|
||||
protected screenService: ScreenService,
|
||||
private i18n: I18n,
|
||||
private videoService: VideoService
|
||||
) {
|
||||
super()
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { Component, OnDestroy, OnInit } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { Location } from '@angular/common'
|
||||
import { immutableAssign } from '@app/shared/misc/utils'
|
||||
import { AuthService } from '../../core/auth'
|
||||
import { AbstractVideoList } from '../../shared/video/abstract-video-list'
|
||||
|
@ -8,7 +7,7 @@ import { VideoSortField } from '../../shared/video/sort-field.type'
|
|||
import { VideoService } from '../../shared/video/video.service'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||
import { Notifier } from '@app/core'
|
||||
import { Notifier, ServerService } from '@app/core'
|
||||
|
||||
@Component({
|
||||
selector: 'my-videos-recently-added',
|
||||
|
@ -17,17 +16,16 @@ import { Notifier } from '@app/core'
|
|||
})
|
||||
export class VideoRecentlyAddedComponent extends AbstractVideoList implements OnInit, OnDestroy {
|
||||
titlePage: string
|
||||
currentRoute = '/videos/recently-added'
|
||||
sort: VideoSortField = '-publishedAt'
|
||||
|
||||
constructor (
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected location: Location,
|
||||
protected serverService: ServerService,
|
||||
protected router: Router,
|
||||
protected notifier: Notifier,
|
||||
protected authService: AuthService,
|
||||
protected i18n: I18n,
|
||||
protected screenService: ScreenService,
|
||||
private i18n: I18n,
|
||||
private videoService: VideoService
|
||||
) {
|
||||
super()
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { Component, OnDestroy, OnInit } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { Location } from '@angular/common'
|
||||
import { immutableAssign } from '@app/shared/misc/utils'
|
||||
import { AuthService } from '../../core/auth'
|
||||
import { AbstractVideoList } from '../../shared/video/abstract-video-list'
|
||||
|
@ -17,18 +16,16 @@ import { Notifier, ServerService } from '@app/core'
|
|||
})
|
||||
export class VideoTrendingComponent extends AbstractVideoList implements OnInit, OnDestroy {
|
||||
titlePage: string
|
||||
currentRoute = '/videos/trending'
|
||||
defaultSort: VideoSortField = '-trending'
|
||||
|
||||
constructor (
|
||||
protected router: Router,
|
||||
protected serverService: ServerService,
|
||||
protected route: ActivatedRoute,
|
||||
protected notifier: Notifier,
|
||||
protected authService: AuthService,
|
||||
protected location: Location,
|
||||
protected screenService: ScreenService,
|
||||
private serverService: ServerService,
|
||||
protected i18n: I18n,
|
||||
private i18n: I18n,
|
||||
private videoService: VideoService
|
||||
) {
|
||||
super()
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Component, OnDestroy, OnInit } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { immutableAssign } from '@app/shared/misc/utils'
|
||||
import { Location } from '@angular/common'
|
||||
import { AuthService } from '../../core/auth'
|
||||
import { AbstractVideoList } from '../../shared/video/abstract-video-list'
|
||||
import { VideoSortField } from '../../shared/video/sort-field.type'
|
||||
|
@ -9,7 +8,7 @@ import { VideoService } from '../../shared/video/video.service'
|
|||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||
import { OwnerDisplayType } from '@app/shared/video/video-miniature.component'
|
||||
import { Notifier } from '@app/core'
|
||||
import { Notifier, ServerService } from '@app/core'
|
||||
|
||||
@Component({
|
||||
selector: 'my-videos-user-subscriptions',
|
||||
|
@ -18,18 +17,17 @@ import { Notifier } from '@app/core'
|
|||
})
|
||||
export class VideoUserSubscriptionsComponent extends AbstractVideoList implements OnInit, OnDestroy {
|
||||
titlePage: string
|
||||
currentRoute = '/videos/subscriptions'
|
||||
sort = '-publishedAt' as VideoSortField
|
||||
ownerDisplayType: OwnerDisplayType = 'auto'
|
||||
|
||||
constructor (
|
||||
protected router: Router,
|
||||
protected serverService: ServerService,
|
||||
protected route: ActivatedRoute,
|
||||
protected notifier: Notifier,
|
||||
protected authService: AuthService,
|
||||
protected location: Location,
|
||||
protected i18n: I18n,
|
||||
protected screenService: ScreenService,
|
||||
private i18n: I18n,
|
||||
private videoService: VideoService
|
||||
) {
|
||||
super()
|
||||
|
|
|
@ -29,6 +29,10 @@ const videosRoutes: Routes = [
|
|||
data: {
|
||||
meta: {
|
||||
title: 'Trending videos'
|
||||
},
|
||||
reuse: {
|
||||
enabled: true,
|
||||
key: 'trending-videos-list'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -38,6 +42,10 @@ const videosRoutes: Routes = [
|
|||
data: {
|
||||
meta: {
|
||||
title: 'Recently added videos'
|
||||
},
|
||||
reuse: {
|
||||
enabled: true,
|
||||
key: 'recently-added-videos-list'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -47,6 +55,10 @@ const videosRoutes: Routes = [
|
|||
data: {
|
||||
meta: {
|
||||
title: 'Subscriptions'
|
||||
},
|
||||
reuse: {
|
||||
enabled: true,
|
||||
key: 'subscription-videos-list'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -56,6 +68,10 @@ const videosRoutes: Routes = [
|
|||
data: {
|
||||
meta: {
|
||||
title: 'Local videos'
|
||||
},
|
||||
reuse: {
|
||||
enabled: true,
|
||||
key: 'local-videos-list'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
import { Op } from 'sequelize'
|
||||
import {
|
||||
AllowNull,
|
||||
BeforeDestroy,
|
||||
|
@ -458,7 +457,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
|
|||
const query = {
|
||||
where: {
|
||||
updatedAt: {
|
||||
[Op.lt]: beforeUpdatedAt
|
||||
[Sequelize.Op.lt]: beforeUpdatedAt
|
||||
},
|
||||
videoId
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
import { Op } from 'sequelize'
|
||||
import * as Bluebird from 'bluebird'
|
||||
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
|
||||
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
||||
|
@ -206,7 +205,7 @@ export class VideoShareModel extends Model<VideoShareModel> {
|
|||
const query = {
|
||||
where: {
|
||||
updatedAt: {
|
||||
[Op.lt]: beforeUpdatedAt
|
||||
[Sequelize.Op.lt]: beforeUpdatedAt
|
||||
},
|
||||
videoId
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue