Add history page on client
This commit is contained in:
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 { 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 { 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 { 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 = [
|
const myAccountRoutes: Routes = [
|
||||||
{
|
{
|
||||||
|
@ -114,6 +115,15 @@ const myAccountRoutes: Routes = [
|
||||||
title: 'Muted instances'
|
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 {
|
.video {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
|
|
@ -21,7 +21,7 @@ export class MyAccountComponent {
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
label: this.i18n('My channels'),
|
label: this.i18n('My channels'),
|
||||||
routerLink: '/my-account/videos'
|
routerLink: '/my-account/video-channels'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: this.i18n('My videos'),
|
label: this.i18n('My videos'),
|
||||||
|
@ -30,6 +30,10 @@ export class MyAccountComponent {
|
||||||
{
|
{
|
||||||
label: this.i18n('My subscriptions'),
|
label: this.i18n('My subscriptions'),
|
||||||
routerLink: '/my-account/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 { 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 { 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 { 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({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -49,7 +50,8 @@ import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-b
|
||||||
MyAccountDangerZoneComponent,
|
MyAccountDangerZoneComponent,
|
||||||
MyAccountSubscriptionsComponent,
|
MyAccountSubscriptionsComponent,
|
||||||
MyAccountBlocklistComponent,
|
MyAccountBlocklistComponent,
|
||||||
MyAccountServerBlocklistComponent
|
MyAccountServerBlocklistComponent,
|
||||||
|
MyAccountHistoryComponent
|
||||||
],
|
],
|
||||||
|
|
||||||
exports: [
|
exports: [
|
||||||
|
|
|
@ -4,7 +4,10 @@
|
||||||
<a *ngIf="menuEntry.routerLink" [routerLink]="menuEntry.routerLink" routerLinkActive="active" class="title-page">{{ menuEntry.label }}</a>
|
<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)">
|
<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 i18n>{{ menuEntry.label }}</ng-container>
|
||||||
<ng-container *ngIf="!!suffixLabels[menuEntry.label]"> - {{ suffixLabels[menuEntry.label] }}</ng-container>
|
<ng-container *ngIf="!!suffixLabels[menuEntry.label]"> - {{ suffixLabels[menuEntry.label] }}</ng-container>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -12,3 +12,7 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/deep/ .dropdown-menu {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { Component, Input, OnDestroy, OnInit } from '@angular/core'
|
import { Component, Input, OnDestroy, OnInit } from '@angular/core'
|
||||||
import { filter, take } from 'rxjs/operators'
|
import { filter, take } from 'rxjs/operators'
|
||||||
import { NavigationStart, Router } from '@angular/router'
|
import { NavigationEnd, Router } from '@angular/router'
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription } from 'rxjs'
|
||||||
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { drop } from 'lodash-es'
|
|
||||||
|
|
||||||
export type TopMenuDropdownParam = {
|
export type TopMenuDropdownParam = {
|
||||||
label: string
|
label: string
|
||||||
|
@ -34,7 +33,7 @@ export class TopMenuDropdownComponent implements OnInit, OnDestroy {
|
||||||
this.updateChildLabels(window.location.pathname)
|
this.updateChildLabels(window.location.pathname)
|
||||||
|
|
||||||
this.routeSub = this.router.events
|
this.routeSub = this.router.events
|
||||||
.pipe(filter(event => event instanceof NavigationStart))
|
.pipe(filter(event => event instanceof NavigationEnd))
|
||||||
.subscribe(() => this.updateChildLabels(window.location.pathname))
|
.subscribe(() => this.updateChildLabels(window.location.pathname))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +51,15 @@ export class TopMenuDropdownComponent implements OnInit, OnDestroy {
|
||||||
.subscribe(e => this.openedOnHover = false)
|
.subscribe(e => this.openedOnHover = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dropdownAnchorClicked (dropdown: NgbDropdown) {
|
||||||
|
if (this.openedOnHover) {
|
||||||
|
this.openedOnHover = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return dropdown.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
closeDropdownIfHovered (dropdown: NgbDropdown) {
|
closeDropdownIfHovered (dropdown: NgbDropdown) {
|
||||||
if (this.openedOnHover === false) return
|
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 { UserModerationDropdownComponent } from '@app/shared/moderation/user-moderation-dropdown.component'
|
||||||
import { BlocklistService } from '@app/shared/blocklist'
|
import { BlocklistService } from '@app/shared/blocklist'
|
||||||
import { TopMenuDropdownComponent } from '@app/shared/menu/top-menu-dropdown.component'
|
import { TopMenuDropdownComponent } from '@app/shared/menu/top-menu-dropdown.component'
|
||||||
|
import { UserHistoryService } from '@app/shared/users/user-history.service'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -181,6 +182,7 @@ import { TopMenuDropdownComponent } from '@app/shared/menu/top-menu-dropdown.com
|
||||||
VideoChangeOwnershipValidatorsService,
|
VideoChangeOwnershipValidatorsService,
|
||||||
VideoAcceptOwnershipValidatorsService,
|
VideoAcceptOwnershipValidatorsService,
|
||||||
BlocklistService,
|
BlocklistService,
|
||||||
|
UserHistoryService,
|
||||||
|
|
||||||
I18nPrimengCalendarService,
|
I18nPrimengCalendarService,
|
||||||
ScreenService,
|
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
|
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
|
return query
|
||||||
|
|
Loading…
Reference in New Issue