diff options
Diffstat (limited to 'client/src/app/videos')
25 files changed, 43 insertions, 652 deletions
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.module.ts b/client/src/app/videos/+video-edit/shared/video-edit.module.ts index c64cea920..cdab694f9 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.module.ts +++ b/client/src/app/videos/+video-edit/shared/video-edit.module.ts | |||
@@ -3,7 +3,7 @@ import { NgModule } from '@angular/core' | |||
3 | import { TagInputModule } from 'ngx-chips' | 3 | import { TagInputModule } from 'ngx-chips' |
4 | import { TabsModule } from 'ngx-bootstrap/tabs' | 4 | import { TabsModule } from 'ngx-bootstrap/tabs' |
5 | 5 | ||
6 | import { VideoService, MarkdownService, VideoDescriptionComponent } from '../../shared' | 6 | import { MarkdownService, VideoDescriptionComponent } from '../../shared' |
7 | import { SharedModule } from '../../../shared' | 7 | import { SharedModule } from '../../../shared' |
8 | 8 | ||
9 | @NgModule({ | 9 | @NgModule({ |
@@ -26,7 +26,6 @@ import { SharedModule } from '../../../shared' | |||
26 | ], | 26 | ], |
27 | 27 | ||
28 | providers: [ | 28 | providers: [ |
29 | VideoService, | ||
30 | MarkdownService | 29 | MarkdownService |
31 | ] | 30 | ] |
32 | }) | 31 | }) |
diff --git a/client/src/app/videos/+video-edit/video-add.component.ts b/client/src/app/videos/+video-edit/video-add.component.ts index 76bfbb515..989addbd7 100644 --- a/client/src/app/videos/+video-edit/video-add.component.ts +++ b/client/src/app/videos/+video-edit/video-add.component.ts | |||
@@ -1,25 +1,23 @@ | |||
1 | import { HttpEventType, HttpResponse } from '@angular/common/http' | ||
1 | import { Component, OnInit, ViewChild } from '@angular/core' | 2 | import { Component, OnInit, ViewChild } from '@angular/core' |
2 | import { FormBuilder, FormGroup } from '@angular/forms' | 3 | import { FormBuilder, FormGroup } from '@angular/forms' |
3 | import { Router } from '@angular/router' | 4 | import { Router } from '@angular/router' |
4 | |||
5 | import { NotificationsService } from 'angular2-notifications' | 5 | import { NotificationsService } from 'angular2-notifications' |
6 | 6 | import { VideoService } from 'app/shared/video/video.service' | |
7 | import { VideoCreate } from '../../../../../shared' | ||
8 | import { AuthService, ServerService } from '../../core' | ||
7 | import { | 9 | import { |
8 | FormReactive, | 10 | FormReactive, |
9 | VIDEO_NAME, | ||
10 | VIDEO_CATEGORY, | 11 | VIDEO_CATEGORY, |
11 | VIDEO_LICENCE, | ||
12 | VIDEO_LANGUAGE, | ||
13 | VIDEO_DESCRIPTION, | ||
14 | VIDEO_TAGS, | ||
15 | VIDEO_CHANNEL, | 12 | VIDEO_CHANNEL, |
13 | VIDEO_DESCRIPTION, | ||
16 | VIDEO_FILE, | 14 | VIDEO_FILE, |
17 | VIDEO_PRIVACY | 15 | VIDEO_LANGUAGE, |
16 | VIDEO_LICENCE, | ||
17 | VIDEO_NAME, | ||
18 | VIDEO_PRIVACY, | ||
19 | VIDEO_TAGS | ||
18 | } from '../../shared' | 20 | } from '../../shared' |
19 | import { AuthService, ServerService } from '../../core' | ||
20 | import { VideoService } from '../shared' | ||
21 | import { VideoCreate } from '../../../../../shared' | ||
22 | import { HttpEventType, HttpResponse } from '@angular/common/http' | ||
23 | 21 | ||
24 | @Component({ | 22 | @Component({ |
25 | selector: 'my-videos-add', | 23 | selector: 'my-videos-add', |
diff --git a/client/src/app/videos/+video-edit/video-update.component.ts b/client/src/app/videos/+video-edit/video-update.component.ts index 0e966cb50..017781866 100644 --- a/client/src/app/videos/+video-edit/video-update.component.ts +++ b/client/src/app/videos/+video-edit/video-update.component.ts | |||
@@ -1,23 +1,22 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { FormBuilder, FormGroup } from '@angular/forms' | 2 | import { FormBuilder, FormGroup } from '@angular/forms' |
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import 'rxjs/add/observable/forkJoin' | ||
5 | |||
6 | import { NotificationsService } from 'angular2-notifications' | 4 | import { NotificationsService } from 'angular2-notifications' |
7 | 5 | import 'rxjs/add/observable/forkJoin' | |
6 | import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum' | ||
8 | import { ServerService } from '../../core' | 7 | import { ServerService } from '../../core' |
9 | import { | 8 | import { |
10 | FormReactive, | 9 | FormReactive, |
11 | VIDEO_NAME, | ||
12 | VIDEO_CATEGORY, | 10 | VIDEO_CATEGORY, |
13 | VIDEO_LICENCE, | ||
14 | VIDEO_LANGUAGE, | ||
15 | VIDEO_DESCRIPTION, | 11 | VIDEO_DESCRIPTION, |
16 | VIDEO_TAGS, | 12 | VIDEO_LANGUAGE, |
17 | VIDEO_PRIVACY | 13 | VIDEO_LICENCE, |
14 | VIDEO_NAME, | ||
15 | VIDEO_PRIVACY, | ||
16 | VIDEO_TAGS | ||
18 | } from '../../shared' | 17 | } from '../../shared' |
19 | import { VideoEdit, VideoService } from '../shared' | 18 | import { VideoService } from '../../shared/video/video.service' |
20 | import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum' | 19 | import { VideoEdit } from '../../shared/video/video-edit.model' |
21 | 20 | ||
22 | @Component({ | 21 | @Component({ |
23 | selector: 'my-videos-update', | 22 | selector: 'my-videos-update', |
diff --git a/client/src/app/videos/+video-watch/video-download.component.ts b/client/src/app/videos/+video-watch/video-download.component.ts index c32f8d586..010a246cd 100644 --- a/client/src/app/videos/+video-watch/video-download.component.ts +++ b/client/src/app/videos/+video-watch/video-download.component.ts | |||
@@ -1,8 +1,6 @@ | |||
1 | import { Component, Input, ViewChild } from '@angular/core' | 1 | import { Component, Input, ViewChild } from '@angular/core' |
2 | |||
3 | import { ModalDirective } from 'ngx-bootstrap/modal' | 2 | import { ModalDirective } from 'ngx-bootstrap/modal' |
4 | 3 | import { VideoDetails } from '../../shared/video/video-details.model' | |
5 | import { VideoDetails } from '../shared' | ||
6 | 4 | ||
7 | @Component({ | 5 | @Component({ |
8 | selector: 'my-video-download', | 6 | selector: 'my-video-download', |
diff --git a/client/src/app/videos/+video-watch/video-report.component.ts b/client/src/app/videos/+video-watch/video-report.component.ts index fc9b5a9d4..b94e4144e 100644 --- a/client/src/app/videos/+video-watch/video-report.component.ts +++ b/client/src/app/videos/+video-watch/video-report.component.ts | |||
@@ -1,11 +1,9 @@ | |||
1 | import { Component, Input, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, Input, OnInit, ViewChild } from '@angular/core' |
2 | import { FormBuilder, FormGroup } from '@angular/forms' | 2 | import { FormBuilder, FormGroup } from '@angular/forms' |
3 | |||
4 | import { ModalDirective } from 'ngx-bootstrap/modal' | ||
5 | import { NotificationsService } from 'angular2-notifications' | 3 | import { NotificationsService } from 'angular2-notifications' |
6 | 4 | import { ModalDirective } from 'ngx-bootstrap/modal' | |
7 | import { FormReactive, VideoAbuseService, VIDEO_ABUSE_REASON } from '../../shared' | 5 | import { FormReactive, VIDEO_ABUSE_REASON, VideoAbuseService } from '../../shared' |
8 | import { VideoDetails, VideoService } from '../shared' | 6 | import { VideoDetails } from '../../shared/video/video-details.model' |
9 | 7 | ||
10 | @Component({ | 8 | @Component({ |
11 | selector: 'my-video-report', | 9 | selector: 'my-video-report', |
diff --git a/client/src/app/videos/+video-watch/video-share.component.ts b/client/src/app/videos/+video-watch/video-share.component.ts index aeef65ecf..4df9adf29 100644 --- a/client/src/app/videos/+video-watch/video-share.component.ts +++ b/client/src/app/videos/+video-watch/video-share.component.ts | |||
@@ -1,8 +1,6 @@ | |||
1 | import { Component, Input, ViewChild } from '@angular/core' | 1 | import { Component, Input, ViewChild } from '@angular/core' |
2 | |||
3 | import { ModalDirective } from 'ngx-bootstrap/modal' | 2 | import { ModalDirective } from 'ngx-bootstrap/modal' |
4 | 3 | import { VideoDetails } from '../../shared/video/video-details.model' | |
5 | import { VideoDetails } from '../shared' | ||
6 | 4 | ||
7 | @Component({ | 5 | @Component({ |
8 | selector: 'my-video-share', | 6 | selector: 'my-video-share', |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts index b26f3092f..eac676be8 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts | |||
@@ -2,6 +2,7 @@ import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/co | |||
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { MetaService } from '@ngx-meta/core' | 3 | import { MetaService } from '@ngx-meta/core' |
4 | import { NotificationsService } from 'angular2-notifications' | 4 | import { NotificationsService } from 'angular2-notifications' |
5 | import { VideoService } from 'app/shared/video/video.service' | ||
5 | import { Observable } from 'rxjs/Observable' | 6 | import { Observable } from 'rxjs/Observable' |
6 | import { Subscription } from 'rxjs/Subscription' | 7 | import { Subscription } from 'rxjs/Subscription' |
7 | import videojs from 'video.js' | 8 | import videojs from 'video.js' |
@@ -9,10 +10,11 @@ import { UserVideoRateType, VideoRateType } from '../../../../../shared' | |||
9 | import '../../../assets/player/peertube-videojs-plugin' | 10 | import '../../../assets/player/peertube-videojs-plugin' |
10 | import { AuthService, ConfirmService } from '../../core' | 11 | import { AuthService, ConfirmService } from '../../core' |
11 | import { VideoBlacklistService } from '../../shared' | 12 | import { VideoBlacklistService } from '../../shared' |
12 | import { MarkdownService, VideoDetails, VideoService } from '../shared' | 13 | import { MarkdownService } from '../shared' |
13 | import { VideoDownloadComponent } from './video-download.component' | 14 | import { VideoDownloadComponent } from './video-download.component' |
14 | import { VideoReportComponent } from './video-report.component' | 15 | import { VideoReportComponent } from './video-report.component' |
15 | import { VideoShareComponent } from './video-share.component' | 16 | import { VideoShareComponent } from './video-share.component' |
17 | import { VideoDetails } from '../../shared/video/video-details.model' | ||
16 | 18 | ||
17 | @Component({ | 19 | @Component({ |
18 | selector: 'my-video-watch', | 20 | selector: 'my-video-watch', |
diff --git a/client/src/app/videos/+video-watch/video-watch.module.ts b/client/src/app/videos/+video-watch/video-watch.module.ts index 1b983200d..0b1dd5c15 100644 --- a/client/src/app/videos/+video-watch/video-watch.module.ts +++ b/client/src/app/videos/+video-watch/video-watch.module.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | 2 | ||
3 | import { VideoWatchRoutingModule } from './video-watch-routing.module' | 3 | import { VideoWatchRoutingModule } from './video-watch-routing.module' |
4 | import { VideoService, MarkdownService } from '../shared' | 4 | import { MarkdownService } from '../shared' |
5 | import { SharedModule } from '../../shared' | 5 | import { SharedModule } from '../../shared' |
6 | 6 | ||
7 | import { VideoWatchComponent } from './video-watch.component' | 7 | import { VideoWatchComponent } from './video-watch.component' |
@@ -28,8 +28,7 @@ import { VideoDownloadComponent } from './video-download.component' | |||
28 | ], | 28 | ], |
29 | 29 | ||
30 | providers: [ | 30 | providers: [ |
31 | MarkdownService, | 31 | MarkdownService |
32 | VideoService | ||
33 | ] | 32 | ] |
34 | }) | 33 | }) |
35 | export class VideoWatchModule { } | 34 | export class VideoWatchModule { } |
diff --git a/client/src/app/videos/shared/index.ts b/client/src/app/videos/shared/index.ts index 3f1458088..3c72ed895 100644 --- a/client/src/app/videos/shared/index.ts +++ b/client/src/app/videos/shared/index.ts | |||
@@ -1,8 +1,2 @@ | |||
1 | export * from './sort-field.type' | ||
2 | export * from './markdown.service' | 1 | export * from './markdown.service' |
3 | export * from './video.model' | ||
4 | export * from './video-details.model' | ||
5 | export * from './video-edit.model' | ||
6 | export * from './video.service' | ||
7 | export * from './video-description.component' | 2 | export * from './video-description.component' |
8 | export * from './video-pagination.model' | ||
diff --git a/client/src/app/videos/shared/sort-field.type.ts b/client/src/app/videos/shared/sort-field.type.ts deleted file mode 100644 index 776f360f8..000000000 --- a/client/src/app/videos/shared/sort-field.type.ts +++ /dev/null | |||
@@ -1,5 +0,0 @@ | |||
1 | export type SortField = 'name' | '-name' | ||
2 | | 'duration' | '-duration' | ||
3 | | 'createdAt' | '-createdAt' | ||
4 | | 'views' | '-views' | ||
5 | | 'likes' | '-likes' | ||
diff --git a/client/src/app/videos/shared/video-details.model.ts b/client/src/app/videos/shared/video-details.model.ts deleted file mode 100644 index 64cb4f847..000000000 --- a/client/src/app/videos/shared/video-details.model.ts +++ /dev/null | |||
@@ -1,84 +0,0 @@ | |||
1 | import { Video } from './video.model' | ||
2 | import { AuthUser } from '../../core' | ||
3 | import { | ||
4 | VideoDetails as VideoDetailsServerModel, | ||
5 | VideoFile, | ||
6 | VideoChannel, | ||
7 | VideoResolution, | ||
8 | UserRight, | ||
9 | VideoPrivacy | ||
10 | } from '../../../../../shared' | ||
11 | |||
12 | export class VideoDetails extends Video implements VideoDetailsServerModel { | ||
13 | account: string | ||
14 | by: string | ||
15 | createdAt: Date | ||
16 | updatedAt: Date | ||
17 | categoryLabel: string | ||
18 | category: number | ||
19 | licenceLabel: string | ||
20 | licence: number | ||
21 | languageLabel: string | ||
22 | language: number | ||
23 | description: string | ||
24 | duration: number | ||
25 | durationLabel: string | ||
26 | id: number | ||
27 | uuid: string | ||
28 | isLocal: boolean | ||
29 | name: string | ||
30 | serverHost: string | ||
31 | tags: string[] | ||
32 | thumbnailPath: string | ||
33 | thumbnailUrl: string | ||
34 | previewPath: string | ||
35 | previewUrl: string | ||
36 | embedPath: string | ||
37 | embedUrl: string | ||
38 | views: number | ||
39 | likes: number | ||
40 | dislikes: number | ||
41 | nsfw: boolean | ||
42 | descriptionPath: string | ||
43 | files: VideoFile[] | ||
44 | channel: VideoChannel | ||
45 | privacy: VideoPrivacy | ||
46 | privacyLabel: string | ||
47 | |||
48 | constructor (hash: VideoDetailsServerModel) { | ||
49 | super(hash) | ||
50 | |||
51 | this.privacy = hash.privacy | ||
52 | this.privacyLabel = hash.privacyLabel | ||
53 | this.descriptionPath = hash.descriptionPath | ||
54 | this.files = hash.files | ||
55 | this.channel = hash.channel | ||
56 | } | ||
57 | |||
58 | getAppropriateMagnetUri (actualDownloadSpeed = 0) { | ||
59 | if (this.files === undefined || this.files.length === 0) return '' | ||
60 | if (this.files.length === 1) return this.files[0].magnetUri | ||
61 | |||
62 | // Find first video that is good for our download speed (remember they are sorted) | ||
63 | let betterResolutionFile = this.files.find(f => actualDownloadSpeed > (f.size / this.duration)) | ||
64 | |||
65 | // If the download speed is too bad, return the lowest resolution we have | ||
66 | if (betterResolutionFile === undefined) { | ||
67 | betterResolutionFile = this.files.find(f => f.resolution === VideoResolution.H_240P) | ||
68 | } | ||
69 | |||
70 | return betterResolutionFile.magnetUri | ||
71 | } | ||
72 | |||
73 | isRemovableBy (user: AuthUser) { | ||
74 | return user && this.isLocal === true && (this.account === user.username || user.hasRight(UserRight.REMOVE_ANY_VIDEO)) | ||
75 | } | ||
76 | |||
77 | isBlackistableBy (user: AuthUser) { | ||
78 | return user && user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) === true && this.isLocal === false | ||
79 | } | ||
80 | |||
81 | isUpdatableBy (user: AuthUser) { | ||
82 | return user && this.isLocal === true && user.username === this.account | ||
83 | } | ||
84 | } | ||
diff --git a/client/src/app/videos/shared/video-edit.model.ts b/client/src/app/videos/shared/video-edit.model.ts deleted file mode 100644 index 88d23a59f..000000000 --- a/client/src/app/videos/shared/video-edit.model.ts +++ /dev/null | |||
@@ -1,50 +0,0 @@ | |||
1 | import { VideoDetails } from './video-details.model' | ||
2 | import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum' | ||
3 | |||
4 | export class VideoEdit { | ||
5 | category: number | ||
6 | licence: number | ||
7 | language: number | ||
8 | description: string | ||
9 | name: string | ||
10 | tags: string[] | ||
11 | nsfw: boolean | ||
12 | channel: number | ||
13 | privacy: VideoPrivacy | ||
14 | uuid?: string | ||
15 | id?: number | ||
16 | |||
17 | constructor (videoDetails: VideoDetails) { | ||
18 | this.id = videoDetails.id | ||
19 | this.uuid = videoDetails.uuid | ||
20 | this.category = videoDetails.category | ||
21 | this.licence = videoDetails.licence | ||
22 | this.language = videoDetails.language | ||
23 | this.description = videoDetails.description | ||
24 | this.name = videoDetails.name | ||
25 | this.tags = videoDetails.tags | ||
26 | this.nsfw = videoDetails.nsfw | ||
27 | this.channel = videoDetails.channel.id | ||
28 | this.privacy = videoDetails.privacy | ||
29 | } | ||
30 | |||
31 | patch (values: Object) { | ||
32 | Object.keys(values).forEach((key) => { | ||
33 | this[key] = values[key] | ||
34 | }) | ||
35 | } | ||
36 | |||
37 | toJSON () { | ||
38 | return { | ||
39 | category: this.category, | ||
40 | licence: this.licence, | ||
41 | language: this.language, | ||
42 | description: this.description, | ||
43 | name: this.name, | ||
44 | tags: this.tags, | ||
45 | nsfw: this.nsfw, | ||
46 | channel: this.channel, | ||
47 | privacy: this.privacy | ||
48 | } | ||
49 | } | ||
50 | } | ||
diff --git a/client/src/app/videos/shared/video-pagination.model.ts b/client/src/app/videos/shared/video-pagination.model.ts deleted file mode 100644 index 9e71769cb..000000000 --- a/client/src/app/videos/shared/video-pagination.model.ts +++ /dev/null | |||
@@ -1,5 +0,0 @@ | |||
1 | export interface VideoPagination { | ||
2 | currentPage: number | ||
3 | itemsPerPage: number | ||
4 | totalItems: number | ||
5 | } | ||
diff --git a/client/src/app/videos/shared/video.model.ts b/client/src/app/videos/shared/video.model.ts deleted file mode 100644 index 0dd41d71b..000000000 --- a/client/src/app/videos/shared/video.model.ts +++ /dev/null | |||
@@ -1,90 +0,0 @@ | |||
1 | import { Video as VideoServerModel } from '../../../../../shared' | ||
2 | import { User } from '../../shared' | ||
3 | |||
4 | export class Video implements VideoServerModel { | ||
5 | account: string | ||
6 | by: string | ||
7 | createdAt: Date | ||
8 | updatedAt: Date | ||
9 | categoryLabel: string | ||
10 | category: number | ||
11 | licenceLabel: string | ||
12 | licence: number | ||
13 | languageLabel: string | ||
14 | language: number | ||
15 | description: string | ||
16 | duration: number | ||
17 | durationLabel: string | ||
18 | id: number | ||
19 | uuid: string | ||
20 | isLocal: boolean | ||
21 | name: string | ||
22 | serverHost: string | ||
23 | tags: string[] | ||
24 | thumbnailPath: string | ||
25 | thumbnailUrl: string | ||
26 | previewPath: string | ||
27 | previewUrl: string | ||
28 | embedPath: string | ||
29 | embedUrl: string | ||
30 | views: number | ||
31 | likes: number | ||
32 | dislikes: number | ||
33 | nsfw: boolean | ||
34 | |||
35 | private static createByString (account: string, serverHost: string) { | ||
36 | return account + '@' + serverHost | ||
37 | } | ||
38 | |||
39 | private static createDurationString (duration: number) { | ||
40 | const minutes = Math.floor(duration / 60) | ||
41 | const seconds = duration % 60 | ||
42 | const minutesPadding = minutes >= 10 ? '' : '0' | ||
43 | const secondsPadding = seconds >= 10 ? '' : '0' | ||
44 | |||
45 | return minutesPadding + minutes.toString() + ':' + secondsPadding + seconds.toString() | ||
46 | } | ||
47 | |||
48 | constructor (hash: VideoServerModel) { | ||
49 | let absoluteAPIUrl = API_URL | ||
50 | if (!absoluteAPIUrl) { | ||
51 | // The API is on the same domain | ||
52 | absoluteAPIUrl = window.location.origin | ||
53 | } | ||
54 | |||
55 | this.account = hash.account | ||
56 | this.createdAt = new Date(hash.createdAt.toString()) | ||
57 | this.categoryLabel = hash.categoryLabel | ||
58 | this.category = hash.category | ||
59 | this.licenceLabel = hash.licenceLabel | ||
60 | this.licence = hash.licence | ||
61 | this.languageLabel = hash.languageLabel | ||
62 | this.language = hash.language | ||
63 | this.description = hash.description | ||
64 | this.duration = hash.duration | ||
65 | this.durationLabel = Video.createDurationString(hash.duration) | ||
66 | this.id = hash.id | ||
67 | this.uuid = hash.uuid | ||
68 | this.isLocal = hash.isLocal | ||
69 | this.name = hash.name | ||
70 | this.serverHost = hash.serverHost | ||
71 | this.tags = hash.tags | ||
72 | this.thumbnailPath = hash.thumbnailPath | ||
73 | this.thumbnailUrl = absoluteAPIUrl + hash.thumbnailPath | ||
74 | this.previewPath = hash.previewPath | ||
75 | this.previewUrl = absoluteAPIUrl + hash.previewPath | ||
76 | this.embedPath = hash.embedPath | ||
77 | this.embedUrl = absoluteAPIUrl + hash.embedPath | ||
78 | this.views = hash.views | ||
79 | this.likes = hash.likes | ||
80 | this.dislikes = hash.dislikes | ||
81 | this.nsfw = hash.nsfw | ||
82 | |||
83 | this.by = Video.createByString(hash.account, hash.serverHost) | ||
84 | } | ||
85 | |||
86 | isVideoNSFWForUser (user: User) { | ||
87 | // If the video is NSFW and the user is not logged in, or the user does not want to display NSFW videos... | ||
88 | return (this.nsfw && (!user || user.displayNSFW === false)) | ||
89 | } | ||
90 | } | ||
diff --git a/client/src/app/videos/shared/video.service.ts b/client/src/app/videos/shared/video.service.ts deleted file mode 100644 index 5d25a26d4..000000000 --- a/client/src/app/videos/shared/video.service.ts +++ /dev/null | |||
@@ -1,176 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { Observable } from 'rxjs/Observable' | ||
3 | import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http' | ||
4 | import 'rxjs/add/operator/catch' | ||
5 | import 'rxjs/add/operator/map' | ||
6 | |||
7 | import { SortField } from './sort-field.type' | ||
8 | import { | ||
9 | RestExtractor, | ||
10 | RestService, | ||
11 | UserService, | ||
12 | Search | ||
13 | } from '../../shared' | ||
14 | import { Video } from './video.model' | ||
15 | import { VideoDetails } from './video-details.model' | ||
16 | import { VideoEdit } from './video-edit.model' | ||
17 | import { VideoPagination } from './video-pagination.model' | ||
18 | import { | ||
19 | UserVideoRate, | ||
20 | VideoRateType, | ||
21 | VideoUpdate, | ||
22 | UserVideoRateUpdate, | ||
23 | Video as VideoServerModel, | ||
24 | VideoDetails as VideoDetailsServerModel, | ||
25 | ResultList | ||
26 | } from '../../../../../shared' | ||
27 | |||
28 | @Injectable() | ||
29 | export class VideoService { | ||
30 | private static BASE_VIDEO_URL = API_URL + '/api/v1/videos/' | ||
31 | |||
32 | constructor ( | ||
33 | private authHttp: HttpClient, | ||
34 | private restExtractor: RestExtractor, | ||
35 | private restService: RestService | ||
36 | ) {} | ||
37 | |||
38 | getVideo (uuid: string): Observable<VideoDetails> { | ||
39 | return this.authHttp.get<VideoDetailsServerModel>(VideoService.BASE_VIDEO_URL + uuid) | ||
40 | .map(videoHash => new VideoDetails(videoHash)) | ||
41 | .catch((res) => this.restExtractor.handleError(res)) | ||
42 | } | ||
43 | |||
44 | viewVideo (uuid: string): Observable<VideoDetails> { | ||
45 | return this.authHttp.post(VideoService.BASE_VIDEO_URL + uuid + '/views', {}) | ||
46 | .map(this.restExtractor.extractDataBool) | ||
47 | .catch(this.restExtractor.handleError) | ||
48 | } | ||
49 | |||
50 | updateVideo (video: VideoEdit) { | ||
51 | const language = video.language ? video.language : null | ||
52 | |||
53 | const body: VideoUpdate = { | ||
54 | name: video.name, | ||
55 | category: video.category, | ||
56 | licence: video.licence, | ||
57 | language, | ||
58 | description: video.description, | ||
59 | privacy: video.privacy, | ||
60 | tags: video.tags, | ||
61 | nsfw: video.nsfw | ||
62 | } | ||
63 | |||
64 | return this.authHttp.put(VideoService.BASE_VIDEO_URL + video.id, body) | ||
65 | .map(this.restExtractor.extractDataBool) | ||
66 | .catch(this.restExtractor.handleError) | ||
67 | } | ||
68 | |||
69 | uploadVideo (video: FormData) { | ||
70 | const req = new HttpRequest('POST', VideoService.BASE_VIDEO_URL + 'upload', video, { reportProgress: true }) | ||
71 | |||
72 | return this.authHttp | ||
73 | .request(req) | ||
74 | .catch(this.restExtractor.handleError) | ||
75 | } | ||
76 | |||
77 | getMyVideos (videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> { | ||
78 | const pagination = this.videoPaginationToRestPagination(videoPagination) | ||
79 | |||
80 | let params = new HttpParams() | ||
81 | params = this.restService.addRestGetParams(params, pagination, sort) | ||
82 | |||
83 | return this.authHttp.get(UserService.BASE_USERS_URL + '/me/videos', { params }) | ||
84 | .map(this.extractVideos) | ||
85 | .catch((res) => this.restExtractor.handleError(res)) | ||
86 | } | ||
87 | |||
88 | getVideos (videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> { | ||
89 | const pagination = this.videoPaginationToRestPagination(videoPagination) | ||
90 | |||
91 | let params = new HttpParams() | ||
92 | params = this.restService.addRestGetParams(params, pagination, sort) | ||
93 | |||
94 | return this.authHttp | ||
95 | .get(VideoService.BASE_VIDEO_URL, { params }) | ||
96 | .map(this.extractVideos) | ||
97 | .catch((res) => this.restExtractor.handleError(res)) | ||
98 | } | ||
99 | |||
100 | searchVideos (search: Search, videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> { | ||
101 | const url = VideoService.BASE_VIDEO_URL + 'search/' + encodeURIComponent(search.value) | ||
102 | |||
103 | const pagination = this.videoPaginationToRestPagination(videoPagination) | ||
104 | |||
105 | let params = new HttpParams() | ||
106 | params = this.restService.addRestGetParams(params, pagination, sort) | ||
107 | |||
108 | if (search.field) params.set('field', search.field) | ||
109 | |||
110 | return this.authHttp | ||
111 | .get<ResultList<VideoServerModel>>(url, { params }) | ||
112 | .map(this.extractVideos) | ||
113 | .catch((res) => this.restExtractor.handleError(res)) | ||
114 | } | ||
115 | |||
116 | removeVideo (id: number) { | ||
117 | return this.authHttp | ||
118 | .delete(VideoService.BASE_VIDEO_URL + id) | ||
119 | .map(this.restExtractor.extractDataBool) | ||
120 | .catch((res) => this.restExtractor.handleError(res)) | ||
121 | } | ||
122 | |||
123 | loadCompleteDescription (descriptionPath: string) { | ||
124 | return this.authHttp | ||
125 | .get(API_URL + descriptionPath) | ||
126 | .map(res => res['description']) | ||
127 | .catch((res) => this.restExtractor.handleError(res)) | ||
128 | } | ||
129 | |||
130 | setVideoLike (id: number) { | ||
131 | return this.setVideoRate(id, 'like') | ||
132 | } | ||
133 | |||
134 | setVideoDislike (id: number) { | ||
135 | return this.setVideoRate(id, 'dislike') | ||
136 | } | ||
137 | |||
138 | getUserVideoRating (id: number): Observable<UserVideoRate> { | ||
139 | const url = UserService.BASE_USERS_URL + 'me/videos/' + id + '/rating' | ||
140 | |||
141 | return this.authHttp | ||
142 | .get(url) | ||
143 | .catch(res => this.restExtractor.handleError(res)) | ||
144 | } | ||
145 | |||
146 | private videoPaginationToRestPagination (videoPagination: VideoPagination) { | ||
147 | const start: number = (videoPagination.currentPage - 1) * videoPagination.itemsPerPage | ||
148 | const count: number = videoPagination.itemsPerPage | ||
149 | |||
150 | return { start, count } | ||
151 | } | ||
152 | |||
153 | private setVideoRate (id: number, rateType: VideoRateType) { | ||
154 | const url = VideoService.BASE_VIDEO_URL + id + '/rate' | ||
155 | const body: UserVideoRateUpdate = { | ||
156 | rating: rateType | ||
157 | } | ||
158 | |||
159 | return this.authHttp | ||
160 | .put(url, body) | ||
161 | .map(this.restExtractor.extractDataBool) | ||
162 | .catch(res => this.restExtractor.handleError(res)) | ||
163 | } | ||
164 | |||
165 | private extractVideos (result: ResultList<VideoServerModel>) { | ||
166 | const videosJson = result.data | ||
167 | const totalVideos = result.total | ||
168 | const videos = [] | ||
169 | |||
170 | for (const videoJson of videosJson) { | ||
171 | videos.push(new Video(videoJson)) | ||
172 | } | ||
173 | |||
174 | return { videos, totalVideos } | ||
175 | } | ||
176 | } | ||
diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.html b/client/src/app/videos/video-list/shared/abstract-video-list.html deleted file mode 100644 index bd4f6b1f8..000000000 --- a/client/src/app/videos/video-list/shared/abstract-video-list.html +++ /dev/null | |||
@@ -1,19 +0,0 @@ | |||
1 | <div class="margin-content"> | ||
2 | <div class="title-page title-page-single"> | ||
3 | {{ titlePage }} | ||
4 | </div> | ||
5 | |||
6 | <div | ||
7 | infiniteScroll | ||
8 | [infiniteScrollUpDistance]="1.5" | ||
9 | [infiniteScrollDistance]="0.5" | ||
10 | (scrolled)="onNearOfBottom()" | ||
11 | (scrolledUp)="onNearOfTop()" | ||
12 | > | ||
13 | <my-video-miniature | ||
14 | class="ng-animate" | ||
15 | *ngFor="let video of videos" [video]="video" [user]="user" [currentSort]="sort" | ||
16 | > | ||
17 | </my-video-miniature> | ||
18 | </div> | ||
19 | </div> | ||
diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.scss b/client/src/app/videos/video-list/shared/abstract-video-list.scss deleted file mode 100644 index e69de29bb..000000000 --- a/client/src/app/videos/video-list/shared/abstract-video-list.scss +++ /dev/null | |||
diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.ts b/client/src/app/videos/video-list/shared/abstract-video-list.ts deleted file mode 100644 index a684ffef4..000000000 --- a/client/src/app/videos/video-list/shared/abstract-video-list.ts +++ /dev/null | |||
@@ -1,121 +0,0 @@ | |||
1 | import { OnDestroy, OnInit } from '@angular/core' | ||
2 | import { ActivatedRoute, Router } from '@angular/router' | ||
3 | |||
4 | import { NotificationsService } from 'angular2-notifications' | ||
5 | import { Observable } from 'rxjs/Observable' | ||
6 | import { Subscription } from 'rxjs/Subscription' | ||
7 | |||
8 | import { SortField, Video, VideoPagination } from '../../shared' | ||
9 | |||
10 | export abstract class AbstractVideoList implements OnInit, OnDestroy { | ||
11 | pagination: VideoPagination = { | ||
12 | currentPage: 1, | ||
13 | itemsPerPage: 25, | ||
14 | totalItems: null | ||
15 | } | ||
16 | sort: SortField = '-createdAt' | ||
17 | videos: Video[] = [] | ||
18 | |||
19 | protected notificationsService: NotificationsService | ||
20 | protected router: Router | ||
21 | protected route: ActivatedRoute | ||
22 | protected subActivatedRoute: Subscription | ||
23 | |||
24 | protected abstract currentRoute: string | ||
25 | |||
26 | abstract titlePage: string | ||
27 | private loadedPages: { [ id: number ]: boolean } = {} | ||
28 | |||
29 | abstract getVideosObservable (): Observable<{ videos: Video[], totalVideos: number}> | ||
30 | |||
31 | ngOnInit () { | ||
32 | // Subscribe to route changes | ||
33 | const routeParams = this.route.snapshot.params | ||
34 | this.loadRouteParams(routeParams) | ||
35 | this.loadMoreVideos('after') | ||
36 | } | ||
37 | |||
38 | ngOnDestroy () { | ||
39 | if (this.subActivatedRoute) { | ||
40 | this.subActivatedRoute.unsubscribe() | ||
41 | } | ||
42 | } | ||
43 | |||
44 | onNearOfTop () { | ||
45 | if (this.pagination.currentPage > 1) { | ||
46 | this.previousPage() | ||
47 | } | ||
48 | } | ||
49 | |||
50 | onNearOfBottom () { | ||
51 | if (this.hasMoreVideos()) { | ||
52 | this.nextPage() | ||
53 | } | ||
54 | } | ||
55 | |||
56 | loadMoreVideos (where: 'before' | 'after') { | ||
57 | if (this.loadedPages[this.pagination.currentPage] === true) return | ||
58 | |||
59 | const observable = this.getVideosObservable() | ||
60 | |||
61 | observable.subscribe( | ||
62 | ({ videos, totalVideos }) => { | ||
63 | this.loadedPages[this.pagination.currentPage] = true | ||
64 | this.pagination.totalItems = totalVideos | ||
65 | |||
66 | if (where === 'before') { | ||
67 | this.videos = videos.concat(this.videos) | ||
68 | } else { | ||
69 | this.videos = this.videos.concat(videos) | ||
70 | } | ||
71 | }, | ||
72 | error => this.notificationsService.error('Error', error.text) | ||
73 | ) | ||
74 | } | ||
75 | |||
76 | protected hasMoreVideos () { | ||
77 | if (!this.pagination.totalItems) return true | ||
78 | |||
79 | const maxPage = this.pagination.totalItems/this.pagination.itemsPerPage | ||
80 | return maxPage > this.pagination.currentPage | ||
81 | } | ||
82 | |||
83 | protected previousPage () { | ||
84 | this.pagination.currentPage-- | ||
85 | |||
86 | this.setNewRouteParams() | ||
87 | this.loadMoreVideos('before') | ||
88 | } | ||
89 | |||
90 | protected nextPage () { | ||
91 | this.pagination.currentPage++ | ||
92 | |||
93 | this.setNewRouteParams() | ||
94 | this.loadMoreVideos('after') | ||
95 | } | ||
96 | |||
97 | protected buildRouteParams () { | ||
98 | // There is always a sort and a current page | ||
99 | const params = { | ||
100 | sort: this.sort, | ||
101 | page: this.pagination.currentPage | ||
102 | } | ||
103 | |||
104 | return params | ||
105 | } | ||
106 | |||
107 | protected loadRouteParams (routeParams: { [ key: string ]: any }) { | ||
108 | this.sort = routeParams['sort'] as SortField || '-createdAt' | ||
109 | |||
110 | if (routeParams['page'] !== undefined) { | ||
111 | this.pagination.currentPage = parseInt(routeParams['page'], 10) | ||
112 | } else { | ||
113 | this.pagination.currentPage = 1 | ||
114 | } | ||
115 | } | ||
116 | |||
117 | protected setNewRouteParams () { | ||
118 | const routeParams = this.buildRouteParams() | ||
119 | this.router.navigate([ this.currentRoute, routeParams ]) | ||
120 | } | ||
121 | } | ||
diff --git a/client/src/app/videos/video-list/shared/index.ts b/client/src/app/videos/video-list/shared/index.ts index 170ca4832..2778f2d9e 100644 --- a/client/src/app/videos/video-list/shared/index.ts +++ b/client/src/app/videos/video-list/shared/index.ts | |||
@@ -1,2 +1 @@ | |||
1 | export * from './abstract-video-list' | ||
2 | export * from './video-miniature.component' | export * from './video-miniature.component' | |
diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.html b/client/src/app/videos/video-list/shared/video-miniature.component.html index aea85b6c6..f2756ca3d 100644 --- a/client/src/app/videos/video-list/shared/video-miniature.component.html +++ b/client/src/app/videos/video-list/shared/video-miniature.component.html | |||
@@ -1,14 +1,5 @@ | |||
1 | <div class="video-miniature"> | 1 | <div class="video-miniature"> |
2 | <a | 2 | <my-video-thumbnail [video]="video" [nsfw]="isVideoNSFWForThisUser()"></my-video-thumbnail> |
3 | [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.description" | ||
4 | class="video-miniature-thumbnail" | ||
5 | > | ||
6 | <img [attr.src]="video.thumbnailUrl" alt="video thumbnail" [ngClass]="{ 'blur-filter': isVideoNSFWForThisUser() }" /> | ||
7 | |||
8 | <div class="video-miniature-thumbnail-overlay"> | ||
9 | {{ video.durationLabel }} | ||
10 | </div> | ||
11 | </a> | ||
12 | 3 | ||
13 | <div class="video-miniature-information"> | 4 | <div class="video-miniature-information"> |
14 | <span class="video-miniature-name"> | 5 | <span class="video-miniature-name"> |
diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.scss b/client/src/app/videos/video-list/shared/video-miniature.component.scss index ed15864d9..658d7af9d 100644 --- a/client/src/app/videos/video-list/shared/video-miniature.component.scss +++ b/client/src/app/videos/video-list/shared/video-miniature.component.scss | |||
@@ -5,35 +5,6 @@ | |||
5 | height: 175px; | 5 | height: 175px; |
6 | vertical-align: top; | 6 | vertical-align: top; |
7 | 7 | ||
8 | .video-miniature-thumbnail { | ||
9 | display: inline-block; | ||
10 | position: relative; | ||
11 | border-radius: 4px; | ||
12 | overflow: hidden; | ||
13 | |||
14 | &:hover { | ||
15 | text-decoration: none !important; | ||
16 | } | ||
17 | |||
18 | img.blur-filter { | ||
19 | filter: blur(5px); | ||
20 | transform : scale(1.03); | ||
21 | } | ||
22 | |||
23 | .video-miniature-thumbnail-overlay { | ||
24 | position: absolute; | ||
25 | right: 5px; | ||
26 | bottom: 5px; | ||
27 | display: inline-block; | ||
28 | background-color: rgba(0, 0, 0, 0.7); | ||
29 | color: #fff; | ||
30 | font-size: 12px; | ||
31 | font-weight: $font-bold; | ||
32 | border-radius: 3px; | ||
33 | padding: 0 5px; | ||
34 | } | ||
35 | } | ||
36 | |||
37 | .video-miniature-information { | 8 | .video-miniature-information { |
38 | width: 200px; | 9 | width: 200px; |
39 | margin-top: 2px; | 10 | margin-top: 2px; |
diff --git a/client/src/app/videos/video-list/shared/video-miniature.component.ts b/client/src/app/videos/video-list/shared/video-miniature.component.ts index e5a87907b..e8fc8e911 100644 --- a/client/src/app/videos/video-list/shared/video-miniature.component.ts +++ b/client/src/app/videos/video-list/shared/video-miniature.component.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | import { Component, Input } from '@angular/core' |
2 | |||
3 | import { SortField, Video } from '../../shared' | ||
4 | import { User } from '../../../shared' | 2 | import { User } from '../../../shared' |
3 | import { SortField } from '../../../shared/video/sort-field.type' | ||
4 | import { Video } from '../../../shared/video/video.model' | ||
5 | 5 | ||
6 | @Component({ | 6 | @Component({ |
7 | selector: 'my-video-miniature', | 7 | selector: 'my-video-miniature', |
diff --git a/client/src/app/videos/video-list/video-recently-added.component.ts b/client/src/app/videos/video-list/video-recently-added.component.ts index 9bf69bd78..d48804414 100644 --- a/client/src/app/videos/video-list/video-recently-added.component.ts +++ b/client/src/app/videos/video-list/video-recently-added.component.ts | |||
@@ -1,13 +1,13 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | 1 | import { Component, OnDestroy, OnInit } from '@angular/core' |
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { NotificationsService } from 'angular2-notifications' |
4 | import { VideoService } from '../shared' | 4 | import { VideoService } from '../../shared/video/video.service' |
5 | import { AbstractVideoList } from './shared' | 5 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' |
6 | 6 | ||
7 | @Component({ | 7 | @Component({ |
8 | selector: 'my-videos-recently-added', | 8 | selector: 'my-videos-recently-added', |
9 | styleUrls: [ './shared/abstract-video-list.scss' ], | 9 | styleUrls: [ '../../shared/video/abstract-video-list.scss' ], |
10 | templateUrl: './shared/abstract-video-list.html' | 10 | templateUrl: '../../shared/video/abstract-video-list.html' |
11 | }) | 11 | }) |
12 | export class VideoRecentlyAddedComponent extends AbstractVideoList implements OnInit, OnDestroy { | 12 | export class VideoRecentlyAddedComponent extends AbstractVideoList implements OnInit, OnDestroy { |
13 | titlePage = 'Recently added' | 13 | titlePage = 'Recently added' |
diff --git a/client/src/app/videos/video-list/video-trending.component.ts b/client/src/app/videos/video-list/video-trending.component.ts index a1df68711..9108289c9 100644 --- a/client/src/app/videos/video-list/video-trending.component.ts +++ b/client/src/app/videos/video-list/video-trending.component.ts | |||
@@ -1,13 +1,13 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | 1 | import { Component, OnDestroy, OnInit } from '@angular/core' |
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { NotificationsService } from 'angular2-notifications' |
4 | import { VideoService } from '../shared' | 4 | import { VideoService } from '../../shared/video/video.service' |
5 | import { AbstractVideoList } from './shared' | 5 | import { AbstractVideoList } from 'app/shared/video/abstract-video-list' |
6 | 6 | ||
7 | @Component({ | 7 | @Component({ |
8 | selector: 'my-videos-trending', | 8 | selector: 'my-videos-trending', |
9 | styleUrls: [ './shared/abstract-video-list.scss' ], | 9 | styleUrls: [ '../../shared/video/abstract-video-list.scss' ], |
10 | templateUrl: './shared/abstract-video-list.html' | 10 | templateUrl: '../../shared/video/abstract-video-list.html' |
11 | }) | 11 | }) |
12 | export class VideoTrendingComponent extends AbstractVideoList implements OnInit, OnDestroy { | 12 | export class VideoTrendingComponent extends AbstractVideoList implements OnInit, OnDestroy { |
13 | titlePage = 'Trending' | 13 | titlePage = 'Trending' |
diff --git a/client/src/app/videos/videos.module.ts b/client/src/app/videos/videos.module.ts index 1d6194158..6d846fd3b 100644 --- a/client/src/app/videos/videos.module.ts +++ b/client/src/app/videos/videos.module.ts | |||
@@ -1,7 +1,5 @@ | |||
1 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | import { InfiniteScrollModule } from 'ngx-infinite-scroll' | ||
3 | import { SharedModule } from '../shared' | 2 | import { SharedModule } from '../shared' |
4 | import { VideoService } from './shared' | ||
5 | import { VideoMiniatureComponent } from './video-list' | 3 | import { VideoMiniatureComponent } from './video-list' |
6 | import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' | 4 | import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' |
7 | import { VideoTrendingComponent } from './video-list/video-trending.component' | 5 | import { VideoTrendingComponent } from './video-list/video-trending.component' |
@@ -11,8 +9,7 @@ import { VideosComponent } from './videos.component' | |||
11 | @NgModule({ | 9 | @NgModule({ |
12 | imports: [ | 10 | imports: [ |
13 | VideosRoutingModule, | 11 | VideosRoutingModule, |
14 | SharedModule, | 12 | SharedModule |
15 | InfiniteScrollModule | ||
16 | ], | 13 | ], |
17 | 14 | ||
18 | declarations: [ | 15 | declarations: [ |
@@ -27,8 +24,6 @@ import { VideosComponent } from './videos.component' | |||
27 | VideosComponent | 24 | VideosComponent |
28 | ], | 25 | ], |
29 | 26 | ||
30 | providers: [ | 27 | providers: [] |
31 | VideoService | ||
32 | ] | ||
33 | }) | 28 | }) |
34 | export class VideosModule { } | 29 | export class VideosModule { } |