mirror of https://github.com/Chocobozzz/PeerTube
Add video miniature dropdown
parent
693263e936
commit
3a0fb65c61
|
@ -15,6 +15,8 @@
|
||||||
|
|
||||||
<div myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" class="videos">
|
<div myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" class="videos">
|
||||||
<div class="video" *ngFor="let video of 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>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -48,7 +48,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="isVideo(result)" class="entry video">
|
<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>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Component, OnDestroy, OnInit } from '@angular/core'
|
import { Component, OnDestroy, OnInit } from '@angular/core'
|
||||||
import { ActivatedRoute, Router } from '@angular/router'
|
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 { forkJoin, Subscription } from 'rxjs'
|
||||||
import { SearchService } from '@app/search/search.service'
|
import { SearchService } from '@app/search/search.service'
|
||||||
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
|
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
|
||||||
|
@ -138,6 +138,10 @@ export class SearchComponent implements OnInit, OnDestroy {
|
||||||
return this.advancedSearch.size()
|
return this.advancedSearch.size()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeVideoFromArray (video: Video) {
|
||||||
|
this.results = this.results.filter(r => !this.isVideo(r) || r.id !== video.id)
|
||||||
|
}
|
||||||
|
|
||||||
private resetPagination () {
|
private resetPagination () {
|
||||||
this.pagination.currentPage = 1
|
this.pagination.currentPage = 1
|
||||||
this.pagination.totalItems = null
|
this.pagination.totalItems = null
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
<div class="dropdown-root" ngbDropdown [placement]="placement">
|
<div class="dropdown-root" ngbDropdown [placement]="placement">
|
||||||
<div
|
<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"
|
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>
|
<span *ngIf="label" class="dropdown-toggle">{{ label }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -12,15 +14,24 @@
|
||||||
|
|
||||||
<ng-container *ngFor="let action of actions">
|
<ng-container *ngFor="let action of actions">
|
||||||
<ng-container *ngIf="action.isDisplayed === undefined || action.isDisplayed(entry) === true">
|
<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 }}
|
{{ action.label }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<div class="dropdown-divider"></div>
|
<div *ngIf="areActionsDisplayed(actions, entry)" class="dropdown-divider"></div>
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,12 +8,19 @@
|
||||||
.action-button {
|
.action-button {
|
||||||
@include peertube-button;
|
@include peertube-button;
|
||||||
|
|
||||||
&.grey {
|
&.button-styled {
|
||||||
@include grey-button;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.orange {
|
&.grey {
|
||||||
@include orange-button;
|
@include grey-button;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.orange {
|
||||||
|
@include orange-button;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover, &:active, &:focus {
|
||||||
|
background-color: $grey-background-color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -23,10 +30,6 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover, &:active, &:focus {
|
|
||||||
background-color: $grey-background-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.more-icon {
|
.more-icon {
|
||||||
width: 21px;
|
width: 21px;
|
||||||
}
|
}
|
||||||
|
@ -48,6 +51,10 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #000 !important;
|
color: #000 !important;
|
||||||
|
|
||||||
|
&.with-icon {
|
||||||
|
@include dropdown-with-icon-item;
|
||||||
|
}
|
||||||
|
|
||||||
a, span {
|
a, span {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
import { Component, Input } from '@angular/core'
|
import { Component, Input } from '@angular/core'
|
||||||
|
import { GlobalIconName } from '@app/shared/images/global-icon.component'
|
||||||
|
|
||||||
export type DropdownAction<T> = {
|
export type DropdownAction<T> = {
|
||||||
label?: string
|
label?: string
|
||||||
|
iconName?: GlobalIconName
|
||||||
handler?: (a: T) => any
|
handler?: (a: T) => any
|
||||||
linkBuilder?: (a: T) => (string | number)[]
|
linkBuilder?: (a: T) => (string | number)[]
|
||||||
isDisplayed?: (a: T) => boolean
|
isDisplayed?: (a: T) => boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DropdownButtonSize = 'normal' | 'small'
|
||||||
|
export type DropdownTheme = 'orange' | 'grey'
|
||||||
|
export type DropdownDirection = 'horizontal' | 'vertical'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-action-dropdown',
|
selector: 'my-action-dropdown',
|
||||||
styleUrls: [ './action-dropdown.component.scss' ],
|
styleUrls: [ './action-dropdown.component.scss' ],
|
||||||
|
@ -16,14 +22,29 @@ export type DropdownAction<T> = {
|
||||||
export class ActionDropdownComponent<T> {
|
export class ActionDropdownComponent<T> {
|
||||||
@Input() actions: DropdownAction<T>[] | DropdownAction<T>[][] = []
|
@Input() actions: DropdownAction<T>[] | DropdownAction<T>[][] = []
|
||||||
@Input() entry: T
|
@Input() entry: T
|
||||||
|
|
||||||
@Input() placement = 'bottom-left'
|
@Input() placement = 'bottom-left'
|
||||||
@Input() buttonSize: 'normal' | 'small' = 'normal'
|
|
||||||
|
@Input() buttonSize: DropdownButtonSize = 'normal'
|
||||||
|
@Input() buttonDirection: DropdownDirection = 'horizontal'
|
||||||
|
@Input() buttonStyled = true
|
||||||
|
|
||||||
@Input() label: string
|
@Input() label: string
|
||||||
@Input() theme: 'orange' | 'grey' = 'grey'
|
@Input() theme: DropdownTheme = 'grey'
|
||||||
|
|
||||||
getActions () {
|
getActions () {
|
||||||
if (this.actions.length !== 0 && Array.isArray(this.actions[0])) return this.actions
|
if (this.actions.length !== 0 && Array.isArray(this.actions[0])) return this.actions
|
||||||
|
|
||||||
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 () {
|
private cacheWindowInnerWidthExpired () {
|
||||||
|
if (!this.lastFunctionCallTime) return true
|
||||||
|
|
||||||
return new Date().getTime() > (this.lastFunctionCallTime + this.cacheForMs)
|
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 { ObjectLengthPipe } from '@app/shared/angular/object-length.pipe'
|
||||||
import { FromNowPipe } from '@app/shared/angular/from-now.pipe'
|
import { FromNowPipe } from '@app/shared/angular/from-now.pipe'
|
||||||
import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive'
|
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({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -95,6 +100,8 @@ import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template
|
||||||
NgbTabsetModule,
|
NgbTabsetModule,
|
||||||
NgbTooltipModule,
|
NgbTooltipModule,
|
||||||
|
|
||||||
|
ClipboardModule,
|
||||||
|
|
||||||
PrimeSharedModule,
|
PrimeSharedModule,
|
||||||
InputMaskModule,
|
InputMaskModule,
|
||||||
NgPipesModule
|
NgPipesModule
|
||||||
|
@ -110,6 +117,11 @@ import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template
|
||||||
VideoAddToPlaylistComponent,
|
VideoAddToPlaylistComponent,
|
||||||
VideoPlaylistElementMiniatureComponent,
|
VideoPlaylistElementMiniatureComponent,
|
||||||
VideosSelectionComponent,
|
VideosSelectionComponent,
|
||||||
|
VideoActionsDropdownComponent,
|
||||||
|
|
||||||
|
VideoDownloadComponent,
|
||||||
|
VideoReportComponent,
|
||||||
|
VideoBlacklistComponent,
|
||||||
|
|
||||||
FeedComponent,
|
FeedComponent,
|
||||||
|
|
||||||
|
@ -158,6 +170,8 @@ import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template
|
||||||
NgbTabsetModule,
|
NgbTabsetModule,
|
||||||
NgbTooltipModule,
|
NgbTooltipModule,
|
||||||
|
|
||||||
|
ClipboardModule,
|
||||||
|
|
||||||
PrimeSharedModule,
|
PrimeSharedModule,
|
||||||
InputMaskModule,
|
InputMaskModule,
|
||||||
BytesPipe,
|
BytesPipe,
|
||||||
|
@ -172,6 +186,11 @@ import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template
|
||||||
VideoAddToPlaylistComponent,
|
VideoAddToPlaylistComponent,
|
||||||
VideoPlaylistElementMiniatureComponent,
|
VideoPlaylistElementMiniatureComponent,
|
||||||
VideosSelectionComponent,
|
VideosSelectionComponent,
|
||||||
|
VideoActionsDropdownComponent,
|
||||||
|
|
||||||
|
VideoDownloadComponent,
|
||||||
|
VideoReportComponent,
|
||||||
|
VideoBlacklistComponent,
|
||||||
|
|
||||||
FeedComponent,
|
FeedComponent,
|
||||||
|
|
||||||
|
|
|
@ -1,74 +1,76 @@
|
||||||
<div class="header">
|
<div class="root">
|
||||||
<div class="first-row">
|
<div class="header">
|
||||||
<div i18n class="title">Save to</div>
|
<div class="first-row">
|
||||||
|
<div i18n class="title">Save to</div>
|
||||||
|
|
||||||
<div class="options" (click)="displayOptions = !displayOptions">
|
<div class="options" (click)="displayOptions = !displayOptions">
|
||||||
<my-global-icon iconName="cog"></my-global-icon>
|
<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>
|
</div>
|
||||||
|
|
||||||
<div class="options-row" *ngIf="displayOptions">
|
<div class="playlist dropdown-item" *ngFor="let playlist of videoPlaylists" (click)="togglePlaylist($event, playlist)">
|
||||||
<div>
|
<my-peertube-checkbox [inputName]="'in-playlist-' + playlist.id" [(ngModel)]="playlist.inPlaylist"></my-peertube-checkbox>
|
||||||
<my-peertube-checkbox
|
|
||||||
inputName="startAt" [(ngModel)]="timestampOptions.startTimestampEnabled"
|
|
||||||
i18n-labelText labelText="Start at"
|
|
||||||
></my-peertube-checkbox>
|
|
||||||
|
|
||||||
<my-timestamp-input
|
<div class="display-name">
|
||||||
[timestamp]="timestampOptions.startTimestamp"
|
{{ playlist.displayName }}
|
||||||
[maxTimestamp]="video.duration"
|
|
||||||
[disabled]="!timestampOptions.startTimestampEnabled"
|
|
||||||
[(ngModel)]="timestampOptions.startTimestamp"
|
|
||||||
></my-timestamp-input>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div *ngIf="playlist.inPlaylist && (playlist.startTimestamp || playlist.stopTimestamp)" class="timestamp-info">
|
||||||
<my-peertube-checkbox
|
{{ formatTimestamp(playlist) }}
|
||||||
inputName="stopAt" [(ngModel)]="timestampOptions.stopTimestampEnabled"
|
</div>
|
||||||
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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
<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 '_variables';
|
||||||
@import '_mixins';
|
@import '_mixins';
|
||||||
|
|
||||||
|
.root {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
min-width: 240px;
|
min-width: 240px;
|
||||||
padding: 6px 24px 10px 24px;
|
padding: 6px 24px 10px 24px;
|
||||||
|
|
|
@ -24,6 +24,7 @@ type PlaylistSummary = {
|
||||||
export class VideoAddToPlaylistComponent extends FormReactive implements OnInit {
|
export class VideoAddToPlaylistComponent extends FormReactive implements OnInit {
|
||||||
@Input() video: Video
|
@Input() video: Video
|
||||||
@Input() currentVideoTimestamp: number
|
@Input() currentVideoTimestamp: number
|
||||||
|
@Input() lazyLoad = false
|
||||||
|
|
||||||
isNewPlaylistBlockOpened = false
|
isNewPlaylistBlockOpened = false
|
||||||
videoPlaylists: PlaylistSummary[] = []
|
videoPlaylists: PlaylistSummary[] = []
|
||||||
|
@ -57,6 +58,10 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit
|
||||||
displayName: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_DISPLAY_NAME
|
displayName: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_DISPLAY_NAME
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (this.lazyLoad !== true) this.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
load () {
|
||||||
forkJoin([
|
forkJoin([
|
||||||
this.videoPlaylistService.listAccountPlaylists(this.user.account, '-updatedAt'),
|
this.videoPlaylistService.listAccountPlaylists(this.user.account, '-updatedAt'),
|
||||||
this.videoPlaylistService.doesVideoExistInPlaylist(this.video.id)
|
this.videoPlaylistService.doesVideoExistInPlaylist(this.video.id)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div [ngClass]="{ 'margin-content': marginContent }">
|
<div class="margin-content">
|
||||||
<div class="videos-header">
|
<div class="videos-header">
|
||||||
<div *ngIf="titlePage" class="title-page title-page-single">
|
<div *ngIf="titlePage" class="title-page title-page-single">
|
||||||
<div placement="bottom" [ngbTooltip]="titleTooltip" container="body">
|
<div placement="bottom" [ngbTooltip]="titleTooltip" container="body">
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
<div class="moderation-block" *ngIf="displayModerationBlock">
|
<div class="moderation-block" *ngIf="displayModerationBlock">
|
||||||
<my-peertube-checkbox
|
<my-peertube-checkbox
|
||||||
(change)="toggleModerationDisplay()"
|
(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>
|
</my-peertube-checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,7 +22,11 @@
|
||||||
myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true"
|
myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true"
|
||||||
class="videos"
|
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>
|
</my-video-miniature>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -26,11 +26,11 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
|
||||||
syndicationItems: Syndication[] = []
|
syndicationItems: Syndication[] = []
|
||||||
|
|
||||||
loadOnInit = true
|
loadOnInit = true
|
||||||
marginContent = true
|
|
||||||
videos: Video[] = []
|
videos: Video[] = []
|
||||||
ownerDisplayType: OwnerDisplayType = 'account'
|
ownerDisplayType: OwnerDisplayType = 'account'
|
||||||
displayModerationBlock = false
|
displayModerationBlock = false
|
||||||
titleTooltip: string
|
titleTooltip: string
|
||||||
|
displayVideoActions = true
|
||||||
|
|
||||||
disabled = false
|
disabled = false
|
||||||
|
|
||||||
|
@ -120,6 +120,10 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
|
||||||
throw new Error('toggleModerationDisplay is not implemented')
|
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
|
// On videos hook for children that want to do something
|
||||||
protected onMoreVideos () { /* empty */ }
|
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 { 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 { VideoDetails } from '../../../shared/video/video-details.model'
|
||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
|
import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
|
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
|
||||||
|
import { FormReactive, VideoBlacklistValidatorsService } from '@app/shared/forms'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-video-blacklist',
|
selector: 'my-video-blacklist',
|
||||||
|
@ -17,6 +18,8 @@ export class VideoBlacklistComponent extends FormReactive implements OnInit {
|
||||||
|
|
||||||
@ViewChild('modal') modal: NgbModal
|
@ViewChild('modal') modal: NgbModal
|
||||||
|
|
||||||
|
@Output() videoBlacklisted = new EventEmitter()
|
||||||
|
|
||||||
error: string = null
|
error: string = null
|
||||||
|
|
||||||
private openedModal: NgbModalRef
|
private openedModal: NgbModalRef
|
||||||
|
@ -60,7 +63,11 @@ export class VideoBlacklistComponent extends FormReactive implements OnInit {
|
||||||
() => {
|
() => {
|
||||||
this.notifier.success(this.i18n('Video blacklisted.'))
|
this.notifier.success(this.i18n('Video blacklisted.'))
|
||||||
this.hide()
|
this.hide()
|
||||||
this.redirectService.redirectToHomepage()
|
|
||||||
|
this.video.blacklisted = true
|
||||||
|
this.video.blacklistedReason = reason
|
||||||
|
|
||||||
|
this.videoBlacklisted.emit()
|
||||||
},
|
},
|
||||||
|
|
||||||
err => this.notifier.error(err.message)
|
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 { VideoDetails } from '../../../shared/video/video-details.model'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
|
@ -9,26 +9,32 @@ import { Notifier } from '@app/core'
|
||||||
templateUrl: './video-download.component.html',
|
templateUrl: './video-download.component.html',
|
||||||
styleUrls: [ './video-download.component.scss' ]
|
styleUrls: [ './video-download.component.scss' ]
|
||||||
})
|
})
|
||||||
export class VideoDownloadComponent implements OnInit {
|
export class VideoDownloadComponent {
|
||||||
@Input() video: VideoDetails = null
|
|
||||||
|
|
||||||
@ViewChild('modal') modal: ElementRef
|
@ViewChild('modal') modal: ElementRef
|
||||||
|
|
||||||
downloadType: 'direct' | 'torrent' | 'magnet' = 'torrent'
|
downloadType: 'direct' | 'torrent' | 'magnet' = 'torrent'
|
||||||
resolutionId: number | string = -1
|
resolutionId: number | string = -1
|
||||||
|
|
||||||
|
private video: VideoDetails
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private notifier: Notifier,
|
private notifier: Notifier,
|
||||||
private modalService: NgbModal,
|
private modalService: NgbModal,
|
||||||
private i18n: I18n
|
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
|
this.resolutionId = this.video.files[0].resolution.id
|
||||||
}
|
}
|
||||||
|
|
||||||
show () {
|
onClose () {
|
||||||
this.modalService.open(this.modal)
|
this.video = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
download () {
|
download () {
|
||||||
|
@ -45,21 +51,16 @@ export class VideoDownloadComponent implements OnInit {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const link = (() => {
|
switch (this.downloadType) {
|
||||||
switch (this.downloadType) {
|
case 'direct':
|
||||||
case 'direct': {
|
return file.fileDownloadUrl
|
||||||
return file.fileDownloadUrl
|
|
||||||
}
|
|
||||||
case 'torrent': {
|
|
||||||
return file.torrentDownloadUrl
|
|
||||||
}
|
|
||||||
case 'magnet': {
|
|
||||||
return file.magnetUri
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
|
|
||||||
return link
|
case 'torrent':
|
||||||
|
return file.torrentDownloadUrl
|
||||||
|
|
||||||
|
case 'magnet':
|
||||||
|
return file.magnetUri
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
activateCopiedMessage () {
|
activateCopiedMessage () {
|
|
@ -1,12 +1,13 @@
|
||||||
import { Component, Input, OnInit, ViewChild } from '@angular/core'
|
import { Component, Input, OnInit, ViewChild } from '@angular/core'
|
||||||
import { Notifier } from '@app/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 { VideoDetails } from '../../../shared/video/video-details.model'
|
||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
|
import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
|
||||||
import { VideoAbuseValidatorsService } from '@app/shared/forms/form-validators/video-abuse-validators.service'
|
import { VideoAbuseValidatorsService } from '@app/shared/forms/form-validators/video-abuse-validators.service'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
|
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
|
||||||
|
import { VideoAbuseService } from '@app/shared/video-abuse'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-video-report',
|
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()
|
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 () {
|
buildLikeAndDislikePercents () {
|
||||||
this.likesPercent = (this.likes / (this.likes + this.dislikes)) * 100
|
this.likesPercent = (this.likes / (this.likes + this.dislikes)) * 100
|
||||||
this.dislikesPercent = (this.dislikes / (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>
|
<my-video-thumbnail [video]="video" [nsfw]="isVideoBlur"></my-video-thumbnail>
|
||||||
|
|
||||||
<div class="video-miniature-information">
|
<div class="video-bottom">
|
||||||
<a
|
<div class="video-miniature-information">
|
||||||
tabindex="-1"
|
<a
|
||||||
class="video-miniature-name"
|
tabindex="-1"
|
||||||
[routerLink]="[ '/videos/watch', video.uuid ]" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoBlur }"
|
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>
|
<ng-container *ngIf="displayOptions.privacyLabel">
|
||||||
<span *ngIf="isPrivateVideo()" class="badge badge-danger" i18n>Private</span>
|
<span *ngIf="isUnlistedVideo()" class="badge badge-warning" i18n>Unlisted</span>
|
||||||
</ng-container>
|
<span *ngIf="isPrivateVideo()" class="badge badge-danger" i18n>Private</span>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
{{ video.name }}
|
{{ video.name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<span class="video-miniature-created-at-views">
|
<span class="video-miniature-created-at-views">
|
||||||
<ng-container *ngIf="displayOptions.date">{{ video.publishedAt | myFromNow }}</ng-container>
|
<ng-container *ngIf="displayOptions.date">{{ video.publishedAt | myFromNow }}</ng-container>
|
||||||
<ng-container *ngIf="displayOptions.date && displayOptions.views"> - </ng-container>
|
<ng-container *ngIf="displayOptions.date && displayOptions.views"> - </ng-container>
|
||||||
<ng-container i18n *ngIf="displayOptions.views">{{ video.views | myNumberFormatter }} views</ng-container>
|
<ng-container i18n *ngIf="displayOptions.views">{{ video.views | myNumberFormatter }} views</ng-container>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<a tabindex="-1" *ngIf="displayOptions.by && displayOwnerAccount()" class="video-miniature-account" [routerLink]="[ '/accounts', video.byAccount ]">
|
<a tabindex="-1" *ngIf="displayOptions.by && displayOwnerAccount()" class="video-miniature-account" [routerLink]="[ '/accounts', video.byAccount ]">
|
||||||
{{ video.byAccount }}
|
{{ video.byAccount }}
|
||||||
</a>
|
</a>
|
||||||
<a tabindex="-1" *ngIf="displayOptions.by && displayOwnerVideoChannel()" class="video-miniature-channel" [routerLink]="[ '/video-channels', video.byVideoChannel ]">
|
<a tabindex="-1" *ngIf="displayOptions.by && displayOwnerVideoChannel()" class="video-miniature-channel" [routerLink]="[ '/video-channels', video.byVideoChannel ]">
|
||||||
{{ video.byVideoChannel }}
|
{{ video.byVideoChannel }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="video-info-privacy">
|
<div class="video-info-privacy">
|
||||||
<ng-container *ngIf="displayOptions.privacyText">{{ video.privacy.label }}</ng-container>
|
<ng-container *ngIf="displayOptions.privacyText">{{ video.privacy.label }}</ng-container>
|
||||||
<ng-container *ngIf="displayOptions.privacyText && displayOptions.state"> - </ng-container>
|
<ng-container *ngIf="displayOptions.privacyText && displayOptions.state"> - </ng-container>
|
||||||
<ng-container *ngIf="displayOptions.state">{{ getStateLabel(video) }}</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>
|
||||||
|
|
||||||
<div *ngIf="displayOptions.blacklistInfo && video.blacklisted" class="video-info-blacklisted">
|
<div class="video-actions">
|
||||||
<span class="blacklisted-label" i18n>Blacklisted</span>
|
<!-- FIXME: remove bottom placement when overflow is fixed in bootstrap dropdown -->
|
||||||
<span class="blacklisted-reason" *ngIf="video.blacklistedReason">{{ video.blacklistedReason }}</span>
|
<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>
|
||||||
|
|
||||||
<div i18n *ngIf="displayOptions.nsfw && video.nsfw" class="video-info-nsfw">
|
|
||||||
Sensitive
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</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 {
|
&.display-as-row {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
@ -91,6 +122,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.video-bottom .video-actions {
|
||||||
|
margin: 0;
|
||||||
|
top: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $small-view) {
|
@media screen and (max-width: $small-view) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: auto;
|
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 { User } from '../users'
|
||||||
import { Video } from './video.model'
|
import { Video } from './video.model'
|
||||||
import { ServerService } from '@app/core'
|
import { ServerService } from '@app/core'
|
||||||
import { VideoPrivacy, VideoState } from '../../../../../shared'
|
import { VideoPrivacy, VideoState } from '../../../../../shared'
|
||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
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 OwnerDisplayType = 'account' | 'videoChannel' | 'auto'
|
||||||
export type MiniatureDisplayOptions = {
|
export type MiniatureDisplayOptions = {
|
||||||
|
@ -38,10 +40,26 @@ export class VideoMiniatureComponent implements OnInit {
|
||||||
blacklistInfo: false
|
blacklistInfo: false
|
||||||
}
|
}
|
||||||
@Input() displayAsRow = 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'
|
private ownerDisplayTypeChosen: 'account' | 'videoChannel'
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
|
private screenService: ScreenService,
|
||||||
private serverService: ServerService,
|
private serverService: ServerService,
|
||||||
private i18n: I18n,
|
private i18n: I18n,
|
||||||
@Inject(LOCALE_ID) private localeId: string
|
@Inject(LOCALE_ID) private localeId: string
|
||||||
|
@ -52,20 +70,10 @@ export class VideoMiniatureComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
if (this.ownerDisplayType === 'account' || this.ownerDisplayType === 'videoChannel') {
|
this.setUpBy()
|
||||||
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)
|
if (this.screenService.isInSmallView()) {
|
||||||
// -> Use the account name
|
this.showActions = true
|
||||||
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'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,4 +117,38 @@ export class VideoMiniatureComponent implements OnInit {
|
||||||
|
|
||||||
return ''
|
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 { 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 { Avatar } from '../../../../../shared/models/avatars/avatar.model'
|
||||||
import { VideoConstant } from '../../../../../shared/models/videos/video-constant.model'
|
import { VideoConstant } from '../../../../../shared/models/videos/video-constant.model'
|
||||||
import { durationToString, getAbsoluteAPIUrl } from '../misc/utils'
|
import { durationToString, getAbsoluteAPIUrl } from '../misc/utils'
|
||||||
import { peertubeTranslate, ServerConfig } from '../../../../../shared/models'
|
import { peertubeTranslate, ServerConfig } from '../../../../../shared/models'
|
||||||
import { Actor } from '@app/shared/actor/actor.model'
|
import { Actor } from '@app/shared/actor/actor.model'
|
||||||
import { VideoScheduleUpdate } from '../../../../../shared/models/videos/video-schedule-update.model'
|
import { VideoScheduleUpdate } from '../../../../../shared/models/videos/video-schedule-update.model'
|
||||||
|
import { AuthUser } from '@app/core'
|
||||||
|
|
||||||
export class Video implements VideoServerModel {
|
export class Video implements VideoServerModel {
|
||||||
byVideoChannel: string
|
byVideoChannel: string
|
||||||
|
@ -141,4 +142,20 @@ export class Video implements VideoServerModel {
|
||||||
// Return default instance config
|
// Return default instance config
|
||||||
return serverConfig.instance.defaultNSFWPolicy !== 'display'
|
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>
|
<my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="_selection[video.id]"></my-peertube-checkbox>
|
||||||
</div>
|
</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 -->
|
<!-- Display only once -->
|
||||||
<div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0">
|
<div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0">
|
||||||
|
|
|
@ -120,37 +120,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="action-dropdown" ngbDropdown placement="top" role="button">
|
<my-video-actions-dropdown
|
||||||
<div class="action-button" ngbDropdownToggle role="button">
|
placement="top" buttonDirection="horizontal" [buttonStyled]="true" [video]="video" (videoRemoved)="onVideoRemoved()"
|
||||||
<my-global-icon class="more-icon" iconName="more-horizontal"></my-global-icon>
|
></my-video-actions-dropdown>
|
||||||
</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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -270,7 +242,4 @@
|
||||||
<ng-template [ngIf]="video !== null">
|
<ng-template [ngIf]="video !== null">
|
||||||
<my-video-support #videoSupportModal [video]="video"></my-video-support>
|
<my-video-support #videoSupportModal [video]="video"></my-video-support>
|
||||||
<my-video-share #videoShareModal [video]="video"></my-video-share>
|
<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>
|
</ng-template>
|
||||||
|
|
|
@ -257,7 +257,9 @@ $player-factor: 1.7; // 16/9
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.action-button:not(:first-child), .action-dropdown {
|
.action-button:not(:first-child),
|
||||||
|
.action-dropdown,
|
||||||
|
my-video-actions-dropdown {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,14 +306,6 @@ $player-factor: 1.7; // 16/9
|
||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-dropdown {
|
|
||||||
display: inline-block;
|
|
||||||
|
|
||||||
.dropdown-menu .dropdown-item {
|
|
||||||
@include dropdown-with-icon-item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-info-likes-dislikes-bar {
|
.video-info-likes-dislikes-bar {
|
||||||
|
|
|
@ -13,10 +13,7 @@ import { AuthService, ConfirmService } from '../../core'
|
||||||
import { RestExtractor, VideoBlacklistService } from '../../shared'
|
import { RestExtractor, VideoBlacklistService } from '../../shared'
|
||||||
import { VideoDetails } from '../../shared/video/video-details.model'
|
import { VideoDetails } from '../../shared/video/video-details.model'
|
||||||
import { VideoService } from '../../shared/video/video.service'
|
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 { VideoShareComponent } from './modal/video-share.component'
|
||||||
import { VideoBlacklistComponent } from './modal/video-blacklist.component'
|
|
||||||
import { SubscribeButtonComponent } from '@app/shared/user-subscription/subscribe-button.component'
|
import { SubscribeButtonComponent } from '@app/shared/user-subscription/subscribe-button.component'
|
||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
import { environment } from '../../../environments/environment'
|
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 { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
|
||||||
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
|
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
|
||||||
import { Video } from '@app/shared/video/video.model'
|
import { Video } from '@app/shared/video/video.model'
|
||||||
|
import { VideoActionsDisplayType } from '@app/shared/video/video-actions-dropdown.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-video-watch',
|
selector: 'my-video-watch',
|
||||||
|
@ -41,11 +39,8 @@ import { Video } from '@app/shared/video/video.model'
|
||||||
export class VideoWatchComponent implements OnInit, OnDestroy {
|
export class VideoWatchComponent implements OnInit, OnDestroy {
|
||||||
private static LOCAL_STORAGE_PRIVACY_CONCERN_KEY = 'video-watch-privacy-concern'
|
private static LOCAL_STORAGE_PRIVACY_CONCERN_KEY = 'video-watch-privacy-concern'
|
||||||
|
|
||||||
@ViewChild('videoDownloadModal') videoDownloadModal: VideoDownloadComponent
|
|
||||||
@ViewChild('videoShareModal') videoShareModal: VideoShareComponent
|
@ViewChild('videoShareModal') videoShareModal: VideoShareComponent
|
||||||
@ViewChild('videoReportModal') videoReportModal: VideoReportComponent
|
|
||||||
@ViewChild('videoSupportModal') videoSupportModal: VideoSupportComponent
|
@ViewChild('videoSupportModal') videoSupportModal: VideoSupportComponent
|
||||||
@ViewChild('videoBlacklistModal') videoBlacklistModal: VideoBlacklistComponent
|
|
||||||
@ViewChild('subscribeButton') subscribeButton: SubscribeButtonComponent
|
@ViewChild('subscribeButton') subscribeButton: SubscribeButtonComponent
|
||||||
|
|
||||||
player: any
|
player: any
|
||||||
|
@ -212,11 +207,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
showReportModal (event: Event) {
|
|
||||||
event.preventDefault()
|
|
||||||
this.videoReportModal.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
showSupportModal () {
|
showSupportModal () {
|
||||||
this.videoSupportModal.show()
|
this.videoSupportModal.show()
|
||||||
}
|
}
|
||||||
|
@ -225,54 +215,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
||||||
this.videoShareModal.show(this.currentTime)
|
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 () {
|
isUserLoggedIn () {
|
||||||
return this.authService.isLoggedIn()
|
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 () {
|
getVideoTags () {
|
||||||
if (!this.video || Array.isArray(this.video.tags) === false) return []
|
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())
|
return this.video.isRemovableBy(this.authService.getUser())
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeVideo (event: Event) {
|
onVideoRemoved () {
|
||||||
event.preventDefault()
|
this.redirectService.redirectToHomepage()
|
||||||
|
|
||||||
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)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
acceptedPrivacyConcern () {
|
acceptedPrivacyConcern () {
|
||||||
|
|
|
@ -1,26 +1,21 @@
|
||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
|
import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
|
||||||
import { ClipboardModule } from 'ngx-clipboard'
|
|
||||||
import { SharedModule } from '../../shared'
|
import { SharedModule } from '../../shared'
|
||||||
import { VideoCommentAddComponent } from './comment/video-comment-add.component'
|
import { VideoCommentAddComponent } from './comment/video-comment-add.component'
|
||||||
import { VideoCommentComponent } from './comment/video-comment.component'
|
import { VideoCommentComponent } from './comment/video-comment.component'
|
||||||
import { VideoCommentService } from './comment/video-comment.service'
|
import { VideoCommentService } from './comment/video-comment.service'
|
||||||
import { VideoCommentsComponent } from './comment/video-comments.component'
|
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 { VideoShareComponent } from './modal/video-share.component'
|
||||||
import { VideoWatchRoutingModule } from './video-watch-routing.module'
|
import { VideoWatchRoutingModule } from './video-watch-routing.module'
|
||||||
import { VideoWatchComponent } from './video-watch.component'
|
import { VideoWatchComponent } from './video-watch.component'
|
||||||
import { NgxQRCodeModule } from 'ngx-qrcode2'
|
import { NgxQRCodeModule } from 'ngx-qrcode2'
|
||||||
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
|
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'
|
import { RecommendationsModule } from '@app/videos/recommendations/recommendations.module'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
VideoWatchRoutingModule,
|
VideoWatchRoutingModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
ClipboardModule,
|
|
||||||
NgbTooltipModule,
|
NgbTooltipModule,
|
||||||
NgxQRCodeModule,
|
NgxQRCodeModule,
|
||||||
RecommendationsModule
|
RecommendationsModule
|
||||||
|
@ -29,10 +24,7 @@ import { RecommendationsModule } from '@app/videos/recommendations/recommendatio
|
||||||
declarations: [
|
declarations: [
|
||||||
VideoWatchComponent,
|
VideoWatchComponent,
|
||||||
|
|
||||||
VideoDownloadComponent,
|
|
||||||
VideoShareComponent,
|
VideoShareComponent,
|
||||||
VideoReportComponent,
|
|
||||||
VideoBlacklistComponent,
|
|
||||||
VideoSupportComponent,
|
VideoSupportComponent,
|
||||||
VideoCommentsComponent,
|
VideoCommentsComponent,
|
||||||
VideoCommentAddComponent,
|
VideoCommentAddComponent,
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<a routerLink="/search" [queryParams]="{ categoryOneOf: [ object.category.id ] }">{{ object.category.label }}</a>
|
<a routerLink="/search" [queryParams]="{ categoryOneOf: [ object.category.id ] }">{{ object.category.label }}</a>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div class="section" *ngFor="let object of overview.tags">
|
<div class="section" *ngFor="let object of overview.tags">
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
<a routerLink="/search" [queryParams]="{ tagsOneOf: [ object.tag ] }">#{{ object.tag }}</a>
|
<a routerLink="/search" [queryParams]="{ tagsOneOf: [ object.tag ] }">#{{ object.tag }}</a>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div class="section channel" *ngFor="let object of overview.channels">
|
<div class="section channel" *ngFor="let object of overview.channels">
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue