Add history page on client

pull/1535/head
Chocobozzz 2018-12-18 09:31:09 +01:00
parent 8b9a525a18
commit 80bfd33c0b
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
13 changed files with 239 additions and 7 deletions

View File

@ -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>

View File

@ -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;
}
}
}

View File

@ -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.')
}
}

View File

@ -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'
}
}
}
]
}

View File

@ -97,7 +97,7 @@
}
}
@media screen and (max-width: 800px) {
@media screen and (max-width: $small-view) {
.video {
flex-direction: column;
height: auto;

View File

@ -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'
}
]
}

View File

@ -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: [

View File

@ -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>

View File

@ -12,3 +12,7 @@
position: relative;
top: 2px;
}
/deep/ .dropdown-menu {
margin-top: 0 !important;
}

View File

@ -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

View File

@ -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,

View File

@ -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))
)
}
}

View File

@ -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