mirror of https://github.com/Chocobozzz/PeerTube
Add videos list admin component
parent
00004f7f6b
commit
33f6dce136
|
@ -44,6 +44,14 @@ export class AdminComponent implements OnInit {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.hasVideosRight()) {
|
||||||
|
overviewItems.children.push({
|
||||||
|
label: $localize`Videos`,
|
||||||
|
routerLink: '/admin/videos',
|
||||||
|
iconName: 'videos'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (overviewItems.children.length !== 0) {
|
if (overviewItems.children.length !== 0) {
|
||||||
this.menuEntries.push(overviewItems)
|
this.menuEntries.push(overviewItems)
|
||||||
}
|
}
|
||||||
|
@ -217,4 +225,8 @@ export class AdminComponent implements OnInit {
|
||||||
private hasVideoCommentsRight () {
|
private hasVideoCommentsRight () {
|
||||||
return this.auth.getUser().hasRight(UserRight.SEE_ALL_COMMENTS)
|
return this.auth.getUser().hasRight(UserRight.SEE_ALL_COMMENTS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private hasVideosRight () {
|
||||||
|
return this.auth.getUser().hasRight(UserRight.SEE_ALL_VIDEOS)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,9 @@ import { SharedFormModule } from '@app/shared/shared-forms'
|
||||||
import { SharedGlobalIconModule } from '@app/shared/shared-icons'
|
import { SharedGlobalIconModule } from '@app/shared/shared-icons'
|
||||||
import { SharedMainModule } from '@app/shared/shared-main'
|
import { SharedMainModule } from '@app/shared/shared-main'
|
||||||
import { SharedModerationModule } from '@app/shared/shared-moderation'
|
import { SharedModerationModule } from '@app/shared/shared-moderation'
|
||||||
|
import { SharedTablesModule } from '@app/shared/shared-tables'
|
||||||
import { SharedVideoCommentModule } from '@app/shared/shared-video-comment'
|
import { SharedVideoCommentModule } from '@app/shared/shared-video-comment'
|
||||||
|
import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature'
|
||||||
import { AdminRoutingModule } from './admin-routing.module'
|
import { AdminRoutingModule } from './admin-routing.module'
|
||||||
import { AdminComponent } from './admin.component'
|
import { AdminComponent } from './admin.component'
|
||||||
import {
|
import {
|
||||||
|
@ -33,7 +35,7 @@ import { AbuseListComponent, VideoBlockListComponent } from './moderation'
|
||||||
import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from './moderation/instance-blocklist'
|
import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from './moderation/instance-blocklist'
|
||||||
import { ModerationComponent } from './moderation/moderation.component'
|
import { ModerationComponent } from './moderation/moderation.component'
|
||||||
import { VideoCommentListComponent } from './moderation/video-comment-list'
|
import { VideoCommentListComponent } from './moderation/video-comment-list'
|
||||||
import { UserCreateComponent, UserListComponent, UserPasswordComponent, UserUpdateComponent } from './overview'
|
import { UserCreateComponent, UserListComponent, UserPasswordComponent, UserUpdateComponent, VideoListComponent } from './overview'
|
||||||
import { PluginListInstalledComponent } from './plugins/plugin-list-installed/plugin-list-installed.component'
|
import { PluginListInstalledComponent } from './plugins/plugin-list-installed/plugin-list-installed.component'
|
||||||
import { PluginSearchComponent } from './plugins/plugin-search/plugin-search.component'
|
import { PluginSearchComponent } from './plugins/plugin-search/plugin-search.component'
|
||||||
import { PluginShowInstalledComponent } from './plugins/plugin-show-installed/plugin-show-installed.component'
|
import { PluginShowInstalledComponent } from './plugins/plugin-show-installed/plugin-show-installed.component'
|
||||||
|
@ -56,6 +58,8 @@ import { JobsComponent } from './system/jobs/jobs.component'
|
||||||
SharedActorImageModule,
|
SharedActorImageModule,
|
||||||
SharedActorImageEditModule,
|
SharedActorImageEditModule,
|
||||||
SharedCustomMarkupModule,
|
SharedCustomMarkupModule,
|
||||||
|
SharedVideoMiniatureModule,
|
||||||
|
SharedTablesModule,
|
||||||
|
|
||||||
TableModule,
|
TableModule,
|
||||||
SelectButtonModule,
|
SelectButtonModule,
|
||||||
|
@ -65,6 +69,8 @@ import { JobsComponent } from './system/jobs/jobs.component'
|
||||||
declarations: [
|
declarations: [
|
||||||
AdminComponent,
|
AdminComponent,
|
||||||
|
|
||||||
|
VideoListComponent,
|
||||||
|
|
||||||
FollowsComponent,
|
FollowsComponent,
|
||||||
FollowersListComponent,
|
FollowersListComponent,
|
||||||
FollowingListComponent,
|
FollowingListComponent,
|
||||||
|
|
|
@ -34,9 +34,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td *ngIf="!videoBlock.reason"></td>
|
<td *ngIf="!videoBlock.reason"></td>
|
||||||
<td *ngIf="videoBlock.reason" class="expand-cell c-hand" [pRowToggler]="videoBlock" i18n-ngbTooltip ngbTooltip="More information" placement="top-left" container="body">
|
<td *ngIf="videoBlock.reason" class="expand-cell c-hand" [pRowToggler]="videoBlock" i18n-ngbTooltip ngbTooltip="More information" placement="top-left" container="body">
|
||||||
<span class="expander">
|
<my-table-expander-icon [expanded]="expanded"></my-table-expander-icon>
|
||||||
<i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i>
|
|
||||||
</span>
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="action-cell">
|
<td class="action-cell">
|
||||||
|
@ -47,22 +45,11 @@
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a [href]="getVideoUrl(videoBlock)" class="table-video-link" [title]="videoBlock.video.name" target="_blank" rel="noopener noreferrer">
|
<my-video-cell [video]="videoBlock.video">
|
||||||
<div class="table-video">
|
<span name>
|
||||||
<div class="table-video-image">
|
<my-global-icon *ngIf="videoBlock.type === 2" i18n-title title="The video was blocked due to automatic blocking of new videos" iconName="robot"></my-global-icon>
|
||||||
<img [src]="videoBlock.video.thumbnailPath">
|
</span>
|
||||||
</div>
|
</my-video-cell>
|
||||||
|
|
||||||
<div class="table-video-text">
|
|
||||||
<div>
|
|
||||||
<my-global-icon i18n-title title="The video was blocked due to automatic blocking of new videos" *ngIf="videoBlock.type === 2" iconName="robot"></my-global-icon>
|
|
||||||
{{ videoBlock.video.name }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-muted">by {{ videoBlock.video.channel?.displayName }} on {{ videoBlock.video.channel?.host }} </div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
|
@ -90,9 +77,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<div class="screenratio">
|
<my-embed [video]="videoBlock.video"></my-embed>
|
||||||
<div [innerHTML]="videoBlock.embedHtml"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,11 +3,10 @@ import { switchMap } from 'rxjs/operators'
|
||||||
import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils'
|
import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils'
|
||||||
import { environment } from 'src/environments/environment'
|
import { environment } from 'src/environments/environment'
|
||||||
import { Component, OnInit } from '@angular/core'
|
import { Component, OnInit } from '@angular/core'
|
||||||
import { DomSanitizer } from '@angular/platform-browser'
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router'
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, ServerService } from '@app/core'
|
import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, ServerService } from '@app/core'
|
||||||
import { AdvancedInputFilter } from '@app/shared/shared-forms'
|
import { AdvancedInputFilter } from '@app/shared/shared-forms'
|
||||||
import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
|
import { DropdownAction, VideoService } from '@app/shared/shared-main'
|
||||||
import { VideoBlockService } from '@app/shared/shared-moderation'
|
import { VideoBlockService } from '@app/shared/shared-moderation'
|
||||||
import { buildVideoEmbedLink, decorateVideoLink } from '@shared/core-utils'
|
import { buildVideoEmbedLink, decorateVideoLink } from '@shared/core-utils'
|
||||||
import { VideoBlacklist, VideoBlacklistType } from '@shared/models'
|
import { VideoBlacklist, VideoBlacklistType } from '@shared/models'
|
||||||
|
@ -18,7 +17,7 @@ import { VideoBlacklist, VideoBlacklistType } from '@shared/models'
|
||||||
styleUrls: [ '../../../shared/shared-moderation/moderation.scss', './video-block-list.component.scss' ]
|
styleUrls: [ '../../../shared/shared-moderation/moderation.scss', './video-block-list.component.scss' ]
|
||||||
})
|
})
|
||||||
export class VideoBlockListComponent extends RestTable implements OnInit {
|
export class VideoBlockListComponent extends RestTable implements OnInit {
|
||||||
blocklist: (VideoBlacklist & { reasonHtml?: string, embedHtml?: string })[] = []
|
blocklist: (VideoBlacklist & { reasonHtml?: string })[] = []
|
||||||
totalRecords = 0
|
totalRecords = 0
|
||||||
sort: SortMeta = { field: 'createdAt', order: -1 }
|
sort: SortMeta = { field: 'createdAt', order: -1 }
|
||||||
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
|
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
|
||||||
|
@ -50,7 +49,6 @@ export class VideoBlockListComponent extends RestTable implements OnInit {
|
||||||
private confirmService: ConfirmService,
|
private confirmService: ConfirmService,
|
||||||
private videoBlocklistService: VideoBlockService,
|
private videoBlocklistService: VideoBlockService,
|
||||||
private markdownRenderer: MarkdownService,
|
private markdownRenderer: MarkdownService,
|
||||||
private sanitizer: DomSanitizer,
|
|
||||||
private videoService: VideoService
|
private videoService: VideoService
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
|
@ -125,10 +123,6 @@ export class VideoBlockListComponent extends RestTable implements OnInit {
|
||||||
return 'VideoBlockListComponent'
|
return 'VideoBlockListComponent'
|
||||||
}
|
}
|
||||||
|
|
||||||
getVideoUrl (videoBlock: VideoBlacklist) {
|
|
||||||
return Video.buildWatchUrl(videoBlock.video)
|
|
||||||
}
|
|
||||||
|
|
||||||
toHtml (text: string) {
|
toHtml (text: string) {
|
||||||
return this.markdownRenderer.textMarkdownToHTML(text)
|
return this.markdownRenderer.textMarkdownToHTML(text)
|
||||||
}
|
}
|
||||||
|
@ -176,8 +170,7 @@ export class VideoBlockListComponent extends RestTable implements OnInit {
|
||||||
|
|
||||||
for (const element of this.blocklist) {
|
for (const element of this.blocklist) {
|
||||||
Object.assign(element, {
|
Object.assign(element, {
|
||||||
reasonHtml: await this.toHtml(element.reason),
|
reasonHtml: await this.toHtml(element.reason)
|
||||||
embedHtml: this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(element))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
export * from './users'
|
export * from './users'
|
||||||
|
export * from './videos'
|
||||||
export * from './overview.routes'
|
export * from './overview.routes'
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { Routes } from '@angular/router'
|
import { Routes } from '@angular/router'
|
||||||
import { UsersRoutes } from './users'
|
import { UsersRoutes } from './users'
|
||||||
|
import { VideosRoutes } from './videos'
|
||||||
|
|
||||||
export const OverviewRoutes: Routes = [
|
export const OverviewRoutes: Routes = [
|
||||||
...UsersRoutes
|
...UsersRoutes,
|
||||||
|
...VideosRoutes
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './video-list.component'
|
||||||
|
export * from './video.routes'
|
|
@ -0,0 +1,86 @@
|
||||||
|
<h1>
|
||||||
|
<my-global-icon iconName="videos" aria-hidden="true"></my-global-icon>
|
||||||
|
<ng-container i18n>Videos</ng-container>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p-table
|
||||||
|
[value]="videos" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
|
||||||
|
[sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true" [(selection)]="selectedVideos"
|
||||||
|
[lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
|
||||||
|
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
|
||||||
|
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} videos"
|
||||||
|
(onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
|
||||||
|
>
|
||||||
|
<ng-template pTemplate="caption">
|
||||||
|
<div class="caption">
|
||||||
|
<div class="left-buttons">
|
||||||
|
<my-action-dropdown
|
||||||
|
*ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange"
|
||||||
|
[actions]="bulkVideoActions" [entry]="selectedVideos"
|
||||||
|
>
|
||||||
|
</my-action-dropdown>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ml-auto">
|
||||||
|
<my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template pTemplate="header">
|
||||||
|
<tr>
|
||||||
|
<th style="width: 40px">
|
||||||
|
<p-tableHeaderCheckbox ariaLabel="Select all rows" i18n-ariaLabel></p-tableHeaderCheckbox>
|
||||||
|
</th>
|
||||||
|
<th style="width: 40px"></th>
|
||||||
|
<th style="width: 60px;"></th>
|
||||||
|
<th i18n>Video</th>
|
||||||
|
<th i18n>Info</th>
|
||||||
|
<th style="width: 150px;" i18n pSortableColumn="publishedAt">Published <p-sortIcon field="publishedAt"></p-sortIcon></th>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template pTemplate="body" let-expanded="expanded" let-video>
|
||||||
|
|
||||||
|
<tr [pSelectableRow]="video">
|
||||||
|
<td class="checkbox-cell">
|
||||||
|
<p-tableCheckbox [value]="video" ariaLabel="Select this row" i18n-ariaLabel></p-tableCheckbox>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="expand-cell" [pRowToggler]="video">
|
||||||
|
<my-table-expander-icon [expanded]="expanded"></my-table-expander-icon>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="action-cell">
|
||||||
|
<my-video-actions-dropdown
|
||||||
|
placement="bottom auto" buttonDirection="horizontal" [buttonStyled]="true" [video]="video"
|
||||||
|
[displayOptions]="videoActionsOptions" (videoRemoved)="onVideoRemoved()"
|
||||||
|
></my-video-actions-dropdown>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<my-video-cell [video]="video"></my-video-cell>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span class="badge badge-blue" i18n>{{ video.privacy.label }}</span>
|
||||||
|
<span *ngIf="video.nsfw" class="badge badge-red" i18n>NSFW</span>
|
||||||
|
<span *ngIf="video.blocked" class="badge badge-red" i18n>NSFW</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
{{ video.publishedAt | date: 'short' }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template pTemplate="rowexpansion" let-video>
|
||||||
|
<tr>
|
||||||
|
<td colspan="50">
|
||||||
|
<my-embed [video]="video"></my-embed>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</p-table>
|
|
@ -0,0 +1,10 @@
|
||||||
|
@use '_variables' as *;
|
||||||
|
@use '_mixins' as *;
|
||||||
|
my-embed {
|
||||||
|
display: block;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
@include table-badge;
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
import { SortMeta } from 'primeng/api'
|
||||||
|
import { Component, OnInit } from '@angular/core'
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
|
import { AuthService, ConfirmService, Notifier, RestPagination, RestTable } from '@app/core'
|
||||||
|
import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
|
||||||
|
import { UserRight } from '@shared/models'
|
||||||
|
import { AdvancedInputFilter } from '@app/shared/shared-forms'
|
||||||
|
import { VideoActionsDisplayType } from '@app/shared/shared-video-miniature'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-video-list',
|
||||||
|
templateUrl: './video-list.component.html',
|
||||||
|
styleUrls: [ './video-list.component.scss' ]
|
||||||
|
})
|
||||||
|
export class VideoListComponent extends RestTable implements OnInit {
|
||||||
|
videos: Video[] = []
|
||||||
|
|
||||||
|
totalRecords = 0
|
||||||
|
sort: SortMeta = { field: 'publishedAt', order: 1 }
|
||||||
|
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
|
||||||
|
|
||||||
|
bulkVideoActions: DropdownAction<Video[]>[][] = []
|
||||||
|
|
||||||
|
selectedVideos: Video[] = []
|
||||||
|
|
||||||
|
inputFilters: AdvancedInputFilter[] = [
|
||||||
|
{
|
||||||
|
title: $localize`Advanced filters`,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
queryParams: { search: 'local:true' },
|
||||||
|
label: $localize`Only local videos`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
videoActionsOptions: VideoActionsDisplayType = {
|
||||||
|
playlist: false,
|
||||||
|
download: false,
|
||||||
|
update: true,
|
||||||
|
blacklist: true,
|
||||||
|
delete: true,
|
||||||
|
report: false,
|
||||||
|
duplicate: true,
|
||||||
|
mute: true,
|
||||||
|
liveInfo: false
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
protected route: ActivatedRoute,
|
||||||
|
protected router: Router,
|
||||||
|
private confirmService: ConfirmService,
|
||||||
|
private auth: AuthService,
|
||||||
|
private notifier: Notifier,
|
||||||
|
private videoService: VideoService
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
get authUser () {
|
||||||
|
return this.auth.getUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
this.initialize()
|
||||||
|
|
||||||
|
this.bulkVideoActions = [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: $localize`Delete`,
|
||||||
|
handler: videos => this.removeVideos(videos),
|
||||||
|
isDisplayed: () => this.authUser.hasRight(UserRight.REMOVE_ANY_VIDEO)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
getIdentifier () {
|
||||||
|
return 'VideoListComponent'
|
||||||
|
}
|
||||||
|
|
||||||
|
isInSelectionMode () {
|
||||||
|
return this.selectedVideos.length !== 0
|
||||||
|
}
|
||||||
|
|
||||||
|
onVideoRemoved () {
|
||||||
|
this.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected reloadData () {
|
||||||
|
this.selectedVideos = []
|
||||||
|
|
||||||
|
this.videoService.getAdminVideos({
|
||||||
|
pagination: this.pagination,
|
||||||
|
sort: this.sort,
|
||||||
|
search: this.search
|
||||||
|
}).subscribe({
|
||||||
|
next: resultList => {
|
||||||
|
this.videos = resultList.data
|
||||||
|
this.totalRecords = resultList.total
|
||||||
|
},
|
||||||
|
|
||||||
|
error: err => this.notifier.error(err.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private async removeVideos (videos: Video[]) {
|
||||||
|
const message = $localize`Are you sure you want to delete these ${videos.length} videos?`
|
||||||
|
const res = await this.confirmService.confirm(message, $localize`Delete`)
|
||||||
|
if (res === false) return
|
||||||
|
|
||||||
|
this.videoService.removeVideo(videos.map(v => v.id))
|
||||||
|
.subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.notifier.success($localize`${videos.length} videos deleted.`)
|
||||||
|
this.reloadData()
|
||||||
|
},
|
||||||
|
|
||||||
|
error: err => this.notifier.error(err.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { Routes } from '@angular/router'
|
||||||
|
import { UserRightGuard } from '@app/core'
|
||||||
|
import { UserRight } from '@shared/models'
|
||||||
|
import { VideoListComponent } from './video-list.component'
|
||||||
|
|
||||||
|
export const VideosRoutes: Routes = [
|
||||||
|
{
|
||||||
|
path: 'videos',
|
||||||
|
canActivate: [ UserRightGuard ],
|
||||||
|
data: {
|
||||||
|
userRight: UserRight.SEE_ALL_VIDEOS
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
redirectTo: 'list',
|
||||||
|
pathMatch: 'full'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'list',
|
||||||
|
component: VideoListComponent,
|
||||||
|
data: {
|
||||||
|
meta: {
|
||||||
|
title: $localize`Videos list`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
|
@ -85,9 +85,9 @@
|
||||||
|
|
||||||
<!-- report right part (video/comment details) -->
|
<!-- report right part (video/comment details) -->
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<div *ngIf="abuse.video" class="screenratio">
|
<div *ngIf="abuse.video">
|
||||||
<div *ngIf="abuse.video.deleted" i18n>The video was deleted</div>
|
<div *ngIf="abuse.video.deleted" i18n>The video was deleted</div>
|
||||||
<div *ngIf="!abuse.video.deleted" [innerHTML]="abuse.embedHtml"></div>
|
<my-embed *ngIf="!abuse.video.deleted" [video]="abuse.video"></my-embed>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="abuse.comment" class="comment-html">
|
<div *ngIf="abuse.comment" class="comment-html">
|
||||||
|
|
|
@ -30,9 +30,7 @@
|
||||||
<ng-template pTemplate="body" let-expanded="expanded" let-abuse>
|
<ng-template pTemplate="body" let-expanded="expanded" let-abuse>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="expand-cell c-hand" [pRowToggler]="abuse" i18n-ngbTooltip ngbTooltip="More information" placement="top-left" container="body">
|
<td class="expand-cell c-hand" [pRowToggler]="abuse" i18n-ngbTooltip ngbTooltip="More information" placement="top-left" container="body">
|
||||||
<span class="expander">
|
<my-table-expander-icon [expanded]="expanded"></my-table-expander-icon>
|
||||||
<i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i>
|
|
||||||
</span>
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="action-cell">
|
<td class="action-cell">
|
||||||
|
@ -61,28 +59,20 @@
|
||||||
<ng-container *ngIf="abuse.video">
|
<ng-container *ngIf="abuse.video">
|
||||||
|
|
||||||
<td *ngIf="!abuse.video.deleted">
|
<td *ngIf="!abuse.video.deleted">
|
||||||
<a [href]="getVideoUrl(abuse)" class="table-video-link" [title]="abuse.video.name" target="_blank" rel="noopener noreferrer">
|
<my-video-cell [video]="abuse.video">
|
||||||
<div class="table-video">
|
<span image>
|
||||||
<div class="table-video-image">
|
|
||||||
<img [src]="abuse.video.thumbnailPath">
|
|
||||||
<span
|
<span
|
||||||
class="table-video-image-label" *ngIf="abuse.count > 1"
|
class="table-video-image-label" *ngIf="abuse.count > 1"
|
||||||
i18n-title title="This video has been reported multiple times."
|
i18n-title title="This video has been reported multiple times."
|
||||||
>
|
>
|
||||||
{{ abuse.nth }}/{{ abuse.count }}
|
{{ abuse.nth }}/{{ abuse.count }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</span>
|
||||||
|
|
||||||
<div class="table-video-text">
|
<span name>
|
||||||
<div>
|
|
||||||
<span *ngIf="!abuse.video.blacklisted" class="glyphicon glyphicon-new-window"></span>
|
|
||||||
<span *ngIf="abuse.video.blacklisted" i18n-title title="The video was blocked" class="glyphicon glyphicon-ban-circle"></span>
|
<span *ngIf="abuse.video.blacklisted" i18n-title title="The video was blocked" class="glyphicon glyphicon-ban-circle"></span>
|
||||||
{{ abuse.video.name }}
|
</span>
|
||||||
</div>
|
</my-video-cell>
|
||||||
<div i18n>by {{ abuse.video.channel?.displayName }} on {{ abuse.video.channel?.host }} </div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td *ngIf="abuse.video.deleted" class="c-hand" [pRowToggler]="abuse">
|
<td *ngIf="abuse.video.deleted" class="c-hand" [pRowToggler]="abuse">
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import * as debug from 'debug'
|
import * as debug from 'debug'
|
||||||
import truncate from 'lodash-es/truncate'
|
import truncate from 'lodash-es/truncate'
|
||||||
import { SortMeta } from 'primeng/api'
|
import { SortMeta } from 'primeng/api'
|
||||||
import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils'
|
|
||||||
import { environment } from 'src/environments/environment'
|
|
||||||
import { Component, Input, OnInit, ViewChild } from '@angular/core'
|
import { Component, Input, OnInit, ViewChild } from '@angular/core'
|
||||||
import { DomSanitizer } from '@angular/platform-browser'
|
import { DomSanitizer } from '@angular/platform-browser'
|
||||||
import { ActivatedRoute, Router } from '@angular/router'
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
|
@ -10,7 +8,6 @@ import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable }
|
||||||
import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main'
|
import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main'
|
||||||
import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation'
|
import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation'
|
||||||
import { VideoCommentService } from '@app/shared/shared-video-comment'
|
import { VideoCommentService } from '@app/shared/shared-video-comment'
|
||||||
import { buildVideoEmbedLink, decorateVideoLink } from '@shared/core-utils'
|
|
||||||
import { AbuseState, AdminAbuse } from '@shared/models'
|
import { AbuseState, AdminAbuse } from '@shared/models'
|
||||||
import { AdvancedInputFilter } from '../shared-forms'
|
import { AdvancedInputFilter } from '../shared-forms'
|
||||||
import { AbuseMessageModalComponent } from './abuse-message-modal.component'
|
import { AbuseMessageModalComponent } from './abuse-message-modal.component'
|
||||||
|
@ -133,19 +130,6 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
|
||||||
return '/a/' + abuse.flaggedAccount.nameWithHost
|
return '/a/' + abuse.flaggedAccount.nameWithHost
|
||||||
}
|
}
|
||||||
|
|
||||||
getVideoEmbed (abuse: AdminAbuse) {
|
|
||||||
return buildVideoOrPlaylistEmbed(
|
|
||||||
decorateVideoLink({
|
|
||||||
url: buildVideoEmbedLink(abuse.video, environment.originServerUrl),
|
|
||||||
title: false,
|
|
||||||
warningTitle: false,
|
|
||||||
startTime: abuse.video.startAt,
|
|
||||||
stopTime: abuse.video.endAt
|
|
||||||
}),
|
|
||||||
abuse.video.name
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeAbuse (abuse: AdminAbuse) {
|
async removeAbuse (abuse: AdminAbuse) {
|
||||||
const res = await this.confirmService.confirm($localize`Do you really want to delete this abuse report?`, $localize`Delete`)
|
const res = await this.confirmService.confirm($localize`Do you really want to delete this abuse report?`, $localize`Delete`)
|
||||||
if (res === false) return
|
if (res === false) return
|
||||||
|
@ -220,8 +204,6 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (abuse.video) {
|
if (abuse.video) {
|
||||||
abuse.embedHtml = this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(abuse))
|
|
||||||
|
|
||||||
if (abuse.video.channel?.ownerAccount) {
|
if (abuse.video.channel?.ownerAccount) {
|
||||||
abuse.video.channel.ownerAccount = new Account(abuse.video.channel.ownerAccount)
|
abuse.video.channel.ownerAccount = new Account(abuse.video.channel.ownerAccount)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import { SafeHtml } from '@angular/platform-browser'
|
|
||||||
import { AdminAbuse } from '@shared/models'
|
|
||||||
import { Account } from '@app/shared/shared-main'
|
import { Account } from '@app/shared/shared-main'
|
||||||
|
import { AdminAbuse } from '@shared/models'
|
||||||
|
|
||||||
// Don't use an abuse model because we need external services to compute some properties
|
// Don't use an abuse model because we need external services to compute some properties
|
||||||
// And this model is only used in this component
|
// And this model is only used in this component
|
||||||
export type ProcessedAbuse = AdminAbuse & {
|
export type ProcessedAbuse = AdminAbuse & {
|
||||||
moderationCommentHtml?: string
|
moderationCommentHtml?: string
|
||||||
reasonHtml?: string
|
reasonHtml?: string
|
||||||
embedHtml?: SafeHtml
|
|
||||||
updatedAt?: Date
|
updatedAt?: Date
|
||||||
|
|
||||||
// override bare server-side definitions with rich client-side definitions
|
// override bare server-side definitions with rich client-side definitions
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
|
|
||||||
import { TableModule } from 'primeng/table'
|
import { TableModule } from 'primeng/table'
|
||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
|
import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module'
|
||||||
import { SharedFormModule } from '../shared-forms/shared-form.module'
|
import { SharedFormModule } from '../shared-forms/shared-form.module'
|
||||||
import { SharedGlobalIconModule } from '../shared-icons'
|
import { SharedGlobalIconModule } from '../shared-icons'
|
||||||
import { SharedMainModule } from '../shared-main/shared-main.module'
|
import { SharedMainModule } from '../shared-main/shared-main.module'
|
||||||
import { SharedModerationModule } from '../shared-moderation'
|
import { SharedModerationModule } from '../shared-moderation'
|
||||||
|
import { SharedTablesModule } from '../shared-tables'
|
||||||
import { SharedVideoCommentModule } from '../shared-video-comment'
|
import { SharedVideoCommentModule } from '../shared-video-comment'
|
||||||
import { AbuseDetailsComponent } from './abuse-details.component'
|
import { AbuseDetailsComponent } from './abuse-details.component'
|
||||||
import { AbuseListTableComponent } from './abuse-list-table.component'
|
import { AbuseListTableComponent } from './abuse-list-table.component'
|
||||||
import { AbuseMessageModalComponent } from './abuse-message-modal.component'
|
import { AbuseMessageModalComponent } from './abuse-message-modal.component'
|
||||||
import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
|
import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
|
||||||
import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module'
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -21,7 +22,8 @@ import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image
|
||||||
SharedModerationModule,
|
SharedModerationModule,
|
||||||
SharedGlobalIconModule,
|
SharedGlobalIconModule,
|
||||||
SharedVideoCommentModule,
|
SharedVideoCommentModule,
|
||||||
SharedActorImageModule
|
SharedActorImageModule,
|
||||||
|
SharedTablesModule
|
||||||
],
|
],
|
||||||
|
|
||||||
declarations: [
|
declarations: [
|
||||||
|
|
|
@ -43,13 +43,8 @@ import {
|
||||||
} from './misc'
|
} from './misc'
|
||||||
import { PluginPlaceholderComponent } from './plugins'
|
import { PluginPlaceholderComponent } from './plugins'
|
||||||
import { ActorRedirectGuard } from './router'
|
import { ActorRedirectGuard } from './router'
|
||||||
import {
|
import { UserHistoryService, UserNotificationsComponent, UserNotificationService, UserQuotaComponent } from './users'
|
||||||
UserHistoryService,
|
import { EmbedComponent, RedundancyService, VideoImportService, VideoOwnershipService, VideoService } from './video'
|
||||||
UserNotificationsComponent,
|
|
||||||
UserNotificationService,
|
|
||||||
UserQuotaComponent
|
|
||||||
} from './users'
|
|
||||||
import { RedundancyService, VideoImportService, VideoOwnershipService, VideoService } from './video'
|
|
||||||
import { VideoCaptionService } from './video-caption'
|
import { VideoCaptionService } from './video-caption'
|
||||||
import { VideoChannelService } from './video-channel'
|
import { VideoChannelService } from './video-channel'
|
||||||
|
|
||||||
|
@ -111,6 +106,8 @@ import { VideoChannelService } from './video-channel'
|
||||||
UserQuotaComponent,
|
UserQuotaComponent,
|
||||||
UserNotificationsComponent,
|
UserNotificationsComponent,
|
||||||
|
|
||||||
|
EmbedComponent,
|
||||||
|
|
||||||
PluginPlaceholderComponent
|
PluginPlaceholderComponent
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -167,6 +164,8 @@ import { VideoChannelService } from './video-channel'
|
||||||
UserQuotaComponent,
|
UserQuotaComponent,
|
||||||
UserNotificationsComponent,
|
UserNotificationsComponent,
|
||||||
|
|
||||||
|
EmbedComponent,
|
||||||
|
|
||||||
PluginPlaceholderComponent
|
PluginPlaceholderComponent
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<div class="screenratio">
|
||||||
|
<div [innerHTML]="embedHTML"></div>
|
||||||
|
</div>
|
|
@ -0,0 +1,10 @@
|
||||||
|
@use '_mixins' as *;
|
||||||
|
@use '_variables' as *;
|
||||||
|
|
||||||
|
.screenratio {
|
||||||
|
@include block-ratio($selector: 'div, ::ng-deep iframe') {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
left: 0;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils'
|
||||||
|
import { environment } from 'src/environments/environment'
|
||||||
|
import { Component, Input, OnInit } from '@angular/core'
|
||||||
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
|
||||||
|
import { buildVideoEmbedLink, decorateVideoLink } from '@shared/core-utils'
|
||||||
|
import { Video } from '@shared/models'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-embed',
|
||||||
|
styleUrls: [ './embed.component.scss' ],
|
||||||
|
templateUrl: './embed.component.html'
|
||||||
|
})
|
||||||
|
export class EmbedComponent implements OnInit {
|
||||||
|
@Input() video: Pick<Video, 'name' | 'uuid'>
|
||||||
|
|
||||||
|
embedHTML: SafeHtml
|
||||||
|
|
||||||
|
constructor (private sanitizer: DomSanitizer) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
const html = buildVideoOrPlaylistEmbed(
|
||||||
|
decorateVideoLink({
|
||||||
|
url: buildVideoEmbedLink(this.video, environment.originServerUrl),
|
||||||
|
|
||||||
|
title: false,
|
||||||
|
warningTitle: false
|
||||||
|
}),
|
||||||
|
this.video.name
|
||||||
|
)
|
||||||
|
|
||||||
|
this.embedHTML = this.sanitizer.bypassSecurityTrustHtml(html)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
export * from './embed.component'
|
||||||
export * from './redundancy.service'
|
export * from './redundancy.service'
|
||||||
export * from './video-details.model'
|
export * from './video-details.model'
|
||||||
export * from './video-edit.model'
|
export * from './video-edit.model'
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { Observable } from 'rxjs'
|
import { SortMeta } from 'primeng/api'
|
||||||
import { catchError, map, switchMap } from 'rxjs/operators'
|
import { from, Observable } from 'rxjs'
|
||||||
|
import { catchError, concatMap, map, switchMap, toArray } from 'rxjs/operators'
|
||||||
import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http'
|
import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { ComponentPaginationLight, RestExtractor, RestService, ServerService, UserService } from '@app/core'
|
import { ComponentPaginationLight, RestExtractor, RestPagination, RestService, ServerService, UserService } from '@app/core'
|
||||||
import { objectToFormData } from '@app/helpers'
|
import { objectToFormData } from '@app/helpers'
|
||||||
import {
|
import {
|
||||||
BooleanBothQuery,
|
BooleanBothQuery,
|
||||||
|
@ -31,8 +32,8 @@ import { VideoEdit } from './video-edit.model'
|
||||||
import { Video } from './video.model'
|
import { Video } from './video.model'
|
||||||
|
|
||||||
export type CommonVideoParams = {
|
export type CommonVideoParams = {
|
||||||
videoPagination: ComponentPaginationLight
|
videoPagination?: ComponentPaginationLight
|
||||||
sort: VideoSortField
|
sort: VideoSortField | SortMeta
|
||||||
filter?: VideoFilter
|
filter?: VideoFilter
|
||||||
categoryOneOf?: number[]
|
categoryOneOf?: number[]
|
||||||
languageOneOf?: string[]
|
languageOneOf?: string[]
|
||||||
|
@ -200,6 +201,31 @@ export class VideoService {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAdminVideos (
|
||||||
|
parameters: Omit<CommonVideoParams, 'filter'> & { pagination: RestPagination, search?: string }
|
||||||
|
): Observable<ResultList<Video>> {
|
||||||
|
const { pagination, search } = parameters
|
||||||
|
|
||||||
|
let params = new HttpParams()
|
||||||
|
params = this.buildCommonVideosParams({ params, ...parameters })
|
||||||
|
|
||||||
|
params = params.set('start', pagination.start.toString())
|
||||||
|
.set('count', pagination.count.toString())
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
params = this.buildAdminParamsFromSearch(search, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!params.has('filter')) params = params.set('filter', 'all')
|
||||||
|
|
||||||
|
return this.authHttp
|
||||||
|
.get<ResultList<Video>>(VideoService.BASE_VIDEO_URL, { params })
|
||||||
|
.pipe(
|
||||||
|
switchMap(res => this.extractVideos(res)),
|
||||||
|
catchError(err => this.restExtractor.handleError(err))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
getVideos (parameters: CommonVideoParams): Observable<ResultList<Video>> {
|
getVideos (parameters: CommonVideoParams): Observable<ResultList<Video>> {
|
||||||
let params = new HttpParams()
|
let params = new HttpParams()
|
||||||
params = this.buildCommonVideosParams({ params, ...parameters })
|
params = this.buildCommonVideosParams({ params, ...parameters })
|
||||||
|
@ -284,11 +310,13 @@ export class VideoService {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
removeVideo (id: number) {
|
removeVideo (idArg: number | number[]) {
|
||||||
return this.authHttp
|
const ids = Array.isArray(idArg) ? idArg : [ idArg ]
|
||||||
.delete(VideoService.BASE_VIDEO_URL + id)
|
|
||||||
|
return from(ids)
|
||||||
.pipe(
|
.pipe(
|
||||||
map(this.restExtractor.extractDataBool),
|
concatMap(id => this.authHttp.delete(VideoService.BASE_VIDEO_URL + id)),
|
||||||
|
toArray(),
|
||||||
catchError(err => this.restExtractor.handleError(err))
|
catchError(err => this.restExtractor.handleError(err))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -393,9 +421,23 @@ export class VideoService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildCommonVideosParams (options: CommonVideoParams & { params: HttpParams }) {
|
private buildCommonVideosParams (options: CommonVideoParams & { params: HttpParams }) {
|
||||||
const { params, videoPagination, sort, filter, categoryOneOf, languageOneOf, skipCount, nsfwPolicy, isLive, nsfw } = options
|
const {
|
||||||
|
params,
|
||||||
|
videoPagination,
|
||||||
|
sort,
|
||||||
|
filter,
|
||||||
|
categoryOneOf,
|
||||||
|
languageOneOf,
|
||||||
|
skipCount,
|
||||||
|
nsfwPolicy,
|
||||||
|
isLive,
|
||||||
|
nsfw
|
||||||
|
} = options
|
||||||
|
|
||||||
|
const pagination = videoPagination
|
||||||
|
? this.restService.componentToRestPagination(videoPagination)
|
||||||
|
: undefined
|
||||||
|
|
||||||
const pagination = this.restService.componentToRestPagination(videoPagination)
|
|
||||||
let newParams = this.restService.addRestGetParams(params, pagination, sort)
|
let newParams = this.restService.addRestGetParams(params, pagination, sort)
|
||||||
|
|
||||||
if (filter) newParams = newParams.set('filter', filter)
|
if (filter) newParams = newParams.set('filter', filter)
|
||||||
|
@ -409,4 +451,19 @@ export class VideoService {
|
||||||
|
|
||||||
return newParams
|
return newParams
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildAdminParamsFromSearch (search: string, params: HttpParams) {
|
||||||
|
const filters = this.restService.parseQueryStringFilter(search, {
|
||||||
|
filter: {
|
||||||
|
prefix: 'local:',
|
||||||
|
handler: v => {
|
||||||
|
if (v === 'true') return 'all-local'
|
||||||
|
|
||||||
|
return 'all'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return this.restService.addObjectParams(params, filters)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,14 +40,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.screenratio {
|
|
||||||
@include block-ratio($selector: 'div, ::ng-deep iframe') {
|
|
||||||
width: 100% !important;
|
|
||||||
height: 100% !important;
|
|
||||||
left: 0;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
.chip {
|
.chip {
|
||||||
@include chip;
|
@include chip;
|
||||||
}
|
}
|
||||||
|
@ -58,13 +50,6 @@ my-action-dropdown.show {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-video-link {
|
|
||||||
@include disable-outline;
|
|
||||||
|
|
||||||
position: relative;
|
|
||||||
top: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-comment-link,
|
.table-comment-link,
|
||||||
.table-account-link {
|
.table-account-link {
|
||||||
@include disable-outline;
|
@include disable-outline;
|
||||||
|
@ -81,68 +66,6 @@ my-action-dropdown.show {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-video {
|
|
||||||
display: inline-flex;
|
|
||||||
|
|
||||||
.table-video-image {
|
|
||||||
$image-height: 45px;
|
|
||||||
|
|
||||||
@include miniature-thumbnail;
|
|
||||||
@include margin-right(0.5rem);
|
|
||||||
|
|
||||||
height: $image-height;
|
|
||||||
width: #{math.div(16, 9) * $image-height};
|
|
||||||
border-radius: 2px;
|
|
||||||
border: 0;
|
|
||||||
background: transparent;
|
|
||||||
display: inline-flex;
|
|
||||||
justify-content: center;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
img {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
color: pvar(--inputPlaceholderColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-video-image-label {
|
|
||||||
@include static-thumbnail-overlay;
|
|
||||||
position: absolute;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-size: 10px;
|
|
||||||
padding: 0 3px;
|
|
||||||
line-height: 1.3;
|
|
||||||
bottom: 2px;
|
|
||||||
right: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-video-text {
|
|
||||||
display: inline-flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 90%;
|
|
||||||
color: pvar(--mainForegroundColor);
|
|
||||||
line-height: 1rem;
|
|
||||||
|
|
||||||
div .glyphicon {
|
|
||||||
@include margin-left(0.1rem);
|
|
||||||
|
|
||||||
font-size: 80%;
|
|
||||||
color: #808080;
|
|
||||||
}
|
|
||||||
|
|
||||||
div + div {
|
|
||||||
color: var(--greyForegroundColor);
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
my-abuse-details {
|
my-abuse-details {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,3 @@ textarea {
|
||||||
@include margin-left(10px);
|
@include margin-left(10px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.screenratio {
|
|
||||||
@include block-ratio($selector: 'div, ::ng-deep iframe') {
|
|
||||||
left: 0;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -35,9 +35,7 @@
|
||||||
<div class="col-7">
|
<div class="col-7">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-12 col-lg-9 mb-2">
|
<div class="col-12 col-lg-9 mb-2">
|
||||||
<div class="screenratio">
|
<my-embed [video]="video"></my-embed>
|
||||||
<div [innerHTML]="embedHtml"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import { mapValues, pickBy } from 'lodash-es'
|
import { mapValues, pickBy } from 'lodash-es'
|
||||||
import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils'
|
|
||||||
import { Component, Input, OnInit, ViewChild } from '@angular/core'
|
import { Component, Input, OnInit, ViewChild } from '@angular/core'
|
||||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
|
import { DomSanitizer } from '@angular/platform-browser'
|
||||||
import { Notifier } from '@app/core'
|
import { Notifier } from '@app/core'
|
||||||
import { ABUSE_REASON_VALIDATOR } from '@app/shared/form-validators/abuse-validators'
|
import { ABUSE_REASON_VALIDATOR } from '@app/shared/form-validators/abuse-validators'
|
||||||
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
|
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
|
||||||
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 { decorateVideoLink } from '@shared/core-utils'
|
|
||||||
import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse'
|
import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse'
|
||||||
import { AbusePredefinedReasonsString } from '@shared/models'
|
import { AbusePredefinedReasonsString } from '@shared/models'
|
||||||
import { Video } from '../../shared-main'
|
import { Video } from '../../shared-main'
|
||||||
|
@ -25,7 +23,6 @@ export class VideoReportComponent extends FormReactive implements OnInit {
|
||||||
|
|
||||||
error: string = null
|
error: string = null
|
||||||
predefinedReasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = []
|
predefinedReasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = []
|
||||||
embedHtml: SafeHtml
|
|
||||||
|
|
||||||
private openedModal: NgbModalRef
|
private openedModal: NgbModalRef
|
||||||
|
|
||||||
|
@ -55,20 +52,6 @@ export class VideoReportComponent extends FormReactive implements OnInit {
|
||||||
return this.form.get('timestamp').value
|
return this.form.get('timestamp').value
|
||||||
}
|
}
|
||||||
|
|
||||||
getVideoEmbed () {
|
|
||||||
return this.sanitizer.bypassSecurityTrustHtml(
|
|
||||||
buildVideoOrPlaylistEmbed(
|
|
||||||
decorateVideoLink({
|
|
||||||
url: this.video.embedUrl,
|
|
||||||
title: false,
|
|
||||||
warningTitle: false
|
|
||||||
}),
|
|
||||||
|
|
||||||
this.video.name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this.buildForm({
|
this.buildForm({
|
||||||
reason: ABUSE_REASON_VALIDATOR,
|
reason: ABUSE_REASON_VALIDATOR,
|
||||||
|
@ -82,8 +65,6 @@ export class VideoReportComponent extends FormReactive implements OnInit {
|
||||||
})
|
})
|
||||||
|
|
||||||
this.predefinedReasons = this.abuseService.getPrefefinedReasons('video')
|
this.predefinedReasons = this.abuseService.getPrefefinedReasons('video')
|
||||||
|
|
||||||
this.embedHtml = this.getVideoEmbed()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
show () {
|
show () {
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './table-expander-icon.component'
|
||||||
|
export * from './video-cell.component'
|
||||||
|
export * from './shared-tables.module'
|
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core'
|
||||||
|
import { SharedMainModule } from '../shared-main/shared-main.module'
|
||||||
|
import { TableExpanderIconComponent } from './table-expander-icon.component'
|
||||||
|
import { VideoCellComponent } from './video-cell.component'
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
SharedMainModule
|
||||||
|
],
|
||||||
|
|
||||||
|
declarations: [
|
||||||
|
VideoCellComponent,
|
||||||
|
TableExpanderIconComponent
|
||||||
|
],
|
||||||
|
|
||||||
|
exports: [
|
||||||
|
VideoCellComponent,
|
||||||
|
TableExpanderIconComponent
|
||||||
|
],
|
||||||
|
|
||||||
|
providers: [
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class SharedTablesModule { }
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Component, Input } from '@angular/core'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-table-expander-icon',
|
||||||
|
template: `
|
||||||
|
<span class="expander">
|
||||||
|
<i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i>
|
||||||
|
</span>`
|
||||||
|
})
|
||||||
|
export class TableExpanderIconComponent {
|
||||||
|
@Input() expanded: boolean
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
<a [href]="getVideoUrl()" class="table-video-link" [title]="video.name" target="_blank" rel="noopener noreferrer">
|
||||||
|
<div class="table-video">
|
||||||
|
<div class="table-video-image">
|
||||||
|
<img [src]="video.thumbnailPath">
|
||||||
|
|
||||||
|
<ng-content select="[image]"></ng-content>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-video-text">
|
||||||
|
<div>
|
||||||
|
<ng-content select="[name]"></ng-content>
|
||||||
|
|
||||||
|
{{ video.name }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-muted">by {{ video.channel?.displayName }} on {{ video.channel?.host }} </div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
|
@ -0,0 +1,74 @@
|
||||||
|
@use 'sass:math';
|
||||||
|
@use '_mixins' as *;
|
||||||
|
@use '_variables' as *;
|
||||||
|
@use '_miniature' as *;
|
||||||
|
|
||||||
|
.table-video-link {
|
||||||
|
@include disable-outline;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-video {
|
||||||
|
display: inline-flex;
|
||||||
|
|
||||||
|
.table-video-image {
|
||||||
|
$image-height: 45px;
|
||||||
|
|
||||||
|
@include miniature-thumbnail;
|
||||||
|
@include margin-right(0.5rem);
|
||||||
|
|
||||||
|
height: $image-height;
|
||||||
|
width: #{math.div(16, 9) * $image-height};
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: pvar(--inputPlaceholderColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-video-image-label {
|
||||||
|
@include static-thumbnail-overlay;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 0 3px;
|
||||||
|
line-height: 1.3;
|
||||||
|
bottom: 2px;
|
||||||
|
right: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-video-text {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 90%;
|
||||||
|
color: pvar(--mainForegroundColor);
|
||||||
|
line-height: 1rem;
|
||||||
|
|
||||||
|
div .glyphicon {
|
||||||
|
@include margin-left(0.1rem);
|
||||||
|
|
||||||
|
font-size: 80%;
|
||||||
|
color: #808080;
|
||||||
|
}
|
||||||
|
|
||||||
|
div + div {
|
||||||
|
color: var(--greyForegroundColor);
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Component, Input } from '@angular/core'
|
||||||
|
import { Video } from '@app/shared/shared-main'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-video-cell',
|
||||||
|
styleUrls: [ 'video-cell.component.scss' ],
|
||||||
|
templateUrl: 'video-cell.component.html'
|
||||||
|
})
|
||||||
|
export class VideoCellComponent {
|
||||||
|
@Input() video: Video
|
||||||
|
|
||||||
|
getVideoUrl () {
|
||||||
|
return Video.buildWatchUrl(this.video)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue