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 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
	
	 Chocobozzz
						Chocobozzz