parent
6504b3bfd9
commit
b33a5b057f
|
@ -34,6 +34,7 @@
|
|||
</div>
|
||||
|
||||
<my-videos-selection
|
||||
[videosContainedInPlaylists]="videosContainedInPlaylists"
|
||||
[pagination]="pagination"
|
||||
[(selection)]="selection"
|
||||
[(videosModel)]="videos"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { concat, Observable } from 'rxjs'
|
||||
import { tap, toArray } from 'rxjs/operators'
|
||||
import { switchMap, tap, toArray } from 'rxjs/operators'
|
||||
import { uniqBy } from 'lodash'
|
||||
import { Component, OnInit, ViewChild } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { AuthService, ComponentPagination, ConfirmService, Notifier, ScreenService, ServerService, User } from '@app/core'
|
||||
|
@ -14,8 +15,9 @@ import {
|
|||
VideoActionsDisplayType,
|
||||
VideosSelectionComponent
|
||||
} from '@app/shared/shared-video-miniature'
|
||||
import { VideoChannel, VideoSortField } from '@shared/models'
|
||||
import { VideoChannel, VideoExistInPlaylist, VideosExistInPlaylists, VideoSortField } from '@shared/models'
|
||||
import { VideoChangeOwnershipComponent } from './modals/video-change-ownership.component'
|
||||
import { VideoPlaylistService } from '@app/shared/shared-video-playlist'
|
||||
|
||||
@Component({
|
||||
templateUrl: './my-videos.component.html',
|
||||
|
@ -26,6 +28,7 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
|
|||
@ViewChild('videoChangeOwnershipModal', { static: true }) videoChangeOwnershipModal: VideoChangeOwnershipComponent
|
||||
@ViewChild('liveStreamInformationModal', { static: true }) liveStreamInformationModal: LiveStreamInformationComponent
|
||||
|
||||
videosContainedInPlaylists: VideosExistInPlaylists
|
||||
titlePage: string
|
||||
selection: SelectionType = {}
|
||||
pagination: ComponentPagination = {
|
||||
|
@ -34,6 +37,7 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
|
|||
totalItems: null
|
||||
}
|
||||
miniatureDisplayOptions: MiniatureDisplayOptions = {
|
||||
containedInPlaylists: true,
|
||||
date: true,
|
||||
views: true,
|
||||
by: true,
|
||||
|
@ -82,7 +86,8 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
|
|||
protected notifier: Notifier,
|
||||
protected screenService: ScreenService,
|
||||
private confirmService: ConfirmService,
|
||||
private videoService: VideoService
|
||||
private videoService: VideoService,
|
||||
private playlistService: VideoPlaylistService
|
||||
) {
|
||||
this.titlePage = $localize`My videos`
|
||||
}
|
||||
|
@ -96,6 +101,15 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
|
|||
this.search = this.route.snapshot.queryParams['search']
|
||||
}
|
||||
|
||||
this.getVideosObservable(this.pagination.currentPage)
|
||||
.pipe(switchMap(({ data }) => this.playlistService.listPlaylistsForVideos(data.map(v => v.id))))
|
||||
.subscribe(result => {
|
||||
this.videosContainedInPlaylists = Object.keys(result).reduce((acc, videoId) => ({
|
||||
...acc,
|
||||
[videoId]: uniqBy(result[videoId], (p: VideoExistInPlaylist) => p.playlistId)
|
||||
}), {})
|
||||
})
|
||||
|
||||
this.authService.userInformationLoaded.subscribe(() => {
|
||||
this.user = this.authService.getUser()
|
||||
this.userChannels = this.user.videoChannels
|
||||
|
|
|
@ -52,6 +52,12 @@
|
|||
<ng-container *ngIf="displayOptions.privacyText && displayOptions.state && getStateLabel(video)"> - </ng-container>
|
||||
<ng-container *ngIf="displayOptions.state">{{ getStateLabel(video) }}</ng-container>
|
||||
</div>
|
||||
|
||||
<div *ngIf="displayOptions.containedInPlaylists" class="video-contained-in-playlists">
|
||||
<a *ngFor="let playlist of containedInPlaylists" class="chip rectangular bg-secondary text-light" [routerLink]="['/w/p/', playlist.playlistShortUUID]" i18n>
|
||||
{{ playlist.playlistDisplayName }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
|
||||
$more-button-width: 40px;
|
||||
|
||||
.chip {
|
||||
@include chip;
|
||||
}
|
||||
|
||||
.video-miniature {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
Output
|
||||
} from '@angular/core'
|
||||
import { AuthService, ScreenService, ServerService, User } from '@app/core'
|
||||
import { HTMLServerConfig, VideoPlaylistType, VideoPrivacy, VideoState } from '@shared/models'
|
||||
import { HTMLServerConfig, VideoExistInPlaylist, VideoPlaylistType, VideoPrivacy, VideoState } from '@shared/models'
|
||||
import { LinkType } from '../../../types/link.type'
|
||||
import { ActorAvatarSize } from '../shared-actor-image/actor-avatar.component'
|
||||
import { Video } from '../shared-main'
|
||||
|
@ -19,6 +19,7 @@ import { VideoPlaylistService } from '../shared-video-playlist'
|
|||
import { VideoActionsDisplayType } from './video-actions-dropdown.component'
|
||||
|
||||
export type MiniatureDisplayOptions = {
|
||||
containedInPlaylists?: boolean
|
||||
date?: boolean
|
||||
views?: boolean
|
||||
by?: boolean
|
||||
|
@ -38,8 +39,10 @@ export type MiniatureDisplayOptions = {
|
|||
export class VideoMiniatureComponent implements OnInit {
|
||||
@Input() user: User
|
||||
@Input() video: Video
|
||||
@Input() containedInPlaylists: VideoExistInPlaylist[]
|
||||
|
||||
@Input() displayOptions: MiniatureDisplayOptions = {
|
||||
containedInPlaylists: false,
|
||||
date: true,
|
||||
views: true,
|
||||
by: true,
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
</div>
|
||||
|
||||
<my-video-miniature
|
||||
[containedInPlaylists]="videosContainedInPlaylists ? videosContainedInPlaylists[video.id] : undefined"
|
||||
[video]="video" [displayAsRow]="true" [displayOptions]="miniatureDisplayOptions"
|
||||
[displayVideoActions]="false" [user]="user"
|
||||
></my-video-miniature>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Observable, Subject } from 'rxjs'
|
||||
import { AfterContentInit, Component, ContentChildren, EventEmitter, Input, Output, QueryList, TemplateRef } from '@angular/core'
|
||||
import { ComponentPagination, Notifier, User } from '@app/core'
|
||||
import { ResultList, VideoSortField } from '@shared/models'
|
||||
import { ResultList, VideosExistInPlaylists, VideoSortField } from '@shared/models'
|
||||
import { PeerTubeTemplateDirective, Video } from '../shared-main'
|
||||
import { MiniatureDisplayOptions } from './video-miniature.component'
|
||||
|
||||
|
@ -13,6 +13,7 @@ export type SelectionType = { [ id: number ]: boolean }
|
|||
styleUrls: [ './videos-selection.component.scss' ]
|
||||
})
|
||||
export class VideosSelectionComponent implements AfterContentInit {
|
||||
@Input() videosContainedInPlaylists: VideosExistInPlaylists
|
||||
@Input() user: User
|
||||
@Input() pagination: ComponentPagination
|
||||
|
||||
|
|
|
@ -75,6 +75,10 @@ export class VideoPlaylistService {
|
|||
)
|
||||
}
|
||||
|
||||
listPlaylistsForVideos (videoIds: number[]) {
|
||||
return this.doVideosExistInPlaylist(videoIds)
|
||||
}
|
||||
|
||||
listMyPlaylistWithCache (user: AuthUser, search?: string) {
|
||||
if (!search) {
|
||||
if (this.myAccountPlaylistCacheRunning) return this.myAccountPlaylistCacheRunning
|
||||
|
@ -188,14 +192,6 @@ export class VideoPlaylistService {
|
|||
return this.authHttp.post<{ videoPlaylistElement: { id: number } }>(url, body)
|
||||
.pipe(
|
||||
tap(res => {
|
||||
const existsResult = this.videoExistsCache[body.videoId]
|
||||
existsResult.push({
|
||||
playlistId,
|
||||
playlistElementId: res.videoPlaylistElement.id,
|
||||
startTimestamp: body.startTimestamp,
|
||||
stopTimestamp: body.stopTimestamp
|
||||
})
|
||||
|
||||
this.runPlaylistCheck(body.videoId)
|
||||
}),
|
||||
catchError(err => this.restExtractor.handleError(err))
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { uuidToShort } from '@shared/extra-utils'
|
||||
import express from 'express'
|
||||
import { VideosExistInPlaylists } from '../../../../shared/models/videos/playlist/video-exist-in-playlist.model'
|
||||
import { asyncMiddleware, authenticate } from '../../../middlewares'
|
||||
|
@ -24,7 +25,7 @@ async function doVideosInPlaylistExist (req: express.Request, res: express.Respo
|
|||
const videoIds = req.query.videoIds.map(i => parseInt(i + '', 10))
|
||||
const user = res.locals.oauth.token.User
|
||||
|
||||
const results = await VideoPlaylistModel.listPlaylistIdsOf(user.Account.id, videoIds)
|
||||
const results = await VideoPlaylistModel.listPlaylistSummariesOf(user.Account.id, videoIds)
|
||||
|
||||
const existObject: VideosExistInPlaylists = {}
|
||||
|
||||
|
@ -37,6 +38,8 @@ async function doVideosInPlaylistExist (req: express.Request, res: express.Respo
|
|||
existObject[element.videoId].push({
|
||||
playlistElementId: element.id,
|
||||
playlistId: result.id,
|
||||
playlistDisplayName: result.name,
|
||||
playlistShortUUID: uuidToShort(result.uuid),
|
||||
startTimestamp: element.startTimestamp,
|
||||
stopTimestamp: element.stopTimestamp
|
||||
})
|
||||
|
|
|
@ -49,7 +49,7 @@ import {
|
|||
MVideoPlaylistFormattable,
|
||||
MVideoPlaylistFull,
|
||||
MVideoPlaylistFullSummary,
|
||||
MVideoPlaylistIdWithElements
|
||||
MVideoPlaylistSummaryWithElements
|
||||
} from '../../types/models/video/video-playlist'
|
||||
import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions } from '../account/account'
|
||||
import { ActorModel } from '../actor/actor'
|
||||
|
@ -470,9 +470,9 @@ export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlayli
|
|||
}))
|
||||
}
|
||||
|
||||
static listPlaylistIdsOf (accountId: number, videoIds: number[]): Promise<MVideoPlaylistIdWithElements[]> {
|
||||
static listPlaylistSummariesOf (accountId: number, videoIds: number[]): Promise<MVideoPlaylistSummaryWithElements[]> {
|
||||
const query = {
|
||||
attributes: [ 'id' ],
|
||||
attributes: [ 'id', 'name', 'uuid' ],
|
||||
where: {
|
||||
ownerAccountId: accountId
|
||||
},
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
setDefaultVideoChannel,
|
||||
waitJobs
|
||||
} from '@shared/server-commands'
|
||||
import { uuidToShort } from '@shared/extra-utils'
|
||||
|
||||
const expect = chai.expect
|
||||
|
||||
|
@ -59,6 +60,7 @@ describe('Test video playlists', function () {
|
|||
let playlistServer2UUID2: string
|
||||
|
||||
let playlistServer1Id: number
|
||||
let playlistServer1DisplayName: string
|
||||
let playlistServer1UUID: string
|
||||
let playlistServer1UUID2: string
|
||||
|
||||
|
@ -492,15 +494,17 @@ describe('Test video playlists', function () {
|
|||
return commands[0].addElement({ playlistId: playlistServer1Id, attributes })
|
||||
}
|
||||
|
||||
const playlistDisplayName = 'playlist 4'
|
||||
const playlist = await commands[0].create({
|
||||
attributes: {
|
||||
displayName: 'playlist 4',
|
||||
displayName: playlistDisplayName,
|
||||
privacy: VideoPlaylistPrivacy.PUBLIC,
|
||||
videoChannelId: servers[0].store.channel.id
|
||||
}
|
||||
})
|
||||
|
||||
playlistServer1Id = playlist.id
|
||||
playlistServer1DisplayName = playlistDisplayName
|
||||
playlistServer1UUID = playlist.uuid
|
||||
|
||||
await addVideo({ videoId: servers[0].store.videos[0].uuid, startTimestamp: 15, stopTimestamp: 28 })
|
||||
|
@ -911,6 +915,8 @@ describe('Test video playlists', function () {
|
|||
const elem = obj[servers[0].store.videos[0].id]
|
||||
expect(elem).to.have.lengthOf(1)
|
||||
expect(elem[0].playlistElementId).to.exist
|
||||
expect(elem[0].playlistDisplayName).to.equal(playlistServer1DisplayName)
|
||||
expect(elem[0].playlistShortUUID).to.equal(uuidToShort(playlistServer1UUID))
|
||||
expect(elem[0].playlistId).to.equal(playlistServer1Id)
|
||||
expect(elem[0].startTimestamp).to.equal(15)
|
||||
expect(elem[0].stopTimestamp).to.equal(28)
|
||||
|
@ -920,6 +926,8 @@ describe('Test video playlists', function () {
|
|||
const elem = obj[servers[0].store.videos[3].id]
|
||||
expect(elem).to.have.lengthOf(1)
|
||||
expect(elem[0].playlistElementId).to.equal(playlistElementServer1Video4)
|
||||
expect(elem[0].playlistDisplayName).to.equal(playlistServer1DisplayName)
|
||||
expect(elem[0].playlistShortUUID).to.equal(uuidToShort(playlistServer1UUID))
|
||||
expect(elem[0].playlistId).to.equal(playlistServer1Id)
|
||||
expect(elem[0].startTimestamp).to.equal(1)
|
||||
expect(elem[0].stopTimestamp).to.equal(35)
|
||||
|
@ -929,6 +937,8 @@ describe('Test video playlists', function () {
|
|||
const elem = obj[servers[0].store.videos[4].id]
|
||||
expect(elem).to.have.lengthOf(1)
|
||||
expect(elem[0].playlistId).to.equal(playlistServer1Id)
|
||||
expect(elem[0].playlistDisplayName).to.equal(playlistServer1DisplayName)
|
||||
expect(elem[0].playlistShortUUID).to.equal(uuidToShort(playlistServer1UUID))
|
||||
expect(elem[0].startTimestamp).to.equal(45)
|
||||
expect(elem[0].stopTimestamp).to.equal(null)
|
||||
}
|
||||
|
|
|
@ -14,6 +14,10 @@ export type MVideoPlaylist = Omit<VideoPlaylistModel, 'OwnerAccount' | 'VideoCha
|
|||
// ############################################################################
|
||||
|
||||
export type MVideoPlaylistId = Pick<MVideoPlaylist, 'id'>
|
||||
export type MVideoPlaylistSummary =
|
||||
Pick<MVideoPlaylist, 'id'> &
|
||||
Pick<MVideoPlaylist, 'name'> &
|
||||
Pick<MVideoPlaylist, 'uuid'>
|
||||
export type MVideoPlaylistPrivacy = Pick<MVideoPlaylist, 'privacy'>
|
||||
export type MVideoPlaylistUUID = Pick<MVideoPlaylist, 'uuid'>
|
||||
export type MVideoPlaylistVideosLength = MVideoPlaylist & { videosLength?: number }
|
||||
|
@ -26,6 +30,10 @@ export type MVideoPlaylistWithElements =
|
|||
MVideoPlaylist &
|
||||
Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]>
|
||||
|
||||
export type MVideoPlaylistSummaryWithElements =
|
||||
MVideoPlaylistSummary &
|
||||
Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]>
|
||||
|
||||
export type MVideoPlaylistIdWithElements =
|
||||
MVideoPlaylistId &
|
||||
Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]>
|
||||
|
|
|
@ -5,6 +5,8 @@ export type VideosExistInPlaylists = {
|
|||
export type VideoExistInPlaylist = {
|
||||
playlistElementId: number
|
||||
playlistId: number
|
||||
playlistDisplayName: string
|
||||
playlistShortUUID: string
|
||||
startTimestamp?: number
|
||||
stopTimestamp?: number
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user