From 911186dae411d78788ccede093c251303187589a Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 29 Jun 2021 17:18:30 +0200 Subject: Reorganize watch components --- .../+videos/+video-watch/shared/metadata/index.ts | 3 + .../metadata/video-avatar-channel.component.html | 11 ++ .../metadata/video-avatar-channel.component.scss | 42 ++++++ .../metadata/video-avatar-channel.component.ts | 26 ++++ .../metadata/video-description.component.html | 19 +++ .../metadata/video-description.component.scss | 46 +++++++ .../shared/metadata/video-description.component.ts | 87 +++++++++++++ .../shared/metadata/video-rate.component.html | 23 ++++ .../shared/metadata/video-rate.component.scss | 15 +++ .../shared/metadata/video-rate.component.ts | 142 +++++++++++++++++++++ 10 files changed, 414 insertions(+) create mode 100644 client/src/app/+videos/+video-watch/shared/metadata/index.ts create mode 100644 client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.html create mode 100644 client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.scss create mode 100644 client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.ts create mode 100644 client/src/app/+videos/+video-watch/shared/metadata/video-description.component.html create mode 100644 client/src/app/+videos/+video-watch/shared/metadata/video-description.component.scss create mode 100644 client/src/app/+videos/+video-watch/shared/metadata/video-description.component.ts create mode 100644 client/src/app/+videos/+video-watch/shared/metadata/video-rate.component.html create mode 100644 client/src/app/+videos/+video-watch/shared/metadata/video-rate.component.scss create mode 100644 client/src/app/+videos/+video-watch/shared/metadata/video-rate.component.ts (limited to 'client/src/app/+videos/+video-watch/shared/metadata') diff --git a/client/src/app/+videos/+video-watch/shared/metadata/index.ts b/client/src/app/+videos/+video-watch/shared/metadata/index.ts new file mode 100644 index 000000000..ba97f7011 --- /dev/null +++ b/client/src/app/+videos/+video-watch/shared/metadata/index.ts @@ -0,0 +1,3 @@ +export * from './video-avatar-channel.component' +export * from './video-description.component' +export * from './video-rate.component' diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.html b/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.html new file mode 100644 index 000000000..5a7221858 --- /dev/null +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.html @@ -0,0 +1,11 @@ +
+ + + +
diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.scss b/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.scss new file mode 100644 index 000000000..1ff8fb96e --- /dev/null +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.scss @@ -0,0 +1,42 @@ +@use '_mixins' as *; + +@mixin main { + @include actor-avatar-size(35px); +} + +@mixin secondary { + height: 60%; + width: 60%; + position: absolute; + bottom: -5px; + right: -5px; + background-color: rgba(0, 0, 0, 0); +} + +.wrapper { + @include actor-avatar-size(35px); + @include margin-right(5px); + + position: relative; + margin-bottom: 5px; + + &.generic-channel { + .account { + @include main(); + } + + .channel { + display: none !important; + } + } + + &:not(.generic-channel) { + .account { + @include secondary(); + } + + .channel { + @include main(); + } + } +} diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.ts b/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.ts new file mode 100644 index 000000000..63edd7bad --- /dev/null +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.ts @@ -0,0 +1,26 @@ +import { Component, Input, OnInit } from '@angular/core' +import { Video } from '@app/shared/shared-main/video' + +@Component({ + selector: 'my-video-avatar-channel', + templateUrl: './video-avatar-channel.component.html', + styleUrls: [ './video-avatar-channel.component.scss' ] +}) +export class VideoAvatarChannelComponent implements OnInit { + @Input() video: Video + @Input() byAccount: string + + @Input() genericChannel: boolean + + channelLinkTitle = '' + accountLinkTitle = '' + + ngOnInit () { + this.channelLinkTitle = $localize`${this.video.account.name} (channel page)` + this.accountLinkTitle = $localize`${this.video.byAccount} (account page)` + } + + isChannelAvatarNull () { + return this.video.channel.avatar === null + } +} diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.html b/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.html new file mode 100644 index 000000000..57f682899 --- /dev/null +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.html @@ -0,0 +1,19 @@ +
+
+ +
+ Show more + + +
+ +
+ Show less + +
+
diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.scss b/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.scss new file mode 100644 index 000000000..fc8b4574c --- /dev/null +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.scss @@ -0,0 +1,46 @@ +@use '_variables' as *; +@use '_mixins' as *; + +.video-info-description { + @include margin-left($video-watch-info-margin-left); + @include margin-right(0); + + margin-top: 20px; + margin-bottom: 20px; + font-size: 15px; + + .video-info-description-html { + @include peertube-word-wrap; + + ::ng-deep a { + text-decoration: none; + } + } + + .glyphicon, + .description-loading { + @include margin-left(3px); + } + + .description-loading { + display: inline-block; + } + + .video-info-description-more { + cursor: pointer; + font-weight: $font-semibold; + color: pvar(--greyForegroundColor); + font-size: 14px; + + .glyphicon { + position: relative; + top: 2px; + } + } +} + +@media screen and (max-width: 450px) { + .video-info-description { + font-size: 14px !important; + } +} diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.ts b/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.ts new file mode 100644 index 000000000..2ea3b206f --- /dev/null +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.ts @@ -0,0 +1,87 @@ +import { Component, EventEmitter, Inject, Input, LOCALE_ID, OnChanges, Output } from '@angular/core' +import { MarkdownService, Notifier } from '@app/core' +import { VideoDetails, VideoService } from '@app/shared/shared-main' + + +@Component({ + selector: 'my-video-description', + templateUrl: './video-description.component.html', + styleUrls: [ './video-description.component.scss' ] +}) +export class VideoDescriptionComponent implements OnChanges { + @Input() video: VideoDetails + + @Output() timestampClicked = new EventEmitter() + + descriptionLoading = false + completeDescriptionShown = false + completeVideoDescription: string + shortVideoDescription: string + videoHTMLDescription = '' + + constructor ( + private videoService: VideoService, + private notifier: Notifier, + private markdownService: MarkdownService, + @Inject(LOCALE_ID) private localeId: string + ) { } + + ngOnChanges () { + this.descriptionLoading = false + this.completeDescriptionShown = false + this.completeVideoDescription = undefined + + this.setVideoDescriptionHTML() + } + + showMoreDescription () { + if (this.completeVideoDescription === undefined) { + return this.loadCompleteDescription() + } + + this.updateVideoDescription(this.completeVideoDescription) + this.completeDescriptionShown = true + } + + showLessDescription () { + this.updateVideoDescription(this.shortVideoDescription) + this.completeDescriptionShown = false + } + + loadCompleteDescription () { + this.descriptionLoading = true + + this.videoService.loadCompleteDescription(this.video.descriptionPath) + .subscribe( + description => { + this.completeDescriptionShown = true + this.descriptionLoading = false + + this.shortVideoDescription = this.video.description + this.completeVideoDescription = description + + this.updateVideoDescription(this.completeVideoDescription) + }, + + error => { + this.descriptionLoading = false + this.notifier.error(error.message) + } + ) + } + + onTimestampClicked (timestamp: number) { + this.timestampClicked.emit(timestamp) + } + + private updateVideoDescription (description: string) { + this.video.description = description + this.setVideoDescriptionHTML() + .catch(err => console.error(err)) + } + + private async setVideoDescriptionHTML () { + const html = await this.markdownService.textMarkdownToHTML(this.video.description) + this.videoHTMLDescription = this.markdownService.processVideoTimestamps(html) + } +} diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-rate.component.html b/client/src/app/+videos/+video-watch/shared/metadata/video-rate.component.html new file mode 100644 index 000000000..7dd9b3678 --- /dev/null +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-rate.component.html @@ -0,0 +1,23 @@ + + + + + + + diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-rate.component.scss b/client/src/app/+videos/+video-watch/shared/metadata/video-rate.component.scss new file mode 100644 index 000000000..f4f696f33 --- /dev/null +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-rate.component.scss @@ -0,0 +1,15 @@ +@use '_variables' as *; +@use '_mixins' as *; + +.action-button-like, +.action-button-dislike { + filter: brightness(120%); + + .count { + margin: 0 5px; + } +} + +.activated { + color: pvar(--activatedActionButtonColor) !important; +} diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-rate.component.ts b/client/src/app/+videos/+video-watch/shared/metadata/video-rate.component.ts new file mode 100644 index 000000000..89a666a62 --- /dev/null +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-rate.component.ts @@ -0,0 +1,142 @@ +import { Hotkey, HotkeysService } from 'angular2-hotkeys' +import { Observable } from 'rxjs' +import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output } from '@angular/core' +import { Notifier, ScreenService } from '@app/core' +import { VideoDetails, VideoService } from '@app/shared/shared-main' +import { UserVideoRateType } from '@shared/models' + +@Component({ + selector: 'my-video-rate', + templateUrl: './video-rate.component.html', + styleUrls: [ './video-rate.component.scss' ] +}) +export class VideoRateComponent implements OnInit, OnChanges, OnDestroy { + @Input() video: VideoDetails + @Input() isUserLoggedIn: boolean + + @Output() userRatingLoaded = new EventEmitter() + @Output() rateUpdated = new EventEmitter() + + userRating: UserVideoRateType + + tooltipLike = '' + tooltipDislike = '' + + private hotkeys: Hotkey[] + + constructor ( + private videoService: VideoService, + private notifier: Notifier, + private hotkeysService: HotkeysService, + private screenService: ScreenService + ) { } + + async ngOnInit () { + // Hide the tooltips for unlogged users in mobile view, this adds confusion with the popover + if (this.isUserLoggedIn || !this.screenService.isInMobileView()) { + this.tooltipLike = $localize`Like this video` + this.tooltipDislike = $localize`Dislike this video` + } + + if (this.isUserLoggedIn) { + this.hotkeys = [ + new Hotkey('shift+l', () => { + this.setLike() + return false + }, undefined, $localize`Like the video`), + + new Hotkey('shift+d', () => { + this.setDislike() + return false + }, undefined, $localize`Dislike the video`) + ] + + this.hotkeysService.add(this.hotkeys) + } + } + + ngOnChanges () { + this.checkUserRating() + } + + ngOnDestroy () { + this.hotkeysService.remove(this.hotkeys) + } + + setLike () { + if (this.isUserLoggedIn === false) return + + // Already liked this video + if (this.userRating === 'like') this.setRating('none') + else this.setRating('like') + } + + setDislike () { + if (this.isUserLoggedIn === false) return + + // Already disliked this video + if (this.userRating === 'dislike') this.setRating('none') + else this.setRating('dislike') + } + + getRatePopoverText () { + if (this.isUserLoggedIn) return undefined + + return $localize`You need to be logged in to rate this video.` + } + + private checkUserRating () { + // Unlogged users do not have ratings + if (this.isUserLoggedIn === false) return + + this.videoService.getUserVideoRating(this.video.id) + .subscribe( + ratingObject => { + if (!ratingObject) return + + this.userRating = ratingObject.rating + this.userRatingLoaded.emit(this.userRating) + }, + + err => this.notifier.error(err.message) + ) + } + + private setRating (nextRating: UserVideoRateType) { + const ratingMethods: { [id in UserVideoRateType]: (id: number) => Observable } = { + like: this.videoService.setVideoLike, + dislike: this.videoService.setVideoDislike, + none: this.videoService.unsetVideoLike + } + + ratingMethods[nextRating].call(this.videoService, this.video.id) + .subscribe( + () => { + // Update the video like attribute + this.updateVideoRating(this.userRating, nextRating) + this.userRating = nextRating + this.rateUpdated.emit(this.userRating) + }, + + (err: { message: string }) => this.notifier.error(err.message) + ) + } + + private updateVideoRating (oldRating: UserVideoRateType, newRating: UserVideoRateType) { + let likesToIncrement = 0 + let dislikesToIncrement = 0 + + if (oldRating) { + if (oldRating === 'like') likesToIncrement-- + if (oldRating === 'dislike') dislikesToIncrement-- + } + + if (newRating === 'like') likesToIncrement++ + if (newRating === 'dislike') dislikesToIncrement++ + + this.video.likes += likesToIncrement + this.video.dislikes += dislikesToIncrement + + this.video.buildLikeAndDislikePercents() + } +} -- cgit v1.2.3