--- /dev/null
+<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>
--- /dev/null
+@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;
+ }
+}
--- /dev/null
+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`
+ }
+}
--- /dev/null
+export * from './action-buttons.component'
+export * from './video-rate.component'
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`
+export * from './action-buttons'
export * from './comment'
export * from './information'
export * from './metadata'
export * from './video-avatar-channel.component'
export * from './video-description.component'
-export * from './video-rate.component'
-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'
constructor (
private videoService: VideoService,
private notifier: Notifier,
- private markdownService: MarkdownService,
- @Inject(LOCALE_ID) private localeId: string
+ private markdownService: MarkdownService
) { }
ngOnChanges () {
<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>
<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>
@include peertube-word-wrap;
@include margin-right(30px);
+
min-height: 40px; // Align with the action buttons
font-size: 27px;
font-weight: $font-semibold;
@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 {
}
}
+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);
@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;
.video-info-date-views {
font-size: 14px;
}
-
- .video-actions-rates {
- margin-top: 10px;
- }
}
}
+
+ my-action-buttons {
+ margin-top: 10px;
+ }
}
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,
})
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
liveInfo: true
}
- userRating: UserVideoRateType
-
private nextVideoUuid = ''
private nextVideoTitle = ''
private currentTime: number
private restExtractor: RestExtractor,
private notifier: Notifier,
private zone: NgZone,
- private redirectService: RedirectService,
private videoCaptionService: VideoCaptionService,
private hotkeysService: HotkeysService,
private hooks: HooksService,
this.hotkeysService.remove(this.hotkeys)
}
- showDownloadModal () {
- this.videoDownloadModal.show(this.video, this.videoCaptions)
- }
-
- isVideoDownloadable () {
- return this.video && this.video instanceof VideoDetails && this.video.downloadEnabled && !this.video.isLive
+ getCurrentTime () {
+ return this.currentTime
}
- showSupportModal () {
- this.supportModal.show()
- }
-
- showShareModal () {
- this.videoShareModal.show(this.currentTime, this.videoWatchPlaylist.currentPlaylistPosition)
+ getCurrentPlaylistPosition () {
+ return this.videoWatchPlaylist.currentPlaylistPosition
}
isUserLoggedIn () {
}
}
- onVideoRemoved () {
- this.redirectService.redirectToHomepage()
- }
-
isVideoToTranscode () {
return this.video && this.video.state.id === VideoState.TO_TRANSCODE
}
return this.video && this.video.scheduledUpdate !== undefined
}
- isLive () {
- return !!(this.video?.isLive)
- }
-
isWaitingForLive () {
return this.video?.state.id === VideoState.WAITING_FOR_LIVE
}
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
})
}
- 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
this.buildPlayer(urlOptions)
.catch(err => console.error('Cannot build the player', err))
- this.setVideoLikesBarTooltipText()
-
this.setOpenGraphTags()
const hookOptions = {
VideoDescriptionComponent,
VideoRateComponent,
VideoWatchPlaylistComponent,
+ ActionButtonsComponent,
PrivacyConcernsComponent
} from './shared'
import { VideoCommentAddComponent } from './shared/comment/video-comment-add.component'
VideoRateComponent,
VideoDescriptionComponent,
PrivacyConcernsComponent,
+ ActionButtonsComponent,
VideoCommentsComponent,
VideoCommentAddComponent,