aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/core/auth/auth-user.model.ts13
-rw-r--r--client/src/app/shared/video-playlist/video-playlist.service.ts6
-rw-r--r--client/src/app/shared/video/video-miniature.component.html5
-rw-r--r--client/src/app/shared/video/video-miniature.component.ts98
-rw-r--r--client/src/app/shared/video/video-thumbnail.component.html14
-rw-r--r--client/src/app/shared/video/video-thumbnail.component.scss7
-rw-r--r--client/src/app/shared/video/video-thumbnail.component.ts85
-rw-r--r--client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts1
8 files changed, 127 insertions, 102 deletions
diff --git a/client/src/app/core/auth/auth-user.model.ts b/client/src/app/core/auth/auth-user.model.ts
index 55a5a6dde..0843743c9 100644
--- a/client/src/app/core/auth/auth-user.model.ts
+++ b/client/src/app/core/auth/auth-user.model.ts
@@ -1,11 +1,10 @@
1import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage' 1import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage'
2import { UserRight } from '../../../../../shared/models/users/user-right.enum' 2import { UserRight } from '../../../../../shared/models/users/user-right.enum'
3import { User as ServerUserModel } from '../../../../../shared/models/users/user.model' 3import { MyUser as ServerMyUserModel, MyUserSpecialPlaylist } from '../../../../../shared/models/users/user.model'
4// Do not use the barrel (dependency loop) 4// Do not use the barrel (dependency loop)
5import { hasUserRight, UserRole } from '../../../../../shared/models/users/user-role' 5import { hasUserRight, UserRole } from '../../../../../shared/models/users/user-role'
6import { User } from '../../shared/users/user.model' 6import { User } from '../../shared/users/user.model'
7import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type' 7import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type'
8import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
9 8
10export type TokenOptions = { 9export type TokenOptions = {
11 accessToken: string 10 accessToken: string
@@ -67,7 +66,7 @@ class Tokens {
67 } 66 }
68} 67}
69 68
70export class AuthUser extends User { 69export class AuthUser extends User implements ServerMyUserModel {
71 private static KEYS = { 70 private static KEYS = {
72 ID: 'id', 71 ID: 'id',
73 ROLE: 'role', 72 ROLE: 'role',
@@ -80,7 +79,7 @@ export class AuthUser extends User {
80 } 79 }
81 80
82 tokens: Tokens 81 tokens: Tokens
83 specialPlaylists: Partial<VideoPlaylist>[] 82 specialPlaylists: MyUserSpecialPlaylist[]
84 83
85 static load () { 84 static load () {
86 const usernameLocalStorage = peertubeLocalStorage.getItem(this.KEYS.USERNAME) 85 const usernameLocalStorage = peertubeLocalStorage.getItem(this.KEYS.USERNAME)
@@ -115,9 +114,11 @@ export class AuthUser extends User {
115 Tokens.flush() 114 Tokens.flush()
116 } 115 }
117 116
118 constructor (userHash: Partial<ServerUserModel>, hashTokens: TokenOptions) { 117 constructor (userHash: Partial<ServerMyUserModel>, hashTokens: TokenOptions) {
119 super(userHash) 118 super(userHash)
119
120 this.tokens = new Tokens(hashTokens) 120 this.tokens = new Tokens(hashTokens)
121 this.specialPlaylists = userHash.specialPlaylists
121 } 122 }
122 123
123 getAccessToken () { 124 getAccessToken () {
@@ -141,7 +142,7 @@ export class AuthUser extends User {
141 return hasUserRight(this.role, right) 142 return hasUserRight(this.role, right)
142 } 143 }
143 144
144 canManage (user: ServerUserModel) { 145 canManage (user: ServerMyUserModel) {
145 const myRole = this.role 146 const myRole = this.role
146 147
147 if (myRole === UserRole.ADMINISTRATOR) return true 148 if (myRole === UserRole.ADMINISTRATOR) return true
diff --git a/client/src/app/shared/video-playlist/video-playlist.service.ts b/client/src/app/shared/video-playlist/video-playlist.service.ts
index fc3b77b2a..d78fdc09f 100644
--- a/client/src/app/shared/video-playlist/video-playlist.service.ts
+++ b/client/src/app/shared/video-playlist/video-playlist.service.ts
@@ -1,4 +1,4 @@
1import { bufferTime, catchError, filter, first, map, share, switchMap } from 'rxjs/operators' 1import { bufferTime, catchError, distinctUntilChanged, filter, first, map, share, switchMap } from 'rxjs/operators'
2import { Injectable } from '@angular/core' 2import { Injectable } from '@angular/core'
3import { Observable, ReplaySubject, Subject } from 'rxjs' 3import { Observable, ReplaySubject, Subject } from 'rxjs'
4import { RestExtractor } from '../rest/rest-extractor.service' 4import { RestExtractor } from '../rest/rest-extractor.service'
@@ -30,7 +30,6 @@ export class VideoPlaylistService {
30 // Use a replay subject because we "next" a value before subscribing 30 // Use a replay subject because we "next" a value before subscribing
31 private videoExistsInPlaylistSubject: Subject<number> = new ReplaySubject(1) 31 private videoExistsInPlaylistSubject: Subject<number> = new ReplaySubject(1)
32 private readonly videoExistsInPlaylistObservable: Observable<VideoExistInPlaylist> 32 private readonly videoExistsInPlaylistObservable: Observable<VideoExistInPlaylist>
33 private cachedWatchLaterPlaylists: VideoPlaylist[]
34 33
35 constructor ( 34 constructor (
36 private authHttp: HttpClient, 35 private authHttp: HttpClient,
@@ -39,6 +38,7 @@ export class VideoPlaylistService {
39 private restService: RestService 38 private restService: RestService
40 ) { 39 ) {
41 this.videoExistsInPlaylistObservable = this.videoExistsInPlaylistSubject.pipe( 40 this.videoExistsInPlaylistObservable = this.videoExistsInPlaylistSubject.pipe(
41 distinctUntilChanged(),
42 bufferTime(500), 42 bufferTime(500),
43 filter(videoIds => videoIds.length !== 0), 43 filter(videoIds => videoIds.length !== 0),
44 switchMap(videoIds => this.doVideosExistInPlaylist(videoIds)), 44 switchMap(videoIds => this.doVideosExistInPlaylist(videoIds)),
@@ -224,7 +224,7 @@ export class VideoPlaylistService {
224 let params = new HttpParams() 224 let params = new HttpParams()
225 params = this.restService.addObjectParams(params, { videoIds }) 225 params = this.restService.addObjectParams(params, { videoIds })
226 226
227 return this.authHttp.get<VideoExistInPlaylist>(url, { params }) 227 return this.authHttp.get<VideoExistInPlaylist>(url, { params, headers: { ignoreLoadingBar: '' } })
228 .pipe(catchError(err => this.restExtractor.handleError(err))) 228 .pipe(catchError(err => this.restExtractor.handleError(err)))
229 } 229 }
230} 230}
diff --git a/client/src/app/shared/video/video-miniature.component.html b/client/src/app/shared/video/video-miniature.component.html
index c6fd570b7..036825e61 100644
--- a/client/src/app/shared/video/video-miniature.component.html
+++ b/client/src/app/shared/video/video-miniature.component.html
@@ -1,5 +1,8 @@
1<div class="video-miniature" [ngClass]="{ 'display-as-row': displayAsRow }" (mouseenter)="loadActions()"> 1<div class="video-miniature" [ngClass]="{ 'display-as-row': displayAsRow }" (mouseenter)="loadActions()">
2 <my-video-thumbnail #thumbnail [video]="video" [nsfw]="isVideoBlur"></my-video-thumbnail> 2 <my-video-thumbnail
3 [video]="video" [nsfw]="isVideoBlur"
4 [displayWatchLaterPlaylist]="isWatchLaterPlaylistDisplayed()" [inWatchLaterPlaylist]="inWatchLaterPlaylist" (watchLaterClick)="onWatchLaterClick($event)"
5 ></my-video-thumbnail>
3 6
4 <div class="video-bottom"> 7 <div class="video-bottom">
5 <div class="video-miniature-information"> 8 <div class="video-miniature-information">
diff --git a/client/src/app/shared/video/video-miniature.component.ts b/client/src/app/shared/video/video-miniature.component.ts
index ba65d33b6..a603f87e5 100644
--- a/client/src/app/shared/video/video-miniature.component.ts
+++ b/client/src/app/shared/video/video-miniature.component.ts
@@ -1,12 +1,24 @@
1import { ChangeDetectionStrategy, Component, EventEmitter, Inject, Input, LOCALE_ID, OnInit, Output, ViewChild } from '@angular/core' 1import {
2 ChangeDetectionStrategy,
3 ChangeDetectorRef,
4 Component,
5 EventEmitter,
6 Inject,
7 Input,
8 LOCALE_ID,
9 OnInit,
10 Output
11} from '@angular/core'
2import { User } from '../users' 12import { User } from '../users'
3import { Video } from './video.model' 13import { Video } from './video.model'
4import { ServerService } from '@app/core' 14import { AuthService, ServerService } from '@app/core'
5import { ServerConfig, VideoPrivacy, VideoState } from '../../../../../shared' 15import { ServerConfig, VideoPlaylistType, VideoPrivacy, VideoState } from '../../../../../shared'
6import { I18n } from '@ngx-translate/i18n-polyfill' 16import { I18n } from '@ngx-translate/i18n-polyfill'
7import { VideoActionsDisplayType } from '@app/shared/video/video-actions-dropdown.component' 17import { VideoActionsDisplayType } from '@app/shared/video/video-actions-dropdown.component'
8import { ScreenService } from '@app/shared/misc/screen.service' 18import { ScreenService } from '@app/shared/misc/screen.service'
9import { VideoThumbnailComponent } from './video-thumbnail.component' 19import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
20import { forkJoin } from 'rxjs'
21import { first } from 'rxjs/operators'
10 22
11export type OwnerDisplayType = 'account' | 'videoChannel' | 'auto' 23export type OwnerDisplayType = 'account' | 'videoChannel' | 'auto'
12export type MiniatureDisplayOptions = { 24export type MiniatureDisplayOptions = {
@@ -47,8 +59,6 @@ export class VideoMiniatureComponent implements OnInit {
47 @Output() videoUnblacklisted = new EventEmitter() 59 @Output() videoUnblacklisted = new EventEmitter()
48 @Output() videoRemoved = new EventEmitter() 60 @Output() videoRemoved = new EventEmitter()
49 61
50 @ViewChild('thumbnail', { static: true }) thumbnail: VideoThumbnailComponent
51
52 videoActionsDisplayOptions: VideoActionsDisplayType = { 62 videoActionsDisplayOptions: VideoActionsDisplayType = {
53 playlist: true, 63 playlist: true,
54 download: false, 64 download: false,
@@ -60,14 +70,28 @@ export class VideoMiniatureComponent implements OnInit {
60 showActions = false 70 showActions = false
61 serverConfig: ServerConfig 71 serverConfig: ServerConfig
62 72
73 addToWatchLaterText: string
74 addedToWatchLaterText: string
75 inWatchLaterPlaylist: boolean
76
77 watchLaterPlaylist: {
78 id: number
79 playlistElementId?: number
80 }
81
63 private ownerDisplayTypeChosen: 'account' | 'videoChannel' 82 private ownerDisplayTypeChosen: 'account' | 'videoChannel'
64 83
65 constructor ( 84 constructor (
66 private screenService: ScreenService, 85 private screenService: ScreenService,
67 private serverService: ServerService, 86 private serverService: ServerService,
68 private i18n: I18n, 87 private i18n: I18n,
88 private authService: AuthService,
89 private videoPlaylistService: VideoPlaylistService,
90 private cd: ChangeDetectorRef,
69 @Inject(LOCALE_ID) private localeId: string 91 @Inject(LOCALE_ID) private localeId: string
70 ) { } 92 ) {
93
94 }
71 95
72 get isVideoBlur () { 96 get isVideoBlur () {
73 return this.video.isVideoNSFWForUser(this.user, this.serverConfig) 97 return this.video.isVideoNSFWForUser(this.user, this.serverConfig)
@@ -131,7 +155,8 @@ export class VideoMiniatureComponent implements OnInit {
131 155
132 loadActions () { 156 loadActions () {
133 if (this.displayVideoActions) this.showActions = true 157 if (this.displayVideoActions) this.showActions = true
134 this.thumbnail.load() 158
159 this.loadWatchLater()
135 } 160 }
136 161
137 onVideoBlacklisted () { 162 onVideoBlacklisted () {
@@ -146,6 +171,38 @@ export class VideoMiniatureComponent implements OnInit {
146 this.videoRemoved.emit() 171 this.videoRemoved.emit()
147 } 172 }
148 173
174 isUserLoggedIn () {
175 return this.authService.isLoggedIn()
176 }
177
178 onWatchLaterClick (currentState: boolean) {
179 if (currentState === true) this.removeFromWatchLater()
180 else this.addToWatchLater()
181
182 this.inWatchLaterPlaylist = !currentState
183 }
184
185 addToWatchLater () {
186 const body = { videoId: this.video.id }
187
188 this.videoPlaylistService.addVideoInPlaylist(this.watchLaterPlaylist.id, body).subscribe(
189 res => {
190 this.watchLaterPlaylist.playlistElementId = res.videoPlaylistElement.id
191 }
192 )
193 }
194
195 removeFromWatchLater () {
196 this.videoPlaylistService.removeVideoFromPlaylist(this.watchLaterPlaylist.id, this.watchLaterPlaylist.playlistElementId)
197 .subscribe(
198 _ => { /* empty */ }
199 )
200 }
201
202 isWatchLaterPlaylistDisplayed () {
203 return this.inWatchLaterPlaylist !== undefined
204 }
205
149 private setUpBy () { 206 private setUpBy () {
150 if (this.ownerDisplayType === 'account' || this.ownerDisplayType === 'videoChannel') { 207 if (this.ownerDisplayType === 'account' || this.ownerDisplayType === 'videoChannel') {
151 this.ownerDisplayTypeChosen = this.ownerDisplayType 208 this.ownerDisplayTypeChosen = this.ownerDisplayType
@@ -163,4 +220,29 @@ export class VideoMiniatureComponent implements OnInit {
163 this.ownerDisplayTypeChosen = 'videoChannel' 220 this.ownerDisplayTypeChosen = 'videoChannel'
164 } 221 }
165 } 222 }
223
224 private loadWatchLater () {
225 if (!this.isUserLoggedIn()) return
226
227 forkJoin([
228 this.videoPlaylistService.doesVideoExistInPlaylist(this.video.id),
229 this.authService.userInformationLoaded.pipe(first())
230 ]).subscribe(
231 ([ existResult ]) => {
232 const watchLaterPlaylist = this.authService.getUser().specialPlaylists.find(p => p.type === VideoPlaylistType.WATCH_LATER)
233 const existsInWatchLater = existResult[ this.video.id ].find(r => r.playlistId === watchLaterPlaylist.id)
234 this.inWatchLaterPlaylist = false
235
236 this.watchLaterPlaylist = {
237 id: watchLaterPlaylist.id
238 }
239
240 if (existsInWatchLater) {
241 this.inWatchLaterPlaylist = true
242 this.watchLaterPlaylist.playlistElementId = existsInWatchLater.playlistElementId
243 }
244
245 this.cd.markForCheck()
246 })
247 }
166} 248}
diff --git a/client/src/app/shared/video/video-thumbnail.component.html b/client/src/app/shared/video/video-thumbnail.component.html
index 9679dfefb..c30a43557 100644
--- a/client/src/app/shared/video/video-thumbnail.component.html
+++ b/client/src/app/shared/video/video-thumbnail.component.html
@@ -1,18 +1,18 @@
1<a 1<a
2 [routerLink]="getVideoRouterLink()" [queryParams]="queryParams" [attr.title]="video.name" 2 [routerLink]="getVideoRouterLink()" [queryParams]="queryParams" [attr.title]="video.name"
3 class="video-thumbnail" 3 class="video-thumbnail"
4 (mouseenter)="load()" (focus)="load()"
5> 4>
6 <img alt="" [attr.aria-labelledby]="video.name" [attr.src]="getImageUrl()" [ngClass]="{ 'blur-filter': nsfw }" /> 5 <img alt="" [attr.aria-labelledby]="video.name" [attr.src]="getImageUrl()" [ngClass]="{ 'blur-filter': nsfw }" loading="lazy" />
7 6
8 <div *ngIf="isUserLoggedIn()" class="video-thumbnail-actions-overlay"> 7 <div *ngIf="displayWatchLaterPlaylist" class="video-thumbnail-actions-overlay">
9 <ng-container *ngIf="addedToWatchLater !== true"> 8 <ng-container *ngIf="inWatchLaterPlaylist !== true">
10 <div class="video-thumbnail-watch-later-overlay" placement="left" [ngbTooltip]="addToWatchLaterText" container="body" (click)="addToWatchLater();$event.stopPropagation();false"> 9 <div class="video-thumbnail-watch-later-overlay" placement="left" [ngbTooltip]="addToWatchLaterText" container="body" (click)="onWatchLaterClick($event)">
11 <my-global-icon iconName="clock" [attr.aria-label]="addToWatchLaterText" role="button"></my-global-icon> 10 <my-global-icon iconName="clock" [attr.aria-label]="addToWatchLaterText" role="button"></my-global-icon>
12 </div> 11 </div>
13 </ng-container> 12 </ng-container>
14 <ng-container *ngIf="addedToWatchLater === true"> 13
15 <div class="video-thumbnail-watch-later-overlay" placement="left" [ngbTooltip]="addedToWatchLaterText" container="body" (click)="removeFromWatchLater();$event.stopPropagation();false"> 14 <ng-container *ngIf="inWatchLaterPlaylist === true">
15 <div class="video-thumbnail-watch-later-overlay" placement="left" [ngbTooltip]="addedToWatchLaterText" container="body" (click)="onWatchLaterClick($event)">
16 <my-global-icon iconName="tick" [attr.aria-label]="addedToWatchLaterText" role="button"></my-global-icon> 16 <my-global-icon iconName="tick" [attr.aria-label]="addedToWatchLaterText" role="button"></my-global-icon>
17 </div> 17 </div>
18 </ng-container> 18 </ng-container>
diff --git a/client/src/app/shared/video/video-thumbnail.component.scss b/client/src/app/shared/video/video-thumbnail.component.scss
index ad221d6ed..573a64987 100644
--- a/client/src/app/shared/video/video-thumbnail.component.scss
+++ b/client/src/app/shared/video/video-thumbnail.component.scss
@@ -35,13 +35,6 @@
35 bottom: 5px; 35 bottom: 5px;
36 } 36 }
37 37
38 &:focus,
39 &:hover {
40 .video-thumbnail-actions-overlay {
41 opacity: 1;
42 }
43 }
44
45 .video-thumbnail-actions-overlay { 38 .video-thumbnail-actions-overlay {
46 position: absolute; 39 position: absolute;
47 display: flex; 40 display: flex;
diff --git a/client/src/app/shared/video/video-thumbnail.component.ts b/client/src/app/shared/video/video-thumbnail.component.ts
index 6f9292d52..2420ec715 100644
--- a/client/src/app/shared/video/video-thumbnail.component.ts
+++ b/client/src/app/shared/video/video-thumbnail.component.ts
@@ -1,9 +1,7 @@
1import { Component, Input, OnInit, ChangeDetectorRef } from '@angular/core' 1import { Component, EventEmitter, Input, Output } from '@angular/core'
2import { Video } from './video.model' 2import { Video } from './video.model'
3import { ScreenService } from '@app/shared/misc/screen.service' 3import { ScreenService } from '@app/shared/misc/screen.service'
4import { AuthService, ThemeService } from '@app/core' 4import { I18n } from '@ngx-translate/i18n-polyfill'
5import { VideoPlaylistService } from '../video-playlist/video-playlist.service'
6import { VideoPlaylistElementCreate } from '../../../../../shared'
7 5
8@Component({ 6@Component({
9 selector: 'my-video-thumbnail', 7 selector: 'my-video-thumbnail',
@@ -16,45 +14,20 @@ export class VideoThumbnailComponent {
16 @Input() routerLink: any[] 14 @Input() routerLink: any[]
17 @Input() queryParams: any[] 15 @Input() queryParams: any[]
18 16
19 addToWatchLaterText = 'Add to watch later' 17 @Input() displayWatchLaterPlaylist: boolean
20 addedToWatchLaterText = 'Added to watch later' 18 @Input() inWatchLaterPlaylist: boolean
21 addedToWatchLater: boolean
22 19
23 watchLaterPlaylist: any 20 @Output() watchLaterClick = new EventEmitter<boolean>()
21
22 addToWatchLaterText: string
23 addedToWatchLaterText: string
24 24
25 constructor ( 25 constructor (
26 private screenService: ScreenService, 26 private screenService: ScreenService,
27 private authService: AuthService, 27 private i18n: I18n
28 private videoPlaylistService: VideoPlaylistService, 28 ) {
29 private cd: ChangeDetectorRef 29 this.addToWatchLaterText = this.i18n('Add to watch later')
30 ) {} 30 this.addedToWatchLaterText = this.i18n('Remove from watch later')
31
32 load () {
33 if (this.addedToWatchLater !== undefined) return
34 if (!this.isUserLoggedIn()) return
35
36 this.videoPlaylistService.doesVideoExistInPlaylist(this.video.id)
37 .subscribe(
38 existResult => {
39 for (const playlist of this.authService.getUser().specialPlaylists) {
40 const existingPlaylist = existResult[ this.video.id ].find(p => p.playlistId === playlist.id)
41 this.addedToWatchLater = !!existingPlaylist
42
43 if (existingPlaylist) {
44 this.watchLaterPlaylist = {
45 playlistId: existingPlaylist.playlistId,
46 playlistElementId: existingPlaylist.playlistElementId
47 }
48 } else {
49 this.watchLaterPlaylist = {
50 playlistId: playlist.id
51 }
52 }
53
54 this.cd.markForCheck()
55 }
56 }
57 )
58 } 31 }
59 32
60 getImageUrl () { 33 getImageUrl () {
@@ -81,36 +54,10 @@ export class VideoThumbnailComponent {
81 return [ '/videos/watch', this.video.uuid ] 54 return [ '/videos/watch', this.video.uuid ]
82 } 55 }
83 56
84 isUserLoggedIn () { 57 onWatchLaterClick (event: Event) {
85 return this.authService.isLoggedIn() 58 this.watchLaterClick.emit(this.inWatchLaterPlaylist)
86 }
87
88 addToWatchLater () {
89 if (this.addedToWatchLater === undefined) return
90 this.addedToWatchLater = true
91
92 this.videoPlaylistService.addVideoInPlaylist(
93 this.watchLaterPlaylist.playlistId,
94 { videoId: this.video.id } as VideoPlaylistElementCreate
95 ).subscribe(
96 res => {
97 this.addedToWatchLater = true
98 this.watchLaterPlaylist.playlistElementId = res.videoPlaylistElement.id
99 }
100 )
101 }
102
103 removeFromWatchLater () {
104 if (this.addedToWatchLater === undefined) return
105 this.addedToWatchLater = false
106 59
107 this.videoPlaylistService.removeVideoFromPlaylist( 60 event.stopPropagation()
108 this.watchLaterPlaylist.playlistId, 61 return false
109 this.watchLaterPlaylist.playlistElementId
110 ).subscribe(
111 _ => {
112 this.addedToWatchLater = false
113 }
114 )
115 } 62 }
116} 63}
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts
index 28e10e562..aa3a85995 100644
--- a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts
+++ b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts
@@ -14,7 +14,6 @@ import { CanComponentDeactivate } from '@app/shared/guards/can-deactivate-guard.
14import { FormValidatorService, UserService } from '@app/shared' 14import { FormValidatorService, UserService } from '@app/shared'
15import { VideoCaptionService } from '@app/shared/video-caption' 15import { VideoCaptionService } from '@app/shared/video-caption'
16import { scrollToTop } from '@app/shared/misc/utils' 16import { scrollToTop } from '@app/shared/misc/utils'
17import { ServerConfig } from '@shared/models'
18 17
19@Component({ 18@Component({
20 selector: 'my-video-upload', 19 selector: 'my-video-upload',