diff options
Diffstat (limited to 'client/src/app/videos')
52 files changed, 488 insertions, 684 deletions
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 index 30aefdbfc..19043eee6 100644 --- 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 | |||
@@ -3,7 +3,7 @@ | |||
3 | 3 | ||
4 | <div class="modal-header"> | 4 | <div class="modal-header"> |
5 | <h4 i18n class="modal-title">Add caption</h4> | 5 | <h4 i18n class="modal-title">Add caption</h4> |
6 | <span class="close" aria-label="Close" role="button" (click)="hide()"></span> | 6 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> |
7 | </div> | 7 | </div> |
8 | 8 | ||
9 | <div class="modal-body"> | 9 | <div class="modal-body"> |
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 index 07c33030a..1413e7262 100644 --- 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 | |||
@@ -5,6 +5,7 @@ import { VideoCaptionsValidatorsService } from '@app/shared/forms/form-validator | |||
5 | import { ServerService } from '@app/core' | 5 | import { ServerService } from '@app/core' |
6 | import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model' | 6 | import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model' |
7 | import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' | 7 | import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' |
8 | import { VideoConstant } from '../../../../../../shared' | ||
8 | 9 | ||
9 | @Component({ | 10 | @Component({ |
10 | selector: 'my-video-caption-add-modal', | 11 | selector: 'my-video-caption-add-modal', |
@@ -19,7 +20,7 @@ export class VideoCaptionAddModalComponent extends FormReactive implements OnIni | |||
19 | 20 | ||
20 | @ViewChild('modal') modal: ElementRef | 21 | @ViewChild('modal') modal: ElementRef |
21 | 22 | ||
22 | videoCaptionLanguages = [] | 23 | videoCaptionLanguages: VideoConstant<string>[] = [] |
23 | 24 | ||
24 | private openedModal: NgbModalRef | 25 | private openedModal: NgbModalRef |
25 | private closingModal = false | 26 | private closingModal = false |
@@ -59,6 +60,7 @@ export class VideoCaptionAddModalComponent extends FormReactive implements OnIni | |||
59 | hide () { | 60 | hide () { |
60 | this.closingModal = true | 61 | this.closingModal = true |
61 | this.openedModal.close() | 62 | this.openedModal.close() |
63 | this.form.reset() | ||
62 | } | 64 | } |
63 | 65 | ||
64 | isReplacingExistingCaption () { | 66 | isReplacingExistingCaption () { |
@@ -70,8 +72,6 @@ export class VideoCaptionAddModalComponent extends FormReactive implements OnIni | |||
70 | } | 72 | } |
71 | 73 | ||
72 | async addCaption () { | 74 | async addCaption () { |
73 | this.hide() | ||
74 | |||
75 | const languageId = this.form.value[ 'language' ] | 75 | const languageId = this.form.value[ 'language' ] |
76 | const languageObject = this.videoCaptionLanguages.find(l => l.id === languageId) | 76 | const languageObject = this.videoCaptionLanguages.find(l => l.id === languageId) |
77 | 77 | ||
@@ -80,6 +80,6 @@ export class VideoCaptionAddModalComponent extends FormReactive implements OnIni | |||
80 | captionfile: this.form.value[ 'captionfile' ] | 80 | captionfile: this.form.value[ 'captionfile' ] |
81 | }) | 81 | }) |
82 | 82 | ||
83 | this.form.reset() | 83 | this.hide() |
84 | } | 84 | } |
85 | } | 85 | } |
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 index 8c74a1ca6..7fd9af208 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.component.html +++ b/client/src/app/videos/+video-edit/shared/video-edit.component.html | |||
@@ -127,10 +127,11 @@ | |||
127 | 127 | ||
128 | <my-peertube-checkbox | 128 | <my-peertube-checkbox |
129 | inputName="downloadEnabled" formControlName="downloadEnabled" | 129 | inputName="downloadEnabled" formControlName="downloadEnabled" |
130 | i18n-labelText labelText="Disable downloading" | 130 | i18n-labelText labelText="Download enabled" |
131 | ></my-peertube-checkbox> | 131 | ></my-peertube-checkbox> |
132 | 132 | ||
133 | <my-peertube-checkbox | 133 | <my-peertube-checkbox |
134 | *ngIf="waitTranscodingEnabled" | ||
134 | inputName="waitTranscoding" formControlName="waitTranscoding" | 135 | inputName="waitTranscoding" formControlName="waitTranscoding" |
135 | i18n-labelText labelText="Wait transcoding before publishing the video" | 136 | i18n-labelText labelText="Wait transcoding before publishing the video" |
136 | i18n-helpHtml helpHtml="If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends." | 137 | i18n-helpHtml helpHtml="If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends." |
@@ -147,7 +148,7 @@ | |||
147 | 148 | ||
148 | <div class="captions-header"> | 149 | <div class="captions-header"> |
149 | <a (click)="openAddCaptionModal()" class="create-caption"> | 150 | <a (click)="openAddCaptionModal()" class="create-caption"> |
150 | <span class="icon icon-add"></span> | 151 | <my-global-icon iconName="add"></my-global-icon> |
151 | <ng-container i18n>Add another caption</ng-container> | 152 | <ng-container i18n>Add another caption</ng-container> |
152 | </a> | 153 | </a> |
153 | </div> | 154 | </div> |
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 index b039d7ad4..bb775cb0a 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.component.scss +++ b/client/src/app/videos/+video-edit/shared/video-edit.component.scss | |||
@@ -5,6 +5,11 @@ | |||
5 | @include peertube-select-container(auto); | 5 | @include peertube-select-container(auto); |
6 | } | 6 | } |
7 | 7 | ||
8 | my-peertube-checkbox { | ||
9 | display: block; | ||
10 | margin-bottom: 1rem; | ||
11 | } | ||
12 | |||
8 | .video-edit { | 13 | .video-edit { |
9 | height: 100%; | 14 | height: 100%; |
10 | min-height: 300px; | 15 | min-height: 300px; |
@@ -18,10 +23,6 @@ | |||
18 | display: block; | 23 | display: block; |
19 | } | 24 | } |
20 | 25 | ||
21 | input, select { | ||
22 | font-size: 15px | ||
23 | } | ||
24 | |||
25 | .label-tags + span { | 26 | .label-tags + span { |
26 | font-size: 15px; | 27 | font-size: 15px; |
27 | } | 28 | } |
@@ -37,7 +38,7 @@ | |||
37 | text-align: right; | 38 | text-align: right; |
38 | 39 | ||
39 | .create-caption { | 40 | .create-caption { |
40 | @include create-button('../../../../assets/images/global/add.svg'); | 41 | @include create-button; |
41 | } | 42 | } |
42 | } | 43 | } |
43 | 44 | ||
@@ -95,13 +96,14 @@ | |||
95 | display: inline-block; | 96 | display: inline-block; |
96 | margin-right: 25px; | 97 | margin-right: 25px; |
97 | 98 | ||
98 | color: #585858; | 99 | color: $grey-foreground-color; |
99 | font-size: 15px; | 100 | font-size: 15px; |
100 | } | 101 | } |
101 | 102 | ||
102 | .submit-button { | 103 | .submit-button { |
103 | @include peertube-button; | 104 | @include peertube-button; |
104 | @include orange-button; | 105 | @include orange-button; |
106 | @include button-with-icon(20px, 1px); | ||
105 | 107 | ||
106 | display: inline-block; | 108 | display: inline-block; |
107 | 109 | ||
@@ -114,16 +116,6 @@ | |||
114 | color: inherit; | 116 | color: inherit; |
115 | font-weight: $font-semibold; | 117 | font-weight: $font-semibold; |
116 | } | 118 | } |
117 | |||
118 | .icon.icon-validate { | ||
119 | @include icon(20px); | ||
120 | |||
121 | cursor: inherit; | ||
122 | position: relative; | ||
123 | top: -1px; | ||
124 | margin-right: 4px; | ||
125 | background-image: url('../../../../assets/images/global/validate.svg'); | ||
126 | } | ||
127 | } | 119 | } |
128 | } | 120 | } |
129 | 121 | ||
@@ -171,10 +163,10 @@ p-calendar { | |||
171 | } | 163 | } |
172 | 164 | ||
173 | tag { | 165 | tag { |
174 | background-color: var(--inputColor) !important; | 166 | background-color: $grey-background-color !important; |
167 | color: #000 !important; | ||
175 | border-radius: 3px !important; | 168 | border-radius: 3px !important; |
176 | font-size: 15px !important; | 169 | font-size: 15px !important; |
177 | color: var(--mainForegroundColor) !important; | ||
178 | height: 30px !important; | 170 | height: 30px !important; |
179 | line-height: 30px !important; | 171 | line-height: 30px !important; |
180 | margin: 0 5px 0 0 !important; | 172 | margin: 0 5px 0 0 !important; |
@@ -197,7 +189,10 @@ p-calendar { | |||
197 | top: -1px; | 189 | top: -1px; |
198 | height: auto !important; | 190 | height: auto !important; |
199 | vertical-align: middle !important; | 191 | vertical-align: middle !important; |
200 | fill: #585858 !important; | 192 | |
193 | path { | ||
194 | fill: $grey-foreground-color !important; | ||
195 | } | ||
201 | } | 196 | } |
202 | 197 | ||
203 | &:hover { | 198 | &:hover { |
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 index 1dae931e2..3ed7a4a10 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.component.ts +++ b/client/src/app/videos/+video-edit/shared/video-edit.component.ts | |||
@@ -2,7 +2,7 @@ import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core' | |||
2 | import { FormArray, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms' | 2 | import { FormArray, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms' |
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { FormReactiveValidationMessages, VideoValidatorsService } from '@app/shared' | 4 | import { FormReactiveValidationMessages, VideoValidatorsService } from '@app/shared' |
5 | import { NotificationsService } from 'angular2-notifications' | 5 | import { Notifier } from '@app/core' |
6 | import { ServerService } from '../../../core/server' | 6 | import { ServerService } from '../../../core/server' |
7 | import { VideoEdit } from '../../../shared/video/video-edit.model' | 7 | import { VideoEdit } from '../../../shared/video/video-edit.model' |
8 | import { map } from 'rxjs/operators' | 8 | import { map } from 'rxjs/operators' |
@@ -27,6 +27,7 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
27 | @Input() userVideoChannels: { id: number, label: string, support: string }[] = [] | 27 | @Input() userVideoChannels: { id: number, label: string, support: string }[] = [] |
28 | @Input() schedulePublicationPossible = true | 28 | @Input() schedulePublicationPossible = true |
29 | @Input() videoCaptions: VideoCaptionEdit[] = [] | 29 | @Input() videoCaptions: VideoCaptionEdit[] = [] |
30 | @Input() waitTranscodingEnabled = true | ||
30 | 31 | ||
31 | @ViewChild('videoCaptionAddModal') videoCaptionAddModal: VideoCaptionAddModalComponent | 32 | @ViewChild('videoCaptionAddModal') videoCaptionAddModal: VideoCaptionAddModalComponent |
32 | 33 | ||
@@ -48,7 +49,7 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
48 | calendarTimezone: string | 49 | calendarTimezone: string |
49 | calendarDateFormat: string | 50 | calendarDateFormat: string |
50 | 51 | ||
51 | private schedulerInterval | 52 | private schedulerInterval: any |
52 | private firstPatchDone = false | 53 | private firstPatchDone = false |
53 | private initialVideoCaptions: string[] = [] | 54 | private initialVideoCaptions: string[] = [] |
54 | 55 | ||
@@ -58,7 +59,7 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
58 | private videoCaptionService: VideoCaptionService, | 59 | private videoCaptionService: VideoCaptionService, |
59 | private route: ActivatedRoute, | 60 | private route: ActivatedRoute, |
60 | private router: Router, | 61 | private router: Router, |
61 | private notificationsService: NotificationsService, | 62 | private notifier: Notifier, |
62 | private serverService: ServerService, | 63 | private serverService: ServerService, |
63 | private i18nPrimengCalendarService: I18nPrimengCalendarService | 64 | private i18nPrimengCalendarService: I18nPrimengCalendarService |
64 | ) { | 65 | ) { |
@@ -77,14 +78,14 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
77 | } | 78 | } |
78 | 79 | ||
79 | updateForm () { | 80 | updateForm () { |
80 | const defaultValues = { | 81 | const defaultValues: any = { |
81 | nsfw: 'false', | 82 | nsfw: 'false', |
82 | commentsEnabled: 'true', | 83 | commentsEnabled: 'true', |
83 | downloadEnabled: 'true', | 84 | downloadEnabled: 'true', |
84 | waitTranscoding: 'true', | 85 | waitTranscoding: 'true', |
85 | tags: [] | 86 | tags: [] |
86 | } | 87 | } |
87 | const obj = { | 88 | const obj: any = { |
88 | name: this.videoValidatorsService.VIDEO_NAME, | 89 | name: this.videoValidatorsService.VIDEO_NAME, |
89 | privacy: this.videoValidatorsService.VIDEO_PRIVACY, | 90 | privacy: this.videoValidatorsService.VIDEO_PRIVACY, |
90 | channelId: this.videoValidatorsService.VIDEO_CHANNEL, | 91 | channelId: this.videoValidatorsService.VIDEO_CHANNEL, |
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 index a933a64f0..28eb143c9 100644 --- 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 | |||
@@ -1,6 +1,6 @@ | |||
1 | <div *ngIf="!hasImportedVideo" class="upload-video-container"> | 1 | <div *ngIf="!hasImportedVideo" class="upload-video-container"> |
2 | <div class="import-video-torrent"> | 2 | <div class="first-step-block"> |
3 | <div class="icon icon-upload"></div> | 3 | <my-global-icon class="upload-icon" iconName="upload"></my-global-icon> |
4 | 4 | ||
5 | <div class="button-file"> | 5 | <div class="button-file"> |
6 | <span i18n>Select the torrent to import</span> | 6 | <span i18n>Select the torrent to import</span> |
@@ -45,7 +45,12 @@ | |||
45 | </div> | 45 | </div> |
46 | </div> | 46 | </div> |
47 | 47 | ||
48 | <div *ngIf="hasImportedVideo" class="alert alert-info" i18n> | 48 | <div *ngIf="error" class="alert alert-danger"> |
49 | <div i18n>Sorry, but something went wrong</div> | ||
50 | {{ error }} | ||
51 | </div> | ||
52 | |||
53 | <div *ngIf="hasImportedVideo && !error" class="alert alert-info" i18n> | ||
49 | Congratulations, the video will be imported with BitTorrent! You can already add information about this video. | 54 | Congratulations, the video will be imported with BitTorrent! You can already add information about this video. |
50 | </div> | 55 | </div> |
51 | 56 | ||
@@ -61,7 +66,7 @@ | |||
61 | (click)="updateSecondStep()" | 66 | (click)="updateSecondStep()" |
62 | [ngClass]="{ disabled: !form.valid || isUpdatingVideo === true }" | 67 | [ngClass]="{ disabled: !form.valid || isUpdatingVideo === true }" |
63 | > | 68 | > |
64 | <span class="icon icon-validate"></span> | 69 | <my-global-icon iconName="validate"></my-global-icon> |
65 | <input type="button" i18n-value value="Update" /> | 70 | <input type="button" i18n-value value="Update" /> |
66 | </div> | 71 | </div> |
67 | </div> | 72 | </div> |
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 index 262b0b68e..6d59ed834 100644 --- 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 | |||
@@ -1,37 +1,7 @@ | |||
1 | @import 'variables'; | 1 | @import 'variables'; |
2 | @import 'mixins'; | 2 | @import 'mixins'; |
3 | 3 | ||
4 | $width-size: 190px; | 4 | .first-step-block { |
5 | |||
6 | .peertube-select-container { | ||
7 | @include peertube-select-container($width-size); | ||
8 | } | ||
9 | |||
10 | .import-video-torrent { | ||
11 | display: flex; | ||
12 | flex-direction: column; | ||
13 | align-items: center; | ||
14 | |||
15 | .icon.icon-upload { | ||
16 | @include icon(90px); | ||
17 | margin-bottom: 25px; | ||
18 | cursor: default; | ||
19 | |||
20 | background-image: url('../../../../assets/images/video/upload.svg'); | ||
21 | } | ||
22 | |||
23 | .button-file { | ||
24 | @include peertube-button-file(auto); | ||
25 | |||
26 | min-width: 190px; | ||
27 | } | ||
28 | |||
29 | .button-file-extension { | ||
30 | display: block; | ||
31 | font-size: 12px; | ||
32 | margin-top: 5px; | ||
33 | } | ||
34 | |||
35 | .torrent-or-magnet { | 5 | .torrent-or-magnet { |
36 | margin: 10px 0; | 6 | margin: 10px 0; |
37 | } | 7 | } |
@@ -39,19 +9,6 @@ $width-size: 190px; | |||
39 | .form-group-magnet-uri { | 9 | .form-group-magnet-uri { |
40 | margin-bottom: 40px; | 10 | margin-bottom: 40px; |
41 | } | 11 | } |
42 | |||
43 | input[type=text] { | ||
44 | @include peertube-input-text($width-size); | ||
45 | display: block; | ||
46 | } | ||
47 | |||
48 | input[type=button] { | ||
49 | @include peertube-button; | ||
50 | @include orange-button; | ||
51 | |||
52 | width: $width-size; | ||
53 | margin-top: 30px; | ||
54 | } | ||
55 | } | 12 | } |
56 | 13 | ||
57 | 14 | ||
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 index 0f6fd17a9..c12a1d653 100644 --- 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 | |||
@@ -1,8 +1,7 @@ | |||
1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, ElementRef, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Router } from '@angular/router' | 2 | import { Router } from '@angular/router' |
3 | import { NotificationsService } from 'angular2-notifications' | ||
4 | import { VideoPrivacy, VideoUpdate } from '../../../../../../shared/models/videos' | 3 | import { VideoPrivacy, VideoUpdate } from '../../../../../../shared/models/videos' |
5 | import { AuthService, ServerService } from '../../../core' | 4 | import { AuthService, Notifier, ServerService } from '../../../core' |
6 | import { VideoService } from '../../../shared/video/video.service' | 5 | import { VideoService } from '../../../shared/video/video.service' |
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | 6 | import { I18n } from '@ngx-translate/i18n-polyfill' |
8 | import { LoadingBarService } from '@ngx-loading-bar/core' | 7 | import { LoadingBarService } from '@ngx-loading-bar/core' |
@@ -12,20 +11,22 @@ import { VideoEdit } from '@app/shared/video/video-edit.model' | |||
12 | import { FormValidatorService } from '@app/shared' | 11 | import { FormValidatorService } from '@app/shared' |
13 | import { VideoCaptionService } from '@app/shared/video-caption' | 12 | import { VideoCaptionService } from '@app/shared/video-caption' |
14 | import { VideoImportService } from '@app/shared/video-import' | 13 | import { VideoImportService } from '@app/shared/video-import' |
14 | import { scrollToTop } from '@app/shared/misc/utils' | ||
15 | 15 | ||
16 | @Component({ | 16 | @Component({ |
17 | selector: 'my-video-import-torrent', | 17 | selector: 'my-video-import-torrent', |
18 | templateUrl: './video-import-torrent.component.html', | 18 | templateUrl: './video-import-torrent.component.html', |
19 | styleUrls: [ | 19 | styleUrls: [ |
20 | '../shared/video-edit.component.scss', | 20 | '../shared/video-edit.component.scss', |
21 | './video-import-torrent.component.scss' | 21 | './video-import-torrent.component.scss', |
22 | './video-send.scss' | ||
22 | ] | 23 | ] |
23 | }) | 24 | }) |
24 | export class VideoImportTorrentComponent extends VideoSend implements OnInit, CanComponentDeactivate { | 25 | export class VideoImportTorrentComponent extends VideoSend implements OnInit, CanComponentDeactivate { |
25 | @Output() firstStepDone = new EventEmitter<string>() | 26 | @Output() firstStepDone = new EventEmitter<string>() |
26 | @ViewChild('torrentfileInput') torrentfileInput | 27 | @Output() firstStepError = new EventEmitter<void>() |
28 | @ViewChild('torrentfileInput') torrentfileInput: ElementRef<HTMLInputElement> | ||
27 | 29 | ||
28 | videoFileName: string | ||
29 | magnetUri = '' | 30 | magnetUri = '' |
30 | 31 | ||
31 | isImportingVideo = false | 32 | isImportingVideo = false |
@@ -33,13 +34,14 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca | |||
33 | isUpdatingVideo = false | 34 | isUpdatingVideo = false |
34 | 35 | ||
35 | video: VideoEdit | 36 | video: VideoEdit |
37 | error: string | ||
36 | 38 | ||
37 | protected readonly DEFAULT_VIDEO_PRIVACY = VideoPrivacy.PUBLIC | 39 | protected readonly DEFAULT_VIDEO_PRIVACY = VideoPrivacy.PUBLIC |
38 | 40 | ||
39 | constructor ( | 41 | constructor ( |
40 | protected formValidatorService: FormValidatorService, | 42 | protected formValidatorService: FormValidatorService, |
41 | protected loadingBar: LoadingBarService, | 43 | protected loadingBar: LoadingBarService, |
42 | protected notificationsService: NotificationsService, | 44 | protected notifier: Notifier, |
43 | protected authService: AuthService, | 45 | protected authService: AuthService, |
44 | protected serverService: ServerService, | 46 | protected serverService: ServerService, |
45 | protected videoService: VideoService, | 47 | protected videoService: VideoService, |
@@ -64,7 +66,7 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca | |||
64 | } | 66 | } |
65 | 67 | ||
66 | fileChange () { | 68 | fileChange () { |
67 | const torrentfile = this.torrentfileInput.nativeElement.files[0] as File | 69 | const torrentfile = this.torrentfileInput.nativeElement.files[0] |
68 | if (!torrentfile) return | 70 | if (!torrentfile) return |
69 | 71 | ||
70 | this.importVideo(torrentfile) | 72 | this.importVideo(torrentfile) |
@@ -106,7 +108,8 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca | |||
106 | err => { | 108 | err => { |
107 | this.loadingBar.complete() | 109 | this.loadingBar.complete() |
108 | this.isImportingVideo = false | 110 | this.isImportingVideo = false |
109 | this.notificationsService.error(this.i18n('Error'), err.message) | 111 | this.firstStepError.emit() |
112 | this.notifier.error(err.message) | ||
110 | } | 113 | } |
111 | ) | 114 | ) |
112 | } | 115 | } |
@@ -125,14 +128,14 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca | |||
125 | .subscribe( | 128 | .subscribe( |
126 | () => { | 129 | () => { |
127 | this.isUpdatingVideo = false | 130 | this.isUpdatingVideo = false |
128 | this.notificationsService.success(this.i18n('Success'), this.i18n('Video to import updated.')) | 131 | this.notifier.success(this.i18n('Video to import updated.')) |
129 | 132 | ||
130 | this.router.navigate([ '/my-account', 'video-imports' ]) | 133 | this.router.navigate([ '/my-account', 'video-imports' ]) |
131 | }, | 134 | }, |
132 | 135 | ||
133 | err => { | 136 | err => { |
134 | this.isUpdatingVideo = false | 137 | this.error = err.message |
135 | this.notificationsService.error(this.i18n('Error'), err.message) | 138 | scrollToTop() |
136 | console.error(err) | 139 | console.error(err) |
137 | } | 140 | } |
138 | ) | 141 | ) |
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 index 9f5fc6d22..3550c3585 100644 --- 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 | |||
@@ -1,6 +1,6 @@ | |||
1 | <div *ngIf="!hasImportedVideo" class="upload-video-container"> | 1 | <div *ngIf="!hasImportedVideo" class="upload-video-container"> |
2 | <div class="import-video-url"> | 2 | <div class="first-step-block"> |
3 | <div class="icon icon-upload"></div> | 3 | <my-global-icon class="upload-icon" iconName="upload"></my-global-icon> |
4 | 4 | ||
5 | <div class="form-group"> | 5 | <div class="form-group"> |
6 | <label i18n for="targetUrl">URL</label> | 6 | <label i18n for="targetUrl">URL</label> |
@@ -37,7 +37,13 @@ | |||
37 | </div> | 37 | </div> |
38 | </div> | 38 | </div> |
39 | 39 | ||
40 | <div *ngIf="hasImportedVideo" class="alert alert-info" i18n> | 40 | |
41 | <div *ngIf="error" class="alert alert-danger"> | ||
42 | <div i18n>Sorry, but something went wrong</div> | ||
43 | {{ error }} | ||
44 | </div> | ||
45 | |||
46 | <div *ngIf="!error && hasImportedVideo" class="alert alert-info" i18n> | ||
41 | Congratulations, the video behind {{ targetUrl }} will be imported! You can already add information about this video. | 47 | Congratulations, the video behind {{ targetUrl }} will be imported! You can already add information about this video. |
42 | </div> | 48 | </div> |
43 | 49 | ||
@@ -53,7 +59,7 @@ | |||
53 | (click)="updateSecondStep()" | 59 | (click)="updateSecondStep()" |
54 | [ngClass]="{ disabled: !form.valid || isUpdatingVideo === true }" | 60 | [ngClass]="{ disabled: !form.valid || isUpdatingVideo === true }" |
55 | > | 61 | > |
56 | <span class="icon icon-validate"></span> | 62 | <my-global-icon iconName="validate"></my-global-icon> |
57 | <input type="button" i18n-value value="Update" /> | 63 | <input type="button" i18n-value value="Update" /> |
58 | </div> | 64 | </div> |
59 | </div> | 65 | </div> |
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.scss b/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.scss deleted file mode 100644 index 7c6deda1d..000000000 --- a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.scss +++ /dev/null | |||
@@ -1,37 +0,0 @@ | |||
1 | @import 'variables'; | ||
2 | @import 'mixins'; | ||
3 | |||
4 | $width-size: 190px; | ||
5 | |||
6 | .peertube-select-container { | ||
7 | @include peertube-select-container($width-size); | ||
8 | } | ||
9 | |||
10 | .import-video-url { | ||
11 | display: flex; | ||
12 | flex-direction: column; | ||
13 | align-items: center; | ||
14 | |||
15 | .icon.icon-upload { | ||
16 | @include icon(90px); | ||
17 | margin-bottom: 25px; | ||
18 | cursor: default; | ||
19 | |||
20 | background-image: url('../../../../assets/images/video/upload.svg'); | ||
21 | } | ||
22 | |||
23 | input[type=text] { | ||
24 | @include peertube-input-text($width-size); | ||
25 | display: block; | ||
26 | } | ||
27 | |||
28 | input[type=button] { | ||
29 | @include peertube-button; | ||
30 | @include orange-button; | ||
31 | |||
32 | width: $width-size; | ||
33 | margin-top: 30px; | ||
34 | } | ||
35 | } | ||
36 | |||
37 | |||
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 index fbc85c74f..d11685916 100644 --- 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 | |||
@@ -1,8 +1,7 @@ | |||
1 | import { Component, EventEmitter, OnInit, Output } from '@angular/core' | 1 | import { Component, EventEmitter, OnInit, Output } from '@angular/core' |
2 | import { Router } from '@angular/router' | 2 | import { Router } from '@angular/router' |
3 | import { NotificationsService } from 'angular2-notifications' | ||
4 | import { VideoPrivacy, VideoUpdate } from '../../../../../../shared/models/videos' | 3 | import { VideoPrivacy, VideoUpdate } from '../../../../../../shared/models/videos' |
5 | import { AuthService, ServerService } from '../../../core' | 4 | import { AuthService, Notifier, ServerService } from '../../../core' |
6 | import { VideoService } from '../../../shared/video/video.service' | 5 | import { VideoService } from '../../../shared/video/video.service' |
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | 6 | import { I18n } from '@ngx-translate/i18n-polyfill' |
8 | import { LoadingBarService } from '@ngx-loading-bar/core' | 7 | import { LoadingBarService } from '@ngx-loading-bar/core' |
@@ -12,33 +11,35 @@ import { VideoEdit } from '@app/shared/video/video-edit.model' | |||
12 | import { FormValidatorService } from '@app/shared' | 11 | import { FormValidatorService } from '@app/shared' |
13 | import { VideoCaptionService } from '@app/shared/video-caption' | 12 | import { VideoCaptionService } from '@app/shared/video-caption' |
14 | import { VideoImportService } from '@app/shared/video-import' | 13 | import { VideoImportService } from '@app/shared/video-import' |
14 | import { scrollToTop } from '@app/shared/misc/utils' | ||
15 | 15 | ||
16 | @Component({ | 16 | @Component({ |
17 | selector: 'my-video-import-url', | 17 | selector: 'my-video-import-url', |
18 | templateUrl: './video-import-url.component.html', | 18 | templateUrl: './video-import-url.component.html', |
19 | styleUrls: [ | 19 | styleUrls: [ |
20 | '../shared/video-edit.component.scss', | 20 | '../shared/video-edit.component.scss', |
21 | './video-import-url.component.scss' | 21 | './video-send.scss' |
22 | ] | 22 | ] |
23 | }) | 23 | }) |
24 | export class VideoImportUrlComponent extends VideoSend implements OnInit, CanComponentDeactivate { | 24 | export class VideoImportUrlComponent extends VideoSend implements OnInit, CanComponentDeactivate { |
25 | @Output() firstStepDone = new EventEmitter<string>() | 25 | @Output() firstStepDone = new EventEmitter<string>() |
26 | @Output() firstStepError = new EventEmitter<void>() | ||
26 | 27 | ||
27 | targetUrl = '' | 28 | targetUrl = '' |
28 | videoFileName: string | ||
29 | 29 | ||
30 | isImportingVideo = false | 30 | isImportingVideo = false |
31 | hasImportedVideo = false | 31 | hasImportedVideo = false |
32 | isUpdatingVideo = false | 32 | isUpdatingVideo = false |
33 | 33 | ||
34 | video: VideoEdit | 34 | video: VideoEdit |
35 | error: string | ||
35 | 36 | ||
36 | protected readonly DEFAULT_VIDEO_PRIVACY = VideoPrivacy.PUBLIC | 37 | protected readonly DEFAULT_VIDEO_PRIVACY = VideoPrivacy.PUBLIC |
37 | 38 | ||
38 | constructor ( | 39 | constructor ( |
39 | protected formValidatorService: FormValidatorService, | 40 | protected formValidatorService: FormValidatorService, |
40 | protected loadingBar: LoadingBarService, | 41 | protected loadingBar: LoadingBarService, |
41 | protected notificationsService: NotificationsService, | 42 | protected notifier: Notifier, |
42 | protected authService: AuthService, | 43 | protected authService: AuthService, |
43 | protected serverService: ServerService, | 44 | protected serverService: ServerService, |
44 | protected videoService: VideoService, | 45 | protected videoService: VideoService, |
@@ -98,7 +99,8 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, CanCom | |||
98 | err => { | 99 | err => { |
99 | this.loadingBar.complete() | 100 | this.loadingBar.complete() |
100 | this.isImportingVideo = false | 101 | this.isImportingVideo = false |
101 | this.notificationsService.error(this.i18n('Error'), err.message) | 102 | this.firstStepError.emit() |
103 | this.notifier.error(err.message) | ||
102 | } | 104 | } |
103 | ) | 105 | ) |
104 | } | 106 | } |
@@ -117,14 +119,14 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, CanCom | |||
117 | .subscribe( | 119 | .subscribe( |
118 | () => { | 120 | () => { |
119 | this.isUpdatingVideo = false | 121 | this.isUpdatingVideo = false |
120 | this.notificationsService.success(this.i18n('Success'), this.i18n('Video to import updated.')) | 122 | this.notifier.success(this.i18n('Video to import updated.')) |
121 | 123 | ||
122 | this.router.navigate([ '/my-account', 'video-imports' ]) | 124 | this.router.navigate([ '/my-account', 'video-imports' ]) |
123 | }, | 125 | }, |
124 | 126 | ||
125 | err => { | 127 | err => { |
126 | this.isUpdatingVideo = false | 128 | this.error = err.message |
127 | this.notificationsService.error(this.i18n('Error'), err.message) | 129 | scrollToTop() |
128 | console.error(err) | 130 | console.error(err) |
129 | } | 131 | } |
130 | ) | 132 | ) |
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 new file mode 100644 index 000000000..8769dd302 --- /dev/null +++ b/client/src/app/videos/+video-edit/video-add-components/video-send.scss | |||
@@ -0,0 +1,54 @@ | |||
1 | @import 'variables'; | ||
2 | @import 'mixins'; | ||
3 | |||
4 | $width-size: 190px; | ||
5 | |||
6 | .alert.alert-danger { | ||
7 | text-align: center; | ||
8 | |||
9 | & > div { | ||
10 | font-weight: $font-semibold; | ||
11 | } | ||
12 | } | ||
13 | |||
14 | .first-step-block { | ||
15 | display: flex; | ||
16 | flex-direction: column; | ||
17 | align-items: center; | ||
18 | |||
19 | .upload-icon { | ||
20 | width: 90px; | ||
21 | margin-bottom: 25px; | ||
22 | |||
23 | @include apply-svg-color(#C6C6C6); | ||
24 | } | ||
25 | |||
26 | .peertube-select-container { | ||
27 | @include peertube-select-container($width-size); | ||
28 | } | ||
29 | |||
30 | input[type=text] { | ||
31 | @include peertube-input-text($width-size); | ||
32 | display: block; | ||
33 | } | ||
34 | |||
35 | input[type=button] { | ||
36 | @include peertube-button; | ||
37 | @include orange-button; | ||
38 | |||
39 | width: $width-size; | ||
40 | margin-top: 30px; | ||
41 | } | ||
42 | |||
43 | .button-file { | ||
44 | @include peertube-button-file(auto); | ||
45 | |||
46 | min-width: 190px; | ||
47 | } | ||
48 | |||
49 | .button-file-extension { | ||
50 | display: block; | ||
51 | font-size: 12px; | ||
52 | margin-top: 5px; | ||
53 | } | ||
54 | } | ||
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 index 6d1bac3f2..580c123a0 100644 --- 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 | |||
@@ -1,18 +1,17 @@ | |||
1 | import { EventEmitter, OnInit } from '@angular/core' | 1 | import { EventEmitter, OnInit } from '@angular/core' |
2 | import { LoadingBarService } from '@ngx-loading-bar/core' | 2 | import { LoadingBarService } from '@ngx-loading-bar/core' |
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { AuthService, Notifier, ServerService } from '@app/core' |
4 | import { catchError, switchMap, tap } from 'rxjs/operators' | 4 | import { catchError, switchMap, tap } from 'rxjs/operators' |
5 | import { FormReactive } from '@app/shared' | 5 | import { FormReactive } from '@app/shared' |
6 | import { CanComponentDeactivate } from '@app/shared/guards/can-deactivate-guard.service' | ||
7 | import { VideoConstant, VideoPrivacy } from '../../../../../../shared' | 6 | import { VideoConstant, VideoPrivacy } from '../../../../../../shared' |
8 | import { AuthService, ServerService } from '@app/core' | ||
9 | import { VideoService } from '@app/shared/video/video.service' | 7 | import { VideoService } from '@app/shared/video/video.service' |
10 | import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model' | 8 | import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model' |
11 | import { VideoCaptionService } from '@app/shared/video-caption' | 9 | import { VideoCaptionService } from '@app/shared/video-caption' |
12 | import { VideoEdit } from '@app/shared/video/video-edit.model' | 10 | import { VideoEdit } from '@app/shared/video/video-edit.model' |
13 | import { populateAsyncUserVideoChannels } from '@app/shared/misc/utils' | 11 | import { populateAsyncUserVideoChannels } from '@app/shared/misc/utils' |
12 | import { CanComponentDeactivateResult } from '@app/shared/guards/can-deactivate-guard.service' | ||
14 | 13 | ||
15 | export abstract class VideoSend extends FormReactive implements OnInit, CanComponentDeactivate { | 14 | export abstract class VideoSend extends FormReactive implements OnInit { |
16 | userVideoChannels: { id: number, label: string, support: string }[] = [] | 15 | userVideoChannels: { id: number, label: string, support: string }[] = [] |
17 | videoPrivacies: VideoConstant<VideoPrivacy>[] = [] | 16 | videoPrivacies: VideoConstant<VideoPrivacy>[] = [] |
18 | videoCaptions: VideoCaptionEdit[] = [] | 17 | videoCaptions: VideoCaptionEdit[] = [] |
@@ -21,16 +20,17 @@ export abstract class VideoSend extends FormReactive implements OnInit, CanCompo | |||
21 | firstStepChannelId = 0 | 20 | firstStepChannelId = 0 |
22 | 21 | ||
23 | abstract firstStepDone: EventEmitter<string> | 22 | abstract firstStepDone: EventEmitter<string> |
23 | abstract firstStepError: EventEmitter<void> | ||
24 | protected abstract readonly DEFAULT_VIDEO_PRIVACY: VideoPrivacy | 24 | protected abstract readonly DEFAULT_VIDEO_PRIVACY: VideoPrivacy |
25 | 25 | ||
26 | protected loadingBar: LoadingBarService | 26 | protected loadingBar: LoadingBarService |
27 | protected notificationsService: NotificationsService | 27 | protected notifier: Notifier |
28 | protected authService: AuthService | 28 | protected authService: AuthService |
29 | protected serverService: ServerService | 29 | protected serverService: ServerService |
30 | protected videoService: VideoService | 30 | protected videoService: VideoService |
31 | protected videoCaptionService: VideoCaptionService | 31 | protected videoCaptionService: VideoCaptionService |
32 | 32 | ||
33 | abstract canDeactivate () | 33 | abstract canDeactivate (): CanComponentDeactivateResult |
34 | 34 | ||
35 | ngOnInit () { | 35 | ngOnInit () { |
36 | this.buildForm({}) | 36 | this.buildForm({}) |
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 index fa57c8cb5..b252cd60a 100644 --- 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 | |||
@@ -1,12 +1,12 @@ | |||
1 | <div *ngIf="!isUploadingVideo" class="upload-video-container"> | 1 | <div *ngIf="!isUploadingVideo" class="upload-video-container"> |
2 | <div class="upload-video"> | 2 | <div class="first-step-block"> |
3 | <div class="icon icon-upload"></div> | 3 | <my-global-icon class="upload-icon" iconName="upload"></my-global-icon> |
4 | 4 | ||
5 | <div class="button-file"> | 5 | <div class="button-file"> |
6 | <span i18n>Select the file to upload</span> | 6 | <span i18n>Select the file to upload</span> |
7 | <input #videofileInput type="file" name="videofile" id="videofile" [accept]="videoExtensions" (change)="fileChange()" /> | 7 | <input #videofileInput type="file" name="videofile" id="videofile" [accept]="videoExtensions" (change)="fileChange()" /> |
8 | </div> | 8 | </div> |
9 | <span class="button-file-extension">(.mp4, .webm, .ogv)</span> | 9 | <span class="button-file-extension">({{ videoExtensions }})</span> |
10 | 10 | ||
11 | <div class="form-group form-group-channel"> | 11 | <div class="form-group form-group-channel"> |
12 | <label i18n for="first-step-channel">Channel</label> | 12 | <label i18n for="first-step-channel">Channel</label> |
@@ -29,7 +29,7 @@ | |||
29 | </div> | 29 | </div> |
30 | </div> | 30 | </div> |
31 | 31 | ||
32 | <div *ngIf="isUploadingVideo" class="upload-progress-cancel"> | 32 | <div *ngIf="isUploadingVideo && !error" class="upload-progress-cancel"> |
33 | <p-progressBar | 33 | <p-progressBar |
34 | [value]="videoUploadPercents" | 34 | [value]="videoUploadPercents" |
35 | [ngClass]="{ processing: videoUploadPercents === 100 && videoUploaded === false }" | 35 | [ngClass]="{ processing: videoUploadPercents === 100 && videoUploaded === false }" |
@@ -37,11 +37,21 @@ | |||
37 | <input *ngIf="videoUploaded === false" type="button" value="Cancel" (click)="cancelUpload()" /> | 37 | <input *ngIf="videoUploaded === false" type="button" value="Cancel" (click)="cancelUpload()" /> |
38 | </div> | 38 | </div> |
39 | 39 | ||
40 | <div *ngIf="error" class="alert alert-danger"> | ||
41 | <div i18n>Sorry, but something went wrong</div> | ||
42 | {{ error }} | ||
43 | </div> | ||
44 | |||
45 | <div *ngIf="videoUploaded && !error" class="alert alert-info" i18n> | ||
46 | Congratulations! Your video is now available in your private library. | ||
47 | </div> | ||
48 | |||
40 | <!-- Hidden because we want to load the component --> | 49 | <!-- Hidden because we want to load the component --> |
41 | <form [hidden]="!isUploadingVideo" novalidate [formGroup]="form"> | 50 | <form [hidden]="!isUploadingVideo" novalidate [formGroup]="form"> |
42 | <my-video-edit | 51 | <my-video-edit |
43 | [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" | 52 | [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" |
44 | [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels" | 53 | [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels" |
54 | [waitTranscodingEnabled]="waitTranscodingEnabled" | ||
45 | ></my-video-edit> | 55 | ></my-video-edit> |
46 | 56 | ||
47 | <div class="submit-container"> | 57 | <div class="submit-container"> |
@@ -51,8 +61,8 @@ | |||
51 | (click)="updateSecondStep()" | 61 | (click)="updateSecondStep()" |
52 | [ngClass]="{ disabled: isPublishingButtonDisabled() }" | 62 | [ngClass]="{ disabled: isPublishingButtonDisabled() }" |
53 | > | 63 | > |
54 | <span class="icon icon-validate"></span> | 64 | <my-global-icon iconName="validate"></my-global-icon> |
55 | <input [disabled]="isPublishingButtonDisabled()" type="button" i18n-value value="Publish" /> | 65 | <input [disabled]="isPublishingButtonDisabled()" type="button" i18n-value value="Publish" /> |
56 | </div> | 66 | </div> |
57 | </div> | 67 | </div> |
58 | </form> \ No newline at end of file | 68 | </form> |
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 index dbae5230d..8adf8f169 100644 --- 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 | |||
@@ -1,39 +1,9 @@ | |||
1 | @import 'variables'; | 1 | @import 'variables'; |
2 | @import 'mixins'; | 2 | @import 'mixins'; |
3 | 3 | ||
4 | .peertube-select-container { | 4 | .first-step-block .form-group-channel { |
5 | @include peertube-select-container(190px); | 5 | margin-bottom: 20px; |
6 | } | 6 | margin-top: 35px; |
7 | |||
8 | .upload-video { | ||
9 | display: flex; | ||
10 | flex-direction: column; | ||
11 | align-items: center; | ||
12 | |||
13 | .form-group-channel { | ||
14 | margin-bottom: 20px; | ||
15 | margin-top: 35px; | ||
16 | } | ||
17 | |||
18 | .icon.icon-upload { | ||
19 | @include icon(90px); | ||
20 | margin-bottom: 25px; | ||
21 | cursor: default; | ||
22 | |||
23 | background-image: url('../../../../assets/images/video/upload.svg'); | ||
24 | } | ||
25 | |||
26 | .button-file { | ||
27 | @include peertube-button-file(auto); | ||
28 | |||
29 | min-width: 190px; | ||
30 | } | ||
31 | |||
32 | .button-file-extension { | ||
33 | display: block; | ||
34 | font-size: 12px; | ||
35 | margin-top: 5px; | ||
36 | } | ||
37 | } | 7 | } |
38 | 8 | ||
39 | .upload-progress-cancel { | 9 | .upload-progress-cancel { |
@@ -46,9 +16,7 @@ | |||
46 | 16 | ||
47 | /deep/ .ui-progressbar { | 17 | /deep/ .ui-progressbar { |
48 | font-size: 15px !important; | 18 | font-size: 15px !important; |
49 | color: #fff !important; | ||
50 | height: 30px !important; | 19 | height: 30px !important; |
51 | line-height: 30px !important; | ||
52 | border-radius: 3px !important; | 20 | border-radius: 3px !important; |
53 | background-color: rgba(11, 204, 41, 0.16) !important; | 21 | background-color: rgba(11, 204, 41, 0.16) !important; |
54 | 22 | ||
@@ -60,6 +28,8 @@ | |||
60 | text-align: left; | 28 | text-align: left; |
61 | padding-left: 18px; | 29 | padding-left: 18px; |
62 | margin-top: 0 !important; | 30 | margin-top: 0 !important; |
31 | color: #fff !important; | ||
32 | line-height: 30px !important; | ||
63 | } | 33 | } |
64 | } | 34 | } |
65 | 35 | ||
@@ -82,4 +52,4 @@ | |||
82 | 52 | ||
83 | margin-left: 10px; | 53 | margin-left: 10px; |
84 | } | 54 | } |
85 | } \ No newline at end of file | 55 | } |
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts index ac6c1786f..9cadf52cb 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts +++ b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts | |||
@@ -1,12 +1,11 @@ | |||
1 | import { HttpEventType, HttpResponse } from '@angular/common/http' | 1 | import { HttpEventType, HttpResponse } from '@angular/common/http' |
2 | import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core' | 2 | import { Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core' |
3 | import { Router } from '@angular/router' | 3 | import { Router } from '@angular/router' |
4 | import { LoadingBarService } from '@ngx-loading-bar/core' | 4 | import { LoadingBarService } from '@ngx-loading-bar/core' |
5 | import { NotificationsService } from 'angular2-notifications' | ||
6 | import { BytesPipe } from 'ngx-pipes' | 5 | import { BytesPipe } from 'ngx-pipes' |
7 | import { Subscription } from 'rxjs' | 6 | import { Subscription } from 'rxjs' |
8 | import { VideoPrivacy } from '../../../../../../shared/models/videos' | 7 | import { VideoPrivacy } from '../../../../../../shared/models/videos' |
9 | import { AuthService, ServerService } from '../../../core' | 8 | import { AuthService, Notifier, ServerService } from '../../../core' |
10 | import { VideoEdit } from '../../../shared/video/video-edit.model' | 9 | import { VideoEdit } from '../../../shared/video/video-edit.model' |
11 | import { VideoService } from '../../../shared/video/video.service' | 10 | import { VideoService } from '../../../shared/video/video.service' |
12 | import { I18n } from '@ngx-translate/i18n-polyfill' | 11 | import { I18n } from '@ngx-translate/i18n-polyfill' |
@@ -14,18 +13,21 @@ import { VideoSend } from '@app/videos/+video-edit/video-add-components/video-se | |||
14 | import { CanComponentDeactivate } from '@app/shared/guards/can-deactivate-guard.service' | 13 | import { CanComponentDeactivate } from '@app/shared/guards/can-deactivate-guard.service' |
15 | import { FormValidatorService, UserService } from '@app/shared' | 14 | import { FormValidatorService, UserService } from '@app/shared' |
16 | import { VideoCaptionService } from '@app/shared/video-caption' | 15 | import { VideoCaptionService } from '@app/shared/video-caption' |
16 | import { scrollToTop } from '@app/shared/misc/utils' | ||
17 | 17 | ||
18 | @Component({ | 18 | @Component({ |
19 | selector: 'my-video-upload', | 19 | selector: 'my-video-upload', |
20 | templateUrl: './video-upload.component.html', | 20 | templateUrl: './video-upload.component.html', |
21 | styleUrls: [ | 21 | styleUrls: [ |
22 | '../shared/video-edit.component.scss', | 22 | '../shared/video-edit.component.scss', |
23 | './video-upload.component.scss' | 23 | './video-upload.component.scss', |
24 | './video-send.scss' | ||
24 | ] | 25 | ] |
25 | }) | 26 | }) |
26 | export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy, CanComponentDeactivate { | 27 | export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy, CanComponentDeactivate { |
27 | @Output() firstStepDone = new EventEmitter<string>() | 28 | @Output() firstStepDone = new EventEmitter<string>() |
28 | @ViewChild('videofileInput') videofileInput | 29 | @Output() firstStepError = new EventEmitter<void>() |
30 | @ViewChild('videofileInput') videofileInput: ElementRef<HTMLInputElement> | ||
29 | 31 | ||
30 | // So that it can be accessed in the template | 32 | // So that it can be accessed in the template |
31 | readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY | 33 | readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY |
@@ -42,13 +44,16 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
42 | id: 0, | 44 | id: 0, |
43 | uuid: '' | 45 | uuid: '' |
44 | } | 46 | } |
47 | waitTranscodingEnabled = true | ||
48 | |||
49 | error: string | ||
45 | 50 | ||
46 | protected readonly DEFAULT_VIDEO_PRIVACY = VideoPrivacy.PUBLIC | 51 | protected readonly DEFAULT_VIDEO_PRIVACY = VideoPrivacy.PUBLIC |
47 | 52 | ||
48 | constructor ( | 53 | constructor ( |
49 | protected formValidatorService: FormValidatorService, | 54 | protected formValidatorService: FormValidatorService, |
50 | protected loadingBar: LoadingBarService, | 55 | protected loadingBar: LoadingBarService, |
51 | protected notificationsService: NotificationsService, | 56 | protected notifier: Notifier, |
52 | protected authService: AuthService, | 57 | protected authService: AuthService, |
53 | protected serverService: ServerService, | 58 | protected serverService: ServerService, |
54 | protected videoService: VideoService, | 59 | protected videoService: VideoService, |
@@ -105,20 +110,15 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
105 | this.isUploadingVideo = false | 110 | this.isUploadingVideo = false |
106 | this.videoUploadPercents = 0 | 111 | this.videoUploadPercents = 0 |
107 | this.videoUploadObservable = null | 112 | this.videoUploadObservable = null |
108 | this.notificationsService.info(this.i18n('Info'), this.i18n('Upload cancelled')) | 113 | this.notifier.info(this.i18n('Upload cancelled')) |
109 | } | 114 | } |
110 | } | 115 | } |
111 | 116 | ||
112 | uploadFirstStep () { | 117 | uploadFirstStep () { |
113 | const videofile = this.videofileInput.nativeElement.files[0] as File | 118 | const videofile = this.videofileInput.nativeElement.files[0] |
114 | if (!videofile) return | 119 | if (!videofile) return |
115 | 120 | ||
116 | // Cannot upload videos > 8GB for now | 121 | // Check global user quota |
117 | if (videofile.size > 8 * 1024 * 1024 * 1024) { | ||
118 | this.notificationsService.error(this.i18n('Error'), this.i18n('We are sorry but PeerTube cannot handle videos > 8GB')) | ||
119 | return | ||
120 | } | ||
121 | |||
122 | const bytePipes = new BytesPipe() | 122 | const bytePipes = new BytesPipe() |
123 | const videoQuota = this.authService.getUser().videoQuota | 123 | const videoQuota = this.authService.getUser().videoQuota |
124 | if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) { | 124 | if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) { |
@@ -130,10 +130,11 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
130 | videoQuota: bytePipes.transform(videoQuota, 0) | 130 | videoQuota: bytePipes.transform(videoQuota, 0) |
131 | } | 131 | } |
132 | ) | 132 | ) |
133 | this.notificationsService.error(this.i18n('Error'), msg) | 133 | this.notifier.error(msg) |
134 | return | 134 | return |
135 | } | 135 | } |
136 | 136 | ||
137 | // Check daily user quota | ||
137 | const videoQuotaDaily = this.authService.getUser().videoQuotaDaily | 138 | const videoQuotaDaily = this.authService.getUser().videoQuotaDaily |
138 | if (videoQuotaDaily !== -1 && (this.userVideoQuotaUsedDaily + videofile.size) > videoQuotaDaily) { | 139 | if (videoQuotaDaily !== -1 && (this.userVideoQuotaUsedDaily + videofile.size) > videoQuotaDaily) { |
139 | const msg = this.i18n( | 140 | const msg = this.i18n( |
@@ -144,10 +145,11 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
144 | quotaDaily: bytePipes.transform(videoQuotaDaily, 0) | 145 | quotaDaily: bytePipes.transform(videoQuotaDaily, 0) |
145 | } | 146 | } |
146 | ) | 147 | ) |
147 | this.notificationsService.error(this.i18n('Error'), msg) | 148 | this.notifier.error(msg) |
148 | return | 149 | return |
149 | } | 150 | } |
150 | 151 | ||
152 | // Build name field | ||
151 | const nameWithoutExtension = videofile.name.replace(/\.[^/.]+$/, '') | 153 | const nameWithoutExtension = videofile.name.replace(/\.[^/.]+$/, '') |
152 | let name: string | 154 | let name: string |
153 | 155 | ||
@@ -155,6 +157,11 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
155 | if (nameWithoutExtension.length < 3) name = videofile.name | 157 | if (nameWithoutExtension.length < 3) name = videofile.name |
156 | else name = nameWithoutExtension | 158 | else name = nameWithoutExtension |
157 | 159 | ||
160 | // Force user to wait transcoding for unsupported video types in web browsers | ||
161 | if (!videofile.name.endsWith('.mp4') && !videofile.name.endsWith('.webm') && !videofile.name.endsWith('.ogv')) { | ||
162 | this.waitTranscodingEnabled = false | ||
163 | } | ||
164 | |||
158 | const privacy = this.firstStepPrivacyId.toString() | 165 | const privacy = this.firstStepPrivacyId.toString() |
159 | const nsfw = false | 166 | const nsfw = false |
160 | const waitTranscoding = true | 167 | const waitTranscoding = true |
@@ -203,7 +210,8 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
203 | this.isUploadingVideo = false | 210 | this.isUploadingVideo = false |
204 | this.videoUploadPercents = 0 | 211 | this.videoUploadPercents = 0 |
205 | this.videoUploadObservable = null | 212 | this.videoUploadObservable = null |
206 | this.notificationsService.error(this.i18n('Error'), err.message) | 213 | this.firstStepError.emit() |
214 | this.notifier.error(err.message) | ||
207 | } | 215 | } |
208 | ) | 216 | ) |
209 | } | 217 | } |
@@ -232,13 +240,13 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
232 | this.isUpdatingVideo = false | 240 | this.isUpdatingVideo = false |
233 | this.isUploadingVideo = false | 241 | this.isUploadingVideo = false |
234 | 242 | ||
235 | this.notificationsService.success(this.i18n('Success'), this.i18n('Video published.')) | 243 | this.notifier.success(this.i18n('Video published.')) |
236 | this.router.navigate([ '/videos/watch', video.uuid ]) | 244 | this.router.navigate([ '/videos/watch', video.uuid ]) |
237 | }, | 245 | }, |
238 | 246 | ||
239 | err => { | 247 | err => { |
240 | this.isUpdatingVideo = false | 248 | this.error = err.message |
241 | this.notificationsService.error(this.i18n('Error'), err.message) | 249 | scrollToTop() |
242 | console.error(err) | 250 | console.error(err) |
243 | } | 251 | } |
244 | ) | 252 | ) |
diff --git a/client/src/app/videos/+video-edit/video-add.component.html b/client/src/app/videos/+video-edit/video-add.component.html index e14e23aed..72a233b72 100644 --- a/client/src/app/videos/+video-edit/video-add.component.html +++ b/client/src/app/videos/+video-edit/video-add.component.html | |||
@@ -6,24 +6,24 @@ | |||
6 | 6 | ||
7 | <ngb-tabset class="video-add-tabset root-tabset bootstrap" [ngClass]="{ 'hide-nav': secondStepType !== undefined }"> | 7 | <ngb-tabset class="video-add-tabset root-tabset bootstrap" [ngClass]="{ 'hide-nav': secondStepType !== undefined }"> |
8 | 8 | ||
9 | <ngb-tab i18n-title title=""> | 9 | <ngb-tab> |
10 | <ng-template ngbTabTitle><span i18n>Upload a file</span></ng-template> | 10 | <ng-template ngbTabTitle><span i18n>Upload a file</span></ng-template> |
11 | <ng-template ngbTabContent> | 11 | <ng-template ngbTabContent> |
12 | <my-video-upload #videoUpload (firstStepDone)="onFirstStepDone('upload', $event)"></my-video-upload> | 12 | <my-video-upload #videoUpload (firstStepDone)="onFirstStepDone('upload', $event)" (firstStepError)="onError()"></my-video-upload> |
13 | </ng-template> | 13 | </ng-template> |
14 | </ngb-tab> | 14 | </ngb-tab> |
15 | 15 | ||
16 | <ngb-tab *ngIf="isVideoImportHttpEnabled()"> | 16 | <ngb-tab *ngIf="isVideoImportHttpEnabled()"> |
17 | <ng-template ngbTabTitle><span i18n>Import with URL</span></ng-template> | 17 | <ng-template ngbTabTitle><span i18n>Import with URL</span></ng-template> |
18 | <ng-template ngbTabContent> | 18 | <ng-template ngbTabContent> |
19 | <my-video-import-url #videoImportUrl (firstStepDone)="onFirstStepDone('import-url', $event)"></my-video-import-url> | 19 | <my-video-import-url #videoImportUrl (firstStepDone)="onFirstStepDone('import-url', $event)" (firstStepError)="onError()"></my-video-import-url> |
20 | </ng-template> | 20 | </ng-template> |
21 | </ngb-tab> | 21 | </ngb-tab> |
22 | 22 | ||
23 | <ngb-tab *ngIf="isVideoImportTorrentEnabled()"> | 23 | <ngb-tab *ngIf="isVideoImportTorrentEnabled()"> |
24 | <ng-template ngbTabTitle><span i18n>Import with torrent</span></ng-template> | 24 | <ng-template ngbTabTitle><span i18n>Import with torrent</span></ng-template> |
25 | <ng-template ngbTabContent> | 25 | <ng-template ngbTabContent> |
26 | <my-video-import-torrent #videoImportTorrent (firstStepDone)="onFirstStepDone('import-torrent', $event)"></my-video-import-torrent> | 26 | <my-video-import-torrent #videoImportTorrent (firstStepDone)="onFirstStepDone('import-torrent', $event)" (firstStepError)="onError()"></my-video-import-torrent> |
27 | </ng-template> | 27 | </ng-template> |
28 | </ngb-tab> | 28 | </ngb-tab> |
29 | </ngb-tabset> | 29 | </ngb-tabset> |
diff --git a/client/src/app/videos/+video-edit/video-add.component.ts b/client/src/app/videos/+video-edit/video-add.component.ts index 1a9247dbe..01fdfcb66 100644 --- a/client/src/app/videos/+video-edit/video-add.component.ts +++ b/client/src/app/videos/+video-edit/video-add.component.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { Component, ViewChild } from '@angular/core' | 1 | import { Component, HostListener, ViewChild } from '@angular/core' |
2 | import { CanComponentDeactivate } from '@app/shared/guards/can-deactivate-guard.service' | 2 | import { CanComponentDeactivate } from '@app/shared/guards/can-deactivate-guard.service' |
3 | import { VideoImportUrlComponent } from '@app/videos/+video-edit/video-add-components/video-import-url.component' | 3 | import { VideoImportUrlComponent } from '@app/videos/+video-edit/video-add-components/video-import-url.component' |
4 | import { VideoUploadComponent } from '@app/videos/+video-edit/video-add-components/video-upload.component' | 4 | import { VideoUploadComponent } from '@app/videos/+video-edit/video-add-components/video-upload.component' |
@@ -27,7 +27,22 @@ export class VideoAddComponent implements CanComponentDeactivate { | |||
27 | this.videoName = videoName | 27 | this.videoName = videoName |
28 | } | 28 | } |
29 | 29 | ||
30 | canDeactivate () { | 30 | onError () { |
31 | this.videoName = undefined | ||
32 | this.secondStepType = undefined | ||
33 | } | ||
34 | |||
35 | @HostListener('window:beforeunload', [ '$event' ]) | ||
36 | onUnload (event: any) { | ||
37 | const { text, canDeactivate } = this.canDeactivate() | ||
38 | |||
39 | if (canDeactivate) return | ||
40 | |||
41 | event.returnValue = text | ||
42 | return text | ||
43 | } | ||
44 | |||
45 | canDeactivate (): { canDeactivate: boolean, text?: string} { | ||
31 | if (this.secondStepType === 'upload') return this.videoUpload.canDeactivate() | 46 | if (this.secondStepType === 'upload') return this.videoUpload.canDeactivate() |
32 | if (this.secondStepType === 'import-url') return this.videoImportUrl.canDeactivate() | 47 | if (this.secondStepType === 'import-url') return this.videoImportUrl.canDeactivate() |
33 | if (this.secondStepType === 'import-torrent') return this.videoImportTorrent.canDeactivate() | 48 | if (this.secondStepType === 'import-torrent') return this.videoImportTorrent.canDeactivate() |
diff --git a/client/src/app/videos/+video-edit/video-update.component.html b/client/src/app/videos/+video-edit/video-update.component.html index 9242c30a0..4992bb369 100644 --- a/client/src/app/videos/+video-edit/video-update.component.html +++ b/client/src/app/videos/+video-edit/video-update.component.html | |||
@@ -8,12 +8,12 @@ | |||
8 | <my-video-edit | 8 | <my-video-edit |
9 | [form]="form" [formErrors]="formErrors" [schedulePublicationPossible]="schedulePublicationPossible" | 9 | [form]="form" [formErrors]="formErrors" [schedulePublicationPossible]="schedulePublicationPossible" |
10 | [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels" | 10 | [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels" |
11 | [videoCaptions]="videoCaptions" | 11 | [videoCaptions]="videoCaptions" [waitTranscodingEnabled]="waitTranscodingEnabled" |
12 | ></my-video-edit> | 12 | ></my-video-edit> |
13 | 13 | ||
14 | <div class="submit-container"> | 14 | <div class="submit-container"> |
15 | <div class="submit-button" (click)="update()" [ngClass]="{ disabled: !form.valid || isUpdatingVideo === true }"> | 15 | <div class="submit-button" (click)="update()" [ngClass]="{ disabled: !form.valid || isUpdatingVideo === true }"> |
16 | <span class="icon icon-validate"></span> | 16 | <my-global-icon iconName="validate"></my-global-icon> |
17 | <input type="button" i18n-value value="Update" /> | 17 | <input type="button" i18n-value value="Update" /> |
18 | </div> | 18 | </div> |
19 | </div> | 19 | </div> |
diff --git a/client/src/app/videos/+video-edit/video-update.component.ts b/client/src/app/videos/+video-edit/video-update.component.ts index 3a0f3a39a..9e849014e 100644 --- a/client/src/app/videos/+video-edit/video-update.component.ts +++ b/client/src/app/videos/+video-edit/video-update.component.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import { map, switchMap } from 'rxjs/operators' | 1 | import { map, switchMap } from 'rxjs/operators' |
2 | import { Component, OnInit } from '@angular/core' | 2 | import { Component, HostListener, OnInit } from '@angular/core' |
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { LoadingBarService } from '@ngx-loading-bar/core' | 4 | import { LoadingBarService } from '@ngx-loading-bar/core' |
5 | import { NotificationsService } from 'angular2-notifications' | 5 | import { Notifier } from '@app/core' |
6 | import { VideoConstant, VideoPrivacy } from '../../../../../shared/models/videos' | 6 | import { VideoConstant, VideoPrivacy } from '../../../../../shared/models/videos' |
7 | import { ServerService } from '../../core' | 7 | import { ServerService } from '../../core' |
8 | import { FormReactive } from '../../shared' | 8 | import { FormReactive } from '../../shared' |
@@ -12,6 +12,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
12 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 12 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
13 | import { VideoCaptionService } from '@app/shared/video-caption' | 13 | import { VideoCaptionService } from '@app/shared/video-caption' |
14 | import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model' | 14 | import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model' |
15 | import { VideoDetails } from '@app/shared/video/video-details.model' | ||
15 | 16 | ||
16 | @Component({ | 17 | @Component({ |
17 | selector: 'my-videos-update', | 18 | selector: 'my-videos-update', |
@@ -26,6 +27,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
26 | userVideoChannels: { id: number, label: string, support: string }[] = [] | 27 | userVideoChannels: { id: number, label: string, support: string }[] = [] |
27 | schedulePublicationPossible = false | 28 | schedulePublicationPossible = false |
28 | videoCaptions: VideoCaptionEdit[] = [] | 29 | videoCaptions: VideoCaptionEdit[] = [] |
30 | waitTranscodingEnabled = true | ||
29 | 31 | ||
30 | private updateDone = false | 32 | private updateDone = false |
31 | 33 | ||
@@ -33,7 +35,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
33 | protected formValidatorService: FormValidatorService, | 35 | protected formValidatorService: FormValidatorService, |
34 | private route: ActivatedRoute, | 36 | private route: ActivatedRoute, |
35 | private router: Router, | 37 | private router: Router, |
36 | private notificationsService: NotificationsService, | 38 | private notifier: Notifier, |
37 | private serverService: ServerService, | 39 | private serverService: ServerService, |
38 | private videoService: VideoService, | 40 | private videoService: VideoService, |
39 | private loadingBar: LoadingBarService, | 41 | private loadingBar: LoadingBarService, |
@@ -65,25 +67,42 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
65 | 67 | ||
66 | this.videoPrivacies = this.videoService.explainedPrivacyLabels(this.videoPrivacies) | 68 | this.videoPrivacies = this.videoService.explainedPrivacyLabels(this.videoPrivacies) |
67 | 69 | ||
70 | const videoFiles = (video as VideoDetails).files | ||
71 | if (videoFiles.length > 1) { // Already transcoded | ||
72 | this.waitTranscodingEnabled = false | ||
73 | } | ||
74 | |||
68 | // FIXME: Angular does not detect the change inside this subscription, so use the patched setTimeout | 75 | // FIXME: Angular does not detect the change inside this subscription, so use the patched setTimeout |
69 | setTimeout(() => this.hydrateFormFromVideo()) | 76 | setTimeout(() => this.hydrateFormFromVideo()) |
70 | }, | 77 | }, |
71 | 78 | ||
72 | err => { | 79 | err => { |
73 | console.error(err) | 80 | console.error(err) |
74 | this.notificationsService.error(this.i18n('Error'), err.message) | 81 | this.notifier.error(err.message) |
75 | } | 82 | } |
76 | ) | 83 | ) |
77 | } | 84 | } |
78 | 85 | ||
79 | canDeactivate () { | 86 | @HostListener('window:beforeunload', [ '$event' ]) |
87 | onUnload (event: any) { | ||
88 | const { text, canDeactivate } = this.canDeactivate() | ||
89 | |||
90 | if (canDeactivate) return | ||
91 | |||
92 | event.returnValue = text | ||
93 | return text | ||
94 | } | ||
95 | |||
96 | canDeactivate (): { canDeactivate: boolean, text?: string } { | ||
80 | if (this.updateDone === true) return { canDeactivate: true } | 97 | if (this.updateDone === true) return { canDeactivate: true } |
81 | 98 | ||
99 | const text = this.i18n('You have unsaved changes! If you leave, your changes will be lost.') | ||
100 | |||
82 | for (const caption of this.videoCaptions) { | 101 | for (const caption of this.videoCaptions) { |
83 | if (caption.action) return { canDeactivate: false } | 102 | if (caption.action) return { canDeactivate: false, text } |
84 | } | 103 | } |
85 | 104 | ||
86 | return { canDeactivate: this.formChanged === false } | 105 | return { canDeactivate: this.formChanged === false, text } |
87 | } | 106 | } |
88 | 107 | ||
89 | checkForm () { | 108 | checkForm () { |
@@ -114,14 +133,14 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
114 | this.updateDone = true | 133 | this.updateDone = true |
115 | this.isUpdatingVideo = false | 134 | this.isUpdatingVideo = false |
116 | this.loadingBar.complete() | 135 | this.loadingBar.complete() |
117 | this.notificationsService.success(this.i18n('Success'), this.i18n('Video updated.')) | 136 | this.notifier.success(this.i18n('Video updated.')) |
118 | this.router.navigate([ '/videos/watch', this.video.uuid ]) | 137 | this.router.navigate([ '/videos/watch', this.video.uuid ]) |
119 | }, | 138 | }, |
120 | 139 | ||
121 | err => { | 140 | err => { |
122 | this.loadingBar.complete() | 141 | this.loadingBar.complete() |
123 | this.isUpdatingVideo = false | 142 | this.isUpdatingVideo = false |
124 | this.notificationsService.error(this.i18n('Error'), err.message) | 143 | this.notifier.error(err.message) |
125 | console.error(err) | 144 | console.error(err) |
126 | } | 145 | } |
127 | ) | 146 | ) |
diff --git a/client/src/app/videos/+video-watch/comment/linkifier.service.ts b/client/src/app/videos/+video-watch/comment/linkifier.service.ts deleted file mode 100644 index 3f4072efd..000000000 --- a/client/src/app/videos/+video-watch/comment/linkifier.service.ts +++ /dev/null | |||
@@ -1,114 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { getAbsoluteAPIUrl } from '@app/shared/misc/utils' | ||
3 | import * as linkify from 'linkifyjs' | ||
4 | import * as linkifyHtml from 'linkifyjs/html' | ||
5 | |||
6 | @Injectable() | ||
7 | export class LinkifierService { | ||
8 | |||
9 | static CLASSNAME = 'linkified' | ||
10 | |||
11 | private linkifyOptions = { | ||
12 | className: { | ||
13 | mention: LinkifierService.CLASSNAME + '-mention', | ||
14 | url: LinkifierService.CLASSNAME + '-url' | ||
15 | } | ||
16 | } | ||
17 | |||
18 | constructor () { | ||
19 | // Apply plugin | ||
20 | this.mentionWithDomainPlugin(linkify) | ||
21 | } | ||
22 | |||
23 | linkify (text: string) { | ||
24 | return linkifyHtml(text, this.linkifyOptions) | ||
25 | } | ||
26 | |||
27 | private mentionWithDomainPlugin (linkify: any) { | ||
28 | const TT = linkify.scanner.TOKENS // Text tokens | ||
29 | const { TOKENS: MT, State } = linkify.parser // Multi tokens, state | ||
30 | const MultiToken = MT.Base | ||
31 | const S_START = linkify.parser.start | ||
32 | |||
33 | const TT_AT = TT.AT | ||
34 | const TT_DOMAIN = TT.DOMAIN | ||
35 | const TT_LOCALHOST = TT.LOCALHOST | ||
36 | const TT_NUM = TT.NUM | ||
37 | const TT_COLON = TT.COLON | ||
38 | const TT_SLASH = TT.SLASH | ||
39 | const TT_TLD = TT.TLD | ||
40 | const TT_UNDERSCORE = TT.UNDERSCORE | ||
41 | const TT_DOT = TT.DOT | ||
42 | |||
43 | function MENTION (value) { | ||
44 | this.v = value | ||
45 | } | ||
46 | |||
47 | linkify.inherits(MultiToken, MENTION, { | ||
48 | type: 'mentionWithDomain', | ||
49 | isLink: true, | ||
50 | toHref () { | ||
51 | return getAbsoluteAPIUrl() + '/services/redirect/accounts/' + this.toString().substr(1) | ||
52 | } | ||
53 | }) | ||
54 | |||
55 | const S_AT = S_START.jump(TT_AT) // @ | ||
56 | const S_AT_SYMS = new State() | ||
57 | const S_MENTION = new State(MENTION) | ||
58 | const S_MENTION_DIVIDER = new State() | ||
59 | const S_MENTION_DIVIDER_SYMS = new State() | ||
60 | |||
61 | // @_, | ||
62 | S_AT.on(TT_UNDERSCORE, S_AT_SYMS) | ||
63 | |||
64 | // @_* | ||
65 | S_AT_SYMS | ||
66 | .on(TT_UNDERSCORE, S_AT_SYMS) | ||
67 | .on(TT_DOT, S_AT_SYMS) | ||
68 | |||
69 | // Valid mention (not made up entirely of symbols) | ||
70 | S_AT | ||
71 | .on(TT_DOMAIN, S_MENTION) | ||
72 | .on(TT_LOCALHOST, S_MENTION) | ||
73 | .on(TT_TLD, S_MENTION) | ||
74 | .on(TT_NUM, S_MENTION) | ||
75 | |||
76 | S_AT_SYMS | ||
77 | .on(TT_DOMAIN, S_MENTION) | ||
78 | .on(TT_LOCALHOST, S_MENTION) | ||
79 | .on(TT_TLD, S_MENTION) | ||
80 | .on(TT_NUM, S_MENTION) | ||
81 | |||
82 | // More valid mentions | ||
83 | S_MENTION | ||
84 | .on(TT_DOMAIN, S_MENTION) | ||
85 | .on(TT_LOCALHOST, S_MENTION) | ||
86 | .on(TT_TLD, S_MENTION) | ||
87 | .on(TT_COLON, S_MENTION) | ||
88 | .on(TT_NUM, S_MENTION) | ||
89 | .on(TT_UNDERSCORE, S_MENTION) | ||
90 | |||
91 | // Mention with a divider | ||
92 | S_MENTION | ||
93 | .on(TT_AT, S_MENTION_DIVIDER) | ||
94 | .on(TT_SLASH, S_MENTION_DIVIDER) | ||
95 | .on(TT_DOT, S_MENTION_DIVIDER) | ||
96 | |||
97 | // Mention _ trailing stash plus syms | ||
98 | S_MENTION_DIVIDER.on(TT_UNDERSCORE, S_MENTION_DIVIDER_SYMS) | ||
99 | S_MENTION_DIVIDER_SYMS.on(TT_UNDERSCORE, S_MENTION_DIVIDER_SYMS) | ||
100 | |||
101 | // Once we get a word token, mentions can start up again | ||
102 | S_MENTION_DIVIDER | ||
103 | .on(TT_DOMAIN, S_MENTION) | ||
104 | .on(TT_LOCALHOST, S_MENTION) | ||
105 | .on(TT_TLD, S_MENTION) | ||
106 | .on(TT_NUM, S_MENTION) | ||
107 | |||
108 | S_MENTION_DIVIDER_SYMS | ||
109 | .on(TT_DOMAIN, S_MENTION) | ||
110 | .on(TT_LOCALHOST, S_MENTION) | ||
111 | .on(TT_TLD, S_MENTION) | ||
112 | .on(TT_NUM, S_MENTION) | ||
113 | } | ||
114 | } | ||
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 index b58a56596..d8a7a78c4 100644 --- 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 | |||
@@ -3,7 +3,7 @@ | |||
3 | <img [src]="getAvatarUrl()" alt="Avatar" /> | 3 | <img [src]="getAvatarUrl()" alt="Avatar" /> |
4 | 4 | ||
5 | <div class="form-group"> | 5 | <div class="form-group"> |
6 | <textarea i18n-placeholder placeholder="Add comment..." autosize | 6 | <textarea i18n-placeholder placeholder="Add comment..." myAutoResize |
7 | [readonly]="(user === null) ? true : false" | 7 | [readonly]="(user === null) ? true : false" |
8 | (click)="openVisitorModal($event)" | 8 | (click)="openVisitorModal($event)" |
9 | formControlName="text" [ngClass]="{ 'input-error': formErrors['text'] }" | 9 | formControlName="text" [ngClass]="{ 'input-error': formErrors['text'] }" |
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 index fb7de0e04..fd85c28f2 100644 --- 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 | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Router } from '@angular/router' | 2 | import { Router } from '@angular/router' |
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { Notifier } from '@app/core' |
4 | import { Observable } from 'rxjs' | 4 | import { Observable } from 'rxjs' |
5 | import { VideoCommentCreate } from '../../../../../../shared/models/videos/video-comment.model' | 5 | import { VideoCommentCreate } from '../../../../../../shared/models/videos/video-comment.model' |
6 | import { FormReactive } from '../../../shared' | 6 | import { FormReactive } from '../../../shared' |
@@ -29,14 +29,14 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit { | |||
29 | @Output() commentCreated = new EventEmitter<VideoCommentCreate>() | 29 | @Output() commentCreated = new EventEmitter<VideoCommentCreate>() |
30 | 30 | ||
31 | @ViewChild('visitorModal') visitorModal: NgbModal | 31 | @ViewChild('visitorModal') visitorModal: NgbModal |
32 | @ViewChild('textarea') private textareaElement: ElementRef | 32 | @ViewChild('textarea') textareaElement: ElementRef |
33 | 33 | ||
34 | private addingComment = false | 34 | addingComment = false |
35 | 35 | ||
36 | constructor ( | 36 | constructor ( |
37 | protected formValidatorService: FormValidatorService, | 37 | protected formValidatorService: FormValidatorService, |
38 | private videoCommentValidatorsService: VideoCommentValidatorsService, | 38 | private videoCommentValidatorsService: VideoCommentValidatorsService, |
39 | private notificationsService: NotificationsService, | 39 | private notifier: Notifier, |
40 | private videoCommentService: VideoCommentService, | 40 | private videoCommentService: VideoCommentService, |
41 | private authService: AuthService, | 41 | private authService: AuthService, |
42 | private modalService: NgbModal, | 42 | private modalService: NgbModal, |
@@ -70,13 +70,13 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit { | |||
70 | } | 70 | } |
71 | 71 | ||
72 | onValidKey () { | 72 | onValidKey () { |
73 | this.onValueChanged() | 73 | this.check() |
74 | if (!this.form.valid) return | 74 | if (!this.form.valid) return |
75 | 75 | ||
76 | this.formValidated() | 76 | this.formValidated() |
77 | } | 77 | } |
78 | 78 | ||
79 | openVisitorModal (event) { | 79 | openVisitorModal (event: any) { |
80 | if (this.user === null) { // we only open it for visitors | 80 | if (this.user === null) { // we only open it for visitors |
81 | // fixing ng-bootstrap ModalService and the "Expression Changed After It Has Been Checked" Error | 81 | // fixing ng-bootstrap ModalService and the "Expression Changed After It Has Been Checked" Error |
82 | event.srcElement.blur() | 82 | event.srcElement.blur() |
@@ -115,7 +115,7 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit { | |||
115 | err => { | 115 | err => { |
116 | this.addingComment = false | 116 | this.addingComment = false |
117 | 117 | ||
118 | this.notificationsService.error(this.i18n('Error'), err.text) | 118 | this.notifier.error(err.text) |
119 | } | 119 | } |
120 | ) | 120 | ) |
121 | } | 121 | } |
@@ -135,7 +135,6 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit { | |||
135 | 135 | ||
136 | gotoLogin () { | 136 | gotoLogin () { |
137 | this.hideVisitorModal() | 137 | this.hideVisitorModal() |
138 | this.authService.redirectUrl = this.router.url | ||
139 | this.router.navigate([ '/login' ]) | 138 | this.router.navigate([ '/login' ]) |
140 | } | 139 | } |
141 | 140 | ||
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 index 84da5727e..731ecbf8f 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment.component.scss +++ b/client/src/app/videos/+video-watch/comment/video-comment.component.scss | |||
@@ -41,7 +41,7 @@ | |||
41 | } | 41 | } |
42 | 42 | ||
43 | .comment-date { | 43 | .comment-date { |
44 | color: #585858; | 44 | color: $grey-foreground-color; |
45 | margin-left: 10px; | 45 | margin-left: 10px; |
46 | } | 46 | } |
47 | } | 47 | } |
@@ -69,7 +69,7 @@ | |||
69 | 69 | ||
70 | .comment-action-reply, | 70 | .comment-action-reply, |
71 | .comment-action-delete { | 71 | .comment-action-delete { |
72 | color: #585858; | 72 | color: $grey-foreground-color; |
73 | cursor: pointer; | 73 | cursor: pointer; |
74 | margin-right: 10px; | 74 | margin-right: 10px; |
75 | 75 | ||
@@ -108,4 +108,4 @@ | |||
108 | .root-comment { | 108 | .root-comment { |
109 | font-size: 14px; | 109 | font-size: 14px; |
110 | } | 110 | } |
111 | } \ No newline at end of file | 111 | } |
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 index e90008de9..aba7f9d1c 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment.component.ts +++ b/client/src/app/videos/+video-watch/comment/video-comment.component.ts | |||
@@ -1,11 +1,10 @@ | |||
1 | import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core' | 1 | import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core' |
2 | import { LinkifierService } from '@app/videos/+video-watch/comment/linkifier.service' | ||
3 | import * as sanitizeHtml from 'sanitize-html' | ||
4 | import { UserRight } from '../../../../../../shared/models/users' | 2 | import { UserRight } from '../../../../../../shared/models/users' |
5 | import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model' | 3 | import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model' |
6 | import { AuthService } from '../../../core/auth' | 4 | import { AuthService } from '../../../core/auth' |
7 | import { Video } from '../../../shared/video/video.model' | 5 | import { Video } from '../../../shared/video/video.model' |
8 | import { VideoComment } from './video-comment.model' | 6 | import { VideoComment } from './video-comment.model' |
7 | import { HtmlRendererService } from '@app/shared/renderer' | ||
9 | 8 | ||
10 | @Component({ | 9 | @Component({ |
11 | selector: 'my-video-comment', | 10 | selector: 'my-video-comment', |
@@ -26,10 +25,10 @@ export class VideoCommentComponent implements OnInit, OnChanges { | |||
26 | @Output() resetReply = new EventEmitter() | 25 | @Output() resetReply = new EventEmitter() |
27 | 26 | ||
28 | sanitizedCommentHTML = '' | 27 | sanitizedCommentHTML = '' |
29 | newParentComments = [] | 28 | newParentComments: VideoComment[] = [] |
30 | 29 | ||
31 | constructor ( | 30 | constructor ( |
32 | private linkifierService: LinkifierService, | 31 | private htmlRenderer: HtmlRendererService, |
33 | private authService: AuthService | 32 | private authService: AuthService |
34 | ) {} | 33 | ) {} |
35 | 34 | ||
@@ -87,27 +86,7 @@ export class VideoCommentComponent implements OnInit, OnChanges { | |||
87 | } | 86 | } |
88 | 87 | ||
89 | private init () { | 88 | private init () { |
90 | // Convert possible markdown to html | 89 | this.sanitizedCommentHTML = this.htmlRenderer.toSafeHtml(this.comment.text) |
91 | const html = this.linkifierService.linkify(this.comment.text) | ||
92 | |||
93 | this.sanitizedCommentHTML = sanitizeHtml(html, { | ||
94 | allowedTags: [ 'a', 'p', 'span', 'br' ], | ||
95 | allowedSchemes: [ 'http', 'https' ], | ||
96 | allowedAttributes: { | ||
97 | 'a': [ 'href', 'class', 'target' ] | ||
98 | }, | ||
99 | transformTags: { | ||
100 | a: (tagName, attribs) => { | ||
101 | return { | ||
102 | tagName, | ||
103 | attribs: Object.assign(attribs, { | ||
104 | target: '_blank', | ||
105 | rel: 'noopener noreferrer' | ||
106 | }) | ||
107 | } | ||
108 | } | ||
109 | } | ||
110 | }) | ||
111 | 90 | ||
112 | this.newParentComments = this.parentComments.concat([ this.comment ]) | 91 | this.newParentComments = this.parentComments.concat([ this.comment ]) |
113 | } | 92 | } |
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 index fe591811e..824fb24c3 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment.model.ts +++ b/client/src/app/videos/+video-watch/comment/video-comment.model.ts | |||
@@ -14,7 +14,7 @@ export class VideoComment implements VideoCommentServerModel { | |||
14 | account: AccountInterface | 14 | account: AccountInterface |
15 | totalReplies: number | 15 | totalReplies: number |
16 | by: string | 16 | by: string |
17 | accountAvatarUrl | 17 | accountAvatarUrl: string |
18 | 18 | ||
19 | constructor (hash: VideoCommentServerModel) { | 19 | constructor (hash: VideoCommentServerModel) { |
20 | this.id = hash.id | 20 | this.id = hash.id |
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 index 9bcb4b7de..b8e5878c5 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment.service.ts +++ b/client/src/app/videos/+video-watch/comment/video-comment.service.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { catchError, map } from 'rxjs/operators' | 1 | import { catchError, map } from 'rxjs/operators' |
2 | import { HttpClient, HttpParams } from '@angular/common/http' | 2 | import { HttpClient, HttpParams } from '@angular/common/http' |
3 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { lineFeedToHtml } from '@app/shared/misc/utils' | 4 | import { objectLineFeedToHtml } from '@app/shared/misc/utils' |
5 | import { Observable } from 'rxjs' | 5 | import { Observable } from 'rxjs' |
6 | import { ResultList, FeedFormat } from '../../../../../../shared/models' | 6 | import { ResultList, FeedFormat } from '../../../../../../shared/models' |
7 | import { | 7 | import { |
@@ -28,22 +28,22 @@ export class VideoCommentService { | |||
28 | 28 | ||
29 | addCommentThread (videoId: number | string, comment: VideoCommentCreate) { | 29 | addCommentThread (videoId: number | string, comment: VideoCommentCreate) { |
30 | const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comment-threads' | 30 | const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comment-threads' |
31 | const normalizedComment = lineFeedToHtml(comment, 'text') | 31 | const normalizedComment = objectLineFeedToHtml(comment, 'text') |
32 | 32 | ||
33 | return this.authHttp.post(url, normalizedComment) | 33 | return this.authHttp.post<{ comment: VideoCommentServerModel }>(url, normalizedComment) |
34 | .pipe( | 34 | .pipe( |
35 | map(data => this.extractVideoComment(data['comment'])), | 35 | map(data => this.extractVideoComment(data.comment)), |
36 | catchError(err => this.restExtractor.handleError(err)) | 36 | catchError(err => this.restExtractor.handleError(err)) |
37 | ) | 37 | ) |
38 | } | 38 | } |
39 | 39 | ||
40 | addCommentReply (videoId: number | string, inReplyToCommentId: number, comment: VideoCommentCreate) { | 40 | addCommentReply (videoId: number | string, inReplyToCommentId: number, comment: VideoCommentCreate) { |
41 | const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comments/' + inReplyToCommentId | 41 | const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comments/' + inReplyToCommentId |
42 | const normalizedComment = lineFeedToHtml(comment, 'text') | 42 | const normalizedComment = objectLineFeedToHtml(comment, 'text') |
43 | 43 | ||
44 | return this.authHttp.post(url, normalizedComment) | 44 | return this.authHttp.post<{ comment: VideoCommentServerModel }>(url, normalizedComment) |
45 | .pipe( | 45 | .pipe( |
46 | map(data => this.extractVideoComment(data[ 'comment' ])), | 46 | map(data => this.extractVideoComment(data.comment)), |
47 | catchError(err => this.restExtractor.handleError(err)) | 47 | catchError(err => this.restExtractor.handleError(err)) |
48 | ) | 48 | ) |
49 | } | 49 | } |
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 index 42e129d65..44016d8ad 100644 --- a/client/src/app/videos/+video-watch/comment/video-comments.component.html +++ b/client/src/app/videos/+video-watch/comment/video-comments.component.html | |||
@@ -4,7 +4,7 @@ | |||
4 | Comments | 4 | Comments |
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | <my-video-feed [syndicationItems]="syndicationItems"></my-video-feed> | 7 | <my-feed [syndicationItems]="syndicationItems"></my-feed> |
8 | </div> | 8 | </div> |
9 | 9 | ||
10 | <ng-template [ngIf]="video.commentsEnabled === true"> | 10 | <ng-template [ngIf]="video.commentsEnabled === true"> |
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 index dbb44c66c..575e331e4 100644 --- a/client/src/app/videos/+video-watch/comment/video-comments.component.scss +++ b/client/src/app/videos/+video-watch/comment/video-comments.component.scss | |||
@@ -23,7 +23,7 @@ | |||
23 | margin-right: 0; | 23 | margin-right: 0; |
24 | } | 24 | } |
25 | 25 | ||
26 | my-video-feed { | 26 | my-feed { |
27 | display: inline-block; | 27 | display: inline-block; |
28 | margin-left: 5px; | 28 | margin-left: 5px; |
29 | } | 29 | } |
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 index c864d82b7..2616820d2 100644 --- a/client/src/app/videos/+video-watch/comment/video-comments.component.ts +++ b/client/src/app/videos/+video-watch/comment/video-comments.component.ts | |||
@@ -1,17 +1,17 @@ | |||
1 | import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild, ElementRef } from '@angular/core' | 1 | import { Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild } from '@angular/core' |
2 | import { ActivatedRoute } from '@angular/router' | 2 | import { ActivatedRoute } from '@angular/router' |
3 | import { ConfirmService } from '@app/core' | 3 | import { ConfirmService, Notifier } from '@app/core' |
4 | import { NotificationsService } from 'angular2-notifications' | ||
5 | import { Subscription } from 'rxjs' | 4 | import { Subscription } from 'rxjs' |
6 | import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model' | 5 | import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model' |
7 | import { AuthService } from '../../../core/auth' | 6 | import { AuthService } from '../../../core/auth' |
8 | import { ComponentPagination } from '../../../shared/rest/component-pagination.model' | 7 | import { ComponentPagination, hasMoreItems } from '../../../shared/rest/component-pagination.model' |
9 | import { User } from '../../../shared/users' | 8 | import { User } from '../../../shared/users' |
10 | import { VideoSortField } from '../../../shared/video/sort-field.type' | 9 | import { VideoSortField } from '../../../shared/video/sort-field.type' |
11 | import { VideoDetails } from '../../../shared/video/video-details.model' | 10 | import { VideoDetails } from '../../../shared/video/video-details.model' |
12 | import { VideoComment } from './video-comment.model' | 11 | import { VideoComment } from './video-comment.model' |
13 | import { VideoCommentService } from './video-comment.service' | 12 | import { VideoCommentService } from './video-comment.service' |
14 | import { I18n } from '@ngx-translate/i18n-polyfill' | 13 | import { I18n } from '@ngx-translate/i18n-polyfill' |
14 | import { Syndication } from '@app/shared/video/syndication.model' | ||
15 | 15 | ||
16 | @Component({ | 16 | @Component({ |
17 | selector: 'my-video-comments', | 17 | selector: 'my-video-comments', |
@@ -35,13 +35,13 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
35 | threadComments: { [ id: number ]: VideoCommentThreadTree } = {} | 35 | threadComments: { [ id: number ]: VideoCommentThreadTree } = {} |
36 | threadLoading: { [ id: number ]: boolean } = {} | 36 | threadLoading: { [ id: number ]: boolean } = {} |
37 | 37 | ||
38 | syndicationItems = [] | 38 | syndicationItems: Syndication[] = [] |
39 | 39 | ||
40 | private sub: Subscription | 40 | private sub: Subscription |
41 | 41 | ||
42 | constructor ( | 42 | constructor ( |
43 | private authService: AuthService, | 43 | private authService: AuthService, |
44 | private notificationsService: NotificationsService, | 44 | private notifier: Notifier, |
45 | private confirmService: ConfirmService, | 45 | private confirmService: ConfirmService, |
46 | private videoCommentService: VideoCommentService, | 46 | private videoCommentService: VideoCommentService, |
47 | private activatedRoute: ActivatedRoute, | 47 | private activatedRoute: ActivatedRoute, |
@@ -83,15 +83,11 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
83 | this.highlightedThread = new VideoComment(res.comment) | 83 | this.highlightedThread = new VideoComment(res.comment) |
84 | 84 | ||
85 | // Scroll to the highlighted thread | 85 | // Scroll to the highlighted thread |
86 | setTimeout(() => { | 86 | setTimeout(() => this.commentHighlightBlock.nativeElement.scrollIntoView(), 0) |
87 | // -60 because of the fixed header | ||
88 | const scrollY = this.commentHighlightBlock.nativeElement.offsetTop - 60 | ||
89 | window.scroll(0, scrollY) | ||
90 | }, 500) | ||
91 | } | 87 | } |
92 | }, | 88 | }, |
93 | 89 | ||
94 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 90 | err => this.notifier.error(err.message) |
95 | ) | 91 | ) |
96 | } | 92 | } |
97 | 93 | ||
@@ -103,7 +99,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
103 | this.componentPagination.totalItems = res.totalComments | 99 | this.componentPagination.totalItems = res.totalComments |
104 | }, | 100 | }, |
105 | 101 | ||
106 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 102 | err => this.notifier.error(err.message) |
107 | ) | 103 | ) |
108 | } | 104 | } |
109 | 105 | ||
@@ -154,7 +150,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
154 | if (this.highlightedThread.id === commentToDelete.id) this.highlightedThread = undefined | 150 | if (this.highlightedThread.id === commentToDelete.id) this.highlightedThread = undefined |
155 | }, | 151 | }, |
156 | 152 | ||
157 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 153 | err => this.notifier.error(err.message) |
158 | ) | 154 | ) |
159 | } | 155 | } |
160 | 156 | ||
@@ -165,22 +161,11 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
165 | onNearOfBottom () { | 161 | onNearOfBottom () { |
166 | this.componentPagination.currentPage++ | 162 | this.componentPagination.currentPage++ |
167 | 163 | ||
168 | if (this.hasMoreComments()) { | 164 | if (hasMoreItems(this.componentPagination)) { |
169 | this.loadMoreComments() | 165 | this.loadMoreComments() |
170 | } | 166 | } |
171 | } | 167 | } |
172 | 168 | ||
173 | private hasMoreComments () { | ||
174 | // No results | ||
175 | if (this.componentPagination.totalItems === 0) return false | ||
176 | |||
177 | // Not loaded yet | ||
178 | if (!this.componentPagination.totalItems) return true | ||
179 | |||
180 | const maxPage = this.componentPagination.totalItems / this.componentPagination.itemsPerPage | ||
181 | return maxPage > this.componentPagination.currentPage | ||
182 | } | ||
183 | |||
184 | private deleteLocalCommentThread (parentComment: VideoCommentThreadTree, commentToDelete: VideoComment) { | 169 | private deleteLocalCommentThread (parentComment: VideoCommentThreadTree, commentToDelete: VideoComment) { |
185 | for (const commentChild of parentComment.children) { | 170 | for (const commentChild of parentComment.children) { |
186 | if (commentChild.comment.id === commentToDelete.id) { | 171 | if (commentChild.comment.id === commentToDelete.id) { |
diff --git a/client/src/app/videos/+video-watch/modal/video-blacklist.component.html b/client/src/app/videos/+video-watch/modal/video-blacklist.component.html index c436501b4..1a87bdcd4 100644 --- a/client/src/app/videos/+video-watch/modal/video-blacklist.component.html +++ b/client/src/app/videos/+video-watch/modal/video-blacklist.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <ng-template #modal> | 1 | <ng-template #modal> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Blacklist video</h4> | 3 | <h4 i18n class="modal-title">Blacklist video</h4> |
4 | <span class="close" aria-label="Close" role="button" (click)="hide()"></span> | 4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> |
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | <div class="modal-body"> | 7 | <div class="modal-body"> |
@@ -15,6 +15,13 @@ | |||
15 | </div> | 15 | </div> |
16 | </div> | 16 | </div> |
17 | 17 | ||
18 | <div class="form-group" *ngIf="video.isLocal"> | ||
19 | <my-peertube-checkbox | ||
20 | inputName="unfederate" formControlName="unfederate" | ||
21 | i18n-labelText labelText="Unfederate the video (ask for its deletion from the remote instances)" | ||
22 | ></my-peertube-checkbox> | ||
23 | </div> | ||
24 | |||
18 | <div class="form-group inputs"> | 25 | <div class="form-group inputs"> |
19 | <span i18n class="action-button action-button-cancel" (click)="hide()"> | 26 | <span i18n class="action-button action-button-cancel" (click)="hide()"> |
20 | Cancel | 27 | Cancel |
diff --git a/client/src/app/videos/+video-watch/modal/video-blacklist.component.ts b/client/src/app/videos/+video-watch/modal/video-blacklist.component.ts index 2c123ebed..50a7cadd1 100644 --- a/client/src/app/videos/+video-watch/modal/video-blacklist.component.ts +++ b/client/src/app/videos/+video-watch/modal/video-blacklist.component.ts | |||
@@ -1,12 +1,11 @@ | |||
1 | import { Component, Input, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, Input, OnInit, ViewChild } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier, RedirectService } from '@app/core' |
3 | import { FormReactive, VideoBlacklistService, VideoBlacklistValidatorsService } from '../../../shared/index' | 3 | import { FormReactive, VideoBlacklistService, VideoBlacklistValidatorsService } from '../../../shared/index' |
4 | import { VideoDetails } from '../../../shared/video/video-details.model' | 4 | import { VideoDetails } from '../../../shared/video/video-details.model' |
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | 5 | import { I18n } from '@ngx-translate/i18n-polyfill' |
6 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 6 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
7 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 7 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
8 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 8 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
9 | import { RedirectService } from '@app/core' | ||
10 | 9 | ||
11 | @Component({ | 10 | @Component({ |
12 | selector: 'my-video-blacklist', | 11 | selector: 'my-video-blacklist', |
@@ -27,7 +26,7 @@ export class VideoBlacklistComponent extends FormReactive implements OnInit { | |||
27 | private modalService: NgbModal, | 26 | private modalService: NgbModal, |
28 | private videoBlacklistValidatorsService: VideoBlacklistValidatorsService, | 27 | private videoBlacklistValidatorsService: VideoBlacklistValidatorsService, |
29 | private videoBlacklistService: VideoBlacklistService, | 28 | private videoBlacklistService: VideoBlacklistService, |
30 | private notificationsService: NotificationsService, | 29 | private notifier: Notifier, |
31 | private redirectService: RedirectService, | 30 | private redirectService: RedirectService, |
32 | private i18n: I18n | 31 | private i18n: I18n |
33 | ) { | 32 | ) { |
@@ -35,9 +34,12 @@ export class VideoBlacklistComponent extends FormReactive implements OnInit { | |||
35 | } | 34 | } |
36 | 35 | ||
37 | ngOnInit () { | 36 | ngOnInit () { |
37 | const defaultValues = { unfederate: 'true' } | ||
38 | |||
38 | this.buildForm({ | 39 | this.buildForm({ |
39 | reason: this.videoBlacklistValidatorsService.VIDEO_BLACKLIST_REASON | 40 | reason: this.videoBlacklistValidatorsService.VIDEO_BLACKLIST_REASON, |
40 | }) | 41 | unfederate: null |
42 | }, defaultValues) | ||
41 | } | 43 | } |
42 | 44 | ||
43 | show () { | 45 | show () { |
@@ -51,16 +53,17 @@ export class VideoBlacklistComponent extends FormReactive implements OnInit { | |||
51 | 53 | ||
52 | blacklist () { | 54 | blacklist () { |
53 | const reason = this.form.value[ 'reason' ] || undefined | 55 | const reason = this.form.value[ 'reason' ] || undefined |
56 | const unfederate = this.video.isLocal ? this.form.value[ 'unfederate' ] : undefined | ||
54 | 57 | ||
55 | this.videoBlacklistService.blacklistVideo(this.video.id, reason) | 58 | this.videoBlacklistService.blacklistVideo(this.video.id, reason, unfederate) |
56 | .subscribe( | 59 | .subscribe( |
57 | () => { | 60 | () => { |
58 | this.notificationsService.success(this.i18n('Success'), this.i18n('Video blacklisted.')) | 61 | this.notifier.success(this.i18n('Video blacklisted.')) |
59 | this.hide() | 62 | this.hide() |
60 | this.redirectService.redirectToHomepage() | 63 | this.redirectService.redirectToHomepage() |
61 | }, | 64 | }, |
62 | 65 | ||
63 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 66 | err => this.notifier.error(err.message) |
64 | ) | 67 | ) |
65 | } | 68 | } |
66 | } | 69 | } |
diff --git a/client/src/app/videos/+video-watch/modal/video-download.component.html b/client/src/app/videos/+video-watch/modal/video-download.component.html index f46f92a17..2bb5d6d37 100644 --- a/client/src/app/videos/+video-watch/modal/video-download.component.html +++ b/client/src/app/videos/+video-watch/modal/video-download.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <ng-template #modal let-hide="close"> | 1 | <ng-template #modal let-hide="close"> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Download video</h4> | 3 | <h4 i18n class="modal-title">Download video</h4> |
4 | <span class="close" aria-hidden="true" (click)="hide()"></span> | 4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> |
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | <div class="modal-body"> | 7 | <div class="modal-body"> |
diff --git a/client/src/app/videos/+video-watch/modal/video-download.component.ts b/client/src/app/videos/+video-watch/modal/video-download.component.ts index b1b2c0623..834385771 100644 --- a/client/src/app/videos/+video-watch/modal/video-download.component.ts +++ b/client/src/app/videos/+video-watch/modal/video-download.component.ts | |||
@@ -2,7 +2,7 @@ import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core' | |||
2 | import { VideoDetails } from '../../../shared/video/video-details.model' | 2 | import { VideoDetails } from '../../../shared/video/video-details.model' |
3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | 4 | import { I18n } from '@ngx-translate/i18n-polyfill' |
5 | import { NotificationsService } from 'angular2-notifications' | 5 | import { Notifier } from '@app/core' |
6 | 6 | ||
7 | @Component({ | 7 | @Component({ |
8 | selector: 'my-video-download', | 8 | selector: 'my-video-download', |
@@ -18,7 +18,7 @@ export class VideoDownloadComponent implements OnInit { | |||
18 | resolutionId: number | string = -1 | 18 | resolutionId: number | string = -1 |
19 | 19 | ||
20 | constructor ( | 20 | constructor ( |
21 | private notificationsService: NotificationsService, | 21 | private notifier: Notifier, |
22 | private modalService: NgbModal, | 22 | private modalService: NgbModal, |
23 | private i18n: I18n | 23 | private i18n: I18n |
24 | ) { } | 24 | ) { } |
@@ -63,6 +63,6 @@ export class VideoDownloadComponent implements OnInit { | |||
63 | } | 63 | } |
64 | 64 | ||
65 | activateCopiedMessage () { | 65 | activateCopiedMessage () { |
66 | this.notificationsService.success(this.i18n('Success'), this.i18n('Copied')) | 66 | this.notifier.success(this.i18n('Copied')) |
67 | } | 67 | } |
68 | } | 68 | } |
diff --git a/client/src/app/videos/+video-watch/modal/video-report.component.html b/client/src/app/videos/+video-watch/modal/video-report.component.html index 8d9a49276..b9434da26 100644 --- a/client/src/app/videos/+video-watch/modal/video-report.component.html +++ b/client/src/app/videos/+video-watch/modal/video-report.component.html | |||
@@ -1,11 +1,16 @@ | |||
1 | <ng-template #modal> | 1 | <ng-template #modal> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Report video</h4> | 3 | <h4 i18n class="modal-title">Report video</h4> |
4 | <span class="close" aria-label="Close" role="button" (click)="hide()"></span> | 4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> |
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | <div class="modal-body"> | 7 | <div class="modal-body"> |
8 | 8 | ||
9 | <div i18n class="information"> | ||
10 | Your report will be sent to moderators of {{ currentHost }}. | ||
11 | <ng-container *ngIf="isRemoteVideo()"> It will be forwarded to origin instance {{ originHost }} too.</ng-container> | ||
12 | </div> | ||
13 | |||
9 | <form novalidate [formGroup]="form" (ngSubmit)="report()"> | 14 | <form novalidate [formGroup]="form" (ngSubmit)="report()"> |
10 | <div class="form-group"> | 15 | <div class="form-group"> |
11 | <textarea i18n-placeholder placeholder="Reason..." formControlName="reason" [ngClass]="{ 'input-error': formErrors['reason'] }"> | 16 | <textarea i18n-placeholder placeholder="Reason..." formControlName="reason" [ngClass]="{ 'input-error': formErrors['reason'] }"> |
diff --git a/client/src/app/videos/+video-watch/modal/video-report.component.scss b/client/src/app/videos/+video-watch/modal/video-report.component.scss index afcdb9a16..4713660a2 100644 --- a/client/src/app/videos/+video-watch/modal/video-report.component.scss +++ b/client/src/app/videos/+video-watch/modal/video-report.component.scss | |||
@@ -1,6 +1,10 @@ | |||
1 | @import 'variables'; | 1 | @import 'variables'; |
2 | @import 'mixins'; | 2 | @import 'mixins'; |
3 | 3 | ||
4 | .information { | ||
5 | margin-bottom: 20px; | ||
6 | } | ||
7 | |||
4 | textarea { | 8 | textarea { |
5 | @include peertube-textarea(100%, 100px); | 9 | @include peertube-textarea(100%, 100px); |
6 | } | 10 | } |
diff --git a/client/src/app/videos/+video-watch/modal/video-report.component.ts b/client/src/app/videos/+video-watch/modal/video-report.component.ts index 297afb19f..911f3b447 100644 --- a/client/src/app/videos/+video-watch/modal/video-report.component.ts +++ b/client/src/app/videos/+video-watch/modal/video-report.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, Input, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, Input, OnInit, ViewChild } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { FormReactive, VideoAbuseService } from '../../../shared/index' | 3 | import { FormReactive, VideoAbuseService } from '../../../shared/index' |
4 | import { VideoDetails } from '../../../shared/video/video-details.model' | 4 | import { VideoDetails } from '../../../shared/video/video-details.model' |
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | 5 | import { I18n } from '@ngx-translate/i18n-polyfill' |
@@ -27,12 +27,24 @@ export class VideoReportComponent extends FormReactive implements OnInit { | |||
27 | private modalService: NgbModal, | 27 | private modalService: NgbModal, |
28 | private videoAbuseValidatorsService: VideoAbuseValidatorsService, | 28 | private videoAbuseValidatorsService: VideoAbuseValidatorsService, |
29 | private videoAbuseService: VideoAbuseService, | 29 | private videoAbuseService: VideoAbuseService, |
30 | private notificationsService: NotificationsService, | 30 | private notifier: Notifier, |
31 | private i18n: I18n | 31 | private i18n: I18n |
32 | ) { | 32 | ) { |
33 | super() | 33 | super() |
34 | } | 34 | } |
35 | 35 | ||
36 | get currentHost () { | ||
37 | return window.location.host | ||
38 | } | ||
39 | |||
40 | get originHost () { | ||
41 | if (this.isRemoteVideo()) { | ||
42 | return this.video.account.host | ||
43 | } | ||
44 | |||
45 | return '' | ||
46 | } | ||
47 | |||
36 | ngOnInit () { | 48 | ngOnInit () { |
37 | this.buildForm({ | 49 | this.buildForm({ |
38 | reason: this.videoAbuseValidatorsService.VIDEO_ABUSE_REASON | 50 | reason: this.videoAbuseValidatorsService.VIDEO_ABUSE_REASON |
@@ -54,11 +66,15 @@ export class VideoReportComponent extends FormReactive implements OnInit { | |||
54 | this.videoAbuseService.reportVideo(this.video.id, reason) | 66 | this.videoAbuseService.reportVideo(this.video.id, reason) |
55 | .subscribe( | 67 | .subscribe( |
56 | () => { | 68 | () => { |
57 | this.notificationsService.success(this.i18n('Success'), this.i18n('Video reported.')) | 69 | this.notifier.success(this.i18n('Video reported.')) |
58 | this.hide() | 70 | this.hide() |
59 | }, | 71 | }, |
60 | 72 | ||
61 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 73 | err => this.notifier.error(err.message) |
62 | ) | 74 | ) |
63 | } | 75 | } |
76 | |||
77 | isRemoteVideo () { | ||
78 | return !this.video.isLocal | ||
79 | } | ||
64 | } | 80 | } |
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 index 301f67f2d..9f3c37fe8 100644 --- a/client/src/app/videos/+video-watch/modal/video-share.component.html +++ b/client/src/app/videos/+video-watch/modal/video-share.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <ng-template #modal let-hide="close"> | 1 | <ng-template #modal let-hide="close"> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Share</h4> | 3 | <h4 i18n class="modal-title">Share</h4> |
4 | <span class="close" aria-hidden="true" (click)="hide()"></span> | 4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> |
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | <div class="modal-body"> | 7 | <div class="modal-body"> |
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 index 17e2b31e1..c6205e355 100644 --- a/client/src/app/videos/+video-watch/modal/video-share.component.ts +++ b/client/src/app/videos/+video-watch/modal/video-share.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, ElementRef, Input, ViewChild } from '@angular/core' | 1 | import { Component, ElementRef, Input, ViewChild } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { VideoDetails } from '../../../shared/video/video-details.model' | 3 | import { VideoDetails } from '../../../shared/video/video-details.model' |
4 | import { buildVideoEmbed, buildVideoLink } from '../../../../assets/player/utils' | 4 | import { buildVideoEmbed, buildVideoLink } from '../../../../assets/player/utils' |
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | 5 | import { I18n } from '@ngx-translate/i18n-polyfill' |
@@ -23,7 +23,7 @@ export class VideoShareComponent { | |||
23 | 23 | ||
24 | constructor ( | 24 | constructor ( |
25 | private modalService: NgbModal, | 25 | private modalService: NgbModal, |
26 | private notificationsService: NotificationsService, | 26 | private notifier: Notifier, |
27 | private i18n: I18n | 27 | private i18n: I18n |
28 | ) { } | 28 | ) { } |
29 | 29 | ||
@@ -49,7 +49,7 @@ export class VideoShareComponent { | |||
49 | } | 49 | } |
50 | 50 | ||
51 | activateCopiedMessage () { | 51 | activateCopiedMessage () { |
52 | this.notificationsService.success(this.i18n('Success'), this.i18n('Copied')) | 52 | this.notifier.success(this.i18n('Copied')) |
53 | } | 53 | } |
54 | 54 | ||
55 | getStartCheckboxLabel () { | 55 | getStartCheckboxLabel () { |
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 index 00c304709..2a05224a8 100644 --- a/client/src/app/videos/+video-watch/modal/video-support.component.html +++ b/client/src/app/videos/+video-watch/modal/video-support.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <ng-template #modal let-hide="close"> | 1 | <ng-template #modal let-hide="close"> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Support</h4> | 3 | <h4 i18n class="modal-title">Support</h4> |
4 | <span class="close" aria-label="Close" role="button" (click)="hide()"></span> | 4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> |
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | <div class="modal-body" [innerHTML]="videoHTMLSupport"></div> | 7 | <div class="modal-body" [innerHTML]="videoHTMLSupport"></div> |
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 index 154002120..deb8fbc67 100644 --- a/client/src/app/videos/+video-watch/modal/video-support.component.ts +++ b/client/src/app/videos/+video-watch/modal/video-support.component.ts | |||
@@ -1,8 +1,7 @@ | |||
1 | import { Component, Input, ViewChild } from '@angular/core' | 1 | import { Component, Input, ViewChild } from '@angular/core' |
2 | import { MarkdownService } from '@app/videos/shared' | ||
3 | |||
4 | import { VideoDetails } from '../../../shared/video/video-details.model' | 2 | import { VideoDetails } from '../../../shared/video/video-details.model' |
5 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
4 | import { MarkdownService } from '@app/shared/renderer' | ||
6 | 5 | ||
7 | @Component({ | 6 | @Component({ |
8 | selector: 'my-video-support', | 7 | selector: 'my-video-support', |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html index ba04d638f..1875230d8 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.html +++ b/client/src/app/videos/+video-watch/video-watch.component.html | |||
@@ -53,55 +53,57 @@ | |||
53 | <div | 53 | <div |
54 | *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()" | 54 | *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()" |
55 | class="action-button action-button-like" role="button" [attr.aria-pressed]="userRating === 'like'" | 55 | class="action-button action-button-like" role="button" [attr.aria-pressed]="userRating === 'like'" |
56 | i18n-title title="Like this video" | ||
56 | > | 57 | > |
57 | <span class="icon icon-like" i18n-title title="Like this video" ></span> | 58 | <my-global-icon iconName="like"></my-global-icon> |
58 | </div> | 59 | </div> |
59 | 60 | ||
60 | <div | 61 | <div |
61 | *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()" | 62 | *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()" |
62 | class="action-button action-button-dislike" role="button" [attr.aria-pressed]="userRating === 'dislike'" | 63 | class="action-button action-button-dislike" role="button" [attr.aria-pressed]="userRating === 'dislike'" |
64 | i18n-title title="Dislike this video" | ||
63 | > | 65 | > |
64 | <span class="icon icon-dislike" i18n-title title="Dislike this video"></span> | 66 | <my-global-icon iconName="dislike"></my-global-icon> |
65 | </div> | 67 | </div> |
66 | 68 | ||
67 | <div *ngIf="video.support" (click)="showSupportModal()" class="action-button action-button-support"> | 69 | <div *ngIf="video.support" (click)="showSupportModal()" class="action-button action-button-support"> |
68 | <span class="icon icon-support"></span> | 70 | <my-global-icon iconName="heart"></my-global-icon> |
69 | <span class="icon-text" i18n>Support</span> | 71 | <span class="icon-text" i18n>Support</span> |
70 | </div> | 72 | </div> |
71 | 73 | ||
72 | <div (click)="showShareModal()" class="action-button action-button-share" role="button"> | 74 | <div (click)="showShareModal()" class="action-button action-button-share" role="button"> |
73 | <span class="icon icon-share"></span> | 75 | <my-global-icon iconName="share"></my-global-icon> |
74 | <span class="icon-text" i18n>Share</span> | 76 | <span class="icon-text" i18n>Share</span> |
75 | </div> | 77 | </div> |
76 | 78 | ||
77 | <div class="action-more" ngbDropdown placement="top" role="button"> | 79 | <div class="action-more" ngbDropdown placement="top" role="button"> |
78 | <div class="action-button" ngbDropdownToggle role="button"> | 80 | <div class="action-button" ngbDropdownToggle role="button"> |
79 | <span class="icon icon-more"></span> | 81 | <my-global-icon class="more-icon" iconName="more"></my-global-icon> |
80 | </div> | 82 | </div> |
81 | 83 | ||
82 | <div ngbDropdownMenu> | 84 | <div ngbDropdownMenu> |
83 | <a *ngIf="isVideoDownloadable()" class="dropdown-item" i18n-title title="Download the video" href="#" (click)="showDownloadModal($event)"> | 85 | <a *ngIf="isVideoDownloadable()" class="dropdown-item" i18n-title title="Download the video" href="#" (click)="showDownloadModal($event)"> |
84 | <span class="icon icon-download"></span> <ng-container i18n>Download</ng-container> | 86 | <my-global-icon iconName="download"></my-global-icon> <ng-container i18n>Download</ng-container> |
85 | </a> | 87 | </a> |
86 | 88 | ||
87 | <a *ngIf="isUserLoggedIn()" class="dropdown-item" i18n-title title="Report this video" href="#" (click)="showReportModal($event)"> | 89 | <a *ngIf="isUserLoggedIn()" class="dropdown-item" i18n-title title="Report this video" href="#" (click)="showReportModal($event)"> |
88 | <span class="icon icon-alert"></span> <ng-container i18n>Report</ng-container> | 90 | <my-global-icon iconName="alert"></my-global-icon> <ng-container i18n>Report</ng-container> |
89 | </a> | 91 | </a> |
90 | 92 | ||
91 | <a *ngIf="isVideoUpdatable()" class="dropdown-item" i18n-title title="Update this video" href="#" [routerLink]="[ '/videos/update', video.uuid ]"> | 93 | <a *ngIf="isVideoUpdatable()" class="dropdown-item" i18n-title title="Update this video" href="#" [routerLink]="[ '/videos/update', video.uuid ]"> |
92 | <span class="icon icon-edit"></span> <ng-container i18n>Update</ng-container> | 94 | <my-global-icon iconName="edit"></my-global-icon> <ng-container i18n>Update</ng-container> |
93 | </a> | 95 | </a> |
94 | 96 | ||
95 | <a *ngIf="isVideoBlacklistable()" class="dropdown-item" i18n-title title="Blacklist this video" href="#" (click)="showBlacklistModal($event)"> | 97 | <a *ngIf="isVideoBlacklistable()" class="dropdown-item" i18n-title title="Blacklist this video" href="#" (click)="showBlacklistModal($event)"> |
96 | <span class="icon icon-blacklist"></span> <ng-container i18n>Blacklist</ng-container> | 98 | <my-global-icon iconName="no"></my-global-icon> <ng-container i18n>Blacklist</ng-container> |
97 | </a> | 99 | </a> |
98 | 100 | ||
99 | <a *ngIf="isVideoUnblacklistable()" class="dropdown-item" i18n-title title="Unblacklist this video" href="#" (click)="unblacklistVideo($event)"> | 101 | <a *ngIf="isVideoUnblacklistable()" class="dropdown-item" i18n-title title="Unblacklist this video" href="#" (click)="unblacklistVideo($event)"> |
100 | <span class="icon icon-unblacklist"></span> <ng-container i18n>Unblacklist</ng-container> | 102 | <my-global-icon iconName="undo"></my-global-icon> <ng-container i18n>Unblacklist</ng-container> |
101 | </a> | 103 | </a> |
102 | 104 | ||
103 | <a *ngIf="isVideoRemovable()" class="dropdown-item" i18n-title title="Delete this video" href="#" (click)="removeVideo($event)"> | 105 | <a *ngIf="isVideoRemovable()" class="dropdown-item" i18n-title title="Delete this video" href="#" (click)="removeVideo($event)"> |
104 | <span class="icon icon-delete"></span> <ng-container i18n>Delete</ng-container> | 106 | <my-global-icon iconName="delete"></my-global-icon> <ng-container i18n>Delete</ng-container> |
105 | </a> | 107 | </a> |
106 | </div> | 108 | </div> |
107 | </div> | 109 | </div> |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss index f31e4694a..b03ed197d 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.scss +++ b/client/src/app/videos/+video-watch/video-watch.component.scss | |||
@@ -162,7 +162,7 @@ $other-videos-width: 260px; | |||
162 | } | 162 | } |
163 | } | 163 | } |
164 | 164 | ||
165 | my-video-feed { | 165 | my-feed { |
166 | margin-left: 5px; | 166 | margin-left: 5px; |
167 | margin-top: 1px; | 167 | margin-top: 1px; |
168 | } | 168 | } |
@@ -183,6 +183,8 @@ $other-videos-width: 260px; | |||
183 | .action-button { | 183 | .action-button { |
184 | @include peertube-button; | 184 | @include peertube-button; |
185 | @include grey-button; | 185 | @include grey-button; |
186 | @include button-with-icon(21px, 0, -1px); | ||
187 | @include apply-svg-color($grey-foreground-color); | ||
186 | 188 | ||
187 | font-size: 15px; | 189 | font-size: 15px; |
188 | font-weight: $font-semibold; | 190 | font-weight: $font-semibold; |
@@ -194,53 +196,25 @@ $other-videos-width: 260px; | |||
194 | display: none; | 196 | display: none; |
195 | } | 197 | } |
196 | 198 | ||
197 | .icon { | ||
198 | @include icon(21px); | ||
199 | |||
200 | position: relative; | ||
201 | top: -2px; | ||
202 | |||
203 | &.icon-like { | ||
204 | background-image: url('../../../assets/images/video/like-grey.svg'); | ||
205 | } | ||
206 | |||
207 | &.icon-dislike { | ||
208 | background-image: url('../../../assets/images/video/dislike-grey.svg'); | ||
209 | } | ||
210 | |||
211 | &.icon-support { | ||
212 | background-image: url('../../../assets/images/video/heart.svg'); | ||
213 | } | ||
214 | |||
215 | &.icon-share { | ||
216 | background-image: url('../../../assets/images/video/share.svg'); | ||
217 | } | ||
218 | |||
219 | &.icon-more { | ||
220 | background-image: url('../../../assets/images/video/more.svg'); | ||
221 | top: -1px; | ||
222 | } | ||
223 | } | ||
224 | |||
225 | .icon-text { | ||
226 | margin-left: 3px; | ||
227 | } | ||
228 | |||
229 | &.action-button-like.activated { | 199 | &.action-button-like.activated { |
230 | background-color: $green; | 200 | background-color: $green; |
231 | 201 | ||
232 | .icon-like { | 202 | my-global-icon { |
233 | background-image: url('../../../assets/images/video/like-white.svg'); | 203 | @include apply-svg-color(#fff); |
234 | } | 204 | } |
235 | } | 205 | } |
236 | 206 | ||
237 | &.action-button-dislike.activated { | 207 | &.action-button-dislike.activated { |
238 | background-color: $red; | 208 | background-color: $red; |
239 | 209 | ||
240 | .icon-dislike { | 210 | my-global-icon { |
241 | background-image: url('../../../assets/images/video/dislike-white.svg'); | 211 | @include apply-svg-color(#fff); |
242 | } | 212 | } |
243 | } | 213 | } |
214 | |||
215 | .icon-text { | ||
216 | margin-left: 3px; | ||
217 | } | ||
244 | } | 218 | } |
245 | 219 | ||
246 | .action-more { | 220 | .action-more { |
@@ -249,36 +223,12 @@ $other-videos-width: 260px; | |||
249 | .dropdown-menu .dropdown-item { | 223 | .dropdown-menu .dropdown-item { |
250 | padding: 6px 24px; | 224 | padding: 6px 24px; |
251 | 225 | ||
252 | .icon { | 226 | my-global-icon { |
253 | @include icon(24px); | 227 | width: 24px; |
254 | 228 | ||
255 | margin-right: 10px; | 229 | margin-right: 10px; |
256 | position: relative; | 230 | position: relative; |
257 | top: -1px; | 231 | top: -2px; |
258 | |||
259 | &.icon-download { | ||
260 | background-image: url('../../../assets/images/video/download-black.svg'); | ||
261 | } | ||
262 | |||
263 | &.icon-edit { | ||
264 | background-image: url('../../../assets/images/global/edit-black.svg'); | ||
265 | } | ||
266 | |||
267 | &.icon-alert { | ||
268 | background-image: url('../../../assets/images/video/alert.svg'); | ||
269 | } | ||
270 | |||
271 | &.icon-blacklist { | ||
272 | background-image: url('../../../assets/images/video/blacklist.svg'); | ||
273 | } | ||
274 | |||
275 | &.icon-unblacklist { | ||
276 | background-image: url('../../../assets/images/global/undo.svg'); | ||
277 | } | ||
278 | |||
279 | &.icon-delete { | ||
280 | background-image: url('../../../assets/images/global/delete-black.svg'); | ||
281 | } | ||
282 | } | 232 | } |
283 | } | 233 | } |
284 | } | 234 | } |
@@ -320,7 +270,7 @@ $other-videos-width: 260px; | |||
320 | .video-info-description-more { | 270 | .video-info-description-more { |
321 | cursor: pointer; | 271 | cursor: pointer; |
322 | font-weight: $font-semibold; | 272 | font-weight: $font-semibold; |
323 | color: #585858; | 273 | color: $grey-foreground-color; |
324 | font-size: 14px; | 274 | font-size: 14px; |
325 | 275 | ||
326 | .glyphicon { | 276 | .glyphicon { |
@@ -339,7 +289,7 @@ $other-videos-width: 260px; | |||
339 | min-width: 91px; | 289 | min-width: 91px; |
340 | padding-right: 5px; | 290 | padding-right: 5px; |
341 | display: inline-block; | 291 | display: inline-block; |
342 | color: #585858; | 292 | color: $grey-foreground-color; |
343 | font-weight: $font-bold; | 293 | font-weight: $font-bold; |
344 | } | 294 | } |
345 | 295 | ||
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts index a7fd45695..4dbfa41e5 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts | |||
@@ -5,30 +5,29 @@ import { RedirectService } from '@app/core/routing/redirect.service' | |||
5 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' | 5 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' |
6 | import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' | 6 | import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' |
7 | import { MetaService } from '@ngx-meta/core' | 7 | import { MetaService } from '@ngx-meta/core' |
8 | import { NotificationsService } from 'angular2-notifications' | 8 | import { Notifier, ServerService } from '@app/core' |
9 | import { forkJoin, Subscription } from 'rxjs' | 9 | import { forkJoin, Subscription } from 'rxjs' |
10 | import * as videojs from 'video.js' | ||
11 | import 'videojs-hotkeys' | ||
12 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' | 10 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' |
13 | import * as WebTorrent from 'webtorrent' | 11 | import { UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '../../../../../shared' |
14 | import { UserVideoRateType, VideoCaption, VideoPrivacy, VideoRateType, VideoState } from '../../../../../shared' | ||
15 | import '../../../assets/player/peertube-videojs-plugin' | ||
16 | import { AuthService, ConfirmService } from '../../core' | 12 | import { AuthService, ConfirmService } from '../../core' |
17 | import { RestExtractor, VideoBlacklistService } from '../../shared' | 13 | import { RestExtractor, VideoBlacklistService } from '../../shared' |
18 | import { VideoDetails } from '../../shared/video/video-details.model' | 14 | import { VideoDetails } from '../../shared/video/video-details.model' |
19 | import { VideoService } from '../../shared/video/video.service' | 15 | import { VideoService } from '../../shared/video/video.service' |
20 | import { MarkdownService } from '../shared' | ||
21 | import { VideoDownloadComponent } from './modal/video-download.component' | 16 | import { VideoDownloadComponent } from './modal/video-download.component' |
22 | import { VideoReportComponent } from './modal/video-report.component' | 17 | import { VideoReportComponent } from './modal/video-report.component' |
23 | import { VideoShareComponent } from './modal/video-share.component' | 18 | import { VideoShareComponent } from './modal/video-share.component' |
24 | import { VideoBlacklistComponent } from './modal/video-blacklist.component' | 19 | import { VideoBlacklistComponent } from './modal/video-blacklist.component' |
25 | import { SubscribeButtonComponent } from '@app/shared/user-subscription/subscribe-button.component' | 20 | import { SubscribeButtonComponent } from '@app/shared/user-subscription/subscribe-button.component' |
26 | import { addContextMenu, getVideojsOptions, loadLocaleInVideoJS } from '../../../assets/player/peertube-player' | ||
27 | import { ServerService } from '@app/core' | ||
28 | import { I18n } from '@ngx-translate/i18n-polyfill' | 21 | import { I18n } from '@ngx-translate/i18n-polyfill' |
29 | import { environment } from '../../../environments/environment' | 22 | import { environment } from '../../../environments/environment' |
30 | import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' | ||
31 | import { VideoCaptionService } from '@app/shared/video-caption' | 23 | import { VideoCaptionService } from '@app/shared/video-caption' |
24 | import { MarkdownService } from '@app/shared/renderer' | ||
25 | import { | ||
26 | P2PMediaLoaderOptions, | ||
27 | PeertubePlayerManager, | ||
28 | PeertubePlayerManagerOptions, | ||
29 | PlayerMode | ||
30 | } from '../../../assets/player/peertube-player-manager' | ||
32 | 31 | ||
33 | @Component({ | 32 | @Component({ |
34 | selector: 'my-video-watch', | 33 | selector: 'my-video-watch', |
@@ -45,7 +44,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
45 | @ViewChild('videoBlacklistModal') videoBlacklistModal: VideoBlacklistComponent | 44 | @ViewChild('videoBlacklistModal') videoBlacklistModal: VideoBlacklistComponent |
46 | @ViewChild('subscribeButton') subscribeButton: SubscribeButtonComponent | 45 | @ViewChild('subscribeButton') subscribeButton: SubscribeButtonComponent |
47 | 46 | ||
48 | player: videojs.Player | 47 | player: any |
49 | playerElement: HTMLVideoElement | 48 | playerElement: HTMLVideoElement |
50 | userRating: UserVideoRateType = null | 49 | userRating: UserVideoRateType = null |
51 | video: VideoDetails = null | 50 | video: VideoDetails = null |
@@ -60,7 +59,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
60 | remoteServerDown = false | 59 | remoteServerDown = false |
61 | hotkeys: Hotkey[] | 60 | hotkeys: Hotkey[] |
62 | 61 | ||
63 | private videojsLocaleLoaded = false | ||
64 | private paramsSub: Subscription | 62 | private paramsSub: Subscription |
65 | 63 | ||
66 | constructor ( | 64 | constructor ( |
@@ -75,7 +73,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
75 | private authService: AuthService, | 73 | private authService: AuthService, |
76 | private serverService: ServerService, | 74 | private serverService: ServerService, |
77 | private restExtractor: RestExtractor, | 75 | private restExtractor: RestExtractor, |
78 | private notificationsService: NotificationsService, | 76 | private notifier: Notifier, |
79 | private markdownService: MarkdownService, | 77 | private markdownService: MarkdownService, |
80 | private zone: NgZone, | 78 | private zone: NgZone, |
81 | private redirectService: RedirectService, | 79 | private redirectService: RedirectService, |
@@ -91,7 +89,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
91 | 89 | ||
92 | ngOnInit () { | 90 | ngOnInit () { |
93 | if ( | 91 | if ( |
94 | WebTorrent.WEBRTC_SUPPORT === false || | 92 | !!((window as any).RTCPeerConnection || (window as any).mozRTCPeerConnection || (window as any).webkitRTCPeerConnection) === false || |
95 | peertubeLocalStorage.getItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY) === 'true' | 93 | peertubeLocalStorage.getItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY) === 'true' |
96 | ) { | 94 | ) { |
97 | this.hasAlreadyAcceptedPrivacyConcern = true | 95 | this.hasAlreadyAcceptedPrivacyConcern = true |
@@ -112,11 +110,14 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
112 | ) | 110 | ) |
113 | .pipe( | 111 | .pipe( |
114 | // If 401, the video is private or blacklisted so redirect to 404 | 112 | // If 401, the video is private or blacklisted so redirect to 404 |
115 | catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 401, 404 ])) | 113 | catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 401, 403, 404 ])) |
116 | ) | 114 | ) |
117 | .subscribe(([ video, captionsResult ]) => { | 115 | .subscribe(([ video, captionsResult ]) => { |
118 | const startTime = this.route.snapshot.queryParams.start | 116 | const startTime = this.route.snapshot.queryParams.start |
119 | this.onVideoFetched(video, captionsResult.data, startTime) | 117 | const subtitle = this.route.snapshot.queryParams.subtitle |
118 | const playerMode = this.route.snapshot.queryParams.mode | ||
119 | |||
120 | this.onVideoFetched(video, captionsResult.data, { startTime, subtitle, playerMode }) | ||
120 | .catch(err => this.handleError(err)) | 121 | .catch(err => this.handleError(err)) |
121 | }) | 122 | }) |
122 | }) | 123 | }) |
@@ -201,7 +202,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
201 | 202 | ||
202 | error => { | 203 | error => { |
203 | this.descriptionLoading = false | 204 | this.descriptionLoading = false |
204 | this.notificationsService.error(this.i18n('Error'), error.message) | 205 | this.notifier.error(error.message) |
205 | } | 206 | } |
206 | ) | 207 | ) |
207 | } | 208 | } |
@@ -243,16 +244,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
243 | 244 | ||
244 | this.videoBlacklistService.removeVideoFromBlacklist(this.video.id).subscribe( | 245 | this.videoBlacklistService.removeVideoFromBlacklist(this.video.id).subscribe( |
245 | () => { | 246 | () => { |
246 | this.notificationsService.success( | 247 | this.notifier.success(this.i18n('Video {{name}} removed from the blacklist.', { name: this.video.name })) |
247 | this.i18n('Success'), | ||
248 | this.i18n('Video {{name}} removed from the blacklist.', { name: this.video.name }) | ||
249 | ) | ||
250 | 248 | ||
251 | this.video.blacklisted = false | 249 | this.video.blacklisted = false |
252 | this.video.blacklistedReason = null | 250 | this.video.blacklistedReason = null |
253 | }, | 251 | }, |
254 | 252 | ||
255 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 253 | err => this.notifier.error(err.message) |
256 | ) | 254 | ) |
257 | } | 255 | } |
258 | 256 | ||
@@ -290,17 +288,14 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
290 | 288 | ||
291 | this.videoService.removeVideo(this.video.id) | 289 | this.videoService.removeVideo(this.video.id) |
292 | .subscribe( | 290 | .subscribe( |
293 | status => { | 291 | () => { |
294 | this.notificationsService.success( | 292 | this.notifier.success(this.i18n('Video {{videoName}} deleted.', { videoName: this.video.name })) |
295 | this.i18n('Success'), | ||
296 | this.i18n('Video {{videoName}} deleted.', { videoName: this.video.name }) | ||
297 | ) | ||
298 | 293 | ||
299 | // Go back to the video-list. | 294 | // Go back to the video-list. |
300 | this.redirectService.redirectToHomepage() | 295 | this.redirectService.redirectToHomepage() |
301 | }, | 296 | }, |
302 | 297 | ||
303 | error => this.notificationsService.error(this.i18n('Error'), error.message) | 298 | error => this.notifier.error(error.message) |
304 | ) | 299 | ) |
305 | } | 300 | } |
306 | 301 | ||
@@ -354,7 +349,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
354 | return | 349 | return |
355 | } | 350 | } |
356 | 351 | ||
357 | this.notificationsService.error(this.i18n('Error'), errorMessage) | 352 | this.notifier.error(errorMessage) |
358 | } | 353 | } |
359 | 354 | ||
360 | private checkUserRating () { | 355 | private checkUserRating () { |
@@ -369,11 +364,15 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
369 | } | 364 | } |
370 | }, | 365 | }, |
371 | 366 | ||
372 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 367 | err => this.notifier.error(err.message) |
373 | ) | 368 | ) |
374 | } | 369 | } |
375 | 370 | ||
376 | private async onVideoFetched (video: VideoDetails, videoCaptions: VideoCaption[], startTimeFromUrl: number) { | 371 | private async onVideoFetched ( |
372 | video: VideoDetails, | ||
373 | videoCaptions: VideoCaption[], | ||
374 | urlOptions: { startTime?: number, subtitle?: string, playerMode?: string } | ||
375 | ) { | ||
377 | this.video = video | 376 | this.video = video |
378 | 377 | ||
379 | // Re init attributes | 378 | // Re init attributes |
@@ -381,8 +380,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
381 | this.completeDescriptionShown = false | 380 | this.completeDescriptionShown = false |
382 | this.remoteServerDown = false | 381 | this.remoteServerDown = false |
383 | 382 | ||
384 | let startTime = startTimeFromUrl || (this.video.userHistory ? this.video.userHistory.currentTime : 0) | 383 | let startTime = urlOptions.startTime || (this.video.userHistory ? this.video.userHistory.currentTime : 0) |
385 | // Don't start the video if we are at the end | 384 | // If we are at the end of the video, reset the timer |
386 | if (this.video.duration - startTime <= 1) startTime = 0 | 385 | if (this.video.duration - startTime <= 1) startTime = 0 |
387 | 386 | ||
388 | if (this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())) { | 387 | if (this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())) { |
@@ -409,40 +408,64 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
409 | src: environment.apiUrl + c.captionPath | 408 | src: environment.apiUrl + c.captionPath |
410 | })) | 409 | })) |
411 | 410 | ||
412 | const videojsOptions = getVideojsOptions({ | 411 | const options: PeertubePlayerManagerOptions = { |
413 | autoplay: this.isAutoplay(), | 412 | common: { |
414 | inactivityTimeout: 2500, | 413 | autoplay: this.isAutoplay(), |
415 | videoFiles: this.video.files, | 414 | |
416 | videoCaptions: playerCaptions, | 415 | playerElement: this.playerElement, |
417 | playerElement: this.playerElement, | 416 | onPlayerElementChange: (element: HTMLVideoElement) => this.playerElement = element, |
418 | videoViewUrl: this.video.privacy.id !== VideoPrivacy.PRIVATE ? this.videoService.getVideoViewUrl(this.video.uuid) : null, | 417 | |
419 | videoDuration: this.video.duration, | 418 | videoDuration: this.video.duration, |
420 | enableHotkeys: true, | 419 | enableHotkeys: true, |
421 | peertubeLink: false, | 420 | inactivityTimeout: 2500, |
422 | poster: this.video.previewUrl, | 421 | poster: this.video.previewUrl, |
423 | startTime, | 422 | startTime, |
424 | theaterMode: true, | 423 | |
425 | language: this.localeId, | 424 | theaterMode: true, |
426 | 425 | captions: videoCaptions.length !== 0, | |
427 | userWatching: this.user ? { | 426 | peertubeLink: false, |
428 | url: this.videoService.getUserWatchingVideoUrl(this.video.uuid), | 427 | |
429 | authorizationHeader: this.authService.getRequestHeaderValue() | 428 | videoViewUrl: this.video.privacy.id !== VideoPrivacy.PRIVATE ? this.videoService.getVideoViewUrl(this.video.uuid) : null, |
430 | } : undefined | 429 | embedUrl: this.video.embedUrl, |
431 | }) | 430 | |
431 | language: this.localeId, | ||
432 | |||
433 | subtitle: urlOptions.subtitle, | ||
434 | |||
435 | userWatching: this.user && this.user.videosHistoryEnabled === true ? { | ||
436 | url: this.videoService.getUserWatchingVideoUrl(this.video.uuid), | ||
437 | authorizationHeader: this.authService.getRequestHeaderValue() | ||
438 | } : undefined, | ||
432 | 439 | ||
433 | if (this.videojsLocaleLoaded === false) { | 440 | serverUrl: environment.apiUrl, |
434 | await loadLocaleInVideoJS(environment.apiUrl, videojs, isOnDevLocale() ? getDevLocale() : this.localeId) | 441 | |
435 | this.videojsLocaleLoaded = true | 442 | videoCaptions: playerCaptions |
443 | }, | ||
444 | |||
445 | webtorrent: { | ||
446 | videoFiles: this.video.files | ||
447 | } | ||
436 | } | 448 | } |
437 | 449 | ||
438 | const self = this | 450 | const mode: PlayerMode = urlOptions.playerMode === 'p2p-media-loader' ? 'p2p-media-loader' : 'webtorrent' |
439 | this.zone.runOutsideAngular(async () => { | 451 | |
440 | videojs(this.playerElement, videojsOptions, function () { | 452 | if (mode === 'p2p-media-loader') { |
441 | self.player = this | 453 | const hlsPlaylist = this.video.getHlsPlaylist() |
442 | this.on('customError', (event, data) => self.handleError(data.err)) | ||
443 | 454 | ||
444 | addContextMenu(self.player, self.video.embedUrl) | 455 | const p2pMediaLoader = { |
445 | }) | 456 | playlistUrl: hlsPlaylist.playlistUrl, |
457 | segmentsSha256Url: hlsPlaylist.segmentsSha256Url, | ||
458 | redundancyBaseUrls: hlsPlaylist.redundancies.map(r => r.baseUrl), | ||
459 | trackerAnnounce: this.video.trackerUrls, | ||
460 | videoFiles: this.video.files | ||
461 | } as P2PMediaLoaderOptions | ||
462 | |||
463 | Object.assign(options, { p2pMediaLoader }) | ||
464 | } | ||
465 | |||
466 | this.zone.runOutsideAngular(async () => { | ||
467 | this.player = await PeertubePlayerManager.initialize(mode, options) | ||
468 | this.player.on('customError', ({ err }: { err: any }) => this.handleError(err)) | ||
446 | }) | 469 | }) |
447 | 470 | ||
448 | this.setVideoDescriptionHTML() | 471 | this.setVideoDescriptionHTML() |
@@ -452,7 +475,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
452 | this.checkUserRating() | 475 | this.checkUserRating() |
453 | } | 476 | } |
454 | 477 | ||
455 | private setRating (nextRating) { | 478 | private setRating (nextRating: UserVideoRateType) { |
456 | let method | 479 | let method |
457 | switch (nextRating) { | 480 | switch (nextRating) { |
458 | case 'like': | 481 | case 'like': |
@@ -474,11 +497,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
474 | this.userRating = nextRating | 497 | this.userRating = nextRating |
475 | }, | 498 | }, |
476 | 499 | ||
477 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 500 | (err: { message: string }) => this.notifier.error(err.message) |
478 | ) | 501 | ) |
479 | } | 502 | } |
480 | 503 | ||
481 | private updateVideoRating (oldRating: UserVideoRateType, newRating: VideoRateType) { | 504 | private updateVideoRating (oldRating: UserVideoRateType, newRating: UserVideoRateType) { |
482 | let likesToIncrement = 0 | 505 | let likesToIncrement = 0 |
483 | let dislikesToIncrement = 0 | 506 | let dislikesToIncrement = 0 |
484 | 507 | ||
diff --git a/client/src/app/videos/+video-watch/video-watch.module.ts b/client/src/app/videos/+video-watch/video-watch.module.ts index 5582ab40f..2f448db78 100644 --- a/client/src/app/videos/+video-watch/video-watch.module.ts +++ b/client/src/app/videos/+video-watch/video-watch.module.ts | |||
@@ -1,9 +1,7 @@ | |||
1 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | import { LinkifierService } from '@app/videos/+video-watch/comment/linkifier.service' | ||
3 | import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' | 2 | import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' |
4 | import { ClipboardModule } from 'ngx-clipboard' | 3 | import { ClipboardModule } from 'ngx-clipboard' |
5 | import { SharedModule } from '../../shared' | 4 | import { SharedModule } from '../../shared' |
6 | import { MarkdownService } from '../shared' | ||
7 | import { VideoCommentAddComponent } from './comment/video-comment-add.component' | 5 | import { VideoCommentAddComponent } from './comment/video-comment-add.component' |
8 | import { VideoCommentComponent } from './comment/video-comment.component' | 6 | import { VideoCommentComponent } from './comment/video-comment.component' |
9 | import { VideoCommentService } from './comment/video-comment.service' | 7 | import { VideoCommentService } from './comment/video-comment.service' |
@@ -17,7 +15,6 @@ import { NgxQRCodeModule } from 'ngx-qrcode2' | |||
17 | import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' | 15 | import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' |
18 | import { VideoBlacklistComponent } from '@app/videos/+video-watch/modal/video-blacklist.component' | 16 | import { VideoBlacklistComponent } from '@app/videos/+video-watch/modal/video-blacklist.component' |
19 | import { RecommendationsModule } from '@app/videos/recommendations/recommendations.module' | 17 | import { RecommendationsModule } from '@app/videos/recommendations/recommendations.module' |
20 | import { TextareaAutosizeModule } from 'ngx-textarea-autosize' | ||
21 | 18 | ||
22 | @NgModule({ | 19 | @NgModule({ |
23 | imports: [ | 20 | imports: [ |
@@ -26,7 +23,6 @@ import { TextareaAutosizeModule } from 'ngx-textarea-autosize' | |||
26 | ClipboardModule, | 23 | ClipboardModule, |
27 | NgbTooltipModule, | 24 | NgbTooltipModule, |
28 | NgxQRCodeModule, | 25 | NgxQRCodeModule, |
29 | TextareaAutosizeModule, | ||
30 | RecommendationsModule | 26 | RecommendationsModule |
31 | ], | 27 | ], |
32 | 28 | ||
@@ -48,8 +44,6 @@ import { TextareaAutosizeModule } from 'ngx-textarea-autosize' | |||
48 | ], | 44 | ], |
49 | 45 | ||
50 | providers: [ | 46 | providers: [ |
51 | MarkdownService, | ||
52 | LinkifierService, | ||
53 | VideoCommentService | 47 | VideoCommentService |
54 | ] | 48 | ] |
55 | }) | 49 | }) |
diff --git a/client/src/app/videos/shared/index.ts b/client/src/app/videos/shared/index.ts deleted file mode 100644 index 7a66944b9..000000000 --- a/client/src/app/videos/shared/index.ts +++ /dev/null | |||
@@ -1 +0,0 @@ | |||
1 | export * from './markdown.service' | ||
diff --git a/client/src/app/videos/shared/markdown.service.ts b/client/src/app/videos/shared/markdown.service.ts deleted file mode 100644 index 07017eca5..000000000 --- a/client/src/app/videos/shared/markdown.service.ts +++ /dev/null | |||
@@ -1,79 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | |||
3 | import * as MarkdownIt from 'markdown-it' | ||
4 | |||
5 | @Injectable() | ||
6 | export class MarkdownService { | ||
7 | static TEXT_RULES = [ | ||
8 | 'linkify', | ||
9 | 'autolink', | ||
10 | 'emphasis', | ||
11 | 'link', | ||
12 | 'newline', | ||
13 | 'list' | ||
14 | ] | ||
15 | static ENHANCED_RULES = MarkdownService.TEXT_RULES.concat([ 'image' ]) | ||
16 | |||
17 | private textMarkdownIt: MarkdownIt.MarkdownIt | ||
18 | private enhancedMarkdownIt: MarkdownIt.MarkdownIt | ||
19 | |||
20 | constructor () { | ||
21 | this.textMarkdownIt = this.createMarkdownIt(MarkdownService.TEXT_RULES) | ||
22 | this.enhancedMarkdownIt = this.createMarkdownIt(MarkdownService.ENHANCED_RULES) | ||
23 | } | ||
24 | |||
25 | textMarkdownToHTML (markdown: string) { | ||
26 | if (!markdown) return '' | ||
27 | |||
28 | const html = this.textMarkdownIt.render(markdown) | ||
29 | return this.avoidTruncatedTags(html) | ||
30 | } | ||
31 | |||
32 | enhancedMarkdownToHTML (markdown: string) { | ||
33 | if (!markdown) return '' | ||
34 | |||
35 | const html = this.enhancedMarkdownIt.render(markdown) | ||
36 | return this.avoidTruncatedTags(html) | ||
37 | } | ||
38 | |||
39 | private createMarkdownIt (rules: string[]) { | ||
40 | const markdownIt = new MarkdownIt('zero', { linkify: true, breaks: true }) | ||
41 | |||
42 | for (let rule of rules) { | ||
43 | markdownIt.enable(rule) | ||
44 | } | ||
45 | |||
46 | this.setTargetToLinks(markdownIt) | ||
47 | |||
48 | return markdownIt | ||
49 | } | ||
50 | |||
51 | private setTargetToLinks (markdownIt: MarkdownIt.MarkdownIt) { | ||
52 | // Snippet from markdown-it documentation: https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md#renderer | ||
53 | const defaultRender = markdownIt.renderer.rules.link_open || function (tokens, idx, options, env, self) { | ||
54 | return self.renderToken(tokens, idx, options) | ||
55 | } | ||
56 | |||
57 | markdownIt.renderer.rules.link_open = function (tokens, index, options, env, self) { | ||
58 | const token = tokens[index] | ||
59 | |||
60 | const targetIndex = token.attrIndex('target') | ||
61 | if (targetIndex < 0) token.attrPush([ 'target', '_blank' ]) | ||
62 | else token.attrs[targetIndex][1] = '_blank' | ||
63 | |||
64 | const relIndex = token.attrIndex('rel') | ||
65 | if (relIndex < 0) token.attrPush([ 'rel', 'noopener noreferrer' ]) | ||
66 | else token.attrs[relIndex][1] = 'noopener noreferrer' | ||
67 | |||
68 | // pass token to default renderer. | ||
69 | return defaultRender(tokens, index, options, env, self) | ||
70 | } | ||
71 | } | ||
72 | |||
73 | private avoidTruncatedTags (html: string) { | ||
74 | return html.replace(/\*\*?([^*]+)$/, '$1') | ||
75 | .replace(/<a[^>]+>([^<]+)<\/a>\s*...((<\/p>)|(<\/li>)|(<\/strong>))?$/mi, '$1...') | ||
76 | .replace(/\[[^\]]+\]?\(?([^\)]+)$/, '$1') | ||
77 | |||
78 | } | ||
79 | } | ||
diff --git a/client/src/app/videos/video-list/video-local.component.ts b/client/src/app/videos/video-list/video-local.component.ts index c91c639ca..c0be4b885 100644 --- a/client/src/app/videos/video-list/video-local.component.ts +++ b/client/src/app/videos/video-list/video-local.component.ts | |||
@@ -2,7 +2,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core' | |||
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { immutableAssign } from '@app/shared/misc/utils' | 3 | import { immutableAssign } from '@app/shared/misc/utils' |
4 | import { Location } from '@angular/common' | 4 | import { Location } from '@angular/common' |
5 | import { NotificationsService } from 'angular2-notifications' | ||
6 | import { AuthService } from '../../core/auth' | 5 | import { AuthService } from '../../core/auth' |
7 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' | 6 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' |
8 | import { VideoSortField } from '../../shared/video/sort-field.type' | 7 | import { VideoSortField } from '../../shared/video/sort-field.type' |
@@ -10,6 +9,8 @@ import { VideoService } from '../../shared/video/video.service' | |||
10 | import { VideoFilter } from '../../../../../shared/models/videos/video-query.type' | 9 | import { VideoFilter } from '../../../../../shared/models/videos/video-query.type' |
11 | import { I18n } from '@ngx-translate/i18n-polyfill' | 10 | import { I18n } from '@ngx-translate/i18n-polyfill' |
12 | import { ScreenService } from '@app/shared/misc/screen.service' | 11 | import { ScreenService } from '@app/shared/misc/screen.service' |
12 | import { UserRight } from '../../../../../shared/models/users' | ||
13 | import { Notifier } from '@app/core' | ||
13 | 14 | ||
14 | @Component({ | 15 | @Component({ |
15 | selector: 'my-videos-local', | 16 | selector: 'my-videos-local', |
@@ -25,7 +26,7 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On | |||
25 | constructor ( | 26 | constructor ( |
26 | protected router: Router, | 27 | protected router: Router, |
27 | protected route: ActivatedRoute, | 28 | protected route: ActivatedRoute, |
28 | protected notificationsService: NotificationsService, | 29 | protected notifier: Notifier, |
29 | protected authService: AuthService, | 30 | protected authService: AuthService, |
30 | protected location: Location, | 31 | protected location: Location, |
31 | protected i18n: I18n, | 32 | protected i18n: I18n, |
@@ -40,6 +41,11 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On | |||
40 | ngOnInit () { | 41 | ngOnInit () { |
41 | super.ngOnInit() | 42 | super.ngOnInit() |
42 | 43 | ||
44 | if (this.authService.isLoggedIn()) { | ||
45 | const user = this.authService.getUser() | ||
46 | this.displayModerationBlock = user.hasRight(UserRight.SEE_ALL_VIDEOS) | ||
47 | } | ||
48 | |||
43 | this.generateSyndicationList() | 49 | this.generateSyndicationList() |
44 | } | 50 | } |
45 | 51 | ||
@@ -56,4 +62,10 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On | |||
56 | generateSyndicationList () { | 62 | generateSyndicationList () { |
57 | this.syndicationItems = this.videoService.getVideoFeedUrls(this.sort, this.filter, this.categoryOneOf) | 63 | this.syndicationItems = this.videoService.getVideoFeedUrls(this.sort, this.filter, this.categoryOneOf) |
58 | } | 64 | } |
65 | |||
66 | toggleModerationDisplay () { | ||
67 | this.filter = this.filter === 'local' ? 'all-local' as 'all-local' : 'local' as 'local' | ||
68 | |||
69 | this.reloadVideos() | ||
70 | } | ||
59 | } | 71 | } |
diff --git a/client/src/app/videos/video-list/video-overview.component.ts b/client/src/app/videos/video-list/video-overview.component.ts index 2c6054721..7ff52b259 100644 --- a/client/src/app/videos/video-list/video-overview.component.ts +++ b/client/src/app/videos/video-list/video-overview.component.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { AuthService } from '@app/core' | 2 | import { AuthService, Notifier } from '@app/core' |
3 | import { NotificationsService } from 'angular2-notifications' | ||
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
5 | import { VideosOverview } from '@app/shared/overview/videos-overview.model' | 4 | import { VideosOverview } from '@app/shared/overview/videos-overview.model' |
6 | import { OverviewService } from '@app/shared/overview' | 5 | import { OverviewService } from '@app/shared/overview' |
@@ -21,7 +20,7 @@ export class VideoOverviewComponent implements OnInit { | |||
21 | 20 | ||
22 | constructor ( | 21 | constructor ( |
23 | private i18n: I18n, | 22 | private i18n: I18n, |
24 | private notificationsService: NotificationsService, | 23 | private notifier: Notifier, |
25 | private authService: AuthService, | 24 | private authService: AuthService, |
26 | private overviewService: OverviewService | 25 | private overviewService: OverviewService |
27 | ) { } | 26 | ) { } |
@@ -43,10 +42,7 @@ export class VideoOverviewComponent implements OnInit { | |||
43 | ) this.notResults = true | 42 | ) this.notResults = true |
44 | }, | 43 | }, |
45 | 44 | ||
46 | err => { | 45 | err => this.notifier.error(err.message) |
47 | console.log(err) | ||
48 | this.notificationsService.error('Error', err.text) | ||
49 | } | ||
50 | ) | 46 | ) |
51 | } | 47 | } |
52 | 48 | ||
diff --git a/client/src/app/videos/video-list/video-recently-added.component.ts b/client/src/app/videos/video-list/video-recently-added.component.ts index ac1fcfff3..f99c8abb6 100644 --- a/client/src/app/videos/video-list/video-recently-added.component.ts +++ b/client/src/app/videos/video-list/video-recently-added.component.ts | |||
@@ -2,13 +2,13 @@ import { Component, OnDestroy, OnInit } from '@angular/core' | |||
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { Location } from '@angular/common' | 3 | import { Location } from '@angular/common' |
4 | import { immutableAssign } from '@app/shared/misc/utils' | 4 | import { immutableAssign } from '@app/shared/misc/utils' |
5 | import { NotificationsService } from 'angular2-notifications' | ||
6 | import { AuthService } from '../../core/auth' | 5 | import { AuthService } from '../../core/auth' |
7 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' | 6 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' |
8 | import { VideoSortField } from '../../shared/video/sort-field.type' | 7 | import { VideoSortField } from '../../shared/video/sort-field.type' |
9 | import { VideoService } from '../../shared/video/video.service' | 8 | import { VideoService } from '../../shared/video/video.service' |
10 | import { I18n } from '@ngx-translate/i18n-polyfill' | 9 | import { I18n } from '@ngx-translate/i18n-polyfill' |
11 | import { ScreenService } from '@app/shared/misc/screen.service' | 10 | import { ScreenService } from '@app/shared/misc/screen.service' |
11 | import { Notifier } from '@app/core' | ||
12 | 12 | ||
13 | @Component({ | 13 | @Component({ |
14 | selector: 'my-videos-recently-added', | 14 | selector: 'my-videos-recently-added', |
@@ -24,7 +24,7 @@ export class VideoRecentlyAddedComponent extends AbstractVideoList implements On | |||
24 | protected router: Router, | 24 | protected router: Router, |
25 | protected route: ActivatedRoute, | 25 | protected route: ActivatedRoute, |
26 | protected location: Location, | 26 | protected location: Location, |
27 | protected notificationsService: NotificationsService, | 27 | protected notifier: Notifier, |
28 | protected authService: AuthService, | 28 | protected authService: AuthService, |
29 | protected i18n: I18n, | 29 | protected i18n: I18n, |
30 | protected screenService: ScreenService, | 30 | protected screenService: ScreenService, |
diff --git a/client/src/app/videos/video-list/video-trending.component.ts b/client/src/app/videos/video-list/video-trending.component.ts index 8f3d3842b..6fd74e67a 100644 --- a/client/src/app/videos/video-list/video-trending.component.ts +++ b/client/src/app/videos/video-list/video-trending.component.ts | |||
@@ -2,13 +2,13 @@ import { Component, OnDestroy, OnInit } from '@angular/core' | |||
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { Location } from '@angular/common' | 3 | import { Location } from '@angular/common' |
4 | import { immutableAssign } from '@app/shared/misc/utils' | 4 | import { immutableAssign } from '@app/shared/misc/utils' |
5 | import { NotificationsService } from 'angular2-notifications' | ||
6 | import { AuthService } from '../../core/auth' | 5 | import { AuthService } from '../../core/auth' |
7 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' | 6 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' |
8 | import { VideoSortField } from '../../shared/video/sort-field.type' | 7 | import { VideoSortField } from '../../shared/video/sort-field.type' |
9 | import { VideoService } from '../../shared/video/video.service' | 8 | import { VideoService } from '../../shared/video/video.service' |
10 | import { I18n } from '@ngx-translate/i18n-polyfill' | 9 | import { I18n } from '@ngx-translate/i18n-polyfill' |
11 | import { ScreenService } from '@app/shared/misc/screen.service' | 10 | import { ScreenService } from '@app/shared/misc/screen.service' |
11 | import { Notifier, ServerService } from '@app/core' | ||
12 | 12 | ||
13 | @Component({ | 13 | @Component({ |
14 | selector: 'my-videos-trending', | 14 | selector: 'my-videos-trending', |
@@ -23,22 +23,37 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit, | |||
23 | constructor ( | 23 | constructor ( |
24 | protected router: Router, | 24 | protected router: Router, |
25 | protected route: ActivatedRoute, | 25 | protected route: ActivatedRoute, |
26 | protected notificationsService: NotificationsService, | 26 | protected notifier: Notifier, |
27 | protected authService: AuthService, | 27 | protected authService: AuthService, |
28 | protected location: Location, | 28 | protected location: Location, |
29 | protected screenService: ScreenService, | 29 | protected screenService: ScreenService, |
30 | private serverService: ServerService, | ||
30 | protected i18n: I18n, | 31 | protected i18n: I18n, |
31 | private videoService: VideoService | 32 | private videoService: VideoService |
32 | ) { | 33 | ) { |
33 | super() | 34 | super() |
34 | |||
35 | this.titlePage = i18n('Trending') | ||
36 | } | 35 | } |
37 | 36 | ||
38 | ngOnInit () { | 37 | ngOnInit () { |
39 | super.ngOnInit() | 38 | super.ngOnInit() |
40 | 39 | ||
41 | this.generateSyndicationList() | 40 | this.generateSyndicationList() |
41 | |||
42 | this.serverService.configLoaded.subscribe( | ||
43 | () => { | ||
44 | const trendingDays = this.serverService.getConfig().trending.videos.intervalDays | ||
45 | |||
46 | if (trendingDays === 1) { | ||
47 | this.titlePage = this.i18n('Trending for the last 24 hours') | ||
48 | this.titleTooltip = this.i18n('Trending videos are those totalizing the greatest number of views during the last 24 hours.') | ||
49 | } else { | ||
50 | this.titlePage = this.i18n('Trending for the last {{days}} days', { days: trendingDays }) | ||
51 | this.titleTooltip = this.i18n( | ||
52 | 'Trending videos are those totalizing the greatest number of views during the last {{days}} days.', | ||
53 | { days: trendingDays } | ||
54 | ) | ||
55 | } | ||
56 | }) | ||
42 | } | 57 | } |
43 | 58 | ||
44 | ngOnDestroy () { | 59 | ngOnDestroy () { |
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 index 6e8959c54..bee828e12 100644 --- a/client/src/app/videos/video-list/video-user-subscriptions.component.ts +++ b/client/src/app/videos/video-list/video-user-subscriptions.component.ts | |||
@@ -2,7 +2,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core' | |||
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { immutableAssign } from '@app/shared/misc/utils' | 3 | import { immutableAssign } from '@app/shared/misc/utils' |
4 | import { Location } from '@angular/common' | 4 | import { Location } from '@angular/common' |
5 | import { NotificationsService } from 'angular2-notifications' | ||
6 | import { AuthService } from '../../core/auth' | 5 | import { AuthService } from '../../core/auth' |
7 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' | 6 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' |
8 | import { VideoSortField } from '../../shared/video/sort-field.type' | 7 | import { VideoSortField } from '../../shared/video/sort-field.type' |
@@ -10,6 +9,7 @@ import { VideoService } from '../../shared/video/video.service' | |||
10 | import { I18n } from '@ngx-translate/i18n-polyfill' | 9 | import { I18n } from '@ngx-translate/i18n-polyfill' |
11 | import { ScreenService } from '@app/shared/misc/screen.service' | 10 | import { ScreenService } from '@app/shared/misc/screen.service' |
12 | import { OwnerDisplayType } from '@app/shared/video/video-miniature.component' | 11 | import { OwnerDisplayType } from '@app/shared/video/video-miniature.component' |
12 | import { Notifier } from '@app/core' | ||
13 | 13 | ||
14 | @Component({ | 14 | @Component({ |
15 | selector: 'my-videos-user-subscriptions', | 15 | selector: 'my-videos-user-subscriptions', |
@@ -25,7 +25,7 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement | |||
25 | constructor ( | 25 | constructor ( |
26 | protected router: Router, | 26 | protected router: Router, |
27 | protected route: ActivatedRoute, | 27 | protected route: ActivatedRoute, |
28 | protected notificationsService: NotificationsService, | 28 | protected notifier: Notifier, |
29 | protected authService: AuthService, | 29 | protected authService: AuthService, |
30 | protected location: Location, | 30 | protected location: Location, |
31 | protected i18n: I18n, | 31 | protected i18n: I18n, |