Move watch action buttons in a dedicated component

This commit is contained in:
Chocobozzz 2021-06-29 17:57:59 +02:00
parent 6ebdd12f88
commit 06a5557979
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
14 changed files with 312 additions and 249 deletions

View File

@ -0,0 +1,85 @@
<div class="video-actions-rates">
<div class="video-actions full-width justify-content-end">
<my-video-rate
[video]="video" [isUserLoggedIn]="isUserLoggedIn"
(rateUpdated)="onRateUpdated($event)" (userRatingLoaded)="onRateUpdated($event)"
></my-video-rate>
<button *ngIf="video.support" (click)="showSupportModal()" (keyup.enter)="showSupportModal()" class="action-button action-button-support" [attr.aria-label]="tooltipSupport"
[ngbTooltip]="tooltipSupport"
placement="bottom auto"
>
<my-global-icon iconName="support" aria-hidden="true"></my-global-icon>
<span class="icon-text" i18n>SUPPORT</span>
</button>
<button (click)="showShareModal()" (keyup.enter)="showShareModal()" class="action-button">
<my-global-icon iconName="share" aria-hidden="true"></my-global-icon>
<span class="icon-text" i18n>SHARE</span>
</button>
<div
class="action-dropdown" ngbDropdown placement="top" role="button" autoClose="outside"
*ngIf="isUserLoggedIn" (openChange)="addContent.openChange($event)"
[ngbTooltip]="tooltipSaveToPlaylist"
placement="bottom auto"
>
<button class="action-button action-button-save" ngbDropdownToggle>
<my-global-icon iconName="playlist-add" aria-hidden="true"></my-global-icon>
<span class="icon-text" i18n>SAVE</span>
</button>
<div ngbDropdownMenu>
<my-video-add-to-playlist #addContent [video]="video"></my-video-add-to-playlist>
</div>
</div>
<ng-container *ngIf="!isUserLoggedIn && !video.isLive">
<button
*ngIf="isVideoDownloadable()" class="action-button action-button-save"
(click)="showDownloadModal()" (keydown.enter)="showDownloadModal()"
>
<my-global-icon iconName="download" aria-hidden="true"></my-global-icon>
<span class="icon-text d-none d-sm-inline" i18n>DOWNLOAD</span>
</button>
<my-video-download #videoDownloadModal></my-video-download>
</ng-container>
<ng-container *ngIf="isUserLoggedIn">
<my-video-actions-dropdown
placement="bottom auto" buttonDirection="horizontal" [buttonStyled]="true" [video]="video" [videoCaptions]="videoCaptions"
[displayOptions]="videoActionsOptions" (videoRemoved)="onVideoRemoved()"
></my-video-actions-dropdown>
</ng-container>
</div>
<div class="likes-dislikes-bar-outer-container">
<div
class="likes-dislikes-bar-inner-container"
*ngIf="video.likes !== 0 || video.dislikes !== 0"
[ngbTooltip]="likesBarTooltipText"
placement="bottom"
>
<div
class="likes-dislikes-bar"
>
<div class="likes-bar" [ngClass]="{ 'liked': userRating !== 'none' }" [ngStyle]="{ 'width.%': video.likesPercent }"></div>
</div>
</div>
</div>
</div>
<div
class="likes-dislikes-bar"
*ngIf="video.likes !== 0 || video.dislikes !== 0"
[ngbTooltip]="likesBarTooltipText"
placement="bottom"
>
<div class="likes-bar" [ngStyle]="{ 'width.%': video.likesPercent }"></div>
</div>
<ng-container *ngIf="video">
<my-support-modal #supportModal [video]="video"></my-support-modal>
<my-video-share #videoShareModal [video]="video" [videoCaptions]="videoCaptions" [playlist]="playlist"></my-video-share>
</ng-container>

View File

@ -0,0 +1,99 @@
@use '_variables' as *;
@use '_mixins' as *;
.video-actions {
height: 40px; // Align with the title
display: flex;
align-items: center;
.action-button:not(:first-child),
.action-dropdown,
my-video-actions-dropdown {
@include margin-left(5px);
}
::ng-deep.action-button {
@include peertube-button;
@include button-with-icon(21px, 0, -1px);
font-size: 100%;
font-weight: $font-semibold;
display: inline-block;
padding: 0 10px;
white-space: nowrap;
background-color: transparent !important;
color: pvar(--actionButtonColor);
text-transform: uppercase;
&::after {
display: none;
}
&:hover {
opacity: 0.9;
}
&.action-button-support {
color: pvar(--supportButtonColor);
my-global-icon {
@include apply-svg-color(pvar(--supportButtonColor));
}
}
&.action-button-support {
my-global-icon {
::ng-deep path:first-child {
fill: pvar(--supportButtonHeartColor) !important;
}
}
}
&.action-button-save {
my-global-icon {
top: 0 !important;
right: -1px;
}
}
.icon-text {
@include margin-left(3px);
}
}
}
.likes-dislikes-bar-outer-container {
position: relative;
}
.likes-dislikes-bar-inner-container {
position: absolute;
height: 20px;
}
.likes-dislikes-bar {
$likes-bar-height: 2px;
height: $likes-bar-height;
margin-top: -$likes-bar-height;
width: 120px;
background-color: #ccc;
position: relative;
top: 10px;
.likes-bar {
height: 100%;
background-color: #909090;
&.liked {
background-color: pvar(--activatedActionButtonColor);
}
}
}
@media screen and (max-width: 450px) {
.action-button .icon-text {
display: none !important;
}
}

View File

@ -0,0 +1,93 @@
import { Component, Input, OnChanges, OnInit, ViewChild } from '@angular/core'
import { RedirectService, ScreenService } from '@app/core'
import { VideoDetails } from '@app/shared/shared-main'
import { VideoShareComponent } from '@app/shared/shared-share-modal'
import { SupportModalComponent } from '@app/shared/shared-support-modal'
import { VideoActionsDisplayType, VideoDownloadComponent } from '@app/shared/shared-video-miniature'
import { VideoPlaylist } from '@app/shared/shared-video-playlist'
import { UserVideoRateType, VideoCaption } from '@shared/models/videos'
@Component({
selector: 'my-action-buttons',
templateUrl: './action-buttons.component.html',
styleUrls: [ './action-buttons.component.scss' ]
})
export class ActionButtonsComponent implements OnInit, OnChanges {
@ViewChild('videoShareModal') videoShareModal: VideoShareComponent
@ViewChild('supportModal') supportModal: SupportModalComponent
@ViewChild('videoDownloadModal') videoDownloadModal: VideoDownloadComponent
@Input() video: VideoDetails
@Input() videoCaptions: VideoCaption[]
@Input() playlist: VideoPlaylist
@Input() isUserLoggedIn: boolean
@Input() currentTime: number
@Input() currentPlaylistPosition: number
likesBarTooltipText = ''
tooltipSupport = ''
tooltipSaveToPlaylist = ''
videoActionsOptions: VideoActionsDisplayType = {
playlist: false,
download: true,
update: true,
blacklist: true,
delete: true,
report: true,
duplicate: true,
mute: true,
liveInfo: true
}
userRating: UserVideoRateType
constructor (
private screenService: ScreenService,
private redirectService: RedirectService
) { }
ngOnInit () {
// Hide the tooltips for unlogged users in mobile view, this adds confusion with the popover
if (this.isUserLoggedIn || !this.screenService.isInMobileView()) {
this.tooltipSupport = $localize`Support options for this video`
this.tooltipSaveToPlaylist = $localize`Save to playlist`
}
}
ngOnChanges () {
this.setVideoLikesBarTooltipText()
}
showDownloadModal () {
this.videoDownloadModal.show(this.video, this.videoCaptions)
}
isVideoDownloadable () {
return this.video && this.video instanceof VideoDetails && this.video.downloadEnabled && !this.video.isLive
}
showSupportModal () {
this.supportModal.show()
}
showShareModal () {
this.videoShareModal.show(this.currentTime, this.currentPlaylistPosition)
}
onRateUpdated (userRating: UserVideoRateType) {
this.userRating = userRating
this.setVideoLikesBarTooltipText()
}
onVideoRemoved () {
this.redirectService.redirectToHomepage()
}
private setVideoLikesBarTooltipText () {
this.likesBarTooltipText = `${this.video.likes} likes / ${this.video.dislikes} dislikes`
}
}

View File

@ -0,0 +1,2 @@
export * from './action-buttons.component'
export * from './video-rate.component'

View File

@ -31,7 +31,7 @@ export class VideoRateComponent implements OnInit, OnChanges, OnDestroy {
private screenService: ScreenService
) { }
async ngOnInit () {
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`

View File

@ -1,3 +1,4 @@
export * from './action-buttons'
export * from './comment'
export * from './information'
export * from './metadata'

View File

@ -1,3 +1,2 @@
export * from './video-avatar-channel.component'
export * from './video-description.component'
export * from './video-rate.component'

View File

@ -1,4 +1,4 @@
import { Component, EventEmitter, Inject, Input, LOCALE_ID, OnChanges, Output } from '@angular/core'
import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'
import { MarkdownService, Notifier } from '@app/core'
import { VideoDetails, VideoService } from '@app/shared/shared-main'
@ -21,8 +21,7 @@ export class VideoDescriptionComponent implements OnChanges {
constructor (
private videoService: VideoService,
private notifier: Notifier,
private markdownService: MarkdownService,
@Inject(LOCALE_ID) private localeId: string
private markdownService: MarkdownService
) { }
ngOnChanges () {

View File

@ -74,90 +74,13 @@
<my-video-views-counter [video]="video"></my-video-views-counter>
</div>
<div class="video-actions-rates">
<div class="video-actions full-width justify-content-end">
<my-video-rate
[video]="video" [isUserLoggedIn]="isUserLoggedIn()"
(rateUpdated)="onRateUpdated($event)" (userRatingLoaded)="onRateUpdated($event)"
></my-video-rate>
<button *ngIf="video.support" (click)="showSupportModal()" (keyup.enter)="showSupportModal()" class="action-button action-button-support" [attr.aria-label]="tooltipSupport"
[ngbTooltip]="tooltipSupport"
placement="bottom auto"
>
<my-global-icon iconName="support" aria-hidden="true"></my-global-icon>
<span class="icon-text" i18n>SUPPORT</span>
</button>
<button (click)="showShareModal()" (keyup.enter)="showShareModal()" class="action-button">
<my-global-icon iconName="share" aria-hidden="true"></my-global-icon>
<span class="icon-text" i18n>SHARE</span>
</button>
<div
class="action-dropdown" ngbDropdown placement="top" role="button" autoClose="outside"
*ngIf="isUserLoggedIn()" (openChange)="addContent.openChange($event)"
[ngbTooltip]="tooltipSaveToPlaylist"
placement="bottom auto"
>
<button class="action-button action-button-save" ngbDropdownToggle>
<my-global-icon iconName="playlist-add" aria-hidden="true"></my-global-icon>
<span class="icon-text" i18n>SAVE</span>
</button>
<div ngbDropdownMenu>
<my-video-add-to-playlist #addContent [video]="video"></my-video-add-to-playlist>
</div>
</div>
<ng-container *ngIf="!isUserLoggedIn() && !isLive()">
<button
*ngIf="isVideoDownloadable()" class="action-button action-button-save"
(click)="showDownloadModal()" (keydown.enter)="showDownloadModal()"
>
<my-global-icon iconName="download" aria-hidden="true"></my-global-icon>
<span class="icon-text d-none d-sm-inline" i18n>DOWNLOAD</span>
</button>
<my-video-download #videoDownloadModal></my-video-download>
</ng-container>
<ng-container *ngIf="isUserLoggedIn()">
<my-video-actions-dropdown
placement="bottom auto" buttonDirection="horizontal" [buttonStyled]="true" [video]="video" [videoCaptions]="videoCaptions"
[displayOptions]="videoActionsOptions" (videoRemoved)="onVideoRemoved()"
></my-video-actions-dropdown>
</ng-container>
</div>
<div class="video-info-likes-dislikes-bar-outer-container">
<div
class="video-info-likes-dislikes-bar-inner-container"
*ngIf="video.likes !== 0 || video.dislikes !== 0"
[ngbTooltip]="likesBarTooltipText"
placement="bottom"
>
<div
class="video-info-likes-dislikes-bar"
>
<div class="likes-bar" [ngClass]="{ 'liked': userRating !== 'none' }" [ngStyle]="{ 'width.%': video.likesPercent }"></div>
</div>
</div>
</div>
</div>
<div
class="video-info-likes-dislikes-bar"
*ngIf="video.likes !== 0 || video.dislikes !== 0"
[ngbTooltip]="likesBarTooltipText"
placement="bottom"
>
<div class="likes-bar" [ngStyle]="{ 'width.%': video.likesPercent }"></div>
</div>
<my-action-buttons
[video]="video" [isUserLoggedIn]="isUserLoggedIn()" [videoCaptions]="videoCaptions" [playlist]="playlist"
[currentTime]="getCurrentTime()" [currentPlaylistPosition]="getCurrentPlaylistPosition()"
></my-action-buttons>
</div>
</div>
<div class="pt-3 border-top video-info-channel d-flex">
<div class="video-info-channel-left d-flex">
<my-video-avatar-channel [video]="video" [genericChannel]="isChannelDisplayNameGeneric()"></my-video-avatar-channel>
@ -264,9 +187,4 @@
<my-privacy-concerns></my-privacy-concerns>
</div>
<ng-container *ngIf="video !== null">
<my-support-modal #supportModal [video]="video"></my-support-modal>
<my-video-share #videoShareModal [video]="video" [videoCaptions]="videoCaptions" [playlist]="playlist"></my-video-share>
</ng-container>
<my-player-styles></my-player-styles>

View File

@ -151,6 +151,7 @@
@include peertube-word-wrap;
@include margin-right(30px);
min-height: 40px; // Align with the action buttons
font-size: 27px;
font-weight: $font-semibold;
@ -211,106 +212,6 @@
@include margin-left(5px);
}
}
.video-actions-rates {
@include margin-left(auto);
@include margin-right(0);
margin-top: 0;
margin-bottom: 10px;
align-items: start;
width: max-content;
.video-actions {
height: 40px; // Align with the title
display: flex;
align-items: center;
.action-button:not(:first-child),
.action-dropdown,
my-video-actions-dropdown {
@include margin-left(5px);
}
::ng-deep.action-button {
@include peertube-button;
@include button-with-icon(21px, 0, -1px);
font-size: 100%;
font-weight: $font-semibold;
display: inline-block;
padding: 0 10px;
white-space: nowrap;
background-color: transparent !important;
color: pvar(--actionButtonColor);
text-transform: uppercase;
&::after {
display: none;
}
&:hover {
opacity: 0.9;
}
&.action-button-support {
color: pvar(--supportButtonColor);
my-global-icon {
@include apply-svg-color(pvar(--supportButtonColor));
}
}
&.action-button-support {
my-global-icon {
::ng-deep path:first-child {
fill: pvar(--supportButtonHeartColor) !important;
}
}
}
&.action-button-save {
my-global-icon {
top: 0 !important;
right: -1px;
}
}
.icon-text {
@include margin-left(3px);
}
}
}
.video-info-likes-dislikes-bar-outer-container {
position: relative;
}
.video-info-likes-dislikes-bar-inner-container {
position: absolute;
height: 20px;
}
.video-info-likes-dislikes-bar {
$likes-bar-height: 2px;
height: $likes-bar-height;
margin-top: -$likes-bar-height;
width: 120px;
background-color: #ccc;
position: relative;
top: 10px;
.likes-bar {
height: 100%;
background-color: #909090;
&.liked {
background-color: pvar(--activatedActionButtonColor);
}
}
}
}
}
.video-attributes {
@ -351,6 +252,18 @@
}
}
my-action-buttons {
@include margin-left(auto);
@include margin-right(0);
display: block;
margin-top: 0;
margin-bottom: 10px;
align-items: start;
width: max-content;
}
my-recommended-videos {
@include padding-left(15px);
@ -411,10 +324,6 @@ my-video-comments {
@media screen and (max-width: 450px) {
.video-bottom {
.action-button .icon-text {
display: none !important;
}
.video-info .video-info-first-row {
.video-info-name {
font-size: 18px;
@ -423,12 +332,12 @@ my-video-comments {
.video-info-date-views {
font-size: 14px;
}
.video-actions-rates {
margin-top: 10px;
}
}
}
my-action-buttons {
margin-top: 10px;
}
}

View File

@ -18,24 +18,13 @@ import {
UserService
} from '@app/core'
import { HooksService } from '@app/core/plugins/hooks.service'
import { RedirectService } from '@app/core/routing/redirect.service'
import { isXPercentInViewport, scrollToTop } from '@app/helpers'
import { Video, VideoCaptionService, VideoDetails, VideoService } from '@app/shared/shared-main'
import { VideoShareComponent } from '@app/shared/shared-share-modal'
import { SupportModalComponent } from '@app/shared/shared-support-modal'
import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription'
import { VideoActionsDisplayType, VideoDownloadComponent } from '@app/shared/shared-video-miniature'
import { VideoActionsDisplayType } from '@app/shared/shared-video-miniature'
import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist'
import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
import {
HTMLServerConfig,
PeerTubeProblemDocument,
ServerErrorCode,
UserVideoRateType,
VideoCaption,
VideoPrivacy,
VideoState
} from '@shared/models'
import { HTMLServerConfig, PeerTubeProblemDocument, ServerErrorCode, VideoCaption, VideoPrivacy, VideoState } from '@shared/models'
import { cleanupVideoWatch, getStoredTheater, getStoredVideoWatchHistory } from '../../../assets/player/peertube-player-local-storage'
import {
CustomizationOptions,
@ -58,10 +47,7 @@ type URLOptions = CustomizationOptions & { playerMode: PlayerMode }
})
export class VideoWatchComponent implements OnInit, OnDestroy {
@ViewChild('videoWatchPlaylist', { static: true }) videoWatchPlaylist: VideoWatchPlaylistComponent
@ViewChild('videoShareModal') videoShareModal: VideoShareComponent
@ViewChild('supportModal') supportModal: SupportModalComponent
@ViewChild('subscribeButton') subscribeButton: SubscribeButtonComponent
@ViewChild('videoDownloadModal') videoDownloadModal: VideoDownloadComponent
player: any
playerElement: HTMLVideoElement
@ -95,8 +81,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
liveInfo: true
}
userRating: UserVideoRateType
private nextVideoUuid = ''
private nextVideoTitle = ''
private currentTime: number
@ -124,7 +108,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
private restExtractor: RestExtractor,
private notifier: Notifier,
private zone: NgZone,
private redirectService: RedirectService,
private videoCaptionService: VideoCaptionService,
private hotkeysService: HotkeysService,
private hooks: HooksService,
@ -203,20 +186,12 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
this.hotkeysService.remove(this.hotkeys)
}
showDownloadModal () {
this.videoDownloadModal.show(this.video, this.videoCaptions)
getCurrentTime () {
return this.currentTime
}
isVideoDownloadable () {
return this.video && this.video instanceof VideoDetails && this.video.downloadEnabled && !this.video.isLive
}
showSupportModal () {
this.supportModal.show()
}
showShareModal () {
this.videoShareModal.show(this.currentTime, this.videoWatchPlaylist.currentPlaylistPosition)
getCurrentPlaylistPosition () {
return this.videoWatchPlaylist.currentPlaylistPosition
}
isUserLoggedIn () {
@ -245,10 +220,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
}
}
onVideoRemoved () {
this.redirectService.redirectToHomepage()
}
isVideoToTranscode () {
return this.video && this.video.state.id === VideoState.TO_TRANSCODE
}
@ -261,10 +232,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
return this.video && this.video.scheduledUpdate !== undefined
}
isLive () {
return !!(this.video?.isLive)
}
isWaitingForLive () {
return this.video?.state.id === VideoState.WAITING_FOR_LIVE
}
@ -311,11 +278,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
this.loadVideo(videoId)
}
onRateUpdated (userRating: UserVideoRateType) {
this.userRating = userRating
this.setVideoLikesBarTooltipText()
}
displayOtherVideosAsRow () {
// Use the same value as in the SASS file
return this.screenService.getWindowInnerWidth() <= 1100
@ -421,10 +383,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
})
}
private setVideoLikesBarTooltipText () {
this.likesBarTooltipText = `${this.video.likes} likes / ${this.video.dislikes} dislikes`
}
private handleError (err: any) {
const errorMessage: string = typeof err === 'string' ? err : err.message
if (!errorMessage) return
@ -467,8 +425,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
this.buildPlayer(urlOptions)
.catch(err => console.error('Cannot build the player', err))
this.setVideoLikesBarTooltipText()
this.setOpenGraphTags()
const hookOptions = {

View File

@ -19,6 +19,7 @@ import {
VideoDescriptionComponent,
VideoRateComponent,
VideoWatchPlaylistComponent,
ActionButtonsComponent,
PrivacyConcernsComponent
} from './shared'
import { VideoCommentAddComponent } from './shared/comment/video-comment-add.component'
@ -53,6 +54,7 @@ import { VideoWatchComponent } from './video-watch.component'
VideoRateComponent,
VideoDescriptionComponent,
PrivacyConcernsComponent,
ActionButtonsComponent,
VideoCommentsComponent,
VideoCommentAddComponent,