Begin videos of an account
authorChocobozzz <florian.bigard@gmail.com>
Fri, 1 Dec 2017 17:56:26 +0000 (18:56 +0100)
committerChocobozzz <florian.bigard@gmail.com>
Fri, 1 Dec 2017 17:56:26 +0000 (18:56 +0100)
43 files changed:
client/src/app/account/account-routing.module.ts
client/src/app/account/account-videos/account-videos.component.html [new file with mode: 0644]
client/src/app/account/account-videos/account-videos.component.scss [new file with mode: 0644]
client/src/app/account/account-videos/account-videos.component.ts [new file with mode: 0644]
client/src/app/account/account.module.ts
client/src/app/shared/shared.module.ts
client/src/app/shared/video/abstract-video-list.html [new file with mode: 0644]
client/src/app/shared/video/abstract-video-list.scss [new file with mode: 0644]
client/src/app/shared/video/abstract-video-list.ts [new file with mode: 0644]
client/src/app/shared/video/sort-field.type.ts [new file with mode: 0644]
client/src/app/shared/video/video-details.model.ts [new file with mode: 0644]
client/src/app/shared/video/video-edit.model.ts [new file with mode: 0644]
client/src/app/shared/video/video-pagination.model.ts [new file with mode: 0644]
client/src/app/shared/video/video-thumbnail.component.html [new file with mode: 0644]
client/src/app/shared/video/video-thumbnail.component.scss [new file with mode: 0644]
client/src/app/shared/video/video-thumbnail.component.ts [new file with mode: 0644]
client/src/app/shared/video/video.model.ts [new file with mode: 0644]
client/src/app/shared/video/video.service.ts [new file with mode: 0644]
client/src/app/videos/+video-edit/shared/video-edit.module.ts
client/src/app/videos/+video-edit/video-add.component.ts
client/src/app/videos/+video-edit/video-update.component.ts
client/src/app/videos/+video-watch/video-download.component.ts
client/src/app/videos/+video-watch/video-report.component.ts
client/src/app/videos/+video-watch/video-share.component.ts
client/src/app/videos/+video-watch/video-watch.component.ts
client/src/app/videos/+video-watch/video-watch.module.ts
client/src/app/videos/shared/index.ts
client/src/app/videos/shared/sort-field.type.ts [deleted file]
client/src/app/videos/shared/video-details.model.ts [deleted file]
client/src/app/videos/shared/video-edit.model.ts [deleted file]
client/src/app/videos/shared/video-pagination.model.ts [deleted file]
client/src/app/videos/shared/video.model.ts [deleted file]
client/src/app/videos/shared/video.service.ts [deleted file]
client/src/app/videos/video-list/shared/abstract-video-list.html [deleted file]
client/src/app/videos/video-list/shared/abstract-video-list.scss [deleted file]
client/src/app/videos/video-list/shared/abstract-video-list.ts [deleted file]
client/src/app/videos/video-list/shared/index.ts
client/src/app/videos/video-list/shared/video-miniature.component.html
client/src/app/videos/video-list/shared/video-miniature.component.scss
client/src/app/videos/video-list/shared/video-miniature.component.ts
client/src/app/videos/video-list/video-recently-added.component.ts
client/src/app/videos/video-list/video-trending.component.ts
client/src/app/videos/videos.module.ts

index 2e9de1cfb19dacd7c5c57c9782749e73a929f0a8..070b9b5c5cce96d3356bcfc3de47292e621f7fde 100644 (file)
@@ -6,6 +6,7 @@ import { MetaGuard } from '@ngx-meta/core'
 import { LoginGuard } from '../core'
 import { AccountComponent } from './account.component'
 import { AccountSettingsComponent } from './account-settings/account-settings.component'
+import { AccountVideosComponent } from './account-videos/account-videos.component'
 
 const accountRoutes: Routes = [
   {
@@ -22,15 +23,15 @@ const accountRoutes: Routes = [
           }
         }
       },
-      // {
-      //   path: 'videos',
-      //   component: AccountVideosComponent,
-      //   data: {
-      //     meta: {
-      //       title: 'Account videos'
-      //     }
-      //   }
-      // }
+      {
+        path: 'videos',
+        component: AccountVideosComponent,
+        data: {
+          meta: {
+            title: 'Account videos'
+          }
+        }
+      }
     ]
   }
 ]
diff --git a/client/src/app/account/account-videos/account-videos.component.html b/client/src/app/account/account-videos/account-videos.component.html
new file mode 100644 (file)
index 0000000..6c8ac45
--- /dev/null
@@ -0,0 +1,9 @@
+<div
+  infiniteScroll
+  [infiniteScrollDistance]="0.5"
+  (scrolled)="onNearOfBottom()"
+>
+  <div *ngFor="let video of videos">
+    <my-video-thumbnail [video]="video"></my-video-thumbnail>
+  </div>
+</div>
diff --git a/client/src/app/account/account-videos/account-videos.component.scss b/client/src/app/account/account-videos/account-videos.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/client/src/app/account/account-videos/account-videos.component.ts b/client/src/app/account/account-videos/account-videos.component.ts
new file mode 100644 (file)
index 0000000..ff94582
--- /dev/null
@@ -0,0 +1,35 @@
+import { Component, OnDestroy, OnInit } from '@angular/core'
+import { AbstractVideoList } from '../../shared/video/abstract-video-list'
+import { ActivatedRoute } from '@angular/router'
+import { Router } from '@angular/router'
+import { NotificationsService } from 'angular2-notifications'
+import { VideoService } from '../../shared/video/video.service'
+
+@Component({
+  selector: 'my-account-videos',
+  templateUrl: './account-videos.component.html',
+  styleUrls: [ './account-videos.component.scss' ]
+})
+export class AccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy {
+  titlePage = 'My videos'
+  currentRoute = '/account/videos'
+
+  constructor (protected router: Router,
+               protected route: ActivatedRoute,
+               protected notificationsService: NotificationsService,
+               private videoService: VideoService) {
+    super()
+  }
+
+  ngOnInit () {
+    super.ngOnInit()
+  }
+
+  ngOnDestroy () {
+    super.ngOnDestroy()
+  }
+
+  getVideosObservable () {
+    return this.videoService.getMyVideos(this.pagination, this.sort)
+  }
+}
index ff444ddeb15ec43d1e868e8e92891d9585ab0956..020199e23d8ae182bfa9f8074dd7ed341fef3fb1 100644 (file)
@@ -6,6 +6,7 @@ import { AccountDetailsComponent } from './account-settings/account-details/acco
 import { AccountSettingsComponent } from './account-settings/account-settings.component'
 import { AccountComponent } from './account.component'
 import { AccountService } from './account.service'
+import { AccountVideosComponent } from './account-videos/account-videos.component'
 
 @NgModule({
   imports: [
@@ -17,7 +18,8 @@ import { AccountService } from './account.service'
     AccountComponent,
     AccountSettingsComponent,
     AccountChangePasswordComponent,
-    AccountDetailsComponent
+    AccountDetailsComponent,
+    AccountVideosComponent
   ],
 
   exports: [
index 7618748e9820054d33d48481f7393e61689e1f28..e76f7636aa6e9355fc3856d489174f7723b01b04 100644 (file)
@@ -20,6 +20,9 @@ import { SearchComponent, SearchService } from './search'
 import { UserService } from './users'
 import { VideoAbuseService } from './video-abuse'
 import { VideoBlacklistService } from './video-blacklist'
+import { VideoThumbnailComponent } from './video/video-thumbnail.component'
+import { VideoService } from './video/video.service'
+import { InfiniteScrollModule } from 'ngx-infinite-scroll'
 
 @NgModule({
   imports: [
@@ -34,7 +37,8 @@ import { VideoBlacklistService } from './video-blacklist'
     ProgressbarModule.forRoot(),
 
     DataTableModule,
-    PrimeSharedModule
+    PrimeSharedModule,
+    InfiniteScrollModule
   ],
 
   declarations: [
@@ -42,6 +46,7 @@ import { VideoBlacklistService } from './video-blacklist'
     KeysPipe,
     SearchComponent,
     LoaderComponent,
+    VideoThumbnailComponent,
     NumberFormatterPipe,
     FromNowPipe
   ],
@@ -58,11 +63,13 @@ import { VideoBlacklistService } from './video-blacklist'
     ProgressbarModule,
     DataTableModule,
     PrimeSharedModule,
+    InfiniteScrollModule,
     BytesPipe,
     KeysPipe,
 
     SearchComponent,
     LoaderComponent,
+    VideoThumbnailComponent,
 
     NumberFormatterPipe,
     FromNowPipe
@@ -75,7 +82,8 @@ import { VideoBlacklistService } from './video-blacklist'
     SearchService,
     VideoAbuseService,
     VideoBlacklistService,
-    UserService
+    UserService,
+    VideoService
   ]
 })
 export class SharedModule { }
diff --git a/client/src/app/shared/video/abstract-video-list.html b/client/src/app/shared/video/abstract-video-list.html
new file mode 100644 (file)
index 0000000..bd4f6b1
--- /dev/null
@@ -0,0 +1,19 @@
+<div class="margin-content">
+  <div class="title-page title-page-single">
+    {{ titlePage }}
+  </div>
+
+  <div
+    infiniteScroll
+    [infiniteScrollUpDistance]="1.5"
+    [infiniteScrollDistance]="0.5"
+    (scrolled)="onNearOfBottom()"
+    (scrolledUp)="onNearOfTop()"
+  >
+    <my-video-miniature
+      class="ng-animate"
+      *ngFor="let video of videos" [video]="video" [user]="user" [currentSort]="sort"
+    >
+    </my-video-miniature>
+  </div>
+</div>
diff --git a/client/src/app/shared/video/abstract-video-list.scss b/client/src/app/shared/video/abstract-video-list.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts
new file mode 100644 (file)
index 0000000..cf717cf
--- /dev/null
@@ -0,0 +1,121 @@
+import { OnDestroy, OnInit } from '@angular/core'
+import { ActivatedRoute, Router } from '@angular/router'
+import { NotificationsService } from 'angular2-notifications'
+import { Observable } from 'rxjs/Observable'
+import { Subscription } from 'rxjs/Subscription'
+import { SortField } from './sort-field.type'
+import { VideoPagination } from './video-pagination.model'
+import { Video } from './video.model'
+
+export abstract class AbstractVideoList implements OnInit, OnDestroy {
+  pagination: VideoPagination = {
+    currentPage: 1,
+    itemsPerPage: 25,
+    totalItems: null
+  }
+  sort: SortField = '-createdAt'
+  videos: Video[] = []
+
+  protected notificationsService: NotificationsService
+  protected router: Router
+  protected route: ActivatedRoute
+  protected subActivatedRoute: Subscription
+
+  protected abstract currentRoute: string
+
+  abstract titlePage: string
+  private loadedPages: { [ id: number ]: boolean } = {}
+
+  abstract getVideosObservable (): Observable<{ videos: Video[], totalVideos: number}>
+
+  ngOnInit () {
+    // Subscribe to route changes
+    const routeParams = this.route.snapshot.params
+    this.loadRouteParams(routeParams)
+    this.loadMoreVideos('after')
+  }
+
+  ngOnDestroy () {
+    if (this.subActivatedRoute) {
+      this.subActivatedRoute.unsubscribe()
+    }
+  }
+
+  onNearOfTop () {
+    if (this.pagination.currentPage > 1) {
+      this.previousPage()
+    }
+  }
+
+  onNearOfBottom () {
+    if (this.hasMoreVideos()) {
+      this.nextPage()
+    }
+  }
+
+  loadMoreVideos (where: 'before' | 'after') {
+    if (this.loadedPages[this.pagination.currentPage] === true) return
+
+    const observable = this.getVideosObservable()
+
+    observable.subscribe(
+      ({ videos, totalVideos }) => {
+        this.loadedPages[this.pagination.currentPage] = true
+        this.pagination.totalItems = totalVideos
+
+        if (where === 'before') {
+          this.videos = videos.concat(this.videos)
+        } else {
+          this.videos = this.videos.concat(videos)
+        }
+      },
+      error => this.notificationsService.error('Error', error.text)
+    )
+  }
+
+  protected hasMoreVideos () {
+    if (!this.pagination.totalItems) return true
+
+    const maxPage = this.pagination.totalItems / this.pagination.itemsPerPage
+    return maxPage > this.pagination.currentPage
+  }
+
+  protected previousPage () {
+    this.pagination.currentPage--
+
+    this.setNewRouteParams()
+    this.loadMoreVideos('before')
+  }
+
+  protected nextPage () {
+    this.pagination.currentPage++
+
+    this.setNewRouteParams()
+    this.loadMoreVideos('after')
+  }
+
+  protected buildRouteParams () {
+    // There is always a sort and a current page
+    const params = {
+      sort: this.sort,
+      page: this.pagination.currentPage
+    }
+
+    return params
+  }
+
+  protected loadRouteParams (routeParams: { [ key: string ]: any }) {
+    this.sort = routeParams['sort'] as SortField || '-createdAt'
+
+    if (routeParams['page'] !== undefined) {
+      this.pagination.currentPage = parseInt(routeParams['page'], 10)
+    } else {
+      this.pagination.currentPage = 1
+    }
+  }
+
+  protected setNewRouteParams () {
+    const routeParams = this.buildRouteParams()
+    this.router.navigate([ this.currentRoute, routeParams ])
+  }
+}
diff --git a/client/src/app/shared/video/sort-field.type.ts b/client/src/app/shared/video/sort-field.type.ts
new file mode 100644 (file)
index 0000000..776f360
--- /dev/null
@@ -0,0 +1,5 @@
+export type SortField = 'name' | '-name'
+                      | 'duration' | '-duration'
+                      | 'createdAt' | '-createdAt'
+                      | 'views' | '-views'
+                      | 'likes' | '-likes'
diff --git a/client/src/app/shared/video/video-details.model.ts b/client/src/app/shared/video/video-details.model.ts
new file mode 100644 (file)
index 0000000..93c380b
--- /dev/null
@@ -0,0 +1,84 @@
+import { Video } from '../../shared/video/video.model'
+import { AuthUser } from '../../core'
+import {
+  VideoDetails as VideoDetailsServerModel,
+  VideoFile,
+  VideoChannel,
+  VideoResolution,
+  UserRight,
+  VideoPrivacy
+} from '../../../../../shared'
+
+export class VideoDetails extends Video implements VideoDetailsServerModel {
+  account: string
+  by: string
+  createdAt: Date
+  updatedAt: Date
+  categoryLabel: string
+  category: number
+  licenceLabel: string
+  licence: number
+  languageLabel: string
+  language: number
+  description: string
+  duration: number
+  durationLabel: string
+  id: number
+  uuid: string
+  isLocal: boolean
+  name: string
+  serverHost: string
+  tags: string[]
+  thumbnailPath: string
+  thumbnailUrl: string
+  previewPath: string
+  previewUrl: string
+  embedPath: string
+  embedUrl: string
+  views: number
+  likes: number
+  dislikes: number
+  nsfw: boolean
+  descriptionPath: string
+  files: VideoFile[]
+  channel: VideoChannel
+  privacy: VideoPrivacy
+  privacyLabel: string
+
+  constructor (hash: VideoDetailsServerModel) {
+    super(hash)
+
+    this.privacy = hash.privacy
+    this.privacyLabel = hash.privacyLabel
+    this.descriptionPath = hash.descriptionPath
+    this.files = hash.files
+    this.channel = hash.channel
+  }
+
+  getAppropriateMagnetUri (actualDownloadSpeed = 0) {
+    if (this.files === undefined || this.files.length === 0) return ''
+    if (this.files.length === 1) return this.files[0].magnetUri
+
+    // Find first video that is good for our download speed (remember they are sorted)
+    let betterResolutionFile = this.files.find(f => actualDownloadSpeed > (f.size / this.duration))
+
+    // If the download speed is too bad, return the lowest resolution we have
+    if (betterResolutionFile === undefined) {
+      betterResolutionFile = this.files.find(f => f.resolution === VideoResolution.H_240P)
+    }
+
+    return betterResolutionFile.magnetUri
+  }
+
+  isRemovableBy (user: AuthUser) {
+    return user && this.isLocal === true && (this.account === user.username || user.hasRight(UserRight.REMOVE_ANY_VIDEO))
+  }
+
+  isBlackistableBy (user: AuthUser) {
+    return user && user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) === true && this.isLocal === false
+  }
+
+  isUpdatableBy (user: AuthUser) {
+    return user && this.isLocal === true && user.username === this.account
+  }
+}
diff --git a/client/src/app/shared/video/video-edit.model.ts b/client/src/app/shared/video/video-edit.model.ts
new file mode 100644 (file)
index 0000000..88d23a5
--- /dev/null
@@ -0,0 +1,50 @@
+import { VideoDetails } from './video-details.model'
+import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum'
+
+export class VideoEdit {
+  category: number
+  licence: number
+  language: number
+  description: string
+  name: string
+  tags: string[]
+  nsfw: boolean
+  channel: number
+  privacy: VideoPrivacy
+  uuid?: string
+  id?: number
+
+  constructor (videoDetails: VideoDetails) {
+    this.id = videoDetails.id
+    this.uuid = videoDetails.uuid
+    this.category = videoDetails.category
+    this.licence = videoDetails.licence
+    this.language = videoDetails.language
+    this.description = videoDetails.description
+    this.name = videoDetails.name
+    this.tags = videoDetails.tags
+    this.nsfw = videoDetails.nsfw
+    this.channel = videoDetails.channel.id
+    this.privacy = videoDetails.privacy
+  }
+
+  patch (values: Object) {
+    Object.keys(values).forEach((key) => {
+      this[key] = values[key]
+    })
+  }
+
+  toJSON () {
+    return {
+      category: this.category,
+      licence: this.licence,
+      language: this.language,
+      description: this.description,
+      name: this.name,
+      tags: this.tags,
+      nsfw: this.nsfw,
+      channel: this.channel,
+      privacy: this.privacy
+    }
+  }
+}
diff --git a/client/src/app/shared/video/video-pagination.model.ts b/client/src/app/shared/video/video-pagination.model.ts
new file mode 100644 (file)
index 0000000..9e71769
--- /dev/null
@@ -0,0 +1,5 @@
+export interface VideoPagination {
+  currentPage: number
+  itemsPerPage: number
+  totalItems: number
+}
diff --git a/client/src/app/shared/video/video-thumbnail.component.html b/client/src/app/shared/video/video-thumbnail.component.html
new file mode 100644 (file)
index 0000000..5c698e8
--- /dev/null
@@ -0,0 +1,10 @@
+<a
+  [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name"
+class="video-thumbnail"
+>
+<img [attr.src]="video.thumbnailUrl" alt="video thumbnail" [ngClass]="{ 'blur-filter': nsfw }" />
+
+<div class="video-thumbnail-overlay">
+  {{ video.durationLabel }}
+</div>
+</a>
diff --git a/client/src/app/shared/video/video-thumbnail.component.scss b/client/src/app/shared/video/video-thumbnail.component.scss
new file mode 100644 (file)
index 0000000..ab4f9bc
--- /dev/null
@@ -0,0 +1,28 @@
+.video-thumbnail {
+  display: inline-block;
+  position: relative;
+  border-radius: 4px;
+  overflow: hidden;
+
+  &:hover {
+    text-decoration: none !important;
+  }
+
+  img.blur-filter {
+    filter: blur(5px);
+    transform : scale(1.03);
+  }
+
+  .video-thumbnail-overlay {
+    position: absolute;
+    right: 5px;
+    bottom: 5px;
+    display: inline-block;
+    background-color: rgba(0, 0, 0, 0.7);
+    color: #fff;
+    font-size: 12px;
+    font-weight: $font-bold;
+    border-radius: 3px;
+    padding: 0 5px;
+  }
+}
diff --git a/client/src/app/shared/video/video-thumbnail.component.ts b/client/src/app/shared/video/video-thumbnail.component.ts
new file mode 100644 (file)
index 0000000..e543e99
--- /dev/null
@@ -0,0 +1,12 @@
+import { Component, Input } from '@angular/core'
+import { Video } from './video.model'
+
+@Component({
+  selector: 'my-video-thumbnail',
+  styleUrls: [ './video-thumbnail.component.scss' ],
+  templateUrl: './video-thumbnail.component.html'
+})
+export class VideoThumbnailComponent {
+  @Input() video: Video
+  @Input() nsfw = false
+}
diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/video/video.model.ts
new file mode 100644 (file)
index 0000000..6929c87
--- /dev/null
@@ -0,0 +1,90 @@
+import { Video as VideoServerModel } from '../../../../../shared'
+import { User } from '../'
+
+export class Video implements VideoServerModel {
+  account: string
+  by: string
+  createdAt: Date
+  updatedAt: Date
+  categoryLabel: string
+  category: number
+  licenceLabel: string
+  licence: number
+  languageLabel: string
+  language: number
+  description: string
+  duration: number
+  durationLabel: string
+  id: number
+  uuid: string
+  isLocal: boolean
+  name: string
+  serverHost: string
+  tags: string[]
+  thumbnailPath: string
+  thumbnailUrl: string
+  previewPath: string
+  previewUrl: string
+  embedPath: string
+  embedUrl: string
+  views: number
+  likes: number
+  dislikes: number
+  nsfw: boolean
+
+  private static createByString (account: string, serverHost: string) {
+    return account + '@' + serverHost
+  }
+
+  private static createDurationString (duration: number) {
+    const minutes = Math.floor(duration / 60)
+    const seconds = duration % 60
+    const minutesPadding = minutes >= 10 ? '' : '0'
+    const secondsPadding = seconds >= 10 ? '' : '0'
+
+    return minutesPadding + minutes.toString() + ':' + secondsPadding + seconds.toString()
+  }
+
+  constructor (hash: VideoServerModel) {
+    let absoluteAPIUrl = API_URL
+    if (!absoluteAPIUrl) {
+      // The API is on the same domain
+      absoluteAPIUrl = window.location.origin
+    }
+
+    this.account = hash.account
+    this.createdAt = new Date(hash.createdAt.toString())
+    this.categoryLabel = hash.categoryLabel
+    this.category = hash.category
+    this.licenceLabel = hash.licenceLabel
+    this.licence = hash.licence
+    this.languageLabel = hash.languageLabel
+    this.language = hash.language
+    this.description = hash.description
+    this.duration = hash.duration
+    this.durationLabel = Video.createDurationString(hash.duration)
+    this.id = hash.id
+    this.uuid = hash.uuid
+    this.isLocal = hash.isLocal
+    this.name = hash.name
+    this.serverHost = hash.serverHost
+    this.tags = hash.tags
+    this.thumbnailPath = hash.thumbnailPath
+    this.thumbnailUrl = absoluteAPIUrl + hash.thumbnailPath
+    this.previewPath = hash.previewPath
+    this.previewUrl = absoluteAPIUrl + hash.previewPath
+    this.embedPath = hash.embedPath
+    this.embedUrl = absoluteAPIUrl + hash.embedPath
+    this.views = hash.views
+    this.likes = hash.likes
+    this.dislikes = hash.dislikes
+    this.nsfw = hash.nsfw
+
+    this.by = Video.createByString(hash.account, hash.serverHost)
+  }
+
+  isVideoNSFWForUser (user: User) {
+    // If the video is NSFW and the user is not logged in, or the user does not want to display NSFW videos...
+    return (this.nsfw && (!user || user.displayNSFW === false))
+  }
+}
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts
new file mode 100644 (file)
index 0000000..b2a2641
--- /dev/null
@@ -0,0 +1,170 @@
+import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http'
+import { Injectable } from '@angular/core'
+import 'rxjs/add/operator/catch'
+import 'rxjs/add/operator/map'
+import { Observable } from 'rxjs/Observable'
+import { Video as VideoServerModel, VideoDetails as VideoDetailsServerModel } from '../../../../../shared'
+import { ResultList } from '../../../../../shared/models/result-list.model'
+import { UserVideoRateUpdate } from '../../../../../shared/models/videos/user-video-rate-update.model'
+import { UserVideoRate } from '../../../../../shared/models/videos/user-video-rate.model'
+import { VideoRateType } from '../../../../../shared/models/videos/video-rate.type'
+import { VideoUpdate } from '../../../../../shared/models/videos/video-update.model'
+import { RestExtractor } from '../rest/rest-extractor.service'
+import { RestService } from '../rest/rest.service'
+import { Search } from '../search/search.model'
+import { UserService } from '../users/user.service'
+import { SortField } from './sort-field.type'
+import { VideoDetails } from './video-details.model'
+import { VideoEdit } from './video-edit.model'
+import { VideoPagination } from './video-pagination.model'
+import { Video } from './video.model'
+
+@Injectable()
+export class VideoService {
+  private static BASE_VIDEO_URL = API_URL + '/api/v1/videos/'
+
+  constructor (
+    private authHttp: HttpClient,
+    private restExtractor: RestExtractor,
+    private restService: RestService
+  ) {}
+
+  getVideo (uuid: string): Observable<VideoDetails> {
+    return this.authHttp.get<VideoDetailsServerModel>(VideoService.BASE_VIDEO_URL + uuid)
+                        .map(videoHash => new VideoDetails(videoHash))
+                        .catch((res) => this.restExtractor.handleError(res))
+  }
+
+  viewVideo (uuid: string): Observable<VideoDetails> {
+    return this.authHttp.post(VideoService.BASE_VIDEO_URL + uuid + '/views', {})
+      .map(this.restExtractor.extractDataBool)
+      .catch(this.restExtractor.handleError)
+  }
+
+  updateVideo (video: VideoEdit) {
+    const language = video.language ? video.language : null
+
+    const body: VideoUpdate = {
+      name: video.name,
+      category: video.category,
+      licence: video.licence,
+      language,
+      description: video.description,
+      privacy: video.privacy,
+      tags: video.tags,
+      nsfw: video.nsfw
+    }
+
+    return this.authHttp.put(VideoService.BASE_VIDEO_URL + video.id, body)
+                        .map(this.restExtractor.extractDataBool)
+                        .catch(this.restExtractor.handleError)
+  }
+
+  uploadVideo (video: FormData) {
+    const req = new HttpRequest('POST', VideoService.BASE_VIDEO_URL + 'upload', video, { reportProgress: true })
+
+    return this.authHttp
+      .request(req)
+      .catch(this.restExtractor.handleError)
+  }
+
+  getMyVideos (videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> {
+    const pagination = this.videoPaginationToRestPagination(videoPagination)
+
+    let params = new HttpParams()
+    params = this.restService.addRestGetParams(params, pagination, sort)
+
+    return this.authHttp.get(UserService.BASE_USERS_URL + '/me/videos', { params })
+      .map(this.extractVideos)
+      .catch((res) => this.restExtractor.handleError(res))
+  }
+
+  getVideos (videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> {
+    const pagination = this.videoPaginationToRestPagination(videoPagination)
+
+    let params = new HttpParams()
+    params = this.restService.addRestGetParams(params, pagination, sort)
+
+    return this.authHttp
+      .get(VideoService.BASE_VIDEO_URL, { params })
+      .map(this.extractVideos)
+      .catch((res) => this.restExtractor.handleError(res))
+  }
+
+  searchVideos (search: Search, videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> {
+    const url = VideoService.BASE_VIDEO_URL + 'search/' + encodeURIComponent(search.value)
+
+    const pagination = this.videoPaginationToRestPagination(videoPagination)
+
+    let params = new HttpParams()
+    params = this.restService.addRestGetParams(params, pagination, sort)
+
+    if (search.field) params.set('field', search.field)
+
+    return this.authHttp
+      .get<ResultList<VideoServerModel>>(url, { params })
+      .map(this.extractVideos)
+      .catch((res) => this.restExtractor.handleError(res))
+  }
+
+  removeVideo (id: number) {
+    return this.authHttp
+      .delete(VideoService.BASE_VIDEO_URL + id)
+      .map(this.restExtractor.extractDataBool)
+      .catch((res) => this.restExtractor.handleError(res))
+  }
+
+  loadCompleteDescription (descriptionPath: string) {
+    return this.authHttp
+      .get(API_URL + descriptionPath)
+      .map(res => res['description'])
+      .catch((res) => this.restExtractor.handleError(res))
+  }
+
+  setVideoLike (id: number) {
+    return this.setVideoRate(id, 'like')
+  }
+
+  setVideoDislike (id: number) {
+    return this.setVideoRate(id, 'dislike')
+  }
+
+  getUserVideoRating (id: number): Observable<UserVideoRate> {
+    const url = UserService.BASE_USERS_URL + 'me/videos/' + id + '/rating'
+
+    return this.authHttp
+      .get(url)
+      .catch(res => this.restExtractor.handleError(res))
+  }
+
+  private videoPaginationToRestPagination (videoPagination: VideoPagination) {
+    const start: number = (videoPagination.currentPage - 1) * videoPagination.itemsPerPage
+    const count: number = videoPagination.itemsPerPage
+
+    return { start, count }
+  }
+
+  private setVideoRate (id: number, rateType: VideoRateType) {
+    const url = VideoService.BASE_VIDEO_URL + id + '/rate'
+    const body: UserVideoRateUpdate = {
+      rating: rateType
+    }
+
+    return this.authHttp
+      .put(url, body)
+      .map(this.restExtractor.extractDataBool)
+      .catch(res => this.restExtractor.handleError(res))
+  }
+
+  private extractVideos (result: ResultList<VideoServerModel>) {
+    const videosJson = result.data
+    const totalVideos = result.total
+    const videos = []
+
+    for (const videoJson of videosJson) {
+      videos.push(new Video(videoJson))
+    }
+
+    return { videos, totalVideos }
+  }
+}
index c64cea9203cd1a086b2b76d4c563b95a109e3ad7..cdab694f9a6d78a4e703dfc9405b7633c1e9b823 100644 (file)
@@ -3,7 +3,7 @@ import { NgModule } from '@angular/core'
 import { TagInputModule } from 'ngx-chips'
 import { TabsModule } from 'ngx-bootstrap/tabs'
 
-import { VideoService, MarkdownService, VideoDescriptionComponent } from '../../shared'
+import { MarkdownService, VideoDescriptionComponent } from '../../shared'
 import { SharedModule } from '../../../shared'
 
 @NgModule({
@@ -26,7 +26,6 @@ import { SharedModule } from '../../../shared'
   ],
 
   providers: [
-    VideoService,
     MarkdownService
   ]
 })
index 76bfbb515c5ae52bec675757c933f83d57278246..989addbd703b2f3e2f6f52fcb19c0a43ad4a76df 100644 (file)
@@ -1,25 +1,23 @@
+import { HttpEventType, HttpResponse } from '@angular/common/http'
 import { Component, OnInit, ViewChild } from '@angular/core'
 import { FormBuilder, FormGroup } from '@angular/forms'
 import { Router } from '@angular/router'
-
 import { NotificationsService } from 'angular2-notifications'
-
+import { VideoService } from 'app/shared/video/video.service'
+import { VideoCreate } from '../../../../../shared'
+import { AuthService, ServerService } from '../../core'
 import {
   FormReactive,
-  VIDEO_NAME,
   VIDEO_CATEGORY,
-  VIDEO_LICENCE,
-  VIDEO_LANGUAGE,
-  VIDEO_DESCRIPTION,
-  VIDEO_TAGS,
   VIDEO_CHANNEL,
+  VIDEO_DESCRIPTION,
   VIDEO_FILE,
-  VIDEO_PRIVACY
+  VIDEO_LANGUAGE,
+  VIDEO_LICENCE,
+  VIDEO_NAME,
+  VIDEO_PRIVACY,
+  VIDEO_TAGS
 } from '../../shared'
-import { AuthService, ServerService } from '../../core'
-import { VideoService } from '../shared'
-import { VideoCreate } from '../../../../../shared'
-import { HttpEventType, HttpResponse } from '@angular/common/http'
 
 @Component({
   selector: 'my-videos-add',
index 0e966cb509e6163eec76259b25f60d1a7c43ebdd..0177818662c2147f528fce4b331a2fc2d8bb9f93 100644 (file)
@@ -1,23 +1,22 @@
 import { Component, OnInit } from '@angular/core'
 import { FormBuilder, FormGroup } from '@angular/forms'
 import { ActivatedRoute, Router } from '@angular/router'
-import 'rxjs/add/observable/forkJoin'
-
 import { NotificationsService } from 'angular2-notifications'
-
+import 'rxjs/add/observable/forkJoin'
+import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum'
 import { ServerService } from '../../core'
 import {
   FormReactive,
-  VIDEO_NAME,
   VIDEO_CATEGORY,
-  VIDEO_LICENCE,
-  VIDEO_LANGUAGE,
   VIDEO_DESCRIPTION,
-  VIDEO_TAGS,
-  VIDEO_PRIVACY
+  VIDEO_LANGUAGE,
+  VIDEO_LICENCE,
+  VIDEO_NAME,
+  VIDEO_PRIVACY,
+  VIDEO_TAGS
 } from '../../shared'
-import { VideoEdit, VideoService } from '../shared'
-import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum'
+import { VideoService } from '../../shared/video/video.service'
+import { VideoEdit } from '../../shared/video/video-edit.model'
 
 @Component({
   selector: 'my-videos-update',
index c32f8d586b162dde50453142e26c4aafbb6bafdc..010a246cd5130dca9869a2968617a1b06aea90de 100644 (file)
@@ -1,8 +1,6 @@
 import { Component, Input, ViewChild } from '@angular/core'
-
 import { ModalDirective } from 'ngx-bootstrap/modal'
-
-import { VideoDetails } from '../shared'
+import { VideoDetails } from '../../shared/video/video-details.model'
 
 @Component({
   selector: 'my-video-download',
index fc9b5a9d4b6cab7420fe4256d25f06dfbe88494f..b94e4144e83d4417979d064695f447588659dcc0 100644 (file)
@@ -1,11 +1,9 @@
 import { Component, Input, OnInit, ViewChild } from '@angular/core'
 import { FormBuilder, FormGroup } from '@angular/forms'
-
-import { ModalDirective } from 'ngx-bootstrap/modal'
 import { NotificationsService } from 'angular2-notifications'
-
-import { FormReactive, VideoAbuseService, VIDEO_ABUSE_REASON } from '../../shared'
-import { VideoDetails, VideoService } from '../shared'
+import { ModalDirective } from 'ngx-bootstrap/modal'
+import { FormReactive, VIDEO_ABUSE_REASON, VideoAbuseService } from '../../shared'
+import { VideoDetails } from '../../shared/video/video-details.model'
 
 @Component({
   selector: 'my-video-report',
index aeef65ecfbd5a87b8b6c8083926a569dc3c3aeae..4df9adf29dea3faa364ad0e7d94fd41e52dc2c26 100644 (file)
@@ -1,8 +1,6 @@
 import { Component, Input, ViewChild } from '@angular/core'
-
 import { ModalDirective } from 'ngx-bootstrap/modal'
-
-import { VideoDetails } from '../shared'
+import { VideoDetails } from '../../shared/video/video-details.model'
 
 @Component({
   selector: 'my-video-share',
index b26f3092fcbd2ee06742407c125097ac7bddc30b..eac676be840cbcc57abb2af77c4fbd76d84ff210 100644 (file)
@@ -2,6 +2,7 @@ import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/co
 import { ActivatedRoute, Router } from '@angular/router'
 import { MetaService } from '@ngx-meta/core'
 import { NotificationsService } from 'angular2-notifications'
+import { VideoService } from 'app/shared/video/video.service'
 import { Observable } from 'rxjs/Observable'
 import { Subscription } from 'rxjs/Subscription'
 import videojs from 'video.js'
@@ -9,10 +10,11 @@ import { UserVideoRateType, VideoRateType } from '../../../../../shared'
 import '../../../assets/player/peertube-videojs-plugin'
 import { AuthService, ConfirmService } from '../../core'
 import { VideoBlacklistService } from '../../shared'
-import { MarkdownService, VideoDetails, VideoService } from '../shared'
+import { MarkdownService } from '../shared'
 import { VideoDownloadComponent } from './video-download.component'
 import { VideoReportComponent } from './video-report.component'
 import { VideoShareComponent } from './video-share.component'
+import { VideoDetails } from '../../shared/video/video-details.model'
 
 @Component({
   selector: 'my-video-watch',
index 1b983200db3824dd307aa7516bdca98fdcb18834..0b1dd5c15ebf47b542f5dbe3c886911f9d10608c 100644 (file)
@@ -1,7 +1,7 @@
 import { NgModule } from '@angular/core'
 
 import { VideoWatchRoutingModule } from './video-watch-routing.module'
-import { VideoService, MarkdownService } from '../shared'
+import { MarkdownService } from '../shared'
 import { SharedModule } from '../../shared'
 
 import { VideoWatchComponent } from './video-watch.component'
@@ -28,8 +28,7 @@ import { VideoDownloadComponent } from './video-download.component'
   ],
 
   providers: [
-    MarkdownService,
-    VideoService
+    MarkdownService
   ]
 })
 export class VideoWatchModule { }
index 3f14580885b291b750a665962d88cf21a5be7033..3c72ed895e6e4dfd0f721f250205ac8ddb361e4a 100644 (file)
@@ -1,8 +1,2 @@
-export * from './sort-field.type'
 export * from './markdown.service'
-export * from './video.model'
-export * from './video-details.model'
-export * from './video-edit.model'
-export * from './video.service'
 export * from './video-description.component'
-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 (file)
index 776f360..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-export type SortField = 'name' | '-name'
-                      | 'duration' | '-duration'
-                      | 'createdAt' | '-createdAt'
-                      | 'views' | '-views'
-                      | '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 (file)
index 64cb4f8..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-import { Video } from './video.model'
-import { AuthUser } from '../../core'
-import {
-  VideoDetails as VideoDetailsServerModel,
-  VideoFile,
-  VideoChannel,
-  VideoResolution,
-  UserRight,
-  VideoPrivacy
-} from '../../../../../shared'
-
-export class VideoDetails extends Video implements VideoDetailsServerModel {
-  account: string
-  by: string
-  createdAt: Date
-  updatedAt: Date
-  categoryLabel: string
-  category: number
-  licenceLabel: string
-  licence: number
-  languageLabel: string
-  language: number
-  description: string
-  duration: number
-  durationLabel: string
-  id: number
-  uuid: string
-  isLocal: boolean
-  name: string
-  serverHost: string
-  tags: string[]
-  thumbnailPath: string
-  thumbnailUrl: string
-  previewPath: string
-  previewUrl: string
-  embedPath: string
-  embedUrl: string
-  views: number
-  likes: number
-  dislikes: number
-  nsfw: boolean
-  descriptionPath: string
-  files: VideoFile[]
-  channel: VideoChannel
-  privacy: VideoPrivacy
-  privacyLabel: string
-
-  constructor (hash: VideoDetailsServerModel) {
-    super(hash)
-
-    this.privacy = hash.privacy
-    this.privacyLabel = hash.privacyLabel
-    this.descriptionPath = hash.descriptionPath
-    this.files = hash.files
-    this.channel = hash.channel
-  }
-
-  getAppropriateMagnetUri (actualDownloadSpeed = 0) {
-    if (this.files === undefined || this.files.length === 0) return ''
-    if (this.files.length === 1) return this.files[0].magnetUri
-
-    // Find first video that is good for our download speed (remember they are sorted)
-    let betterResolutionFile = this.files.find(f => actualDownloadSpeed > (f.size / this.duration))
-
-    // If the download speed is too bad, return the lowest resolution we have
-    if (betterResolutionFile === undefined) {
-      betterResolutionFile = this.files.find(f => f.resolution === VideoResolution.H_240P)
-    }
-
-    return betterResolutionFile.magnetUri
-  }
-
-  isRemovableBy (user: AuthUser) {
-    return user && this.isLocal === true && (this.account === user.username || user.hasRight(UserRight.REMOVE_ANY_VIDEO))
-  }
-
-  isBlackistableBy (user: AuthUser) {
-    return user && user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) === true && this.isLocal === false
-  }
-
-  isUpdatableBy (user: AuthUser) {
-    return user && this.isLocal === true && user.username === this.account
-  }
-}
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 (file)
index 88d23a5..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-import { VideoDetails } from './video-details.model'
-import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum'
-
-export class VideoEdit {
-  category: number
-  licence: number
-  language: number
-  description: string
-  name: string
-  tags: string[]
-  nsfw: boolean
-  channel: number
-  privacy: VideoPrivacy
-  uuid?: string
-  id?: number
-
-  constructor (videoDetails: VideoDetails) {
-    this.id = videoDetails.id
-    this.uuid = videoDetails.uuid
-    this.category = videoDetails.category
-    this.licence = videoDetails.licence
-    this.language = videoDetails.language
-    this.description = videoDetails.description
-    this.name = videoDetails.name
-    this.tags = videoDetails.tags
-    this.nsfw = videoDetails.nsfw
-    this.channel = videoDetails.channel.id
-    this.privacy = videoDetails.privacy
-  }
-
-  patch (values: Object) {
-    Object.keys(values).forEach((key) => {
-      this[key] = values[key]
-    })
-  }
-
-  toJSON () {
-    return {
-      category: this.category,
-      licence: this.licence,
-      language: this.language,
-      description: this.description,
-      name: this.name,
-      tags: this.tags,
-      nsfw: this.nsfw,
-      channel: this.channel,
-      privacy: this.privacy
-    }
-  }
-}
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 (file)
index 9e71769..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-export interface VideoPagination {
-  currentPage: number
-  itemsPerPage: number
-  totalItems: number
-}
diff --git a/client/src/app/videos/shared/video.model.ts b/client/src/app/videos/shared/video.model.ts
deleted file mode 100644 (file)
index 0dd41d7..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-import { Video as VideoServerModel } from '../../../../../shared'
-import { User } from '../../shared'
-
-export class Video implements VideoServerModel {
-  account: string
-  by: string
-  createdAt: Date
-  updatedAt: Date
-  categoryLabel: string
-  category: number
-  licenceLabel: string
-  licence: number
-  languageLabel: string
-  language: number
-  description: string
-  duration: number
-  durationLabel: string
-  id: number
-  uuid: string
-  isLocal: boolean
-  name: string
-  serverHost: string
-  tags: string[]
-  thumbnailPath: string
-  thumbnailUrl: string
-  previewPath: string
-  previewUrl: string
-  embedPath: string
-  embedUrl: string
-  views: number
-  likes: number
-  dislikes: number
-  nsfw: boolean
-
-  private static createByString (account: string, serverHost: string) {
-    return account + '@' + serverHost
-  }
-
-  private static createDurationString (duration: number) {
-    const minutes = Math.floor(duration / 60)
-    const seconds = duration % 60
-    const minutesPadding = minutes >= 10 ? '' : '0'
-    const secondsPadding = seconds >= 10 ? '' : '0'
-
-    return minutesPadding + minutes.toString() + ':' + secondsPadding + seconds.toString()
-  }
-
-  constructor (hash: VideoServerModel) {
-    let absoluteAPIUrl = API_URL
-    if (!absoluteAPIUrl) {
-      // The API is on the same domain
-      absoluteAPIUrl = window.location.origin
-    }
-
-    this.account = hash.account
-    this.createdAt = new Date(hash.createdAt.toString())
-    this.categoryLabel = hash.categoryLabel
-    this.category = hash.category
-    this.licenceLabel = hash.licenceLabel
-    this.licence = hash.licence
-    this.languageLabel = hash.languageLabel
-    this.language = hash.language
-    this.description = hash.description
-    this.duration = hash.duration
-    this.durationLabel = Video.createDurationString(hash.duration)
-    this.id = hash.id
-    this.uuid = hash.uuid
-    this.isLocal = hash.isLocal
-    this.name = hash.name
-    this.serverHost = hash.serverHost
-    this.tags = hash.tags
-    this.thumbnailPath = hash.thumbnailPath
-    this.thumbnailUrl = absoluteAPIUrl + hash.thumbnailPath
-    this.previewPath = hash.previewPath
-    this.previewUrl = absoluteAPIUrl + hash.previewPath
-    this.embedPath = hash.embedPath
-    this.embedUrl = absoluteAPIUrl + hash.embedPath
-    this.views = hash.views
-    this.likes = hash.likes
-    this.dislikes = hash.dislikes
-    this.nsfw = hash.nsfw
-
-    this.by = Video.createByString(hash.account, hash.serverHost)
-  }
-
-  isVideoNSFWForUser (user: User) {
-    // If the video is NSFW and the user is not logged in, or the user does not want to display NSFW videos...
-    return (this.nsfw && (!user || user.displayNSFW === false))
-  }
-}
diff --git a/client/src/app/videos/shared/video.service.ts b/client/src/app/videos/shared/video.service.ts
deleted file mode 100644 (file)
index 5d25a26..0000000
+++ /dev/null
@@ -1,176 +0,0 @@
-import { Injectable } from '@angular/core'
-import { Observable } from 'rxjs/Observable'
-import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http'
-import 'rxjs/add/operator/catch'
-import 'rxjs/add/operator/map'
-
-import { SortField } from './sort-field.type'
-import {
-  RestExtractor,
-  RestService,
-  UserService,
-  Search
-} from '../../shared'
-import { Video } from './video.model'
-import { VideoDetails } from './video-details.model'
-import { VideoEdit } from './video-edit.model'
-import { VideoPagination } from './video-pagination.model'
-import {
-  UserVideoRate,
-  VideoRateType,
-  VideoUpdate,
-  UserVideoRateUpdate,
-  Video as VideoServerModel,
-  VideoDetails as VideoDetailsServerModel,
-  ResultList
-} from '../../../../../shared'
-
-@Injectable()
-export class VideoService {
-  private static BASE_VIDEO_URL = API_URL + '/api/v1/videos/'
-
-  constructor (
-    private authHttp: HttpClient,
-    private restExtractor: RestExtractor,
-    private restService: RestService
-  ) {}
-
-  getVideo (uuid: string): Observable<VideoDetails> {
-    return this.authHttp.get<VideoDetailsServerModel>(VideoService.BASE_VIDEO_URL + uuid)
-                        .map(videoHash => new VideoDetails(videoHash))
-                        .catch((res) => this.restExtractor.handleError(res))
-  }
-
-  viewVideo (uuid: string): Observable<VideoDetails> {
-    return this.authHttp.post(VideoService.BASE_VIDEO_URL + uuid + '/views', {})
-      .map(this.restExtractor.extractDataBool)
-      .catch(this.restExtractor.handleError)
-  }
-
-  updateVideo (video: VideoEdit) {
-    const language = video.language ? video.language : null
-
-    const body: VideoUpdate = {
-      name: video.name,
-      category: video.category,
-      licence: video.licence,
-      language,
-      description: video.description,
-      privacy: video.privacy,
-      tags: video.tags,
-      nsfw: video.nsfw
-    }
-
-    return this.authHttp.put(VideoService.BASE_VIDEO_URL + video.id, body)
-                        .map(this.restExtractor.extractDataBool)
-                        .catch(this.restExtractor.handleError)
-  }
-
-  uploadVideo (video: FormData) {
-    const req = new HttpRequest('POST', VideoService.BASE_VIDEO_URL + 'upload', video, { reportProgress: true })
-
-    return this.authHttp
-      .request(req)
-      .catch(this.restExtractor.handleError)
-  }
-
-  getMyVideos (videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> {
-    const pagination = this.videoPaginationToRestPagination(videoPagination)
-
-    let params = new HttpParams()
-    params = this.restService.addRestGetParams(params, pagination, sort)
-
-    return this.authHttp.get(UserService.BASE_USERS_URL + '/me/videos', { params })
-      .map(this.extractVideos)
-      .catch((res) => this.restExtractor.handleError(res))
-  }
-
-  getVideos (videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> {
-    const pagination = this.videoPaginationToRestPagination(videoPagination)
-
-    let params = new HttpParams()
-    params = this.restService.addRestGetParams(params, pagination, sort)
-
-    return this.authHttp
-      .get(VideoService.BASE_VIDEO_URL, { params })
-      .map(this.extractVideos)
-      .catch((res) => this.restExtractor.handleError(res))
-  }
-
-  searchVideos (search: Search, videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> {
-    const url = VideoService.BASE_VIDEO_URL + 'search/' + encodeURIComponent(search.value)
-
-    const pagination = this.videoPaginationToRestPagination(videoPagination)
-
-    let params = new HttpParams()
-    params = this.restService.addRestGetParams(params, pagination, sort)
-
-    if (search.field) params.set('field', search.field)
-
-    return this.authHttp
-      .get<ResultList<VideoServerModel>>(url, { params })
-      .map(this.extractVideos)
-      .catch((res) => this.restExtractor.handleError(res))
-  }
-
-  removeVideo (id: number) {
-    return this.authHttp
-      .delete(VideoService.BASE_VIDEO_URL + id)
-      .map(this.restExtractor.extractDataBool)
-      .catch((res) => this.restExtractor.handleError(res))
-  }
-
-  loadCompleteDescription (descriptionPath: string) {
-    return this.authHttp
-      .get(API_URL + descriptionPath)
-      .map(res => res['description'])
-      .catch((res) => this.restExtractor.handleError(res))
-  }
-
-  setVideoLike (id: number) {
-    return this.setVideoRate(id, 'like')
-  }
-
-  setVideoDislike (id: number) {
-    return this.setVideoRate(id, 'dislike')
-  }
-
-  getUserVideoRating (id: number): Observable<UserVideoRate> {
-    const url = UserService.BASE_USERS_URL + 'me/videos/' + id + '/rating'
-
-    return this.authHttp
-      .get(url)
-      .catch(res => this.restExtractor.handleError(res))
-  }
-
-  private videoPaginationToRestPagination (videoPagination: VideoPagination) {
-    const start: number = (videoPagination.currentPage - 1) * videoPagination.itemsPerPage
-    const count: number = videoPagination.itemsPerPage
-
-    return { start, count }
-  }
-
-  private setVideoRate (id: number, rateType: VideoRateType) {
-    const url = VideoService.BASE_VIDEO_URL + id + '/rate'
-    const body: UserVideoRateUpdate = {
-      rating: rateType
-    }
-
-    return this.authHttp
-      .put(url, body)
-      .map(this.restExtractor.extractDataBool)
-      .catch(res => this.restExtractor.handleError(res))
-  }
-
-  private extractVideos (result: ResultList<VideoServerModel>) {
-    const videosJson = result.data
-    const totalVideos = result.total
-    const videos = []
-
-    for (const videoJson of videosJson) {
-      videos.push(new Video(videoJson))
-    }
-
-    return { videos, totalVideos }
-  }
-}
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 (file)
index bd4f6b1..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-<div class="margin-content">
-  <div class="title-page title-page-single">
-    {{ titlePage }}
-  </div>
-
-  <div
-    infiniteScroll
-    [infiniteScrollUpDistance]="1.5"
-    [infiniteScrollDistance]="0.5"
-    (scrolled)="onNearOfBottom()"
-    (scrolledUp)="onNearOfTop()"
-  >
-    <my-video-miniature
-      class="ng-animate"
-      *ngFor="let video of videos" [video]="video" [user]="user" [currentSort]="sort"
-    >
-    </my-video-miniature>
-  </div>
-</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 (file)
index e69de29..0000000
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 (file)
index a684ffe..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-import { OnDestroy, OnInit } from '@angular/core'
-import { ActivatedRoute, Router } from '@angular/router'
-
-import { NotificationsService } from 'angular2-notifications'
-import { Observable } from 'rxjs/Observable'
-import { Subscription } from 'rxjs/Subscription'
-
-import { SortField, Video, VideoPagination } from '../../shared'
-
-export abstract class AbstractVideoList implements OnInit, OnDestroy {
-  pagination: VideoPagination = {
-    currentPage: 1,
-    itemsPerPage: 25,
-    totalItems: null
-  }
-  sort: SortField = '-createdAt'
-  videos: Video[] = []
-
-  protected notificationsService: NotificationsService
-  protected router: Router
-  protected route: ActivatedRoute
-  protected subActivatedRoute: Subscription
-
-  protected abstract currentRoute: string
-
-  abstract titlePage: string
-  private loadedPages: { [ id: number ]: boolean } = {}
-
-  abstract getVideosObservable (): Observable<{ videos: Video[], totalVideos: number}>
-
-  ngOnInit () {
-    // Subscribe to route changes
-    const routeParams = this.route.snapshot.params
-    this.loadRouteParams(routeParams)
-    this.loadMoreVideos('after')
-  }
-
-  ngOnDestroy () {
-    if (this.subActivatedRoute) {
-      this.subActivatedRoute.unsubscribe()
-    }
-  }
-
-  onNearOfTop () {
-    if (this.pagination.currentPage > 1) {
-      this.previousPage()
-    }
-  }
-
-  onNearOfBottom () {
-    if (this.hasMoreVideos()) {
-      this.nextPage()
-    }
-  }
-
-  loadMoreVideos (where: 'before' | 'after') {
-    if (this.loadedPages[this.pagination.currentPage] === true) return
-
-    const observable = this.getVideosObservable()
-
-    observable.subscribe(
-      ({ videos, totalVideos }) => {
-        this.loadedPages[this.pagination.currentPage] = true
-        this.pagination.totalItems = totalVideos
-
-        if (where === 'before') {
-          this.videos = videos.concat(this.videos)
-        } else {
-          this.videos = this.videos.concat(videos)
-        }
-      },
-      error => this.notificationsService.error('Error', error.text)
-    )
-  }
-
-  protected hasMoreVideos () {
-    if (!this.pagination.totalItems) return true
-
-    const maxPage = this.pagination.totalItems/this.pagination.itemsPerPage
-    return maxPage > this.pagination.currentPage
-  }
-
-  protected previousPage () {
-    this.pagination.currentPage--
-
-    this.setNewRouteParams()
-    this.loadMoreVideos('before')
-  }
-
-  protected nextPage () {
-    this.pagination.currentPage++
-
-    this.setNewRouteParams()
-    this.loadMoreVideos('after')
-  }
-
-  protected buildRouteParams () {
-    // There is always a sort and a current page
-    const params = {
-      sort: this.sort,
-      page: this.pagination.currentPage
-    }
-
-    return params
-  }
-
-  protected loadRouteParams (routeParams: { [ key: string ]: any }) {
-    this.sort = routeParams['sort'] as SortField || '-createdAt'
-
-    if (routeParams['page'] !== undefined) {
-      this.pagination.currentPage = parseInt(routeParams['page'], 10)
-    } else {
-      this.pagination.currentPage = 1
-    }
-  }
-
-  protected setNewRouteParams () {
-    const routeParams = this.buildRouteParams()
-    this.router.navigate([ this.currentRoute, routeParams ])
-  }
-}
index 170ca4832d21312592389707a0b17972a2907d5d..2778f2d9ecee20e48a184503320ce09972b84c8a 100644 (file)
@@ -1,2 +1 @@
-export * from './abstract-video-list'
 export * from './video-miniature.component'
index aea85b6c6b2449b19b921bffd7a22d39cb9d190b..f2756ca3d106c328c1eadad16151b92d26147ef0 100644 (file)
@@ -1,14 +1,5 @@
 <div class="video-miniature">
-  <a
-    [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.description"
-    class="video-miniature-thumbnail"
-  >
-    <img [attr.src]="video.thumbnailUrl" alt="video thumbnail" [ngClass]="{ 'blur-filter': isVideoNSFWForThisUser() }" />
-
-    <div class="video-miniature-thumbnail-overlay">
-      {{ video.durationLabel }}
-    </div>
-  </a>
+  <my-video-thumbnail [video]="video" [nsfw]="isVideoNSFWForThisUser()"></my-video-thumbnail>
 
   <div class="video-miniature-information">
     <span class="video-miniature-name">
index ed15864d9ae29df411654fcf82fff284fb146515..658d7af9dde3329315e027a07deb80eaf838d3c7 100644 (file)
@@ -5,35 +5,6 @@
   height: 175px;
   vertical-align: top;
 
-  .video-miniature-thumbnail {
-    display: inline-block;
-    position: relative;
-    border-radius: 4px;
-    overflow: hidden;
-
-    &:hover {
-      text-decoration: none !important;
-    }
-
-    img.blur-filter {
-      filter: blur(5px);
-      transform : scale(1.03);
-    }
-
-    .video-miniature-thumbnail-overlay {
-      position: absolute;
-      right: 5px;
-      bottom: 5px;
-      display: inline-block;
-      background-color: rgba(0, 0, 0, 0.7);
-      color: #fff;
-      font-size: 12px;
-      font-weight: $font-bold;
-      border-radius: 3px;
-      padding: 0 5px;
-    }
-  }
-
   .video-miniature-information {
     width: 200px;
     margin-top: 2px;
index e5a87907b3dd6106363ee00fad38f6722a10ad8a..e8fc8e9115f4d02fa4288d50fa06802bd3bef877 100644 (file)
@@ -1,7 +1,7 @@
 import { Component, Input } from '@angular/core'
-
-import { SortField, Video } from '../../shared'
 import { User } from '../../../shared'
+import { SortField } from '../../../shared/video/sort-field.type'
+import { Video } from '../../../shared/video/video.model'
 
 @Component({
   selector: 'my-video-miniature',
index 9bf69bd7875005e75ad2500bf75d030937fd0320..d48804414b75cdc9c91cd3a299e77c18a72d3533 100644 (file)
@@ -1,13 +1,13 @@
 import { Component, OnDestroy, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { NotificationsService } from 'angular2-notifications'
-import { VideoService } from '../shared'
-import { AbstractVideoList } from './shared'
+import { VideoService } from '../../shared/video/video.service'
+import { AbstractVideoList } from '../../shared/video/abstract-video-list'
 
 @Component({
   selector: 'my-videos-recently-added',
-  styleUrls: [ './shared/abstract-video-list.scss' ],
-  templateUrl: './shared/abstract-video-list.html'
+  styleUrls: [ '../../shared/video/abstract-video-list.scss' ],
+  templateUrl: '../../shared/video/abstract-video-list.html'
 })
 export class VideoRecentlyAddedComponent extends AbstractVideoList implements OnInit, OnDestroy {
   titlePage = 'Recently added'
index a1df68711590f4534b2f026993f317771b28cf02..9108289c918d3e088c35a0320fb682f2d8f01fac 100644 (file)
@@ -1,13 +1,13 @@
 import { Component, OnDestroy, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { NotificationsService } from 'angular2-notifications'
-import { VideoService } from '../shared'
-import { AbstractVideoList } from './shared'
+import { VideoService } from '../../shared/video/video.service'
+import { AbstractVideoList } from 'app/shared/video/abstract-video-list'
 
 @Component({
   selector: 'my-videos-trending',
-  styleUrls: [ './shared/abstract-video-list.scss' ],
-  templateUrl: './shared/abstract-video-list.html'
+  styleUrls: [ '../../shared/video/abstract-video-list.scss' ],
+  templateUrl: '../../shared/video/abstract-video-list.html'
 })
 export class VideoTrendingComponent extends AbstractVideoList implements OnInit, OnDestroy {
   titlePage = 'Trending'
index 1d61941581042e68dd968ef0402c6fb754158f71..6d846fd3bfc67d1b139e5a7ace08a51912166486 100644 (file)
@@ -1,7 +1,5 @@
 import { NgModule } from '@angular/core'
-import { InfiniteScrollModule } from 'ngx-infinite-scroll'
 import { SharedModule } from '../shared'
-import { VideoService } from './shared'
 import { VideoMiniatureComponent } from './video-list'
 import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component'
 import { VideoTrendingComponent } from './video-list/video-trending.component'
@@ -11,8 +9,7 @@ import { VideosComponent } from './videos.component'
 @NgModule({
   imports: [
     VideosRoutingModule,
-    SharedModule,
-    InfiniteScrollModule
+    SharedModule
   ],
 
   declarations: [
@@ -27,8 +24,6 @@ import { VideosComponent } from './videos.component'
     VideosComponent
   ],
 
-  providers: [
-    VideoService
-  ]
+  providers: []
 })
 export class VideosModule { }