aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/+videos/+video-watch/shared/action-buttons
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/+videos/+video-watch/shared/action-buttons')
-rw-r--r--client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.html85
-rw-r--r--client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.scss99
-rw-r--r--client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.ts93
-rw-r--r--client/src/app/+videos/+video-watch/shared/action-buttons/index.ts2
-rw-r--r--client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.html23
-rw-r--r--client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.scss15
-rw-r--r--client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.ts142
7 files changed, 459 insertions, 0 deletions
diff --git a/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.html b/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.html
new file mode 100644
index 000000000..8fb244cc4
--- /dev/null
+++ b/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.html
@@ -0,0 +1,85 @@
1<div class="video-actions-rates">
2 <div class="video-actions full-width justify-content-end">
3 <my-video-rate
4 [video]="video" [isUserLoggedIn]="isUserLoggedIn"
5 (rateUpdated)="onRateUpdated($event)" (userRatingLoaded)="onRateUpdated($event)"
6 ></my-video-rate>
7
8 <button *ngIf="video.support" (click)="showSupportModal()" (keyup.enter)="showSupportModal()" class="action-button action-button-support" [attr.aria-label]="tooltipSupport"
9 [ngbTooltip]="tooltipSupport"
10 placement="bottom auto"
11 >
12 <my-global-icon iconName="support" aria-hidden="true"></my-global-icon>
13 <span class="icon-text" i18n>SUPPORT</span>
14 </button>
15
16 <button (click)="showShareModal()" (keyup.enter)="showShareModal()" class="action-button">
17 <my-global-icon iconName="share" aria-hidden="true"></my-global-icon>
18 <span class="icon-text" i18n>SHARE</span>
19 </button>
20
21 <div
22 class="action-dropdown" ngbDropdown placement="top" role="button" autoClose="outside"
23 *ngIf="isUserLoggedIn" (openChange)="addContent.openChange($event)"
24 [ngbTooltip]="tooltipSaveToPlaylist"
25 placement="bottom auto"
26 >
27 <button class="action-button action-button-save" ngbDropdownToggle>
28 <my-global-icon iconName="playlist-add" aria-hidden="true"></my-global-icon>
29 <span class="icon-text" i18n>SAVE</span>
30 </button>
31
32 <div ngbDropdownMenu>
33 <my-video-add-to-playlist #addContent [video]="video"></my-video-add-to-playlist>
34 </div>
35 </div>
36
37 <ng-container *ngIf="!isUserLoggedIn && !video.isLive">
38 <button
39 *ngIf="isVideoDownloadable()" class="action-button action-button-save"
40 (click)="showDownloadModal()" (keydown.enter)="showDownloadModal()"
41 >
42 <my-global-icon iconName="download" aria-hidden="true"></my-global-icon>
43 <span class="icon-text d-none d-sm-inline" i18n>DOWNLOAD</span>
44 </button>
45
46 <my-video-download #videoDownloadModal></my-video-download>
47 </ng-container>
48
49 <ng-container *ngIf="isUserLoggedIn">
50 <my-video-actions-dropdown
51 placement="bottom auto" buttonDirection="horizontal" [buttonStyled]="true" [video]="video" [videoCaptions]="videoCaptions"
52 [displayOptions]="videoActionsOptions" (videoRemoved)="onVideoRemoved()"
53 ></my-video-actions-dropdown>
54 </ng-container>
55 </div>
56
57 <div class="likes-dislikes-bar-outer-container">
58 <div
59 class="likes-dislikes-bar-inner-container"
60 *ngIf="video.likes !== 0 || video.dislikes !== 0"
61 [ngbTooltip]="likesBarTooltipText"
62 placement="bottom"
63 >
64 <div
65 class="likes-dislikes-bar"
66 >
67 <div class="likes-bar" [ngClass]="{ 'liked': userRating !== 'none' }" [ngStyle]="{ 'width.%': video.likesPercent }"></div>
68 </div>
69 </div>
70 </div>
71</div>
72
73<div
74 class="likes-dislikes-bar"
75 *ngIf="video.likes !== 0 || video.dislikes !== 0"
76 [ngbTooltip]="likesBarTooltipText"
77 placement="bottom"
78>
79 <div class="likes-bar" [ngStyle]="{ 'width.%': video.likesPercent }"></div>
80</div>
81
82<ng-container *ngIf="video">
83 <my-support-modal #supportModal [video]="video"></my-support-modal>
84 <my-video-share #videoShareModal [video]="video" [videoCaptions]="videoCaptions" [playlist]="playlist"></my-video-share>
85</ng-container>
diff --git a/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.scss b/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.scss
new file mode 100644
index 000000000..967d515e6
--- /dev/null
+++ b/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.scss
@@ -0,0 +1,99 @@
1@use '_variables' as *;
2@use '_mixins' as *;
3
4.video-actions {
5 height: 40px; // Align with the title
6 display: flex;
7 align-items: center;
8
9 .action-button:not(:first-child),
10 .action-dropdown,
11 my-video-actions-dropdown {
12 @include margin-left(5px);
13 }
14
15 ::ng-deep.action-button {
16 @include peertube-button;
17 @include button-with-icon(21px, 0, -1px);
18
19 font-size: 100%;
20 font-weight: $font-semibold;
21 display: inline-block;
22 padding: 0 10px;
23 white-space: nowrap;
24 background-color: transparent !important;
25 color: pvar(--actionButtonColor);
26 text-transform: uppercase;
27
28 &::after {
29 display: none;
30 }
31
32 &:hover {
33 opacity: 0.9;
34 }
35
36 &.action-button-support {
37 color: pvar(--supportButtonColor);
38
39 my-global-icon {
40 @include apply-svg-color(pvar(--supportButtonColor));
41 }
42 }
43
44 &.action-button-support {
45 my-global-icon {
46 ::ng-deep path:first-child {
47 fill: pvar(--supportButtonHeartColor) !important;
48 }
49 }
50 }
51
52 &.action-button-save {
53 my-global-icon {
54 top: 0 !important;
55 right: -1px;
56 }
57 }
58
59 .icon-text {
60 @include margin-left(3px);
61 }
62 }
63}
64
65.likes-dislikes-bar-outer-container {
66 position: relative;
67}
68
69.likes-dislikes-bar-inner-container {
70 position: absolute;
71 height: 20px;
72}
73
74.likes-dislikes-bar {
75 $likes-bar-height: 2px;
76
77 height: $likes-bar-height;
78 margin-top: -$likes-bar-height;
79
80 width: 120px;
81 background-color: #ccc;
82 position: relative;
83 top: 10px;
84
85 .likes-bar {
86 height: 100%;
87 background-color: #909090;
88
89 &.liked {
90 background-color: pvar(--activatedActionButtonColor);
91 }
92 }
93}
94
95@media screen and (max-width: 450px) {
96 .action-button .icon-text {
97 display: none !important;
98 }
99}
diff --git a/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.ts b/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.ts
new file mode 100644
index 000000000..e59238ffe
--- /dev/null
+++ b/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.ts
@@ -0,0 +1,93 @@
1import { Component, Input, OnChanges, OnInit, ViewChild } from '@angular/core'
2import { RedirectService, ScreenService } from '@app/core'
3import { VideoDetails } from '@app/shared/shared-main'
4import { VideoShareComponent } from '@app/shared/shared-share-modal'
5import { SupportModalComponent } from '@app/shared/shared-support-modal'
6import { VideoActionsDisplayType, VideoDownloadComponent } from '@app/shared/shared-video-miniature'
7import { VideoPlaylist } from '@app/shared/shared-video-playlist'
8import { UserVideoRateType, VideoCaption } from '@shared/models/videos'
9
10@Component({
11 selector: 'my-action-buttons',
12 templateUrl: './action-buttons.component.html',
13 styleUrls: [ './action-buttons.component.scss' ]
14})
15export class ActionButtonsComponent implements OnInit, OnChanges {
16 @ViewChild('videoShareModal') videoShareModal: VideoShareComponent
17 @ViewChild('supportModal') supportModal: SupportModalComponent
18 @ViewChild('videoDownloadModal') videoDownloadModal: VideoDownloadComponent
19
20 @Input() video: VideoDetails
21 @Input() videoCaptions: VideoCaption[]
22 @Input() playlist: VideoPlaylist
23
24 @Input() isUserLoggedIn: boolean
25
26 @Input() currentTime: number
27 @Input() currentPlaylistPosition: number
28
29 likesBarTooltipText = ''
30
31 tooltipSupport = ''
32 tooltipSaveToPlaylist = ''
33
34 videoActionsOptions: VideoActionsDisplayType = {
35 playlist: false,
36 download: true,
37 update: true,
38 blacklist: true,
39 delete: true,
40 report: true,
41 duplicate: true,
42 mute: true,
43 liveInfo: true
44 }
45
46 userRating: UserVideoRateType
47
48 constructor (
49 private screenService: ScreenService,
50 private redirectService: RedirectService
51 ) { }
52
53 ngOnInit () {
54 // Hide the tooltips for unlogged users in mobile view, this adds confusion with the popover
55 if (this.isUserLoggedIn || !this.screenService.isInMobileView()) {
56 this.tooltipSupport = $localize`Support options for this video`
57 this.tooltipSaveToPlaylist = $localize`Save to playlist`
58 }
59 }
60
61 ngOnChanges () {
62 this.setVideoLikesBarTooltipText()
63 }
64
65 showDownloadModal () {
66 this.videoDownloadModal.show(this.video, this.videoCaptions)
67 }
68
69 isVideoDownloadable () {
70 return this.video && this.video instanceof VideoDetails && this.video.downloadEnabled && !this.video.isLive
71 }
72
73 showSupportModal () {
74 this.supportModal.show()
75 }
76
77 showShareModal () {
78 this.videoShareModal.show(this.currentTime, this.currentPlaylistPosition)
79 }
80
81 onRateUpdated (userRating: UserVideoRateType) {
82 this.userRating = userRating
83 this.setVideoLikesBarTooltipText()
84 }
85
86 onVideoRemoved () {
87 this.redirectService.redirectToHomepage()
88 }
89
90 private setVideoLikesBarTooltipText () {
91 this.likesBarTooltipText = `${this.video.likes} likes / ${this.video.dislikes} dislikes`
92 }
93}
diff --git a/client/src/app/+videos/+video-watch/shared/action-buttons/index.ts b/client/src/app/+videos/+video-watch/shared/action-buttons/index.ts
new file mode 100644
index 000000000..3844dd12e
--- /dev/null
+++ b/client/src/app/+videos/+video-watch/shared/action-buttons/index.ts
@@ -0,0 +1,2 @@
1export * from './action-buttons.component'
2export * from './video-rate.component'
diff --git a/client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.html b/client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.html
new file mode 100644
index 000000000..7dd9b3678
--- /dev/null
+++ b/client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.html
@@ -0,0 +1,23 @@
1<ng-template #ratePopoverText>
2 <span [innerHTML]="getRatePopoverText()"></span>
3</ng-template>
4
5<button
6 [ngbPopover]="getRatePopoverText() && ratePopoverText" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()" (keyup.enter)="setLike()"
7 class="action-button action-button-like" [attr.aria-pressed]="userRating === 'like'" [attr.aria-label]="tooltipLike"
8 [ngbTooltip]="tooltipLike"
9 placement="bottom auto"
10>
11 <my-global-icon iconName="like"></my-global-icon>
12 <span *ngIf="video.likes" class="count">{{ video.likes }}</span>
13</button>
14
15<button
16 [ngbPopover]="getRatePopoverText() && ratePopoverText" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()" (keyup.enter)="setDislike()"
17 class="action-button action-button-dislike" [attr.aria-pressed]="userRating === 'dislike'" [attr.aria-label]="tooltipDislike"
18 [ngbTooltip]="tooltipDislike"
19 placement="bottom auto"
20>
21 <my-global-icon iconName="dislike"></my-global-icon>
22 <span *ngIf="video.dislikes" class="count">{{ video.dislikes }}</span>
23</button>
diff --git a/client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.scss b/client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.scss
new file mode 100644
index 000000000..f4f696f33
--- /dev/null
+++ b/client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.scss
@@ -0,0 +1,15 @@
1@use '_variables' as *;
2@use '_mixins' as *;
3
4.action-button-like,
5.action-button-dislike {
6 filter: brightness(120%);
7
8 .count {
9 margin: 0 5px;
10 }
11}
12
13.activated {
14 color: pvar(--activatedActionButtonColor) !important;
15}
diff --git a/client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.ts b/client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.ts
new file mode 100644
index 000000000..ecb5a9281
--- /dev/null
+++ b/client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.ts
@@ -0,0 +1,142 @@
1import { Hotkey, HotkeysService } from 'angular2-hotkeys'
2import { Observable } from 'rxjs'
3import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output } from '@angular/core'
4import { Notifier, ScreenService } from '@app/core'
5import { VideoDetails, VideoService } from '@app/shared/shared-main'
6import { UserVideoRateType } from '@shared/models'
7
8@Component({
9 selector: 'my-video-rate',
10 templateUrl: './video-rate.component.html',
11 styleUrls: [ './video-rate.component.scss' ]
12})
13export class VideoRateComponent implements OnInit, OnChanges, OnDestroy {
14 @Input() video: VideoDetails
15 @Input() isUserLoggedIn: boolean
16
17 @Output() userRatingLoaded = new EventEmitter<UserVideoRateType>()
18 @Output() rateUpdated = new EventEmitter<UserVideoRateType>()
19
20 userRating: UserVideoRateType
21
22 tooltipLike = ''
23 tooltipDislike = ''
24
25 private hotkeys: Hotkey[]
26
27 constructor (
28 private videoService: VideoService,
29 private notifier: Notifier,
30 private hotkeysService: HotkeysService,
31 private screenService: ScreenService
32 ) { }
33
34 ngOnInit () {
35 // Hide the tooltips for unlogged users in mobile view, this adds confusion with the popover
36 if (this.isUserLoggedIn || !this.screenService.isInMobileView()) {
37 this.tooltipLike = $localize`Like this video`
38 this.tooltipDislike = $localize`Dislike this video`
39 }
40
41 if (this.isUserLoggedIn) {
42 this.hotkeys = [
43 new Hotkey('shift+l', () => {
44 this.setLike()
45 return false
46 }, undefined, $localize`Like the video`),
47
48 new Hotkey('shift+d', () => {
49 this.setDislike()
50 return false
51 }, undefined, $localize`Dislike the video`)
52 ]
53
54 this.hotkeysService.add(this.hotkeys)
55 }
56 }
57
58 ngOnChanges () {
59 this.checkUserRating()
60 }
61
62 ngOnDestroy () {
63 this.hotkeysService.remove(this.hotkeys)
64 }
65
66 setLike () {
67 if (this.isUserLoggedIn === false) return
68
69 // Already liked this video
70 if (this.userRating === 'like') this.setRating('none')
71 else this.setRating('like')
72 }
73
74 setDislike () {
75 if (this.isUserLoggedIn === false) return
76
77 // Already disliked this video
78 if (this.userRating === 'dislike') this.setRating('none')
79 else this.setRating('dislike')
80 }
81
82 getRatePopoverText () {
83 if (this.isUserLoggedIn) return undefined
84
85 return $localize`You need to be <a href="/login">logged in</a> to rate this video.`
86 }
87
88 private checkUserRating () {
89 // Unlogged users do not have ratings
90 if (this.isUserLoggedIn === false) return
91
92 this.videoService.getUserVideoRating(this.video.id)
93 .subscribe(
94 ratingObject => {
95 if (!ratingObject) return
96
97 this.userRating = ratingObject.rating
98 this.userRatingLoaded.emit(this.userRating)
99 },
100
101 err => this.notifier.error(err.message)
102 )
103 }
104
105 private setRating (nextRating: UserVideoRateType) {
106 const ratingMethods: { [id in UserVideoRateType]: (id: number) => Observable<any> } = {
107 like: this.videoService.setVideoLike,
108 dislike: this.videoService.setVideoDislike,
109 none: this.videoService.unsetVideoLike
110 }
111
112 ratingMethods[nextRating].call(this.videoService, this.video.id)
113 .subscribe(
114 () => {
115 // Update the video like attribute
116 this.updateVideoRating(this.userRating, nextRating)
117 this.userRating = nextRating
118 this.rateUpdated.emit(this.userRating)
119 },
120
121 (err: { message: string }) => this.notifier.error(err.message)
122 )
123 }
124
125 private updateVideoRating (oldRating: UserVideoRateType, newRating: UserVideoRateType) {
126 let likesToIncrement = 0
127 let dislikesToIncrement = 0
128
129 if (oldRating) {
130 if (oldRating === 'like') likesToIncrement--
131 if (oldRating === 'dislike') dislikesToIncrement--
132 }
133
134 if (newRating === 'like') likesToIncrement++
135 if (newRating === 'dislike') dislikesToIncrement++
136
137 this.video.likes += likesToIncrement
138 this.video.dislikes += dislikesToIncrement
139
140 this.video.buildLikeAndDislikePercents()
141 }
142}