Add video miniature dropdown
This commit is contained in:
parent
693263e936
commit
3a0fb65c61
|
@ -15,6 +15,8 @@
|
|||
|
||||
<div myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" class="videos">
|
||||
<div class="video" *ngFor="let video of videos">
|
||||
<my-video-miniature [video]="video" [displayAsRow]="true"></my-video-miniature>
|
||||
<my-video-miniature
|
||||
[video]="video" [displayAsRow]="true"
|
||||
(videoRemoved)="removeVideoFromArray(video)" (videoBlacklisted)="removeVideoFromArray(video)"></my-video-miniature>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -48,7 +48,10 @@
|
|||
</div>
|
||||
|
||||
<div *ngIf="isVideo(result)" class="entry video">
|
||||
<my-video-miniature [video]="result" [user]="user" [displayAsRow]="true"></my-video-miniature>
|
||||
<my-video-miniature
|
||||
[video]="result" [user]="user" [displayAsRow]="true"
|
||||
(videoBlacklisted)="removeVideoFromArray(result)" (videoRemoved)="removeVideoFromArray(result)"
|
||||
></my-video-miniature>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Component, OnDestroy, OnInit } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { AuthService, Notifier, ServerService } from '@app/core'
|
||||
import { AuthService, Notifier } from '@app/core'
|
||||
import { forkJoin, Subscription } from 'rxjs'
|
||||
import { SearchService } from '@app/search/search.service'
|
||||
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
|
||||
|
@ -138,6 +138,10 @@ export class SearchComponent implements OnInit, OnDestroy {
|
|||
return this.advancedSearch.size()
|
||||
}
|
||||
|
||||
removeVideoFromArray (video: Video) {
|
||||
this.results = this.results.filter(r => !this.isVideo(r) || r.id !== video.id)
|
||||
}
|
||||
|
||||
private resetPagination () {
|
||||
this.pagination.currentPage = 1
|
||||
this.pagination.totalItems = null
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
<div class="dropdown-root" ngbDropdown [placement]="placement">
|
||||
<div
|
||||
class="action-button" [ngClass]="{ small: buttonSize === 'small', grey: theme === 'grey', orange: theme === 'orange' }"
|
||||
class="action-button" [ngClass]="{ small: buttonSize === 'small', grey: theme === 'grey', orange: theme === 'orange', 'button-styled': buttonStyled }"
|
||||
ngbDropdownToggle role="button"
|
||||
>
|
||||
<my-global-icon *ngIf="!label" class="more-icon" iconName="more-horizontal"></my-global-icon>
|
||||
<my-global-icon *ngIf="!label && buttonDirection === 'horizontal'" class="more-icon" iconName="more-horizontal"></my-global-icon>
|
||||
<my-global-icon *ngIf="!label && buttonDirection === 'vertical'" class="more-icon" iconName="more-vertical"></my-global-icon>
|
||||
|
||||
<span *ngIf="label" class="dropdown-toggle">{{ label }}</span>
|
||||
</div>
|
||||
|
||||
|
@ -12,15 +14,24 @@
|
|||
|
||||
<ng-container *ngFor="let action of actions">
|
||||
<ng-container *ngIf="action.isDisplayed === undefined || action.isDisplayed(entry) === true">
|
||||
<a *ngIf="action.linkBuilder" class="dropdown-item" [routerLink]="action.linkBuilder(entry)">{{ action.label }}</a>
|
||||
|
||||
<span *ngIf="!action.linkBuilder" class="custom-action dropdown-item" (click)="action.handler(entry)" role="button">
|
||||
<a *ngIf="action.linkBuilder" [ngClass]="{ 'with-icon': !!action.iconName }" class="dropdown-item" [routerLink]="action.linkBuilder(entry)">
|
||||
<my-global-icon *ngIf="action.iconName" [iconName]="action.iconName" [ngClass]="'icon-' + action.iconName"></my-global-icon>
|
||||
{{ action.label }}
|
||||
</a>
|
||||
|
||||
<span
|
||||
*ngIf="!action.linkBuilder" [ngClass]="{ 'with-icon': !!action.iconName }" (click)="action.handler(entry)"
|
||||
class="custom-action dropdown-item" role="button"
|
||||
>
|
||||
<my-global-icon *ngIf="action.iconName" [iconName]="action.iconName" [ngClass]="'icon-' + action.iconName"></my-global-icon>
|
||||
{{ action.label }}
|
||||
</span>
|
||||
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
<div *ngIf="areActionsDisplayed(actions, entry)" class="dropdown-divider"></div>
|
||||
|
||||
</ng-container>
|
||||
</div>
|
||||
|
|
|
@ -8,12 +8,19 @@
|
|||
.action-button {
|
||||
@include peertube-button;
|
||||
|
||||
&.grey {
|
||||
@include grey-button;
|
||||
}
|
||||
&.button-styled {
|
||||
|
||||
&.orange {
|
||||
@include orange-button;
|
||||
&.grey {
|
||||
@include grey-button;
|
||||
}
|
||||
|
||||
&.orange {
|
||||
@include orange-button;
|
||||
}
|
||||
|
||||
&:hover, &:active, &:focus {
|
||||
background-color: $grey-background-color;
|
||||
}
|
||||
}
|
||||
|
||||
display: inline-block;
|
||||
|
@ -23,10 +30,6 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
&:hover, &:active, &:focus {
|
||||
background-color: $grey-background-color;
|
||||
}
|
||||
|
||||
.more-icon {
|
||||
width: 21px;
|
||||
}
|
||||
|
@ -48,6 +51,10 @@
|
|||
cursor: pointer;
|
||||
color: #000 !important;
|
||||
|
||||
&.with-icon {
|
||||
@include dropdown-with-icon-item;
|
||||
}
|
||||
|
||||
a, span {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
import { Component, Input } from '@angular/core'
|
||||
import { GlobalIconName } from '@app/shared/images/global-icon.component'
|
||||
|
||||
export type DropdownAction<T> = {
|
||||
label?: string
|
||||
iconName?: GlobalIconName
|
||||
handler?: (a: T) => any
|
||||
linkBuilder?: (a: T) => (string | number)[]
|
||||
isDisplayed?: (a: T) => boolean
|
||||
}
|
||||
|
||||
export type DropdownButtonSize = 'normal' | 'small'
|
||||
export type DropdownTheme = 'orange' | 'grey'
|
||||
export type DropdownDirection = 'horizontal' | 'vertical'
|
||||
|
||||
@Component({
|
||||
selector: 'my-action-dropdown',
|
||||
styleUrls: [ './action-dropdown.component.scss' ],
|
||||
|
@ -16,14 +22,29 @@ export type DropdownAction<T> = {
|
|||
export class ActionDropdownComponent<T> {
|
||||
@Input() actions: DropdownAction<T>[] | DropdownAction<T>[][] = []
|
||||
@Input() entry: T
|
||||
|
||||
@Input() placement = 'bottom-left'
|
||||
@Input() buttonSize: 'normal' | 'small' = 'normal'
|
||||
|
||||
@Input() buttonSize: DropdownButtonSize = 'normal'
|
||||
@Input() buttonDirection: DropdownDirection = 'horizontal'
|
||||
@Input() buttonStyled = true
|
||||
|
||||
@Input() label: string
|
||||
@Input() theme: 'orange' | 'grey' = 'grey'
|
||||
@Input() theme: DropdownTheme = 'grey'
|
||||
|
||||
getActions () {
|
||||
if (this.actions.length !== 0 && Array.isArray(this.actions[0])) return this.actions
|
||||
|
||||
return [ this.actions ]
|
||||
}
|
||||
|
||||
areActionsDisplayed (actions: DropdownAction<T>[], entry: T) {
|
||||
return actions.some(a => a.isDisplayed === undefined || a.isDisplayed(entry))
|
||||
}
|
||||
|
||||
handleClick (event: Event, action: DropdownAction<T>) {
|
||||
event.preventDefault()
|
||||
|
||||
// action.handler(entry)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,8 @@ export class ScreenService {
|
|||
}
|
||||
|
||||
private cacheWindowInnerWidthExpired () {
|
||||
if (!this.lastFunctionCallTime) return true
|
||||
|
||||
return new Date().getTime() > (this.lastFunctionCallTime + this.cacheForMs)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,6 +80,11 @@ import { NumberFormatterPipe } from '@app/shared/angular/number-formatter.pipe'
|
|||
import { ObjectLengthPipe } from '@app/shared/angular/object-length.pipe'
|
||||
import { FromNowPipe } from '@app/shared/angular/from-now.pipe'
|
||||
import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive'
|
||||
import { VideoActionsDropdownComponent } from '@app/shared/video/video-actions-dropdown.component'
|
||||
import { VideoBlacklistComponent } from '@app/shared/video/modals/video-blacklist.component'
|
||||
import { VideoDownloadComponent } from '@app/shared/video/modals/video-download.component'
|
||||
import { VideoReportComponent } from '@app/shared/video/modals/video-report.component'
|
||||
import { ClipboardModule } from 'ngx-clipboard'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -95,6 +100,8 @@ import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template
|
|||
NgbTabsetModule,
|
||||
NgbTooltipModule,
|
||||
|
||||
ClipboardModule,
|
||||
|
||||
PrimeSharedModule,
|
||||
InputMaskModule,
|
||||
NgPipesModule
|
||||
|
@ -110,6 +117,11 @@ import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template
|
|||
VideoAddToPlaylistComponent,
|
||||
VideoPlaylistElementMiniatureComponent,
|
||||
VideosSelectionComponent,
|
||||
VideoActionsDropdownComponent,
|
||||
|
||||
VideoDownloadComponent,
|
||||
VideoReportComponent,
|
||||
VideoBlacklistComponent,
|
||||
|
||||
FeedComponent,
|
||||
|
||||
|
@ -158,6 +170,8 @@ import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template
|
|||
NgbTabsetModule,
|
||||
NgbTooltipModule,
|
||||
|
||||
ClipboardModule,
|
||||
|
||||
PrimeSharedModule,
|
||||
InputMaskModule,
|
||||
BytesPipe,
|
||||
|
@ -172,6 +186,11 @@ import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template
|
|||
VideoAddToPlaylistComponent,
|
||||
VideoPlaylistElementMiniatureComponent,
|
||||
VideosSelectionComponent,
|
||||
VideoActionsDropdownComponent,
|
||||
|
||||
VideoDownloadComponent,
|
||||
VideoReportComponent,
|
||||
VideoBlacklistComponent,
|
||||
|
||||
FeedComponent,
|
||||
|
||||
|
|
|
@ -1,74 +1,76 @@
|
|||
<div class="header">
|
||||
<div class="first-row">
|
||||
<div i18n class="title">Save to</div>
|
||||
<div class="root">
|
||||
<div class="header">
|
||||
<div class="first-row">
|
||||
<div i18n class="title">Save to</div>
|
||||
|
||||
<div class="options" (click)="displayOptions = !displayOptions">
|
||||
<my-global-icon iconName="cog"></my-global-icon>
|
||||
<div class="options" (click)="displayOptions = !displayOptions">
|
||||
<my-global-icon iconName="cog"></my-global-icon>
|
||||
|
||||
<span i18n>Options</span>
|
||||
<span i18n>Options</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="options-row" *ngIf="displayOptions">
|
||||
<div>
|
||||
<my-peertube-checkbox
|
||||
inputName="startAt" [(ngModel)]="timestampOptions.startTimestampEnabled"
|
||||
i18n-labelText labelText="Start at"
|
||||
></my-peertube-checkbox>
|
||||
|
||||
<my-timestamp-input
|
||||
[timestamp]="timestampOptions.startTimestamp"
|
||||
[maxTimestamp]="video.duration"
|
||||
[disabled]="!timestampOptions.startTimestampEnabled"
|
||||
[(ngModel)]="timestampOptions.startTimestamp"
|
||||
></my-timestamp-input>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<my-peertube-checkbox
|
||||
inputName="stopAt" [(ngModel)]="timestampOptions.stopTimestampEnabled"
|
||||
i18n-labelText labelText="Stop at"
|
||||
></my-peertube-checkbox>
|
||||
|
||||
<my-timestamp-input
|
||||
[timestamp]="timestampOptions.stopTimestamp"
|
||||
[maxTimestamp]="video.duration"
|
||||
[disabled]="!timestampOptions.stopTimestampEnabled"
|
||||
[(ngModel)]="timestampOptions.stopTimestamp"
|
||||
></my-timestamp-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="options-row" *ngIf="displayOptions">
|
||||
<div>
|
||||
<my-peertube-checkbox
|
||||
inputName="startAt" [(ngModel)]="timestampOptions.startTimestampEnabled"
|
||||
i18n-labelText labelText="Start at"
|
||||
></my-peertube-checkbox>
|
||||
<div class="playlist dropdown-item" *ngFor="let playlist of videoPlaylists" (click)="togglePlaylist($event, playlist)">
|
||||
<my-peertube-checkbox [inputName]="'in-playlist-' + playlist.id" [(ngModel)]="playlist.inPlaylist"></my-peertube-checkbox>
|
||||
|
||||
<my-timestamp-input
|
||||
[timestamp]="timestampOptions.startTimestamp"
|
||||
[maxTimestamp]="video.duration"
|
||||
[disabled]="!timestampOptions.startTimestampEnabled"
|
||||
[(ngModel)]="timestampOptions.startTimestamp"
|
||||
></my-timestamp-input>
|
||||
</div>
|
||||
<div class="display-name">
|
||||
{{ playlist.displayName }}
|
||||
|
||||
<div>
|
||||
<my-peertube-checkbox
|
||||
inputName="stopAt" [(ngModel)]="timestampOptions.stopTimestampEnabled"
|
||||
i18n-labelText labelText="Stop at"
|
||||
></my-peertube-checkbox>
|
||||
|
||||
<my-timestamp-input
|
||||
[timestamp]="timestampOptions.stopTimestamp"
|
||||
[maxTimestamp]="video.duration"
|
||||
[disabled]="!timestampOptions.stopTimestampEnabled"
|
||||
[(ngModel)]="timestampOptions.stopTimestamp"
|
||||
></my-timestamp-input>
|
||||
<div *ngIf="playlist.inPlaylist && (playlist.startTimestamp || playlist.stopTimestamp)" class="timestamp-info">
|
||||
{{ formatTimestamp(playlist) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="new-playlist-button dropdown-item" (click)="openCreateBlock($event)" [hidden]="isNewPlaylistBlockOpened">
|
||||
<my-global-icon iconName="add"></my-global-icon>
|
||||
|
||||
Create a new playlist
|
||||
</div>
|
||||
|
||||
<form class="new-playlist-block dropdown-item" *ngIf="isNewPlaylistBlockOpened" (ngSubmit)="createPlaylist()" [formGroup]="form">
|
||||
<div class="form-group">
|
||||
<label i18n for="displayName">Display name</label>
|
||||
<input
|
||||
type="text" id="displayName"
|
||||
formControlName="displayName" [ngClass]="{ 'input-error': formErrors['displayName'] }"
|
||||
>
|
||||
<div *ngIf="formErrors['displayName']" class="form-error">
|
||||
{{ formErrors['displayName'] }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="submit" i18n-value value="Create" [disabled]="!form.valid">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="playlist dropdown-item" *ngFor="let playlist of videoPlaylists" (click)="togglePlaylist($event, playlist)">
|
||||
<my-peertube-checkbox [inputName]="'in-playlist-' + playlist.id" [(ngModel)]="playlist.inPlaylist"></my-peertube-checkbox>
|
||||
|
||||
<div class="display-name">
|
||||
{{ playlist.displayName }}
|
||||
|
||||
<div *ngIf="playlist.inPlaylist && (playlist.startTimestamp || playlist.stopTimestamp)" class="timestamp-info">
|
||||
{{ formatTimestamp(playlist) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="new-playlist-button dropdown-item" (click)="openCreateBlock($event)" [hidden]="isNewPlaylistBlockOpened">
|
||||
<my-global-icon iconName="add"></my-global-icon>
|
||||
|
||||
Create a new playlist
|
||||
</div>
|
||||
|
||||
<form class="new-playlist-block dropdown-item" *ngIf="isNewPlaylistBlockOpened" (ngSubmit)="createPlaylist()" [formGroup]="form">
|
||||
<div class="form-group">
|
||||
<label i18n for="displayName">Display name</label>
|
||||
<input
|
||||
type="text" id="displayName"
|
||||
formControlName="displayName" [ngClass]="{ 'input-error': formErrors['displayName'] }"
|
||||
>
|
||||
<div *ngIf="formErrors['displayName']" class="form-error">
|
||||
{{ formErrors['displayName'] }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="submit" i18n-value value="Create" [disabled]="!form.valid">
|
||||
</form>
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
@import '_variables';
|
||||
@import '_mixins';
|
||||
|
||||
.root {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
min-width: 240px;
|
||||
padding: 6px 24px 10px 24px;
|
||||
|
|
|
@ -24,6 +24,7 @@ type PlaylistSummary = {
|
|||
export class VideoAddToPlaylistComponent extends FormReactive implements OnInit {
|
||||
@Input() video: Video
|
||||
@Input() currentVideoTimestamp: number
|
||||
@Input() lazyLoad = false
|
||||
|
||||
isNewPlaylistBlockOpened = false
|
||||
videoPlaylists: PlaylistSummary[] = []
|
||||
|
@ -57,6 +58,10 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit
|
|||
displayName: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_DISPLAY_NAME
|
||||
})
|
||||
|
||||
if (this.lazyLoad !== true) this.load()
|
||||
}
|
||||
|
||||
load () {
|
||||
forkJoin([
|
||||
this.videoPlaylistService.listAccountPlaylists(this.user.account, '-updatedAt'),
|
||||
this.videoPlaylistService.doesVideoExistInPlaylist(this.video.id)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div [ngClass]="{ 'margin-content': marginContent }">
|
||||
<div class="margin-content">
|
||||
<div class="videos-header">
|
||||
<div *ngIf="titlePage" class="title-page title-page-single">
|
||||
<div placement="bottom" [ngbTooltip]="titleTooltip" container="body">
|
||||
|
@ -11,7 +11,7 @@
|
|||
<div class="moderation-block" *ngIf="displayModerationBlock">
|
||||
<my-peertube-checkbox
|
||||
(change)="toggleModerationDisplay()"
|
||||
inputName="display-unlisted-private" i18n-labelText labelText="Display unlisted and private videos"
|
||||
inputName="display-unlisted-private" i18n-labelText labelText="Display unlisted and private videos"
|
||||
>
|
||||
</my-peertube-checkbox>
|
||||
</div>
|
||||
|
@ -22,7 +22,11 @@
|
|||
myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true"
|
||||
class="videos"
|
||||
>
|
||||
<my-video-miniature *ngFor="let video of videos; trackBy: videoById" [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType">
|
||||
<my-video-miniature
|
||||
*ngFor="let video of videos; trackBy: videoById" [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType"
|
||||
[displayVideoActions]="displayVideoActions"
|
||||
(videoBlacklisted)="removeVideoFromArray(video)" (videoRemoved)="removeVideoFromArray(video)"
|
||||
>
|
||||
</my-video-miniature>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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 */ }
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { Component, Input, OnInit, ViewChild } from '@angular/core'
|
||||
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
|
||||
import { Notifier, RedirectService } from '@app/core'
|
||||
import { FormReactive, VideoBlacklistService, VideoBlacklistValidatorsService } from '../../../shared/index'
|
||||
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',
|
||||
|
@ -17,6 +18,8 @@ export class VideoBlacklistComponent extends FormReactive implements OnInit {
|
|||
|
||||
@ViewChild('modal') modal: NgbModal
|
||||
|
||||
@Output() videoBlacklisted = new EventEmitter()
|
||||
|
||||
error: string = null
|
||||
|
||||
private openedModal: NgbModalRef
|
||||
|
@ -60,7 +63,11 @@ export class VideoBlacklistComponent extends FormReactive implements OnInit {
|
|||
() => {
|
||||
this.notifier.success(this.i18n('Video blacklisted.'))
|
||||
this.hide()
|
||||
this.redirectService.redirectToHomepage()
|
||||
|
||||
this.video.blacklisted = true
|
||||
this.video.blacklistedReason = reason
|
||||
|
||||
this.videoBlacklisted.emit()
|
||||
},
|
||||
|
||||
err => this.notifier.error(err.message)
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'
|
||||
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'
|
||||
|
@ -9,26 +9,32 @@ import { Notifier } from '@app/core'
|
|||
templateUrl: './video-download.component.html',
|
||||
styleUrls: [ './video-download.component.scss' ]
|
||||
})
|
||||
export class VideoDownloadComponent implements OnInit {
|
||||
@Input() video: VideoDetails = null
|
||||
|
||||
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
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
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
|
||||
}
|
||||
|
||||
show () {
|
||||
this.modalService.open(this.modal)
|
||||
onClose () {
|
||||
this.video = undefined
|
||||
}
|
||||
|
||||
download () {
|
||||
|
@ -45,21 +51,16 @@ export class VideoDownloadComponent implements OnInit {
|
|||
return
|
||||
}
|
||||
|
||||
const link = (() => {
|
||||
switch (this.downloadType) {
|
||||
case 'direct': {
|
||||
return file.fileDownloadUrl
|
||||
}
|
||||
case 'torrent': {
|
||||
return file.torrentDownloadUrl
|
||||
}
|
||||
case 'magnet': {
|
||||
return file.magnetUri
|
||||
}
|
||||
}
|
||||
})()
|
||||
switch (this.downloadType) {
|
||||
case 'direct':
|
||||
return file.fileDownloadUrl
|
||||
|
||||
return link
|
||||
case 'torrent':
|
||||
return file.torrentDownloadUrl
|
||||
|
||||
case 'magnet':
|
||||
return file.magnetUri
|
||||
}
|
||||
}
|
||||
|
||||
activateCopiedMessage () {
|
|
@ -1,12 +1,13 @@
|
|||
import { Component, Input, OnInit, ViewChild } from '@angular/core'
|
||||
import { Notifier } from '@app/core'
|
||||
import { FormReactive, VideoAbuseService } from '../../../shared/index'
|
||||
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',
|
|
@ -0,0 +1,21 @@
|
|||
<ng-container *ngIf="videoActions.length !== 0">
|
||||
|
||||
<div class="playlist-dropdown" ngbDropdown #playlistDropdown="ngbDropdown" role="button" autoClose="outside" [placement]="getPlaylistDropdownPlacement()"
|
||||
*ngIf="isUserLoggedIn() && displayOptions.playlist" (openChange)="playlistAdd.openChange($event)"
|
||||
>
|
||||
<span class="anchor" ngbDropdownAnchor></span>
|
||||
|
||||
<div ngbDropdownMenu>
|
||||
<my-video-add-to-playlist #playlistAdd [video]="video" [lazyLoad]="true"></my-video-add-to-playlist>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<my-action-dropdown
|
||||
[actions]="videoActions" [label]="label" [entry]="{ video: video }" (mouseenter)="loadDropdownInformation()"
|
||||
[buttonSize]="buttonSize" [placement]="placement" [buttonDirection]="buttonDirection" [buttonStyled]="buttonStyled"
|
||||
></my-action-dropdown>
|
||||
|
||||
<my-video-download #videoDownloadModal></my-video-download>
|
||||
<my-video-report #videoReportModal [video]="video"></my-video-report>
|
||||
<my-video-blacklist #videoBlacklistModal [video]="video" (videoBlacklisted)="onVideoBlacklisted()"></my-video-blacklist>
|
||||
</ng-container>
|
|
@ -0,0 +1,12 @@
|
|||
.playlist-dropdown {
|
||||
position: absolute;
|
||||
|
||||
.anchor {
|
||||
display: block;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .icon-playlist-add {
|
||||
left: 2px;
|
||||
}
|
|
@ -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'
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -1,47 +1,56 @@
|
|||
<div class="video-miniature" [ngClass]="{ 'display-as-row': displayAsRow }">
|
||||
<div class="video-miniature" [ngClass]="{ 'display-as-row': displayAsRow }" (mouseenter)="loadActions()">
|
||||
<my-video-thumbnail [video]="video" [nsfw]="isVideoBlur"></my-video-thumbnail>
|
||||
|
||||
<div class="video-miniature-information">
|
||||
<a
|
||||
tabindex="-1"
|
||||
class="video-miniature-name"
|
||||
[routerLink]="[ '/videos/watch', video.uuid ]" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoBlur }"
|
||||
>
|
||||
<ng-container *ngIf="displayOptions.privacyLabel">
|
||||
<span *ngIf="isUnlistedVideo()" class="badge badge-warning" i18n>Unlisted</span>
|
||||
<span *ngIf="isPrivateVideo()" class="badge badge-danger" i18n>Private</span>
|
||||
</ng-container>
|
||||
<div class="video-bottom">
|
||||
<div class="video-miniature-information">
|
||||
<a
|
||||
tabindex="-1"
|
||||
class="video-miniature-name"
|
||||
[routerLink]="[ '/videos/watch', video.uuid ]" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoBlur }"
|
||||
>
|
||||
<ng-container *ngIf="displayOptions.privacyLabel">
|
||||
<span *ngIf="isUnlistedVideo()" class="badge badge-warning" i18n>Unlisted</span>
|
||||
<span *ngIf="isPrivateVideo()" class="badge badge-danger" i18n>Private</span>
|
||||
</ng-container>
|
||||
|
||||
{{ video.name }}
|
||||
</a>
|
||||
{{ video.name }}
|
||||
</a>
|
||||
|
||||
<span class="video-miniature-created-at-views">
|
||||
<ng-container *ngIf="displayOptions.date">{{ video.publishedAt | myFromNow }}</ng-container>
|
||||
<ng-container *ngIf="displayOptions.date && displayOptions.views"> - </ng-container>
|
||||
<ng-container i18n *ngIf="displayOptions.views">{{ video.views | myNumberFormatter }} views</ng-container>
|
||||
</span>
|
||||
<span class="video-miniature-created-at-views">
|
||||
<ng-container *ngIf="displayOptions.date">{{ video.publishedAt | myFromNow }}</ng-container>
|
||||
<ng-container *ngIf="displayOptions.date && displayOptions.views"> - </ng-container>
|
||||
<ng-container i18n *ngIf="displayOptions.views">{{ video.views | myNumberFormatter }} views</ng-container>
|
||||
</span>
|
||||
|
||||
<a tabindex="-1" *ngIf="displayOptions.by && displayOwnerAccount()" class="video-miniature-account" [routerLink]="[ '/accounts', video.byAccount ]">
|
||||
{{ video.byAccount }}
|
||||
</a>
|
||||
<a tabindex="-1" *ngIf="displayOptions.by && displayOwnerVideoChannel()" class="video-miniature-channel" [routerLink]="[ '/video-channels', video.byVideoChannel ]">
|
||||
{{ video.byVideoChannel }}
|
||||
</a>
|
||||
<a tabindex="-1" *ngIf="displayOptions.by && displayOwnerAccount()" class="video-miniature-account" [routerLink]="[ '/accounts', video.byAccount ]">
|
||||
{{ video.byAccount }}
|
||||
</a>
|
||||
<a tabindex="-1" *ngIf="displayOptions.by && displayOwnerVideoChannel()" class="video-miniature-channel" [routerLink]="[ '/video-channels', video.byVideoChannel ]">
|
||||
{{ video.byVideoChannel }}
|
||||
</a>
|
||||
|
||||
<div class="video-info-privacy">
|
||||
<ng-container *ngIf="displayOptions.privacyText">{{ video.privacy.label }}</ng-container>
|
||||
<ng-container *ngIf="displayOptions.privacyText && displayOptions.state"> - </ng-container>
|
||||
<ng-container *ngIf="displayOptions.state">{{ getStateLabel(video) }}</ng-container>
|
||||
<div class="video-info-privacy">
|
||||
<ng-container *ngIf="displayOptions.privacyText">{{ video.privacy.label }}</ng-container>
|
||||
<ng-container *ngIf="displayOptions.privacyText && displayOptions.state"> - </ng-container>
|
||||
<ng-container *ngIf="displayOptions.state">{{ getStateLabel(video) }}</ng-container>
|
||||
</div>
|
||||
|
||||
<div *ngIf="displayOptions.blacklistInfo && video.blacklisted" class="video-info-blacklisted">
|
||||
<span class="blacklisted-label" i18n>Blacklisted</span>
|
||||
<span class="blacklisted-reason" *ngIf="video.blacklistedReason">{{ video.blacklistedReason }}</span>
|
||||
</div>
|
||||
|
||||
<div i18n *ngIf="displayOptions.nsfw && video.nsfw" class="video-info-nsfw">
|
||||
Sensitive
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="displayOptions.blacklistInfo && video.blacklisted" class="video-info-blacklisted">
|
||||
<span class="blacklisted-label" i18n>Blacklisted</span>
|
||||
<span class="blacklisted-reason" *ngIf="video.blacklistedReason">{{ video.blacklistedReason }}</span>
|
||||
<div class="video-actions">
|
||||
<!-- FIXME: remove bottom placement when overflow is fixed in bootstrap dropdown -->
|
||||
<my-video-actions-dropdown
|
||||
*ngIf="showActions" [video]="video" [displayOptions]="videoActionsDisplayOptions" placement="bottom-left bottom-right left"
|
||||
(videoRemoved)="onVideoRemoved()" (videoBlacklisted)="onVideoBlacklisted()" (videoUnblacklisted)="onVideoUnblacklisted()"
|
||||
></my-video-actions-dropdown>
|
||||
</div>
|
||||
|
||||
<div i18n *ngIf="displayOptions.nsfw && video.nsfw" class="video-info-nsfw">
|
||||
Sensitive
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="_selection[video.id]"></my-peertube-checkbox>
|
||||
</div>
|
||||
|
||||
<my-video-miniature [video]="video" [displayAsRow]="true" [displayOptions]="miniatureDisplayOptions"></my-video-miniature>
|
||||
<my-video-miniature [video]="video" [displayAsRow]="true" [displayOptions]="miniatureDisplayOptions" [displayVideoActions]="false"></my-video-miniature>
|
||||
|
||||
<!-- Display only once -->
|
||||
<div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0">
|
||||
|
|
|
@ -120,37 +120,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-dropdown" ngbDropdown placement="top" role="button">
|
||||
<div class="action-button" ngbDropdownToggle role="button">
|
||||
<my-global-icon class="more-icon" iconName="more-horizontal"></my-global-icon>
|
||||
</div>
|
||||
|
||||
<div ngbDropdownMenu>
|
||||
<a *ngIf="isVideoDownloadable()" class="dropdown-item" i18n-title title="Download the video" href="#" (click)="showDownloadModal($event)">
|
||||
<my-global-icon iconName="download"></my-global-icon> <ng-container i18n>Download</ng-container>
|
||||
</a>
|
||||
|
||||
<a *ngIf="isUserLoggedIn()" class="dropdown-item" i18n-title title="Report this video" href="#" (click)="showReportModal($event)">
|
||||
<my-global-icon iconName="alert"></my-global-icon> <ng-container i18n>Report</ng-container>
|
||||
</a>
|
||||
|
||||
<a *ngIf="isVideoUpdatable()" class="dropdown-item" i18n-title title="Update this video" href="#" [routerLink]="[ '/videos/update', video.uuid ]">
|
||||
<my-global-icon iconName="edit"></my-global-icon> <ng-container i18n>Update</ng-container>
|
||||
</a>
|
||||
|
||||
<a *ngIf="isVideoBlacklistable()" class="dropdown-item" i18n-title title="Blacklist this video" href="#" (click)="showBlacklistModal($event)">
|
||||
<my-global-icon iconName="no"></my-global-icon> <ng-container i18n>Blacklist</ng-container>
|
||||
</a>
|
||||
|
||||
<a *ngIf="isVideoUnblacklistable()" class="dropdown-item" i18n-title title="Unblacklist this video" href="#" (click)="unblacklistVideo($event)">
|
||||
<my-global-icon iconName="undo"></my-global-icon> <ng-container i18n>Unblacklist</ng-container>
|
||||
</a>
|
||||
|
||||
<a *ngIf="isVideoRemovable()" class="dropdown-item" i18n-title title="Delete this video" href="#" (click)="removeVideo($event)">
|
||||
<my-global-icon iconName="delete"></my-global-icon> <ng-container i18n>Delete</ng-container>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<my-video-actions-dropdown
|
||||
placement="top" buttonDirection="horizontal" [buttonStyled]="true" [video]="video" (videoRemoved)="onVideoRemoved()"
|
||||
></my-video-actions-dropdown>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
@ -270,7 +242,4 @@
|
|||
<ng-template [ngIf]="video !== null">
|
||||
<my-video-support #videoSupportModal [video]="video"></my-video-support>
|
||||
<my-video-share #videoShareModal [video]="video"></my-video-share>
|
||||
<my-video-download #videoDownloadModal [video]="video"></my-video-download>
|
||||
<my-video-report #videoReportModal [video]="video"></my-video-report>
|
||||
<my-video-blacklist #videoBlacklistModal [video]="video"></my-video-blacklist>
|
||||
</ng-template>
|
||||
|
|
|
@ -257,7 +257,9 @@ $player-factor: 1.7; // 16/9
|
|||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.action-button:not(:first-child), .action-dropdown {
|
||||
.action-button:not(:first-child),
|
||||
.action-dropdown,
|
||||
my-video-actions-dropdown {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
|
@ -304,14 +306,6 @@ $player-factor: 1.7; // 16/9
|
|||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-dropdown {
|
||||
display: inline-block;
|
||||
|
||||
.dropdown-menu .dropdown-item {
|
||||
@include dropdown-with-icon-item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.video-info-likes-dislikes-bar {
|
||||
|
|
|
@ -13,10 +13,7 @@ import { AuthService, ConfirmService } from '../../core'
|
|||
import { RestExtractor, VideoBlacklistService } from '../../shared'
|
||||
import { VideoDetails } from '../../shared/video/video-details.model'
|
||||
import { VideoService } from '../../shared/video/video.service'
|
||||
import { VideoDownloadComponent } from './modal/video-download.component'
|
||||
import { VideoReportComponent } from './modal/video-report.component'
|
||||
import { VideoShareComponent } from './modal/video-share.component'
|
||||
import { VideoBlacklistComponent } from './modal/video-blacklist.component'
|
||||
import { SubscribeButtonComponent } from '@app/shared/user-subscription/subscribe-button.component'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
import { environment } from '../../../environments/environment'
|
||||
|
@ -32,6 +29,7 @@ import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
|
|||
import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
|
||||
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
|
||||
import { Video } from '@app/shared/video/video.model'
|
||||
import { VideoActionsDisplayType } from '@app/shared/video/video-actions-dropdown.component'
|
||||
|
||||
@Component({
|
||||
selector: 'my-video-watch',
|
||||
|
@ -41,11 +39,8 @@ import { Video } from '@app/shared/video/video.model'
|
|||
export class VideoWatchComponent implements OnInit, OnDestroy {
|
||||
private static LOCAL_STORAGE_PRIVACY_CONCERN_KEY = 'video-watch-privacy-concern'
|
||||
|
||||
@ViewChild('videoDownloadModal') videoDownloadModal: VideoDownloadComponent
|
||||
@ViewChild('videoShareModal') videoShareModal: VideoShareComponent
|
||||
@ViewChild('videoReportModal') videoReportModal: VideoReportComponent
|
||||
@ViewChild('videoSupportModal') videoSupportModal: VideoSupportComponent
|
||||
@ViewChild('videoBlacklistModal') videoBlacklistModal: VideoBlacklistComponent
|
||||
@ViewChild('subscribeButton') subscribeButton: SubscribeButtonComponent
|
||||
|
||||
player: any
|
||||
|
@ -212,11 +207,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
)
|
||||
}
|
||||
|
||||
showReportModal (event: Event) {
|
||||
event.preventDefault()
|
||||
this.videoReportModal.show()
|
||||
}
|
||||
|
||||
showSupportModal () {
|
||||
this.videoSupportModal.show()
|
||||
}
|
||||
|
@ -225,54 +215,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
this.videoShareModal.show(this.currentTime)
|
||||
}
|
||||
|
||||
showDownloadModal (event: Event) {
|
||||
event.preventDefault()
|
||||
this.videoDownloadModal.show()
|
||||
}
|
||||
|
||||
showBlacklistModal (event: Event) {
|
||||
event.preventDefault()
|
||||
this.videoBlacklistModal.show()
|
||||
}
|
||||
|
||||
async unblacklistVideo (event: Event) {
|
||||
event.preventDefault()
|
||||
|
||||
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
|
||||
},
|
||||
|
||||
err => this.notifier.error(err.message)
|
||||
)
|
||||
}
|
||||
|
||||
isUserLoggedIn () {
|
||||
return this.authService.isLoggedIn()
|
||||
}
|
||||
|
||||
isVideoUpdatable () {
|
||||
return this.video.isUpdatableBy(this.authService.getUser())
|
||||
}
|
||||
|
||||
isVideoBlacklistable () {
|
||||
return this.video.isBlackistableBy(this.user)
|
||||
}
|
||||
|
||||
isVideoUnblacklistable () {
|
||||
return this.video.isUnblacklistableBy(this.user)
|
||||
}
|
||||
|
||||
getVideoTags () {
|
||||
if (!this.video || Array.isArray(this.video.tags) === false) return []
|
||||
|
||||
|
@ -283,23 +229,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
return this.video.isRemovableBy(this.authService.getUser())
|
||||
}
|
||||
|
||||
async removeVideo (event: Event) {
|
||||
event.preventDefault()
|
||||
|
||||
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 }))
|
||||
|
||||
// Go back to the video-list.
|
||||
this.redirectService.redirectToHomepage()
|
||||
},
|
||||
|
||||
error => this.notifier.error(error.message)
|
||||
)
|
||||
onVideoRemoved () {
|
||||
this.redirectService.redirectToHomepage()
|
||||
}
|
||||
|
||||
acceptedPrivacyConcern () {
|
||||
|
|
|
@ -1,26 +1,21 @@
|
|||
import { NgModule } from '@angular/core'
|
||||
import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
|
||||
import { ClipboardModule } from 'ngx-clipboard'
|
||||
import { SharedModule } from '../../shared'
|
||||
import { VideoCommentAddComponent } from './comment/video-comment-add.component'
|
||||
import { VideoCommentComponent } from './comment/video-comment.component'
|
||||
import { VideoCommentService } from './comment/video-comment.service'
|
||||
import { VideoCommentsComponent } from './comment/video-comments.component'
|
||||
import { VideoDownloadComponent } from './modal/video-download.component'
|
||||
import { VideoReportComponent } from './modal/video-report.component'
|
||||
import { VideoShareComponent } from './modal/video-share.component'
|
||||
import { VideoWatchRoutingModule } from './video-watch-routing.module'
|
||||
import { VideoWatchComponent } from './video-watch.component'
|
||||
import { NgxQRCodeModule } from 'ngx-qrcode2'
|
||||
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { VideoBlacklistComponent } from '@app/videos/+video-watch/modal/video-blacklist.component'
|
||||
import { RecommendationsModule } from '@app/videos/recommendations/recommendations.module'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
VideoWatchRoutingModule,
|
||||
SharedModule,
|
||||
ClipboardModule,
|
||||
NgbTooltipModule,
|
||||
NgxQRCodeModule,
|
||||
RecommendationsModule
|
||||
|
@ -29,10 +24,7 @@ import { RecommendationsModule } from '@app/videos/recommendations/recommendatio
|
|||
declarations: [
|
||||
VideoWatchComponent,
|
||||
|
||||
VideoDownloadComponent,
|
||||
VideoShareComponent,
|
||||
VideoReportComponent,
|
||||
VideoBlacklistComponent,
|
||||
VideoSupportComponent,
|
||||
VideoCommentsComponent,
|
||||
VideoCommentAddComponent,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<a routerLink="/search" [queryParams]="{ categoryOneOf: [ object.category.id ] }">{{ object.category.label }}</a>
|
||||
</div>
|
||||
|
||||
<my-video-miniature *ngFor="let video of object.videos" [video]="video" [user]="user"></my-video-miniature>
|
||||
<my-video-miniature *ngFor="let video of object.videos" [video]="video" [user]="user" [displayVideoActions]="false"></my-video-miniature>
|
||||
</div>
|
||||
|
||||
<div class="section" *ngFor="let object of overview.tags">
|
||||
|
@ -15,7 +15,7 @@
|
|||
<a routerLink="/search" [queryParams]="{ tagsOneOf: [ object.tag ] }">#{{ object.tag }}</a>
|
||||
</div>
|
||||
|
||||
<my-video-miniature *ngFor="let video of object.videos" [video]="video" [user]="user"></my-video-miniature>
|
||||
<my-video-miniature *ngFor="let video of object.videos" [video]="video" [user]="user" [displayVideoActions]="false"></my-video-miniature>
|
||||
</div>
|
||||
|
||||
<div class="section channel" *ngFor="let object of overview.channels">
|
||||
|
@ -27,7 +27,7 @@
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<my-video-miniature *ngFor="let video of object.videos" [video]="video" [user]="user"></my-video-miniature>
|
||||
<my-video-miniature *ngFor="let video of object.videos" [video]="video" [user]="user" [displayVideoActions]="false"></my-video-miniature>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue