From 1942f11d5ee6926ad93dc1b79fae18325ba5de18 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 23 Jun 2020 14:49:20 +0200 Subject: Lazy load all routes --- .../shared/i18n-primeng-calendar.service.ts | 94 --- .../shared/video-caption-add-modal.component.html | 47 -- .../shared/video-caption-add-modal.component.scss | 20 - .../shared/video-caption-add-modal.component.ts | 85 --- .../+video-edit/shared/video-edit.component.html | 280 -------- .../+video-edit/shared/video-edit.component.scss | 197 ------ .../+video-edit/shared/video-edit.component.ts | 274 -------- .../videos/+video-edit/shared/video-edit.module.ts | 38 - .../video-add-components/drag-drop.directive.ts | 30 - .../video-import-torrent.component.html | 76 -- .../video-import-torrent.component.scss | 18 - .../video-import-torrent.component.ts | 147 ---- .../video-import-url.component.html | 72 -- .../video-import-url.component.ts | 178 ----- .../video-add-components/video-send.scss | 46 -- .../+video-edit/video-add-components/video-send.ts | 71 -- .../video-upload.component.html | 90 --- .../video-upload.component.scss | 49 -- .../video-add-components/video-upload.component.ts | 306 -------- .../videos/+video-edit/video-add-routing.module.ts | 20 - .../videos/+video-edit/video-add.component.html | 46 -- .../videos/+video-edit/video-add.component.scss | 89 --- .../app/videos/+video-edit/video-add.component.ts | 77 -- .../src/app/videos/+video-edit/video-add.module.ts | 32 - .../+video-edit/video-update-routing.module.ts | 24 - .../videos/+video-edit/video-update.component.html | 22 - .../videos/+video-edit/video-update.component.ts | 155 ---- .../app/videos/+video-edit/video-update.module.ts | 26 - .../videos/+video-edit/video-update.resolver.ts | 44 -- .../comment/video-comment-add.component.html | 56 -- .../comment/video-comment-add.component.scss | 82 --- .../comment/video-comment-add.component.ts | 149 ---- .../comment/video-comment-thread-tree.model.ts | 7 - .../comment/video-comment.component.html | 95 --- .../comment/video-comment.component.scss | 189 ----- .../comment/video-comment.component.ts | 131 ---- .../+video-watch/comment/video-comment.model.ts | 48 -- .../+video-watch/comment/video-comment.service.ts | 149 ---- .../comment/video-comments.component.html | 98 --- .../comment/video-comments.component.scss | 53 -- .../comment/video-comments.component.ts | 232 ------ .../+video-watch/modal/video-share.component.html | 187 ----- .../+video-watch/modal/video-share.component.scss | 79 --- .../+video-watch/modal/video-share.component.ts | 126 ---- .../modal/video-support.component.html | 15 - .../modal/video-support.component.scss | 3 - .../+video-watch/modal/video-support.component.ts | 29 - .../timestamp-route-transformer.directive.ts | 39 - .../+video-watch/video-duration-formatter.pipe.ts | 28 - .../video-watch-playlist.component.html | 46 -- .../video-watch-playlist.component.scss | 83 --- .../+video-watch/video-watch-playlist.component.ts | 201 ------ .../+video-watch/video-watch-routing.module.ts | 27 - .../videos/+video-watch/video-watch.component.html | 277 -------- .../videos/+video-watch/video-watch.component.scss | 607 ---------------- .../videos/+video-watch/video-watch.component.ts | 782 --------------------- .../app/videos/+video-watch/video-watch.module.ts | 65 -- client/src/app/videos/index.ts | 1 - .../recent-videos-recommendation.service.ts | 82 --- .../recommendations/recommendation-info.model.ts | 4 - .../recommendations/recommendations.module.ts | 32 - .../recommendations/recommendations.service.ts | 7 - .../recommended-videos.component.html | 24 - .../recommended-videos.component.scss | 31 - .../recommended-videos.component.ts | 91 --- .../recommendations/recommended-videos.store.ts | 37 - client/src/app/videos/video-list/index.ts | 5 - client/src/app/videos/video-list/overview/index.ts | 3 - .../videos/video-list/overview/overview.service.ts | 78 -- .../overview/video-overview.component.html | 52 -- .../overview/video-overview.component.scss | 16 - .../overview/video-overview.component.ts | 94 --- .../video-list/overview/videos-overview.model.ts | 20 - .../app/videos/video-list/video-local.component.ts | 86 --- .../video-list/video-most-liked.component.ts | 70 -- .../video-list/video-recently-added.component.ts | 74 -- .../videos/video-list/video-trending.component.ts | 87 --- .../video-user-subscriptions.component.ts | 75 -- client/src/app/videos/videos-routing.module.ts | 125 ---- client/src/app/videos/videos.component.ts | 6 - client/src/app/videos/videos.module.ts | 44 -- 81 files changed, 7680 deletions(-) delete mode 100644 client/src/app/videos/+video-edit/shared/i18n-primeng-calendar.service.ts delete mode 100644 client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.html delete mode 100644 client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.scss delete mode 100644 client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.ts delete mode 100644 client/src/app/videos/+video-edit/shared/video-edit.component.html delete mode 100644 client/src/app/videos/+video-edit/shared/video-edit.component.scss delete mode 100644 client/src/app/videos/+video-edit/shared/video-edit.component.ts delete mode 100644 client/src/app/videos/+video-edit/shared/video-edit.module.ts delete mode 100644 client/src/app/videos/+video-edit/video-add-components/drag-drop.directive.ts delete mode 100644 client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html delete mode 100644 client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.scss delete mode 100644 client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.ts delete mode 100644 client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html delete mode 100644 client/src/app/videos/+video-edit/video-add-components/video-import-url.component.ts delete mode 100644 client/src/app/videos/+video-edit/video-add-components/video-send.scss delete mode 100644 client/src/app/videos/+video-edit/video-add-components/video-send.ts delete mode 100644 client/src/app/videos/+video-edit/video-add-components/video-upload.component.html delete mode 100644 client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss delete mode 100644 client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts delete mode 100644 client/src/app/videos/+video-edit/video-add-routing.module.ts delete mode 100644 client/src/app/videos/+video-edit/video-add.component.html delete mode 100644 client/src/app/videos/+video-edit/video-add.component.scss delete mode 100644 client/src/app/videos/+video-edit/video-add.component.ts delete mode 100644 client/src/app/videos/+video-edit/video-add.module.ts delete mode 100644 client/src/app/videos/+video-edit/video-update-routing.module.ts delete mode 100644 client/src/app/videos/+video-edit/video-update.component.html delete mode 100644 client/src/app/videos/+video-edit/video-update.component.ts delete mode 100644 client/src/app/videos/+video-edit/video-update.module.ts delete mode 100644 client/src/app/videos/+video-edit/video-update.resolver.ts delete mode 100644 client/src/app/videos/+video-watch/comment/video-comment-add.component.html delete mode 100644 client/src/app/videos/+video-watch/comment/video-comment-add.component.scss delete mode 100644 client/src/app/videos/+video-watch/comment/video-comment-add.component.ts delete mode 100644 client/src/app/videos/+video-watch/comment/video-comment-thread-tree.model.ts delete mode 100644 client/src/app/videos/+video-watch/comment/video-comment.component.html delete mode 100644 client/src/app/videos/+video-watch/comment/video-comment.component.scss delete mode 100644 client/src/app/videos/+video-watch/comment/video-comment.component.ts delete mode 100644 client/src/app/videos/+video-watch/comment/video-comment.model.ts delete mode 100644 client/src/app/videos/+video-watch/comment/video-comment.service.ts delete mode 100644 client/src/app/videos/+video-watch/comment/video-comments.component.html delete mode 100644 client/src/app/videos/+video-watch/comment/video-comments.component.scss delete mode 100644 client/src/app/videos/+video-watch/comment/video-comments.component.ts delete mode 100644 client/src/app/videos/+video-watch/modal/video-share.component.html delete mode 100644 client/src/app/videos/+video-watch/modal/video-share.component.scss delete mode 100644 client/src/app/videos/+video-watch/modal/video-share.component.ts delete mode 100644 client/src/app/videos/+video-watch/modal/video-support.component.html delete mode 100644 client/src/app/videos/+video-watch/modal/video-support.component.scss delete mode 100644 client/src/app/videos/+video-watch/modal/video-support.component.ts delete mode 100644 client/src/app/videos/+video-watch/timestamp-route-transformer.directive.ts delete mode 100644 client/src/app/videos/+video-watch/video-duration-formatter.pipe.ts delete mode 100644 client/src/app/videos/+video-watch/video-watch-playlist.component.html delete mode 100644 client/src/app/videos/+video-watch/video-watch-playlist.component.scss delete mode 100644 client/src/app/videos/+video-watch/video-watch-playlist.component.ts delete mode 100644 client/src/app/videos/+video-watch/video-watch-routing.module.ts delete mode 100644 client/src/app/videos/+video-watch/video-watch.component.html delete mode 100644 client/src/app/videos/+video-watch/video-watch.component.scss delete mode 100644 client/src/app/videos/+video-watch/video-watch.component.ts delete mode 100644 client/src/app/videos/+video-watch/video-watch.module.ts delete mode 100644 client/src/app/videos/index.ts delete mode 100644 client/src/app/videos/recommendations/recent-videos-recommendation.service.ts delete mode 100644 client/src/app/videos/recommendations/recommendation-info.model.ts delete mode 100644 client/src/app/videos/recommendations/recommendations.module.ts delete mode 100644 client/src/app/videos/recommendations/recommendations.service.ts delete mode 100644 client/src/app/videos/recommendations/recommended-videos.component.html delete mode 100644 client/src/app/videos/recommendations/recommended-videos.component.scss delete mode 100644 client/src/app/videos/recommendations/recommended-videos.component.ts delete mode 100644 client/src/app/videos/recommendations/recommended-videos.store.ts delete mode 100644 client/src/app/videos/video-list/index.ts delete mode 100644 client/src/app/videos/video-list/overview/index.ts delete mode 100644 client/src/app/videos/video-list/overview/overview.service.ts delete mode 100644 client/src/app/videos/video-list/overview/video-overview.component.html delete mode 100644 client/src/app/videos/video-list/overview/video-overview.component.scss delete mode 100644 client/src/app/videos/video-list/overview/video-overview.component.ts delete mode 100644 client/src/app/videos/video-list/overview/videos-overview.model.ts delete mode 100644 client/src/app/videos/video-list/video-local.component.ts delete mode 100644 client/src/app/videos/video-list/video-most-liked.component.ts delete mode 100644 client/src/app/videos/video-list/video-recently-added.component.ts delete mode 100644 client/src/app/videos/video-list/video-trending.component.ts delete mode 100644 client/src/app/videos/video-list/video-user-subscriptions.component.ts delete mode 100644 client/src/app/videos/videos-routing.module.ts delete mode 100644 client/src/app/videos/videos.component.ts delete mode 100644 client/src/app/videos/videos.module.ts (limited to 'client/src/app/videos') diff --git a/client/src/app/videos/+video-edit/shared/i18n-primeng-calendar.service.ts b/client/src/app/videos/+video-edit/shared/i18n-primeng-calendar.service.ts deleted file mode 100644 index b05852ff8..000000000 --- a/client/src/app/videos/+video-edit/shared/i18n-primeng-calendar.service.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { I18n } from '@ngx-translate/i18n-polyfill' -import { Injectable } from '@angular/core' - -@Injectable() -export class I18nPrimengCalendarService { - private readonly calendarLocale: any = {} - - constructor (private i18n: I18n) { - this.calendarLocale = { - firstDayOfWeek: 0, - dayNames: [ - this.i18n('Sunday'), - this.i18n('Monday'), - this.i18n('Tuesday'), - this.i18n('Wednesday'), - this.i18n('Thursday'), - this.i18n('Friday'), - this.i18n('Saturday') - ], - - dayNamesShort: [ - this.i18n({ value: 'Sun', description: 'Day name short' }), - this.i18n({ value: 'Mon', description: 'Day name short' }), - this.i18n({ value: 'Tue', description: 'Day name short' }), - this.i18n({ value: 'Wed', description: 'Day name short' }), - this.i18n({ value: 'Thu', description: 'Day name short' }), - this.i18n({ value: 'Fri', description: 'Day name short' }), - this.i18n({ value: 'Sat', description: 'Day name short' }) - ], - - dayNamesMin: [ - this.i18n({ value: 'Su', description: 'Day name min' }), - this.i18n({ value: 'Mo', description: 'Day name min' }), - this.i18n({ value: 'Tu', description: 'Day name min' }), - this.i18n({ value: 'We', description: 'Day name min' }), - this.i18n({ value: 'Th', description: 'Day name min' }), - this.i18n({ value: 'Fr', description: 'Day name min' }), - this.i18n({ value: 'Sa', description: 'Day name min' }) - ], - - monthNames: [ - this.i18n('January'), - this.i18n('February'), - this.i18n('March'), - this.i18n('April'), - this.i18n('May'), - this.i18n('June'), - this.i18n('July'), - this.i18n('August'), - this.i18n('September'), - this.i18n('October'), - this.i18n('November'), - this.i18n('December') - ], - - monthNamesShort: [ - this.i18n({ value: 'Jan', description: 'Month name short' }), - this.i18n({ value: 'Feb', description: 'Month name short' }), - this.i18n({ value: 'Mar', description: 'Month name short' }), - this.i18n({ value: 'Apr', description: 'Month name short' }), - this.i18n({ value: 'May', description: 'Month name short' }), - this.i18n({ value: 'Jun', description: 'Month name short' }), - this.i18n({ value: 'Jul', description: 'Month name short' }), - this.i18n({ value: 'Aug', description: 'Month name short' }), - this.i18n({ value: 'Sep', description: 'Month name short' }), - this.i18n({ value: 'Oct', description: 'Month name short' }), - this.i18n({ value: 'Nov', description: 'Month name short' }), - this.i18n({ value: 'Dec', description: 'Month name short' }) - ], - - today: this.i18n('Today'), - - clear: this.i18n('Clear') - } - } - - getCalendarLocale () { - return this.calendarLocale - } - - getTimezone () { - const gmt = new Date().toString().match(/([A-Z]+[\+-][0-9]+)/)[1] - const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone - - return `${timezone} - ${gmt}` - } - - getDateFormat () { - return this.i18n({ - value: 'yy-mm-dd ', - description: 'Date format in this locale.' - }) - } -} diff --git a/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.html b/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.html deleted file mode 100644 index 6a9e31b5a..000000000 --- a/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - diff --git a/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.scss b/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.scss deleted file mode 100644 index b257a16a9..000000000 --- a/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.scss +++ /dev/null @@ -1,20 +0,0 @@ -@import '_variables'; -@import '_mixins'; - -.peertube-select-container { - @include peertube-select-container(auto); -} - -.caption-file { - margin-top: 20px; - width: max-content; - - ::ng-deep .root { - width: max-content; - } -} - -.warning-replace-caption { - color: red; - margin-top: 10px; -} \ No newline at end of file diff --git a/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.ts b/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.ts deleted file mode 100644 index a90d04ce8..000000000 --- a/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' -import { ServerService } from '@app/core' -import { FormReactive, FormValidatorService, VideoCaptionsValidatorsService } from '@app/shared/shared-forms' -import { VideoCaptionEdit } from '@app/shared/shared-main' -import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' -import { ServerConfig, VideoConstant } from '@shared/models' - -@Component({ - selector: 'my-video-caption-add-modal', - styleUrls: [ './video-caption-add-modal.component.scss' ], - templateUrl: './video-caption-add-modal.component.html' -}) - -export class VideoCaptionAddModalComponent extends FormReactive implements OnInit { - @Input() existingCaptions: string[] - @Input() serverConfig: ServerConfig - - @Output() captionAdded = new EventEmitter() - - @ViewChild('modal', { static: true }) modal: ElementRef - - videoCaptionLanguages: VideoConstant[] = [] - - private openedModal: NgbModalRef - private closingModal = false - - constructor ( - protected formValidatorService: FormValidatorService, - private modalService: NgbModal, - private serverService: ServerService, - private videoCaptionsValidatorsService: VideoCaptionsValidatorsService - ) { - super() - } - - get videoCaptionExtensions () { - return this.serverConfig.videoCaption.file.extensions - } - - get videoCaptionMaxSize () { - return this.serverConfig.videoCaption.file.size.max - } - - ngOnInit () { - this.serverService.getVideoLanguages() - .subscribe(languages => this.videoCaptionLanguages = languages) - - this.buildForm({ - language: this.videoCaptionsValidatorsService.VIDEO_CAPTION_LANGUAGE, - captionfile: this.videoCaptionsValidatorsService.VIDEO_CAPTION_FILE - }) - } - - show () { - this.closingModal = false - - this.openedModal = this.modalService.open(this.modal, { centered: true, keyboard: false }) - } - - hide () { - this.closingModal = true - this.openedModal.close() - this.form.reset() - } - - isReplacingExistingCaption () { - if (this.closingModal === true) return false - - const languageId = this.form.value[ 'language' ] - - return languageId && this.existingCaptions.indexOf(languageId) !== -1 - } - - async addCaption () { - const languageId = this.form.value[ 'language' ] - const languageObject = this.videoCaptionLanguages.find(l => l.id === languageId) - - this.captionAdded.emit({ - language: languageObject, - captionfile: this.form.value[ 'captionfile' ] - }) - - this.hide() - } -} diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.html b/client/src/app/videos/+video-edit/shared/video-edit.component.html deleted file mode 100644 index c11a60dce..000000000 --- a/client/src/app/videos/+video-edit/shared/video-edit.component.html +++ /dev/null @@ -1,280 +0,0 @@ -
- - -
-
- - diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.scss b/client/src/app/videos/+video-edit/shared/video-edit.component.scss deleted file mode 100644 index 69b907288..000000000 --- a/client/src/app/videos/+video-edit/shared/video-edit.component.scss +++ /dev/null @@ -1,197 +0,0 @@ -// Bootstrap grid utilities require functions, variables and mixins -@import 'node_modules/bootstrap/scss/functions'; -@import 'node_modules/bootstrap/scss/variables'; -@import 'node_modules/bootstrap/scss/mixins'; -@import 'node_modules/bootstrap/scss/grid'; - -@import 'variables'; -@import 'mixins'; - -label { - font-weight: $font-regular; - font-size: 100%; -} - -.peertube-select-container { - @include peertube-select-container(auto); -} - -.title-page a { - color: pvar(--mainForegroundColor); - - &:hover { - text-decoration: none; - opacity: .8; - } -} - -my-peertube-checkbox { - display: block; - margin-bottom: 1rem; -} - -.nav-tabs { - margin-bottom: 15px; -} - -.video-edit { - height: 100%; - min-height: 300px; - - .form-group { - margin-bottom: 25px; - } - - input { - @include peertube-input-text(100%); - display: block; - } - - .label-tags + span { - font-size: 15px; - } - - .advanced-settings .form-group { - margin-bottom: 20px; - } -} - -.captions { - - .captions-header { - text-align: right; - margin-bottom: 1rem; - - .create-caption { - @include create-button; - } - } - - .caption-entry { - display: flex; - height: 40px; - align-items: center; - - a.caption-entry-label { - @include disable-default-a-behaviour; - - flex-grow: 1; - color: #000; - - &:hover { - opacity: 0.8; - } - } - - .caption-entry-label { - font-size: 15px; - font-weight: bold; - - margin-right: 20px; - width: 150px; - } - - .caption-entry-state { - width: 200px; - - &.caption-entry-state-create { - color: #39CC0B; - } - - &.caption-entry-state-delete { - color: #FF0000; - } - } - - .caption-entry-delete { - @include peertube-button; - @include grey-button; - } - } - - .no-caption { - text-align: center; - font-size: 15px; - } -} - -.submit-container { - text-align: right; - - .message-submit { - display: inline-block; - margin-right: 25px; - - color: pvar(--greyForegroundColor); - font-size: 15px; - } - - .submit-button { - @include peertube-button; - @include orange-button; - @include button-with-icon(20px, 1px); - - display: inline-block; - - input { - cursor: inherit; - background-color: inherit; - border: none; - padding: 0; - outline: 0; - color: inherit; - font-weight: $font-semibold; - } - } -} - -p-calendar { - display: block; - - ::ng-deep { - input, - .ui-calendar { - width: 100%; - } - - input { - @include peertube-input-text(100%); - color: #000; - } - } -} - -@include ng2-tags; - -// columns for the video -.col-video-edit { - @include make-col-ready(); - - @include media-breakpoint-up(md) { - @include make-col(7); - - & + .col-video-edit { - @include make-col(5); - } - } - - @include media-breakpoint-up(xl) { - @include make-col(8); - - & + .col-video-edit { - @include make-col(4); - } - } -} - -:host-context(.expanded) { - .col-video-edit { - @include media-breakpoint-up(md) { - @include make-col(8); - - & + .col-video-edit { - @include make-col(4); - } - } - } -} diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.ts b/client/src/app/videos/+video-edit/shared/video-edit.component.ts deleted file mode 100644 index 239e453ad..000000000 --- a/client/src/app/videos/+video-edit/shared/video-edit.component.ts +++ /dev/null @@ -1,274 +0,0 @@ -import { map } from 'rxjs/operators' -import { Component, Input, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' -import { FormArray, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms' -import { ServerService } from '@app/core' -import { removeElementFromArray } from '@app/helpers' -import { FormReactiveValidationMessages, FormValidatorService, VideoValidatorsService } from '@app/shared/shared-forms' -import { VideoCaptionEdit, VideoEdit, VideoService } from '@app/shared/shared-main' -import { ServerConfig, VideoConstant, VideoPrivacy } from '@shared/models' -import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service' -import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' - -@Component({ - selector: 'my-video-edit', - styleUrls: [ './video-edit.component.scss' ], - templateUrl: './video-edit.component.html' -}) -export class VideoEditComponent implements OnInit, OnDestroy { - @Input() form: FormGroup - @Input() formErrors: { [ id: string ]: string } = {} - @Input() validationMessages: FormReactiveValidationMessages = {} - @Input() userVideoChannels: { id: number, label: string, support: string }[] = [] - @Input() schedulePublicationPossible = true - @Input() videoCaptions: (VideoCaptionEdit & { captionPath?: string })[] = [] - @Input() waitTranscodingEnabled = true - - @ViewChild('videoCaptionAddModal', { static: true }) videoCaptionAddModal: VideoCaptionAddModalComponent - - // So that it can be accessed in the template - readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY - - videoPrivacies: VideoConstant[] = [] - videoCategories: VideoConstant[] = [] - videoLicences: VideoConstant[] = [] - videoLanguages: VideoConstant[] = [] - - tagValidators: ValidatorFn[] - tagValidatorsMessages: { [ name: string ]: string } - - schedulePublicationEnabled = false - - calendarLocale: any = {} - minScheduledDate = new Date() - myYearRange = '1880:' + (new Date()).getFullYear() - - calendarTimezone: string - calendarDateFormat: string - - serverConfig: ServerConfig - - private schedulerInterval: any - private firstPatchDone = false - private initialVideoCaptions: string[] = [] - - constructor ( - private formValidatorService: FormValidatorService, - private videoValidatorsService: VideoValidatorsService, - private videoService: VideoService, - private serverService: ServerService, - private i18nPrimengCalendarService: I18nPrimengCalendarService, - private ngZone: NgZone - ) { - this.tagValidators = this.videoValidatorsService.VIDEO_TAGS.VALIDATORS - this.tagValidatorsMessages = this.videoValidatorsService.VIDEO_TAGS.MESSAGES - - this.calendarLocale = this.i18nPrimengCalendarService.getCalendarLocale() - this.calendarTimezone = this.i18nPrimengCalendarService.getTimezone() - this.calendarDateFormat = this.i18nPrimengCalendarService.getDateFormat() - } - - get existingCaptions () { - return this.videoCaptions - .filter(c => c.action !== 'REMOVE') - .map(c => c.language.id) - } - - updateForm () { - const defaultValues: any = { - nsfw: 'false', - commentsEnabled: 'true', - downloadEnabled: 'true', - waitTranscoding: 'true', - tags: [] - } - const obj: any = { - name: this.videoValidatorsService.VIDEO_NAME, - privacy: this.videoValidatorsService.VIDEO_PRIVACY, - channelId: this.videoValidatorsService.VIDEO_CHANNEL, - nsfw: null, - commentsEnabled: null, - downloadEnabled: null, - waitTranscoding: null, - category: this.videoValidatorsService.VIDEO_CATEGORY, - licence: this.videoValidatorsService.VIDEO_LICENCE, - language: this.videoValidatorsService.VIDEO_LANGUAGE, - description: this.videoValidatorsService.VIDEO_DESCRIPTION, - tags: null, - previewfile: null, - support: this.videoValidatorsService.VIDEO_SUPPORT, - schedulePublicationAt: this.videoValidatorsService.VIDEO_SCHEDULE_PUBLICATION_AT, - originallyPublishedAt: this.videoValidatorsService.VIDEO_ORIGINALLY_PUBLISHED_AT - } - - this.formValidatorService.updateForm( - this.form, - this.formErrors, - this.validationMessages, - obj, - defaultValues - ) - - this.form.addControl('captions', new FormArray([ - new FormGroup({ - language: new FormControl(), - captionfile: new FormControl() - }) - ])) - - this.trackChannelChange() - this.trackPrivacyChange() - } - - ngOnInit () { - this.updateForm() - - this.serverService.getVideoCategories() - .subscribe(res => this.videoCategories = res) - this.serverService.getVideoLicences() - .subscribe(res => this.videoLicences = res) - this.serverService.getVideoLanguages() - .subscribe(res => this.videoLanguages = res) - - this.serverService.getVideoPrivacies() - .subscribe(privacies => this.videoPrivacies = this.videoService.explainedPrivacyLabels(privacies)) - - this.serverConfig = this.serverService.getTmpConfig() - this.serverService.getConfig() - .subscribe(config => this.serverConfig = config) - - this.initialVideoCaptions = this.videoCaptions.map(c => c.language.id) - - this.ngZone.runOutsideAngular(() => { - this.schedulerInterval = setInterval(() => this.minScheduledDate = new Date(), 1000 * 60) // Update every minute - }) - } - - ngOnDestroy () { - if (this.schedulerInterval) clearInterval(this.schedulerInterval) - } - - onCaptionAdded (caption: VideoCaptionEdit) { - const existingCaption = this.videoCaptions.find(c => c.language.id === caption.language.id) - - // Replace existing caption? - if (existingCaption) { - Object.assign(existingCaption, caption, { action: 'CREATE' as 'CREATE' }) - } else { - this.videoCaptions.push( - Object.assign(caption, { action: 'CREATE' as 'CREATE' }) - ) - } - - this.sortVideoCaptions() - } - - async deleteCaption (caption: VideoCaptionEdit) { - // Caption recovers his former state - if (caption.action && this.initialVideoCaptions.indexOf(caption.language.id) !== -1) { - caption.action = undefined - return - } - - // This caption is not on the server, just remove it from our array - if (caption.action === 'CREATE') { - removeElementFromArray(this.videoCaptions, caption) - return - } - - caption.action = 'REMOVE' as 'REMOVE' - } - - openAddCaptionModal () { - this.videoCaptionAddModal.show() - } - - private sortVideoCaptions () { - this.videoCaptions.sort((v1, v2) => { - if (v1.language.label < v2.language.label) return -1 - if (v1.language.label === v2.language.label) return 0 - - return 1 - }) - } - - private trackPrivacyChange () { - // We will update the schedule input and the wait transcoding checkbox validators - this.form.controls[ 'privacy' ] - .valueChanges - .pipe(map(res => parseInt(res.toString(), 10))) - .subscribe( - newPrivacyId => { - - this.schedulePublicationEnabled = newPrivacyId === this.SPECIAL_SCHEDULED_PRIVACY - - // Value changed - const scheduleControl = this.form.get('schedulePublicationAt') - const waitTranscodingControl = this.form.get('waitTranscoding') - - if (this.schedulePublicationEnabled) { - scheduleControl.setValidators([ Validators.required ]) - - waitTranscodingControl.disable() - waitTranscodingControl.setValue(false) - } else { - scheduleControl.clearValidators() - - waitTranscodingControl.enable() - - // Do not update the control value on first patch (values come from the server) - if (this.firstPatchDone === true) { - waitTranscodingControl.setValue(true) - } - } - - scheduleControl.updateValueAndValidity() - waitTranscodingControl.updateValueAndValidity() - - this.firstPatchDone = true - - } - ) - } - - private trackChannelChange () { - // We will update the "support" field depending on the channel - this.form.controls[ 'channelId' ] - .valueChanges - .pipe(map(res => parseInt(res.toString(), 10))) - .subscribe( - newChannelId => { - const oldChannelId = parseInt(this.form.value[ 'channelId' ], 10) - - // Not initialized yet - if (isNaN(newChannelId)) return - const newChannel = this.userVideoChannels.find(c => c.id === newChannelId) - if (!newChannel) return - - // Wait support field update - setTimeout(() => { - const currentSupport = this.form.value[ 'support' ] - - // First time we set the channel? - if (isNaN(oldChannelId) && !currentSupport) return this.updateSupportField(newChannel.support) - - const oldChannel = this.userVideoChannels.find(c => c.id === oldChannelId) - if (!newChannel || !oldChannel) { - console.error('Cannot find new or old channel.') - return - } - - // If the current support text is not the same than the old channel, the user updated it. - // We don't want the user to lose his text, so stop here - if (currentSupport && currentSupport !== oldChannel.support) return - - // Update the support text with our new channel - this.updateSupportField(newChannel.support) - }) - } - ) - } - - private updateSupportField (support: string) { - return this.form.patchValue({ support: support || '' }) - } -} 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 deleted file mode 100644 index 96061a300..000000000 --- a/client/src/app/videos/+video-edit/shared/video-edit.module.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { TagInputModule } from 'ngx-chips' -import { CalendarModule } from 'primeng/calendar' -import { NgModule } from '@angular/core' -import { SharedFormModule } from '@app/shared/shared-forms' -import { SharedGlobalIconModule } from '@app/shared/shared-icons' -import { SharedMainModule } from '@app/shared/shared-main' -import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' -import { VideoEditComponent } from './video-edit.component' - -@NgModule({ - imports: [ - TagInputModule, - CalendarModule, - - SharedMainModule, - SharedFormModule, - SharedGlobalIconModule - ], - - declarations: [ - VideoEditComponent, - VideoCaptionAddModalComponent - ], - - exports: [ - TagInputModule, - CalendarModule, - - SharedMainModule, - SharedFormModule, - SharedGlobalIconModule, - - VideoEditComponent - ], - - providers: [] -}) -export class VideoEditModule { } diff --git a/client/src/app/videos/+video-edit/video-add-components/drag-drop.directive.ts b/client/src/app/videos/+video-edit/video-add-components/drag-drop.directive.ts deleted file mode 100644 index 7b1a38c62..000000000 --- a/client/src/app/videos/+video-edit/video-add-components/drag-drop.directive.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Directive, Output, EventEmitter, HostBinding, HostListener } from '@angular/core' - -@Directive({ - selector: '[dragDrop]' -}) -export class DragDropDirective { - @Output() fileDropped = new EventEmitter() - - @HostBinding('class.dragover') dragover = false - - @HostListener('dragover', ['$event']) onDragOver (e: Event) { - e.preventDefault() - e.stopPropagation() - this.dragover = true - } - - @HostListener('dragleave', ['$event']) public onDragLeave (e: Event) { - e.preventDefault() - e.stopPropagation() - this.dragover = false - } - - @HostListener('drop', ['$event']) public ondrop (e: DragEvent) { - e.preventDefault() - e.stopPropagation() - this.dragover = false - const files = e.dataTransfer.files - if (files.length > 0) this.fileDropped.emit(files) - } -} diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html b/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html deleted file mode 100644 index 7287f799d..000000000 --- a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html +++ /dev/null @@ -1,76 +0,0 @@ -
-
- - -
- Select the torrent to import - -
- -
- -
- - - - - You can import any torrent file that points to a mp4 file. - You should make sure you have diffusion rights over the content it points to, otherwise it could cause legal trouble to yourself and your instance. - - - - - -
- -
- -
- -
-
- -
- -
- -
-
- - -
-
- -
-
Sorry, but something went wrong
- {{ error }} -
- -
- Congratulations, the video will be imported with BitTorrent! You can already add information about this video. -
- - -
- - -
-
- - -
-
-
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.scss b/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.scss deleted file mode 100644 index 1fef74994..000000000 --- a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.scss +++ /dev/null @@ -1,18 +0,0 @@ -@import 'variables'; -@import 'mixins'; - -.first-step-block { - .torrent-or-magnet { - @include divider($color: pvar(--inputPlaceholderColor), $background: pvar(--submenuColor)); - - &[data-content] { - margin: 1.5rem 0; - } - } - - .form-group-magnet-uri { - margin-bottom: 40px; - } -} - - diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.ts b/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.ts deleted file mode 100644 index 5b453a1d9..000000000 --- a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { Component, ElementRef, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' -import { Router } from '@angular/router' -import { AuthService, CanComponentDeactivate, Notifier, ServerService } from '@app/core' -import { scrollToTop } from '@app/helpers' -import { FormValidatorService } from '@app/shared/shared-forms' -import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main' -import { VideoSend } from '@app/videos/+video-edit/video-add-components/video-send' -import { LoadingBarService } from '@ngx-loading-bar/core' -import { I18n } from '@ngx-translate/i18n-polyfill' -import { VideoPrivacy, VideoUpdate } from '@shared/models' - -@Component({ - selector: 'my-video-import-torrent', - templateUrl: './video-import-torrent.component.html', - styleUrls: [ - '../shared/video-edit.component.scss', - './video-import-torrent.component.scss', - './video-send.scss' - ] -}) -export class VideoImportTorrentComponent extends VideoSend implements OnInit, CanComponentDeactivate { - @Output() firstStepDone = new EventEmitter() - @Output() firstStepError = new EventEmitter() - @ViewChild('torrentfileInput') torrentfileInput: ElementRef - - magnetUri = '' - - isImportingVideo = false - hasImportedVideo = false - isUpdatingVideo = false - - video: VideoEdit - error: string - - protected readonly DEFAULT_VIDEO_PRIVACY = VideoPrivacy.PUBLIC - - constructor ( - protected formValidatorService: FormValidatorService, - protected loadingBar: LoadingBarService, - protected notifier: Notifier, - protected authService: AuthService, - protected serverService: ServerService, - protected videoService: VideoService, - protected videoCaptionService: VideoCaptionService, - private router: Router, - private videoImportService: VideoImportService, - private i18n: I18n - ) { - super() - } - - ngOnInit () { - super.ngOnInit() - } - - canDeactivate () { - return { canDeactivate: true } - } - - isMagnetUrlValid () { - return !!this.magnetUri - } - - fileChange () { - const torrentfile = this.torrentfileInput.nativeElement.files[0] - if (!torrentfile) return - - this.importVideo(torrentfile) - } - - setTorrentFile (files: FileList) { - this.torrentfileInput.nativeElement.files = files - this.fileChange() - } - - importVideo (torrentfile?: Blob) { - this.isImportingVideo = true - - const videoUpdate: VideoUpdate = { - privacy: this.firstStepPrivacyId, - waitTranscoding: false, - commentsEnabled: true, - downloadEnabled: true, - channelId: this.firstStepChannelId - } - - this.loadingBar.start() - - this.videoImportService.importVideoTorrent(torrentfile || this.magnetUri, videoUpdate).subscribe( - res => { - this.loadingBar.complete() - this.firstStepDone.emit(res.video.name) - this.isImportingVideo = false - this.hasImportedVideo = true - - this.video = new VideoEdit(Object.assign(res.video, { - commentsEnabled: videoUpdate.commentsEnabled, - downloadEnabled: videoUpdate.downloadEnabled, - support: null, - thumbnailUrl: null, - previewUrl: null - })) - - this.hydrateFormFromVideo() - }, - - err => { - this.loadingBar.complete() - this.isImportingVideo = false - this.firstStepError.emit() - this.notifier.error(err.message) - } - ) - } - - updateSecondStep () { - if (this.checkForm() === false) { - return - } - - this.video.patch(this.form.value) - - this.isUpdatingVideo = true - - // Update the video - this.updateVideoAndCaptions(this.video) - .subscribe( - () => { - this.isUpdatingVideo = false - this.notifier.success(this.i18n('Video to import updated.')) - - this.router.navigate([ '/my-account', 'video-imports' ]) - }, - - err => { - this.error = err.message - scrollToTop() - console.error(err) - } - ) - - } - - private hydrateFormFromVideo () { - this.form.patchValue(this.video.toFormPatch()) - } -} diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html b/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html deleted file mode 100644 index 1910da403..000000000 --- a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html +++ /dev/null @@ -1,72 +0,0 @@ -
-
- - -
- - - - - - You can import any URL supported by youtube-dl - or URL that points to a raw MP4 file. - You should make sure you have diffusion rights over the content it points to, otherwise it could cause legal trouble to yourself and your instance. - - - - - -
- -
- -
- -
-
- -
- -
- -
-
- - -
-
- - -
-
Sorry, but something went wrong
- {{ error }} -
- -
- Congratulations, the video behind {{ targetUrl }} will be imported! You can already add information about this video. -
- - -
- - -
-
- - -
-
-
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.ts b/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.ts deleted file mode 100644 index d0bd1f54d..000000000 --- a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { map, switchMap } from 'rxjs/operators' -import { Component, EventEmitter, OnInit, Output } from '@angular/core' -import { Router } from '@angular/router' -import { AuthService, CanComponentDeactivate, Notifier, ServerService } from '@app/core' -import { getAbsoluteAPIUrl, scrollToTop } from '@app/helpers' -import { FormValidatorService } from '@app/shared/shared-forms' -import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main' -import { VideoSend } from '@app/videos/+video-edit/video-add-components/video-send' -import { LoadingBarService } from '@ngx-loading-bar/core' -import { I18n } from '@ngx-translate/i18n-polyfill' -import { VideoPrivacy, VideoUpdate } from '@shared/models' - -@Component({ - selector: 'my-video-import-url', - templateUrl: './video-import-url.component.html', - styleUrls: [ - '../shared/video-edit.component.scss', - './video-send.scss' - ] -}) -export class VideoImportUrlComponent extends VideoSend implements OnInit, CanComponentDeactivate { - @Output() firstStepDone = new EventEmitter() - @Output() firstStepError = new EventEmitter() - - targetUrl = '' - - isImportingVideo = false - hasImportedVideo = false - isUpdatingVideo = false - - video: VideoEdit - error: string - - protected readonly DEFAULT_VIDEO_PRIVACY = VideoPrivacy.PUBLIC - - constructor ( - protected formValidatorService: FormValidatorService, - protected loadingBar: LoadingBarService, - protected notifier: Notifier, - protected authService: AuthService, - protected serverService: ServerService, - protected videoService: VideoService, - protected videoCaptionService: VideoCaptionService, - private router: Router, - private videoImportService: VideoImportService, - private i18n: I18n - ) { - super() - } - - ngOnInit () { - super.ngOnInit() - } - - canDeactivate () { - return { canDeactivate: true } - } - - isTargetUrlValid () { - return this.targetUrl && this.targetUrl.match(/https?:\/\//) - } - - importVideo () { - this.isImportingVideo = true - - const videoUpdate: VideoUpdate = { - privacy: this.firstStepPrivacyId, - waitTranscoding: false, - commentsEnabled: true, - downloadEnabled: true, - channelId: this.firstStepChannelId - } - - this.loadingBar.start() - - this.videoImportService - .importVideoUrl(this.targetUrl, videoUpdate) - .pipe( - switchMap(res => { - return this.videoCaptionService - .listCaptions(res.video.id) - .pipe( - map(result => ({ video: res.video, videoCaptions: result.data })) - ) - }) - ) - .subscribe( - ({ video, videoCaptions }) => { - this.loadingBar.complete() - this.firstStepDone.emit(video.name) - this.isImportingVideo = false - this.hasImportedVideo = true - - const absoluteAPIUrl = getAbsoluteAPIUrl() - - const thumbnailUrl = video.thumbnailPath - ? absoluteAPIUrl + video.thumbnailPath - : null - - const previewUrl = video.previewPath - ? absoluteAPIUrl + video.previewPath - : null - - this.video = new VideoEdit(Object.assign(video, { - commentsEnabled: videoUpdate.commentsEnabled, - downloadEnabled: videoUpdate.downloadEnabled, - support: null, - thumbnailUrl, - previewUrl - })) - - this.videoCaptions = videoCaptions - - this.hydrateFormFromVideo() - }, - - err => { - this.loadingBar.complete() - this.isImportingVideo = false - this.firstStepError.emit() - this.notifier.error(err.message) - } - ) - } - - updateSecondStep () { - if (this.checkForm() === false) { - return - } - - this.video.patch(this.form.value) - - this.isUpdatingVideo = true - - // Update the video - this.updateVideoAndCaptions(this.video) - .subscribe( - () => { - this.isUpdatingVideo = false - this.notifier.success(this.i18n('Video to import updated.')) - - this.router.navigate([ '/my-account', 'video-imports' ]) - }, - - err => { - this.error = err.message - scrollToTop() - console.error(err) - } - ) - - } - - private hydrateFormFromVideo () { - this.form.patchValue(this.video.toFormPatch()) - - const objects = [ - { - url: 'thumbnailUrl', - name: 'thumbnailfile' - }, - { - url: 'previewUrl', - name: 'previewfile' - } - ] - - for (const obj of objects) { - fetch(this.video[obj.url]) - .then(response => response.blob()) - .then(data => { - this.form.patchValue({ - [ obj.name ]: data - }) - }) - } - } -} diff --git a/client/src/app/videos/+video-edit/video-add-components/video-send.scss b/client/src/app/videos/+video-edit/video-add-components/video-send.scss deleted file mode 100644 index ebe14c59e..000000000 --- a/client/src/app/videos/+video-edit/video-add-components/video-send.scss +++ /dev/null @@ -1,46 +0,0 @@ -@import 'variables'; -@import 'mixins'; - -$width-size: 190px; - -.alert.alert-danger { - text-align: center; - - & > div { - font-weight: $font-semibold; - } -} - -.first-step-block { - display: flex; - flex-direction: column; - align-items: center; - - .upload-icon { - width: 90px; - margin-bottom: 25px; - - @include apply-svg-color(#C6C6C6); - } - - .peertube-select-container { - @include peertube-select-container($width-size); - } - - input[type=text] { - @include peertube-input-text($width-size); - display: block; - } - - input[type=button] { - @include peertube-button; - @include orange-button; - - width: $width-size; - margin-top: 30px; - } - - .button-file { - @include peertube-button-file(max-content); - } -} diff --git a/client/src/app/videos/+video-edit/video-add-components/video-send.ts b/client/src/app/videos/+video-edit/video-add-components/video-send.ts deleted file mode 100644 index 94479321d..000000000 --- a/client/src/app/videos/+video-edit/video-add-components/video-send.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { catchError, switchMap, tap } from 'rxjs/operators' -import { EventEmitter, OnInit } from '@angular/core' -import { AuthService, CanComponentDeactivateResult, Notifier, ServerService } from '@app/core' -import { populateAsyncUserVideoChannels } from '@app/helpers' -import { FormReactive } from '@app/shared/shared-forms' -import { VideoCaptionEdit, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main' -import { LoadingBarService } from '@ngx-loading-bar/core' -import { ServerConfig, VideoConstant, VideoPrivacy } from '@shared/models' - -export abstract class VideoSend extends FormReactive implements OnInit { - userVideoChannels: { id: number, label: string, support: string }[] = [] - videoPrivacies: VideoConstant[] = [] - videoCaptions: VideoCaptionEdit[] = [] - - firstStepPrivacyId = 0 - firstStepChannelId = 0 - - abstract firstStepDone: EventEmitter - abstract firstStepError: EventEmitter - protected abstract readonly DEFAULT_VIDEO_PRIVACY: VideoPrivacy - - protected loadingBar: LoadingBarService - protected notifier: Notifier - protected authService: AuthService - protected serverService: ServerService - protected videoService: VideoService - protected videoCaptionService: VideoCaptionService - protected serverConfig: ServerConfig - - abstract canDeactivate (): CanComponentDeactivateResult - - ngOnInit () { - this.buildForm({}) - - populateAsyncUserVideoChannels(this.authService, this.userVideoChannels) - .then(() => this.firstStepChannelId = this.userVideoChannels[ 0 ].id) - - this.serverConfig = this.serverService.getTmpConfig() - this.serverService.getConfig() - .subscribe(config => this.serverConfig = config) - - this.serverService.getVideoPrivacies() - .subscribe( - privacies => { - this.videoPrivacies = privacies - - this.firstStepPrivacyId = this.DEFAULT_VIDEO_PRIVACY - }) - } - - checkForm () { - this.forceCheck() - - return this.form.valid - } - - protected updateVideoAndCaptions (video: VideoEdit) { - this.loadingBar.start() - - return this.videoService.updateVideo(video) - .pipe( - // Then update captions - switchMap(() => this.videoCaptionService.updateCaptions(video.id, this.videoCaptions)), - tap(() => this.loadingBar.complete()), - catchError(err => { - this.loadingBar.complete() - throw err - }) - ) - } -} diff --git a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html deleted file mode 100644 index dad88a661..000000000 --- a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html +++ /dev/null @@ -1,90 +0,0 @@ -
-
- - -
- Select the file to upload - -
- -
- -
- -
-
- -
- -
- -
-
- - -
- - -
- Image that will be merged with your audio file. -
- The chosen image will be definitive and cannot be modified. -
- - -
- -
- -
-
-
-
- -
-
-
- Processing… - {{ videoUploadPercents }}% -
-
- -
- -
-
Sorry, but something went wrong
- {{ error }} -
- -
- Congratulations! Your video is now available in your private library. -
- - -
- - -
-
Publish will be available when upload is finished
- -
- - -
-
-
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss deleted file mode 100644 index a4f87b0b8..000000000 --- a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss +++ /dev/null @@ -1,49 +0,0 @@ -@import 'variables'; -@import 'mixins'; - -.first-step-block { - .form-group-channel { - margin-bottom: 20px; - margin-top: 35px; - } - - .audio-image-info { - margin-bottom: 10px; - } - - .audio-preview { - margin: 30px 0; - } -} - -.upload-progress-cancel { - display: flex; - margin-top: 25px; - margin-bottom: 40px; - - .progress { - @include progressbar; - flex-grow: 1; - height: 30px; - font-size: 15px; - background-color: rgba(11, 204, 41, 0.16); - - .progress-bar { - background-color: $green; - line-height: 30px; - text-align: left; - font-weight: $font-bold; - - span { - margin-left: 18px; - } - } - } - - input { - @include peertube-button; - @include grey-button; - - margin-left: 10px; - } -} diff --git a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts deleted file mode 100644 index eb7ac32ae..000000000 --- a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts +++ /dev/null @@ -1,306 +0,0 @@ -import { BytesPipe } from 'ngx-pipes' -import { Subscription } from 'rxjs' -import { HttpEventType, HttpResponse } from '@angular/common/http' -import { Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core' -import { Router } from '@angular/router' -import { AuthService, CanComponentDeactivate, Notifier, ServerService, UserService } from '@app/core' -import { scrollToTop } from '@app/helpers' -import { FormValidatorService } from '@app/shared/shared-forms' -import { VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main' -import { VideoSend } from '@app/videos/+video-edit/video-add-components/video-send' -import { LoadingBarService } from '@ngx-loading-bar/core' -import { I18n } from '@ngx-translate/i18n-polyfill' -import { VideoPrivacy } from '@shared/models' - -@Component({ - selector: 'my-video-upload', - templateUrl: './video-upload.component.html', - styleUrls: [ - '../shared/video-edit.component.scss', - './video-upload.component.scss', - './video-send.scss' - ] -}) -export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy, CanComponentDeactivate { - @Output() firstStepDone = new EventEmitter() - @Output() firstStepError = new EventEmitter() - @ViewChild('videofileInput') videofileInput: ElementRef - - // So that it can be accessed in the template - readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY - - userVideoQuotaUsed = 0 - userVideoQuotaUsedDaily = 0 - - isUploadingAudioFile = false - isUploadingVideo = false - isUpdatingVideo = false - - videoUploaded = false - videoUploadObservable: Subscription = null - videoUploadPercents = 0 - videoUploadedIds = { - id: 0, - uuid: '' - } - - waitTranscodingEnabled = true - previewfileUpload: File - - error: string - - protected readonly DEFAULT_VIDEO_PRIVACY = VideoPrivacy.PUBLIC - - constructor ( - protected formValidatorService: FormValidatorService, - protected loadingBar: LoadingBarService, - protected notifier: Notifier, - protected authService: AuthService, - protected serverService: ServerService, - protected videoService: VideoService, - protected videoCaptionService: VideoCaptionService, - private userService: UserService, - private router: Router, - private i18n: I18n - ) { - super() - } - - get videoExtensions () { - return this.serverConfig.video.file.extensions.join(', ') - } - - ngOnInit () { - super.ngOnInit() - - this.userService.getMyVideoQuotaUsed() - .subscribe(data => { - this.userVideoQuotaUsed = data.videoQuotaUsed - this.userVideoQuotaUsedDaily = data.videoQuotaUsedDaily - }) - } - - ngOnDestroy () { - if (this.videoUploadObservable) this.videoUploadObservable.unsubscribe() - } - - canDeactivate () { - let text = '' - - if (this.videoUploaded === true) { - // FIXME: cannot concatenate strings inside i18n service :/ - text = this.i18n('Your video was uploaded to your account and is private.') + ' ' + - this.i18n('But associated data (tags, description...) will be lost, are you sure you want to leave this page?') - } else { - text = this.i18n('Your video is not uploaded yet, are you sure you want to leave this page?') - } - - return { - canDeactivate: !this.isUploadingVideo, - text - } - } - - getVideoFile () { - return this.videofileInput.nativeElement.files[0] - } - - setVideoFile (files: FileList) { - this.videofileInput.nativeElement.files = files - this.fileChange() - } - - getAudioUploadLabel () { - const videofile = this.getVideoFile() - if (!videofile) return this.i18n('Upload') - - return this.i18n('Upload {{videofileName}}', { videofileName: videofile.name }) - } - - fileChange () { - this.uploadFirstStep() - } - - cancelUpload () { - if (this.videoUploadObservable !== null) { - this.videoUploadObservable.unsubscribe() - - this.isUploadingVideo = false - this.videoUploadPercents = 0 - this.videoUploadObservable = null - - this.firstStepError.emit() - - this.notifier.info(this.i18n('Upload cancelled')) - } - } - - uploadFirstStep (clickedOnButton = false) { - const videofile = this.getVideoFile() - if (!videofile) return - - if (!this.checkGlobalUserQuota(videofile)) return - if (!this.checkDailyUserQuota(videofile)) return - - if (clickedOnButton === false && this.isAudioFile(videofile.name)) { - this.isUploadingAudioFile = true - return - } - - // Build name field - const nameWithoutExtension = videofile.name.replace(/\.[^/.]+$/, '') - let name: string - - // If the name of the file is very small, keep the extension - if (nameWithoutExtension.length < 3) name = videofile.name - else name = nameWithoutExtension - - // Force user to wait transcoding for unsupported video types in web browsers - if (!videofile.name.endsWith('.mp4') && !videofile.name.endsWith('.webm') && !videofile.name.endsWith('.ogv')) { - this.waitTranscodingEnabled = false - } - - const privacy = this.firstStepPrivacyId.toString() - const nsfw = this.serverConfig.instance.isNSFW - const waitTranscoding = true - const commentsEnabled = true - const downloadEnabled = true - const channelId = this.firstStepChannelId.toString() - - const formData = new FormData() - formData.append('name', name) - // Put the video "private" -> we are waiting the user validation of the second step - formData.append('privacy', VideoPrivacy.PRIVATE.toString()) - formData.append('nsfw', '' + nsfw) - formData.append('commentsEnabled', '' + commentsEnabled) - formData.append('downloadEnabled', '' + downloadEnabled) - formData.append('waitTranscoding', '' + waitTranscoding) - formData.append('channelId', '' + channelId) - formData.append('videofile', videofile) - - if (this.previewfileUpload) { - formData.append('previewfile', this.previewfileUpload) - formData.append('thumbnailfile', this.previewfileUpload) - } - - this.isUploadingVideo = true - this.firstStepDone.emit(name) - - this.form.patchValue({ - name, - privacy, - nsfw, - channelId, - previewfile: this.previewfileUpload - }) - - this.videoUploadObservable = this.videoService.uploadVideo(formData).subscribe( - event => { - if (event.type === HttpEventType.UploadProgress) { - this.videoUploadPercents = Math.round(100 * event.loaded / event.total) - } else if (event instanceof HttpResponse) { - this.videoUploaded = true - - this.videoUploadedIds = event.body.video - - this.videoUploadObservable = null - } - }, - - err => { - // Reset progress - this.isUploadingVideo = false - this.videoUploadPercents = 0 - this.videoUploadObservable = null - this.firstStepError.emit() - this.notifier.error(err.message) - } - ) - } - - isPublishingButtonDisabled () { - return !this.form.valid || - this.isUpdatingVideo === true || - this.videoUploaded !== true - } - - updateSecondStep () { - if (this.checkForm() === false) { - return - } - - const video = new VideoEdit() - video.patch(this.form.value) - video.id = this.videoUploadedIds.id - video.uuid = this.videoUploadedIds.uuid - - this.isUpdatingVideo = true - - this.updateVideoAndCaptions(video) - .subscribe( - () => { - this.isUpdatingVideo = false - this.isUploadingVideo = false - - this.notifier.success(this.i18n('Video published.')) - this.router.navigate([ '/videos/watch', video.uuid ]) - }, - - err => { - this.error = err.message - scrollToTop() - console.error(err) - } - ) - } - - private checkGlobalUserQuota (videofile: File) { - const bytePipes = new BytesPipe() - - // Check global user quota - const videoQuota = this.authService.getUser().videoQuota - if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) { - const msg = this.i18n( - 'Your video quota is exceeded with this video (video size: {{videoSize}}, used: {{videoQuotaUsed}}, quota: {{videoQuota}})', - { - videoSize: bytePipes.transform(videofile.size, 0), - videoQuotaUsed: bytePipes.transform(this.userVideoQuotaUsed, 0), - videoQuota: bytePipes.transform(videoQuota, 0) - } - ) - this.notifier.error(msg) - - return false - } - - return true - } - - private checkDailyUserQuota (videofile: File) { - const bytePipes = new BytesPipe() - - // Check daily user quota - const videoQuotaDaily = this.authService.getUser().videoQuotaDaily - if (videoQuotaDaily !== -1 && (this.userVideoQuotaUsedDaily + videofile.size) > videoQuotaDaily) { - const msg = this.i18n( - 'Your daily video quota is exceeded with this video (video size: {{videoSize}}, used: {{quotaUsedDaily}}, quota: {{quotaDaily}})', - { - videoSize: bytePipes.transform(videofile.size, 0), - quotaUsedDaily: bytePipes.transform(this.userVideoQuotaUsedDaily, 0), - quotaDaily: bytePipes.transform(videoQuotaDaily, 0) - } - ) - this.notifier.error(msg) - - return false - } - - return true - } - - private isAudioFile (filename: string) { - const extensions = [ '.mp3', '.flac', '.ogg', '.wma', '.wav' ] - - return extensions.some(e => filename.endsWith(e)) - } -} diff --git a/client/src/app/videos/+video-edit/video-add-routing.module.ts b/client/src/app/videos/+video-edit/video-add-routing.module.ts deleted file mode 100644 index 9ff66bea0..000000000 --- a/client/src/app/videos/+video-edit/video-add-routing.module.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { NgModule } from '@angular/core' -import { RouterModule, Routes } from '@angular/router' -import { CanDeactivateGuard, LoginGuard } from '@app/core' -import { MetaGuard } from '@ngx-meta/core' -import { VideoAddComponent } from './video-add.component' - -const videoAddRoutes: Routes = [ - { - path: '', - component: VideoAddComponent, - canActivate: [ MetaGuard, LoginGuard ], - canDeactivate: [ CanDeactivateGuard ] - } -] - -@NgModule({ - imports: [ RouterModule.forChild(videoAddRoutes) ], - exports: [ RouterModule ] -}) -export class VideoAddRoutingModule {} diff --git a/client/src/app/videos/+video-edit/video-add.component.html b/client/src/app/videos/+video-edit/video-add.component.html deleted file mode 100644 index 79bfc6e5c..000000000 --- a/client/src/app/videos/+video-edit/video-add.component.html +++ /dev/null @@ -1,46 +0,0 @@ -
-
- We recommend you to not use the root user to publish your videos, since it's the super-admin account of your instance. -
- Instead, create a dedicated account to upload your videos. -
- -
- Import {{ videoName }} - Upload {{ videoName }} -
- - - -
-
diff --git a/client/src/app/videos/+video-edit/video-add.component.scss b/client/src/app/videos/+video-edit/video-add.component.scss deleted file mode 100644 index 0ad57d897..000000000 --- a/client/src/app/videos/+video-edit/video-add.component.scss +++ /dev/null @@ -1,89 +0,0 @@ -@import '_variables'; -@import '_mixins'; - -$border-width: 3px; -$border-type: solid; -$border-color: #EAEAEA; -$nav-link-height: 40px; - -.margin-content { - padding-top: 50px; -} - -.alert { - font-size: 15px; -} - -::ng-deep .video-add-nav { - border-bottom: $border-width $border-type $border-color; - margin: 50px 0 0 0 !important; - - &.hide-nav { - display: none !important; - } - - a.nav-link { - @include disable-default-a-behaviour; - - margin-bottom: -$border-width; - height: $nav-link-height !important; - padding: 0 30px !important; - font-size: 15px; - - &.active { - border: $border-width $border-type $border-color; - border-bottom: none; - background-color: pvar(--submenuColor) !important; - - span { - border-bottom: 2px solid pvar(--mainColor); - font-weight: $font-bold; - } - } - } -} - -::ng-deep .upload-video-container { - border: $border-width $border-type $border-color; - border-top: transparent; - - background-color: pvar(--submenuColor); - border-bottom-left-radius: 3px; - border-bottom-right-radius: 3px; - width: 100%; - min-height: 440px; - padding-bottom: 20px; - display: flex; - justify-content: center; - align-items: center; - - &.dragover { - border: 3px dashed pvar(--mainColor); - } -} - -@mixin nav-scroll { - ::ng-deep .video-add-nav { - height: #{$nav-link-height + $border-width * 2}; - overflow-x: auto; - white-space: nowrap; - flex-wrap: unset; - - /* Hide active tab style to not have a moving tab effect */ - a.nav-link.active { - border: none; - background-color: pvar(--mainBackgroundColor) !important; - } - } -} - -/* Make .video-add-nav tabs scrollable on small devices */ -@media screen and (max-width: $small-view) { - @include nav-scroll(); -} - -@media screen and (max-width: #{$small-view + $menu-width}) { - :host-context(.main-col:not(.expanded)) { - @include nav-scroll(); - } -} diff --git a/client/src/app/videos/+video-edit/video-add.component.ts b/client/src/app/videos/+video-edit/video-add.component.ts deleted file mode 100644 index 5bd768809..000000000 --- a/client/src/app/videos/+video-edit/video-add.component.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Component, HostListener, OnInit, ViewChild } from '@angular/core' -import { AuthService, CanComponentDeactivate, ServerService } from '@app/core' -import { ServerConfig } from '@shared/models' -import { VideoImportTorrentComponent } from './video-add-components/video-import-torrent.component' -import { VideoImportUrlComponent } from './video-add-components/video-import-url.component' -import { VideoUploadComponent } from './video-add-components/video-upload.component' - -@Component({ - selector: 'my-videos-add', - templateUrl: './video-add.component.html', - styleUrls: [ './video-add.component.scss' ] -}) -export class VideoAddComponent implements OnInit, CanComponentDeactivate { - @ViewChild('videoUpload') videoUpload: VideoUploadComponent - @ViewChild('videoImportUrl') videoImportUrl: VideoImportUrlComponent - @ViewChild('videoImportTorrent') videoImportTorrent: VideoImportTorrentComponent - - secondStepType: 'upload' | 'import-url' | 'import-torrent' - videoName: string - serverConfig: ServerConfig - - constructor ( - private auth: AuthService, - private serverService: ServerService - ) {} - - ngOnInit () { - this.serverConfig = this.serverService.getTmpConfig() - - this.serverService.getConfig() - .subscribe(config => this.serverConfig = config) - } - - onFirstStepDone (type: 'upload' | 'import-url' | 'import-torrent', videoName: string) { - this.secondStepType = type - this.videoName = videoName - } - - onError () { - this.videoName = undefined - this.secondStepType = undefined - } - - @HostListener('window:beforeunload', [ '$event' ]) - onUnload (event: any) { - const { text, canDeactivate } = this.canDeactivate() - - if (canDeactivate) return - - event.returnValue = text - return text - } - - canDeactivate (): { canDeactivate: boolean, text?: string} { - if (this.secondStepType === 'upload') return this.videoUpload.canDeactivate() - if (this.secondStepType === 'import-url') return this.videoImportUrl.canDeactivate() - if (this.secondStepType === 'import-torrent') return this.videoImportTorrent.canDeactivate() - - return { canDeactivate: true } - } - - isVideoImportHttpEnabled () { - return this.serverConfig.import.videos.http.enabled - } - - isVideoImportTorrentEnabled () { - return this.serverConfig.import.videos.torrent.enabled - } - - isInSecondStep () { - return !!this.secondStepType - } - - isRootUser () { - return this.auth.getUser().username === 'root' - } -} diff --git a/client/src/app/videos/+video-edit/video-add.module.ts b/client/src/app/videos/+video-edit/video-add.module.ts deleted file mode 100644 index 477c1cf5e..000000000 --- a/client/src/app/videos/+video-edit/video-add.module.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { NgModule } from '@angular/core' -import { CanDeactivateGuard } from '@app/core' -import { VideoEditModule } from './shared/video-edit.module' -import { DragDropDirective } from './video-add-components/drag-drop.directive' -import { VideoImportTorrentComponent } from './video-add-components/video-import-torrent.component' -import { VideoImportUrlComponent } from './video-add-components/video-import-url.component' -import { VideoUploadComponent } from './video-add-components/video-upload.component' -import { VideoAddRoutingModule } from './video-add-routing.module' -import { VideoAddComponent } from './video-add.component' - -@NgModule({ - imports: [ - VideoAddRoutingModule, - - VideoEditModule - ], - - declarations: [ - VideoAddComponent, - VideoUploadComponent, - VideoImportUrlComponent, - VideoImportTorrentComponent, - DragDropDirective - ], - - exports: [ ], - - providers: [ - CanDeactivateGuard - ] -}) -export class VideoAddModule { } diff --git a/client/src/app/videos/+video-edit/video-update-routing.module.ts b/client/src/app/videos/+video-edit/video-update-routing.module.ts deleted file mode 100644 index a04351b05..000000000 --- a/client/src/app/videos/+video-edit/video-update-routing.module.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { NgModule } from '@angular/core' -import { RouterModule, Routes } from '@angular/router' -import { CanDeactivateGuard, LoginGuard } from '@app/core' -import { MetaGuard } from '@ngx-meta/core' -import { VideoUpdateComponent } from './video-update.component' -import { VideoUpdateResolver } from './video-update.resolver' - -const videoUpdateRoutes: Routes = [ - { - path: '', - component: VideoUpdateComponent, - canActivate: [ MetaGuard, LoginGuard ], - canDeactivate: [ CanDeactivateGuard ], - resolve: { - videoData: VideoUpdateResolver - } - } -] - -@NgModule({ - imports: [ RouterModule.forChild(videoUpdateRoutes) ], - exports: [ RouterModule ] -}) -export class VideoUpdateRoutingModule {} diff --git a/client/src/app/videos/+video-edit/video-update.component.html b/client/src/app/videos/+video-edit/video-update.component.html deleted file mode 100644 index fbc642db9..000000000 --- a/client/src/app/videos/+video-edit/video-update.component.html +++ /dev/null @@ -1,22 +0,0 @@ -
-
- Update - {{ video?.name }} -
- -
- - - -
-
- - -
-
-
-
diff --git a/client/src/app/videos/+video-edit/video-update.component.ts b/client/src/app/videos/+video-edit/video-update.component.ts deleted file mode 100644 index 7bd6eb553..000000000 --- a/client/src/app/videos/+video-edit/video-update.component.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { map, switchMap } from 'rxjs/operators' -import { Component, HostListener, OnInit } from '@angular/core' -import { ActivatedRoute, Router } from '@angular/router' -import { Notifier } from '@app/core' -import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' -import { VideoCaptionEdit, VideoCaptionService, VideoDetails, VideoEdit, VideoService } from '@app/shared/shared-main' -import { LoadingBarService } from '@ngx-loading-bar/core' -import { I18n } from '@ngx-translate/i18n-polyfill' -import { VideoPrivacy } from '@shared/models' - -@Component({ - selector: 'my-videos-update', - styleUrls: [ './shared/video-edit.component.scss' ], - templateUrl: './video-update.component.html' -}) -export class VideoUpdateComponent extends FormReactive implements OnInit { - video: VideoEdit - - isUpdatingVideo = false - userVideoChannels: { id: number, label: string, support: string }[] = [] - schedulePublicationPossible = false - videoCaptions: VideoCaptionEdit[] = [] - waitTranscodingEnabled = true - - private updateDone = false - - constructor ( - protected formValidatorService: FormValidatorService, - private route: ActivatedRoute, - private router: Router, - private notifier: Notifier, - private videoService: VideoService, - private loadingBar: LoadingBarService, - private videoCaptionService: VideoCaptionService, - private i18n: I18n - ) { - super() - } - - ngOnInit () { - this.buildForm({}) - - this.route.data - .pipe(map(data => data.videoData)) - .subscribe(({ video, videoChannels, videoCaptions }) => { - this.video = new VideoEdit(video) - this.userVideoChannels = videoChannels - this.videoCaptions = videoCaptions - - this.schedulePublicationPossible = this.video.privacy === VideoPrivacy.PRIVATE - - const videoFiles = (video as VideoDetails).getFiles() - if (videoFiles.length > 1) { // Already transcoded - this.waitTranscodingEnabled = false - } - - // FIXME: Angular does not detect the change inside this subscription, so use the patched setTimeout - setTimeout(() => this.hydrateFormFromVideo()) - }, - - err => { - console.error(err) - this.notifier.error(err.message) - } - ) - } - - @HostListener('window:beforeunload', [ '$event' ]) - onUnload (event: any) { - const { text, canDeactivate } = this.canDeactivate() - - if (canDeactivate) return - - event.returnValue = text - return text - } - - canDeactivate (): { canDeactivate: boolean, text?: string } { - if (this.updateDone === true) return { canDeactivate: true } - - const text = this.i18n('You have unsaved changes! If you leave, your changes will be lost.') - - for (const caption of this.videoCaptions) { - if (caption.action) return { canDeactivate: false, text } - } - - return { canDeactivate: this.formChanged === false, text } - } - - checkForm () { - this.forceCheck() - - return this.form.valid - } - - update () { - if (this.checkForm() === false - || this.isUpdatingVideo === true) { - return - } - - this.video.patch(this.form.value) - - this.loadingBar.start() - this.isUpdatingVideo = true - - // Update the video - this.videoService.updateVideo(this.video) - .pipe( - // Then update captions - switchMap(() => this.videoCaptionService.updateCaptions(this.video.id, this.videoCaptions)) - ) - .subscribe( - () => { - this.updateDone = true - this.isUpdatingVideo = false - this.loadingBar.complete() - this.notifier.success(this.i18n('Video updated.')) - this.router.navigate([ '/videos/watch', this.video.uuid ]) - }, - - err => { - this.loadingBar.complete() - this.isUpdatingVideo = false - this.notifier.error(err.message) - console.error(err) - } - ) - } - - private hydrateFormFromVideo () { - this.form.patchValue(this.video.toFormPatch()) - - const objects = [ - { - url: 'thumbnailUrl', - name: 'thumbnailfile' - }, - { - url: 'previewUrl', - name: 'previewfile' - } - ] - - for (const obj of objects) { - fetch(this.video[obj.url]) - .then(response => response.blob()) - .then(data => { - this.form.patchValue({ - [ obj.name ]: data - }) - }) - } - } -} diff --git a/client/src/app/videos/+video-edit/video-update.module.ts b/client/src/app/videos/+video-edit/video-update.module.ts deleted file mode 100644 index 322c69629..000000000 --- a/client/src/app/videos/+video-edit/video-update.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { NgModule } from '@angular/core' -import { CanDeactivateGuard } from '@app/core' -import { VideoUpdateResolver } from '@app/videos/+video-edit/video-update.resolver' -import { VideoEditModule } from './shared/video-edit.module' -import { VideoUpdateRoutingModule } from './video-update-routing.module' -import { VideoUpdateComponent } from './video-update.component' - -@NgModule({ - imports: [ - VideoUpdateRoutingModule, - - VideoEditModule - ], - - declarations: [ - VideoUpdateComponent - ], - - exports: [ ], - - providers: [ - VideoUpdateResolver, - CanDeactivateGuard - ] -}) -export class VideoUpdateModule { } diff --git a/client/src/app/videos/+video-edit/video-update.resolver.ts b/client/src/app/videos/+video-edit/video-update.resolver.ts deleted file mode 100644 index 30bcf4d74..000000000 --- a/client/src/app/videos/+video-edit/video-update.resolver.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { forkJoin } from 'rxjs' -import { map, switchMap } from 'rxjs/operators' -import { Injectable } from '@angular/core' -import { ActivatedRouteSnapshot, Resolve } from '@angular/router' -import { VideoCaptionService, VideoChannelService, VideoService } from '@app/shared/shared-main' - -@Injectable() -export class VideoUpdateResolver implements Resolve { - constructor ( - private videoService: VideoService, - private videoChannelService: VideoChannelService, - private videoCaptionService: VideoCaptionService - ) { - } - - resolve (route: ActivatedRouteSnapshot) { - const uuid: string = route.params[ 'uuid' ] - - return this.videoService.getVideo({ videoId: uuid }) - .pipe( - switchMap(video => { - return forkJoin([ - this.videoService - .loadCompleteDescription(video.descriptionPath) - .pipe(map(description => Object.assign(video, { description }))), - - this.videoChannelService - .listAccountVideoChannels(video.account) - .pipe( - map(result => result.data), - map(videoChannels => videoChannels.map(c => ({ id: c.id, label: c.displayName, support: c.support }))) - ), - - this.videoCaptionService - .listCaptions(video.id) - .pipe( - map(result => result.data) - ) - ]) - }), - map(([ video, videoChannels, videoCaptions ]) => ({ video, videoChannels, videoCaptions })) - ) - } -} diff --git a/client/src/app/videos/+video-watch/comment/video-comment-add.component.html b/client/src/app/videos/+video-watch/comment/video-comment-add.component.html deleted file mode 100644 index 9b43d91da..000000000 --- a/client/src/app/videos/+video-watch/comment/video-comment-add.component.html +++ /dev/null @@ -1,56 +0,0 @@ -
-
- Avatar - -
- -
- {{ formErrors.text }} -
-
-
- -
- - -
-
- - - - - - diff --git a/client/src/app/videos/+video-watch/comment/video-comment-add.component.scss b/client/src/app/videos/+video-watch/comment/video-comment-add.component.scss deleted file mode 100644 index b3725ab94..000000000 --- a/client/src/app/videos/+video-watch/comment/video-comment-add.component.scss +++ /dev/null @@ -1,82 +0,0 @@ -@import '_variables'; -@import '_mixins'; - -form { - margin-bottom: 30px; -} - -.avatar-and-textarea { - display: flex; - margin-bottom: 10px; - - img { - @include avatar(25px); - - vertical-align: top; - margin-right: 10px; - } - - .form-group { - flex-grow: 1; - margin: 0; - - textarea { - @include peertube-textarea(100%, 60px); - - &:focus::placeholder { - opacity: 0; - } - } - } -} - -.comment-buttons { - display: flex; - justify-content: flex-end; - - button { - @include peertube-button; - @include disable-outline; - @include disable-default-a-behaviour; - - &:not(:last-child) { - margin-right: .5rem; - } - - &:last-child { - @include orange-button; - } - } - - .cancel-button { - @include tertiary-button; - - font-weight: $font-semibold; - display: inline-block; - padding: 0 10px 0 10px; - white-space: nowrap; - background: transparent; - } -} - -@media screen and (max-width: 600px) { - textarea, .comment-buttons button { - font-size: 14px !important; - } - - textarea { - padding: 5px !important; - } -} - -.modal-body { - .btn { - @include peertube-button; - @include orange-button; - } - - span { - float: left; - margin-bottom: 20px; - } -} diff --git a/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts b/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts deleted file mode 100644 index 79505c779..000000000 --- a/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { Observable } from 'rxjs' -import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' -import { Router } from '@angular/router' -import { Notifier, User } from '@app/core' -import { FormReactive, FormValidatorService, VideoCommentValidatorsService } from '@app/shared/shared-forms' -import { Video } from '@app/shared/shared-main' -import { NgbModal } from '@ng-bootstrap/ng-bootstrap' -import { VideoCommentCreate } from '@shared/models' -import { VideoComment } from './video-comment.model' -import { VideoCommentService } from './video-comment.service' - -@Component({ - selector: 'my-video-comment-add', - templateUrl: './video-comment-add.component.html', - styleUrls: ['./video-comment-add.component.scss'] -}) -export class VideoCommentAddComponent extends FormReactive implements OnInit { - @Input() user: User - @Input() video: Video - @Input() parentComment: VideoComment - @Input() parentComments: VideoComment[] - @Input() focusOnInit = false - - @Output() commentCreated = new EventEmitter() - @Output() cancel = new EventEmitter() - - @ViewChild('visitorModal', { static: true }) visitorModal: NgbModal - @ViewChild('textarea', { static: true }) textareaElement: ElementRef - - addingComment = false - - constructor ( - protected formValidatorService: FormValidatorService, - private videoCommentValidatorsService: VideoCommentValidatorsService, - private notifier: Notifier, - private videoCommentService: VideoCommentService, - private modalService: NgbModal, - private router: Router - ) { - super() - } - - ngOnInit () { - this.buildForm({ - text: this.videoCommentValidatorsService.VIDEO_COMMENT_TEXT - }) - - if (this.user) { - if (this.focusOnInit === true) { - this.textareaElement.nativeElement.focus() - } - - if (this.parentComment) { - const mentions = this.parentComments - .filter(c => c.account && c.account.id !== this.user.account.id) // Don't add mention of ourselves - .map(c => '@' + c.by) - - const mentionsSet = new Set(mentions) - const mentionsText = Array.from(mentionsSet).join(' ') + ' ' - - this.form.patchValue({ text: mentionsText }) - } - } - } - - onValidKey () { - this.check() - if (!this.form.valid) return - - this.formValidated() - } - - openVisitorModal (event: any) { - if (this.user === null) { // we only open it for visitors - // fixing ng-bootstrap ModalService and the "Expression Changed After It Has Been Checked" Error - event.srcElement.blur() - event.preventDefault() - - this.modalService.open(this.visitorModal) - } - } - - hideVisitorModal () { - this.modalService.dismissAll() - } - - formValidated () { - // If we validate very quickly the comment form, we might comment twice - if (this.addingComment) return - - this.addingComment = true - - const commentCreate: VideoCommentCreate = this.form.value - let obs: Observable - - if (this.parentComment) { - obs = this.addCommentReply(commentCreate) - } else { - obs = this.addCommentThread(commentCreate) - } - - obs.subscribe( - comment => { - this.addingComment = false - this.commentCreated.emit(comment) - this.form.reset() - }, - - err => { - this.addingComment = false - - this.notifier.error(err.text) - } - ) - } - - isAddButtonDisplayed () { - return this.form.value['text'] - } - - getUri () { - return window.location.href - } - - getAvatarUrl () { - if (this.user) return this.user.accountAvatarUrl - return window.location.origin + '/client/assets/images/default-avatar.png' - } - - gotoLogin () { - this.hideVisitorModal() - this.router.navigate([ '/login' ]) - } - - cancelCommentReply () { - this.cancel.emit(null) - this.form.value['text'] = this.textareaElement.nativeElement.value = '' - } - - private addCommentReply (commentCreate: VideoCommentCreate) { - return this.videoCommentService - .addCommentReply(this.video.id, this.parentComment.id, commentCreate) - } - - private addCommentThread (commentCreate: VideoCommentCreate) { - return this.videoCommentService - .addCommentThread(this.video.id, commentCreate) - } -} diff --git a/client/src/app/videos/+video-watch/comment/video-comment-thread-tree.model.ts b/client/src/app/videos/+video-watch/comment/video-comment-thread-tree.model.ts deleted file mode 100644 index 7c2aaeadd..000000000 --- a/client/src/app/videos/+video-watch/comment/video-comment-thread-tree.model.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { VideoCommentThreadTree as VideoCommentThreadTreeServerModel } from '@shared/models' -import { VideoComment } from './video-comment.model' - -export class VideoCommentThreadTree implements VideoCommentThreadTreeServerModel { - comment: VideoComment - children: VideoCommentThreadTree[] -} diff --git a/client/src/app/videos/+video-watch/comment/video-comment.component.html b/client/src/app/videos/+video-watch/comment/video-comment.component.html deleted file mode 100644 index 002de57e4..000000000 --- a/client/src/app/videos/+video-watch/comment/video-comment.component.html +++ /dev/null @@ -1,95 +0,0 @@ -
-
- - Avatar - - -
-
- -
- - -
- -
Highlighted comment
- - -
- -
-
Reply
-
Delete
- - -
-
- - - - -
- This comment has been deleted -
-
- - - -
-
- -
-
- - -
-
-
diff --git a/client/src/app/videos/+video-watch/comment/video-comment.component.scss b/client/src/app/videos/+video-watch/comment/video-comment.component.scss deleted file mode 100644 index e7ef79561..000000000 --- a/client/src/app/videos/+video-watch/comment/video-comment.component.scss +++ /dev/null @@ -1,189 +0,0 @@ -@import '_variables'; -@import '_mixins'; - -.root-comment { - font-size: 15px; - display: flex; - - .left { - display: flex; - flex-direction: column; - align-items: center; - margin-right: 10px; - - .vertical-border { - width: 2px; - height: 100%; - background-color: rgba(0, 0, 0, 0.05); - margin: 10px calc(1rem + 1px); - } - } - - .right { - width: 100%; - } - - .comment-avatar { - @include avatar(36px); - } - - .comment { - flex-grow: 1; - // Fix word-wrap with flex - min-width: 1px; - - .highlighted-comment { - display: inline-block; - background-color: #F5F5F5; - color: #3d3d3d; - padding: 0 5px; - font-size: 13px; - margin-bottom: 5px; - font-weight: $font-semibold; - border-radius: 3px; - } - - .comment-account-date { - display: flex; - margin-bottom: 4px; - - .video-author { - height: 20px; - background-color: #888888; - border-radius: 12px; - margin-bottom: 2px; - max-width: 100%; - box-sizing: border-box; - flex-direction: row; - align-items: center; - display: inline-flex; - padding-right: 6px; - padding-left: 6px; - color: white !important; - } - - .comment-account { - word-break: break-all; - font-weight: 600; - font-size: 90%; - - a { - @include disable-default-a-behaviour; - - color: pvar(--mainForegroundColor); - } - - .comment-account-fid { - opacity: .6; - } - } - - .comment-date { - font-size: 90%; - color: pvar(--greyForegroundColor); - margin-left: 5px; - text-decoration: none; - } - } - - .comment-html { - @include peertube-word-wrap; - - // Mentions - ::ng-deep a { - - &:not(.linkified-url) { - @include disable-default-a-behaviour; - - color: pvar(--mainForegroundColor); - - font-weight: $font-semibold; - } - - } - - // Paragraphs - ::ng-deep p { - margin-bottom: .3rem; - } - - &.comment-html-deleted { - color: pvar(--greyForegroundColor); - margin-bottom: 1rem; - } - } - - .comment-actions { - margin-bottom: 10px; - display: flex; - - ::ng-deep .dropdown-toggle, - .comment-action-reply, - .comment-action-delete { - color: pvar(--greyForegroundColor); - cursor: pointer; - margin-right: 10px; - - &:hover { - color: pvar(--mainForegroundColor); - } - } - - ::ng-deep .action-button { - background-color: transparent; - padding: 0; - font-weight: unset; - } - } - - my-video-comment-add { - ::ng-deep form { - margin-top: 1rem; - margin-bottom: 0; - } - } - } - - .children { - // Reduce avatars size for replies - .comment-avatar { - @include avatar(25px); - } - - .left { - margin-right: 6px; - } - } -} - -@media screen and (max-width: 1200px) { - .children { - margin-left: -10px; - } -} - -@media screen and (max-width: 600px) { - .root-comment { - .children { - margin-left: -20px; - - .left { - align-items: flex-start; - - .vertical-border { - margin-left: 2px; - } - } - } - - .comment { - .comment-account-date { - flex-direction: column; - - .comment-date { - margin-left: 0; - } - } - } - } -} diff --git a/client/src/app/videos/+video-watch/comment/video-comment.component.ts b/client/src/app/videos/+video-watch/comment/video-comment.component.ts deleted file mode 100644 index 27846c1ad..000000000 --- a/client/src/app/videos/+video-watch/comment/video-comment.component.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core' -import { MarkdownService, Notifier, UserService } from '@app/core' -import { AuthService } from '@app/core/auth' -import { Account, Actor, Video } from '@app/shared/shared-main' -import { User, UserRight } from '@shared/models' -import { VideoCommentThreadTree } from './video-comment-thread-tree.model' -import { VideoComment } from './video-comment.model' - -@Component({ - selector: 'my-video-comment', - templateUrl: './video-comment.component.html', - styleUrls: ['./video-comment.component.scss'] -}) -export class VideoCommentComponent implements OnInit, OnChanges { - @Input() video: Video - @Input() comment: VideoComment - @Input() parentComments: VideoComment[] = [] - @Input() commentTree: VideoCommentThreadTree - @Input() inReplyToCommentId: number - @Input() highlightedComment = false - @Input() firstInThread = false - - @Output() wantedToDelete = new EventEmitter() - @Output() wantedToReply = new EventEmitter() - @Output() threadCreated = new EventEmitter() - @Output() resetReply = new EventEmitter() - @Output() timestampClicked = new EventEmitter() - - sanitizedCommentHTML = '' - newParentComments: VideoComment[] = [] - - commentAccount: Account - commentUser: User - - constructor ( - private markdownService: MarkdownService, - private authService: AuthService, - private userService: UserService, - private notifier: Notifier - ) {} - - get user () { - return this.authService.getUser() - } - - ngOnInit () { - this.init() - } - - ngOnChanges () { - this.init() - } - - onCommentReplyCreated (createdComment: VideoComment) { - if (!this.commentTree) { - this.commentTree = { - comment: this.comment, - children: [] - } - - this.threadCreated.emit(this.commentTree) - } - - this.commentTree.children.unshift({ - comment: createdComment, - children: [] - }) - this.resetReply.emit() - } - - onWantToReply (comment?: VideoComment) { - this.wantedToReply.emit(comment || this.comment) - } - - onWantToDelete (comment?: VideoComment) { - this.wantedToDelete.emit(comment || this.comment) - } - - isUserLoggedIn () { - return this.authService.isLoggedIn() - } - - onResetReply () { - this.resetReply.emit() - } - - handleTimestampClicked (timestamp: number) { - this.timestampClicked.emit(timestamp) - } - - isRemovableByUser () { - return this.comment.account && this.isUserLoggedIn() && - ( - this.user.account.id === this.comment.account.id || - this.user.account.id === this.video.account.id || - this.user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) - ) - } - - switchToDefaultAvatar ($event: Event) { - ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL() - } - - private getUserIfNeeded (account: Account) { - if (!account.userId) return - if (!this.authService.isLoggedIn()) return - - const user = this.authService.getUser() - if (user.hasRight(UserRight.MANAGE_USERS)) { - this.userService.getUserWithCache(account.userId) - .subscribe( - user => this.commentUser = user, - - err => this.notifier.error(err.message) - ) - } - } - - private async init () { - const html = await this.markdownService.textMarkdownToHTML(this.comment.text, true) - this.sanitizedCommentHTML = await this.markdownService.processVideoTimestamps(html) - this.newParentComments = this.parentComments.concat([ this.comment ]) - - if (this.comment.account) { - this.commentAccount = new Account(this.comment.account) - this.getUserIfNeeded(this.commentAccount) - } else { - this.comment.account = null - } - } -} diff --git a/client/src/app/videos/+video-watch/comment/video-comment.model.ts b/client/src/app/videos/+video-watch/comment/video-comment.model.ts deleted file mode 100644 index e85443196..000000000 --- a/client/src/app/videos/+video-watch/comment/video-comment.model.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { getAbsoluteAPIUrl } from '@app/helpers' -import { Actor } from '@app/shared/shared-main' -import { Account as AccountInterface, VideoComment as VideoCommentServerModel } from '@shared/models' - -export class VideoComment implements VideoCommentServerModel { - id: number - url: string - text: string - threadId: number - inReplyToCommentId: number - videoId: number - createdAt: Date | string - updatedAt: Date | string - deletedAt: Date | string - isDeleted: boolean - account: AccountInterface - totalRepliesFromVideoAuthor: number - totalReplies: number - by: string - accountAvatarUrl: string - - isLocal: boolean - - constructor (hash: VideoCommentServerModel) { - this.id = hash.id - this.url = hash.url - this.text = hash.text - this.threadId = hash.threadId - this.inReplyToCommentId = hash.inReplyToCommentId - this.videoId = hash.videoId - this.createdAt = new Date(hash.createdAt.toString()) - this.updatedAt = new Date(hash.updatedAt.toString()) - this.deletedAt = hash.deletedAt ? new Date(hash.deletedAt.toString()) : null - this.isDeleted = hash.isDeleted - this.account = hash.account - this.totalRepliesFromVideoAuthor = hash.totalRepliesFromVideoAuthor - this.totalReplies = hash.totalReplies - - if (this.account) { - this.by = Actor.CREATE_BY_STRING(this.account.name, this.account.host) - this.accountAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.account) - - const absoluteAPIUrl = getAbsoluteAPIUrl() - const thisHost = new URL(absoluteAPIUrl).host - this.isLocal = this.account.host.trim() === thisHost - } - } -} diff --git a/client/src/app/videos/+video-watch/comment/video-comment.service.ts b/client/src/app/videos/+video-watch/comment/video-comment.service.ts deleted file mode 100644 index a73fb9ca8..000000000 --- a/client/src/app/videos/+video-watch/comment/video-comment.service.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { Observable } from 'rxjs' -import { catchError, map } from 'rxjs/operators' -import { HttpClient, HttpParams } from '@angular/common/http' -import { Injectable } from '@angular/core' -import { ComponentPaginationLight, RestExtractor, RestService } from '@app/core' -import { objectLineFeedToHtml } from '@app/helpers' -import { - FeedFormat, - ResultList, - VideoComment as VideoCommentServerModel, - VideoCommentCreate, - VideoCommentThreadTree as VideoCommentThreadTreeServerModel -} from '@shared/models' -import { environment } from '../../../../environments/environment' -import { VideoCommentThreadTree } from './video-comment-thread-tree.model' -import { VideoComment } from './video-comment.model' - -@Injectable() -export class VideoCommentService { - private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/' - private static BASE_FEEDS_URL = environment.apiUrl + '/feeds/video-comments.' - - constructor ( - private authHttp: HttpClient, - private restExtractor: RestExtractor, - private restService: RestService - ) {} - - addCommentThread (videoId: number | string, comment: VideoCommentCreate) { - const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comment-threads' - const normalizedComment = objectLineFeedToHtml(comment, 'text') - - return this.authHttp.post<{ comment: VideoCommentServerModel }>(url, normalizedComment) - .pipe( - map(data => this.extractVideoComment(data.comment)), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - addCommentReply (videoId: number | string, inReplyToCommentId: number, comment: VideoCommentCreate) { - const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comments/' + inReplyToCommentId - const normalizedComment = objectLineFeedToHtml(comment, 'text') - - return this.authHttp.post<{ comment: VideoCommentServerModel }>(url, normalizedComment) - .pipe( - map(data => this.extractVideoComment(data.comment)), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - getVideoCommentThreads (parameters: { - videoId: number | string, - componentPagination: ComponentPaginationLight, - sort: string - }): Observable> { - const { videoId, componentPagination, sort } = parameters - - const pagination = this.restService.componentPaginationToRestPagination(componentPagination) - - let params = new HttpParams() - params = this.restService.addRestGetParams(params, pagination, sort) - - const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comment-threads' - return this.authHttp.get>(url, { params }) - .pipe( - map(result => this.extractVideoComments(result)), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - getVideoThreadComments (parameters: { - videoId: number | string, - threadId: number - }): Observable { - const { videoId, threadId } = parameters - const url = `${VideoCommentService.BASE_VIDEO_URL + videoId}/comment-threads/${threadId}` - - return this.authHttp - .get(url) - .pipe( - map(tree => this.extractVideoCommentTree(tree)), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - deleteVideoComment (videoId: number | string, commentId: number) { - const url = `${VideoCommentService.BASE_VIDEO_URL + videoId}/comments/${commentId}` - - return this.authHttp - .delete(url) - .pipe( - map(this.restExtractor.extractDataBool), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - getVideoCommentsFeeds (videoUUID?: string) { - const feeds = [ - { - format: FeedFormat.RSS, - label: 'rss 2.0', - url: VideoCommentService.BASE_FEEDS_URL + FeedFormat.RSS.toLowerCase() - }, - { - format: FeedFormat.ATOM, - label: 'atom 1.0', - url: VideoCommentService.BASE_FEEDS_URL + FeedFormat.ATOM.toLowerCase() - }, - { - format: FeedFormat.JSON, - label: 'json 1.0', - url: VideoCommentService.BASE_FEEDS_URL + FeedFormat.JSON.toLowerCase() - } - ] - - if (videoUUID !== undefined) { - for (const feed of feeds) { - feed.url += '?videoId=' + videoUUID - } - } - - return feeds - } - - private extractVideoComment (videoComment: VideoCommentServerModel) { - return new VideoComment(videoComment) - } - - private extractVideoComments (result: ResultList) { - const videoCommentsJson = result.data - const totalComments = result.total - const comments: VideoComment[] = [] - - for (const videoCommentJson of videoCommentsJson) { - comments.push(new VideoComment(videoCommentJson)) - } - - return { data: comments, total: totalComments } - } - - private extractVideoCommentTree (tree: VideoCommentThreadTreeServerModel) { - if (!tree) return tree as VideoCommentThreadTree - - tree.comment = new VideoComment(tree.comment) - tree.children.forEach(c => this.extractVideoCommentTree(c)) - - return tree as VideoCommentThreadTree - } -} diff --git a/client/src/app/videos/+video-watch/comment/video-comments.component.html b/client/src/app/videos/+video-watch/comment/video-comments.component.html deleted file mode 100644 index dd1d43560..000000000 --- a/client/src/app/videos/+video-watch/comment/video-comments.component.html +++ /dev/null @@ -1,98 +0,0 @@ -
-
-

- - - 1 Comment - {{ componentPagination.totalItems }} Comments - - Comments -

- - - -
- -
- - -
-
-
- - - - -
No comments.
- -
-
-
- -
- -
- -
- - - - - - View {{ comment.totalReplies }} replies from {{ video?.account?.displayName || 'the author' }} and others - - - View {{ comment.totalReplies }} replies from {{ video?.account?.displayName || 'the author' }} - - - View {{ comment.totalReplies }} replies - - -
-
- -
-
-
- -
- Comments are disabled. -
-
diff --git a/client/src/app/videos/+video-watch/comment/video-comments.component.scss b/client/src/app/videos/+video-watch/comment/video-comments.component.scss deleted file mode 100644 index df42fae73..000000000 --- a/client/src/app/videos/+video-watch/comment/video-comments.component.scss +++ /dev/null @@ -1,53 +0,0 @@ -@import '_variables'; -@import '_mixins'; - -#highlighted-comment { - margin-bottom: 25px; -} - -.view-replies { - font-weight: $font-semibold; - font-size: 15px; - cursor: pointer; -} - -.glyphicon, .comment-thread-loading { - margin-right: 5px; - display: inline-block; - font-size: 13px; -} - -.title-block { - .title-page { - margin-right: 0; - } - - my-feed { - display: inline-block; - margin-left: 5px; - opacity: 0; - transition: ease-in .2s opacity; - } - &:hover my-feed { - opacity: 1; - } -} - -#dropdown-sort-comments { - font-weight: 600; - text-transform: uppercase; - border: none; - transform: translateY(-7%); -} - -@media screen and (max-width: 600px) { - .view-replies { - margin-left: 46px; - } -} - -@media screen and (max-width: 450px) { - .view-replies { - font-size: 14px; - } -} diff --git a/client/src/app/videos/+video-watch/comment/video-comments.component.ts b/client/src/app/videos/+video-watch/comment/video-comments.component.ts deleted file mode 100644 index df0018ec6..000000000 --- a/client/src/app/videos/+video-watch/comment/video-comments.component.ts +++ /dev/null @@ -1,232 +0,0 @@ -import { Subject, Subscription } from 'rxjs' -import { Component, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core' -import { ActivatedRoute } from '@angular/router' -import { AuthService, ComponentPagination, ConfirmService, hasMoreItems, Notifier, User } from '@app/core' -import { HooksService } from '@app/core/plugins/hooks.service' -import { Syndication, VideoDetails } from '@app/shared/shared-main' -import { I18n } from '@ngx-translate/i18n-polyfill' -import { VideoCommentThreadTree } from './video-comment-thread-tree.model' -import { VideoComment } from './video-comment.model' -import { VideoCommentService } from './video-comment.service' - -@Component({ - selector: 'my-video-comments', - templateUrl: './video-comments.component.html', - styleUrls: ['./video-comments.component.scss'] -}) -export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { - @ViewChild('commentHighlightBlock') commentHighlightBlock: ElementRef - @Input() video: VideoDetails - @Input() user: User - - @Output() timestampClicked = new EventEmitter() - - comments: VideoComment[] = [] - highlightedThread: VideoComment - sort = '-createdAt' - componentPagination: ComponentPagination = { - currentPage: 1, - itemsPerPage: 10, - totalItems: null - } - inReplyToCommentId: number - threadComments: { [ id: number ]: VideoCommentThreadTree } = {} - threadLoading: { [ id: number ]: boolean } = {} - - syndicationItems: Syndication[] = [] - - onDataSubject = new Subject() - - private sub: Subscription - - constructor ( - private authService: AuthService, - private notifier: Notifier, - private confirmService: ConfirmService, - private videoCommentService: VideoCommentService, - private activatedRoute: ActivatedRoute, - private i18n: I18n, - private hooks: HooksService - ) {} - - ngOnInit () { - // Find highlighted comment in params - this.sub = this.activatedRoute.params.subscribe( - params => { - if (params['threadId']) { - const highlightedThreadId = +params['threadId'] - this.processHighlightedThread(highlightedThreadId) - } - } - ) - } - - ngOnChanges (changes: SimpleChanges) { - if (changes['video']) { - this.resetVideo() - } - } - - ngOnDestroy () { - if (this.sub) this.sub.unsubscribe() - } - - viewReplies (commentId: number, highlightThread = false) { - this.threadLoading[commentId] = true - - const params = { - videoId: this.video.id, - threadId: commentId - } - - const obs = this.hooks.wrapObsFun( - this.videoCommentService.getVideoThreadComments.bind(this.videoCommentService), - params, - 'video-watch', - 'filter:api.video-watch.video-thread-replies.list.params', - 'filter:api.video-watch.video-thread-replies.list.result' - ) - - obs.subscribe( - res => { - this.threadComments[commentId] = res - this.threadLoading[commentId] = false - this.hooks.runAction('action:video-watch.video-thread-replies.loaded', 'video-watch', { data: res }) - - if (highlightThread) { - this.highlightedThread = new VideoComment(res.comment) - - // Scroll to the highlighted thread - setTimeout(() => this.commentHighlightBlock.nativeElement.scrollIntoView(), 0) - } - }, - - err => this.notifier.error(err.message) - ) - } - - loadMoreThreads () { - const params = { - videoId: this.video.id, - componentPagination: this.componentPagination, - sort: this.sort - } - - const obs = this.hooks.wrapObsFun( - this.videoCommentService.getVideoCommentThreads.bind(this.videoCommentService), - params, - 'video-watch', - 'filter:api.video-watch.video-threads.list.params', - 'filter:api.video-watch.video-threads.list.result' - ) - - obs.subscribe( - res => { - this.comments = this.comments.concat(res.data) - this.componentPagination.totalItems = res.total - - this.onDataSubject.next(res.data) - this.hooks.runAction('action:video-watch.video-threads.loaded', 'video-watch', { data: this.componentPagination }) - }, - - err => this.notifier.error(err.message) - ) - } - - onCommentThreadCreated (comment: VideoComment) { - this.comments.unshift(comment) - } - - onWantedToReply (comment: VideoComment) { - this.inReplyToCommentId = comment.id - } - - onResetReply () { - this.inReplyToCommentId = undefined - } - - onThreadCreated (commentTree: VideoCommentThreadTree) { - this.viewReplies(commentTree.comment.id) - } - - handleSortChange (sort: string) { - if (this.sort === sort) return - - this.sort = sort - this.resetVideo() - } - - handleTimestampClicked (timestamp: number) { - this.timestampClicked.emit(timestamp) - } - - async onWantedToDelete (commentToDelete: VideoComment) { - let message = 'Do you really want to delete this comment?' - - if (commentToDelete.isLocal || this.video.isLocal) { - message += this.i18n(' The deletion will be sent to remote instances so they can reflect the change.') - } else { - message += this.i18n(' It is a remote comment, so the deletion will only be effective on your instance.') - } - - const res = await this.confirmService.confirm(message, this.i18n('Delete')) - if (res === false) return - - this.videoCommentService.deleteVideoComment(commentToDelete.videoId, commentToDelete.id) - .subscribe( - () => { - if (this.highlightedThread?.id === commentToDelete.id) { - commentToDelete = this.comments.find(c => c.id === commentToDelete.id) - - this.highlightedThread = undefined - } - - // Mark the comment as deleted - this.softDeleteComment(commentToDelete) - }, - - err => this.notifier.error(err.message) - ) - } - - isUserLoggedIn () { - return this.authService.isLoggedIn() - } - - onNearOfBottom () { - if (hasMoreItems(this.componentPagination)) { - this.componentPagination.currentPage++ - this.loadMoreThreads() - } - } - - private softDeleteComment (comment: VideoComment) { - comment.isDeleted = true - comment.deletedAt = new Date() - comment.text = '' - comment.account = null - } - - private resetVideo () { - if (this.video.commentsEnabled === true) { - // Reset all our fields - this.highlightedThread = null - this.comments = [] - this.threadComments = {} - this.threadLoading = {} - this.inReplyToCommentId = undefined - this.componentPagination.currentPage = 1 - this.componentPagination.totalItems = null - - this.syndicationItems = this.videoCommentService.getVideoCommentsFeeds(this.video.uuid) - this.loadMoreThreads() - } - } - - private processHighlightedThread (highlightedThreadId: number) { - this.highlightedThread = this.comments.find(c => c.id === highlightedThreadId) - - const highlightThread = true - this.viewReplies(highlightedThreadId, highlightThread) - } -} diff --git a/client/src/app/videos/+video-watch/modal/video-share.component.html b/client/src/app/videos/+video-watch/modal/video-share.component.html deleted file mode 100644 index 5e6a2d518..000000000 --- a/client/src/app/videos/+video-watch/modal/video-share.component.html +++ /dev/null @@ -1,187 +0,0 @@ - - - - - - - diff --git a/client/src/app/videos/+video-watch/modal/video-share.component.scss b/client/src/app/videos/+video-watch/modal/video-share.component.scss deleted file mode 100644 index 091d4dc3b..000000000 --- a/client/src/app/videos/+video-watch/modal/video-share.component.scss +++ /dev/null @@ -1,79 +0,0 @@ -@import '_mixins'; -@import '_variables'; - -my-input-readonly-copy { - width: 100%; -} - -.title-page.title-page-single { - margin-top: 0; -} - -.playlist { - margin-bottom: 50px; -} - -.peertube-select-container { - @include peertube-select-container(200px); -} - -.qr-code-group { - text-align: center; -} - -.nav-content { - margin-top: 30px; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; -} - -.alert { - margin-top: 20px; -} - -.filters { - margin-top: 30px; - - .advanced-filters-button { - display: flex; - justify-content: center; - align-items: center; - margin-top: 20px; - font-size: 16px; - font-weight: $font-semibold; - cursor: pointer; - - .glyphicon { - margin-right: 5px; - } - } - - .form-group { - margin-bottom: 0; - height: 34px; - display: flex; - align-items: center; - } - - .video-caption-block { - display: flex; - align-items: center; - - .peertube-select-container { - margin-left: 10px; - } - } - - .start-at, - .stop-at { - width: 300px; - display: flex; - align-items: center; - - my-timestamp-input { - margin-left: 10px; - } - } -} diff --git a/client/src/app/videos/+video-watch/modal/video-share.component.ts b/client/src/app/videos/+video-watch/modal/video-share.component.ts deleted file mode 100644 index b42b775c1..000000000 --- a/client/src/app/videos/+video-watch/modal/video-share.component.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { Component, ElementRef, Input, ViewChild } from '@angular/core' -import { buildVideoEmbed, buildVideoLink } from '../../../../assets/player/utils' -import { NgbModal } from '@ng-bootstrap/ng-bootstrap' -import { VideoCaption } from '@shared/models' -import { VideoDetails } from '@app/shared/shared-main' -import { VideoPlaylist } from '@app/shared/shared-video-playlist' - -type Customizations = { - startAtCheckbox: boolean - startAt: number - - stopAtCheckbox: boolean - stopAt: number - - subtitleCheckbox: boolean - subtitle: string - - loop: boolean - autoplay: boolean - muted: boolean - title: boolean - warningTitle: boolean - controls: boolean -} - -@Component({ - selector: 'my-video-share', - templateUrl: './video-share.component.html', - styleUrls: [ './video-share.component.scss' ] -}) -export class VideoShareComponent { - @ViewChild('modal', { static: true }) modal: ElementRef - - @Input() video: VideoDetails = null - @Input() videoCaptions: VideoCaption[] = [] - @Input() playlist: VideoPlaylist = null - - activeId: 'url' | 'qrcode' | 'embed' = 'url' - customizations: Customizations - isAdvancedCustomizationCollapsed = true - includeVideoInPlaylist = false - - constructor (private modalService: NgbModal) { } - - show (currentVideoTimestamp?: number) { - let subtitle: string - if (this.videoCaptions.length !== 0) { - subtitle = this.videoCaptions[0].language.id - } - - this.customizations = { - startAtCheckbox: false, - startAt: currentVideoTimestamp ? Math.floor(currentVideoTimestamp) : 0, - - stopAtCheckbox: false, - stopAt: this.video.duration, - - subtitleCheckbox: false, - subtitle, - - loop: false, - autoplay: false, - muted: false, - - // Embed options - title: true, - warningTitle: true, - controls: true - } - - this.modalService.open(this.modal, { centered: true }) - } - - getVideoIframeCode () { - const options = this.getOptions(this.video.embedUrl) - - const embedUrl = buildVideoLink(options) - return buildVideoEmbed(embedUrl) - } - - getVideoUrl () { - const baseUrl = window.location.origin + '/videos/watch/' + this.video.uuid - const options = this.getOptions(baseUrl) - - return buildVideoLink(options) - } - - getPlaylistUrl () { - const base = window.location.origin + '/videos/watch/playlist/' + this.playlist.uuid - - if (!this.includeVideoInPlaylist) return base - - return base + '?videoId=' + this.video.uuid - } - - notSecure () { - return window.location.protocol === 'http:' - } - - isInEmbedTab () { - return this.activeId === 'embed' - } - - hasPlaylist () { - return !!this.playlist - } - - private getOptions (baseUrl?: string) { - return { - baseUrl, - - startTime: this.customizations.startAtCheckbox ? this.customizations.startAt : undefined, - stopTime: this.customizations.stopAtCheckbox ? this.customizations.stopAt : undefined, - - subtitle: this.customizations.subtitleCheckbox ? this.customizations.subtitle : undefined, - - loop: this.customizations.loop, - autoplay: this.customizations.autoplay, - muted: this.customizations.muted, - - title: this.customizations.title, - warningTitle: this.customizations.warningTitle, - controls: this.customizations.controls - } - } -} diff --git a/client/src/app/videos/+video-watch/modal/video-support.component.html b/client/src/app/videos/+video-watch/modal/video-support.component.html deleted file mode 100644 index 935656d23..000000000 --- a/client/src/app/videos/+video-watch/modal/video-support.component.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - diff --git a/client/src/app/videos/+video-watch/modal/video-support.component.scss b/client/src/app/videos/+video-watch/modal/video-support.component.scss deleted file mode 100644 index 184e09027..000000000 --- a/client/src/app/videos/+video-watch/modal/video-support.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -.action-button-cancel { - margin-right: 0 !important; -} diff --git a/client/src/app/videos/+video-watch/modal/video-support.component.ts b/client/src/app/videos/+video-watch/modal/video-support.component.ts deleted file mode 100644 index 48d5f2948..000000000 --- a/client/src/app/videos/+video-watch/modal/video-support.component.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Component, Input, ViewChild } from '@angular/core' -import { MarkdownService } from '@app/core' -import { VideoDetails } from '@app/shared/shared-main' -import { NgbModal } from '@ng-bootstrap/ng-bootstrap' - -@Component({ - selector: 'my-video-support', - templateUrl: './video-support.component.html', - styleUrls: [ './video-support.component.scss' ] -}) -export class VideoSupportComponent { - @Input() video: VideoDetails = null - - @ViewChild('modal', { static: true }) modal: NgbModal - - videoHTMLSupport = '' - - constructor ( - private markdownService: MarkdownService, - private modalService: NgbModal - ) { } - - show () { - this.modalService.open(this.modal, { centered: true }) - - this.markdownService.enhancedMarkdownToHTML(this.video.support) - .then(r => this.videoHTMLSupport = r) - } -} diff --git a/client/src/app/videos/+video-watch/timestamp-route-transformer.directive.ts b/client/src/app/videos/+video-watch/timestamp-route-transformer.directive.ts deleted file mode 100644 index 45e023695..000000000 --- a/client/src/app/videos/+video-watch/timestamp-route-transformer.directive.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Directive, EventEmitter, HostListener, Output } from '@angular/core' - -@Directive({ - selector: '[timestampRouteTransformer]' -}) -export class TimestampRouteTransformerDirective { - @Output() timestampClicked = new EventEmitter() - - @HostListener('click', ['$event']) - public onClick ($event: Event) { - const target = $event.target as HTMLLinkElement - - if (target.hasAttribute('href') !== true) return - - const ngxLink = document.createElement('a') - ngxLink.href = target.getAttribute('href') - - // we only care about reflective links - if (ngxLink.host !== window.location.host) return - - const ngxLinkParams = new URLSearchParams(ngxLink.search) - if (ngxLinkParams.has('start') !== true) return - - const separators = ['h', 'm', 's'] - const start = ngxLinkParams - .get('start') - .match(new RegExp('(\\d{1,9}[' + separators.join('') + '])','g')) // match digits before any given separator - .map(t => { - if (t.includes('h')) return parseInt(t, 10) * 3600 - if (t.includes('m')) return parseInt(t, 10) * 60 - return parseInt(t, 10) - }) - .reduce((acc, t) => acc + t) - - this.timestampClicked.emit(start) - - $event.preventDefault() - } -} diff --git a/client/src/app/videos/+video-watch/video-duration-formatter.pipe.ts b/client/src/app/videos/+video-watch/video-duration-formatter.pipe.ts deleted file mode 100644 index 4b6767415..000000000 --- a/client/src/app/videos/+video-watch/video-duration-formatter.pipe.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' -import { I18n } from '@ngx-translate/i18n-polyfill' - -@Pipe({ - name: 'myVideoDurationFormatter' -}) -export class VideoDurationPipe implements PipeTransform { - - constructor (private i18n: I18n) { - - } - - transform (value: number): string { - const hours = Math.floor(value / 3600) - const minutes = Math.floor((value % 3600) / 60) - const seconds = value % 60 - - if (hours > 0) { - return this.i18n('{{hours}} h {{minutes}} min {{seconds}} sec', { hours, minutes, seconds }) - } - - if (minutes > 0) { - return this.i18n('{{minutes}} min {{seconds}} sec', { minutes, seconds }) - } - - return this.i18n('{{seconds}} sec', { seconds }) - } -} diff --git a/client/src/app/videos/+video-watch/video-watch-playlist.component.html b/client/src/app/videos/+video-watch/video-watch-playlist.component.html deleted file mode 100644 index 246ef83cf..000000000 --- a/client/src/app/videos/+video-watch/video-watch-playlist.component.html +++ /dev/null @@ -1,46 +0,0 @@ -
-
-
- {{ playlist.displayName }} - - Unlisted - Private - Public -
- -
-
{{ playlist.ownerBy }}
-
- {{ currentPlaylistPosition }}{{ playlistPagination.totalItems }} -
-
- -
- - - -
-
- -
- -
-
diff --git a/client/src/app/videos/+video-watch/video-watch-playlist.component.scss b/client/src/app/videos/+video-watch/video-watch-playlist.component.scss deleted file mode 100644 index 0b0a2a899..000000000 --- a/client/src/app/videos/+video-watch/video-watch-playlist.component.scss +++ /dev/null @@ -1,83 +0,0 @@ -@import '_variables'; -@import '_mixins'; -@import '_bootstrap-variables'; -@import '_miniature'; - -.playlist { - min-width: 200px; - max-width: 470px; - height: 66vh; - background-color: pvar(--mainBackgroundColor); - overflow-y: auto; - border-bottom: 1px solid $separator-border-color; - - .playlist-info { - padding: 5px 30px; - background-color: #e4e4e4; - - .playlist-display-name { - font-size: 18px; - font-weight: $font-semibold; - margin-bottom: 5px; - } - - .playlist-by-index { - color: pvar(--greyForegroundColor); - display: flex; - - .playlist-by { - margin-right: 5px; - } - - .playlist-index span:first-child::after { - content: '/'; - margin: 0 3px; - } - } - - .playlist-controls { - display: flex; - margin: 10px 0; - - my-global-icon:not(:last-child) { - margin-right: .5rem; - } - - my-global-icon { - &:not(.active) { - opacity: .5 - } - - ::ng-deep { - cursor: pointer; - } - } - } - } - - my-video-playlist-element-miniature { - ::ng-deep { - .video { - .position { - margin-right: 0; - } - - .video-info { - .video-info-name { - font-size: 15px; - } - } - } - - my-video-thumbnail { - @include thumbnail-size-component(90px, 50px); - } - - .fake-thumbnail { - width: 90px; - height: 50px; - } - } - } -} - diff --git a/client/src/app/videos/+video-watch/video-watch-playlist.component.ts b/client/src/app/videos/+video-watch/video-watch-playlist.component.ts deleted file mode 100644 index 2c21be643..000000000 --- a/client/src/app/videos/+video-watch/video-watch-playlist.component.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { Component, Input } from '@angular/core' -import { Router } from '@angular/router' -import { AuthService, ComponentPagination, LocalStorageService, Notifier, SessionStorageService, UserService } from '@app/core' -import { peertubeLocalStorage, peertubeSessionStorage } from '@app/helpers/peertube-web-storage' -import { VideoPlaylist, VideoPlaylistElement, VideoPlaylistService } from '@app/shared/shared-video-playlist' -import { I18n } from '@ngx-translate/i18n-polyfill' -import { VideoDetails, VideoPlaylistPrivacy } from '@shared/models' - -@Component({ - selector: 'my-video-watch-playlist', - templateUrl: './video-watch-playlist.component.html', - styleUrls: [ './video-watch-playlist.component.scss' ] -}) -export class VideoWatchPlaylistComponent { - static LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'auto_play_video_playlist' - static SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'loop_playlist' - - @Input() video: VideoDetails - @Input() playlist: VideoPlaylist - - playlistElements: VideoPlaylistElement[] = [] - playlistPagination: ComponentPagination = { - currentPage: 1, - itemsPerPage: 30, - totalItems: null - } - - autoPlayNextVideoPlaylist: boolean - autoPlayNextVideoPlaylistSwitchText = '' - loopPlaylist: boolean - loopPlaylistSwitchText = '' - noPlaylistVideos = false - currentPlaylistPosition = 1 - - constructor ( - private userService: UserService, - private auth: AuthService, - private notifier: Notifier, - private i18n: I18n, - private videoPlaylist: VideoPlaylistService, - private localStorageService: LocalStorageService, - private sessionStorageService: SessionStorageService, - private router: Router - ) { - // defaults to true - this.autoPlayNextVideoPlaylist = this.auth.isLoggedIn() - ? this.auth.getUser().autoPlayNextVideoPlaylist - : this.localStorageService.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) !== 'false' - this.setAutoPlayNextVideoPlaylistSwitchText() - - // defaults to false - this.loopPlaylist = this.sessionStorageService.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true' - this.setLoopPlaylistSwitchText() - } - - onPlaylistVideosNearOfBottom () { - // Last page - if (this.playlistPagination.totalItems <= (this.playlistPagination.currentPage * this.playlistPagination.itemsPerPage)) return - - this.playlistPagination.currentPage += 1 - this.loadPlaylistElements(this.playlist,false) - } - - onElementRemoved (playlistElement: VideoPlaylistElement) { - this.playlistElements = this.playlistElements.filter(e => e.id !== playlistElement.id) - - this.playlistPagination.totalItems-- - } - - isPlaylistOwned () { - return this.playlist.isLocal === true && - this.auth.isLoggedIn() && - this.playlist.ownerAccount.name === this.auth.getUser().username - } - - isUnlistedPlaylist () { - return this.playlist.privacy.id === VideoPlaylistPrivacy.UNLISTED - } - - isPrivatePlaylist () { - return this.playlist.privacy.id === VideoPlaylistPrivacy.PRIVATE - } - - isPublicPlaylist () { - return this.playlist.privacy.id === VideoPlaylistPrivacy.PUBLIC - } - - loadPlaylistElements (playlist: VideoPlaylist, redirectToFirst = false) { - this.videoPlaylist.getPlaylistVideos(playlist.uuid, this.playlistPagination) - .subscribe(({ total, data }) => { - this.playlistElements = this.playlistElements.concat(data) - this.playlistPagination.totalItems = total - - const firstAvailableVideos = this.playlistElements.find(e => !!e.video) - if (!firstAvailableVideos) { - this.noPlaylistVideos = true - return - } - - this.updatePlaylistIndex(this.video) - - if (redirectToFirst) { - const extras = { - queryParams: { - start: firstAvailableVideos.startTimestamp, - stop: firstAvailableVideos.stopTimestamp, - videoId: firstAvailableVideos.video.uuid - }, - replaceUrl: true - } - this.router.navigate([], extras) - } - }) - } - - updatePlaylistIndex (video: VideoDetails) { - if (this.playlistElements.length === 0 || !video) return - - for (const playlistElement of this.playlistElements) { - if (playlistElement.video && playlistElement.video.id === video.id) { - this.currentPlaylistPosition = playlistElement.position - return - } - } - - // Load more videos to find our video - this.onPlaylistVideosNearOfBottom() - } - - findNextPlaylistVideo (position = this.currentPlaylistPosition): VideoPlaylistElement { - if (this.currentPlaylistPosition >= this.playlistPagination.totalItems) { - // we have reached the end of the playlist: either loop or stop - if (this.loopPlaylist) { - this.currentPlaylistPosition = position = 0 - } else { - return - } - } - - const next = this.playlistElements.find(e => e.position === position) - - if (!next || !next.video) { - return this.findNextPlaylistVideo(position + 1) - } - - return next - } - - navigateToNextPlaylistVideo () { - const next = this.findNextPlaylistVideo(this.currentPlaylistPosition + 1) - if (!next) return - const start = next.startTimestamp - const stop = next.stopTimestamp - this.router.navigate([],{ queryParams: { videoId: next.video.uuid, start, stop } }) - } - - switchAutoPlayNextVideoPlaylist () { - this.autoPlayNextVideoPlaylist = !this.autoPlayNextVideoPlaylist - this.setAutoPlayNextVideoPlaylistSwitchText() - - peertubeLocalStorage.setItem( - VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST, - this.autoPlayNextVideoPlaylist.toString() - ) - - if (this.auth.isLoggedIn()) { - const details = { - autoPlayNextVideoPlaylist: this.autoPlayNextVideoPlaylist - } - - this.userService.updateMyProfile(details).subscribe( - () => { - this.auth.refreshUserInformation() - }, - err => this.notifier.error(err.message) - ) - } - } - - switchLoopPlaylist () { - this.loopPlaylist = !this.loopPlaylist - this.setLoopPlaylistSwitchText() - - peertubeSessionStorage.setItem( - VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST, - this.loopPlaylist.toString() - ) - } - - private setAutoPlayNextVideoPlaylistSwitchText () { - this.autoPlayNextVideoPlaylistSwitchText = this.autoPlayNextVideoPlaylist - ? this.i18n('Stop autoplaying next video') - : this.i18n('Autoplay next video') - } - - private setLoopPlaylistSwitchText () { - this.loopPlaylistSwitchText = this.loopPlaylist - ? this.i18n('Stop looping playlist videos') - : this.i18n('Loop playlist videos') - } -} diff --git a/client/src/app/videos/+video-watch/video-watch-routing.module.ts b/client/src/app/videos/+video-watch/video-watch-routing.module.ts deleted file mode 100644 index d8fecb87d..000000000 --- a/client/src/app/videos/+video-watch/video-watch-routing.module.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { NgModule } from '@angular/core' -import { RouterModule, Routes } from '@angular/router' -import { MetaGuard } from '@ngx-meta/core' -import { VideoWatchComponent } from './video-watch.component' - -const videoWatchRoutes: Routes = [ - { - path: 'playlist/:playlistId', - component: VideoWatchComponent, - canActivate: [ MetaGuard ] - }, - { - path: ':videoId/comments/:commentId', - redirectTo: ':videoId' - }, - { - path: ':videoId', - component: VideoWatchComponent, - canActivate: [ MetaGuard ] - } -] - -@NgModule({ - imports: [ RouterModule.forChild(videoWatchRoutes) ], - exports: [ RouterModule ] -}) -export class VideoWatchRoutingModule {} diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html deleted file mode 100644 index 0447268f0..000000000 --- a/client/src/app/videos/+video-watch/video-watch.component.html +++ /dev/null @@ -1,277 +0,0 @@ -
- -
-
- Sorry, but this video is not available because the remote instance is not responding. -
- Please try again later. -
- -
- - -
- -
-
- The video is being imported, it will be available when the import is finished. -
- -
- The video is being transcoded, it may not work properly. -
- -
- This video will be published on {{ video.scheduledUpdate.updateAt | date: 'full' }}. -
- -
-
This video is blocked.
- {{ video.blockedReason }} -
-
- - -
-
-
-
-
-

{{ video.name }}

- -
- Published • {{ video.views | myNumberFormatter }} views -
-
- -
-
-

{{ video.name }}

-
- -
-
- Published • {{ video.views | myNumberFormatter }} views -
- -
-
- - - - - - - - -
- - -
- -
-
- - -
- -
-
-
- -
-
-
-
- -
- -
-
-
- - - -
- -
- -
-
- -
- Show more - - -
- -
- Show less - -
-
- -
-
- Privacy - {{ video.privacy.label }} -
- -
- Origin instance - {{ video.originInstanceHost }} -
- -
- Originally published - {{ video.originallyPublishedAt | date: 'dd MMMM yyyy' }} -
- -
- Category - {{ video.category.label }} - {{ video.category.label }} -
- -
- Licence - {{ video.licence.label }} - {{ video.licence.label }} -
- -
- Language - {{ video.language.label }} - {{ video.language.label }} -
- -
- Tags - {{ tag }} -
- -
- Duration - {{ video.duration | myVideoDurationFormatter }} -
-
- - -
- - -
- -
-
- - Friendly Reminder: - - the sharing system used for this video implies that some technical information about your system (such as a public IP address) can be sent to other peers. - - - More information -
- -
- OK -
-
-
- - - - - diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss deleted file mode 100644 index 2e083982e..000000000 --- a/client/src/app/videos/+video-watch/video-watch.component.scss +++ /dev/null @@ -1,607 +0,0 @@ -@import '_variables'; -@import '_mixins'; -@import '_bootstrap-variables'; -@import '_miniature'; - -$player-factor: 1.7; // 16/9 -$video-info-margin-left: 44px; - -@function getPlayerHeight($width){ - @return calc(#{$width} / #{$player-factor}) -} - -@function getPlayerWidth($height){ - @return calc(#{$height} * #{$player-factor}) -} - -@mixin playlist-below-player { - width: 100% !important; - height: auto !important; - max-height: 300px !important; - max-width: initial; - border-bottom: 1px solid $separator-border-color !important; -} - -.root { - &.theater-enabled #video-wrapper { - flex-direction: column; - justify-content: center; - - #videojs-wrapper { - width: 100%; - } - - ::ng-deep .video-js { - $height: calc(100vh - #{$header-height} - #{$theater-bottom-space}); - - height: $height; - width: 100%; - max-width: initial; - } - - my-video-watch-playlist ::ng-deep .playlist { - @include playlist-below-player; - } - } -} - -.blocked-label { - font-weight: $font-semibold; -} - -#video-wrapper { - background-color: #000; - display: flex; - justify-content: center; - - #videojs-wrapper { - display: flex; - justify-content: center; - flex-grow: 1; - } - - .remote-server-down { - color: #fff; - display: flex; - flex-direction: column; - align-items: center; - text-align: center; - justify-content: center; - background-color: #141313; - width: 100%; - font-size: 24px; - height: 500px; - - @media screen and (max-width: 1000px) { - font-size: 20px; - } - - @media screen and (max-width: 600px) { - font-size: 16px; - } - } - - ::ng-deep .video-js { - width: 100%; - max-width: getPlayerWidth(66vh); - height: 66vh; - - // VideoJS create an inner video player - video { - outline: 0; - position: relative !important; - } - } - - @media screen and (max-width: 600px) { - .remote-server-down, - ::ng-deep .video-js { - width: 100vw; - height: getPlayerHeight(100vw) - } - } -} - -.alert { - text-align: center; - border-radius: 0; -} - -.flex-direction-column { - flex-direction: column; -} - -#video-not-found { - height: 300px; - line-height: 300px; - margin-top: 50px; - text-align: center; - font-weight: $font-semibold; - font-size: 15px; -} - -.video-bottom { - display: flex; - margin-top: 1.5rem; - - .video-info { - flex-grow: 1; - // Set min width for flex item - min-width: 1px; - max-width: 100%; - - .video-info-first-row { - display: flex; - - & > div:first-child { - flex-grow: 1; - } - - .video-info-name { - margin-right: 30px; - min-height: 40px; // Align with the action buttons - font-size: 27px; - font-weight: $font-semibold; - flex-grow: 1; - } - - .video-info-first-row-bottom { - display: flex; - flex-wrap: wrap; - align-items: center; - width: 100%; - } - - .video-info-date-views { - align-self: start; - margin-bottom: 10px; - margin-right: 10px; - font-size: 1em; - } - - .video-info-channel { - font-weight: $font-semibold; - font-size: 15px; - - a { - @include disable-default-a-behaviour; - - color: pvar(--mainForegroundColor); - - &:hover { - opacity: 0.8; - } - - img { - @include avatar(18px); - - margin: -2px 5px 0 0; - } - } - - .video-info-channel-left { - flex-grow: 1; - - .video-info-channel-left-links { - display: flex; - flex-direction: column; - position: relative; - line-height: 1.37; - - a:nth-of-type(2) { - font-weight: 500; - font-size: 90%; - } - } - } - - my-subscribe-button { - margin-left: 5px; - } - } - - my-feed { - margin-left: 5px; - margin-top: 1px; - } - - .video-actions-rates { - margin: 0 0 10px 0; - align-items: start; - width: max-content; - margin-left: auto; - - .video-actions { - height: 40px; // Align with the title - display: flex; - align-items: center; - - .action-button:not(:first-child), - .action-dropdown, - my-video-actions-dropdown { - margin-left: 5px; - } - - ::ng-deep.action-button { - @include peertube-button; - @include button-with-icon(21px, 0, -1px); - @include apply-svg-color(pvar(--actionButtonColor)); - - font-size: 100%; - font-weight: $font-semibold; - display: inline-block; - padding: 0 10px 0 10px; - white-space: nowrap; - background-color: transparent !important; - color: pvar(--actionButtonColor); - text-transform: uppercase; - - &::after { - display: none; - } - - &:hover { - opacity: 0.9; - } - - &.action-button-like, - &.action-button-dislike { - filter: brightness(120%); - - .count { - margin-right: 5px; - } - } - - &.action-button-like.activated { - .count { - color: pvar(--activatedActionButtonColor); - } - - my-global-icon { - @include apply-svg-color(pvar(--activatedActionButtonColor)); - } - } - - &.action-button-dislike.activated { - .count { - color: pvar(--activatedActionButtonColor); - } - - my-global-icon { - @include apply-svg-color(pvar(--activatedActionButtonColor)); - } - } - - &.action-button-support { - color: pvar(--supportButtonColor); - - my-global-icon { - @include apply-svg-color(pvar(--supportButtonColor)); - } - } - - &.action-button-support { - my-global-icon { - ::ng-deep path:first-child { - fill: pvar(--supportButtonHeartColor) !important; - } - } - } - - &.action-button-save { - my-global-icon { - top: 0 !important; - right: -1px; - } - } - - .icon-text { - margin-left: 3px; - } - } - } - - .video-info-likes-dislikes-bar-outer-container { - position: relative; - } - - .video-info-likes-dislikes-bar-inner-container { - position: absolute; - height: 20px; - } - - .video-info-likes-dislikes-bar { - $likes-bar-height: 2px; - height: $likes-bar-height; - margin-top: -$likes-bar-height; - width: 120px; - background-color: #ccc; - position: relative; - top: 10px; - - .likes-bar { - height: 100%; - background-color: #909090; - - &.liked { - background-color: pvar(--activatedActionButtonColor); - } - } - } - } - } - - .video-info-description { - margin: 20px 0; - margin-left: $video-info-margin-left; - font-size: 15px; - - .video-info-description-html { - @include peertube-word-wrap; - - /deep/ a { - text-decoration: none; - } - } - - .glyphicon, .description-loading { - margin-left: 3px; - } - - .description-loading { - display: inline-block; - } - - .video-info-description-more { - cursor: pointer; - font-weight: $font-semibold; - color: pvar(--greyForegroundColor); - font-size: 14px; - - .glyphicon { - position: relative; - top: 2px; - } - } - } - - .video-attributes { - margin-left: $video-info-margin-left; - } - - .video-attributes .video-attribute { - font-size: 13px; - display: block; - margin-bottom: 12px; - - .video-attribute-label { - min-width: 142px; - padding-right: 5px; - display: inline-block; - color: pvar(--greyForegroundColor); - font-weight: $font-bold; - } - - a.video-attribute-value { - @include disable-default-a-behaviour; - color: pvar(--mainForegroundColor); - - &:hover { - opacity: 0.9; - } - } - - &.video-attribute-tags { - .video-attribute-value:not(:nth-child(2)) { - &::before { - content: ', ' - } - } - } - } - } - - ::ng-deep .other-videos { - padding-left: 15px; - min-width: $video-miniature-width; - - @media screen and (min-width: 1800px - (3* $video-miniature-width)) { - width: min-content; - } - - .title-page { - margin: 0 !important; - } - - .video-miniature { - display: flex; - width: max-content; - height: 100%; - padding-bottom: 20px; - flex-wrap: wrap; - } - - .video-bottom { - @media screen and (max-width: 1800px - (3* $video-miniature-width)) { - margin-left: 1rem; - } - @media screen and (max-width: 500px) { - margin-left: 0; - margin-top: .5rem; - } - } - } -} - -my-video-comments { - display: inline-block; - width: 100%; - margin-bottom: 20px; -} - -// If the view is not expanded, take into account the menu -.privacy-concerns { - z-index: z(dropdown) + 1; - width: calc(100% - #{$menu-width}); -} - -@media screen and (max-width: $small-view) { - .privacy-concerns { - margin-left: $menu-width - 15px; // Menu is absolute - } -} - -:host-context(.expanded) { - .privacy-concerns { - width: 100%; - margin-left: -15px; - } -} - -.privacy-concerns { - position: fixed; - bottom: 0; - z-index: z(privacymsg); - - padding: 5px 15px; - - display: flex; - flex-wrap: nowrap; - align-items: center; - justify-content: space-between; - background-color: rgba(0, 0, 0, 0.9); - color: #fff; - - .privacy-concerns-text { - margin: 0 5px; - } - - a { - @include disable-default-a-behaviour; - - color: pvar(--mainColor); - transition: color 0.3s; - - &:hover { - color: #fff; - } - } - - .privacy-concerns-button { - padding: 5px 8px 5px 7px; - margin-left: auto; - border-radius: 3px; - white-space: nowrap; - cursor: pointer; - transition: background-color 0.3s; - font-weight: $font-semibold; - - &:hover { - background-color: #000; - } - } - - .privacy-concerns-okay { - background-color: pvar(--mainColor); - margin-left: 10px; - } -} - -@media screen and (max-width: 1600px) { - .video-bottom .video-info .video-attributes .video-attribute { - margin-bottom: 5px; - } -} - -@media screen and (max-width: 1300px) { - .privacy-concerns { - font-size: 12px; - padding: 2px 5px; - - .privacy-concerns-text { - margin: 0; - } - } -} - -@media screen and (max-width: 1100px) { - #video-wrapper { - flex-direction: column; - justify-content: center; - - my-video-watch-playlist ::ng-deep .playlist { - @include playlist-below-player; - } - } - - .video-bottom { - flex-direction: column; - - ::ng-deep .other-videos { - padding-left: 0 !important; - - ::ng-deep .video-miniature { - flex-direction: row; - width: auto; - } - } - } -} - -@media screen and (max-width: 600px) { - .video-bottom { - margin-top: 20px !important; - padding-bottom: 20px !important; - - .video-info { - padding: 0; - - .video-info-first-row { - - .video-info-name { - font-size: 20px; - height: auto; - } - } - } - } - - ::ng-deep .other-videos .video-miniature { - flex-direction: column; - } - - .privacy-concerns { - width: 100%; - - strong { - display: none; - } - } -} - -@media screen and (max-width: 450px) { - .video-bottom { - .action-button .icon-text { - display: none !important; - } - - .video-info .video-info-first-row { - .video-info-name { - font-size: 18px; - } - - .video-info-date-views { - font-size: 14px; - } - - .video-actions-rates { - margin-top: 10px; - } - } - - .video-info-description { - font-size: 14px !important; - } - } -} diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts deleted file mode 100644 index 5b0b34c80..000000000 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ /dev/null @@ -1,782 +0,0 @@ -import { Hotkey, HotkeysService } from 'angular2-hotkeys' -import { forkJoin, Observable, Subscription } from 'rxjs' -import { catchError } from 'rxjs/operators' -import { PlatformLocation } from '@angular/common' -import { ChangeDetectorRef, Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' -import { ActivatedRoute, Router } from '@angular/router' -import { AuthService, AuthUser, ConfirmService, MarkdownService, Notifier, RestExtractor, ServerService, UserService } from '@app/core' -import { HooksService } from '@app/core/plugins/hooks.service' -import { RedirectService } from '@app/core/routing/redirect.service' -import { isXPercentInViewport, peertubeLocalStorage, scrollToTop } from '@app/helpers' -import { Video, VideoCaptionService, VideoDetails, VideoService } from '@app/shared/shared-main' -import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription' -import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist' -import { MetaService } from '@ngx-meta/core' -import { I18n } from '@ngx-translate/i18n-polyfill' -import { ServerConfig, UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '@shared/models' -import { getStoredP2PEnabled, getStoredTheater } from '../../../assets/player/peertube-player-local-storage' -import { - CustomizationOptions, - P2PMediaLoaderOptions, - PeertubePlayerManager, - PeertubePlayerManagerOptions, - PlayerMode, - videojs -} from '../../../assets/player/peertube-player-manager' -import { isWebRTCDisabled, timeToInt } from '../../../assets/player/utils' -import { environment } from '../../../environments/environment' -import { VideoShareComponent } from './modal/video-share.component' -import { VideoSupportComponent } from './modal/video-support.component' -import { VideoWatchPlaylistComponent } from './video-watch-playlist.component' - -@Component({ - selector: 'my-video-watch', - templateUrl: './video-watch.component.html', - styleUrls: [ './video-watch.component.scss' ] -}) -export class VideoWatchComponent implements OnInit, OnDestroy { - private static LOCAL_STORAGE_PRIVACY_CONCERN_KEY = 'video-watch-privacy-concern' - - @ViewChild('videoWatchPlaylist', { static: true }) videoWatchPlaylist: VideoWatchPlaylistComponent - @ViewChild('videoShareModal') videoShareModal: VideoShareComponent - @ViewChild('videoSupportModal') videoSupportModal: VideoSupportComponent - @ViewChild('subscribeButton') subscribeButton: SubscribeButtonComponent - - player: any - playerElement: HTMLVideoElement - theaterEnabled = false - userRating: UserVideoRateType = null - descriptionLoading = false - - video: VideoDetails = null - videoCaptions: VideoCaption[] = [] - - playlist: VideoPlaylist = null - - completeDescriptionShown = false - completeVideoDescription: string - shortVideoDescription: string - videoHTMLDescription = '' - likesBarTooltipText = '' - hasAlreadyAcceptedPrivacyConcern = false - remoteServerDown = false - hotkeys: Hotkey[] = [] - - tooltipLike = '' - tooltipDislike = '' - tooltipSupport = '' - tooltipSaveToPlaylist = '' - - private nextVideoUuid = '' - private nextVideoTitle = '' - private currentTime: number - private paramsSub: Subscription - private queryParamsSub: Subscription - private configSub: Subscription - - private serverConfig: ServerConfig - - constructor ( - private elementRef: ElementRef, - private changeDetector: ChangeDetectorRef, - private route: ActivatedRoute, - private router: Router, - private videoService: VideoService, - private playlistService: VideoPlaylistService, - private confirmService: ConfirmService, - private metaService: MetaService, - private authService: AuthService, - private userService: UserService, - private serverService: ServerService, - private restExtractor: RestExtractor, - private notifier: Notifier, - private markdownService: MarkdownService, - private zone: NgZone, - private redirectService: RedirectService, - private videoCaptionService: VideoCaptionService, - private i18n: I18n, - private hotkeysService: HotkeysService, - private hooks: HooksService, - private location: PlatformLocation, - @Inject(LOCALE_ID) private localeId: string - ) { - this.tooltipLike = this.i18n('Like this video') - this.tooltipDislike = this.i18n('Dislike this video') - this.tooltipSupport = this.i18n('Support options for this video') - this.tooltipSaveToPlaylist = this.i18n('Save to playlist') - } - - get user () { - return this.authService.getUser() - } - - get anonymousUser () { - return this.userService.getAnonymousUser() - } - - async ngOnInit () { - this.serverConfig = this.serverService.getTmpConfig() - - this.configSub = this.serverService.getConfig() - .subscribe(config => { - this.serverConfig = config - - if ( - isWebRTCDisabled() || - this.serverConfig.tracker.enabled === false || - getStoredP2PEnabled() === false || - peertubeLocalStorage.getItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY) === 'true' - ) { - this.hasAlreadyAcceptedPrivacyConcern = true - } - }) - - this.paramsSub = this.route.params.subscribe(routeParams => { - const videoId = routeParams[ 'videoId' ] - if (videoId) this.loadVideo(videoId) - - const playlistId = routeParams[ 'playlistId' ] - if (playlistId) this.loadPlaylist(playlistId) - }) - - this.queryParamsSub = this.route.queryParams.subscribe(async queryParams => { - const videoId = queryParams[ 'videoId' ] - if (videoId) this.loadVideo(videoId) - - const start = queryParams[ 'start' ] - if (this.player && start) this.player.currentTime(parseInt(start, 10)) - }) - - this.initHotkeys() - - this.theaterEnabled = getStoredTheater() - - this.hooks.runAction('action:video-watch.init', 'video-watch') - } - - ngOnDestroy () { - this.flushPlayer() - - // Unsubscribe subscriptions - if (this.paramsSub) this.paramsSub.unsubscribe() - if (this.queryParamsSub) this.queryParamsSub.unsubscribe() - - // Unbind hotkeys - this.hotkeysService.remove(this.hotkeys) - } - - setLike () { - if (this.isUserLoggedIn() === false) return - - // Already liked this video - if (this.userRating === 'like') this.setRating('none') - else this.setRating('like') - } - - setDislike () { - if (this.isUserLoggedIn() === false) return - - // Already disliked this video - if (this.userRating === 'dislike') this.setRating('none') - else this.setRating('dislike') - } - - getRatePopoverText () { - if (this.isUserLoggedIn()) return undefined - - return this.i18n('You need to be connected to rate this content.') - } - - showMoreDescription () { - if (this.completeVideoDescription === undefined) { - return this.loadCompleteDescription() - } - - this.updateVideoDescription(this.completeVideoDescription) - this.completeDescriptionShown = true - } - - showLessDescription () { - this.updateVideoDescription(this.shortVideoDescription) - this.completeDescriptionShown = false - } - - loadCompleteDescription () { - this.descriptionLoading = true - - this.videoService.loadCompleteDescription(this.video.descriptionPath) - .subscribe( - description => { - this.completeDescriptionShown = true - this.descriptionLoading = false - - this.shortVideoDescription = this.video.description - this.completeVideoDescription = description - - this.updateVideoDescription(this.completeVideoDescription) - }, - - error => { - this.descriptionLoading = false - this.notifier.error(error.message) - } - ) - } - - showSupportModal () { - this.pausePlayer() - - this.videoSupportModal.show() - } - - showShareModal () { - this.pausePlayer() - - this.videoShareModal.show(this.currentTime) - } - - isUserLoggedIn () { - return this.authService.isLoggedIn() - } - - getVideoTags () { - if (!this.video || Array.isArray(this.video.tags) === false) return [] - - return this.video.tags - } - - onRecommendations (videos: Video[]) { - if (videos.length > 0) { - // The recommended videos's first element should be the next video - const video = videos[0] - this.nextVideoUuid = video.uuid - this.nextVideoTitle = video.name - } - } - - onModalOpened () { - this.pausePlayer() - } - - onVideoRemoved () { - this.redirectService.redirectToHomepage() - } - - declinedPrivacyConcern () { - peertubeLocalStorage.setItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY, 'false') - this.hasAlreadyAcceptedPrivacyConcern = false - } - - acceptedPrivacyConcern () { - peertubeLocalStorage.setItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY, 'true') - this.hasAlreadyAcceptedPrivacyConcern = true - } - - isVideoToTranscode () { - return this.video && this.video.state.id === VideoState.TO_TRANSCODE - } - - isVideoToImport () { - return this.video && this.video.state.id === VideoState.TO_IMPORT - } - - hasVideoScheduledPublication () { - return this.video && this.video.scheduledUpdate !== undefined - } - - isVideoBlur (video: Video) { - return video.isVideoNSFWForUser(this.user, this.serverConfig) - } - - isAutoPlayEnabled () { - return ( - (this.user && this.user.autoPlayNextVideo) || - this.anonymousUser.autoPlayNextVideo - ) - } - - handleTimestampClicked (timestamp: number) { - if (this.player) this.player.currentTime(timestamp) - scrollToTop() - } - - isPlaylistAutoPlayEnabled () { - return ( - (this.user && this.user.autoPlayNextVideoPlaylist) || - this.anonymousUser.autoPlayNextVideoPlaylist - ) - } - - private loadVideo (videoId: string) { - // Video did not change - if (this.video && this.video.uuid === videoId) return - - if (this.player) this.player.pause() - - const videoObs = this.hooks.wrapObsFun( - this.videoService.getVideo.bind(this.videoService), - { videoId }, - 'video-watch', - 'filter:api.video-watch.video.get.params', - 'filter:api.video-watch.video.get.result' - ) - - // Video did change - forkJoin([ - videoObs, - this.videoCaptionService.listCaptions(videoId) - ]) - .pipe( - // If 401, the video is private or blocked so redirect to 404 - catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 401, 403, 404 ])) - ) - .subscribe(([ video, captionsResult ]) => { - const queryParams = this.route.snapshot.queryParams - - const urlOptions = { - startTime: queryParams.start, - stopTime: queryParams.stop, - - muted: queryParams.muted, - loop: queryParams.loop, - subtitle: queryParams.subtitle, - - playerMode: queryParams.mode, - peertubeLink: false - } - - this.onVideoFetched(video, captionsResult.data, urlOptions) - .catch(err => this.handleError(err)) - }) - } - - private loadPlaylist (playlistId: string) { - // Playlist did not change - if (this.playlist && this.playlist.uuid === playlistId) return - - this.playlistService.getVideoPlaylist(playlistId) - .pipe( - // If 401, the video is private or blocked so redirect to 404 - catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 401, 403, 404 ])) - ) - .subscribe(playlist => { - this.playlist = playlist - - const videoId = this.route.snapshot.queryParams['videoId'] - this.videoWatchPlaylist.loadPlaylistElements(playlist, !videoId) - }) - } - - private updateVideoDescription (description: string) { - this.video.description = description - this.setVideoDescriptionHTML() - .catch(err => console.error(err)) - } - - private async setVideoDescriptionHTML () { - const html = await this.markdownService.textMarkdownToHTML(this.video.description) - this.videoHTMLDescription = await this.markdownService.processVideoTimestamps(html) - } - - private setVideoLikesBarTooltipText () { - this.likesBarTooltipText = this.i18n('{{likesNumber}} likes / {{dislikesNumber}} dislikes', { - likesNumber: this.video.likes, - dislikesNumber: this.video.dislikes - }) - } - - private handleError (err: any) { - const errorMessage: string = typeof err === 'string' ? err : err.message - if (!errorMessage) return - - // Display a message in the video player instead of a notification - if (errorMessage.indexOf('from xs param') !== -1) { - this.flushPlayer() - this.remoteServerDown = true - this.changeDetector.detectChanges() - - return - } - - this.notifier.error(errorMessage) - } - - private checkUserRating () { - // Unlogged users do not have ratings - if (this.isUserLoggedIn() === false) return - - this.videoService.getUserVideoRating(this.video.id) - .subscribe( - ratingObject => { - if (ratingObject) { - this.userRating = ratingObject.rating - } - }, - - err => this.notifier.error(err.message) - ) - } - - private async onVideoFetched ( - video: VideoDetails, - videoCaptions: VideoCaption[], - urlOptions: CustomizationOptions & { playerMode: PlayerMode } - ) { - this.video = video - this.videoCaptions = videoCaptions - - // Re init attributes - this.descriptionLoading = false - this.completeDescriptionShown = false - this.remoteServerDown = false - this.currentTime = undefined - - this.videoWatchPlaylist.updatePlaylistIndex(video) - - if (this.isVideoBlur(this.video)) { - const res = await this.confirmService.confirm( - this.i18n('This video contains mature or explicit content. Are you sure you want to watch it?'), - this.i18n('Mature or explicit content') - ) - if (res === false) return this.location.back() - } - - // Flush old player if needed - this.flushPlayer() - - // Build video element, because videojs removes it on dispose - const playerElementWrapper = this.elementRef.nativeElement.querySelector('#videojs-wrapper') - this.playerElement = document.createElement('video') - this.playerElement.className = 'video-js vjs-peertube-skin' - this.playerElement.setAttribute('playsinline', 'true') - playerElementWrapper.appendChild(this.playerElement) - - const params = { - video: this.video, - videoCaptions, - urlOptions, - user: this.user - } - const { playerMode, playerOptions } = await this.hooks.wrapFun( - this.buildPlayerManagerOptions.bind(this), - params, - 'video-watch', - 'filter:internal.video-watch.player.build-options.params', - 'filter:internal.video-watch.player.build-options.result' - ) - - this.zone.runOutsideAngular(async () => { - this.player = await PeertubePlayerManager.initialize(playerMode, playerOptions, player => this.player = player) - this.player.focus() - - this.player.on('customError', ({ err }: { err: any }) => this.handleError(err)) - - this.player.on('timeupdate', () => { - this.currentTime = Math.floor(this.player.currentTime()) - }) - - /** - * replaces this.player.one('ended') - * 'condition()': true to make the upnext functionality trigger, - * false to disable the upnext functionality - * go to the next video in 'condition()' if you don't want of the timer. - * 'next': function triggered at the end of the timer. - * 'suspended': function used at each clic of the timer checking if we need - * to reset progress and wait until 'suspended' becomes truthy again. - */ - this.player.upnext({ - timeout: 10000, // 10s - headText: this.i18n('Up Next'), - cancelText: this.i18n('Cancel'), - suspendedText: this.i18n('Autoplay is suspended'), - getTitle: () => this.nextVideoTitle, - next: () => this.zone.run(() => this.autoplayNext()), - condition: () => { - if (this.playlist) { - if (this.isPlaylistAutoPlayEnabled()) { - // upnext will not trigger, and instead the next video will play immediately - this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo()) - } - } else if (this.isAutoPlayEnabled()) { - return true // upnext will trigger - } - return false // upnext will not trigger, and instead leave the video stopping - }, - suspended: () => { - return ( - !isXPercentInViewport(this.player.el(), 80) || - !document.getElementById('content').contains(document.activeElement) - ) - } - }) - - this.player.one('stopped', () => { - if (this.playlist) { - if (this.isPlaylistAutoPlayEnabled()) this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo()) - } - }) - - this.player.on('theaterChange', (_: any, enabled: boolean) => { - this.zone.run(() => this.theaterEnabled = enabled) - }) - - this.hooks.runAction('action:video-watch.player.loaded', 'video-watch', { player: this.player }) - }) - - this.setVideoDescriptionHTML() - this.setVideoLikesBarTooltipText() - - this.setOpenGraphTags() - this.checkUserRating() - - this.hooks.runAction('action:video-watch.video.loaded', 'video-watch', { videojs }) - } - - private autoplayNext () { - if (this.playlist) { - this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo()) - } else if (this.nextVideoUuid) { - this.router.navigate([ '/videos/watch', this.nextVideoUuid ]) - } - } - - private setRating (nextRating: UserVideoRateType) { - const ratingMethods: { [id in UserVideoRateType]: (id: number) => Observable } = { - like: this.videoService.setVideoLike, - dislike: this.videoService.setVideoDislike, - none: this.videoService.unsetVideoLike - } - - ratingMethods[nextRating].call(this.videoService, this.video.id) - .subscribe( - () => { - // Update the video like attribute - this.updateVideoRating(this.userRating, nextRating) - this.userRating = nextRating - }, - - (err: { message: string }) => this.notifier.error(err.message) - ) - } - - private updateVideoRating (oldRating: UserVideoRateType, newRating: UserVideoRateType) { - let likesToIncrement = 0 - let dislikesToIncrement = 0 - - if (oldRating) { - if (oldRating === 'like') likesToIncrement-- - if (oldRating === 'dislike') dislikesToIncrement-- - } - - if (newRating === 'like') likesToIncrement++ - if (newRating === 'dislike') dislikesToIncrement++ - - this.video.likes += likesToIncrement - this.video.dislikes += dislikesToIncrement - - this.video.buildLikeAndDislikePercents() - this.setVideoLikesBarTooltipText() - } - - private setOpenGraphTags () { - this.metaService.setTitle(this.video.name) - - this.metaService.setTag('og:type', 'video') - - this.metaService.setTag('og:title', this.video.name) - this.metaService.setTag('name', this.video.name) - - this.metaService.setTag('og:description', this.video.description) - this.metaService.setTag('description', this.video.description) - - this.metaService.setTag('og:image', this.video.previewPath) - - this.metaService.setTag('og:duration', this.video.duration.toString()) - - this.metaService.setTag('og:site_name', 'PeerTube') - - this.metaService.setTag('og:url', window.location.href) - this.metaService.setTag('url', window.location.href) - } - - private isAutoplay () { - // We'll jump to the thread id, so do not play the video - if (this.route.snapshot.params['threadId']) return false - - // Otherwise true by default - if (!this.user) return true - - // Be sure the autoPlay is set to false - return this.user.autoPlayVideo !== false - } - - private flushPlayer () { - // Remove player if it exists - if (this.player) { - try { - this.player.dispose() - this.player = undefined - } catch (err) { - console.error('Cannot dispose player.', err) - } - } - } - - private buildPlayerManagerOptions (params: { - video: VideoDetails, - videoCaptions: VideoCaption[], - urlOptions: CustomizationOptions & { playerMode: PlayerMode }, - user?: AuthUser - }) { - const { video, videoCaptions, urlOptions, user } = params - const getStartTime = () => { - const byUrl = urlOptions.startTime !== undefined - const byHistory = video.userHistory && (!this.playlist || urlOptions.resume !== undefined) - - if (byUrl) { - return timeToInt(urlOptions.startTime) - } else if (byHistory) { - return video.userHistory.currentTime - } else { - return 0 - } - } - - let startTime = getStartTime() - // If we are at the end of the video, reset the timer - if (video.duration - startTime <= 1) startTime = 0 - - const playerCaptions = videoCaptions.map(c => ({ - label: c.language.label, - language: c.language.id, - src: environment.apiUrl + c.captionPath - })) - - const options: PeertubePlayerManagerOptions = { - common: { - autoplay: this.isAutoplay(), - nextVideo: () => this.zone.run(() => this.autoplayNext()), - - playerElement: this.playerElement, - onPlayerElementChange: (element: HTMLVideoElement) => this.playerElement = element, - - videoDuration: video.duration, - enableHotkeys: true, - inactivityTimeout: 2500, - poster: video.previewUrl, - - startTime, - stopTime: urlOptions.stopTime, - controls: urlOptions.controls, - muted: urlOptions.muted, - loop: urlOptions.loop, - subtitle: urlOptions.subtitle, - - peertubeLink: urlOptions.peertubeLink, - - theaterButton: true, - captions: videoCaptions.length !== 0, - - videoViewUrl: video.privacy.id !== VideoPrivacy.PRIVATE - ? this.videoService.getVideoViewUrl(video.uuid) - : null, - embedUrl: video.embedUrl, - - language: this.localeId, - - userWatching: user && user.videosHistoryEnabled === true ? { - url: this.videoService.getUserWatchingVideoUrl(video.uuid), - authorizationHeader: this.authService.getRequestHeaderValue() - } : undefined, - - serverUrl: environment.apiUrl, - - videoCaptions: playerCaptions - }, - - webtorrent: { - videoFiles: video.files - } - } - - let mode: PlayerMode - - if (urlOptions.playerMode) { - if (urlOptions.playerMode === 'p2p-media-loader') mode = 'p2p-media-loader' - else mode = 'webtorrent' - } else { - if (video.hasHlsPlaylist()) mode = 'p2p-media-loader' - else mode = 'webtorrent' - } - - // p2p-media-loader needs TextEncoder, try to fallback on WebTorrent - if (typeof TextEncoder === 'undefined') { - mode = 'webtorrent' - } - - if (mode === 'p2p-media-loader') { - const hlsPlaylist = video.getHlsPlaylist() - - const p2pMediaLoader = { - playlistUrl: hlsPlaylist.playlistUrl, - segmentsSha256Url: hlsPlaylist.segmentsSha256Url, - redundancyBaseUrls: hlsPlaylist.redundancies.map(r => r.baseUrl), - trackerAnnounce: video.trackerUrls, - videoFiles: hlsPlaylist.files - } as P2PMediaLoaderOptions - - Object.assign(options, { p2pMediaLoader }) - } - - return { playerMode: mode, playerOptions: options } - } - - private pausePlayer () { - if (!this.player) return - - this.player.pause() - } - - private initHotkeys () { - this.hotkeys = [ - // These hotkeys are managed by the player - new Hotkey('f', e => e, undefined, this.i18n('Enter/exit fullscreen (requires player focus)')), - new Hotkey('space', e => e, undefined, this.i18n('Play/Pause the video (requires player focus)')), - new Hotkey('m', e => e, undefined, this.i18n('Mute/unmute the video (requires player focus)')), - - new Hotkey('0-9', e => e, undefined, this.i18n('Skip to a percentage of the video: 0 is 0% and 9 is 90% (requires player focus)')), - - new Hotkey('up', e => e, undefined, this.i18n('Increase the volume (requires player focus)')), - new Hotkey('down', e => e, undefined, this.i18n('Decrease the volume (requires player focus)')), - - new Hotkey('right', e => e, undefined, this.i18n('Seek the video forward (requires player focus)')), - new Hotkey('left', e => e, undefined, this.i18n('Seek the video backward (requires player focus)')), - - new Hotkey('>', e => e, undefined, this.i18n('Increase playback rate (requires player focus)')), - new Hotkey('<', e => e, undefined, this.i18n('Decrease playback rate (requires player focus)')), - - new Hotkey('.', e => e, undefined, this.i18n('Navigate in the video frame by frame (requires player focus)')) - ] - - if (this.isUserLoggedIn()) { - this.hotkeys = this.hotkeys.concat([ - new Hotkey('shift+l', () => { - this.setLike() - return false - }, undefined, this.i18n('Like the video')), - - new Hotkey('shift+d', () => { - this.setDislike() - return false - }, undefined, this.i18n('Dislike the video')), - - new Hotkey('shift+s', () => { - this.subscribeButton.subscribed ? this.subscribeButton.unsubscribe() : this.subscribeButton.subscribe() - return false - }, undefined, this.i18n('Subscribe to the account')) - ]) - } - - this.hotkeysService.add(this.hotkeys) - } -} diff --git a/client/src/app/videos/+video-watch/video-watch.module.ts b/client/src/app/videos/+video-watch/video-watch.module.ts deleted file mode 100644 index a1c54f065..000000000 --- a/client/src/app/videos/+video-watch/video-watch.module.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { QRCodeModule } from 'angularx-qrcode' -import { NgModule } from '@angular/core' -import { SharedFormModule } from '@app/shared/shared-forms' -import { SharedGlobalIconModule } from '@app/shared/shared-icons' -import { SharedMainModule } from '@app/shared/shared-main' -import { SharedModerationModule } from '@app/shared/shared-moderation' -import { SharedUserSubscriptionModule } from '@app/shared/shared-user-subscription' -import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature' -import { SharedVideoPlaylistModule } from '@app/shared/shared-video-playlist' -import { RecommendationsModule } from '@app/videos/recommendations/recommendations.module' -import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' -import { VideoCommentAddComponent } from './comment/video-comment-add.component' -import { VideoCommentComponent } from './comment/video-comment.component' -import { VideoCommentService } from './comment/video-comment.service' -import { VideoCommentsComponent } from './comment/video-comments.component' -import { VideoShareComponent } from './modal/video-share.component' -import { VideoSupportComponent } from './modal/video-support.component' -import { TimestampRouteTransformerDirective } from './timestamp-route-transformer.directive' -import { VideoDurationPipe } from './video-duration-formatter.pipe' -import { VideoWatchPlaylistComponent } from './video-watch-playlist.component' -import { VideoWatchRoutingModule } from './video-watch-routing.module' -import { VideoWatchComponent } from './video-watch.component' - -@NgModule({ - imports: [ - VideoWatchRoutingModule, - NgbTooltipModule, - QRCodeModule, - RecommendationsModule, - - SharedMainModule, - SharedFormModule, - SharedVideoMiniatureModule, - SharedVideoPlaylistModule, - SharedUserSubscriptionModule, - SharedModerationModule, - SharedGlobalIconModule - ], - - declarations: [ - VideoWatchComponent, - VideoWatchPlaylistComponent, - - VideoShareComponent, - VideoSupportComponent, - VideoCommentsComponent, - VideoCommentAddComponent, - VideoCommentComponent, - - TimestampRouteTransformerDirective, - VideoDurationPipe, - TimestampRouteTransformerDirective - ], - - exports: [ - VideoWatchComponent, - - TimestampRouteTransformerDirective - ], - - providers: [ - VideoCommentService - ] -}) -export class VideoWatchModule { } diff --git a/client/src/app/videos/index.ts b/client/src/app/videos/index.ts deleted file mode 100644 index 028a5854b..000000000 --- a/client/src/app/videos/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './videos.module' diff --git a/client/src/app/videos/recommendations/recent-videos-recommendation.service.ts b/client/src/app/videos/recommendations/recent-videos-recommendation.service.ts deleted file mode 100644 index a376453bf..000000000 --- a/client/src/app/videos/recommendations/recent-videos-recommendation.service.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { Observable, of } from 'rxjs' -import { map, switchMap } from 'rxjs/operators' -import { Injectable } from '@angular/core' -import { ServerService, UserService } from '@app/core' -import { AdvancedSearch } from '@app/search/advanced-search.model' -import { SearchService } from '@app/search/search.service' -import { Video, VideoService } from '@app/shared/shared-main' -import { ServerConfig } from '@shared/models' -import { RecommendationInfo } from './recommendation-info.model' -import { RecommendationService } from './recommendations.service' - -/** - * Provides "recommendations" by providing the most recently uploaded videos. - */ -@Injectable() -export class RecentVideosRecommendationService implements RecommendationService { - readonly pageSize = 5 - - private config: ServerConfig - - constructor ( - private videos: VideoService, - private searchService: SearchService, - private userService: UserService, - private serverService: ServerService - ) { - this.config = this.serverService.getTmpConfig() - - this.serverService.getConfig() - .subscribe(config => this.config = config) - } - - getRecommendations (recommendation: RecommendationInfo): Observable { - return this.fetchPage(1, recommendation) - .pipe( - map(videos => { - const otherVideos = videos.filter(v => v.uuid !== recommendation.uuid) - return otherVideos.slice(0, this.pageSize) - }) - ) - } - - private fetchPage (page: number, recommendation: RecommendationInfo): Observable { - const pagination = { currentPage: page, itemsPerPage: this.pageSize + 1 } - const defaultSubscription = this.videos.getVideos({ videoPagination: pagination, sort: '-createdAt' }) - .pipe(map(v => v.data)) - - const tags = recommendation.tags - const searchIndexConfig = this.config.search.searchIndex - if ( - !tags || tags.length === 0 || - (searchIndexConfig.enabled === true && searchIndexConfig.disableLocalSearch === true) - ) { - return defaultSubscription - } - - return this.userService.getAnonymousOrLoggedUser() - .pipe( - map(user => { - return { - search: '', - componentPagination: pagination, - advancedSearch: new AdvancedSearch({ - tagsOneOf: recommendation.tags.join(','), - sort: '-createdAt', - searchTarget: 'local', - nsfw: user.nsfwPolicy - ? this.videos.nsfwPolicyToParam(user.nsfwPolicy) - : undefined - }) - } - }), - switchMap(params => this.searchService.searchVideos(params)), - map(v => v.data), - switchMap(videos => { - if (videos.length <= 1) return defaultSubscription - - return of(videos) - }) - ) - } -} diff --git a/client/src/app/videos/recommendations/recommendation-info.model.ts b/client/src/app/videos/recommendations/recommendation-info.model.ts deleted file mode 100644 index 0233563bb..000000000 --- a/client/src/app/videos/recommendations/recommendation-info.model.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface RecommendationInfo { - uuid: string - tags?: string[] -} diff --git a/client/src/app/videos/recommendations/recommendations.module.ts b/client/src/app/videos/recommendations/recommendations.module.ts deleted file mode 100644 index 03cc272ca..000000000 --- a/client/src/app/videos/recommendations/recommendations.module.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { InputSwitchModule } from 'primeng/inputswitch' -import { CommonModule } from '@angular/common' -import { NgModule } from '@angular/core' -import { SharedMainModule } from '@app/shared/shared-main' -import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature' -import { SharedVideoPlaylistModule } from '@app/shared/shared-video-playlist' -import { RecentVideosRecommendationService } from './recent-videos-recommendation.service' -import { RecommendedVideosComponent } from './recommended-videos.component' -import { RecommendedVideosStore } from './recommended-videos.store' - -@NgModule({ - imports: [ - CommonModule, - InputSwitchModule, - - SharedMainModule, - SharedVideoPlaylistModule, - SharedVideoMiniatureModule - ], - declarations: [ - RecommendedVideosComponent - ], - exports: [ - RecommendedVideosComponent - ], - providers: [ - RecommendedVideosStore, - RecentVideosRecommendationService - ] -}) -export class RecommendationsModule { -} diff --git a/client/src/app/videos/recommendations/recommendations.service.ts b/client/src/app/videos/recommendations/recommendations.service.ts deleted file mode 100644 index 1d79d35f6..000000000 --- a/client/src/app/videos/recommendations/recommendations.service.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Observable } from 'rxjs' -import { Video } from '@app/shared/shared-main' -import { RecommendationInfo } from './recommendation-info.model' - -export interface RecommendationService { - getRecommendations (recommendation: RecommendationInfo): Observable -} diff --git a/client/src/app/videos/recommendations/recommended-videos.component.html b/client/src/app/videos/recommendations/recommended-videos.component.html deleted file mode 100644 index 0467cabf5..000000000 --- a/client/src/app/videos/recommendations/recommended-videos.component.html +++ /dev/null @@ -1,24 +0,0 @@ -
- -
-

- Other videos -

-
- AUTOPLAY - -
-
- - - - - -
-
-
-
diff --git a/client/src/app/videos/recommendations/recommended-videos.component.scss b/client/src/app/videos/recommendations/recommended-videos.component.scss deleted file mode 100644 index b278c9654..000000000 --- a/client/src/app/videos/recommendations/recommended-videos.component.scss +++ /dev/null @@ -1,31 +0,0 @@ -.title-page-container { - display: flex; - justify-content: space-between; - align-items: baseline; - margin-bottom: 25px; - flex-wrap: wrap-reverse; - - .title-page.active, .title-page.title-page-single { - margin-bottom: unset; - margin-right: .5rem !important; - } -} - -.title-page-autoplay { - display: flex; - width: max-content; - height: max-content; - align-items: center; - margin-left: auto; - - span { - margin-right: 0.3rem; - text-transform: uppercase; - font-size: 85%; - font-weight: 600; - } -} - -hr { - margin-top: 0; -} diff --git a/client/src/app/videos/recommendations/recommended-videos.component.ts b/client/src/app/videos/recommendations/recommended-videos.component.ts deleted file mode 100644 index 016975341..000000000 --- a/client/src/app/videos/recommendations/recommended-videos.component.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Observable } from 'rxjs' -import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core' -import { AuthService, Notifier, SessionStorageService, User, UserService } from '@app/core' -import { Video } from '@app/shared/shared-main' -import { MiniatureDisplayOptions } from '@app/shared/shared-video-miniature' -import { VideoPlaylist } from '@app/shared/shared-video-playlist' -import { I18n } from '@ngx-translate/i18n-polyfill' -import { RecommendationInfo } from './recommendation-info.model' -import { RecommendedVideosStore } from './recommended-videos.store' - -@Component({ - selector: 'my-recommended-videos', - templateUrl: './recommended-videos.component.html', - styleUrls: [ './recommended-videos.component.scss' ] -}) -export class RecommendedVideosComponent implements OnInit, OnChanges { - @Input() inputRecommendation: RecommendationInfo - @Input() playlist: VideoPlaylist - @Output() gotRecommendations = new EventEmitter() - - autoPlayNextVideo: boolean - autoPlayNextVideoTooltip: string - - displayOptions: MiniatureDisplayOptions = { - date: true, - views: true, - by: true, - avatar: true - } - - userMiniature: User - - readonly hasVideos$: Observable - readonly videos$: Observable - - constructor ( - private userService: UserService, - private authService: AuthService, - private notifier: Notifier, - private i18n: I18n, - private store: RecommendedVideosStore, - private sessionStorageService: SessionStorageService - ) { - this.videos$ = this.store.recommendations$ - this.hasVideos$ = this.store.hasRecommendations$ - this.videos$.subscribe(videos => this.gotRecommendations.emit(videos)) - - if (this.authService.isLoggedIn()) { - this.autoPlayNextVideo = this.authService.getUser().autoPlayNextVideo - } else { - this.autoPlayNextVideo = this.sessionStorageService.getItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true' || false - this.sessionStorageService.watch([User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO]).subscribe( - () => this.autoPlayNextVideo = this.sessionStorageService.getItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true' - ) - } - - this.autoPlayNextVideoTooltip = this.i18n('When active, the next video is automatically played after the current one.') - } - - ngOnInit () { - this.userService.getAnonymousOrLoggedUser() - .subscribe(user => this.userMiniature = user) - } - - ngOnChanges () { - if (this.inputRecommendation) { - this.store.requestNewRecommendations(this.inputRecommendation) - } - } - - onVideoRemoved () { - this.store.requestNewRecommendations(this.inputRecommendation) - } - - switchAutoPlayNextVideo () { - this.sessionStorageService.setItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO, this.autoPlayNextVideo.toString()) - - if (this.authService.isLoggedIn()) { - const details = { - autoPlayNextVideo: this.autoPlayNextVideo - } - - this.userService.updateMyProfile(details).subscribe( - () => { - this.authService.refreshUserInformation() - }, - err => this.notifier.error(err.message) - ) - } - } -} diff --git a/client/src/app/videos/recommendations/recommended-videos.store.ts b/client/src/app/videos/recommendations/recommended-videos.store.ts deleted file mode 100644 index 8c3fb6480..000000000 --- a/client/src/app/videos/recommendations/recommended-videos.store.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Observable, ReplaySubject } from 'rxjs' -import { map, shareReplay, switchMap, take } from 'rxjs/operators' -import { Inject, Injectable } from '@angular/core' -import { Video } from '@app/shared/shared-main' -import { RecentVideosRecommendationService } from './recent-videos-recommendation.service' -import { RecommendationInfo } from './recommendation-info.model' -import { RecommendationService } from './recommendations.service' - -/** - * This store is intended to provide data for the RecommendedVideosComponent. - */ -@Injectable() -export class RecommendedVideosStore { - public readonly recommendations$: Observable - public readonly hasRecommendations$: Observable - private readonly requestsForLoad$$ = new ReplaySubject(1) - - constructor ( - @Inject(RecentVideosRecommendationService) private recommendations: RecommendationService - ) { - this.recommendations$ = this.requestsForLoad$$.pipe( - switchMap(requestedRecommendation => { - return this.recommendations.getRecommendations(requestedRecommendation) - .pipe(take(1)) - }), - shareReplay() - ) - - this.hasRecommendations$ = this.recommendations$.pipe( - map(otherVideos => otherVideos.length > 0) - ) - } - - requestNewRecommendations (recommend: RecommendationInfo) { - this.requestsForLoad$$.next(recommend) - } -} diff --git a/client/src/app/videos/video-list/index.ts b/client/src/app/videos/video-list/index.ts deleted file mode 100644 index af1bd58b7..000000000 --- a/client/src/app/videos/video-list/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './overview' -export * from './video-local.component' -export * from './video-recently-added.component' -export * from './video-trending.component' -export * from './video-most-liked.component' diff --git a/client/src/app/videos/video-list/overview/index.ts b/client/src/app/videos/video-list/overview/index.ts deleted file mode 100644 index e6cfa4802..000000000 --- a/client/src/app/videos/video-list/overview/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './overview.service' -export * from './video-overview.component' -export * from './videos-overview.model' diff --git a/client/src/app/videos/video-list/overview/overview.service.ts b/client/src/app/videos/video-list/overview/overview.service.ts deleted file mode 100644 index 4458454d5..000000000 --- a/client/src/app/videos/video-list/overview/overview.service.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { forkJoin, Observable, of } from 'rxjs' -import { catchError, map, switchMap, tap } from 'rxjs/operators' -import { HttpClient, HttpParams } from '@angular/common/http' -import { Injectable } from '@angular/core' -import { RestExtractor, ServerService } from '@app/core' -import { immutableAssign } from '@app/helpers' -import { VideoService } from '@app/shared/shared-main' -import { peertubeTranslate, VideosOverview as VideosOverviewServer } from '@shared/models' -import { environment } from '../../../../environments/environment' -import { VideosOverview } from './videos-overview.model' - -@Injectable() -export class OverviewService { - static BASE_OVERVIEW_URL = environment.apiUrl + '/api/v1/overviews/' - - constructor ( - private authHttp: HttpClient, - private restExtractor: RestExtractor, - private videosService: VideoService, - private serverService: ServerService - ) {} - - getVideosOverview (page: number): Observable { - let params = new HttpParams() - params = params.append('page', page + '') - - return this.authHttp - .get(OverviewService.BASE_OVERVIEW_URL + 'videos', { params }) - .pipe( - switchMap(serverVideosOverview => this.updateVideosOverview(serverVideosOverview)), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - private updateVideosOverview (serverVideosOverview: VideosOverviewServer): Observable { - const observables: Observable[] = [] - const videosOverviewResult: VideosOverview = { - tags: [], - categories: [], - channels: [] - } - - // Build videos objects - for (const key of Object.keys(serverVideosOverview)) { - for (const object of serverVideosOverview[ key ]) { - observables.push( - of(object.videos) - .pipe( - switchMap(videos => this.videosService.extractVideos({ total: 0, data: videos })), - map(result => result.data), - tap(videos => { - videosOverviewResult[key].push(immutableAssign(object, { videos })) - }) - ) - ) - } - } - - if (observables.length === 0) return of(videosOverviewResult) - - return forkJoin(observables) - .pipe( - // Translate categories - switchMap(() => { - return this.serverService.getServerLocale() - .pipe( - tap(translations => { - for (const c of videosOverviewResult.categories) { - c.category.label = peertubeTranslate(c.category.label, translations) - } - }) - ) - }), - map(() => videosOverviewResult) - ) - } - -} diff --git a/client/src/app/videos/video-list/overview/video-overview.component.html b/client/src/app/videos/video-list/overview/video-overview.component.html deleted file mode 100644 index ca986c634..000000000 --- a/client/src/app/videos/video-list/overview/video-overview.component.html +++ /dev/null @@ -1,52 +0,0 @@ -

Discover

-
- -
No results.
- -
- - -
-

- {{ object.category.label }} -

- -
- - -
-
- -
-

- #{{ object.tag }} -

- -
- - -
-
- - - -
- -
- -
diff --git a/client/src/app/videos/video-list/overview/video-overview.component.scss b/client/src/app/videos/video-list/overview/video-overview.component.scss deleted file mode 100644 index c1d10188a..000000000 --- a/client/src/app/videos/video-list/overview/video-overview.component.scss +++ /dev/null @@ -1,16 +0,0 @@ -@import '_variables'; -@import '_mixins'; -@import '_miniature'; - -.section-title { - // make the element span a full grid row within .videos grid - grid-column: 1 / -1; -} - -.margin-content { - @include fluid-videos-miniature-layout; -} - -.section { - @include miniature-rows; -} diff --git a/client/src/app/videos/video-list/overview/video-overview.component.ts b/client/src/app/videos/video-list/overview/video-overview.component.ts deleted file mode 100644 index b3be1d7b5..000000000 --- a/client/src/app/videos/video-list/overview/video-overview.component.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { Subject } from 'rxjs' -import { Component, OnInit } from '@angular/core' -import { Notifier, ScreenService, User, UserService } from '@app/core' -import { Video } from '@app/shared/shared-main' -import { OverviewService } from './overview.service' -import { VideosOverview } from './videos-overview.model' - -@Component({ - selector: 'my-video-overview', - templateUrl: './video-overview.component.html', - styleUrls: [ './video-overview.component.scss' ] -}) -export class VideoOverviewComponent implements OnInit { - onDataSubject = new Subject() - - overviews: VideosOverview[] = [] - notResults = false - - userMiniature: User - - private loaded = false - private currentPage = 1 - private maxPage = 20 - private lastWasEmpty = false - private isLoading = false - - constructor ( - private notifier: Notifier, - private userService: UserService, - private overviewService: OverviewService, - private screenService: ScreenService - ) { } - - ngOnInit () { - this.loadMoreResults() - - this.userService.getAnonymousOrLoggedUser() - .subscribe(user => this.userMiniature = user) - - this.userService.listenAnonymousUpdate() - .subscribe(user => this.userMiniature = user) - } - - buildVideoChannelBy (object: { videos: Video[] }) { - return object.videos[0].byVideoChannel - } - - buildVideoChannelAvatarUrl (object: { videos: Video[] }) { - return object.videos[0].videoChannelAvatarUrl - } - - buildVideos (videos: Video[]) { - const numberOfVideos = this.screenService.getNumberOfAvailableMiniatures() - - return videos.slice(0, numberOfVideos * 2) - } - - onNearOfBottom () { - if (this.currentPage >= this.maxPage) return - if (this.lastWasEmpty) return - if (this.isLoading) return - - this.currentPage++ - this.loadMoreResults() - } - - private loadMoreResults () { - this.isLoading = true - - this.overviewService.getVideosOverview(this.currentPage) - .subscribe( - overview => { - this.isLoading = false - - if (overview.tags.length === 0 && overview.channels.length === 0 && overview.categories.length === 0) { - this.lastWasEmpty = true - if (this.loaded === false) this.notResults = true - - return - } - - this.loaded = true - this.onDataSubject.next(overview) - - this.overviews.push(overview) - }, - - err => { - this.notifier.error(err.message) - this.isLoading = false - } - ) - } -} diff --git a/client/src/app/videos/video-list/overview/videos-overview.model.ts b/client/src/app/videos/video-list/overview/videos-overview.model.ts deleted file mode 100644 index 6765ad9b7..000000000 --- a/client/src/app/videos/video-list/overview/videos-overview.model.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Video } from '@app/shared/shared-main' -import { VideoChannelSummary, VideoConstant, VideosOverview as VideosOverviewServer } from '@shared/models' - -export class VideosOverview implements VideosOverviewServer { - channels: { - channel: VideoChannelSummary - videos: Video[] - }[] - - categories: { - category: VideoConstant - videos: Video[] - }[] - - tags: { - tag: string - videos: Video[] - }[] - [key: string]: any -} diff --git a/client/src/app/videos/video-list/video-local.component.ts b/client/src/app/videos/video-list/video-local.component.ts deleted file mode 100644 index b4c71ac49..000000000 --- a/client/src/app/videos/video-list/video-local.component.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Component, OnDestroy, OnInit } from '@angular/core' -import { ActivatedRoute, Router } from '@angular/router' -import { AuthService, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core' -import { HooksService } from '@app/core/plugins/hooks.service' -import { immutableAssign } from '@app/helpers' -import { VideoService } from '@app/shared/shared-main' -import { AbstractVideoList } from '@app/shared/shared-video-miniature' -import { I18n } from '@ngx-translate/i18n-polyfill' -import { UserRight, VideoFilter, VideoSortField } from '@shared/models' - -@Component({ - selector: 'my-videos-local', - styleUrls: [ '../../shared/shared-video-miniature/abstract-video-list.scss' ], - templateUrl: '../../shared/shared-video-miniature/abstract-video-list.html' -}) -export class VideoLocalComponent extends AbstractVideoList implements OnInit, OnDestroy { - titlePage: string - sort = '-publishedAt' as VideoSortField - filter: VideoFilter = 'local' - - useUserVideoPreferences = true - - constructor ( - protected i18n: I18n, - protected router: Router, - protected serverService: ServerService, - protected route: ActivatedRoute, - protected notifier: Notifier, - protected authService: AuthService, - protected userService: UserService, - protected screenService: ScreenService, - protected storageService: LocalStorageService, - private videoService: VideoService, - private hooks: HooksService - ) { - super() - - this.titlePage = i18n('Local videos') - } - - ngOnInit () { - super.ngOnInit() - - if (this.authService.isLoggedIn()) { - const user = this.authService.getUser() - this.displayModerationBlock = user.hasRight(UserRight.SEE_ALL_VIDEOS) - } - - this.generateSyndicationList() - } - - ngOnDestroy () { - super.ngOnDestroy() - } - - getVideosObservable (page: number) { - const newPagination = immutableAssign(this.pagination, { currentPage: page }) - const params = { - videoPagination: newPagination, - sort: this.sort, - filter: this.filter, - categoryOneOf: this.categoryOneOf, - languageOneOf: this.languageOneOf, - nsfwPolicy: this.nsfwPolicy, - skipCount: true - } - - return this.hooks.wrapObsFun( - this.videoService.getVideos.bind(this.videoService), - params, - 'common', - 'filter:api.local-videos.videos.list.params', - 'filter:api.local-videos.videos.list.result' - ) - } - - generateSyndicationList () { - this.syndicationItems = this.videoService.getVideoFeedUrls(this.sort, this.filter, this.categoryOneOf) - } - - toggleModerationDisplay () { - this.filter = this.filter === 'local' ? 'all-local' as 'all-local' : 'local' as 'local' - - this.reloadVideos() - } -} diff --git a/client/src/app/videos/video-list/video-most-liked.component.ts b/client/src/app/videos/video-list/video-most-liked.component.ts deleted file mode 100644 index ca14851bb..000000000 --- a/client/src/app/videos/video-list/video-most-liked.component.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Component, OnInit } from '@angular/core' -import { ActivatedRoute, Router } from '@angular/router' -import { AuthService, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core' -import { HooksService } from '@app/core/plugins/hooks.service' -import { immutableAssign } from '@app/helpers' -import { VideoService } from '@app/shared/shared-main' -import { AbstractVideoList } from '@app/shared/shared-video-miniature' -import { I18n } from '@ngx-translate/i18n-polyfill' -import { VideoSortField } from '@shared/models' - -@Component({ - selector: 'my-videos-most-liked', - styleUrls: [ '../../shared/shared-video-miniature/abstract-video-list.scss' ], - templateUrl: '../../shared/shared-video-miniature/abstract-video-list.html' -}) -export class VideoMostLikedComponent extends AbstractVideoList implements OnInit { - titlePage: string - defaultSort: VideoSortField = '-likes' - - useUserVideoPreferences = true - - constructor ( - protected i18n: I18n, - protected router: Router, - protected serverService: ServerService, - protected route: ActivatedRoute, - protected notifier: Notifier, - protected authService: AuthService, - protected userService: UserService, - protected screenService: ScreenService, - protected storageService: LocalStorageService, - private videoService: VideoService, - private hooks: HooksService - ) { - super() - } - - ngOnInit () { - super.ngOnInit() - - this.generateSyndicationList() - - this.titlePage = this.i18n('Most liked videos') - this.titleTooltip = this.i18n('Videos that have the higher number of likes.') - } - - getVideosObservable (page: number) { - const newPagination = immutableAssign(this.pagination, { currentPage: page }) - const params = { - videoPagination: newPagination, - sort: this.sort, - categoryOneOf: this.categoryOneOf, - languageOneOf: this.languageOneOf, - nsfwPolicy: this.nsfwPolicy, - skipCount: true - } - - return this.hooks.wrapObsFun( - this.videoService.getVideos.bind(this.videoService), - params, - 'common', - 'filter:api.most-liked-videos.videos.list.params', - 'filter:api.most-liked-videos.videos.list.result' - ) - } - - generateSyndicationList () { - this.syndicationItems = this.videoService.getVideoFeedUrls(this.sort, undefined, this.categoryOneOf) - } -} 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 deleted file mode 100644 index c9395133f..000000000 --- a/client/src/app/videos/video-list/video-recently-added.component.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Component, OnDestroy, OnInit } from '@angular/core' -import { ActivatedRoute, Router } from '@angular/router' -import { AuthService, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core' -import { HooksService } from '@app/core/plugins/hooks.service' -import { immutableAssign } from '@app/helpers' -import { VideoService } from '@app/shared/shared-main' -import { AbstractVideoList } from '@app/shared/shared-video-miniature' -import { I18n } from '@ngx-translate/i18n-polyfill' -import { VideoSortField } from '@shared/models' - -@Component({ - selector: 'my-videos-recently-added', - styleUrls: [ '../../shared/shared-video-miniature/abstract-video-list.scss' ], - templateUrl: '../../shared/shared-video-miniature/abstract-video-list.html' -}) -export class VideoRecentlyAddedComponent extends AbstractVideoList implements OnInit, OnDestroy { - titlePage: string - sort: VideoSortField = '-publishedAt' - groupByDate = true - - useUserVideoPreferences = true - - constructor ( - protected i18n: I18n, - protected route: ActivatedRoute, - protected serverService: ServerService, - protected router: Router, - protected notifier: Notifier, - protected authService: AuthService, - protected userService: UserService, - protected screenService: ScreenService, - protected storageService: LocalStorageService, - private videoService: VideoService, - private hooks: HooksService - ) { - super() - - this.titlePage = i18n('Recently added') - } - - ngOnInit () { - super.ngOnInit() - - this.generateSyndicationList() - } - - ngOnDestroy () { - super.ngOnDestroy() - } - - getVideosObservable (page: number) { - const newPagination = immutableAssign(this.pagination, { currentPage: page }) - const params = { - videoPagination: newPagination, - sort: this.sort, - categoryOneOf: this.categoryOneOf, - languageOneOf: this.languageOneOf, - nsfwPolicy: this.nsfwPolicy, - skipCount: true - } - - return this.hooks.wrapObsFun( - this.videoService.getVideos.bind(this.videoService), - params, - 'common', - 'filter:api.recently-added-videos.videos.list.params', - 'filter:api.recently-added-videos.videos.list.result' - ) - } - - generateSyndicationList () { - this.syndicationItems = this.videoService.getVideoFeedUrls(this.sort, undefined, this.categoryOneOf) - } -} diff --git a/client/src/app/videos/video-list/video-trending.component.ts b/client/src/app/videos/video-list/video-trending.component.ts deleted file mode 100644 index 10eab18de..000000000 --- a/client/src/app/videos/video-list/video-trending.component.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Component, OnDestroy, OnInit } from '@angular/core' -import { ActivatedRoute, Router } from '@angular/router' -import { AuthService, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core' -import { HooksService } from '@app/core/plugins/hooks.service' -import { immutableAssign } from '@app/helpers' -import { VideoService } from '@app/shared/shared-main' -import { AbstractVideoList } from '@app/shared/shared-video-miniature' -import { I18n } from '@ngx-translate/i18n-polyfill' -import { VideoSortField } from '@shared/models' - -@Component({ - selector: 'my-videos-trending', - styleUrls: [ '../../shared/shared-video-miniature/abstract-video-list.scss' ], - templateUrl: '../../shared/shared-video-miniature/abstract-video-list.html' -}) -export class VideoTrendingComponent extends AbstractVideoList implements OnInit, OnDestroy { - titlePage: string - defaultSort: VideoSortField = '-trending' - - useUserVideoPreferences = true - - constructor ( - protected i18n: I18n, - protected router: Router, - protected serverService: ServerService, - protected route: ActivatedRoute, - protected notifier: Notifier, - protected authService: AuthService, - protected userService: UserService, - protected screenService: ScreenService, - protected storageService: LocalStorageService, - private videoService: VideoService, - private hooks: HooksService - ) { - super() - } - - ngOnInit () { - super.ngOnInit() - - this.generateSyndicationList() - - this.serverService.getConfig().subscribe( - config => { - const trendingDays = config.trending.videos.intervalDays - - if (trendingDays === 1) { - this.titlePage = this.i18n('Trending for the last 24 hours') - this.titleTooltip = this.i18n('Trending videos are those totalizing the greatest number of views during the last 24 hours') - } else { - this.titlePage = this.i18n('Trending for the last {{days}} days', { days: trendingDays }) - this.titleTooltip = this.i18n( - 'Trending videos are those totalizing the greatest number of views during the last {{days}} days', - { days: trendingDays } - ) - } - }) - } - - ngOnDestroy () { - super.ngOnDestroy() - } - - getVideosObservable (page: number) { - const newPagination = immutableAssign(this.pagination, { currentPage: page }) - const params = { - videoPagination: newPagination, - sort: this.sort, - categoryOneOf: this.categoryOneOf, - languageOneOf: this.languageOneOf, - nsfwPolicy: this.nsfwPolicy, - skipCount: true - } - - return this.hooks.wrapObsFun( - this.videoService.getVideos.bind(this.videoService), - params, - 'common', - 'filter:api.trending-videos.videos.list.params', - 'filter:api.trending-videos.videos.list.result' - ) - } - - generateSyndicationList () { - this.syndicationItems = this.videoService.getVideoFeedUrls(this.sort, undefined, this.categoryOneOf) - } -} diff --git a/client/src/app/videos/video-list/video-user-subscriptions.component.ts b/client/src/app/videos/video-list/video-user-subscriptions.component.ts deleted file mode 100644 index 41ad9b277..000000000 --- a/client/src/app/videos/video-list/video-user-subscriptions.component.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Component, OnDestroy, OnInit } from '@angular/core' -import { ActivatedRoute, Router } from '@angular/router' -import { AuthService, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core' -import { HooksService } from '@app/core/plugins/hooks.service' -import { immutableAssign } from '@app/helpers' -import { VideoService } from '@app/shared/shared-main' -import { UserSubscriptionService } from '@app/shared/shared-user-subscription' -import { AbstractVideoList, OwnerDisplayType } from '@app/shared/shared-video-miniature' -import { I18n } from '@ngx-translate/i18n-polyfill' -import { VideoSortField } from '@shared/models' - -@Component({ - selector: 'my-videos-user-subscriptions', - styleUrls: [ '../../shared/shared-video-miniature/abstract-video-list.scss' ], - templateUrl: '../../shared/shared-video-miniature/abstract-video-list.html' -}) -export class VideoUserSubscriptionsComponent extends AbstractVideoList implements OnInit, OnDestroy { - titlePage: string - sort = '-publishedAt' as VideoSortField - ownerDisplayType: OwnerDisplayType = 'auto' - groupByDate = true - - constructor ( - protected i18n: I18n, - protected router: Router, - protected serverService: ServerService, - protected route: ActivatedRoute, - protected notifier: Notifier, - protected authService: AuthService, - protected userService: UserService, - protected screenService: ScreenService, - protected storageService: LocalStorageService, - private userSubscription: UserSubscriptionService, - private videoService: VideoService, - private hooks: HooksService - ) { - super() - - this.titlePage = i18n('Videos from your subscriptions') - this.actions.push({ - routerLink: '/my-account/subscriptions', - label: i18n('Subscriptions'), - iconName: 'cog' - }) - } - - ngOnInit () { - super.ngOnInit() - } - - ngOnDestroy () { - super.ngOnDestroy() - } - - getVideosObservable (page: number) { - const newPagination = immutableAssign(this.pagination, { currentPage: page }) - const params = { - videoPagination: newPagination, - sort: this.sort, - skipCount: true - } - - return this.hooks.wrapObsFun( - this.userSubscription.getUserSubscriptionVideos.bind(this.userSubscription), - params, - 'common', - 'filter:api.user-subscriptions-videos.videos.list.params', - 'filter:api.user-subscriptions-videos.videos.list.result' - ) - } - - generateSyndicationList () { - // not implemented yet - } -} diff --git a/client/src/app/videos/videos-routing.module.ts b/client/src/app/videos/videos-routing.module.ts deleted file mode 100644 index 16b65be63..000000000 --- a/client/src/app/videos/videos-routing.module.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { NgModule } from '@angular/core' -import { RouterModule, Routes } from '@angular/router' -import { VideoLocalComponent } from '@app/videos/video-list/video-local.component' -import { MetaGuard } from '@ngx-meta/core' -import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' -import { VideoTrendingComponent } from './video-list/video-trending.component' -import { VideoMostLikedComponent } from './video-list/video-most-liked.component' -import { VideosComponent } from './videos.component' -import { VideoUserSubscriptionsComponent } from './video-list/video-user-subscriptions.component' -import { VideoOverviewComponent } from './video-list/overview/video-overview.component' - -const videosRoutes: Routes = [ - { - path: 'videos', - component: VideosComponent, - canActivateChild: [ MetaGuard ], - children: [ - { - path: 'overview', - component: VideoOverviewComponent, - data: { - meta: { - title: 'Discover videos' - } - } - }, - { - path: 'trending', - component: VideoTrendingComponent, - data: { - meta: { - title: 'Trending videos' - }, - reuse: { - enabled: true, - key: 'trending-videos-list' - } - } - }, - { - path: 'most-liked', - component: VideoMostLikedComponent, - data: { - meta: { - title: 'Most liked videos' - }, - reuse: { - enabled: true, - key: 'most-liked-videos-list' - } - } - }, - { - path: 'recently-added', - component: VideoRecentlyAddedComponent, - data: { - meta: { - title: 'Recently added videos' - }, - reuse: { - enabled: true, - key: 'recently-added-videos-list' - } - } - }, - { - path: 'subscriptions', - component: VideoUserSubscriptionsComponent, - data: { - meta: { - title: 'Subscriptions' - }, - reuse: { - enabled: true, - key: 'subscription-videos-list' - } - } - }, - { - path: 'local', - component: VideoLocalComponent, - data: { - meta: { - title: 'Local videos' - }, - reuse: { - enabled: true, - key: 'local-videos-list' - } - } - }, - { - path: 'upload', - loadChildren: () => import('@app/videos/+video-edit/video-add.module').then(m => m.VideoAddModule), - data: { - meta: { - title: 'Upload a video' - } - } - }, - { - path: 'update/:uuid', - loadChildren: () => import('@app/videos/+video-edit/video-update.module').then(m => m.VideoUpdateModule), - data: { - meta: { - title: 'Edit a video' - } - } - }, - { - path: 'watch', - loadChildren: () => import('@app/videos/+video-watch/video-watch.module').then(m => m.VideoWatchModule), - data: { - preload: 3000 - } - } - ] - } -] - -@NgModule({ - imports: [ RouterModule.forChild(videosRoutes) ], - exports: [ RouterModule ] -}) -export class VideosRoutingModule {} diff --git a/client/src/app/videos/videos.component.ts b/client/src/app/videos/videos.component.ts deleted file mode 100644 index 585a3ad9a..000000000 --- a/client/src/app/videos/videos.component.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Component } from '@angular/core' - -@Component({ - template: '' -}) -export class VideosComponent {} diff --git a/client/src/app/videos/videos.module.ts b/client/src/app/videos/videos.module.ts deleted file mode 100644 index 217e5bb50..000000000 --- a/client/src/app/videos/videos.module.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { NgModule } from '@angular/core' -import { SharedFormModule } from '@app/shared/shared-forms' -import { SharedGlobalIconModule } from '@app/shared/shared-icons' -import { SharedMainModule } from '@app/shared/shared-main' -import { SharedUserSubscriptionModule } from '@app/shared/shared-user-subscription' -import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature' -import { VideoOverviewComponent } from './video-list/overview/video-overview.component' -import { VideoLocalComponent } from './video-list/video-local.component' -import { VideoMostLikedComponent } from './video-list/video-most-liked.component' -import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' -import { VideoTrendingComponent } from './video-list/video-trending.component' -import { VideoUserSubscriptionsComponent } from './video-list/video-user-subscriptions.component' -import { VideosRoutingModule } from './videos-routing.module' -import { VideosComponent } from './videos.component' - -@NgModule({ - imports: [ - VideosRoutingModule, - - SharedMainModule, - SharedFormModule, - SharedVideoMiniatureModule, - SharedUserSubscriptionModule, - SharedGlobalIconModule - ], - - declarations: [ - VideosComponent, - - VideoTrendingComponent, - VideoMostLikedComponent, - VideoRecentlyAddedComponent, - VideoLocalComponent, - VideoUserSubscriptionsComponent, - VideoOverviewComponent - ], - - exports: [ - VideosComponent - ], - - providers: [] -}) -export class VideosModule { } -- cgit v1.2.3