mirror of https://github.com/Chocobozzz/PeerTube
Add history page on client
parent
8b9a525a18
commit
80bfd33c0b
|
@ -0,0 +1,15 @@
|
|||
<div i18n *ngIf="pagination.totalItems === 0">You don't have 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 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>
|
||||
</div>
|
|
@ -0,0 +1,68 @@
|
|||
@import '_variables';
|
||||
@import '_mixins';
|
||||
|
||||
.video {
|
||||
@include row-blocks;
|
||||
|
||||
my-video-thumbnail {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.video-info {
|
||||
flex-grow: 1;
|
||||
|
||||
.video-info-name {
|
||||
@include disable-default-a-behaviour;
|
||||
|
||||
color: var(--mainForegroundColor);
|
||||
display: block;
|
||||
width: fit-content;
|
||||
font-size: 18px;
|
||||
font-weight: $font-semibold;
|
||||
}
|
||||
|
||||
.video-info-date-views {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.video-info-account {
|
||||
@include disable-default-a-behaviour;
|
||||
|
||||
display: block;
|
||||
width: fit-content;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
color: #585858;
|
||||
|
||||
&:hover {
|
||||
color: #303030;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $small-view) {
|
||||
.video {
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
text-align: center;
|
||||
|
||||
.video-info-name {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
my-video-thumbnail {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.video-buttons {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
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 { NotificationsService } from 'angular2-notifications'
|
||||
import { AuthService } from '../../core/auth'
|
||||
import { ConfirmService } from '../../core/confirm'
|
||||
import { AbstractVideoList } from '../../shared/video/abstract-video-list'
|
||||
import { VideoService } from '../../shared/video/video.service'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||
import { UserHistoryService } from '@app/shared/users/user-history.service'
|
||||
|
||||
@Component({
|
||||
selector: 'my-account-history',
|
||||
templateUrl: './my-account-history.component.html',
|
||||
styleUrls: [ './my-account-history.component.scss' ]
|
||||
})
|
||||
export class MyAccountHistoryComponent extends AbstractVideoList implements OnInit, OnDestroy {
|
||||
titlePage: string
|
||||
currentRoute = '/my-account/history/videos'
|
||||
pagination: ComponentPagination = {
|
||||
currentPage: 1,
|
||||
itemsPerPage: 5,
|
||||
totalItems: null
|
||||
}
|
||||
|
||||
protected baseVideoWidth = -1
|
||||
protected baseVideoHeight = 155
|
||||
|
||||
constructor (
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected authService: AuthService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected location: Location,
|
||||
protected screenService: ScreenService,
|
||||
protected i18n: I18n,
|
||||
private confirmService: ConfirmService,
|
||||
private videoService: VideoService,
|
||||
private userHistoryService: UserHistoryService
|
||||
) {
|
||||
super()
|
||||
|
||||
this.titlePage = this.i18n('My videos history')
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
super.ngOnInit()
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
super.ngOnDestroy()
|
||||
}
|
||||
|
||||
getVideosObservable (page: number) {
|
||||
const newPagination = immutableAssign(this.pagination, { currentPage: page })
|
||||
|
||||
return this.userHistoryService.getUserVideosHistory(newPagination)
|
||||
}
|
||||
|
||||
generateSyndicationList () {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ import { MyAccountSubscriptionsComponent } from '@app/+my-account/my-account-sub
|
|||
import { MyAccountOwnershipComponent } from '@app/+my-account/my-account-ownership/my-account-ownership.component'
|
||||
import { MyAccountBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-blocklist.component'
|
||||
import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-server-blocklist.component'
|
||||
import { MyAccountHistoryComponent } from '@app/+my-account/my-account-history/my-account-history.component'
|
||||
|
||||
const myAccountRoutes: Routes = [
|
||||
{
|
||||
|
@ -114,6 +115,15 @@ const myAccountRoutes: Routes = [
|
|||
title: 'Muted instances'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'history/videos',
|
||||
component: MyAccountHistoryComponent,
|
||||
data: {
|
||||
meta: {
|
||||
title: 'Videos history'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
@media screen and (max-width: $small-view) {
|
||||
.video {
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
|
|
|
@ -21,7 +21,7 @@ export class MyAccountComponent {
|
|||
children: [
|
||||
{
|
||||
label: this.i18n('My channels'),
|
||||
routerLink: '/my-account/videos'
|
||||
routerLink: '/my-account/video-channels'
|
||||
},
|
||||
{
|
||||
label: this.i18n('My videos'),
|
||||
|
@ -30,6 +30,10 @@ export class MyAccountComponent {
|
|||
{
|
||||
label: this.i18n('My subscriptions'),
|
||||
routerLink: '/my-account/subscriptions'
|
||||
},
|
||||
{
|
||||
label: this.i18n('My history'),
|
||||
routerLink: '/my-account/history/videos'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import { MyAccountDangerZoneComponent } from '@app/+my-account/my-account-settin
|
|||
import { MyAccountSubscriptionsComponent } from '@app/+my-account/my-account-subscriptions/my-account-subscriptions.component'
|
||||
import { MyAccountBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-blocklist.component'
|
||||
import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-server-blocklist.component'
|
||||
import { MyAccountHistoryComponent } from '@app/+my-account/my-account-history/my-account-history.component'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -49,7 +50,8 @@ import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-b
|
|||
MyAccountDangerZoneComponent,
|
||||
MyAccountSubscriptionsComponent,
|
||||
MyAccountBlocklistComponent,
|
||||
MyAccountServerBlocklistComponent
|
||||
MyAccountServerBlocklistComponent,
|
||||
MyAccountHistoryComponent
|
||||
],
|
||||
|
||||
exports: [
|
||||
|
|
|
@ -4,7 +4,10 @@
|
|||
<a *ngIf="menuEntry.routerLink" [routerLink]="menuEntry.routerLink" routerLinkActive="active" class="title-page">{{ menuEntry.label }}</a>
|
||||
|
||||
<div *ngIf="!menuEntry.routerLink" ngbDropdown class="parent-entry" #dropdown="ngbDropdown" (mouseleave)="closeDropdownIfHovered(dropdown)">
|
||||
<span (mouseenter)="openDropdownOnHover(dropdown)" role="button" class="title-page" [ngClass]="{ active: !!suffixLabels[menuEntry.label] }" ngbDropdownToggle>
|
||||
<span
|
||||
(mouseenter)="openDropdownOnHover(dropdown)" [ngClass]="{ active: !!suffixLabels[menuEntry.label] }" ngbDropdownAnchor
|
||||
(click)="dropdownAnchorClicked(dropdown)" role="button" class="title-page"
|
||||
>
|
||||
<ng-container i18n>{{ menuEntry.label }}</ng-container>
|
||||
<ng-container *ngIf="!!suffixLabels[menuEntry.label]"> - {{ suffixLabels[menuEntry.label] }}</ng-container>
|
||||
</span>
|
||||
|
|
|
@ -12,3 +12,7 @@
|
|||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
/deep/ .dropdown-menu {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { Component, Input, OnDestroy, OnInit } from '@angular/core'
|
||||
import { filter, take } from 'rxjs/operators'
|
||||
import { NavigationStart, Router } from '@angular/router'
|
||||
import { NavigationEnd, Router } from '@angular/router'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { drop } from 'lodash-es'
|
||||
|
||||
export type TopMenuDropdownParam = {
|
||||
label: string
|
||||
|
@ -34,7 +33,7 @@ export class TopMenuDropdownComponent implements OnInit, OnDestroy {
|
|||
this.updateChildLabels(window.location.pathname)
|
||||
|
||||
this.routeSub = this.router.events
|
||||
.pipe(filter(event => event instanceof NavigationStart))
|
||||
.pipe(filter(event => event instanceof NavigationEnd))
|
||||
.subscribe(() => this.updateChildLabels(window.location.pathname))
|
||||
}
|
||||
|
||||
|
@ -52,6 +51,15 @@ export class TopMenuDropdownComponent implements OnInit, OnDestroy {
|
|||
.subscribe(e => this.openedOnHover = false)
|
||||
}
|
||||
|
||||
dropdownAnchorClicked (dropdown: NgbDropdown) {
|
||||
if (this.openedOnHover) {
|
||||
this.openedOnHover = false
|
||||
return
|
||||
}
|
||||
|
||||
return dropdown.toggle()
|
||||
}
|
||||
|
||||
closeDropdownIfHovered (dropdown: NgbDropdown) {
|
||||
if (this.openedOnHover === false) return
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@ import { UserBanModalComponent } from '@app/shared/moderation'
|
|||
import { UserModerationDropdownComponent } from '@app/shared/moderation/user-moderation-dropdown.component'
|
||||
import { BlocklistService } from '@app/shared/blocklist'
|
||||
import { TopMenuDropdownComponent } from '@app/shared/menu/top-menu-dropdown.component'
|
||||
import { UserHistoryService } from '@app/shared/users/user-history.service'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -181,6 +182,7 @@ import { TopMenuDropdownComponent } from '@app/shared/menu/top-menu-dropdown.com
|
|||
VideoChangeOwnershipValidatorsService,
|
||||
VideoAcceptOwnershipValidatorsService,
|
||||
BlocklistService,
|
||||
UserHistoryService,
|
||||
|
||||
I18nPrimengCalendarService,
|
||||
ScreenService,
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import { HttpClient, HttpParams } from '@angular/common/http'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { environment } from '../../../environments/environment'
|
||||
import { RestExtractor } from '../rest/rest-extractor.service'
|
||||
import { RestService } from '../rest/rest.service'
|
||||
import { Video } from '../video/video.model'
|
||||
import { catchError, map, switchMap } from 'rxjs/operators'
|
||||
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
|
||||
import { VideoService } from '@app/shared/video/video.service'
|
||||
import { ResultList } from '../../../../../shared'
|
||||
|
||||
@Injectable()
|
||||
export class UserHistoryService {
|
||||
static BASE_USER_VIDEOS_HISTORY_URL = environment.apiUrl + '/api/v1/users/me/history/videos'
|
||||
|
||||
constructor (
|
||||
private authHttp: HttpClient,
|
||||
private restExtractor: RestExtractor,
|
||||
private restService: RestService,
|
||||
private videoService: VideoService
|
||||
) {}
|
||||
|
||||
getUserVideosHistory (historyPagination: ComponentPagination) {
|
||||
const pagination = this.restService.componentPaginationToRestPagination(historyPagination)
|
||||
|
||||
let params = new HttpParams()
|
||||
params = this.restService.addRestGetParams(params, pagination)
|
||||
|
||||
return this.authHttp
|
||||
.get<ResultList<Video>>(UserHistoryService.BASE_USER_VIDEOS_HISTORY_URL, { params })
|
||||
.pipe(
|
||||
switchMap(res => this.videoService.extractVideos(res)),
|
||||
catchError(err => this.restExtractor.handleError(err))
|
||||
)
|
||||
}
|
||||
|
||||
deleteUserVideosHistory () {
|
||||
return this.authHttp
|
||||
.post(UserHistoryService.BASE_USER_VIDEOS_HISTORY_URL + '/remove', {})
|
||||
.pipe(
|
||||
map(() => this.restExtractor.extractDataBool()),
|
||||
catchError(err => this.restExtractor.handleError(err))
|
||||
)
|
||||
}
|
||||
}
|
|
@ -425,6 +425,11 @@ type AvailableForListIDsOptions = {
|
|||
userId: options.historyOfUser.id
|
||||
}
|
||||
})
|
||||
|
||||
// Even if the relation is n:m, we know that a user only have 0..1 video history
|
||||
// So we won't have multiple rows for the same video
|
||||
// Without this, we would not be able to sort on "updatedAt" column of UserVideoHistoryModel
|
||||
query.subQuery = false
|
||||
}
|
||||
|
||||
return query
|
||||
|
|
Loading…
Reference in New Issue