From fd45e8f43c2638478599ca75632518054461da85 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 31 Oct 2017 11:52:52 +0100 Subject: Add video privacy setting --- client/src/app/app.component.ts | 6 +- client/src/app/core/menu/menu.component.html | 5 + client/src/app/core/server/server.service.ts | 14 ++- .../src/app/shared/forms/form-validators/video.ts | 7 ++ client/src/app/shared/search/search-field.type.ts | 2 +- client/src/app/shared/search/search.component.html | 4 +- client/src/app/shared/search/search.component.ts | 17 ++-- .../videos/+video-edit/video-add.component.html | 12 +++ .../app/videos/+video-edit/video-add.component.ts | 10 +- .../videos/+video-edit/video-update.component.html | 12 +++ .../videos/+video-edit/video-update.component.ts | 20 +++- .../videos/+video-watch/video-watch.component.html | 11 ++- .../src/app/videos/shared/video-details.model.ts | 7 +- client/src/app/videos/shared/video-edit.model.ts | 6 +- client/src/app/videos/shared/video.service.ts | 55 +++++++---- client/src/app/videos/video-list/index.ts | 5 +- .../app/videos/video-list/loader.component.html | 3 - .../src/app/videos/video-list/loader.component.ts | 11 --- .../app/videos/video-list/my-videos.component.ts | 36 +++++++ .../video-list/shared/abstract-video-list.html | 28 ++++++ .../video-list/shared/abstract-video-list.scss | 37 ++++++++ .../video-list/shared/abstract-video-list.ts | 104 +++++++++++++++++++++ client/src/app/videos/video-list/shared/index.ts | 4 + .../videos/video-list/shared/loader.component.html | 3 + .../videos/video-list/shared/loader.component.ts | 11 +++ .../shared/video-miniature.component.html | 33 +++++++ .../shared/video-miniature.component.scss | 102 ++++++++++++++++++++ .../video-list/shared/video-miniature.component.ts | 19 ++++ .../video-list/shared/video-sort.component.html | 5 + .../video-list/shared/video-sort.component.ts | 39 ++++++++ .../videos/video-list/video-list.component.html | 28 ------ .../videos/video-list/video-list.component.scss | 37 -------- .../app/videos/video-list/video-list.component.ts | 102 +++++--------------- .../video-list/video-miniature.component.html | 33 ------- .../video-list/video-miniature.component.scss | 102 -------------------- .../videos/video-list/video-miniature.component.ts | 19 ---- .../videos/video-list/video-sort.component.html | 5 - .../app/videos/video-list/video-sort.component.ts | 39 -------- client/src/app/videos/videos-routing.module.ts | 11 ++- client/src/app/videos/videos.module.ts | 9 +- client/src/sass/video-js-custom.scss | 59 +++--------- 41 files changed, 620 insertions(+), 452 deletions(-) delete mode 100644 client/src/app/videos/video-list/loader.component.html delete mode 100644 client/src/app/videos/video-list/loader.component.ts create mode 100644 client/src/app/videos/video-list/my-videos.component.ts create mode 100644 client/src/app/videos/video-list/shared/abstract-video-list.html create mode 100644 client/src/app/videos/video-list/shared/abstract-video-list.scss create mode 100644 client/src/app/videos/video-list/shared/abstract-video-list.ts create mode 100644 client/src/app/videos/video-list/shared/index.ts create mode 100644 client/src/app/videos/video-list/shared/loader.component.html create mode 100644 client/src/app/videos/video-list/shared/loader.component.ts create mode 100644 client/src/app/videos/video-list/shared/video-miniature.component.html create mode 100644 client/src/app/videos/video-list/shared/video-miniature.component.scss create mode 100644 client/src/app/videos/video-list/shared/video-miniature.component.ts create mode 100644 client/src/app/videos/video-list/shared/video-sort.component.html create mode 100644 client/src/app/videos/video-list/shared/video-sort.component.ts delete mode 100644 client/src/app/videos/video-list/video-list.component.html delete mode 100644 client/src/app/videos/video-list/video-list.component.scss delete mode 100644 client/src/app/videos/video-list/video-miniature.component.html delete mode 100644 client/src/app/videos/video-list/video-miniature.component.scss delete mode 100644 client/src/app/videos/video-list/video-miniature.component.ts delete mode 100644 client/src/app/videos/video-list/video-sort.component.html delete mode 100644 client/src/app/videos/video-list/video-sort.component.ts (limited to 'client') diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 984470d69..bef1599fc 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, ViewContainerRef } from '@angular/core' +import { Component, OnInit } from '@angular/core' import { Router } from '@angular/router' import { AuthService, ServerService } from './core' @@ -28,8 +28,7 @@ export class AppComponent implements OnInit { constructor ( private router: Router, private authService: AuthService, - private serverService: ServerService, - private userService: UserService + private serverService: ServerService ) {} ngOnInit () { @@ -45,6 +44,7 @@ export class AppComponent implements OnInit { this.serverService.loadVideoCategories() this.serverService.loadVideoLanguages() this.serverService.loadVideoLicences() + this.serverService.loadVideoPrivacies() // Do not display menu on small screens if (window.innerWidth < 600) { diff --git a/client/src/app/core/menu/menu.component.html b/client/src/app/core/menu/menu.component.html index 2d8aace54..fcde23fdd 100644 --- a/client/src/app/core/menu/menu.component.html +++ b/client/src/app/core/menu/menu.component.html @@ -23,6 +23,11 @@ My account + + + + My videos +
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts index ae507afce..cbc4074c9 100644 --- a/client/src/app/core/server/server.service.ts +++ b/client/src/app/core/server/server.service.ts @@ -19,6 +19,7 @@ export class ServerService { private videoCategories: Array<{ id: number, label: string }> = [] private videoLicences: Array<{ id: number, label: string }> = [] private videoLanguages: Array<{ id: number, label: string }> = [] + private videoPrivacies: Array<{ id: number, label: string }> = [] constructor (private http: HttpClient) {} @@ -39,6 +40,10 @@ export class ServerService { return this.loadVideoAttributeEnum('languages', this.videoLanguages) } + loadVideoPrivacies () { + return this.loadVideoAttributeEnum('privacies', this.videoPrivacies) + } + getConfig () { return this.config } @@ -55,7 +60,14 @@ export class ServerService { return this.videoLanguages } - private loadVideoAttributeEnum (attributeName: 'categories' | 'licences' | 'languages', hashToPopulate: { id: number, label: string }[]) { + getVideoPrivacies () { + return this.videoPrivacies + } + + private loadVideoAttributeEnum ( + attributeName: 'categories' | 'licences' | 'languages' | 'privacies', + hashToPopulate: { id: number, label: string }[] + ) { return this.http.get(ServerService.BASE_VIDEO_URL + attributeName) .subscribe(data => { Object.keys(data) diff --git a/client/src/app/shared/forms/form-validators/video.ts b/client/src/app/shared/forms/form-validators/video.ts index 434773501..65f11f5da 100644 --- a/client/src/app/shared/forms/form-validators/video.ts +++ b/client/src/app/shared/forms/form-validators/video.ts @@ -9,6 +9,13 @@ export const VIDEO_NAME = { } } +export const VIDEO_PRIVACY = { + VALIDATORS: [ Validators.required ], + MESSAGES: { + 'required': 'Video privacy is required.' + } +} + export const VIDEO_CATEGORY = { VALIDATORS: [ Validators.required ], MESSAGES: { diff --git a/client/src/app/shared/search/search-field.type.ts b/client/src/app/shared/search/search-field.type.ts index 63557898a..ff0bb8de1 100644 --- a/client/src/app/shared/search/search-field.type.ts +++ b/client/src/app/shared/search/search-field.type.ts @@ -1 +1 @@ -export type SearchField = 'name' | 'author' | 'host' | 'magnetUri' | 'tags' +export type SearchField = 'name' | 'author' | 'host' | 'tags' diff --git a/client/src/app/shared/search/search.component.html b/client/src/app/shared/search/search.component.html index c6c6ff6a8..0302447d0 100644 --- a/client/src/app/shared/search/search.component.html +++ b/client/src/app/shared/search/search.component.html @@ -6,12 +6,12 @@
+
+ + + +
+ {{ formErrors.privacy }} +
+
+
+
+ + + +
+ {{ formErrors.privacy }} +
+
+
{ this.video = new VideoEdit(video) + // We cannot set private a video that was not private anymore + if (video.privacy !== VideoPrivacy.PRIVATE) { + const newVideoPrivacies = [] + for (const p of this.videoPrivacies) { + if (p.id !== VideoPrivacy.PRIVATE) newVideoPrivacies.push(p) + } + + this.videoPrivacies = newVideoPrivacies + } + this.hydrateFormFromVideo() }, diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html index 71f986ccd..53648a8d8 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.html +++ b/client/src/app/videos/+video-watch/video-watch.component.html @@ -22,7 +22,7 @@
Video not found :'(
- +
Download: {{ downloadSpeed | bytes }}/s
Upload: {{ uploadSpeed | bytes }}/s
@@ -142,6 +142,15 @@
+
+ + Privacy: + + + {{ video.privacyLabel }} + +
+
Category: diff --git a/client/src/app/videos/shared/video-details.model.ts b/client/src/app/videos/shared/video-details.model.ts index 68ded5210..84f96a25f 100644 --- a/client/src/app/videos/shared/video-details.model.ts +++ b/client/src/app/videos/shared/video-details.model.ts @@ -5,7 +5,8 @@ import { VideoFile, VideoChannel, VideoResolution, - UserRight + UserRight, + VideoPrivacy } from '../../../../../shared' export class VideoDetails extends Video implements VideoDetailsServerModel { @@ -41,10 +42,14 @@ export class VideoDetails extends Video implements VideoDetailsServerModel { descriptionPath: string files: VideoFile[] channel: VideoChannel + privacy: VideoPrivacy + privacyLabel: string constructor (hash: VideoDetailsServerModel) { super(hash) + this.privacy = hash.privacy + this.privacyLabel = hash.privacyLabel this.descriptionPath = hash.descriptionPath this.files = hash.files this.channel = hash.channel diff --git a/client/src/app/videos/shared/video-edit.model.ts b/client/src/app/videos/shared/video-edit.model.ts index e0b7bf130..88d23a59f 100644 --- a/client/src/app/videos/shared/video-edit.model.ts +++ b/client/src/app/videos/shared/video-edit.model.ts @@ -1,4 +1,5 @@ import { VideoDetails } from './video-details.model' +import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum' export class VideoEdit { category: number @@ -9,6 +10,7 @@ export class VideoEdit { tags: string[] nsfw: boolean channel: number + privacy: VideoPrivacy uuid?: string id?: number @@ -23,6 +25,7 @@ export class VideoEdit { this.tags = videoDetails.tags this.nsfw = videoDetails.nsfw this.channel = videoDetails.channel.id + this.privacy = videoDetails.privacy } patch (values: Object) { @@ -40,7 +43,8 @@ export class VideoEdit { name: this.name, tags: this.tags, nsfw: this.nsfw, - channel: this.channel + channel: this.channel, + privacy: this.privacy } } } diff --git a/client/src/app/videos/shared/video.service.ts b/client/src/app/videos/shared/video.service.ts index 7d5372334..8459aa0d3 100644 --- a/client/src/app/videos/shared/video.service.ts +++ b/client/src/app/videos/shared/video.service.ts @@ -19,7 +19,6 @@ import { UserVideoRate, VideoRateType, VideoUpdate, - VideoAbuseCreate, UserVideoRateUpdate, Video as VideoServerModel, VideoDetails as VideoDetailsServerModel, @@ -51,6 +50,7 @@ export class VideoService { licence: video.licence, language, description: video.description, + privacy: video.privacy, tags: video.tags, nsfw: video.nsfw } @@ -63,22 +63,35 @@ export class VideoService { uploadVideo (video: FormData) { const req = new HttpRequest('POST', VideoService.BASE_VIDEO_URL + 'upload', video, { reportProgress: true }) - return this.authHttp.request(req) - .catch(this.restExtractor.handleError) + return this.authHttp + .request(req) + .catch(this.restExtractor.handleError) } - getVideos (videoPagination: VideoPagination, sort: SortField) { + getMyVideos (videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> { const pagination = this.videoPaginationToRestPagination(videoPagination) let params = new HttpParams() params = this.restService.addRestGetParams(params, pagination, sort) - return this.authHttp.get(VideoService.BASE_VIDEO_URL, { params }) - .map(this.extractVideos) - .catch((res) => this.restExtractor.handleError(res)) + return this.authHttp.get(UserService.BASE_USERS_URL + '/me/videos', { params }) + .map(this.extractVideos) + .catch((res) => this.restExtractor.handleError(res)) } - searchVideos (search: Search, videoPagination: VideoPagination, sort: SortField) { + getVideos (videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> { + const pagination = this.videoPaginationToRestPagination(videoPagination) + + let params = new HttpParams() + params = this.restService.addRestGetParams(params, pagination, sort) + + return this.authHttp + .get(VideoService.BASE_VIDEO_URL, { params }) + .map(this.extractVideos) + .catch((res) => this.restExtractor.handleError(res)) + } + + searchVideos (search: Search, videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> { const url = VideoService.BASE_VIDEO_URL + 'search/' + encodeURIComponent(search.value) const pagination = this.videoPaginationToRestPagination(videoPagination) @@ -88,15 +101,17 @@ export class VideoService { if (search.field) params.set('field', search.field) - return this.authHttp.get>(url, { params }) - .map(this.extractVideos) - .catch((res) => this.restExtractor.handleError(res)) + return this.authHttp + .get>(url, { params }) + .map(this.extractVideos) + .catch((res) => this.restExtractor.handleError(res)) } removeVideo (id: number) { - return this.authHttp.delete(VideoService.BASE_VIDEO_URL + id) - .map(this.restExtractor.extractDataBool) - .catch((res) => this.restExtractor.handleError(res)) + return this.authHttp + .delete(VideoService.BASE_VIDEO_URL + id) + .map(this.restExtractor.extractDataBool) + .catch((res) => this.restExtractor.handleError(res)) } loadCompleteDescription (descriptionPath: string) { @@ -117,8 +132,9 @@ export class VideoService { getUserVideoRating (id: number): Observable { const url = UserService.BASE_USERS_URL + 'me/videos/' + id + '/rating' - return this.authHttp.get(url) - .catch(res => this.restExtractor.handleError(res)) + return this.authHttp + .get(url) + .catch(res => this.restExtractor.handleError(res)) } private videoPaginationToRestPagination (videoPagination: VideoPagination) { @@ -134,9 +150,10 @@ export class VideoService { rating: rateType } - return this.authHttp.put(url, body) - .map(this.restExtractor.extractDataBool) - .catch(res => this.restExtractor.handleError(res)) + return this.authHttp + .put(url, body) + .map(this.restExtractor.extractDataBool) + .catch(res => this.restExtractor.handleError(res)) } private extractVideos (result: ResultList) { diff --git a/client/src/app/videos/video-list/index.ts b/client/src/app/videos/video-list/index.ts index a490e6bb5..ed2bb1657 100644 --- a/client/src/app/videos/video-list/index.ts +++ b/client/src/app/videos/video-list/index.ts @@ -1,4 +1,3 @@ -export * from './loader.component' +export * from './my-videos.component' export * from './video-list.component' -export * from './video-miniature.component' -export * from './video-sort.component' +export * from './shared' diff --git a/client/src/app/videos/video-list/loader.component.html b/client/src/app/videos/video-list/loader.component.html deleted file mode 100644 index 38d06950e..000000000 --- a/client/src/app/videos/video-list/loader.component.html +++ /dev/null @@ -1,3 +0,0 @@ -
-
-
diff --git a/client/src/app/videos/video-list/loader.component.ts b/client/src/app/videos/video-list/loader.component.ts deleted file mode 100644 index f37d70c85..000000000 --- a/client/src/app/videos/video-list/loader.component.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Component, Input } from '@angular/core' - -@Component({ - selector: 'my-loader', - styleUrls: [ ], - templateUrl: './loader.component.html' -}) - -export class LoaderComponent { - @Input() loading: boolean -} diff --git a/client/src/app/videos/video-list/my-videos.component.ts b/client/src/app/videos/video-list/my-videos.component.ts new file mode 100644 index 000000000..648741a40 --- /dev/null +++ b/client/src/app/videos/video-list/my-videos.component.ts @@ -0,0 +1,36 @@ +import { Component, OnDestroy, OnInit } from '@angular/core' +import { ActivatedRoute, Router } from '@angular/router' + +import { NotificationsService } from 'angular2-notifications' + +import { AbstractVideoList } from './shared' +import { VideoService } from '../shared' + +@Component({ + selector: 'my-videos', + styleUrls: [ './shared/abstract-video-list.scss' ], + templateUrl: './shared/abstract-video-list.html' +}) +export class MyVideosComponent extends AbstractVideoList implements OnInit, OnDestroy { + + constructor ( + protected router: Router, + protected route: ActivatedRoute, + protected notificationsService: NotificationsService, + private videoService: VideoService + ) { + super() + } + + ngOnInit () { + super.ngOnInit() + } + + ngOnDestroy () { + this.subActivatedRoute.unsubscribe() + } + + getVideosObservable () { + return this.videoService.getMyVideos(this.pagination, this.sort) + } +} diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.html b/client/src/app/videos/video-list/shared/abstract-video-list.html new file mode 100644 index 000000000..680fba3f5 --- /dev/null +++ b/client/src/app/videos/video-list/shared/abstract-video-list.html @@ -0,0 +1,28 @@ +
+
+
+
+ {{ pagination.totalItems }} videos + + +
+ + +
+
+
+ +
+
There is no video.
+ + + +
+ + diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.scss b/client/src/app/videos/video-list/shared/abstract-video-list.scss new file mode 100644 index 000000000..4b4409602 --- /dev/null +++ b/client/src/app/videos/video-list/shared/abstract-video-list.scss @@ -0,0 +1,37 @@ +.videos-info { + @media screen and (max-width: 400px) { + margin-left: 0; + } + + border-bottom: 1px solid #f1f1f1; + height: 40px; + line-height: 40px; + + .videos-total-results { + font-size: 13px; + } + + my-loader { + display: inline-block; + margin-left: 5px; + } +} + +.videos-miniatures { + text-align: center; + padding-top: 0; + + my-video-miniature { + text-align: left; + } + + .no-video { + margin-top: 50px; + text-align: center; + } +} + +pagination { + display: block; + text-align: center; +} diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.ts b/client/src/app/videos/video-list/shared/abstract-video-list.ts new file mode 100644 index 000000000..87d5bc48a --- /dev/null +++ b/client/src/app/videos/video-list/shared/abstract-video-list.ts @@ -0,0 +1,104 @@ +import { OnDestroy, OnInit } from '@angular/core' +import { ActivatedRoute, Router } from '@angular/router' +import { Subscription } from 'rxjs/Subscription' +import { BehaviorSubject } from 'rxjs/BehaviorSubject' +import { Observable } from 'rxjs/Observable' + +import { NotificationsService } from 'angular2-notifications' + +import { + SortField, + Video, + VideoPagination +} from '../../shared' + +export abstract class AbstractVideoList implements OnInit, OnDestroy { + loading: BehaviorSubject = new BehaviorSubject(false) + pagination: VideoPagination = { + currentPage: 1, + itemsPerPage: 25, + totalItems: null + } + sort: SortField + videos: Video[] = [] + + protected notificationsService: NotificationsService + protected router: Router + protected route: ActivatedRoute + + protected subActivatedRoute: Subscription + + abstract getVideosObservable (): Observable<{ videos: Video[], totalVideos: number}> + + ngOnInit () { + // Subscribe to route changes + this.subActivatedRoute = this.route.params.subscribe(routeParams => { + this.loadRouteParams(routeParams) + + this.getVideos() + }) + } + + ngOnDestroy () { + this.subActivatedRoute.unsubscribe() + } + + getVideos () { + this.loading.next(true) + this.videos = [] + + const observable = this.getVideosObservable() + + observable.subscribe( + ({ videos, totalVideos }) => { + this.videos = videos + this.pagination.totalItems = totalVideos + + this.loading.next(false) + }, + error => this.notificationsService.error('Error', error.text) + ) + } + + isThereNoVideo () { + return !this.loading.getValue() && this.videos.length === 0 + } + + onPageChanged (event: { page: number }) { + // Be sure the current page is set + this.pagination.currentPage = event.page + + this.navigateToNewParams() + } + + onSort (sort: SortField) { + this.sort = sort + + this.navigateToNewParams() + } + + protected buildRouteParams () { + // There is always a sort and a current page + const params = { + sort: this.sort, + page: this.pagination.currentPage + } + + return params + } + + protected loadRouteParams (routeParams: { [ key: string ]: any }) { + this.sort = routeParams['sort'] as SortField || '-createdAt' + + if (routeParams['page'] !== undefined) { + this.pagination.currentPage = parseInt(routeParams['page'], 10) + } else { + this.pagination.currentPage = 1 + } + } + + protected navigateToNewParams () { + const routeParams = this.buildRouteParams() + this.router.navigate([ '/videos/list', routeParams ]) + } +} diff --git a/client/src/app/videos/video-list/shared/index.ts b/client/src/app/videos/video-list/shared/index.ts new file mode 100644 index 000000000..2c9804e6d --- /dev/null +++ b/client/src/app/videos/video-list/shared/index.ts @@ -0,0 +1,4 @@ +export * from './abstract-video-list' +export * from './loader.component' +export * from './video-miniature.component' +export * from './video-sort.component' diff --git a/client/src/app/videos/video-list/shared/loader.component.html b/client/src/app/videos/video-list/shared/loader.component.html new file mode 100644 index 000000000..38d06950e --- /dev/null +++ b/client/src/app/videos/video-list/shared/loader.component.html @@ -0,0 +1,3 @@ +
+
+
diff --git a/client/src/app/videos/video-list/shared/loader.component.ts b/client/src/app/videos/video-list/shared/loader.component.ts new file mode 100644 index 000000000..f37d70c85 --- /dev/null +++ b/client/src/app/videos/video-list/shared/loader.component.ts @@ -0,0 +1,11 @@ +import { Component, Input } from '@angular/core' + +@Component({ + selector: 'my-loader', + styleUrls: [ ], + templateUrl: './loader.component.html' +}) + +export class LoaderComponent { + @Input() loading: boolean +} diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.html b/client/src/app/videos/video-list/shared/video-miniature.component.html new file mode 100644 index 000000000..abe87025f --- /dev/null +++ b/client/src/app/videos/video-list/shared/video-miniature.component.html @@ -0,0 +1,33 @@ + diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.scss b/client/src/app/videos/video-list/shared/video-miniature.component.scss new file mode 100644 index 000000000..066792d10 --- /dev/null +++ b/client/src/app/videos/video-list/shared/video-miniature.component.scss @@ -0,0 +1,102 @@ +.video-miniature { + margin-top: 30px; + display: inline-block; + position: relative; + height: 190px; + width: 220px; + vertical-align: top; + + .video-miniature-thumbnail { + display: inline-block; + position: relative; + border-radius: 3px; + overflow: hidden; + + &:hover { + text-decoration: none !important; + } + + img.blur-filter { + filter: blur(5px); + transform : scale(1.03); + } + + .video-miniature-thumbnail-overlay { + position: absolute; + right: 0px; + bottom: 0px; + display: inline-block; + background-color: rgba(0, 0, 0, 0.7); + color: #fff; + padding: 3px 5px; + font-size: 11px; + font-weight: bold; + width: 100%; + + .video-miniature-thumbnail-overlay-views { + + } + + .video-miniature-thumbnail-overlay-duration { + float: right; + } + } + } + + .video-miniature-information { + width: 200px; + + .video-miniature-name { + height: 23px; + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: bold; + transition: color 0.2s; + font-size: 15px; + + &:hover { + text-decoration: none; + } + + &.blur-filter { + filter: blur(3px); + padding-left: 4px; + } + + .video-miniature-tags { + // Fix for chrome when tags are long + width: 201px; + + .video-miniature-tag { + font-size: 13px; + cursor: pointer; + position: relative; + top: -2px; + + .label { + transition: background-color 0.2s; + } + } + } + } + + .video-miniature-author, .video-miniature-created-at { + display: block; + margin-left: 1px; + font-size: 11px; + color: $video-miniature-other-infos; + opacity: 0.9; + } + + .video-miniature-author { + transition: color 0.2s; + + &:hover { + color: #23527c; + text-decoration: none; + } + } + } +} diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.ts b/client/src/app/videos/video-list/shared/video-miniature.component.ts new file mode 100644 index 000000000..e5a87907b --- /dev/null +++ b/client/src/app/videos/video-list/shared/video-miniature.component.ts @@ -0,0 +1,19 @@ +import { Component, Input } from '@angular/core' + +import { SortField, Video } from '../../shared' +import { User } from '../../../shared' + +@Component({ + selector: 'my-video-miniature', + styleUrls: [ './video-miniature.component.scss' ], + templateUrl: './video-miniature.component.html' +}) +export class VideoMiniatureComponent { + @Input() currentSort: SortField + @Input() user: User + @Input() video: Video + + isVideoNSFWForThisUser () { + return this.video.isVideoNSFWForUser(this.user) + } +} diff --git a/client/src/app/videos/video-list/shared/video-sort.component.html b/client/src/app/videos/video-list/shared/video-sort.component.html new file mode 100644 index 000000000..3bece0b22 --- /dev/null +++ b/client/src/app/videos/video-list/shared/video-sort.component.html @@ -0,0 +1,5 @@ + diff --git a/client/src/app/videos/video-list/shared/video-sort.component.ts b/client/src/app/videos/video-list/shared/video-sort.component.ts new file mode 100644 index 000000000..8aa89d32b --- /dev/null +++ b/client/src/app/videos/video-list/shared/video-sort.component.ts @@ -0,0 +1,39 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core' + +import { SortField } from '../../shared' + +@Component({ + selector: 'my-video-sort', + templateUrl: './video-sort.component.html' +}) + +export class VideoSortComponent { + @Output() sort = new EventEmitter() + + @Input() currentSort: SortField + + sortChoices: { [ P in SortField ]: string } = { + 'name': 'Name - Asc', + '-name': 'Name - Desc', + 'duration': 'Duration - Asc', + '-duration': 'Duration - Desc', + 'createdAt': 'Created Date - Asc', + '-createdAt': 'Created Date - Desc', + 'views': 'Views - Asc', + '-views': 'Views - Desc', + 'likes': 'Likes - Asc', + '-likes': 'Likes - Desc' + } + + get choiceKeys () { + return Object.keys(this.sortChoices) + } + + getStringChoice (choiceKey: SortField) { + return this.sortChoices[choiceKey] + } + + onSortChange () { + this.sort.emit(this.currentSort) + } +} diff --git a/client/src/app/videos/video-list/video-list.component.html b/client/src/app/videos/video-list/video-list.component.html deleted file mode 100644 index 680fba3f5..000000000 --- a/client/src/app/videos/video-list/video-list.component.html +++ /dev/null @@ -1,28 +0,0 @@ -
-
-
-
- {{ pagination.totalItems }} videos - - -
- - -
-
-
- -
-
There is no video.
- - - -
- - diff --git a/client/src/app/videos/video-list/video-list.component.scss b/client/src/app/videos/video-list/video-list.component.scss deleted file mode 100644 index 4b4409602..000000000 --- a/client/src/app/videos/video-list/video-list.component.scss +++ /dev/null @@ -1,37 +0,0 @@ -.videos-info { - @media screen and (max-width: 400px) { - margin-left: 0; - } - - border-bottom: 1px solid #f1f1f1; - height: 40px; - line-height: 40px; - - .videos-total-results { - font-size: 13px; - } - - my-loader { - display: inline-block; - margin-left: 5px; - } -} - -.videos-miniatures { - text-align: center; - padding-top: 0; - - my-video-miniature { - text-align: left; - } - - .no-video { - margin-top: 50px; - text-align: center; - } -} - -pagination { - display: block; - text-align: center; -} diff --git a/client/src/app/videos/video-list/video-list.component.ts b/client/src/app/videos/video-list/video-list.component.ts index bf6f60215..784162679 100644 --- a/client/src/app/videos/video-list/video-list.component.ts +++ b/client/src/app/videos/video-list/video-list.component.ts @@ -1,51 +1,33 @@ import { Component, OnDestroy, OnInit } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { Subscription } from 'rxjs/Subscription' -import { BehaviorSubject } from 'rxjs/BehaviorSubject' import { NotificationsService } from 'angular2-notifications' -import { AuthService } from '../../core' -import { - SortField, - Video, - VideoService, - VideoPagination -} from '../shared' -import { Search, SearchField, SearchService, User } from '../../shared' +import { VideoService } from '../shared' +import { Search, SearchField, SearchService } from '../../shared' +import { AbstractVideoList } from './shared' @Component({ selector: 'my-videos-list', - styleUrls: [ './video-list.component.scss' ], - templateUrl: './video-list.component.html' + styleUrls: [ './shared/abstract-video-list.scss' ], + templateUrl: './shared/abstract-video-list.html' }) -export class VideoListComponent implements OnInit, OnDestroy { - loading: BehaviorSubject = new BehaviorSubject(false) - pagination: VideoPagination = { - currentPage: 1, - itemsPerPage: 25, - totalItems: null - } - sort: SortField - user: User - videos: Video[] = [] - +export class VideoListComponent extends AbstractVideoList implements OnInit, OnDestroy { private search: Search - private subActivatedRoute: Subscription private subSearch: Subscription constructor ( - private authService: AuthService, - private notificationsService: NotificationsService, - private router: Router, - private route: ActivatedRoute, + protected router: Router, + protected route: ActivatedRoute, + protected notificationsService: NotificationsService, private videoService: VideoService, private searchService: SearchService - ) {} + ) { + super() + } ngOnInit () { - this.user = this.authService.getUser() - // Subscribe to route changes this.subActivatedRoute = this.route.params.subscribe(routeParams => { this.loadRouteParams(routeParams) @@ -66,14 +48,12 @@ export class VideoListComponent implements OnInit, OnDestroy { } ngOnDestroy () { - this.subActivatedRoute.unsubscribe() + super.ngOnDestroy() + this.subSearch.unsubscribe() } - getVideos () { - this.loading.next(true) - this.videos = [] - + getVideosObservable () { let observable = null if (this.search.value) { observable = this.videoService.searchVideos(this.search, this.pagination, this.sort) @@ -81,40 +61,11 @@ export class VideoListComponent implements OnInit, OnDestroy { observable = this.videoService.getVideos(this.pagination, this.sort) } - observable.subscribe( - ({ videos, totalVideos }) => { - this.videos = videos - this.pagination.totalItems = totalVideos - - this.loading.next(false) - }, - error => this.notificationsService.error('Error', error.text) - ) - } - - isThereNoVideo () { - return !this.loading.getValue() && this.videos.length === 0 - } - - onPageChanged (event: { page: number }) { - // Be sure the current page is set - this.pagination.currentPage = event.page - - this.navigateToNewParams() + return observable } - onSort (sort: SortField) { - this.sort = sort - - this.navigateToNewParams() - } - - private buildRouteParams () { - // There is always a sort and a current page - const params = { - sort: this.sort, - page: this.pagination.currentPage - } + protected buildRouteParams () { + const params = super.buildRouteParams() // Maybe there is a search if (this.search.value) { @@ -125,7 +76,9 @@ export class VideoListComponent implements OnInit, OnDestroy { return params } - private loadRouteParams (routeParams: { [ key: string ]: any }) { + protected loadRouteParams (routeParams: { [ key: string ]: any }) { + super.loadRouteParams(routeParams) + if (routeParams['search'] !== undefined) { this.search = { value: routeParams['search'], @@ -137,18 +90,5 @@ export class VideoListComponent implements OnInit, OnDestroy { field: 'name' } } - - this.sort = routeParams['sort'] as SortField || '-createdAt' - - if (routeParams['page'] !== undefined) { - this.pagination.currentPage = parseInt(routeParams['page'], 10) - } else { - this.pagination.currentPage = 1 - } - } - - private navigateToNewParams () { - const routeParams = this.buildRouteParams() - this.router.navigate([ '/videos/list', routeParams ]) } } diff --git a/client/src/app/videos/video-list/video-miniature.component.html b/client/src/app/videos/video-list/video-miniature.component.html deleted file mode 100644 index abe87025f..000000000 --- a/client/src/app/videos/video-list/video-miniature.component.html +++ /dev/null @@ -1,33 +0,0 @@ - diff --git a/client/src/app/videos/video-list/video-miniature.component.scss b/client/src/app/videos/video-list/video-miniature.component.scss deleted file mode 100644 index 066792d10..000000000 --- a/client/src/app/videos/video-list/video-miniature.component.scss +++ /dev/null @@ -1,102 +0,0 @@ -.video-miniature { - margin-top: 30px; - display: inline-block; - position: relative; - height: 190px; - width: 220px; - vertical-align: top; - - .video-miniature-thumbnail { - display: inline-block; - position: relative; - border-radius: 3px; - overflow: hidden; - - &:hover { - text-decoration: none !important; - } - - img.blur-filter { - filter: blur(5px); - transform : scale(1.03); - } - - .video-miniature-thumbnail-overlay { - position: absolute; - right: 0px; - bottom: 0px; - display: inline-block; - background-color: rgba(0, 0, 0, 0.7); - color: #fff; - padding: 3px 5px; - font-size: 11px; - font-weight: bold; - width: 100%; - - .video-miniature-thumbnail-overlay-views { - - } - - .video-miniature-thumbnail-overlay-duration { - float: right; - } - } - } - - .video-miniature-information { - width: 200px; - - .video-miniature-name { - height: 23px; - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-weight: bold; - transition: color 0.2s; - font-size: 15px; - - &:hover { - text-decoration: none; - } - - &.blur-filter { - filter: blur(3px); - padding-left: 4px; - } - - .video-miniature-tags { - // Fix for chrome when tags are long - width: 201px; - - .video-miniature-tag { - font-size: 13px; - cursor: pointer; - position: relative; - top: -2px; - - .label { - transition: background-color 0.2s; - } - } - } - } - - .video-miniature-author, .video-miniature-created-at { - display: block; - margin-left: 1px; - font-size: 11px; - color: $video-miniature-other-infos; - opacity: 0.9; - } - - .video-miniature-author { - transition: color 0.2s; - - &:hover { - color: #23527c; - text-decoration: none; - } - } - } -} diff --git a/client/src/app/videos/video-list/video-miniature.component.ts b/client/src/app/videos/video-list/video-miniature.component.ts deleted file mode 100644 index 18434dad2..000000000 --- a/client/src/app/videos/video-list/video-miniature.component.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Component, Input } from '@angular/core' - -import { SortField, Video } from '../shared' -import { User } from '../../shared' - -@Component({ - selector: 'my-video-miniature', - styleUrls: [ './video-miniature.component.scss' ], - templateUrl: './video-miniature.component.html' -}) -export class VideoMiniatureComponent { - @Input() currentSort: SortField - @Input() user: User - @Input() video: Video - - isVideoNSFWForThisUser () { - return this.video.isVideoNSFWForUser(this.user) - } -} diff --git a/client/src/app/videos/video-list/video-sort.component.html b/client/src/app/videos/video-list/video-sort.component.html deleted file mode 100644 index 3bece0b22..000000000 --- a/client/src/app/videos/video-list/video-sort.component.html +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/client/src/app/videos/video-list/video-sort.component.ts b/client/src/app/videos/video-list/video-sort.component.ts deleted file mode 100644 index 64916bf16..000000000 --- a/client/src/app/videos/video-list/video-sort.component.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core' - -import { SortField } from '../shared' - -@Component({ - selector: 'my-video-sort', - templateUrl: './video-sort.component.html' -}) - -export class VideoSortComponent { - @Output() sort = new EventEmitter() - - @Input() currentSort: SortField - - sortChoices: { [ P in SortField ]: string } = { - 'name': 'Name - Asc', - '-name': 'Name - Desc', - 'duration': 'Duration - Asc', - '-duration': 'Duration - Desc', - 'createdAt': 'Created Date - Asc', - '-createdAt': 'Created Date - Desc', - 'views': 'Views - Asc', - '-views': 'Views - Desc', - 'likes': 'Likes - Asc', - '-likes': 'Likes - Desc' - } - - get choiceKeys () { - return Object.keys(this.sortChoices) - } - - getStringChoice (choiceKey: SortField) { - return this.sortChoices[choiceKey] - } - - onSortChange () { - this.sort.emit(this.currentSort) - } -} diff --git a/client/src/app/videos/videos-routing.module.ts b/client/src/app/videos/videos-routing.module.ts index d3869748b..3ca3e5486 100644 --- a/client/src/app/videos/videos-routing.module.ts +++ b/client/src/app/videos/videos-routing.module.ts @@ -3,7 +3,7 @@ import { RouterModule, Routes } from '@angular/router' import { MetaGuard } from '@ngx-meta/core' -import { VideoListComponent } from './video-list' +import { VideoListComponent, MyVideosComponent } from './video-list' import { VideosComponent } from './videos.component' const videosRoutes: Routes = [ @@ -12,6 +12,15 @@ const videosRoutes: Routes = [ component: VideosComponent, canActivateChild: [ MetaGuard ], children: [ + { + path: 'mine', + component: MyVideosComponent, + data: { + meta: { + title: 'My videos' + } + } + }, { path: 'list', component: VideoListComponent, diff --git a/client/src/app/videos/videos.module.ts b/client/src/app/videos/videos.module.ts index 3a0c3feac..ecc351b65 100644 --- a/client/src/app/videos/videos.module.ts +++ b/client/src/app/videos/videos.module.ts @@ -2,7 +2,13 @@ import { NgModule } from '@angular/core' import { VideosRoutingModule } from './videos-routing.module' import { VideosComponent } from './videos.component' -import { LoaderComponent, VideoListComponent, VideoMiniatureComponent, VideoSortComponent } from './video-list' +import { + LoaderComponent, + VideoListComponent, + MyVideosComponent, + VideoMiniatureComponent, + VideoSortComponent +} from './video-list' import { VideoService } from './shared' import { SharedModule } from '../shared' @@ -16,6 +22,7 @@ import { SharedModule } from '../shared' VideosComponent, VideoListComponent, + MyVideosComponent, VideoMiniatureComponent, VideoSortComponent, diff --git a/client/src/sass/video-js-custom.scss b/client/src/sass/video-js-custom.scss index c5f668f17..6ad21988e 100644 --- a/client/src/sass/video-js-custom.scss +++ b/client/src/sass/video-js-custom.scss @@ -334,71 +334,34 @@ $slider-bg-color: lighten($primary-background-color, 33%); // Thanks: https://projects.lukehaas.me/css-loaders/ .vjs-loading-spinner { - border: none; - opacity: 1; + margin: -25px 0 0 -25px; + position: absolute; + top: 50%; + left: 50%; font-size: 10px; - text-indent: -9999em; - width: 5em; - height: 5em; - border-radius: 50%; - background: #ffffff; - background: -moz-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%); - background: -webkit-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%); - background: -o-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%); - background: -ms-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%); - background: linear-gradient(to right, #ffffff 10%, rgba(255, 255, 255, 0) 42%); position: relative; - -webkit-animation: load3 1.4s infinite linear; - animation: load3 1.4s infinite linear; - -webkit-transform: translateZ(0); - -ms-transform: translateZ(0); + text-indent: -9999em; + border: 0.7em solid rgba(255, 255, 255, 0.2); + border-left-color: #ffffff; transform: translateZ(0); + animation: spinner 1.4s infinite linear; &:before { - width: 50%; - height: 50%; - background: #ffffff; - border-radius: 100% 0 0 0; - position: absolute; - top: 0; - left: 0; - content: ''; animation: none !important; - margin: 0 !important; } &:after { - background: #000; - width: 75%; - height: 75%; border-radius: 50%; - content: ''; - margin: auto; - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; + width: 6em; + height: 6em; animation: none !important; } - @-webkit-keyframes load3 { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } - } - @keyframes load3 { + @keyframes spinner { 0% { - -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { - -webkit-transform: rotate(360deg); transform: rotate(360deg); } } -- cgit v1.2.3