From 3a0fb65c61f80b510bce979a45d59d17948745e8 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 5 Apr 2019 10:52:27 +0200 Subject: Add video miniature dropdown --- .../src/app/shared/video/abstract-video-list.html | 10 +- client/src/app/shared/video/abstract-video-list.ts | 6 +- .../video/modals/video-blacklist.component.html | 38 ++++ .../video/modals/video-blacklist.component.scss | 6 + .../video/modals/video-blacklist.component.ts | 76 +++++++ .../video/modals/video-download.component.html | 52 +++++ .../video/modals/video-download.component.scss | 25 +++ .../video/modals/video-download.component.ts | 69 ++++++ .../video/modals/video-report.component.html | 36 ++++ .../video/modals/video-report.component.scss | 10 + .../shared/video/modals/video-report.component.ts | 81 +++++++ .../video/video-actions-dropdown.component.html | 21 ++ .../video/video-actions-dropdown.component.scss | 12 ++ .../video/video-actions-dropdown.component.ts | 237 +++++++++++++++++++++ client/src/app/shared/video/video-details.model.ts | 16 -- .../shared/video/video-miniature.component.html | 89 ++++---- .../shared/video/video-miniature.component.scss | 36 ++++ .../app/shared/video/video-miniature.component.ts | 70 ++++-- client/src/app/shared/video/video.model.ts | 19 +- .../shared/video/videos-selection.component.html | 2 +- 20 files changed, 835 insertions(+), 76 deletions(-) create mode 100644 client/src/app/shared/video/modals/video-blacklist.component.html create mode 100644 client/src/app/shared/video/modals/video-blacklist.component.scss create mode 100644 client/src/app/shared/video/modals/video-blacklist.component.ts create mode 100644 client/src/app/shared/video/modals/video-download.component.html create mode 100644 client/src/app/shared/video/modals/video-download.component.scss create mode 100644 client/src/app/shared/video/modals/video-download.component.ts create mode 100644 client/src/app/shared/video/modals/video-report.component.html create mode 100644 client/src/app/shared/video/modals/video-report.component.scss create mode 100644 client/src/app/shared/video/modals/video-report.component.ts create mode 100644 client/src/app/shared/video/video-actions-dropdown.component.html create mode 100644 client/src/app/shared/video/video-actions-dropdown.component.scss create mode 100644 client/src/app/shared/video/video-actions-dropdown.component.ts (limited to 'client/src/app/shared/video') diff --git a/client/src/app/shared/video/abstract-video-list.html b/client/src/app/shared/video/abstract-video-list.html index e134654a3..d1b761674 100644 --- a/client/src/app/shared/video/abstract-video-list.html +++ b/client/src/app/shared/video/abstract-video-list.html @@ -1,4 +1,4 @@ -
+
@@ -11,7 +11,7 @@
@@ -22,7 +22,11 @@ myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" class="videos" > - +
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts index 099650129..cf43d429d 100644 --- a/client/src/app/shared/video/abstract-video-list.ts +++ b/client/src/app/shared/video/abstract-video-list.ts @@ -26,11 +26,11 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor syndicationItems: Syndication[] = [] loadOnInit = true - marginContent = true videos: Video[] = [] ownerDisplayType: OwnerDisplayType = 'account' displayModerationBlock = false titleTooltip: string + displayVideoActions = true disabled = false @@ -120,6 +120,10 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor throw new Error('toggleModerationDisplay is not implemented') } + removeVideoFromArray (video: Video) { + this.videos = this.videos.filter(v => v.id !== video.id) + } + // On videos hook for children that want to do something protected onMoreVideos () { /* empty */ } diff --git a/client/src/app/shared/video/modals/video-blacklist.component.html b/client/src/app/shared/video/modals/video-blacklist.component.html new file mode 100644 index 000000000..1a87bdcd4 --- /dev/null +++ b/client/src/app/shared/video/modals/video-blacklist.component.html @@ -0,0 +1,38 @@ + + + + + diff --git a/client/src/app/shared/video/modals/video-blacklist.component.scss b/client/src/app/shared/video/modals/video-blacklist.component.scss new file mode 100644 index 000000000..afcdb9a16 --- /dev/null +++ b/client/src/app/shared/video/modals/video-blacklist.component.scss @@ -0,0 +1,6 @@ +@import 'variables'; +@import 'mixins'; + +textarea { + @include peertube-textarea(100%, 100px); +} diff --git a/client/src/app/shared/video/modals/video-blacklist.component.ts b/client/src/app/shared/video/modals/video-blacklist.component.ts new file mode 100644 index 000000000..4e4e8dc50 --- /dev/null +++ b/client/src/app/shared/video/modals/video-blacklist.component.ts @@ -0,0 +1,76 @@ +import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' +import { Notifier, RedirectService } from '@app/core' +import { VideoBlacklistService } from '../../../shared/video-blacklist' +import { VideoDetails } from '../../../shared/video/video-details.model' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' +import { NgbModal } from '@ng-bootstrap/ng-bootstrap' +import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' +import { FormReactive, VideoBlacklistValidatorsService } from '@app/shared/forms' + +@Component({ + selector: 'my-video-blacklist', + templateUrl: './video-blacklist.component.html', + styleUrls: [ './video-blacklist.component.scss' ] +}) +export class VideoBlacklistComponent extends FormReactive implements OnInit { + @Input() video: VideoDetails = null + + @ViewChild('modal') modal: NgbModal + + @Output() videoBlacklisted = new EventEmitter() + + error: string = null + + private openedModal: NgbModalRef + + constructor ( + protected formValidatorService: FormValidatorService, + private modalService: NgbModal, + private videoBlacklistValidatorsService: VideoBlacklistValidatorsService, + private videoBlacklistService: VideoBlacklistService, + private notifier: Notifier, + private redirectService: RedirectService, + private i18n: I18n + ) { + super() + } + + ngOnInit () { + const defaultValues = { unfederate: 'true' } + + this.buildForm({ + reason: this.videoBlacklistValidatorsService.VIDEO_BLACKLIST_REASON, + unfederate: null + }, defaultValues) + } + + show () { + this.openedModal = this.modalService.open(this.modal, { keyboard: false }) + } + + hide () { + this.openedModal.close() + this.openedModal = null + } + + blacklist () { + const reason = this.form.value[ 'reason' ] || undefined + const unfederate = this.video.isLocal ? this.form.value[ 'unfederate' ] : undefined + + this.videoBlacklistService.blacklistVideo(this.video.id, reason, unfederate) + .subscribe( + () => { + this.notifier.success(this.i18n('Video blacklisted.')) + this.hide() + + this.video.blacklisted = true + this.video.blacklistedReason = reason + + this.videoBlacklisted.emit() + }, + + err => this.notifier.error(err.message) + ) + } +} diff --git a/client/src/app/shared/video/modals/video-download.component.html b/client/src/app/shared/video/modals/video-download.component.html new file mode 100644 index 000000000..2bb5d6d37 --- /dev/null +++ b/client/src/app/shared/video/modals/video-download.component.html @@ -0,0 +1,52 @@ + + + + + + + diff --git a/client/src/app/shared/video/modals/video-download.component.scss b/client/src/app/shared/video/modals/video-download.component.scss new file mode 100644 index 000000000..3e826c3b6 --- /dev/null +++ b/client/src/app/shared/video/modals/video-download.component.scss @@ -0,0 +1,25 @@ +@import 'variables'; +@import 'mixins'; + +.peertube-select-container { + @include peertube-select-container(100px); + + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-right: none; + + select { + height: inherit; + } +} + +.download-type { + margin-top: 30px; + + .peertube-radio-container { + @include peertube-radio-container; + + display: inline-block; + margin-right: 30px; + } +} diff --git a/client/src/app/shared/video/modals/video-download.component.ts b/client/src/app/shared/video/modals/video-download.component.ts new file mode 100644 index 000000000..64aaeb3c8 --- /dev/null +++ b/client/src/app/shared/video/modals/video-download.component.ts @@ -0,0 +1,69 @@ +import { Component, ElementRef, ViewChild } from '@angular/core' +import { VideoDetails } from '../../../shared/video/video-details.model' +import { NgbModal } from '@ng-bootstrap/ng-bootstrap' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { Notifier } from '@app/core' + +@Component({ + selector: 'my-video-download', + templateUrl: './video-download.component.html', + styleUrls: [ './video-download.component.scss' ] +}) +export class VideoDownloadComponent { + @ViewChild('modal') modal: ElementRef + + downloadType: 'direct' | 'torrent' | 'magnet' = 'torrent' + resolutionId: number | string = -1 + + private video: VideoDetails + + constructor ( + private notifier: Notifier, + private modalService: NgbModal, + private i18n: I18n + ) { } + + show (video: VideoDetails) { + this.video = video + + const m = this.modalService.open(this.modal) + m.result.then(() => this.onClose()) + .catch(() => this.onClose()) + + this.resolutionId = this.video.files[0].resolution.id + } + + onClose () { + this.video = undefined + } + + download () { + window.location.assign(this.getLink()) + } + + getLink () { + // HTML select send us a string, so convert it to a number + this.resolutionId = parseInt(this.resolutionId.toString(), 10) + + const file = this.video.files.find(f => f.resolution.id === this.resolutionId) + if (!file) { + console.error('Could not find file with resolution %d.', this.resolutionId) + return + } + + switch (this.downloadType) { + case 'direct': + return file.fileDownloadUrl + + case 'torrent': + return file.torrentDownloadUrl + + case 'magnet': + return file.magnetUri + } + } + + activateCopiedMessage () { + this.notifier.success(this.i18n('Copied')) + } +} diff --git a/client/src/app/shared/video/modals/video-report.component.html b/client/src/app/shared/video/modals/video-report.component.html new file mode 100644 index 000000000..b9434da26 --- /dev/null +++ b/client/src/app/shared/video/modals/video-report.component.html @@ -0,0 +1,36 @@ + + + + + diff --git a/client/src/app/shared/video/modals/video-report.component.scss b/client/src/app/shared/video/modals/video-report.component.scss new file mode 100644 index 000000000..4713660a2 --- /dev/null +++ b/client/src/app/shared/video/modals/video-report.component.scss @@ -0,0 +1,10 @@ +@import 'variables'; +@import 'mixins'; + +.information { + margin-bottom: 20px; +} + +textarea { + @include peertube-textarea(100%, 100px); +} diff --git a/client/src/app/shared/video/modals/video-report.component.ts b/client/src/app/shared/video/modals/video-report.component.ts new file mode 100644 index 000000000..725dd020f --- /dev/null +++ b/client/src/app/shared/video/modals/video-report.component.ts @@ -0,0 +1,81 @@ +import { Component, Input, OnInit, ViewChild } from '@angular/core' +import { Notifier } from '@app/core' +import { FormReactive } from '../../../shared/forms' +import { VideoDetails } from '../../../shared/video/video-details.model' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' +import { VideoAbuseValidatorsService } from '@app/shared/forms/form-validators/video-abuse-validators.service' +import { NgbModal } from '@ng-bootstrap/ng-bootstrap' +import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' +import { VideoAbuseService } from '@app/shared/video-abuse' + +@Component({ + selector: 'my-video-report', + templateUrl: './video-report.component.html', + styleUrls: [ './video-report.component.scss' ] +}) +export class VideoReportComponent extends FormReactive implements OnInit { + @Input() video: VideoDetails = null + + @ViewChild('modal') modal: NgbModal + + error: string = null + + private openedModal: NgbModalRef + + constructor ( + protected formValidatorService: FormValidatorService, + private modalService: NgbModal, + private videoAbuseValidatorsService: VideoAbuseValidatorsService, + private videoAbuseService: VideoAbuseService, + private notifier: Notifier, + private i18n: I18n + ) { + super() + } + + get currentHost () { + return window.location.host + } + + get originHost () { + if (this.isRemoteVideo()) { + return this.video.account.host + } + + return '' + } + + ngOnInit () { + this.buildForm({ + reason: this.videoAbuseValidatorsService.VIDEO_ABUSE_REASON + }) + } + + show () { + this.openedModal = this.modalService.open(this.modal, { keyboard: false }) + } + + hide () { + this.openedModal.close() + this.openedModal = null + } + + report () { + const reason = this.form.value['reason'] + + this.videoAbuseService.reportVideo(this.video.id, reason) + .subscribe( + () => { + this.notifier.success(this.i18n('Video reported.')) + this.hide() + }, + + err => this.notifier.error(err.message) + ) + } + + isRemoteVideo () { + return !this.video.isLocal + } +} diff --git a/client/src/app/shared/video/video-actions-dropdown.component.html b/client/src/app/shared/video/video-actions-dropdown.component.html new file mode 100644 index 000000000..300fe318a --- /dev/null +++ b/client/src/app/shared/video/video-actions-dropdown.component.html @@ -0,0 +1,21 @@ + + +
+ + +
+ +
+
+ + + + + + +
diff --git a/client/src/app/shared/video/video-actions-dropdown.component.scss b/client/src/app/shared/video/video-actions-dropdown.component.scss new file mode 100644 index 000000000..7ffdce822 --- /dev/null +++ b/client/src/app/shared/video/video-actions-dropdown.component.scss @@ -0,0 +1,12 @@ +.playlist-dropdown { + position: absolute; + + .anchor { + display: block; + opacity: 0; + } +} + +/deep/ .icon-playlist-add { + left: 2px; +} diff --git a/client/src/app/shared/video/video-actions-dropdown.component.ts b/client/src/app/shared/video/video-actions-dropdown.component.ts new file mode 100644 index 000000000..90bdf7df8 --- /dev/null +++ b/client/src/app/shared/video/video-actions-dropdown.component.ts @@ -0,0 +1,237 @@ +import { Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { DropdownAction, DropdownButtonSize, DropdownDirection } from '@app/shared/buttons/action-dropdown.component' +import { AuthService, ConfirmService, Notifier, ServerService } from '@app/core' +import { BlocklistService } from '@app/shared/blocklist' +import { Video } from '@app/shared/video/video.model' +import { VideoService } from '@app/shared/video/video.service' +import { VideoDetails } from '@app/shared/video/video-details.model' +import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' +import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component' +import { VideoDownloadComponent } from '@app/shared/video/modals/video-download.component' +import { VideoReportComponent } from '@app/shared/video/modals/video-report.component' +import { VideoBlacklistComponent } from '@app/shared/video/modals/video-blacklist.component' +import { VideoBlacklistService } from '@app/shared/video-blacklist' +import { ScreenService } from '@app/shared/misc/screen.service' + +export type VideoActionsDisplayType = { + playlist?: boolean + download?: boolean + update?: boolean + blacklist?: boolean + delete?: boolean + report?: boolean +} + +@Component({ + selector: 'my-video-actions-dropdown', + templateUrl: './video-actions-dropdown.component.html', + styleUrls: [ './video-actions-dropdown.component.scss' ] +}) +export class VideoActionsDropdownComponent implements OnChanges { + @ViewChild('playlistDropdown') playlistDropdown: NgbDropdown + @ViewChild('playlistAdd') playlistAdd: VideoAddToPlaylistComponent + + @ViewChild('videoDownloadModal') videoDownloadModal: VideoDownloadComponent + @ViewChild('videoReportModal') videoReportModal: VideoReportComponent + @ViewChild('videoBlacklistModal') videoBlacklistModal: VideoBlacklistComponent + + @Input() video: Video | VideoDetails + + @Input() displayOptions: VideoActionsDisplayType = { + playlist: false, + download: true, + update: true, + blacklist: true, + delete: true, + report: true + } + @Input() placement: string = 'left' + + @Input() label: string + + @Input() buttonStyled = false + @Input() buttonSize: DropdownButtonSize = 'normal' + @Input() buttonDirection: DropdownDirection = 'vertical' + + @Output() videoRemoved = new EventEmitter() + @Output() videoUnblacklisted = new EventEmitter() + @Output() videoBlacklisted = new EventEmitter() + + videoActions: DropdownAction<{ video: Video }>[][] = [] + + private loaded = false + + constructor ( + private authService: AuthService, + private notifier: Notifier, + private confirmService: ConfirmService, + private videoBlacklistService: VideoBlacklistService, + private serverService: ServerService, + private screenService: ScreenService, + private videoService: VideoService, + private blocklistService: BlocklistService, + private i18n: I18n + ) { } + + get user () { + return this.authService.getUser() + } + + ngOnChanges () { + this.buildActions() + } + + isUserLoggedIn () { + return this.authService.isLoggedIn() + } + + loadDropdownInformation () { + if (!this.isUserLoggedIn() || this.loaded === true) return + + this.loaded = true + + if (this.displayOptions.playlist) this.playlistAdd.load() + } + + /* Show modals */ + + showDownloadModal () { + this.videoDownloadModal.show(this.video as VideoDetails) + } + + showReportModal () { + this.videoReportModal.show() + } + + showBlacklistModal () { + this.videoBlacklistModal.show() + } + + /* Actions checker */ + + isVideoUpdatable () { + return this.video.isUpdatableBy(this.user) + } + + isVideoRemovable () { + return this.video.isRemovableBy(this.user) + } + + isVideoBlacklistable () { + return this.video.isBlackistableBy(this.user) + } + + isVideoUnblacklistable () { + return this.video.isUnblacklistableBy(this.user) + } + + /* Action handlers */ + + async unblacklistVideo () { + const confirmMessage = this.i18n( + 'Do you really want to remove this video from the blacklist? It will be available again in the videos list.' + ) + + const res = await this.confirmService.confirm(confirmMessage, this.i18n('Unblacklist')) + if (res === false) return + + this.videoBlacklistService.removeVideoFromBlacklist(this.video.id).subscribe( + () => { + this.notifier.success(this.i18n('Video {{name}} removed from the blacklist.', { name: this.video.name })) + + this.video.blacklisted = false + this.video.blacklistedReason = null + + this.videoUnblacklisted.emit() + }, + + err => this.notifier.error(err.message) + ) + } + + async removeVideo () { + const res = await this.confirmService.confirm(this.i18n('Do you really want to delete this video?'), this.i18n('Delete')) + if (res === false) return + + this.videoService.removeVideo(this.video.id) + .subscribe( + () => { + this.notifier.success(this.i18n('Video {{videoName}} deleted.', { videoName: this.video.name })) + + this.videoRemoved.emit() + }, + + error => this.notifier.error(error.message) + ) + } + + onVideoBlacklisted () { + this.videoBlacklisted.emit() + } + + getPlaylistDropdownPlacement () { + if (this.screenService.isInSmallView()) { + return 'bottom-right' + } + + return 'bottom-left bottom-right' + } + + private buildActions () { + this.videoActions = [] + + if (this.authService.isLoggedIn()) { + this.videoActions.push([ + { + label: this.i18n('Save to playlist'), + handler: () => this.playlistDropdown.toggle(), + isDisplayed: () => this.displayOptions.playlist, + iconName: 'playlist-add' + } + ]) + + this.videoActions.push([ + { + label: this.i18n('Download'), + handler: () => this.showDownloadModal(), + isDisplayed: () => this.displayOptions.download, + iconName: 'download' + }, + { + label: this.i18n('Update'), + linkBuilder: ({ video }) => [ '/videos/update', video.uuid ], + iconName: 'edit', + isDisplayed: () => this.displayOptions.update && this.isVideoUpdatable() + }, + { + label: this.i18n('Blacklist'), + handler: () => this.showBlacklistModal(), + iconName: 'no', + isDisplayed: () => this.displayOptions.blacklist && this.isVideoBlacklistable() + }, + { + label: this.i18n('Unblacklist'), + handler: () => this.unblacklistVideo(), + iconName: 'undo', + isDisplayed: () => this.displayOptions.blacklist && this.isVideoUnblacklistable() + }, + { + label: this.i18n('Delete'), + handler: () => this.removeVideo(), + isDisplayed: () => this.displayOptions.delete && this.isVideoRemovable(), + iconName: 'delete' + } + ]) + + this.videoActions.push([ + { + label: this.i18n('Report'), + handler: () => this.showReportModal(), + isDisplayed: () => this.displayOptions.report, + iconName: 'alert' + } + ]) + } + } +} diff --git a/client/src/app/shared/video/video-details.model.ts b/client/src/app/shared/video/video-details.model.ts index 388357343..8463e15d7 100644 --- a/client/src/app/shared/video/video-details.model.ts +++ b/client/src/app/shared/video/video-details.model.ts @@ -44,22 +44,6 @@ export class VideoDetails extends Video implements VideoDetailsServerModel { this.buildLikeAndDislikePercents() } - isRemovableBy (user: AuthUser) { - return user && this.isLocal === true && (this.account.name === user.username || user.hasRight(UserRight.REMOVE_ANY_VIDEO)) - } - - isBlackistableBy (user: AuthUser) { - return this.blacklisted !== true && user && user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) === true - } - - isUnblacklistableBy (user: AuthUser) { - return this.blacklisted === true && user && user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) === true - } - - isUpdatableBy (user: AuthUser) { - return user && this.isLocal === true && (this.account.name === user.username || user.hasRight(UserRight.UPDATE_ANY_VIDEO)) - } - buildLikeAndDislikePercents () { this.likesPercent = (this.likes / (this.likes + this.dislikes)) * 100 this.dislikesPercent = (this.dislikes / (this.likes + this.dislikes)) * 100 diff --git a/client/src/app/shared/video/video-miniature.component.html b/client/src/app/shared/video/video-miniature.component.html index f4ae0b0dd..7af0f1113 100644 --- a/client/src/app/shared/video/video-miniature.component.html +++ b/client/src/app/shared/video/video-miniature.component.html @@ -1,47 +1,56 @@ -
+
-
- - - Unlisted - Private - - - {{ video.name }} - - - - {{ video.publishedAt | myFromNow }} - - - {{ video.views | myNumberFormatter }} views - - - - - {{ video.byVideoChannel }} - - -
- {{ video.privacy.label }} - - - {{ getStateLabel(video) }} +
+
+ + + Unlisted + Private + + + {{ video.name }} + + + + {{ video.publishedAt | myFromNow }} + - + {{ video.views | myNumberFormatter }} views + + + + + {{ video.byVideoChannel }} + + +
+ {{ video.privacy.label }} + - + {{ getStateLabel(video) }} +
+ +
+ Blacklisted + {{ video.blacklistedReason }} +
+ +
+ Sensitive +
-
- Blacklisted - {{ video.blacklistedReason }} +
+ +
- -
- Sensitive -
-
diff --git a/client/src/app/shared/video/video-miniature.component.scss b/client/src/app/shared/video/video-miniature.component.scss index fdc3dc033..0d4e59c2a 100644 --- a/client/src/app/shared/video/video-miniature.component.scss +++ b/client/src/app/shared/video/video-miniature.component.scss @@ -56,6 +56,37 @@ } } + .video-bottom { + display: flex; + + .video-actions { + margin-top: 3px; + margin-right: 10px; + } + + /deep/ .dropdown-root:not(.show) { + display: none; + } + + &:hover /deep/ .dropdown-root { + display: block; + } + + /deep/ .playlist-dropdown.show + my-action-dropdown .dropdown-root { + display: block; + } + + @media screen and (max-width: $small-view) { + .video-actions { + margin-right: 0; + } + + /deep/ .dropdown-root { + display: block !important; + } + } + } + &.display-as-row { flex-direction: row; margin-bottom: 0; @@ -91,6 +122,11 @@ } } + .video-bottom .video-actions { + margin: 0; + top: -3px; + } + @media screen and (max-width: $small-view) { flex-direction: column; height: auto; diff --git a/client/src/app/shared/video/video-miniature.component.ts b/client/src/app/shared/video/video-miniature.component.ts index 800417a79..e3552abba 100644 --- a/client/src/app/shared/video/video-miniature.component.ts +++ b/client/src/app/shared/video/video-miniature.component.ts @@ -1,9 +1,11 @@ -import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core' +import { ChangeDetectionStrategy, Component, EventEmitter, Inject, Input, LOCALE_ID, OnInit, Output } from '@angular/core' import { User } from '../users' import { Video } from './video.model' import { ServerService } from '@app/core' import { VideoPrivacy, VideoState } from '../../../../../shared' import { I18n } from '@ngx-translate/i18n-polyfill' +import { VideoActionsDisplayType } from '@app/shared/video/video-actions-dropdown.component' +import { ScreenService } from '@app/shared/misc/screen.service' export type OwnerDisplayType = 'account' | 'videoChannel' | 'auto' export type MiniatureDisplayOptions = { @@ -38,10 +40,26 @@ export class VideoMiniatureComponent implements OnInit { blacklistInfo: false } @Input() displayAsRow = false + @Input() displayVideoActions = true + + @Output() videoBlacklisted = new EventEmitter() + @Output() videoUnblacklisted = new EventEmitter() + @Output() videoRemoved = new EventEmitter() + + videoActionsDisplayOptions: VideoActionsDisplayType = { + playlist: true, + download: false, + update: true, + blacklist: true, + delete: true, + report: true + } + showActions = false private ownerDisplayTypeChosen: 'account' | 'videoChannel' constructor ( + private screenService: ScreenService, private serverService: ServerService, private i18n: I18n, @Inject(LOCALE_ID) private localeId: string @@ -52,20 +70,10 @@ export class VideoMiniatureComponent implements OnInit { } ngOnInit () { - if (this.ownerDisplayType === 'account' || this.ownerDisplayType === 'videoChannel') { - this.ownerDisplayTypeChosen = this.ownerDisplayType - return - } + this.setUpBy() - // If the video channel name an UUID (not really displayable, we changed this behaviour in v1.0.0-beta.12) - // -> Use the account name - if ( - this.video.channel.name === `${this.video.account.name}_channel` || - this.video.channel.name.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/) - ) { - this.ownerDisplayTypeChosen = 'account' - } else { - this.ownerDisplayTypeChosen = 'videoChannel' + if (this.screenService.isInSmallView()) { + this.showActions = true } } @@ -109,4 +117,38 @@ export class VideoMiniatureComponent implements OnInit { return '' } + + loadActions () { + if (this.displayVideoActions) this.showActions = true + } + + onVideoBlacklisted () { + this.videoBlacklisted.emit() + } + + onVideoUnblacklisted () { + this.videoUnblacklisted.emit() + } + + onVideoRemoved () { + this.videoRemoved.emit() + } + + private setUpBy () { + if (this.ownerDisplayType === 'account' || this.ownerDisplayType === 'videoChannel') { + this.ownerDisplayTypeChosen = this.ownerDisplayType + return + } + + // If the video channel name an UUID (not really displayable, we changed this behaviour in v1.0.0-beta.12) + // -> Use the account name + if ( + this.video.channel.name === `${this.video.account.name}_channel` || + this.video.channel.name.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/) + ) { + this.ownerDisplayTypeChosen = 'account' + } else { + this.ownerDisplayTypeChosen = 'videoChannel' + } + } } diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/video/video.model.ts index 95b5e3671..0cef3eb8f 100644 --- a/client/src/app/shared/video/video.model.ts +++ b/client/src/app/shared/video/video.model.ts @@ -1,11 +1,12 @@ import { User } from '../' -import { PlaylistElement, Video as VideoServerModel, VideoPrivacy, VideoState } from '../../../../../shared' +import { PlaylistElement, UserRight, Video as VideoServerModel, VideoPrivacy, VideoState } from '../../../../../shared' import { Avatar } from '../../../../../shared/models/avatars/avatar.model' import { VideoConstant } from '../../../../../shared/models/videos/video-constant.model' import { durationToString, getAbsoluteAPIUrl } from '../misc/utils' import { peertubeTranslate, ServerConfig } from '../../../../../shared/models' import { Actor } from '@app/shared/actor/actor.model' import { VideoScheduleUpdate } from '../../../../../shared/models/videos/video-schedule-update.model' +import { AuthUser } from '@app/core' export class Video implements VideoServerModel { byVideoChannel: string @@ -141,4 +142,20 @@ export class Video implements VideoServerModel { // Return default instance config return serverConfig.instance.defaultNSFWPolicy !== 'display' } + + isRemovableBy (user: AuthUser) { + return user && this.isLocal === true && (this.account.name === user.username || user.hasRight(UserRight.REMOVE_ANY_VIDEO)) + } + + isBlackistableBy (user: AuthUser) { + return this.blacklisted !== true && user && user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) === true + } + + isUnblacklistableBy (user: AuthUser) { + return this.blacklisted === true && user && user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) === true + } + + isUpdatableBy (user: AuthUser) { + return user && this.isLocal === true && (this.account.name === user.username || user.hasRight(UserRight.UPDATE_ANY_VIDEO)) + } } diff --git a/client/src/app/shared/video/videos-selection.component.html b/client/src/app/shared/video/videos-selection.component.html index 6f3401b4b..53809b6fd 100644 --- a/client/src/app/shared/video/videos-selection.component.html +++ b/client/src/app/shared/video/videos-selection.component.html @@ -6,7 +6,7 @@
- +
-- cgit v1.2.3