From 33f6dce136ca6e969fe374efa099bee3f2a3599d Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 27 Oct 2021 11:42:05 +0200 Subject: [PATCH] Add videos list admin component --- client/src/app/+admin/admin.component.ts | 12 ++ client/src/app/+admin/admin.module.ts | 8 +- .../video-block-list.component.html | 29 +---- .../video-block-list.component.ts | 13 +- client/src/app/+admin/overview/index.ts | 1 + .../app/+admin/overview/overview.routes.ts | 4 +- .../src/app/+admin/overview/videos/index.ts | 2 + .../overview/videos/video-list.component.html | 86 ++++++++++++ .../overview/videos/video-list.component.scss | 10 ++ .../overview/videos/video-list.component.ts | 123 ++++++++++++++++++ .../+admin/overview/videos/video.routes.ts | 30 +++++ .../abuse-details.component.html | 4 +- .../abuse-list-table.component.html | 38 ++---- .../abuse-list-table.component.ts | 18 --- .../processed-abuse.model.ts | 4 +- .../shared-abuse-list.module.ts | 6 +- .../shared/shared-main/shared-main.module.ts | 13 +- .../shared-main/video/embed.component.html | 3 + .../shared-main/video/embed.component.scss | 10 ++ .../shared-main/video/embed.component.ts | 35 +++++ .../src/app/shared/shared-main/video/index.ts | 1 + .../shared/shared-main/video/video.service.ts | 85 ++++++++++-- .../shared/shared-moderation/moderation.scss | 77 ----------- .../report-modals/report.component.scss | 6 - .../report-modals/video-report.component.html | 4 +- .../report-modals/video-report.component.ts | 21 +-- client/src/app/shared/shared-tables/index.ts | 3 + .../shared-tables/shared-tables.module.ts | 25 ++++ .../table-expander-icon.component.ts | 12 ++ .../shared-tables/video-cell.component.html | 19 +++ .../shared-tables/video-cell.component.scss | 74 +++++++++++ .../shared-tables/video-cell.component.ts | 15 +++ 32 files changed, 581 insertions(+), 210 deletions(-) create mode 100644 client/src/app/+admin/overview/videos/index.ts create mode 100644 client/src/app/+admin/overview/videos/video-list.component.html create mode 100644 client/src/app/+admin/overview/videos/video-list.component.scss create mode 100644 client/src/app/+admin/overview/videos/video-list.component.ts create mode 100644 client/src/app/+admin/overview/videos/video.routes.ts create mode 100644 client/src/app/shared/shared-main/video/embed.component.html create mode 100644 client/src/app/shared/shared-main/video/embed.component.scss create mode 100644 client/src/app/shared/shared-main/video/embed.component.ts create mode 100644 client/src/app/shared/shared-tables/index.ts create mode 100644 client/src/app/shared/shared-tables/shared-tables.module.ts create mode 100644 client/src/app/shared/shared-tables/table-expander-icon.component.ts create mode 100644 client/src/app/shared/shared-tables/video-cell.component.html create mode 100644 client/src/app/shared/shared-tables/video-cell.component.scss create mode 100644 client/src/app/shared/shared-tables/video-cell.component.ts diff --git a/client/src/app/+admin/admin.component.ts b/client/src/app/+admin/admin.component.ts index 27d5e0a10..b8a957d1c 100644 --- a/client/src/app/+admin/admin.component.ts +++ b/client/src/app/+admin/admin.component.ts @@ -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) { this.menuEntries.push(overviewItems) } @@ -217,4 +225,8 @@ export class AdminComponent implements OnInit { private hasVideoCommentsRight () { return this.auth.getUser().hasRight(UserRight.SEE_ALL_COMMENTS) } + + private hasVideosRight () { + return this.auth.getUser().hasRight(UserRight.SEE_ALL_VIDEOS) + } } diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts index a2bd88880..d04c11a20 100644 --- a/client/src/app/+admin/admin.module.ts +++ b/client/src/app/+admin/admin.module.ts @@ -10,7 +10,9 @@ import { SharedFormModule } from '@app/shared/shared-forms' import { SharedGlobalIconModule } from '@app/shared/shared-icons' import { SharedMainModule } from '@app/shared/shared-main' import { SharedModerationModule } from '@app/shared/shared-moderation' +import { SharedTablesModule } from '@app/shared/shared-tables' import { SharedVideoCommentModule } from '@app/shared/shared-video-comment' +import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature' import { AdminRoutingModule } from './admin-routing.module' import { AdminComponent } from './admin.component' import { @@ -33,7 +35,7 @@ import { AbuseListComponent, VideoBlockListComponent } from './moderation' import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from './moderation/instance-blocklist' import { ModerationComponent } from './moderation/moderation.component' 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 { PluginSearchComponent } from './plugins/plugin-search/plugin-search.component' import { PluginShowInstalledComponent } from './plugins/plugin-show-installed/plugin-show-installed.component' @@ -56,6 +58,8 @@ import { JobsComponent } from './system/jobs/jobs.component' SharedActorImageModule, SharedActorImageEditModule, SharedCustomMarkupModule, + SharedVideoMiniatureModule, + SharedTablesModule, TableModule, SelectButtonModule, @@ -65,6 +69,8 @@ import { JobsComponent } from './system/jobs/jobs.component' declarations: [ AdminComponent, + VideoListComponent, + FollowsComponent, FollowersListComponent, FollowingListComponent, diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html index 7efa87dd0..3cd69cfbc 100644 --- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html +++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html @@ -34,9 +34,7 @@ - - - + @@ -47,22 +45,11 @@ - -
-
- -
- -
-
- - {{ videoBlock.video.name }} -
- -
by {{ videoBlock.video.channel?.displayName }} on {{ videoBlock.video.channel?.host }}
-
-
-
+ + + + + @@ -90,9 +77,7 @@
-
-
-
+
diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts index 7baf34ca2..1fe8d0f9d 100644 --- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts +++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts @@ -3,11 +3,10 @@ import { switchMap } from 'rxjs/operators' import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils' import { environment } from 'src/environments/environment' import { Component, OnInit } from '@angular/core' -import { DomSanitizer } from '@angular/platform-browser' import { ActivatedRoute, Router } from '@angular/router' import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' 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 { buildVideoEmbedLink, decorateVideoLink } from '@shared/core-utils' 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' ] }) export class VideoBlockListComponent extends RestTable implements OnInit { - blocklist: (VideoBlacklist & { reasonHtml?: string, embedHtml?: string })[] = [] + blocklist: (VideoBlacklist & { reasonHtml?: string })[] = [] totalRecords = 0 sort: SortMeta = { field: 'createdAt', order: -1 } pagination: RestPagination = { count: this.rowsPerPage, start: 0 } @@ -50,7 +49,6 @@ export class VideoBlockListComponent extends RestTable implements OnInit { private confirmService: ConfirmService, private videoBlocklistService: VideoBlockService, private markdownRenderer: MarkdownService, - private sanitizer: DomSanitizer, private videoService: VideoService ) { super() @@ -125,10 +123,6 @@ export class VideoBlockListComponent extends RestTable implements OnInit { return 'VideoBlockListComponent' } - getVideoUrl (videoBlock: VideoBlacklist) { - return Video.buildWatchUrl(videoBlock.video) - } - toHtml (text: string) { return this.markdownRenderer.textMarkdownToHTML(text) } @@ -176,8 +170,7 @@ export class VideoBlockListComponent extends RestTable implements OnInit { for (const element of this.blocklist) { Object.assign(element, { - reasonHtml: await this.toHtml(element.reason), - embedHtml: this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(element)) + reasonHtml: await this.toHtml(element.reason) }) } }, diff --git a/client/src/app/+admin/overview/index.ts b/client/src/app/+admin/overview/index.ts index b71a6a45f..a9c46893f 100644 --- a/client/src/app/+admin/overview/index.ts +++ b/client/src/app/+admin/overview/index.ts @@ -1,2 +1,3 @@ export * from './users' +export * from './videos' export * from './overview.routes' diff --git a/client/src/app/+admin/overview/overview.routes.ts b/client/src/app/+admin/overview/overview.routes.ts index cb5986072..1e6686d16 100644 --- a/client/src/app/+admin/overview/overview.routes.ts +++ b/client/src/app/+admin/overview/overview.routes.ts @@ -1,6 +1,8 @@ import { Routes } from '@angular/router' import { UsersRoutes } from './users' +import { VideosRoutes } from './videos' export const OverviewRoutes: Routes = [ - ...UsersRoutes + ...UsersRoutes, + ...VideosRoutes ] diff --git a/client/src/app/+admin/overview/videos/index.ts b/client/src/app/+admin/overview/videos/index.ts new file mode 100644 index 000000000..40c2ffe72 --- /dev/null +++ b/client/src/app/+admin/overview/videos/index.ts @@ -0,0 +1,2 @@ +export * from './video-list.component' +export * from './video.routes' diff --git a/client/src/app/+admin/overview/videos/video-list.component.html b/client/src/app/+admin/overview/videos/video-list.component.html new file mode 100644 index 000000000..1f1e9cc6e --- /dev/null +++ b/client/src/app/+admin/overview/videos/video-list.component.html @@ -0,0 +1,86 @@ +

+ + Videos +

+ + + +
+
+ + +
+ +
+ +
+ +
+
+ + + + + + + + + Video + Info + Published + + + + + + + + + + + + + + + + + + + + + + + + {{ video.privacy.label }} + NSFW + NSFW + + + + {{ video.publishedAt | date: 'short' }} + + + + + + + + + + + + +
diff --git a/client/src/app/+admin/overview/videos/video-list.component.scss b/client/src/app/+admin/overview/videos/video-list.component.scss new file mode 100644 index 000000000..fcdb457f2 --- /dev/null +++ b/client/src/app/+admin/overview/videos/video-list.component.scss @@ -0,0 +1,10 @@ +@use '_variables' as *; +@use '_mixins' as *; +my-embed { + display: block; + max-width: 500px; +} + +.badge { + @include table-badge; +} diff --git a/client/src/app/+admin/overview/videos/video-list.component.ts b/client/src/app/+admin/overview/videos/video-list.component.ts new file mode 100644 index 000000000..a445bc209 --- /dev/null +++ b/client/src/app/+admin/overview/videos/video-list.component.ts @@ -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[][] = [] + + 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) + }) + } +} diff --git a/client/src/app/+admin/overview/videos/video.routes.ts b/client/src/app/+admin/overview/videos/video.routes.ts new file mode 100644 index 000000000..984df7b82 --- /dev/null +++ b/client/src/app/+admin/overview/videos/video.routes.ts @@ -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` + } + } + } + ] + } +] diff --git a/client/src/app/shared/shared-abuse-list/abuse-details.component.html b/client/src/app/shared/shared-abuse-list/abuse-details.component.html index 2f0bc5ae5..a1a4586f0 100644 --- a/client/src/app/shared/shared-abuse-list/abuse-details.component.html +++ b/client/src/app/shared/shared-abuse-list/abuse-details.component.html @@ -85,9 +85,9 @@
-
+
The video was deleted
-
+
diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html index 4bf83316b..d957eaeab 100644 --- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html +++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html @@ -30,9 +30,7 @@ - - - + @@ -61,28 +59,20 @@ - -
-
- - - {{ abuse.nth }}/{{ abuse.count }} - -
+ + + + {{ abuse.nth }}/{{ abuse.count }} + + -
-
- - - {{ abuse.video.name }} -
-
by {{ abuse.video.channel?.displayName }} on {{ abuse.video.channel?.host }}
-
-
-
+ + + + diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts index 297993e39..10f5861b9 100644 --- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts +++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts @@ -1,8 +1,6 @@ import * as debug from 'debug' import truncate from 'lodash-es/truncate' 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 { DomSanitizer } from '@angular/platform-browser' 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 { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation' import { VideoCommentService } from '@app/shared/shared-video-comment' -import { buildVideoEmbedLink, decorateVideoLink } from '@shared/core-utils' import { AbuseState, AdminAbuse } from '@shared/models' import { AdvancedInputFilter } from '../shared-forms' import { AbuseMessageModalComponent } from './abuse-message-modal.component' @@ -133,19 +130,6 @@ export class AbuseListTableComponent extends RestTable implements OnInit { 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) { const res = await this.confirmService.confirm($localize`Do you really want to delete this abuse report?`, $localize`Delete`) if (res === false) return @@ -220,8 +204,6 @@ export class AbuseListTableComponent extends RestTable implements OnInit { } if (abuse.video) { - abuse.embedHtml = this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(abuse)) - if (abuse.video.channel?.ownerAccount) { abuse.video.channel.ownerAccount = new Account(abuse.video.channel.ownerAccount) } diff --git a/client/src/app/shared/shared-abuse-list/processed-abuse.model.ts b/client/src/app/shared/shared-abuse-list/processed-abuse.model.ts index 194d52a33..b9a9bd889 100644 --- a/client/src/app/shared/shared-abuse-list/processed-abuse.model.ts +++ b/client/src/app/shared/shared-abuse-list/processed-abuse.model.ts @@ -1,13 +1,11 @@ -import { SafeHtml } from '@angular/platform-browser' -import { AdminAbuse } from '@shared/models' 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 // And this model is only used in this component export type ProcessedAbuse = AdminAbuse & { moderationCommentHtml?: string reasonHtml?: string - embedHtml?: SafeHtml updatedAt?: Date // override bare server-side definitions with rich client-side definitions diff --git a/client/src/app/shared/shared-abuse-list/shared-abuse-list.module.ts b/client/src/app/shared/shared-abuse-list/shared-abuse-list.module.ts index 8f3830a17..eeda27fa6 100644 --- a/client/src/app/shared/shared-abuse-list/shared-abuse-list.module.ts +++ b/client/src/app/shared/shared-abuse-list/shared-abuse-list.module.ts @@ -1,16 +1,17 @@ import { TableModule } from 'primeng/table' import { NgModule } from '@angular/core' +import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module' import { SharedFormModule } from '../shared-forms/shared-form.module' import { SharedGlobalIconModule } from '../shared-icons' import { SharedMainModule } from '../shared-main/shared-main.module' import { SharedModerationModule } from '../shared-moderation' +import { SharedTablesModule } from '../shared-tables' import { SharedVideoCommentModule } from '../shared-video-comment' import { AbuseDetailsComponent } from './abuse-details.component' import { AbuseListTableComponent } from './abuse-list-table.component' import { AbuseMessageModalComponent } from './abuse-message-modal.component' import { ModerationCommentModalComponent } from './moderation-comment-modal.component' -import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module' @NgModule({ imports: [ @@ -21,7 +22,8 @@ import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image SharedModerationModule, SharedGlobalIconModule, SharedVideoCommentModule, - SharedActorImageModule + SharedActorImageModule, + SharedTablesModule ], declarations: [ diff --git a/client/src/app/shared/shared-main/shared-main.module.ts b/client/src/app/shared/shared-main/shared-main.module.ts index 93989780d..a90b59e41 100644 --- a/client/src/app/shared/shared-main/shared-main.module.ts +++ b/client/src/app/shared/shared-main/shared-main.module.ts @@ -43,13 +43,8 @@ import { } from './misc' import { PluginPlaceholderComponent } from './plugins' import { ActorRedirectGuard } from './router' -import { - UserHistoryService, - UserNotificationsComponent, - UserNotificationService, - UserQuotaComponent -} from './users' -import { RedundancyService, VideoImportService, VideoOwnershipService, VideoService } from './video' +import { UserHistoryService, UserNotificationsComponent, UserNotificationService, UserQuotaComponent } from './users' +import { EmbedComponent, RedundancyService, VideoImportService, VideoOwnershipService, VideoService } from './video' import { VideoCaptionService } from './video-caption' import { VideoChannelService } from './video-channel' @@ -111,6 +106,8 @@ import { VideoChannelService } from './video-channel' UserQuotaComponent, UserNotificationsComponent, + EmbedComponent, + PluginPlaceholderComponent ], @@ -167,6 +164,8 @@ import { VideoChannelService } from './video-channel' UserQuotaComponent, UserNotificationsComponent, + EmbedComponent, + PluginPlaceholderComponent ], diff --git a/client/src/app/shared/shared-main/video/embed.component.html b/client/src/app/shared/shared-main/video/embed.component.html new file mode 100644 index 000000000..3b088d058 --- /dev/null +++ b/client/src/app/shared/shared-main/video/embed.component.html @@ -0,0 +1,3 @@ +
+
+
diff --git a/client/src/app/shared/shared-main/video/embed.component.scss b/client/src/app/shared/shared-main/video/embed.component.scss new file mode 100644 index 000000000..420ba6f23 --- /dev/null +++ b/client/src/app/shared/shared-main/video/embed.component.scss @@ -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; + }; +} diff --git a/client/src/app/shared/shared-main/video/embed.component.ts b/client/src/app/shared/shared-main/video/embed.component.ts new file mode 100644 index 000000000..4732efa44 --- /dev/null +++ b/client/src/app/shared/shared-main/video/embed.component.ts @@ -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 + + 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) + } +} diff --git a/client/src/app/shared/shared-main/video/index.ts b/client/src/app/shared/shared-main/video/index.ts index 3053df4ef..e72c0c3d6 100644 --- a/client/src/app/shared/shared-main/video/index.ts +++ b/client/src/app/shared/shared-main/video/index.ts @@ -1,3 +1,4 @@ +export * from './embed.component' export * from './redundancy.service' export * from './video-details.model' export * from './video-edit.model' diff --git a/client/src/app/shared/shared-main/video/video.service.ts b/client/src/app/shared/shared-main/video/video.service.ts index 7935569e7..9e3aa1e6a 100644 --- a/client/src/app/shared/shared-main/video/video.service.ts +++ b/client/src/app/shared/shared-main/video/video.service.ts @@ -1,8 +1,9 @@ -import { Observable } from 'rxjs' -import { catchError, map, switchMap } from 'rxjs/operators' +import { SortMeta } from 'primeng/api' +import { from, Observable } from 'rxjs' +import { catchError, concatMap, map, switchMap, toArray } from 'rxjs/operators' import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http' 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 { BooleanBothQuery, @@ -31,8 +32,8 @@ import { VideoEdit } from './video-edit.model' import { Video } from './video.model' export type CommonVideoParams = { - videoPagination: ComponentPaginationLight - sort: VideoSortField + videoPagination?: ComponentPaginationLight + sort: VideoSortField | SortMeta filter?: VideoFilter categoryOneOf?: number[] languageOneOf?: string[] @@ -200,6 +201,31 @@ export class VideoService { ) } + getAdminVideos ( + parameters: Omit & { pagination: RestPagination, search?: string } + ): Observable> { + 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>(VideoService.BASE_VIDEO_URL, { params }) + .pipe( + switchMap(res => this.extractVideos(res)), + catchError(err => this.restExtractor.handleError(err)) + ) + } + getVideos (parameters: CommonVideoParams): Observable> { let params = new HttpParams() params = this.buildCommonVideosParams({ params, ...parameters }) @@ -284,13 +310,15 @@ export class VideoService { ) } - removeVideo (id: number) { - return this.authHttp - .delete(VideoService.BASE_VIDEO_URL + id) - .pipe( - map(this.restExtractor.extractDataBool), - catchError(err => this.restExtractor.handleError(err)) - ) + removeVideo (idArg: number | number[]) { + const ids = Array.isArray(idArg) ? idArg : [ idArg ] + + return from(ids) + .pipe( + concatMap(id => this.authHttp.delete(VideoService.BASE_VIDEO_URL + id)), + toArray(), + catchError(err => this.restExtractor.handleError(err)) + ) } loadCompleteDescription (descriptionPath: string) { @@ -393,9 +421,23 @@ export class VideoService { } 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) if (filter) newParams = newParams.set('filter', filter) @@ -409,4 +451,19 @@ export class VideoService { 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) + } } diff --git a/client/src/app/shared/shared-moderation/moderation.scss b/client/src/app/shared/shared-moderation/moderation.scss index 815e2791f..eaf5a8250 100644 --- a/client/src/app/shared/shared-moderation/moderation.scss +++ b/client/src/app/shared/shared-moderation/moderation.scss @@ -40,14 +40,6 @@ } } -.screenratio { - @include block-ratio($selector: 'div, ::ng-deep iframe') { - width: 100% !important; - height: 100% !important; - left: 0; - }; -} - .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-account-link { @include disable-outline; @@ -81,68 +66,6 @@ my-action-dropdown.show { 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 { width: 100%; } diff --git a/client/src/app/shared/shared-moderation/report-modals/report.component.scss b/client/src/app/shared/shared-moderation/report-modals/report.component.scss index 06e50ac2d..76ec0a6ed 100644 --- a/client/src/app/shared/shared-moderation/report-modals/report.component.scss +++ b/client/src/app/shared/shared-moderation/report-modals/report.component.scss @@ -19,9 +19,3 @@ textarea { @include margin-left(10px); } } - -.screenratio { - @include block-ratio($selector: 'div, ::ng-deep iframe') { - left: 0; - }; -} diff --git a/client/src/app/shared/shared-moderation/report-modals/video-report.component.html b/client/src/app/shared/shared-moderation/report-modals/video-report.component.html index 1aae64bff..afac108fc 100644 --- a/client/src/app/shared/shared-moderation/report-modals/video-report.component.html +++ b/client/src/app/shared/shared-moderation/report-modals/video-report.component.html @@ -35,9 +35,7 @@
-
-
-
+
diff --git a/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts index 278d60ac6..38dd92910 100644 --- a/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts +++ b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts @@ -1,13 +1,11 @@ import { mapValues, pickBy } from 'lodash-es' -import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils' 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 { ABUSE_REASON_VALIDATOR } from '@app/shared/form-validators/abuse-validators' import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' -import { decorateVideoLink } from '@shared/core-utils' import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse' import { AbusePredefinedReasonsString } from '@shared/models' import { Video } from '../../shared-main' @@ -25,7 +23,6 @@ export class VideoReportComponent extends FormReactive implements OnInit { error: string = null predefinedReasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = [] - embedHtml: SafeHtml private openedModal: NgbModalRef @@ -55,20 +52,6 @@ export class VideoReportComponent extends FormReactive implements OnInit { 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 () { this.buildForm({ reason: ABUSE_REASON_VALIDATOR, @@ -82,8 +65,6 @@ export class VideoReportComponent extends FormReactive implements OnInit { }) this.predefinedReasons = this.abuseService.getPrefefinedReasons('video') - - this.embedHtml = this.getVideoEmbed() } show () { diff --git a/client/src/app/shared/shared-tables/index.ts b/client/src/app/shared/shared-tables/index.ts new file mode 100644 index 000000000..e7b593257 --- /dev/null +++ b/client/src/app/shared/shared-tables/index.ts @@ -0,0 +1,3 @@ +export * from './table-expander-icon.component' +export * from './video-cell.component' +export * from './shared-tables.module' diff --git a/client/src/app/shared/shared-tables/shared-tables.module.ts b/client/src/app/shared/shared-tables/shared-tables.module.ts new file mode 100644 index 000000000..c528365a0 --- /dev/null +++ b/client/src/app/shared/shared-tables/shared-tables.module.ts @@ -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 { } diff --git a/client/src/app/shared/shared-tables/table-expander-icon.component.ts b/client/src/app/shared/shared-tables/table-expander-icon.component.ts new file mode 100644 index 000000000..3756b475a --- /dev/null +++ b/client/src/app/shared/shared-tables/table-expander-icon.component.ts @@ -0,0 +1,12 @@ +import { Component, Input } from '@angular/core' + +@Component({ + selector: 'my-table-expander-icon', + template: ` + + +` +}) +export class TableExpanderIconComponent { + @Input() expanded: boolean +} diff --git a/client/src/app/shared/shared-tables/video-cell.component.html b/client/src/app/shared/shared-tables/video-cell.component.html new file mode 100644 index 000000000..fb7d852ab --- /dev/null +++ b/client/src/app/shared/shared-tables/video-cell.component.html @@ -0,0 +1,19 @@ + +
+
+ + + +
+ +
+
+ + + {{ video.name }} +
+ +
by {{ video.channel?.displayName }} on {{ video.channel?.host }}
+
+
+
diff --git a/client/src/app/shared/shared-tables/video-cell.component.scss b/client/src/app/shared/shared-tables/video-cell.component.scss new file mode 100644 index 000000000..7efb61502 --- /dev/null +++ b/client/src/app/shared/shared-tables/video-cell.component.scss @@ -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; + } + } +} diff --git a/client/src/app/shared/shared-tables/video-cell.component.ts b/client/src/app/shared/shared-tables/video-cell.component.ts new file mode 100644 index 000000000..62984180d --- /dev/null +++ b/client/src/app/shared/shared-tables/video-cell.component.ts @@ -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) + } +}