From: Chocobozzz Date: Tue, 18 Dec 2018 08:31:09 +0000 (+0100) Subject: Add history page on client X-Git-Tag: v1.2.0-rc.1~59 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=80bfd33c0bf910e2cfdd3270b14ba9eddd90e2e8;p=oweals%2Fpeertube.git Add history page on client --- diff --git a/client/src/app/+my-account/my-account-history/my-account-history.component.html b/client/src/app/+my-account/my-account-history/my-account-history.component.html new file mode 100644 index 000000000..653b33f89 --- /dev/null +++ b/client/src/app/+my-account/my-account-history/my-account-history.component.html @@ -0,0 +1,15 @@ +
You don't have history yet.
+ +
+
+
+ + +
+ {{ video.name }} + {{ video.views | myNumberFormatter }} views + +
+
+
+
diff --git a/client/src/app/+my-account/my-account-history/my-account-history.component.scss b/client/src/app/+my-account/my-account-history/my-account-history.component.scss new file mode 100644 index 000000000..115bb0e5c --- /dev/null +++ b/client/src/app/+my-account/my-account-history/my-account-history.component.scss @@ -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; + } + } +} diff --git a/client/src/app/+my-account/my-account-history/my-account-history.component.ts b/client/src/app/+my-account/my-account-history/my-account-history.component.ts new file mode 100644 index 000000000..508552167 --- /dev/null +++ b/client/src/app/+my-account/my-account-history/my-account-history.component.ts @@ -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.') + } +} diff --git a/client/src/app/+my-account/my-account-routing.module.ts b/client/src/app/+my-account/my-account-routing.module.ts index 601e517b4..a2cbeaffc 100644 --- a/client/src/app/+my-account/my-account-routing.module.ts +++ b/client/src/app/+my-account/my-account-routing.module.ts @@ -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' + } + } } ] } diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss b/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss index 2db81a3fe..a735562f8 100644 --- a/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss +++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss @@ -97,7 +97,7 @@ } } -@media screen and (max-width: 800px) { +@media screen and (max-width: $small-view) { .video { flex-direction: column; height: auto; diff --git a/client/src/app/+my-account/my-account.component.ts b/client/src/app/+my-account/my-account.component.ts index d9381ebfa..1bac9547d 100644 --- a/client/src/app/+my-account/my-account.component.ts +++ b/client/src/app/+my-account/my-account.component.ts @@ -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' } ] } diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts index 017ebd57d..c05406438 100644 --- a/client/src/app/+my-account/my-account.module.ts +++ b/client/src/app/+my-account/my-account.module.ts @@ -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: [ diff --git a/client/src/app/shared/menu/top-menu-dropdown.component.html b/client/src/app/shared/menu/top-menu-dropdown.component.html index 2d6d1c4bf..d3c896019 100644 --- a/client/src/app/shared/menu/top-menu-dropdown.component.html +++ b/client/src/app/shared/menu/top-menu-dropdown.component.html @@ -4,7 +4,10 @@ {{ menuEntry.label }}
- + {{ menuEntry.label }} - {{ suffixLabels[menuEntry.label] }} diff --git a/client/src/app/shared/menu/top-menu-dropdown.component.scss b/client/src/app/shared/menu/top-menu-dropdown.component.scss index f3ef8f814..77159532f 100644 --- a/client/src/app/shared/menu/top-menu-dropdown.component.scss +++ b/client/src/app/shared/menu/top-menu-dropdown.component.scss @@ -12,3 +12,7 @@ position: relative; top: 2px; } + +/deep/ .dropdown-menu { + margin-top: 0 !important; +} diff --git a/client/src/app/shared/menu/top-menu-dropdown.component.ts b/client/src/app/shared/menu/top-menu-dropdown.component.ts index 272b721b2..e859c30dd 100644 --- a/client/src/app/shared/menu/top-menu-dropdown.component.ts +++ b/client/src/app/shared/menu/top-menu-dropdown.component.ts @@ -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 diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index 9810e9485..4a5d664db 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts @@ -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, diff --git a/client/src/app/shared/users/user-history.service.ts b/client/src/app/shared/users/user-history.service.ts new file mode 100644 index 000000000..9ed25bfc7 --- /dev/null +++ b/client/src/app/shared/users/user-history.service.ts @@ -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>(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)) + ) + } +} diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 199ea9ea4..3f282580c 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -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