Add miniature quick actions to add video to Watch later playlist
This commit is contained in:
parent
cca1e13b96
commit
29128b2f5c
|
@ -5,6 +5,7 @@ import { User as ServerUserModel } from '../../../../../shared/models/users/user
|
||||||
import { hasUserRight, UserRole } from '../../../../../shared/models/users/user-role'
|
import { hasUserRight, UserRole } from '../../../../../shared/models/users/user-role'
|
||||||
import { User } from '../../shared/users/user.model'
|
import { User } from '../../shared/users/user.model'
|
||||||
import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type'
|
import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type'
|
||||||
|
import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
|
||||||
|
|
||||||
export type TokenOptions = {
|
export type TokenOptions = {
|
||||||
accessToken: string
|
accessToken: string
|
||||||
|
@ -79,6 +80,7 @@ export class AuthUser extends User {
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens: Tokens
|
tokens: Tokens
|
||||||
|
specialPlaylists: Partial<VideoPlaylist>[]
|
||||||
|
|
||||||
static load () {
|
static load () {
|
||||||
const usernameLocalStorage = peertubeLocalStorage.getItem(this.KEYS.USERNAME)
|
const usernameLocalStorage = peertubeLocalStorage.getItem(this.KEYS.USERNAME)
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { Router } from '@angular/router'
|
import { Router } from '@angular/router'
|
||||||
import { Notifier } from '@app/core/notification/notifier.service'
|
import { Notifier } from '@app/core/notification/notifier.service'
|
||||||
import { OAuthClientLocal, User as UserServerModel, UserRefreshToken } from '../../../../../shared'
|
import { OAuthClientLocal, MyUser as UserServerModel, UserRefreshToken } from '../../../../../shared'
|
||||||
import { User } from '../../../../../shared/models/users'
|
import { User } from '../../../../../shared/models/users'
|
||||||
import { UserLogin } from '../../../../../shared/models/users/user-login.model'
|
import { UserLogin } from '../../../../../shared/models/users/user-login.model'
|
||||||
import { environment } from '../../../environments/environment'
|
import { environment } from '../../../environments/environment'
|
||||||
|
|
|
@ -28,13 +28,11 @@ export class LoginComponent extends FormReactive implements OnInit {
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
protected formValidatorService: FormValidatorService,
|
protected formValidatorService: FormValidatorService,
|
||||||
private router: Router,
|
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private modalService: NgbModal,
|
private modalService: NgbModal,
|
||||||
private loginValidatorsService: LoginValidatorsService,
|
private loginValidatorsService: LoginValidatorsService,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
private serverService: ServerService,
|
|
||||||
private redirectService: RedirectService,
|
private redirectService: RedirectService,
|
||||||
private notifier: Notifier,
|
private notifier: Notifier,
|
||||||
private i18n: I18n
|
private i18n: I18n
|
||||||
|
|
|
@ -10,6 +10,7 @@ const icons = {
|
||||||
'sparkle': require('!!raw-loader?!../../../assets/images/global/sparkle.svg'),
|
'sparkle': require('!!raw-loader?!../../../assets/images/global/sparkle.svg'),
|
||||||
'alert': require('!!raw-loader?!../../../assets/images/global/alert.svg'),
|
'alert': require('!!raw-loader?!../../../assets/images/global/alert.svg'),
|
||||||
'cloud-error': require('!!raw-loader?!../../../assets/images/global/cloud-error.svg'),
|
'cloud-error': require('!!raw-loader?!../../../assets/images/global/cloud-error.svg'),
|
||||||
|
'clock': require('!!raw-loader?!../../../assets/images/global/clock.svg'),
|
||||||
'user-add': require('!!raw-loader?!../../../assets/images/global/user-add.svg'),
|
'user-add': require('!!raw-loader?!../../../assets/images/global/user-add.svg'),
|
||||||
'no': require('!!raw-loader?!../../../assets/images/global/no.svg'),
|
'no': require('!!raw-loader?!../../../assets/images/global/no.svg'),
|
||||||
'cloud-download': require('!!raw-loader?!../../../assets/images/global/cloud-download.svg'),
|
'cloud-download': require('!!raw-loader?!../../../assets/images/global/cloud-download.svg'),
|
||||||
|
|
|
@ -30,6 +30,7 @@ export class VideoPlaylistService {
|
||||||
// Use a replay subject because we "next" a value before subscribing
|
// Use a replay subject because we "next" a value before subscribing
|
||||||
private videoExistsInPlaylistSubject: Subject<number> = new ReplaySubject(1)
|
private videoExistsInPlaylistSubject: Subject<number> = new ReplaySubject(1)
|
||||||
private readonly videoExistsInPlaylistObservable: Observable<VideoExistInPlaylist>
|
private readonly videoExistsInPlaylistObservable: Observable<VideoExistInPlaylist>
|
||||||
|
private cachedWatchLaterPlaylists: VideoPlaylist[]
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private authHttp: HttpClient,
|
private authHttp: HttpClient,
|
||||||
|
|
|
@ -1,9 +1,23 @@
|
||||||
<a
|
<a
|
||||||
[routerLink]="getVideoRouterLink()" [queryParams]="queryParams" [attr.title]="video.name"
|
[routerLink]="getVideoRouterLink()" [queryParams]="queryParams" [attr.title]="video.name"
|
||||||
class="video-thumbnail"
|
class="video-thumbnail"
|
||||||
|
(mouseenter)="load()"
|
||||||
>
|
>
|
||||||
<img alt="" [attr.aria-labelledby]="video.name" [attr.src]="getImageUrl()" [ngClass]="{ 'blur-filter': nsfw }" />
|
<img alt="" [attr.aria-labelledby]="video.name" [attr.src]="getImageUrl()" [ngClass]="{ 'blur-filter': nsfw }" />
|
||||||
|
|
||||||
|
<div *ngIf="isUserLoggedIn()" class="video-thumbnail-actions-overlay">
|
||||||
|
<ng-container *ngIf="addedToWatchLater !== true">
|
||||||
|
<div class="video-thumbnail-watch-later-overlay" placement="left" [ngbTooltip]="addToWatchLaterText" container="body" (click)="addToWatchLater();$event.stopPropagation();false">
|
||||||
|
<my-global-icon iconName="clock" [attr.aria-label]="addToWatchLaterText" role="button"></my-global-icon>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="addedToWatchLater === true">
|
||||||
|
<div class="video-thumbnail-watch-later-overlay" placement="left" [ngbTooltip]="addedToWatchLaterText" container="body" (click)="removeFromWatchLater();$event.stopPropagation();false">
|
||||||
|
<my-global-icon iconName="tick" [attr.aria-label]="addedToWatchLaterText" role="button"></my-global-icon>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="video-thumbnail-duration-overlay">{{ video.durationLabel }}</div>
|
<div class="video-thumbnail-duration-overlay">{{ video.durationLabel }}</div>
|
||||||
|
|
||||||
<div class="play-overlay">
|
<div class="play-overlay">
|
||||||
|
|
|
@ -18,16 +18,50 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.video-thumbnail-watch-later-overlay,
|
||||||
.video-thumbnail-duration-overlay {
|
.video-thumbnail-duration-overlay {
|
||||||
@include static-thumbnail-overlay;
|
@include static-thumbnail-overlay;
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
right: 5px;
|
|
||||||
bottom: 5px;
|
|
||||||
padding: 0 5px;
|
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: $font-bold;
|
font-weight: $font-bold;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.video-thumbnail-duration-overlay {
|
||||||
|
position: absolute;
|
||||||
|
padding: 0 5px;
|
||||||
|
right: 5px;
|
||||||
|
bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.video-thumbnail-actions-overlay {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-thumbnail-actions-overlay {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
right: 5px;
|
||||||
|
top: 5px;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
div:not(:first-child) {
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-thumbnail-watch-later-overlay {
|
||||||
|
padding: 3px;
|
||||||
|
|
||||||
|
my-global-icon {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
|
||||||
|
@include apply-svg-color(#fff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
import { Component, Input } from '@angular/core'
|
import { Component, Input, OnInit, ChangeDetectorRef } from '@angular/core'
|
||||||
import { Video } from './video.model'
|
import { Video } from './video.model'
|
||||||
import { ScreenService } from '@app/shared/misc/screen.service'
|
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||||
|
import { AuthService, ThemeService } from '@app/core'
|
||||||
|
import { VideoPlaylistService } from '../video-playlist/video-playlist.service'
|
||||||
|
import { VideoPlaylistType } from '@shared/models'
|
||||||
|
import { forkJoin } from 'rxjs'
|
||||||
|
import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model'
|
||||||
|
import { VideoPlaylist } from '../video-playlist/video-playlist.model'
|
||||||
|
import { VideoPlaylistElementCreate } from '../../../../../shared'
|
||||||
|
import { VideoExistInPlaylist } from '@shared/models/videos/playlist/video-exist-in-playlist.model'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-video-thumbnail',
|
selector: 'my-video-thumbnail',
|
||||||
|
@ -13,7 +21,44 @@ export class VideoThumbnailComponent {
|
||||||
@Input() routerLink: any[]
|
@Input() routerLink: any[]
|
||||||
@Input() queryParams: any[]
|
@Input() queryParams: any[]
|
||||||
|
|
||||||
constructor (private screenService: ScreenService) {
|
addToWatchLaterText = 'Add to watch later'
|
||||||
|
addedToWatchLaterText = 'Added to watch later'
|
||||||
|
addedToWatchLater: boolean
|
||||||
|
|
||||||
|
watchLaterPlaylist: any
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private screenService: ScreenService,
|
||||||
|
private authService: AuthService,
|
||||||
|
private videoPlaylistService: VideoPlaylistService,
|
||||||
|
private cd: ChangeDetectorRef
|
||||||
|
) {}
|
||||||
|
|
||||||
|
load () {
|
||||||
|
if (this.addedToWatchLater !== undefined) return
|
||||||
|
|
||||||
|
this.videoPlaylistService.doesVideoExistInPlaylist(this.video.id)
|
||||||
|
.subscribe(
|
||||||
|
existResult => {
|
||||||
|
for (const playlist of this.authService.getUser().specialPlaylists) {
|
||||||
|
const existingPlaylist = existResult[ this.video.id ].find(p => p.playlistId === playlist.id)
|
||||||
|
this.addedToWatchLater = !!existingPlaylist
|
||||||
|
|
||||||
|
if (existingPlaylist) {
|
||||||
|
this.watchLaterPlaylist = {
|
||||||
|
playlistId: existingPlaylist.playlistId,
|
||||||
|
playlistElementId: existingPlaylist.playlistElementId
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.watchLaterPlaylist = {
|
||||||
|
playlistId: playlist.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cd.markForCheck()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
getImageUrl () {
|
getImageUrl () {
|
||||||
|
@ -39,4 +84,37 @@ export class VideoThumbnailComponent {
|
||||||
|
|
||||||
return [ '/videos/watch', this.video.uuid ]
|
return [ '/videos/watch', this.video.uuid ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isUserLoggedIn () {
|
||||||
|
return this.authService.isLoggedIn()
|
||||||
|
}
|
||||||
|
|
||||||
|
addToWatchLater () {
|
||||||
|
if (this.addedToWatchLater === undefined) return
|
||||||
|
this.addedToWatchLater = true
|
||||||
|
|
||||||
|
this.videoPlaylistService.addVideoInPlaylist(
|
||||||
|
this.watchLaterPlaylist.playlistId,
|
||||||
|
{ videoId: this.video.id } as VideoPlaylistElementCreate
|
||||||
|
).subscribe(
|
||||||
|
res => {
|
||||||
|
this.addedToWatchLater = true
|
||||||
|
this.watchLaterPlaylist.playlistElementId = res.videoPlaylistElement.id
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFromWatchLater () {
|
||||||
|
if (this.addedToWatchLater === undefined) return
|
||||||
|
this.addedToWatchLater = false
|
||||||
|
|
||||||
|
this.videoPlaylistService.removeVideoFromPlaylist(
|
||||||
|
this.watchLaterPlaylist.playlistId,
|
||||||
|
this.watchLaterPlaylist.playlistElementId
|
||||||
|
).subscribe(
|
||||||
|
_ => {
|
||||||
|
this.addedToWatchLater = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Artboard-4" transform="translate(-488.000000, -159.000000)" stroke="#000000" stroke-width="2">
|
||||||
|
<g id="31" transform="translate(488.000000, 159.000000)">
|
||||||
|
<path d="M12,21 C7.02943725,21 3,16.9705627 3,12 C3,7.02943725 7.02943725,3 12,3 C16.9705627,3 21,7.02943725 21,12 C21,16.9705627 16.9705627,21 12,21 Z" id="Base"/>
|
||||||
|
<path d="M12,12 L16,12" id="Path-18" stroke-linecap="round"/>
|
||||||
|
<path d="M12,12 L12,7" id="Path-40" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 813 B |
|
@ -128,7 +128,7 @@ async function getUserInformation (req: express.Request, res: express.Response)
|
||||||
// We did not load channels in res.locals.user
|
// We did not load channels in res.locals.user
|
||||||
const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
|
const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
|
||||||
|
|
||||||
return res.json(user.toFormattedJSON())
|
return res.json(user.toFormattedJSON({ me: true }))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getUserVideoQuotaUsed (req: express.Request, res: express.Response) {
|
async function getUserVideoQuotaUsed (req: express.Request, res: express.Response) {
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {
|
||||||
Table,
|
Table,
|
||||||
UpdatedAt
|
UpdatedAt
|
||||||
} from 'sequelize-typescript'
|
} from 'sequelize-typescript'
|
||||||
import { hasUserRight, USER_ROLE_LABELS, UserRight, VideoPrivacy } from '../../../shared'
|
import { hasUserRight, USER_ROLE_LABELS, UserRight, VideoPrivacy, MyUser } from '../../../shared'
|
||||||
import { User, UserRole } from '../../../shared/models/users'
|
import { User, UserRole } from '../../../shared/models/users'
|
||||||
import {
|
import {
|
||||||
isNoInstanceConfigWarningModal,
|
isNoInstanceConfigWarningModal,
|
||||||
|
@ -45,6 +45,7 @@ import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto'
|
||||||
import { OAuthTokenModel } from '../oauth/oauth-token'
|
import { OAuthTokenModel } from '../oauth/oauth-token'
|
||||||
import { getSort, throwIfNotValid } from '../utils'
|
import { getSort, throwIfNotValid } from '../utils'
|
||||||
import { VideoChannelModel } from '../video/video-channel'
|
import { VideoChannelModel } from '../video/video-channel'
|
||||||
|
import { VideoPlaylistModel } from '../video/video-playlist'
|
||||||
import { AccountModel } from './account'
|
import { AccountModel } from './account'
|
||||||
import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type'
|
import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type'
|
||||||
import { values } from 'lodash'
|
import { values } from 'lodash'
|
||||||
|
@ -68,7 +69,8 @@ import {
|
||||||
} from '@server/typings/models'
|
} from '@server/typings/models'
|
||||||
|
|
||||||
enum ScopeNames {
|
enum ScopeNames {
|
||||||
WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL'
|
WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL',
|
||||||
|
WITH_SPECIAL_PLAYLISTS = 'WITH_SPECIAL_PLAYLISTS'
|
||||||
}
|
}
|
||||||
|
|
||||||
@DefaultScope(() => ({
|
@DefaultScope(() => ({
|
||||||
|
@ -96,6 +98,16 @@ enum ScopeNames {
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
[ScopeNames.WITH_SPECIAL_PLAYLISTS]: {
|
||||||
|
attributes: {
|
||||||
|
include: [
|
||||||
|
[
|
||||||
|
literal('(select array(select "id" from "videoPlaylist" where "ownerAccountId" in (select id from public.account where "userId" = "UserModel"."id") and name LIKE \'Watch later\'))'),
|
||||||
|
'specialPlaylists'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@Table({
|
@Table({
|
||||||
|
@ -431,7 +443,10 @@ export class UserModel extends Model<UserModel> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return UserModel.scope(ScopeNames.WITH_VIDEO_CHANNEL).findOne(query)
|
return UserModel.scope([
|
||||||
|
ScopeNames.WITH_VIDEO_CHANNEL,
|
||||||
|
ScopeNames.WITH_SPECIAL_PLAYLISTS
|
||||||
|
]).findOne(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadByEmail (email: string): Bluebird<MUserDefault> {
|
static loadByEmail (email: string): Bluebird<MUserDefault> {
|
||||||
|
@ -610,11 +625,11 @@ export class UserModel extends Model<UserModel> {
|
||||||
return comparePassword(password, this.password)
|
return comparePassword(password, this.password)
|
||||||
}
|
}
|
||||||
|
|
||||||
toFormattedJSON (this: MUserFormattable, parameters: { withAdminFlags?: boolean } = {}): User {
|
toFormattedJSON (this: MUserFormattable, parameters: { withAdminFlags?: boolean, me?: boolean } = {}): User | MyUser {
|
||||||
const videoQuotaUsed = this.get('videoQuotaUsed')
|
const videoQuotaUsed = this.get('videoQuotaUsed')
|
||||||
const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily')
|
const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily')
|
||||||
|
|
||||||
const json: User = {
|
const json: User | MyUser = {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
username: this.username,
|
username: this.username,
|
||||||
email: this.email,
|
email: this.email,
|
||||||
|
@ -675,6 +690,12 @@ export class UserModel extends Model<UserModel> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parameters.me) {
|
||||||
|
Object.assign(json, {
|
||||||
|
specialPlaylists: (this.get('specialPlaylists') as Array<number>).map(p => ({ id: p }))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import * as chai from 'chai'
|
import * as chai from 'chai'
|
||||||
import 'mocha'
|
import 'mocha'
|
||||||
import { User, UserRole, Video } from '../../../../shared/index'
|
import { User, UserRole, Video, MyUser } from '../../../../shared/index'
|
||||||
import {
|
import {
|
||||||
blockUser,
|
blockUser,
|
||||||
cleanupTests,
|
cleanupTests,
|
||||||
|
@ -251,7 +251,7 @@ describe('Test users', function () {
|
||||||
|
|
||||||
it('Should be able to get user information', async function () {
|
it('Should be able to get user information', async function () {
|
||||||
const res1 = await getMyUserInformation(server.url, accessTokenUser)
|
const res1 = await getMyUserInformation(server.url, accessTokenUser)
|
||||||
const userMe: User = res1.body
|
const userMe: User & MyUser = res1.body
|
||||||
|
|
||||||
const res2 = await getUserInformation(server.url, server.accessToken, userMe.id)
|
const res2 = await getUserInformation(server.url, server.accessToken, userMe.id)
|
||||||
const userGet: User = res2.body
|
const userGet: User = res2.body
|
||||||
|
@ -269,6 +269,8 @@ describe('Test users', function () {
|
||||||
|
|
||||||
expect(userMe.adminFlags).to.be.undefined
|
expect(userMe.adminFlags).to.be.undefined
|
||||||
expect(userGet.adminFlags).to.equal(UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST)
|
expect(userGet.adminFlags).to.equal(UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST)
|
||||||
|
|
||||||
|
expect(userMe.specialPlaylists).to.have.lengthOf(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Account } from '../actors'
|
import { Account } from '../actors'
|
||||||
import { VideoChannel } from '../videos/channel/video-channel.model'
|
import { VideoChannel } from '../videos/channel/video-channel.model'
|
||||||
|
import { VideoPlaylist } from '../videos/playlist/video-playlist.model'
|
||||||
import { UserRole } from './user-role'
|
import { UserRole } from './user-role'
|
||||||
import { NSFWPolicyType } from '../videos/nsfw-policy.type'
|
import { NSFWPolicyType } from '../videos/nsfw-policy.type'
|
||||||
import { UserNotificationSetting } from './user-notification-setting.model'
|
import { UserNotificationSetting } from './user-notification-setting.model'
|
||||||
|
@ -45,3 +46,7 @@ export interface User {
|
||||||
|
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MyUser extends User {
|
||||||
|
specialPlaylists: Partial<VideoPlaylist>[]
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue