diff options
Diffstat (limited to 'client/src/app/videos')
50 files changed, 632 insertions, 439 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 19043eee6..6a9e31b5a 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 | |||
@@ -9,7 +9,7 @@ | |||
9 | <div class="modal-body"> | 9 | <div class="modal-body"> |
10 | <label i18n for="language">Language</label> | 10 | <label i18n for="language">Language</label> |
11 | <div class="peertube-select-container"> | 11 | <div class="peertube-select-container"> |
12 | <select id="language" formControlName="language"> | 12 | <select id="language" formControlName="language" class="form-control"> |
13 | <option></option> | 13 | <option></option> |
14 | <option *ngFor="let language of videoCaptionLanguages" [value]="language.id">{{ language.label }}</option> | 14 | <option *ngFor="let language of videoCaptionLanguages" [value]="language.id">{{ language.label }}</option> |
15 | </select> | 15 | </select> |
@@ -23,6 +23,7 @@ | |||
23 | <my-reactive-file | 23 | <my-reactive-file |
24 | formControlName="captionfile" inputName="captionfile" i18n-inputLabel inputLabel="Select the caption file" | 24 | formControlName="captionfile" inputName="captionfile" i18n-inputLabel inputLabel="Select the caption file" |
25 | [extensions]="videoCaptionExtensions" [maxFileSize]="videoCaptionMaxSize" [displayFilename]="true" | 25 | [extensions]="videoCaptionExtensions" [maxFileSize]="videoCaptionMaxSize" [displayFilename]="true" |
26 | i18n-ngbTooltip [ngbTooltip]="'(extensions: ' + videoCaptionExtensions.join(', ') + ')'" | ||
26 | ></my-reactive-file> | 27 | ></my-reactive-file> |
27 | </div> | 28 | </div> |
28 | 29 | ||
@@ -32,9 +33,10 @@ | |||
32 | </div> | 33 | </div> |
33 | 34 | ||
34 | <div class="modal-footer inputs"> | 35 | <div class="modal-footer inputs"> |
35 | <span i18n class="action-button action-button-cancel" (click)="hide()"> | 36 | <input |
36 | Cancel | 37 | type="button" role="button" i18n-value value="Cancel" class="action-button action-button-cancel" |
37 | </span> | 38 | (click)="hide()" (key.enter)="hide()" |
39 | > | ||
38 | 40 | ||
39 | <input | 41 | <input |
40 | type="submit" i18n-value value="Add this caption" class="action-button-submit" | 42 | type="submit" i18n-value value="Add this caption" class="action-button-submit" |
diff --git a/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.scss b/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.scss index c6da1877e..b257a16a9 100644 --- a/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.scss +++ b/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.scss | |||
@@ -7,6 +7,11 @@ | |||
7 | 7 | ||
8 | .caption-file { | 8 | .caption-file { |
9 | margin-top: 20px; | 9 | margin-top: 20px; |
10 | width: max-content; | ||
11 | |||
12 | ::ng-deep .root { | ||
13 | width: max-content; | ||
14 | } | ||
10 | } | 15 | } |
11 | 16 | ||
12 | .warning-replace-caption { | 17 | .warning-replace-caption { |
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 1a9bf5171..9856aac9e 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 | |||
@@ -56,7 +56,7 @@ export class VideoCaptionAddModalComponent extends FormReactive implements OnIni | |||
56 | show () { | 56 | show () { |
57 | this.closingModal = false | 57 | this.closingModal = false |
58 | 58 | ||
59 | this.openedModal = this.modalService.open(this.modal, { keyboard: false }) | 59 | this.openedModal = this.modalService.open(this.modal, { centered: true, keyboard: false }) |
60 | } | 60 | } |
61 | 61 | ||
62 | hide () { | 62 | hide () { |
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.html b/client/src/app/videos/+video-edit/shared/video-edit.component.html index e40649d95..9a0e4f848 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 | |||
@@ -1,13 +1,15 @@ | |||
1 | <div class="video-edit" [formGroup]="form"> | 1 | <div class="video-edit" [formGroup]="form"> |
2 | <ngb-tabset class="root-tabset bootstrap"> | 2 | <div ngbNav #nav="ngbNav" class="nav-tabs"> |
3 | 3 | ||
4 | <ngb-tab i18n-title title="Basic info"> | 4 | <ng-container ngbNavItem> |
5 | <ng-template ngbTabContent> | 5 | <a ngbNavLink i18n>Basic info</a> |
6 | |||
7 | <ng-template ngbNavContent> | ||
6 | <div class="row"> | 8 | <div class="row"> |
7 | <div class="col-md-8"> | 9 | <div class="col-video-edit"> |
8 | <div class="form-group"> | 10 | <div class="form-group"> |
9 | <label i18n for="name">Title</label> | 11 | <label i18n for="name">Title</label> |
10 | <input type="text" id="name" formControlName="name" /> | 12 | <input type="text" id="name" class="form-control" formControlName="name" /> |
11 | <div *ngIf="formErrors.name" class="form-error"> | 13 | <div *ngIf="formErrors.name" class="form-error"> |
12 | {{ formErrors.name }} | 14 | {{ formErrors.name }} |
13 | </div> | 15 | </div> |
@@ -29,7 +31,7 @@ | |||
29 | <tag-input | 31 | <tag-input |
30 | [validators]="tagValidators" [errorMessages]="tagValidatorsMessages" | 32 | [validators]="tagValidators" [errorMessages]="tagValidatorsMessages" |
31 | i18n-placeholder placeholder="+ Tag" i18n-secondaryPlaceholder secondaryPlaceholder="Enter a new tag" | 33 | i18n-placeholder placeholder="+ Tag" i18n-secondaryPlaceholder secondaryPlaceholder="Enter a new tag" |
32 | formControlName="tags" maxItems="5" modelAsStrings="true" | 34 | formControlName="tags" [maxItems]="5" [modelAsStrings]="true" |
33 | ></tag-input> | 35 | ></tag-input> |
34 | </div> | 36 | </div> |
35 | 37 | ||
@@ -44,7 +46,7 @@ | |||
44 | </ng-template> | 46 | </ng-template> |
45 | </my-help> | 47 | </my-help> |
46 | 48 | ||
47 | <my-markdown-textarea truncate="250" formControlName="description" markdownVideo="true"></my-markdown-textarea> | 49 | <my-markdown-textarea [truncate]="250" formControlName="description" [markdownVideo]="true"></my-markdown-textarea> |
48 | 50 | ||
49 | <div *ngIf="formErrors.description" class="form-error"> | 51 | <div *ngIf="formErrors.description" class="form-error"> |
50 | {{ formErrors.description }} | 52 | {{ formErrors.description }} |
@@ -52,11 +54,11 @@ | |||
52 | </div> | 54 | </div> |
53 | </div> | 55 | </div> |
54 | 56 | ||
55 | <div class="col-md-4"> | 57 | <div class="col-video-edit"> |
56 | <div class="form-group"> | 58 | <div class="form-group"> |
57 | <label i18n>Channel</label> | 59 | <label i18n>Channel</label> |
58 | <div class="peertube-select-container"> | 60 | <div class="peertube-select-container"> |
59 | <select formControlName="channelId"> | 61 | <select formControlName="channelId" class="form-control"> |
60 | <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option> | 62 | <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option> |
61 | </select> | 63 | </select> |
62 | </div> | 64 | </div> |
@@ -65,7 +67,7 @@ | |||
65 | <div class="form-group"> | 67 | <div class="form-group"> |
66 | <label i18n for="category">Category</label> | 68 | <label i18n for="category">Category</label> |
67 | <div class="peertube-select-container"> | 69 | <div class="peertube-select-container"> |
68 | <select id="category" formControlName="category"> | 70 | <select id="category" formControlName="category" class="form-control"> |
69 | <option></option> | 71 | <option></option> |
70 | <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option> | 72 | <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option> |
71 | </select> | 73 | </select> |
@@ -79,7 +81,7 @@ | |||
79 | <div class="form-group"> | 81 | <div class="form-group"> |
80 | <label i18n for="licence">Licence</label> | 82 | <label i18n for="licence">Licence</label> |
81 | <div class="peertube-select-container"> | 83 | <div class="peertube-select-container"> |
82 | <select id="licence" formControlName="licence"> | 84 | <select id="licence" formControlName="licence" class="form-control"> |
83 | <option></option> | 85 | <option></option> |
84 | <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option> | 86 | <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option> |
85 | </select> | 87 | </select> |
@@ -93,7 +95,7 @@ | |||
93 | <div class="form-group"> | 95 | <div class="form-group"> |
94 | <label i18n for="language">Language</label> | 96 | <label i18n for="language">Language</label> |
95 | <div class="peertube-select-container"> | 97 | <div class="peertube-select-container"> |
96 | <select id="language" formControlName="language"> | 98 | <select id="language" formControlName="language" class="form-control"> |
97 | <option></option> | 99 | <option></option> |
98 | <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option> | 100 | <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option> |
99 | </select> | 101 | </select> |
@@ -107,7 +109,7 @@ | |||
107 | <div class="form-group"> | 109 | <div class="form-group"> |
108 | <label i18n for="privacy">Privacy</label> | 110 | <label i18n for="privacy">Privacy</label> |
109 | <div class="peertube-select-container"> | 111 | <div class="peertube-select-container"> |
110 | <select id="privacy" formControlName="privacy"> | 112 | <select id="privacy" formControlName="privacy" class="form-control"> |
111 | <option></option> | 113 | <option></option> |
112 | <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option> | 114 | <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option> |
113 | <option *ngIf="schedulePublicationPossible" [value]="SPECIAL_SCHEDULED_PRIVACY">Scheduled</option> | 115 | <option *ngIf="schedulePublicationPossible" [value]="SPECIAL_SCHEDULED_PRIVACY">Scheduled</option> |
@@ -155,10 +157,12 @@ | |||
155 | </div> | 157 | </div> |
156 | </div> | 158 | </div> |
157 | </ng-template> | 159 | </ng-template> |
158 | </ngb-tab> | 160 | </ng-container> |
161 | |||
162 | <ng-container ngbNavItem> | ||
163 | <a ngbNavLink i18n>Captions</a> | ||
159 | 164 | ||
160 | <ngb-tab i18n-title title="Captions"> | 165 | <ng-template ngbNavContent> |
161 | <ng-template ngbTabContent> | ||
162 | <div class="captions"> | 166 | <div class="captions"> |
163 | 167 | ||
164 | <div class="captions-header"> | 168 | <div class="captions-header"> |
@@ -206,10 +210,12 @@ | |||
206 | 210 | ||
207 | </div> | 211 | </div> |
208 | </ng-template> | 212 | </ng-template> |
209 | </ngb-tab> | 213 | </ng-container> |
214 | |||
215 | <ng-container ngbNavItem> | ||
216 | <a ngbNavLink i18n>Advanced settings</a> | ||
210 | 217 | ||
211 | <ngb-tab i18n-title title="Advanced settings"> | 218 | <ng-template ngbNavContent> |
212 | <ng-template ngbTabContent> | ||
213 | <div class="row advanced-settings"> | 219 | <div class="row advanced-settings"> |
214 | <div class="col-md-12 col-xl-8"> | 220 | <div class="col-md-12 col-xl-8"> |
215 | 221 | ||
@@ -226,7 +232,7 @@ | |||
226 | <label i18n for="support">Support</label> | 232 | <label i18n for="support">Support</label> |
227 | <my-help helpType="markdownEnhanced" i18n-preHtml preHtml="Short text to tell people how they can support you (membership platform...)."></my-help> | 233 | <my-help helpType="markdownEnhanced" i18n-preHtml preHtml="Short text to tell people how they can support you (membership platform...)."></my-help> |
228 | <my-markdown-textarea | 234 | <my-markdown-textarea |
229 | id="support" formControlName="support" textareaWidth="500px" [previewColumn]="true" markdownType="enhanced" | 235 | id="support" formControlName="support" markdownType="enhanced" |
230 | [classes]="{ 'input-error': formErrors['support'] }" | 236 | [classes]="{ 'input-error': formErrors['support'] }" |
231 | ></my-markdown-textarea> | 237 | ></my-markdown-textarea> |
232 | <div *ngIf="formErrors.support" class="form-error"> | 238 | <div *ngIf="formErrors.support" class="form-error"> |
@@ -262,10 +268,11 @@ | |||
262 | </div> | 268 | </div> |
263 | </div> | 269 | </div> |
264 | </ng-template> | 270 | </ng-template> |
265 | </ngb-tab> | 271 | </ng-container> |
266 | 272 | ||
267 | </ngb-tabset> | 273 | </div> |
268 | 274 | ||
275 | <div [ngbNavOutlet]="nav"></div> | ||
269 | </div> | 276 | </div> |
270 | 277 | ||
271 | <my-video-caption-add-modal | 278 | <my-video-caption-add-modal |
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 144914731..2f9067132 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 | |||
@@ -1,5 +1,16 @@ | |||
1 | @import '_variables'; | 1 | // Bootstrap grid utilities require functions, variables and mixins |
2 | @import '_mixins'; | 2 | @import 'node_modules/bootstrap/scss/functions'; |
3 | @import 'node_modules/bootstrap/scss/variables'; | ||
4 | @import 'node_modules/bootstrap/scss/mixins'; | ||
5 | @import 'node_modules/bootstrap/scss/grid'; | ||
6 | |||
7 | @import 'variables'; | ||
8 | @import 'mixins'; | ||
9 | |||
10 | label { | ||
11 | font-weight: $font-regular; | ||
12 | font-size: 100%; | ||
13 | } | ||
3 | 14 | ||
4 | .peertube-select-container { | 15 | .peertube-select-container { |
5 | @include peertube-select-container(auto); | 16 | @include peertube-select-container(auto); |
@@ -19,6 +30,10 @@ my-peertube-checkbox { | |||
19 | margin-bottom: 1rem; | 30 | margin-bottom: 1rem; |
20 | } | 31 | } |
21 | 32 | ||
33 | .nav-tabs { | ||
34 | margin-bottom: 15px; | ||
35 | } | ||
36 | |||
22 | .video-edit { | 37 | .video-edit { |
23 | height: 100%; | 38 | height: 100%; |
24 | min-height: 300px; | 39 | min-height: 300px; |
@@ -45,6 +60,7 @@ my-peertube-checkbox { | |||
45 | 60 | ||
46 | .captions-header { | 61 | .captions-header { |
47 | text-align: right; | 62 | text-align: right; |
63 | margin-bottom: 1rem; | ||
48 | 64 | ||
49 | .create-caption { | 65 | .create-caption { |
50 | @include create-button; | 66 | @include create-button; |
@@ -59,6 +75,7 @@ my-peertube-checkbox { | |||
59 | a.caption-entry-label { | 75 | a.caption-entry-label { |
60 | @include disable-default-a-behaviour; | 76 | @include disable-default-a-behaviour; |
61 | 77 | ||
78 | flex-grow: 1; | ||
62 | color: #000; | 79 | color: #000; |
63 | 80 | ||
64 | &:hover { | 81 | &:hover { |
@@ -144,69 +161,37 @@ p-calendar { | |||
144 | } | 161 | } |
145 | } | 162 | } |
146 | 163 | ||
147 | ::ng-deep { | 164 | @include ng2-tags; |
148 | .root-tabset > .nav { | ||
149 | margin-bottom: 15px; | ||
150 | } | ||
151 | 165 | ||
152 | .ng2-tag-input { | 166 | // columns for the video |
153 | border: none !important; | 167 | .col-video-edit { |
154 | } | 168 | @include make-col-ready(); |
155 | 169 | ||
156 | .ng2-tags-container { | 170 | @include media-breakpoint-up(md) { |
157 | display: flex; | 171 | @include make-col(7); |
158 | align-items: center; | ||
159 | border: 1px solid #C6C6C6; | ||
160 | border-radius: 3px; | ||
161 | padding: 5px !important; | ||
162 | height: max-content; | ||
163 | } | ||
164 | 172 | ||
165 | tag-input-form { | 173 | & + .col-video-edit { |
166 | input { | 174 | @include make-col(5); |
167 | height: 30px !important; | ||
168 | font-size: 12px !important; | ||
169 | |||
170 | background-color: var(--mainBackgroundColor) !important; | ||
171 | color: var(--mainForegroundColor) !important; | ||
172 | } | 175 | } |
173 | } | 176 | } |
174 | 177 | ||
175 | tag { | 178 | @include media-breakpoint-up(xl) { |
176 | background-color: $grey-background-color !important; | 179 | @include make-col(8); |
177 | color: #000 !important; | 180 | |
178 | border-radius: 3px !important; | 181 | & + .col-video-edit { |
179 | font-size: 12px !important; | 182 | @include make-col(4); |
180 | height: 30px !important; | ||
181 | line-height: 30px !important; | ||
182 | margin: 0 5px 0 0 !important; | ||
183 | cursor: default !important; | ||
184 | padding: 0 8px 0 10px !important; | ||
185 | |||
186 | div { | ||
187 | height: 100% !important; | ||
188 | } | 183 | } |
189 | } | 184 | } |
185 | } | ||
190 | 186 | ||
191 | delete-icon { | 187 | :host-context(.expanded) { |
192 | cursor: pointer !important; | 188 | .col-video-edit { |
193 | height: auto !important; | 189 | @include media-breakpoint-up(md) { |
194 | vertical-align: middle !important; | 190 | @include make-col(8); |
195 | padding-left: 6px !important; | ||
196 | |||
197 | svg { | ||
198 | position: relative; | ||
199 | top: -1px; | ||
200 | height: auto !important; | ||
201 | vertical-align: middle !important; | ||
202 | 191 | ||
203 | path { | 192 | & + .col-video-edit { |
204 | fill: $grey-foreground-color !important; | 193 | @include make-col(4); |
205 | } | 194 | } |
206 | } | 195 | } |
207 | |||
208 | &:hover { | ||
209 | transform: none !important; | ||
210 | } | ||
211 | } | 196 | } |
212 | } | 197 | } |
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.module.ts b/client/src/app/videos/+video-edit/shared/video-edit.module.ts index 39b6daa93..1357d607c 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.module.ts +++ b/client/src/app/videos/+video-edit/shared/video-edit.module.ts | |||
@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core' | |||
2 | import { TagInputModule } from 'ngx-chips' | 2 | import { TagInputModule } from 'ngx-chips' |
3 | import { SharedModule } from '../../../shared/' | 3 | import { SharedModule } from '../../../shared/' |
4 | import { VideoEditComponent } from './video-edit.component' | 4 | import { VideoEditComponent } from './video-edit.component' |
5 | import { CalendarModule } from 'primeng/components/calendar/calendar' | 5 | import { CalendarModule } from 'primeng/calendar' |
6 | import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' | 6 | import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' |
7 | 7 | ||
8 | @NgModule({ | 8 | @NgModule({ |
diff --git a/client/src/app/videos/+video-edit/video-add-components/drag-drop.directive.ts b/client/src/app/videos/+video-edit/video-add-components/drag-drop.directive.ts new file mode 100644 index 000000000..7b1a38c62 --- /dev/null +++ b/client/src/app/videos/+video-edit/video-add-components/drag-drop.directive.ts | |||
@@ -0,0 +1,30 @@ | |||
1 | import { Directive, Output, EventEmitter, HostBinding, HostListener } from '@angular/core' | ||
2 | |||
3 | @Directive({ | ||
4 | selector: '[dragDrop]' | ||
5 | }) | ||
6 | export class DragDropDirective { | ||
7 | @Output() fileDropped = new EventEmitter<FileList>() | ||
8 | |||
9 | @HostBinding('class.dragover') dragover = false | ||
10 | |||
11 | @HostListener('dragover', ['$event']) onDragOver (e: Event) { | ||
12 | e.preventDefault() | ||
13 | e.stopPropagation() | ||
14 | this.dragover = true | ||
15 | } | ||
16 | |||
17 | @HostListener('dragleave', ['$event']) public onDragLeave (e: Event) { | ||
18 | e.preventDefault() | ||
19 | e.stopPropagation() | ||
20 | this.dragover = false | ||
21 | } | ||
22 | |||
23 | @HostListener('drop', ['$event']) public ondrop (e: DragEvent) { | ||
24 | e.preventDefault() | ||
25 | e.stopPropagation() | ||
26 | this.dragover = false | ||
27 | const files = e.dataTransfer.files | ||
28 | if (files.length > 0) this.fileDropped.emit(files) | ||
29 | } | ||
30 | } | ||
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 c290fd4b1..c2ee3ad57 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,14 +1,13 @@ | |||
1 | <div *ngIf="!hasImportedVideo" class="upload-video-container"> | 1 | <div *ngIf="!hasImportedVideo" class="upload-video-container" dragDrop (fileDropped)="setTorrentFile($event)"> |
2 | <div class="first-step-block"> | 2 | <div class="first-step-block"> |
3 | <my-global-icon class="upload-icon" iconName="upload"></my-global-icon> | 3 | <my-global-icon class="upload-icon" iconName="upload"></my-global-icon> |
4 | 4 | ||
5 | <div class="button-file"> | 5 | <div class="button-file form-control" [ngbTooltip]="'(extensions: .torrent)'"> |
6 | <span i18n>Select the torrent to import</span> | 6 | <span i18n>Select the torrent to import</span> |
7 | <input #torrentfileInput type="file" name="torrentfile" id="torrentfile" accept=".torrent" (change)="fileChange()" /> | 7 | <input #torrentfileInput type="file" name="torrentfile" id="torrentfile" accept=".torrent" (change)="fileChange()" /> |
8 | </div> | 8 | </div> |
9 | <span class="button-file-extension">(.torrent)</span> | ||
10 | 9 | ||
11 | <div class="torrent-or-magnet" i18n>Or</div> | 10 | <div class="torrent-or-magnet" i18n-data-content data-content="OR"></div> |
12 | 11 | ||
13 | <div class="form-group form-group-magnet-uri"> | 12 | <div class="form-group form-group-magnet-uri"> |
14 | <label i18n for="magnetUri">Paste magnet URI</label> | 13 | <label i18n for="magnetUri">Paste magnet URI</label> |
@@ -21,13 +20,13 @@ | |||
21 | </ng-template> | 20 | </ng-template> |
22 | </my-help> | 21 | </my-help> |
23 | 22 | ||
24 | <input type="text" id="magnetUri" [(ngModel)]="magnetUri" /> | 23 | <input type="text" id="magnetUri" [(ngModel)]="magnetUri" class="form-control" /> |
25 | </div> | 24 | </div> |
26 | 25 | ||
27 | <div class="form-group"> | 26 | <div class="form-group"> |
28 | <label i18n for="first-step-channel">Channel</label> | 27 | <label i18n for="first-step-channel">Channel</label> |
29 | <div class="peertube-select-container"> | 28 | <div class="peertube-select-container"> |
30 | <select id="first-step-channel" [(ngModel)]="firstStepChannelId"> | 29 | <select id="first-step-channel" [(ngModel)]="firstStepChannelId" class="form-control"> |
31 | <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option> | 30 | <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option> |
32 | </select> | 31 | </select> |
33 | </div> | 32 | </div> |
@@ -36,7 +35,7 @@ | |||
36 | <div class="form-group"> | 35 | <div class="form-group"> |
37 | <label i18n for="first-step-privacy">Privacy</label> | 36 | <label i18n for="first-step-privacy">Privacy</label> |
38 | <div class="peertube-select-container"> | 37 | <div class="peertube-select-container"> |
39 | <select id="first-step-privacy" [(ngModel)]="firstStepPrivacyId"> | 38 | <select id="first-step-privacy" [(ngModel)]="firstStepPrivacyId" class="form-control"> |
40 | <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option> | 39 | <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option> |
41 | </select> | 40 | </select> |
42 | </div> | 41 | </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 6d59ed834..3b46475b4 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 | |||
@@ -3,7 +3,11 @@ | |||
3 | 3 | ||
4 | .first-step-block { | 4 | .first-step-block { |
5 | .torrent-or-magnet { | 5 | .torrent-or-magnet { |
6 | margin: 10px 0; | 6 | @include divider($color: var(--inputPlaceholderColor), $background: var(--submenuColor)); |
7 | |||
8 | &[data-content] { | ||
9 | margin: 1.5rem 0; | ||
10 | } | ||
7 | } | 11 | } |
8 | 12 | ||
9 | .form-group-magnet-uri { | 13 | .form-group-magnet-uri { |
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 74e1e755b..4d0b0b080 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 | |||
@@ -25,7 +25,7 @@ import { scrollToTop } from '@app/shared/misc/utils' | |||
25 | export class VideoImportTorrentComponent extends VideoSend implements OnInit, CanComponentDeactivate { | 25 | export class VideoImportTorrentComponent extends VideoSend implements OnInit, CanComponentDeactivate { |
26 | @Output() firstStepDone = new EventEmitter<string>() | 26 | @Output() firstStepDone = new EventEmitter<string>() |
27 | @Output() firstStepError = new EventEmitter<void>() | 27 | @Output() firstStepError = new EventEmitter<void>() |
28 | @ViewChild('torrentfileInput', { static: false }) torrentfileInput: ElementRef<HTMLInputElement> | 28 | @ViewChild('torrentfileInput') torrentfileInput: ElementRef<HTMLInputElement> |
29 | 29 | ||
30 | magnetUri = '' | 30 | magnetUri = '' |
31 | 31 | ||
@@ -72,6 +72,11 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca | |||
72 | this.importVideo(torrentfile) | 72 | this.importVideo(torrentfile) |
73 | } | 73 | } |
74 | 74 | ||
75 | setTorrentFile (files: FileList) { | ||
76 | this.torrentfileInput.nativeElement.files = files | ||
77 | this.fileChange() | ||
78 | } | ||
79 | |||
75 | importVideo (torrentfile?: Blob) { | 80 | importVideo (torrentfile?: Blob) { |
76 | this.isImportingVideo = true | 81 | this.isImportingVideo = true |
77 | 82 | ||
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 09d0b8272..9a26fe308 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 | |||
@@ -15,13 +15,13 @@ | |||
15 | </ng-template> | 15 | </ng-template> |
16 | </my-help> | 16 | </my-help> |
17 | 17 | ||
18 | <input type="text" id="targetUrl" [(ngModel)]="targetUrl" /> | 18 | <input type="text" id="targetUrl" [(ngModel)]="targetUrl" class="form-control" /> |
19 | </div> | 19 | </div> |
20 | 20 | ||
21 | <div class="form-group"> | 21 | <div class="form-group"> |
22 | <label i18n for="first-step-channel">Channel</label> | 22 | <label i18n for="first-step-channel">Channel</label> |
23 | <div class="peertube-select-container"> | 23 | <div class="peertube-select-container"> |
24 | <select id="first-step-channel" [(ngModel)]="firstStepChannelId"> | 24 | <select id="first-step-channel" [(ngModel)]="firstStepChannelId" class="form-control"> |
25 | <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option> | 25 | <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option> |
26 | </select> | 26 | </select> |
27 | </div> | 27 | </div> |
@@ -30,7 +30,7 @@ | |||
30 | <div class="form-group"> | 30 | <div class="form-group"> |
31 | <label i18n for="first-step-privacy">Privacy</label> | 31 | <label i18n for="first-step-privacy">Privacy</label> |
32 | <div class="peertube-select-container"> | 32 | <div class="peertube-select-container"> |
33 | <select id="first-step-privacy" [(ngModel)]="firstStepPrivacyId"> | 33 | <select id="first-step-privacy" [(ngModel)]="firstStepPrivacyId" class="form-control"> |
34 | <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option> | 34 | <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option> |
35 | </select> | 35 | </select> |
36 | </div> | 36 | </div> |
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 a5578bebd..213c42333 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 | |||
@@ -11,7 +11,8 @@ import { VideoEdit } from '@app/shared/video/video-edit.model' | |||
11 | import { FormValidatorService } from '@app/shared' | 11 | import { FormValidatorService } from '@app/shared' |
12 | import { VideoCaptionService } from '@app/shared/video-caption' | 12 | import { VideoCaptionService } from '@app/shared/video-caption' |
13 | import { VideoImportService } from '@app/shared/video-import' | 13 | import { VideoImportService } from '@app/shared/video-import' |
14 | import { scrollToTop } from '@app/shared/misc/utils' | 14 | import { scrollToTop, getAbsoluteAPIUrl } from '@app/shared/misc/utils' |
15 | import { switchMap, map } from 'rxjs/operators' | ||
15 | 16 | ||
16 | @Component({ | 17 | @Component({ |
17 | selector: 'my-video-import-url', | 18 | selector: 'my-video-import-url', |
@@ -76,31 +77,54 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, CanCom | |||
76 | 77 | ||
77 | this.loadingBar.start() | 78 | this.loadingBar.start() |
78 | 79 | ||
79 | this.videoImportService.importVideoUrl(this.targetUrl, videoUpdate).subscribe( | 80 | this.videoImportService |
80 | res => { | 81 | .importVideoUrl(this.targetUrl, videoUpdate) |
81 | this.loadingBar.complete() | 82 | .pipe( |
82 | this.firstStepDone.emit(res.video.name) | 83 | switchMap(res => { |
83 | this.isImportingVideo = false | 84 | return this.videoCaptionService |
84 | this.hasImportedVideo = true | 85 | .listCaptions(res.video.id) |
85 | 86 | .pipe( | |
86 | this.video = new VideoEdit(Object.assign(res.video, { | 87 | map(result => ({ video: res.video, videoCaptions: result.data })) |
87 | commentsEnabled: videoUpdate.commentsEnabled, | 88 | ) |
88 | downloadEnabled: videoUpdate.downloadEnabled, | 89 | }) |
89 | support: null, | 90 | ) |
90 | thumbnailUrl: null, | 91 | .subscribe( |
91 | previewUrl: null | 92 | ({ video, videoCaptions }) => { |
92 | })) | 93 | this.loadingBar.complete() |
93 | 94 | this.firstStepDone.emit(video.name) | |
94 | this.hydrateFormFromVideo() | 95 | this.isImportingVideo = false |
95 | }, | 96 | this.hasImportedVideo = true |
96 | 97 | ||
97 | err => { | 98 | const absoluteAPIUrl = getAbsoluteAPIUrl() |
98 | this.loadingBar.complete() | 99 | |
99 | this.isImportingVideo = false | 100 | const thumbnailUrl = video.thumbnailPath |
100 | this.firstStepError.emit() | 101 | ? absoluteAPIUrl + video.thumbnailPath |
101 | this.notifier.error(err.message) | 102 | : null |
102 | } | 103 | |
103 | ) | 104 | const previewUrl = video.previewPath |
105 | ? absoluteAPIUrl + video.previewPath | ||
106 | : null | ||
107 | |||
108 | this.video = new VideoEdit(Object.assign(video, { | ||
109 | commentsEnabled: videoUpdate.commentsEnabled, | ||
110 | downloadEnabled: videoUpdate.downloadEnabled, | ||
111 | support: null, | ||
112 | thumbnailUrl, | ||
113 | previewUrl | ||
114 | })) | ||
115 | |||
116 | this.videoCaptions = videoCaptions | ||
117 | |||
118 | this.hydrateFormFromVideo() | ||
119 | }, | ||
120 | |||
121 | err => { | ||
122 | this.loadingBar.complete() | ||
123 | this.isImportingVideo = false | ||
124 | this.firstStepError.emit() | ||
125 | this.notifier.error(err.message) | ||
126 | } | ||
127 | ) | ||
104 | } | 128 | } |
105 | 129 | ||
106 | updateSecondStep () { | 130 | updateSecondStep () { |
@@ -133,5 +157,26 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, CanCom | |||
133 | 157 | ||
134 | private hydrateFormFromVideo () { | 158 | private hydrateFormFromVideo () { |
135 | this.form.patchValue(this.video.toFormPatch()) | 159 | this.form.patchValue(this.video.toFormPatch()) |
160 | |||
161 | const objects = [ | ||
162 | { | ||
163 | url: 'thumbnailUrl', | ||
164 | name: 'thumbnailfile' | ||
165 | }, | ||
166 | { | ||
167 | url: 'previewUrl', | ||
168 | name: 'previewfile' | ||
169 | } | ||
170 | ] | ||
171 | |||
172 | for (const obj of objects) { | ||
173 | fetch(this.video[obj.url]) | ||
174 | .then(response => response.blob()) | ||
175 | .then(data => { | ||
176 | this.form.patchValue({ | ||
177 | [ obj.name ]: data | ||
178 | }) | ||
179 | }) | ||
180 | } | ||
136 | } | 181 | } |
137 | } | 182 | } |
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 index 8769dd302..ebe14c59e 100644 --- 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 | |||
@@ -41,14 +41,6 @@ $width-size: 190px; | |||
41 | } | 41 | } |
42 | 42 | ||
43 | .button-file { | 43 | .button-file { |
44 | @include peertube-button-file(auto); | 44 | @include peertube-button-file(max-content); |
45 | |||
46 | min-width: 190px; | ||
47 | } | ||
48 | |||
49 | .button-file-extension { | ||
50 | display: block; | ||
51 | font-size: 12px; | ||
52 | margin-top: 5px; | ||
53 | } | 45 | } |
54 | } | 46 | } |
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 0f904affb..950e55a52 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,17 +1,16 @@ | |||
1 | <div *ngIf="!isUploadingVideo" class="upload-video-container"> | 1 | <div *ngIf="!isUploadingVideo" class="upload-video-container" dragDrop (fileDropped)="setVideoFile($event)"> |
2 | <div class="first-step-block"> | 2 | <div class="first-step-block"> |
3 | <my-global-icon class="upload-icon" iconName="upload"></my-global-icon> | 3 | <my-global-icon class="upload-icon" iconName="upload"></my-global-icon> |
4 | 4 | ||
5 | <div class="button-file"> | 5 | <div class="button-file form-control" [ngbTooltip]="'(extensions: ' + videoExtensions + ')'"> |
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()" autofocus /> |
8 | </div> | 8 | </div> |
9 | <span class="button-file-extension">({{ videoExtensions }})</span> | ||
10 | 9 | ||
11 | <div class="form-group form-group-channel"> | 10 | <div class="form-group form-group-channel"> |
12 | <label i18n for="first-step-channel">Channel</label> | 11 | <label i18n for="first-step-channel">Channel</label> |
13 | <div class="peertube-select-container"> | 12 | <div class="peertube-select-container"> |
14 | <select id="first-step-channel" [(ngModel)]="firstStepChannelId"> | 13 | <select id="first-step-channel" [(ngModel)]="firstStepChannelId" class="form-control"> |
15 | <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option> | 14 | <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option> |
16 | </select> | 15 | </select> |
17 | </div> | 16 | </div> |
@@ -20,7 +19,7 @@ | |||
20 | <div class="form-group"> | 19 | <div class="form-group"> |
21 | <label i18n for="first-step-privacy">Privacy</label> | 20 | <label i18n for="first-step-privacy">Privacy</label> |
22 | <div class="peertube-select-container"> | 21 | <div class="peertube-select-container"> |
23 | <select id="first-step-privacy" [(ngModel)]="firstStepPrivacyId"> | 22 | <select id="first-step-privacy" [(ngModel)]="firstStepPrivacyId" class="form-control"> |
24 | <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option> | 23 | <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option> |
25 | <option i18n [value]="SPECIAL_SCHEDULED_PRIVACY">Scheduled</option> | 24 | <option i18n [value]="SPECIAL_SCHEDULED_PRIVACY">Scheduled</option> |
26 | </select> | 25 | </select> |
@@ -51,10 +50,12 @@ | |||
51 | </div> | 50 | </div> |
52 | 51 | ||
53 | <div *ngIf="isUploadingVideo && !error" class="upload-progress-cancel"> | 52 | <div *ngIf="isUploadingVideo && !error" class="upload-progress-cancel"> |
54 | <p-progressBar | 53 | <div class="progress" i18n-title title="Total video quota"> |
55 | [value]="videoUploadPercents" | 54 | <div class="progress-bar" role="progressbar" [style]="{ width: videoUploadPercents + '%' }" [attr.aria-valuenow]="videoUploadPercents" aria-valuemin="0" [attr.aria-valuemax]="100"> |
56 | [ngClass]="{ processing: videoUploadPercents === 100 && videoUploaded === false }" | 55 | <span *ngIf="videoUploadPercents === 100 && videoUploaded === false" i18n>Processing…</span> |
57 | ></p-progressBar> | 56 | <span *ngIf="videoUploadPercents !== 100 || videoUploaded">{{ videoUploadPercents }}%</span> |
57 | </div> | ||
58 | </div> | ||
58 | <input *ngIf="videoUploaded === false" type="button" value="Cancel" (click)="cancelUpload()" /> | 59 | <input *ngIf="videoUploaded === false" type="button" value="Cancel" (click)="cancelUpload()" /> |
59 | </div> | 60 | </div> |
60 | 61 | ||
@@ -68,7 +69,7 @@ | |||
68 | </div> | 69 | </div> |
69 | 70 | ||
70 | <!-- Hidden because we want to load the component --> | 71 | <!-- Hidden because we want to load the component --> |
71 | <form [hidden]="!isUploadingVideo" novalidate [formGroup]="form"> | 72 | <form [hidden]="!isUploadingVideo" novalidate [formGroup]="form" class="mb-3"> |
72 | <my-video-edit | 73 | <my-video-edit |
73 | [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" | 74 | [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" |
74 | [validationMessages]="validationMessages" [userVideoChannels]="userVideoChannels" | 75 | [validationMessages]="validationMessages" [userVideoChannels]="userVideoChannels" |
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 b5628e276..a4f87b0b8 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 | |||
@@ -2,7 +2,6 @@ | |||
2 | @import 'mixins'; | 2 | @import 'mixins'; |
3 | 3 | ||
4 | .first-step-block { | 4 | .first-step-block { |
5 | |||
6 | .form-group-channel { | 5 | .form-group-channel { |
7 | margin-bottom: 20px; | 6 | margin-bottom: 20px; |
8 | margin-top: 35px; | 7 | margin-top: 35px; |
@@ -22,37 +21,21 @@ | |||
22 | margin-top: 25px; | 21 | margin-top: 25px; |
23 | margin-bottom: 40px; | 22 | margin-bottom: 40px; |
24 | 23 | ||
25 | p-progressBar { | 24 | .progress { |
25 | @include progressbar; | ||
26 | flex-grow: 1; | 26 | flex-grow: 1; |
27 | 27 | height: 30px; | |
28 | ::ng-deep .ui-progressbar { | 28 | font-size: 15px; |
29 | font-size: 15px !important; | 29 | background-color: rgba(11, 204, 41, 0.16); |
30 | height: 30px !important; | 30 | |
31 | border-radius: 3px !important; | 31 | .progress-bar { |
32 | background-color: rgba(11, 204, 41, 0.16) !important; | 32 | background-color: $green; |
33 | 33 | line-height: 30px; | |
34 | .ui-progressbar-value { | 34 | text-align: left; |
35 | background-color: #0BCC29 !important; | 35 | font-weight: $font-bold; |
36 | } | 36 | |
37 | 37 | span { | |
38 | .ui-progressbar-label { | 38 | margin-left: 18px; |
39 | text-align: left; | ||
40 | padding-left: 18px; | ||
41 | margin-top: 0 !important; | ||
42 | color: #fff !important; | ||
43 | line-height: 30px !important; | ||
44 | } | ||
45 | } | ||
46 | |||
47 | &.processing { | ||
48 | ::ng-deep .ui-progressbar-label { | ||
49 | // Same color as background to hide "100%" | ||
50 | color: rgba(11, 204, 41, 0.16) !important; | ||
51 | |||
52 | &::before { | ||
53 | content: 'Processing...'; | ||
54 | color: #fff; | ||
55 | } | ||
56 | } | 39 | } |
57 | } | 40 | } |
58 | } | 41 | } |
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 aa87f9581..9ce3fbc6d 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 | |||
@@ -27,7 +27,7 @@ import { scrollToTop } from '@app/shared/misc/utils' | |||
27 | export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy, CanComponentDeactivate { | 27 | export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy, CanComponentDeactivate { |
28 | @Output() firstStepDone = new EventEmitter<string>() | 28 | @Output() firstStepDone = new EventEmitter<string>() |
29 | @Output() firstStepError = new EventEmitter<void>() | 29 | @Output() firstStepError = new EventEmitter<void>() |
30 | @ViewChild('videofileInput', { static: false }) videofileInput: ElementRef<HTMLInputElement> | 30 | @ViewChild('videofileInput') videofileInput: ElementRef<HTMLInputElement> |
31 | 31 | ||
32 | // So that it can be accessed in the template | 32 | // So that it can be accessed in the template |
33 | readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY | 33 | readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY |
@@ -70,7 +70,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
70 | } | 70 | } |
71 | 71 | ||
72 | get videoExtensions () { | 72 | get videoExtensions () { |
73 | return this.serverConfig.video.file.extensions.join(',') | 73 | return this.serverConfig.video.file.extensions.join(', ') |
74 | } | 74 | } |
75 | 75 | ||
76 | ngOnInit () { | 76 | ngOnInit () { |
@@ -108,6 +108,11 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
108 | return this.videofileInput.nativeElement.files[0] | 108 | return this.videofileInput.nativeElement.files[0] |
109 | } | 109 | } |
110 | 110 | ||
111 | setVideoFile (files: FileList) { | ||
112 | this.videofileInput.nativeElement.files = files | ||
113 | this.fileChange() | ||
114 | } | ||
115 | |||
111 | getAudioUploadLabel () { | 116 | getAudioUploadLabel () { |
112 | const videofile = this.getVideoFile() | 117 | const videofile = this.getVideoFile() |
113 | if (!videofile) return this.i18n('Upload') | 118 | if (!videofile) return this.i18n('Upload') |
@@ -122,9 +127,13 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
122 | cancelUpload () { | 127 | cancelUpload () { |
123 | if (this.videoUploadObservable !== null) { | 128 | if (this.videoUploadObservable !== null) { |
124 | this.videoUploadObservable.unsubscribe() | 129 | this.videoUploadObservable.unsubscribe() |
130 | |||
125 | this.isUploadingVideo = false | 131 | this.isUploadingVideo = false |
126 | this.videoUploadPercents = 0 | 132 | this.videoUploadPercents = 0 |
127 | this.videoUploadObservable = null | 133 | this.videoUploadObservable = null |
134 | |||
135 | this.firstStepError.emit() | ||
136 | |||
128 | this.notifier.info(this.i18n('Upload cancelled')) | 137 | this.notifier.info(this.i18n('Upload cancelled')) |
129 | } | 138 | } |
130 | } | 139 | } |
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 a99988600..79bfc6e5c 100644 --- a/client/src/app/videos/+video-edit/video-add.component.html +++ b/client/src/app/videos/+video-edit/video-add.component.html | |||
@@ -10,27 +10,37 @@ | |||
10 | <ng-container *ngIf="secondStepType === 'upload'" i18n>Upload {{ videoName }}</ng-container> | 10 | <ng-container *ngIf="secondStepType === 'upload'" i18n>Upload {{ videoName }}</ng-container> |
11 | </div> | 11 | </div> |
12 | 12 | ||
13 | <ngb-tabset class="video-add-tabset root-tabset bootstrap" [ngClass]="{ 'hide-nav': secondStepType !== undefined }"> | 13 | <div ngbNav #nav="ngbNav" class="nav-tabs video-add-nav" [ngClass]="{ 'hide-nav': secondStepType !== undefined }"> |
14 | <ng-container ngbNavItem> | ||
15 | <a ngbNavLink> | ||
16 | <span i18n>Upload a file</span> | ||
17 | </a> | ||
14 | 18 | ||
15 | <ngb-tab> | 19 | <ng-template ngbNavContent> |
16 | <ng-template ngbTabTitle><span i18n>Upload a file</span></ng-template> | ||
17 | <ng-template ngbTabContent> | ||
18 | <my-video-upload #videoUpload (firstStepDone)="onFirstStepDone('upload', $event)" (firstStepError)="onError()"></my-video-upload> | 20 | <my-video-upload #videoUpload (firstStepDone)="onFirstStepDone('upload', $event)" (firstStepError)="onError()"></my-video-upload> |
19 | </ng-template> | 21 | </ng-template> |
20 | </ngb-tab> | 22 | </ng-container> |
21 | 23 | ||
22 | <ngb-tab *ngIf="isVideoImportHttpEnabled()"> | 24 | <ng-container ngbNavItem *ngIf="isVideoImportHttpEnabled()"> |
23 | <ng-template ngbTabTitle><span i18n>Import with URL</span></ng-template> | 25 | <a ngbNavLink> |
24 | <ng-template ngbTabContent> | 26 | <span i18n>Import with URL</span> |
27 | </a> | ||
28 | |||
29 | <ng-template ngbNavContent> | ||
25 | <my-video-import-url #videoImportUrl (firstStepDone)="onFirstStepDone('import-url', $event)" (firstStepError)="onError()"></my-video-import-url> | 30 | <my-video-import-url #videoImportUrl (firstStepDone)="onFirstStepDone('import-url', $event)" (firstStepError)="onError()"></my-video-import-url> |
26 | </ng-template> | 31 | </ng-template> |
27 | </ngb-tab> | 32 | </ng-container> |
33 | |||
34 | <ng-container ngbNavItem *ngIf="isVideoImportTorrentEnabled()"> | ||
35 | <a ngbNavLink> | ||
36 | <span i18n>Import with torrent</span> | ||
37 | </a> | ||
28 | 38 | ||
29 | <ngb-tab *ngIf="isVideoImportTorrentEnabled()"> | 39 | <ng-template ngbNavContent> |
30 | <ng-template ngbTabTitle><span i18n>Import with torrent</span></ng-template> | ||
31 | <ng-template ngbTabContent> | ||
32 | <my-video-import-torrent #videoImportTorrent (firstStepDone)="onFirstStepDone('import-torrent', $event)" (firstStepError)="onError()"></my-video-import-torrent> | 40 | <my-video-import-torrent #videoImportTorrent (firstStepDone)="onFirstStepDone('import-torrent', $event)" (firstStepError)="onError()"></my-video-import-torrent> |
33 | </ng-template> | 41 | </ng-template> |
34 | </ngb-tab> | 42 | </ng-container> |
35 | </ngb-tabset> | 43 | </div> |
44 | |||
45 | <div [ngbNavOutlet]="nav"></div> | ||
36 | </div> | 46 | </div> |
diff --git a/client/src/app/videos/+video-edit/video-add.component.scss b/client/src/app/videos/+video-edit/video-add.component.scss index 7acab3744..1316e09e4 100644 --- a/client/src/app/videos/+video-edit/video-add.component.scss +++ b/client/src/app/videos/+video-edit/video-add.component.scss | |||
@@ -4,6 +4,7 @@ | |||
4 | $border-width: 3px; | 4 | $border-width: 3px; |
5 | $border-type: solid; | 5 | $border-type: solid; |
6 | $border-color: #EAEAEA; | 6 | $border-color: #EAEAEA; |
7 | $nav-link-height: 40px; | ||
7 | 8 | ||
8 | .margin-content { | 9 | .margin-content { |
9 | padding-top: 50px; | 10 | padding-top: 50px; |
@@ -13,52 +14,76 @@ $border-color: #EAEAEA; | |||
13 | font-size: 15px; | 14 | font-size: 15px; |
14 | } | 15 | } |
15 | 16 | ||
16 | ::ng-deep .root-tabset.video-add-tabset { | 17 | ::ng-deep .video-add-nav { |
17 | margin-top: 50px; | 18 | border-bottom: $border-width $border-type $border-color; |
19 | margin: 50px 0 0 0 !important; | ||
18 | 20 | ||
19 | &.hide-nav > .nav { | 21 | &.hide-nav { |
20 | display: none !important; | 22 | display: none !important; |
21 | } | 23 | } |
22 | 24 | ||
23 | & > .nav { | 25 | a.nav-link { |
24 | border-bottom: $border-width $border-type $border-color; | 26 | @include disable-default-a-behaviour; |
25 | margin: 0 !important; | ||
26 | 27 | ||
27 | & > li { | 28 | margin-bottom: -$border-width; |
28 | margin-bottom: -$border-width; | 29 | height: $nav-link-height !important; |
30 | padding: 0 30px !important; | ||
31 | font-size: 15px; | ||
32 | |||
33 | &.active { | ||
34 | border: $border-width $border-type $border-color; | ||
35 | border-bottom: none; | ||
36 | background-color: var(--submenuColor) !important; | ||
37 | |||
38 | span { | ||
39 | border-bottom: 2px solid var(--mainColor); | ||
40 | font-weight: $font-bold; | ||
41 | } | ||
29 | } | 42 | } |
43 | } | ||
44 | } | ||
45 | |||
46 | ::ng-deep .upload-video-container { | ||
47 | border: $border-width $border-type $border-color; | ||
48 | border-top: transparent; | ||
30 | 49 | ||
31 | a.nav-link { | 50 | background-color: var(--submenuColor); |
32 | @include disable-default-a-behaviour; | 51 | border-bottom-left-radius: 3px; |
52 | border-bottom-right-radius: 3px; | ||
53 | width: 100%; | ||
54 | min-height: 440px; | ||
55 | padding-bottom: 20px; | ||
56 | display: flex; | ||
57 | justify-content: center; | ||
58 | align-items: center; | ||
33 | 59 | ||
34 | height: 40px !important; | 60 | &.dragover { |
35 | padding: 0 30px !important; | 61 | border: 3px dashed var(--mainColor); |
36 | font-size: 15px; | 62 | } |
63 | } | ||
37 | 64 | ||
38 | &.active { | 65 | @mixin nav-scroll { |
39 | border: $border-width $border-type $border-color; | 66 | ::ng-deep .video-add-nav { |
40 | border-bottom: none; | 67 | height: #{$nav-link-height + $border-width * 2}; |
41 | background-color: var(--submenuColor) !important; | 68 | overflow-x: auto; |
69 | white-space: nowrap; | ||
70 | flex-wrap: unset; | ||
42 | 71 | ||
43 | span { | 72 | /* Hide active tab style to not have a moving tab effect */ |
44 | border-bottom: 2px solid var(--mainColor); | 73 | a.nav-link.active { |
45 | font-weight: $font-bold; | 74 | border: none; |
46 | } | 75 | background-color: var(--mainBackgroundColor) !important; |
47 | } | ||
48 | } | 76 | } |
49 | } | 77 | } |
78 | } | ||
79 | |||
80 | /* Make .video-add-nav tabs scrollable on small devices */ | ||
81 | @media screen and (max-width: $small-view) { | ||
82 | @include nav-scroll(); | ||
83 | } | ||
50 | 84 | ||
51 | .upload-video-container { | 85 | @media screen and (max-width: #{$small-view + $menu-width}) { |
52 | border: $border-width $border-type $border-color; | 86 | :host-context(.main-col:not(.expanded)) { |
53 | border-top: none; | 87 | @include nav-scroll(); |
54 | |||
55 | background-color: var(--submenuColor); | ||
56 | border-radius: 3px; | ||
57 | width: 100%; | ||
58 | min-height: 440px; | ||
59 | padding-bottom: 20px; | ||
60 | display: flex; | ||
61 | justify-content: center; | ||
62 | align-items: center; | ||
63 | } | 88 | } |
64 | } | 89 | } |
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 401d8a08f..30ab08ea0 100644 --- a/client/src/app/videos/+video-edit/video-add.component.ts +++ b/client/src/app/videos/+video-edit/video-add.component.ts | |||
@@ -12,9 +12,9 @@ import { ServerConfig } from '@shared/models' | |||
12 | styleUrls: [ './video-add.component.scss' ] | 12 | styleUrls: [ './video-add.component.scss' ] |
13 | }) | 13 | }) |
14 | export class VideoAddComponent implements OnInit, CanComponentDeactivate { | 14 | export class VideoAddComponent implements OnInit, CanComponentDeactivate { |
15 | @ViewChild('videoUpload', { static: false }) videoUpload: VideoUploadComponent | 15 | @ViewChild('videoUpload') videoUpload: VideoUploadComponent |
16 | @ViewChild('videoImportUrl', { static: false }) videoImportUrl: VideoImportUrlComponent | 16 | @ViewChild('videoImportUrl') videoImportUrl: VideoImportUrlComponent |
17 | @ViewChild('videoImportTorrent', { static: false }) videoImportTorrent: VideoImportTorrentComponent | 17 | @ViewChild('videoImportTorrent') videoImportTorrent: VideoImportTorrentComponent |
18 | 18 | ||
19 | secondStepType: 'upload' | 'import-url' | 'import-torrent' | 19 | secondStepType: 'upload' | 'import-url' | 'import-torrent' |
20 | videoName: string | 20 | videoName: string |
diff --git a/client/src/app/videos/+video-edit/video-add.module.ts b/client/src/app/videos/+video-edit/video-add.module.ts index 870f7cb97..b8f5a9a47 100644 --- a/client/src/app/videos/+video-edit/video-add.module.ts +++ b/client/src/app/videos/+video-edit/video-add.module.ts | |||
@@ -3,27 +3,28 @@ import { SharedModule } from '../../shared' | |||
3 | import { VideoEditModule } from './shared/video-edit.module' | 3 | import { VideoEditModule } from './shared/video-edit.module' |
4 | import { VideoAddRoutingModule } from './video-add-routing.module' | 4 | import { VideoAddRoutingModule } from './video-add-routing.module' |
5 | import { VideoAddComponent } from './video-add.component' | 5 | import { VideoAddComponent } from './video-add.component' |
6 | import { DragDropDirective } from './video-add-components/drag-drop.directive' | ||
6 | import { CanDeactivateGuard } from '../../shared/guards/can-deactivate-guard.service' | 7 | import { CanDeactivateGuard } from '../../shared/guards/can-deactivate-guard.service' |
7 | import { VideoUploadComponent } from '@app/videos/+video-edit/video-add-components/video-upload.component' | 8 | import { VideoUploadComponent } from '@app/videos/+video-edit/video-add-components/video-upload.component' |
8 | import { VideoImportUrlComponent } from '@app/videos/+video-edit/video-add-components/video-import-url.component' | 9 | import { VideoImportUrlComponent } from '@app/videos/+video-edit/video-add-components/video-import-url.component' |
9 | import { VideoImportTorrentComponent } from '@app/videos/+video-edit/video-add-components/video-import-torrent.component' | 10 | import { VideoImportTorrentComponent } from '@app/videos/+video-edit/video-add-components/video-import-torrent.component' |
10 | import { ProgressBarModule } from 'primeng/progressbar' | ||
11 | 11 | ||
12 | @NgModule({ | 12 | @NgModule({ |
13 | imports: [ | 13 | imports: [ |
14 | VideoAddRoutingModule, | 14 | VideoAddRoutingModule, |
15 | VideoEditModule, | 15 | VideoEditModule, |
16 | SharedModule, | 16 | SharedModule |
17 | ProgressBarModule | ||
18 | ], | 17 | ], |
19 | declarations: [ | 18 | declarations: [ |
20 | VideoAddComponent, | 19 | VideoAddComponent, |
21 | VideoUploadComponent, | 20 | VideoUploadComponent, |
22 | VideoImportUrlComponent, | 21 | VideoImportUrlComponent, |
23 | VideoImportTorrentComponent | 22 | VideoImportTorrentComponent, |
23 | DragDropDirective | ||
24 | ], | 24 | ], |
25 | exports: [ | 25 | exports: [ |
26 | VideoAddComponent | 26 | VideoAddComponent, |
27 | DragDropDirective | ||
27 | ], | 28 | ], |
28 | providers: [ | 29 | providers: [ |
29 | CanDeactivateGuard | 30 | CanDeactivateGuard |
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 3a9977df6..9b43d91da 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 | |||
@@ -17,7 +17,7 @@ | |||
17 | </div> | 17 | </div> |
18 | 18 | ||
19 | <div class="comment-buttons"> | 19 | <div class="comment-buttons"> |
20 | <button *ngIf="isAddButtonDisplayed()" class="cancel-button" (click)="cancelCommentReply()" i18n> | 20 | <button *ngIf="isAddButtonDisplayed()" class="cancel-button" (click)="cancelCommentReply()" type="button" i18n> |
21 | Cancel | 21 | Cancel |
22 | </button> | 22 | </button> |
23 | <button *ngIf="isAddButtonDisplayed()" [ngClass]="{ disabled: !form.valid || addingComment }" i18n> | 23 | <button *ngIf="isAddButtonDisplayed()" [ngClass]="{ disabled: !form.valid || addingComment }" i18n> |
@@ -29,15 +29,11 @@ | |||
29 | <ng-template #visitorModal let-modal> | 29 | <ng-template #visitorModal let-modal> |
30 | <div class="modal-header"> | 30 | <div class="modal-header"> |
31 | <h4 class="modal-title" id="modal-basic-title" i18n>You are one step away from commenting</h4> | 31 | <h4 class="modal-title" id="modal-basic-title" i18n>You are one step away from commenting</h4> |
32 | <button type="button" class="close" aria-label="Close" (click)="hideVisitorModal()"></button> | 32 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hideVisitorModal()"></my-global-icon> |
33 | </div> | 33 | </div> |
34 | <div class="modal-body"> | 34 | <div class="modal-body"> |
35 | <span i18n> | 35 | <span i18n> |
36 | If you have an account on this instance, you can login: | 36 | You can comment using an account on any ActivityPub-compatible instance. |
37 | </span> | ||
38 | <span class="btn btn-sm mx-3" role="button" (click)="gotoLogin()" i18n>login to comment</span> | ||
39 | <span i18n> | ||
40 | Otherwise, you can comment using an account on any ActivityPub-compatible instance. | ||
41 | On most platforms, you can find the video by typing its URL in the search bar and then comment it | 37 | On most platforms, you can find the video by typing its URL in the search bar and then comment it |
42 | from within the software's interface. | 38 | from within the software's interface. |
43 | </span> | 39 | </span> |
@@ -47,8 +43,14 @@ | |||
47 | <my-remote-subscribe [interact]="true" [uri]="getUri()"></my-remote-subscribe> | 43 | <my-remote-subscribe [interact]="true" [uri]="getUri()"></my-remote-subscribe> |
48 | </div> | 44 | </div> |
49 | <div class="modal-footer inputs"> | 45 | <div class="modal-footer inputs"> |
50 | <span i18n class="action-button action-button-cancel" role="button" (click)="hideVisitorModal()"> | 46 | <input |
51 | Cancel | 47 | type="button" role="button" i18n-value value="Cancel" class="action-button action-button-cancel" |
52 | </span> | 48 | (click)="hideVisitorModal()" (key.enter)="hideVisitorModal()" |
49 | > | ||
50 | |||
51 | <input | ||
52 | type="submit" i18n-value value="Login to comment" class="action-button-submit" | ||
53 | (click)="gotoLogin()" | ||
54 | > | ||
53 | </div> | 55 | </div> |
54 | </ng-template> | 56 | </ng-template> |
diff --git a/client/src/app/videos/+video-watch/comment/video-comment-add.component.scss b/client/src/app/videos/+video-watch/comment/video-comment-add.component.scss index c04727ba0..b3725ab94 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment-add.component.scss +++ b/client/src/app/videos/+video-watch/comment/video-comment-add.component.scss | |||
@@ -30,25 +30,33 @@ form { | |||
30 | } | 30 | } |
31 | } | 31 | } |
32 | 32 | ||
33 | .cancel-button { | ||
34 | font-weight: $font-semibold; | ||
35 | display: inline-block; | ||
36 | padding: 0 10px 0 10px; | ||
37 | white-space: nowrap; | ||
38 | background: transparent; | ||
39 | } | ||
40 | |||
41 | .comment-buttons { | 33 | .comment-buttons { |
42 | display: flex; | 34 | display: flex; |
43 | justify-content: flex-end; | 35 | justify-content: flex-end; |
44 | 36 | ||
45 | button { | 37 | button { |
46 | @include peertube-button; | 38 | @include peertube-button; |
39 | @include disable-outline; | ||
40 | @include disable-default-a-behaviour; | ||
41 | |||
42 | &:not(:last-child) { | ||
43 | margin-right: .5rem; | ||
44 | } | ||
47 | 45 | ||
48 | &:last-child { | 46 | &:last-child { |
49 | @include orange-button; | 47 | @include orange-button; |
50 | } | 48 | } |
51 | } | 49 | } |
50 | |||
51 | .cancel-button { | ||
52 | @include tertiary-button; | ||
53 | |||
54 | font-weight: $font-semibold; | ||
55 | display: inline-block; | ||
56 | padding: 0 10px 0 10px; | ||
57 | white-space: nowrap; | ||
58 | background: transparent; | ||
59 | } | ||
52 | } | 60 | } |
53 | 61 | ||
54 | @media screen and (max-width: 600px) { | 62 | @media screen and (max-width: 600px) { |
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 1be96ad9e..e1a8f6260 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 | |||
@@ -11,7 +11,6 @@ import { VideoCommentService } from './video-comment.service' | |||
11 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 11 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
12 | import { VideoCommentValidatorsService } from '@app/shared/forms/form-validators/video-comment-validators.service' | 12 | import { VideoCommentValidatorsService } from '@app/shared/forms/form-validators/video-comment-validators.service' |
13 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 13 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
14 | import { AuthService } from '@app/core/auth' | ||
15 | 14 | ||
16 | @Component({ | 15 | @Component({ |
17 | selector: 'my-video-comment-add', | 16 | selector: 'my-video-comment-add', |
@@ -25,7 +24,7 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit { | |||
25 | @Input() parentComments: VideoComment[] | 24 | @Input() parentComments: VideoComment[] |
26 | @Input() focusOnInit = false | 25 | @Input() focusOnInit = false |
27 | 26 | ||
28 | @Output() commentCreated = new EventEmitter<VideoCommentCreate>() | 27 | @Output() commentCreated = new EventEmitter<VideoComment>() |
29 | @Output() cancel = new EventEmitter() | 28 | @Output() cancel = new EventEmitter() |
30 | 29 | ||
31 | @ViewChild('visitorModal', { static: true }) visitorModal: NgbModal | 30 | @ViewChild('visitorModal', { static: true }) visitorModal: NgbModal |
@@ -38,7 +37,6 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit { | |||
38 | private videoCommentValidatorsService: VideoCommentValidatorsService, | 37 | private videoCommentValidatorsService: VideoCommentValidatorsService, |
39 | private notifier: Notifier, | 38 | private notifier: Notifier, |
40 | private videoCommentService: VideoCommentService, | 39 | private videoCommentService: VideoCommentService, |
41 | private authService: AuthService, | ||
42 | private modalService: NgbModal, | 40 | private modalService: NgbModal, |
43 | private router: Router | 41 | private router: Router |
44 | ) { | 42 | ) { |
@@ -57,7 +55,7 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit { | |||
57 | 55 | ||
58 | if (this.parentComment) { | 56 | if (this.parentComment) { |
59 | const mentions = this.parentComments | 57 | const mentions = this.parentComments |
60 | .filter(c => c.account.id !== this.user.account.id) // Don't add mention of ourselves | 58 | .filter(c => c.account && c.account.id !== this.user.account.id) // Don't add mention of ourselves |
61 | .map(c => '@' + c.by) | 59 | .map(c => '@' + c.by) |
62 | 60 | ||
63 | const mentionsSet = new Set(mentions) | 61 | const mentionsSet = new Set(mentions) |
@@ -96,7 +94,7 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit { | |||
96 | this.addingComment = true | 94 | this.addingComment = true |
97 | 95 | ||
98 | const commentCreate: VideoCommentCreate = this.form.value | 96 | const commentCreate: VideoCommentCreate = this.form.value |
99 | let obs: Observable<any> | 97 | let obs: Observable<VideoComment> |
100 | 98 | ||
101 | if (this.parentComment) { | 99 | if (this.parentComment) { |
102 | obs = this.addCommentReply(commentCreate) | 100 | obs = this.addCommentReply(commentCreate) |
@@ -139,6 +137,7 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit { | |||
139 | 137 | ||
140 | cancelCommentReply () { | 138 | cancelCommentReply () { |
141 | this.cancel.emit(null) | 139 | this.cancel.emit(null) |
140 | this.form.value['text'] = this.textareaElement.nativeElement.value = '' | ||
142 | } | 141 | } |
143 | 142 | ||
144 | private addCommentReply (commentCreate: VideoCommentCreate) { | 143 | private addCommentReply (commentCreate: VideoCommentCreate) { |
diff --git a/client/src/app/videos/+video-watch/comment/video-comment-thread-tree.model.ts b/client/src/app/videos/+video-watch/comment/video-comment-thread-tree.model.ts new file mode 100644 index 000000000..1566d7369 --- /dev/null +++ b/client/src/app/videos/+video-watch/comment/video-comment-thread-tree.model.ts | |||
@@ -0,0 +1,7 @@ | |||
1 | import { VideoCommentThreadTree as VideoCommentThreadTreeServerModel } from '../../../../../../shared/models/videos/video-comment.model' | ||
2 | import { VideoComment } from '@app/videos/+video-watch/comment/video-comment.model' | ||
3 | |||
4 | export class VideoCommentThreadTree implements VideoCommentThreadTreeServerModel { | ||
5 | comment: VideoComment | ||
6 | children: VideoCommentThreadTree[] | ||
7 | } | ||
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 61f9335d1..868addd58 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,6 +1,5 @@ | |||
1 | import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core' | 1 | import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core' |
2 | import { User, UserRight } from '../../../../../../shared/models/users' | 2 | import { User, UserRight } from '../../../../../../shared/models/users' |
3 | import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model' | ||
4 | import { AuthService } from '@app/core/auth' | 3 | import { AuthService } from '@app/core/auth' |
5 | import { AccountService } from '@app/shared/account/account.service' | 4 | import { AccountService } from '@app/shared/account/account.service' |
6 | import { Video } from '@app/shared/video/video.model' | 5 | import { Video } from '@app/shared/video/video.model' |
@@ -10,6 +9,7 @@ import { Account } from '@app/shared/account/account.model' | |||
10 | import { Notifier } from '@app/core' | 9 | import { Notifier } from '@app/core' |
11 | import { UserService } from '@app/shared' | 10 | import { UserService } from '@app/shared' |
12 | import { Actor } from '@app/shared/actor/actor.model' | 11 | import { Actor } from '@app/shared/actor/actor.model' |
12 | import { VideoCommentThreadTree } from '@app/videos/+video-watch/comment/video-comment-thread-tree.model' | ||
13 | 13 | ||
14 | @Component({ | 14 | @Component({ |
15 | selector: 'my-video-comment', | 15 | selector: 'my-video-comment', |
@@ -98,6 +98,7 @@ export class VideoCommentComponent implements OnInit, OnChanges { | |||
98 | return this.comment.account && this.isUserLoggedIn() && | 98 | return this.comment.account && this.isUserLoggedIn() && |
99 | ( | 99 | ( |
100 | this.user.account.id === this.comment.account.id || | 100 | this.user.account.id === this.comment.account.id || |
101 | this.user.account.id === this.video.account.id || | ||
101 | this.user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) | 102 | this.user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) |
102 | ) | 103 | ) |
103 | } | 104 | } |
@@ -125,7 +126,12 @@ export class VideoCommentComponent implements OnInit, OnChanges { | |||
125 | const html = await this.markdownService.textMarkdownToHTML(this.comment.text, true) | 126 | const html = await this.markdownService.textMarkdownToHTML(this.comment.text, true) |
126 | this.sanitizedCommentHTML = await this.markdownService.processVideoTimestamps(html) | 127 | this.sanitizedCommentHTML = await this.markdownService.processVideoTimestamps(html) |
127 | this.newParentComments = this.parentComments.concat([ this.comment ]) | 128 | this.newParentComments = this.parentComments.concat([ this.comment ]) |
128 | this.commentAccount = new Account(this.comment.account) | 129 | |
129 | this.getUserIfNeeded(this.commentAccount) | 130 | if (this.comment.account) { |
131 | this.commentAccount = new Account(this.comment.account) | ||
132 | this.getUserIfNeeded(this.commentAccount) | ||
133 | } else { | ||
134 | this.comment.account = null | ||
135 | } | ||
130 | } | 136 | } |
131 | } | 137 | } |
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 aaeb0ea9c..171fc4acc 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 | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Account as AccountInterface } from '../../../../../../shared/models/actors' | 1 | import { Account as AccountInterface } from '../../../../../../shared/models/actors' |
2 | import { VideoComment as VideoCommentServerModel } from '../../../../../../shared/models/videos/video-comment.model' | 2 | import { VideoComment as VideoCommentServerModel, VideoCommentCreate } from '../../../../../../shared/models/videos/video-comment.model' |
3 | import { Actor } from '@app/shared/actor/actor.model' | 3 | import { Actor } from '@app/shared/actor/actor.model' |
4 | import { getAbsoluteAPIUrl } from '@app/shared/misc/utils' | 4 | import { getAbsoluteAPIUrl } from '@app/shared/misc/utils' |
5 | 5 | ||
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 a81e5236a..0b0715390 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 | |||
@@ -7,13 +7,14 @@ import { FeedFormat, ResultList } from '../../../../../../shared/models' | |||
7 | import { | 7 | import { |
8 | VideoComment as VideoCommentServerModel, | 8 | VideoComment as VideoCommentServerModel, |
9 | VideoCommentCreate, | 9 | VideoCommentCreate, |
10 | VideoCommentThreadTree | 10 | VideoCommentThreadTree as VideoCommentThreadTreeServerModel |
11 | } from '../../../../../../shared/models/videos/video-comment.model' | 11 | } from '../../../../../../shared/models/videos/video-comment.model' |
12 | import { environment } from '../../../../environments/environment' | 12 | import { environment } from '../../../../environments/environment' |
13 | import { RestExtractor, RestService } from '../../../shared/rest' | 13 | import { RestExtractor, RestService } from '../../../shared/rest' |
14 | import { ComponentPaginationLight } from '../../../shared/rest/component-pagination.model' | 14 | import { ComponentPaginationLight } from '../../../shared/rest/component-pagination.model' |
15 | import { CommentSortField } from '../../../shared/video/sort-field.type' | 15 | import { CommentSortField } from '../../../shared/video/sort-field.type' |
16 | import { VideoComment } from './video-comment.model' | 16 | import { VideoComment } from './video-comment.model' |
17 | import { VideoCommentThreadTree } from '@app/videos/+video-watch/comment/video-comment-thread-tree.model' | ||
17 | 18 | ||
18 | @Injectable() | 19 | @Injectable() |
19 | export class VideoCommentService { | 20 | export class VideoCommentService { |
@@ -76,9 +77,9 @@ export class VideoCommentService { | |||
76 | const url = `${VideoCommentService.BASE_VIDEO_URL + videoId}/comment-threads/${threadId}` | 77 | const url = `${VideoCommentService.BASE_VIDEO_URL + videoId}/comment-threads/${threadId}` |
77 | 78 | ||
78 | return this.authHttp | 79 | return this.authHttp |
79 | .get(url) | 80 | .get<VideoCommentThreadTreeServerModel>(url) |
80 | .pipe( | 81 | .pipe( |
81 | map(tree => this.extractVideoCommentTree(tree as VideoCommentThreadTree)), | 82 | map(tree => this.extractVideoCommentTree(tree)), |
82 | catchError(err => this.restExtractor.handleError(err)) | 83 | catchError(err => this.restExtractor.handleError(err)) |
83 | ) | 84 | ) |
84 | } | 85 | } |
@@ -138,12 +139,12 @@ export class VideoCommentService { | |||
138 | return { data: comments, total: totalComments } | 139 | return { data: comments, total: totalComments } |
139 | } | 140 | } |
140 | 141 | ||
141 | private extractVideoCommentTree (tree: VideoCommentThreadTree) { | 142 | private extractVideoCommentTree (tree: VideoCommentThreadTreeServerModel) { |
142 | if (!tree) return tree | 143 | if (!tree) return tree as VideoCommentThreadTree |
143 | 144 | ||
144 | tree.comment = new VideoComment(tree.comment) | 145 | tree.comment = new VideoComment(tree.comment) |
145 | tree.children.forEach(c => this.extractVideoCommentTree(c)) | 146 | tree.children.forEach(c => this.extractVideoCommentTree(c)) |
146 | 147 | ||
147 | return tree | 148 | return tree as VideoCommentThreadTree |
148 | } | 149 | } |
149 | } | 150 | } |
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 2bf52ab86..a21042f09 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 | |||
@@ -13,7 +13,7 @@ | |||
13 | 13 | ||
14 | <div ngbDropdown class="d-inline-block ml-4"> | 14 | <div ngbDropdown class="d-inline-block ml-4"> |
15 | <button class="btn btn-sm btn-outline-secondary" id="dropdownSortComments" ngbDropdownToggle i18n> | 15 | <button class="btn btn-sm btn-outline-secondary" id="dropdownSortComments" ngbDropdownToggle i18n> |
16 | Sort by | 16 | SORT BY |
17 | </button> | 17 | </button> |
18 | <div ngbDropdownMenu aria-labelledby="dropdownSortComments"> | 18 | <div ngbDropdownMenu aria-labelledby="dropdownSortComments"> |
19 | <button (click)="handleSortChange('-createdAt')" ngbDropdownItem i18n>Most recent first (default)</button> | 19 | <button (click)="handleSortChange('-createdAt')" ngbDropdownItem i18n>Most recent first (default)</button> |
@@ -38,7 +38,8 @@ | |||
38 | (nearOfBottom)="onNearOfBottom()" | 38 | (nearOfBottom)="onNearOfBottom()" |
39 | [dataObservable]="onDataSubject.asObservable()" | 39 | [dataObservable]="onDataSubject.asObservable()" |
40 | > | 40 | > |
41 | <div #commentHighlightBlock id="highlighted-comment"> | 41 | <div> |
42 | <div class="anchor" #commentHighlightBlock id="highlighted-comment"></div> | ||
42 | <my-video-comment | 43 | <my-video-comment |
43 | *ngIf="highlightedThread" | 44 | *ngIf="highlightedThread" |
44 | [comment]="highlightedThread" | 45 | [comment]="highlightedThread" |
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 f95ff5aba..5ed1ac629 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 | |||
@@ -17,8 +17,20 @@ | |||
17 | font-size: 13px; | 17 | font-size: 13px; |
18 | } | 18 | } |
19 | 19 | ||
20 | .title-block .title-page { | 20 | .title-block { |
21 | margin-right: 0; | 21 | .title-page { |
22 | margin-right: 0; | ||
23 | } | ||
24 | |||
25 | my-feed { | ||
26 | display: inline-block; | ||
27 | margin-left: 5px; | ||
28 | opacity: 0; | ||
29 | transition: ease-in .2s opacity; | ||
30 | } | ||
31 | &:hover my-feed { | ||
32 | opacity: 1; | ||
33 | } | ||
22 | } | 34 | } |
23 | 35 | ||
24 | #dropdownSortComments { | 36 | #dropdownSortComments { |
@@ -28,11 +40,6 @@ | |||
28 | transform: translateY(-7%); | 40 | transform: translateY(-7%); |
29 | } | 41 | } |
30 | 42 | ||
31 | my-feed { | ||
32 | display: inline-block; | ||
33 | margin-left: 5px; | ||
34 | } | ||
35 | |||
36 | @media screen and (max-width: 600px) { | 43 | @media screen and (max-width: 600px) { |
37 | .view-replies { | 44 | .view-replies { |
38 | margin-left: 46px; | 45 | margin-left: 46px; |
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 974c61d6c..c6c28e3f7 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,8 +1,7 @@ | |||
1 | import { Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild, Output, EventEmitter } from '@angular/core' | 1 | import { Component, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core' |
2 | import { ActivatedRoute } from '@angular/router' | 2 | import { ActivatedRoute } from '@angular/router' |
3 | import { ConfirmService, Notifier } from '@app/core' | 3 | import { ConfirmService, Notifier } from '@app/core' |
4 | import { Subject, Subscription } from 'rxjs' | 4 | import { Subject, Subscription } from 'rxjs' |
5 | import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model' | ||
6 | import { AuthService } from '../../../core/auth' | 5 | import { AuthService } from '../../../core/auth' |
7 | import { ComponentPagination, hasMoreItems } from '../../../shared/rest/component-pagination.model' | 6 | import { ComponentPagination, hasMoreItems } from '../../../shared/rest/component-pagination.model' |
8 | import { User } from '../../../shared/users' | 7 | import { User } from '../../../shared/users' |
@@ -13,6 +12,7 @@ import { VideoCommentService } from './video-comment.service' | |||
13 | import { I18n } from '@ngx-translate/i18n-polyfill' | 12 | import { I18n } from '@ngx-translate/i18n-polyfill' |
14 | import { Syndication } from '@app/shared/video/syndication.model' | 13 | import { Syndication } from '@app/shared/video/syndication.model' |
15 | import { HooksService } from '@app/core/plugins/hooks.service' | 14 | import { HooksService } from '@app/core/plugins/hooks.service' |
15 | import { VideoCommentThreadTree } from '@app/videos/+video-watch/comment/video-comment-thread-tree.model' | ||
16 | 16 | ||
17 | @Component({ | 17 | @Component({ |
18 | selector: 'my-video-comments', | 18 | selector: 'my-video-comments', |
@@ -20,7 +20,7 @@ import { HooksService } from '@app/core/plugins/hooks.service' | |||
20 | styleUrls: ['./video-comments.component.scss'] | 20 | styleUrls: ['./video-comments.component.scss'] |
21 | }) | 21 | }) |
22 | export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | 22 | export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { |
23 | @ViewChild('commentHighlightBlock', { static: false }) commentHighlightBlock: ElementRef | 23 | @ViewChild('commentHighlightBlock') commentHighlightBlock: ElementRef |
24 | @Input() video: VideoDetails | 24 | @Input() video: VideoDetails |
25 | @Input() user: User | 25 | @Input() user: User |
26 | 26 | ||
@@ -96,6 +96,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
96 | res => { | 96 | res => { |
97 | this.threadComments[commentId] = res | 97 | this.threadComments[commentId] = res |
98 | this.threadLoading[commentId] = false | 98 | this.threadLoading[commentId] = false |
99 | this.hooks.runAction('action:video-watch.video-thread-replies.loaded', 'video-watch', { data: res }) | ||
99 | 100 | ||
100 | if (highlightThread) { | 101 | if (highlightThread) { |
101 | this.highlightedThread = new VideoComment(res.comment) | 102 | this.highlightedThread = new VideoComment(res.comment) |
@@ -130,6 +131,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
130 | this.componentPagination.totalItems = res.total | 131 | this.componentPagination.totalItems = res.total |
131 | 132 | ||
132 | this.onDataSubject.next(res.data) | 133 | this.onDataSubject.next(res.data) |
134 | this.hooks.runAction('action:video-watch.video-threads.loaded', 'video-watch', { data: this.componentPagination }) | ||
133 | }, | 135 | }, |
134 | 136 | ||
135 | err => this.notifier.error(err.message) | 137 | err => this.notifier.error(err.message) |
@@ -167,7 +169,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
167 | let message = 'Do you really want to delete this comment?' | 169 | let message = 'Do you really want to delete this comment?' |
168 | 170 | ||
169 | if (commentToDelete.isLocal) { | 171 | if (commentToDelete.isLocal) { |
170 | message += this.i18n(' The deletion will be sent to remote instances, so they remove the comment too.') | 172 | message += this.i18n(' The deletion will be sent to remote instances so they can reflect the change.') |
171 | } else { | 173 | } else { |
172 | message += this.i18n(' It is a remote comment, so the deletion will only be effective on your instance.') | 174 | message += this.i18n(' It is a remote comment, so the deletion will only be effective on your instance.') |
173 | } | 175 | } |
@@ -181,7 +183,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
181 | // Mark the comment as deleted | 183 | // Mark the comment as deleted |
182 | this.softDeleteComment(commentToDelete) | 184 | this.softDeleteComment(commentToDelete) |
183 | 185 | ||
184 | if (this.highlightedThread.id === commentToDelete.id) this.highlightedThread = undefined | 186 | if (this.highlightedThread?.id === commentToDelete.id) this.highlightedThread = undefined |
185 | }, | 187 | }, |
186 | 188 | ||
187 | err => this.notifier.error(err.message) | 189 | err => this.notifier.error(err.message) |
@@ -193,9 +195,8 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
193 | } | 195 | } |
194 | 196 | ||
195 | onNearOfBottom () { | 197 | onNearOfBottom () { |
196 | this.componentPagination.currentPage++ | ||
197 | |||
198 | if (hasMoreItems(this.componentPagination)) { | 198 | if (hasMoreItems(this.componentPagination)) { |
199 | this.componentPagination.currentPage++ | ||
199 | this.loadMoreThreads() | 200 | this.loadMoreThreads() |
200 | } | 201 | } |
201 | } | 202 | } |
@@ -219,7 +220,6 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
219 | this.componentPagination.totalItems = null | 220 | this.componentPagination.totalItems = null |
220 | 221 | ||
221 | this.syndicationItems = this.videoCommentService.getVideoCommentsFeeds(this.video.uuid) | 222 | this.syndicationItems = this.videoCommentService.getVideoCommentsFeeds(this.video.uuid) |
222 | |||
223 | this.loadMoreThreads() | 223 | this.loadMoreThreads() |
224 | } | 224 | } |
225 | } | 225 | } |
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 549a9f30e..5e6a2d518 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 | |||
@@ -27,29 +27,33 @@ | |||
27 | <div class="video"> | 27 | <div class="video"> |
28 | <div class="title-page title-page-single" *ngIf="hasPlaylist()" i18n>Share the video</div> | 28 | <div class="title-page title-page-single" *ngIf="hasPlaylist()" i18n>Share the video</div> |
29 | 29 | ||
30 | <ngb-tabset class="root-tabset bootstrap" (tabChange)="onTabChange($event)"> | 30 | <div ngbNav #nav="ngbNav" class="nav-tabs" [(activeId)]="activeId"> |
31 | 31 | ||
32 | <ngb-tab i18n-title title="URL" id="url"> | 32 | <ng-container ngbNavItem="url"> |
33 | <ng-template ngbTabContent> | 33 | <a ngbNavLink i18n>URL</a> |
34 | 34 | ||
35 | <div class="tab-content"> | 35 | <ng-template ngbNavContent> |
36 | <div class="nav-content"> | ||
36 | <my-input-readonly-copy [value]="getVideoUrl()"></my-input-readonly-copy> | 37 | <my-input-readonly-copy [value]="getVideoUrl()"></my-input-readonly-copy> |
37 | </div> | 38 | </div> |
38 | |||
39 | </ng-template> | 39 | </ng-template> |
40 | </ngb-tab> | 40 | </ng-container> |
41 | |||
42 | <ng-container ngbNavItem="qrcode"> | ||
43 | <a ngbNavLink i18n>QR-Code</a> | ||
41 | 44 | ||
42 | <ngb-tab i18n-title title="QR-Code" id="qrcode"> | 45 | <ng-template ngbNavContent> |
43 | <ng-template ngbTabContent> | 46 | <div class="nav-content"> |
44 | <div class="tab-content"> | 47 | <qrcode [qrdata]="getVideoUrl()" [size]="256" level="Q"></qrcode> |
45 | <qrcode [qrdata]="getVideoUrl()" size="256" level="Q"></qrcode> | ||
46 | </div> | 48 | </div> |
47 | </ng-template> | 49 | </ng-template> |
48 | </ngb-tab> | 50 | </ng-container> |
51 | |||
52 | <ng-container ngbNavItem="embed"> | ||
53 | <a ngbNavLink i18n>Embed</a> | ||
49 | 54 | ||
50 | <ngb-tab i18n-title title="Embed" id="embed"> | 55 | <ng-template ngbNavContent> |
51 | <ng-template ngbTabContent> | 56 | <div class="nav-content"> |
52 | <div class="tab-content"> | ||
53 | <my-input-readonly-copy [value]="getVideoIframeCode()"></my-input-readonly-copy> | 57 | <my-input-readonly-copy [value]="getVideoIframeCode()"></my-input-readonly-copy> |
54 | 58 | ||
55 | <div i18n *ngIf="notSecure()" class="alert alert-warning"> | 59 | <div i18n *ngIf="notSecure()" class="alert alert-warning"> |
@@ -57,9 +61,11 @@ | |||
57 | </div> | 61 | </div> |
58 | </div> | 62 | </div> |
59 | </ng-template> | 63 | </ng-template> |
60 | </ngb-tab> | 64 | </ng-container> |
61 | 65 | ||
62 | </ngb-tabset> | 66 | </div> |
67 | |||
68 | <div [ngbNavOutlet]="nav"></div> | ||
63 | 69 | ||
64 | <div class="filters"> | 70 | <div class="filters"> |
65 | <div> | 71 | <div> |
@@ -92,26 +98,6 @@ | |||
92 | </div> | 98 | </div> |
93 | </div> | 99 | </div> |
94 | 100 | ||
95 | <div (click)="isAdvancedCustomizationCollapsed = !isAdvancedCustomizationCollapsed" role="button" class="advanced-filters-button" | ||
96 | [attr.aria-expanded]="!isAdvancedCustomizationCollapsed" aria-controls="collapseBasic"> | ||
97 | |||
98 | <ng-container *ngIf="isAdvancedCustomizationCollapsed"> | ||
99 | <span class="glyphicon glyphicon-menu-down"></span> | ||
100 | |||
101 | <ng-container i18n> | ||
102 | More customization | ||
103 | </ng-container> | ||
104 | </ng-container> | ||
105 | |||
106 | <ng-container *ngIf="!isAdvancedCustomizationCollapsed"> | ||
107 | <span class="glyphicon glyphicon-menu-up"></span> | ||
108 | |||
109 | <ng-container i18n> | ||
110 | Less customization | ||
111 | </ng-container> | ||
112 | </ng-container> | ||
113 | </div> | ||
114 | |||
115 | <div class="advanced-filters collapse-transition" [ngbCollapse]="isAdvancedCustomizationCollapsed"> | 101 | <div class="advanced-filters collapse-transition" [ngbCollapse]="isAdvancedCustomizationCollapsed"> |
116 | <div> | 102 | <div> |
117 | <div class="form-group stop-at"> | 103 | <div class="form-group stop-at"> |
@@ -174,12 +160,28 @@ | |||
174 | </div> | 160 | </div> |
175 | </ng-container> | 161 | </ng-container> |
176 | </div> | 162 | </div> |
163 | |||
164 | <div (click)="isAdvancedCustomizationCollapsed = !isAdvancedCustomizationCollapsed" role="button" class="advanced-filters-button" | ||
165 | [attr.aria-expanded]="!isAdvancedCustomizationCollapsed" aria-controls="collapseBasic"> | ||
166 | |||
167 | <ng-container *ngIf="isAdvancedCustomizationCollapsed"> | ||
168 | <span class="glyphicon glyphicon-menu-down"></span> | ||
169 | |||
170 | <ng-container i18n> | ||
171 | More customization | ||
172 | </ng-container> | ||
173 | </ng-container> | ||
174 | |||
175 | <ng-container *ngIf="!isAdvancedCustomizationCollapsed"> | ||
176 | <span class="glyphicon glyphicon-menu-up"></span> | ||
177 | |||
178 | <ng-container i18n> | ||
179 | Less customization | ||
180 | </ng-container> | ||
181 | </ng-container> | ||
182 | </div> | ||
177 | </div> | 183 | </div> |
178 | </div> | 184 | </div> |
179 | </div> | 185 | </div> |
180 | 186 | ||
181 | <div class="modal-footer inputs"> | ||
182 | <span i18n class="action-button action-button-cancel" (click)="hide()">Close</span> | ||
183 | </div> | ||
184 | |||
185 | </ng-template> | 187 | </ng-template> |
diff --git a/client/src/app/videos/+video-watch/modal/video-share.component.scss b/client/src/app/videos/+video-watch/modal/video-share.component.scss index 8b5952da6..091d4dc3b 100644 --- a/client/src/app/videos/+video-watch/modal/video-share.component.scss +++ b/client/src/app/videos/+video-watch/modal/video-share.component.scss | |||
@@ -17,15 +17,11 @@ my-input-readonly-copy { | |||
17 | @include peertube-select-container(200px); | 17 | @include peertube-select-container(200px); |
18 | } | 18 | } |
19 | 19 | ||
20 | .action-button-cancel { | ||
21 | margin-right: 0 !important; | ||
22 | } | ||
23 | |||
24 | .qr-code-group { | 20 | .qr-code-group { |
25 | text-align: center; | 21 | text-align: center; |
26 | } | 22 | } |
27 | 23 | ||
28 | .tab-content { | 24 | .nav-content { |
29 | margin-top: 30px; | 25 | margin-top: 30px; |
30 | display: flex; | 26 | display: flex; |
31 | justify-content: center; | 27 | justify-content: center; |
@@ -39,14 +35,12 @@ my-input-readonly-copy { | |||
39 | 35 | ||
40 | .filters { | 36 | .filters { |
41 | margin-top: 30px; | 37 | margin-top: 30px; |
42 | padding-top: 30px; | ||
43 | border-top: 1px solid $separator-border-color; | ||
44 | 38 | ||
45 | .advanced-filters-button { | 39 | .advanced-filters-button { |
46 | display: flex; | 40 | display: flex; |
47 | justify-content: center; | 41 | justify-content: center; |
48 | align-items: center; | 42 | align-items: center; |
49 | margin-top: 30px; | 43 | margin-top: 20px; |
50 | font-size: 16px; | 44 | font-size: 16px; |
51 | font-weight: $font-semibold; | 45 | font-weight: $font-semibold; |
52 | cursor: pointer; | 46 | cursor: pointer; |
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 a2b38b3a0..3550556a0 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,9 +1,7 @@ | |||
1 | import { Component, ElementRef, Input, ViewChild } from '@angular/core' | 1 | import { Component, ElementRef, Input, ViewChild } from '@angular/core' |
2 | import { Notifier } from '@app/core' | ||
3 | import { VideoDetails } from '../../../shared/video/video-details.model' | 2 | import { VideoDetails } from '../../../shared/video/video-details.model' |
4 | import { buildVideoEmbed, buildVideoLink } from '../../../../assets/player/utils' | 3 | import { buildVideoEmbed, buildVideoLink } from '../../../../assets/player/utils' |
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | 4 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
6 | import { NgbModal, NgbTabChangeEvent } from '@ng-bootstrap/ng-bootstrap' | ||
7 | import { VideoCaption } from '@shared/models' | 5 | import { VideoCaption } from '@shared/models' |
8 | import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' | 6 | import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' |
9 | 7 | ||
@@ -37,7 +35,7 @@ export class VideoShareComponent { | |||
37 | @Input() videoCaptions: VideoCaption[] = [] | 35 | @Input() videoCaptions: VideoCaption[] = [] |
38 | @Input() playlist: VideoPlaylist = null | 36 | @Input() playlist: VideoPlaylist = null |
39 | 37 | ||
40 | activeId: 'url' | 'qrcode' | 'embed' | 38 | activeId: 'url' | 'qrcode' | 'embed' = 'url' |
41 | customizations: Customizations | 39 | customizations: Customizations |
42 | isAdvancedCustomizationCollapsed = true | 40 | isAdvancedCustomizationCollapsed = true |
43 | includeVideoInPlaylist = false | 41 | includeVideoInPlaylist = false |
@@ -74,7 +72,7 @@ export class VideoShareComponent { | |||
74 | controls: true | 72 | controls: true |
75 | } | 73 | } |
76 | 74 | ||
77 | this.modalService.open(this.modal) | 75 | this.modalService.open(this.modal, { centered: true }) |
78 | } | 76 | } |
79 | 77 | ||
80 | getVideoIframeCode () { | 78 | getVideoIframeCode () { |
@@ -103,10 +101,6 @@ export class VideoShareComponent { | |||
103 | return window.location.protocol === 'http:' | 101 | return window.location.protocol === 'http:' |
104 | } | 102 | } |
105 | 103 | ||
106 | onTabChange (event: NgbTabChangeEvent) { | ||
107 | this.activeId = event.nextId as any | ||
108 | } | ||
109 | |||
110 | isInEmbedTab () { | 104 | isInEmbedTab () { |
111 | return this.activeId === 'embed' | 105 | return this.activeId === 'embed' |
112 | } | 106 | } |
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 608a4632b..935656d23 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 | |||
@@ -7,6 +7,9 @@ | |||
7 | <div class="modal-body" [innerHTML]="videoHTMLSupport"></div> | 7 | <div class="modal-body" [innerHTML]="videoHTMLSupport"></div> |
8 | 8 | ||
9 | <div class="modal-footer inputs"> | 9 | <div class="modal-footer inputs"> |
10 | <span i18n class="action-button action-button-cancel" (click)="hide()">Maybe later</span> | 10 | <input |
11 | type="button" role="button" i18n-value value="Maybe later" class="action-button action-button-cancel" | ||
12 | (click)="hide()" (key.enter)="hide()" | ||
13 | > | ||
11 | </div> | 14 | </div> |
12 | </ng-template> | 15 | </ng-template> |
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 b56a51fbf..0058172f2 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 | |||
@@ -21,7 +21,7 @@ export class VideoSupportComponent { | |||
21 | ) { } | 21 | ) { } |
22 | 22 | ||
23 | show () { | 23 | show () { |
24 | this.modalService.open(this.modal) | 24 | this.modalService.open(this.modal, { centered: true }) |
25 | 25 | ||
26 | this.markdownService.enhancedMarkdownToHTML(this.video.support) | 26 | this.markdownService.enhancedMarkdownToHTML(this.video.support) |
27 | .then(r => this.videoHTMLSupport = r) | 27 | .then(r => this.videoHTMLSupport = r) |
diff --git a/client/src/app/videos/+video-watch/video-watch-playlist.component.ts b/client/src/app/videos/+video-watch/video-watch-playlist.component.ts index c5ed36000..827c34d41 100644 --- a/client/src/app/videos/+video-watch/video-watch-playlist.component.ts +++ b/client/src/app/videos/+video-watch/video-watch-playlist.component.ts | |||
@@ -9,6 +9,7 @@ import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist. | |||
9 | import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model' | 9 | import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model' |
10 | import { peertubeLocalStorage, peertubeSessionStorage } from '@app/shared/misc/peertube-web-storage' | 10 | import { peertubeLocalStorage, peertubeSessionStorage } from '@app/shared/misc/peertube-web-storage' |
11 | import { I18n } from '@ngx-translate/i18n-polyfill' | 11 | import { I18n } from '@ngx-translate/i18n-polyfill' |
12 | import { SessionStorageService, LocalStorageService } from '@app/shared/misc/storage.service' | ||
12 | 13 | ||
13 | @Component({ | 14 | @Component({ |
14 | selector: 'my-video-watch-playlist', | 15 | selector: 'my-video-watch-playlist', |
@@ -42,16 +43,18 @@ export class VideoWatchPlaylistComponent { | |||
42 | private notifier: Notifier, | 43 | private notifier: Notifier, |
43 | private i18n: I18n, | 44 | private i18n: I18n, |
44 | private videoPlaylist: VideoPlaylistService, | 45 | private videoPlaylist: VideoPlaylistService, |
46 | private localStorageService: LocalStorageService, | ||
47 | private sessionStorageService: SessionStorageService, | ||
45 | private router: Router | 48 | private router: Router |
46 | ) { | 49 | ) { |
47 | // defaults to true | 50 | // defaults to true |
48 | this.autoPlayNextVideoPlaylist = this.auth.isLoggedIn() | 51 | this.autoPlayNextVideoPlaylist = this.auth.isLoggedIn() |
49 | ? this.auth.getUser().autoPlayNextVideoPlaylist | 52 | ? this.auth.getUser().autoPlayNextVideoPlaylist |
50 | : peertubeLocalStorage.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) !== 'false' | 53 | : this.localStorageService.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) !== 'false' |
51 | this.setAutoPlayNextVideoPlaylistSwitchText() | 54 | this.setAutoPlayNextVideoPlaylistSwitchText() |
52 | 55 | ||
53 | // defaults to false | 56 | // defaults to false |
54 | this.loopPlaylist = peertubeSessionStorage.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true' | 57 | this.loopPlaylist = this.sessionStorageService.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true' |
55 | this.setLoopPlaylistSwitchText() | 58 | this.setLoopPlaylistSwitchText() |
56 | } | 59 | } |
57 | 60 | ||
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 bc3a3ffdd..0244860dd 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.html +++ b/client/src/app/videos/+video-watch/video-watch.component.html | |||
@@ -84,12 +84,12 @@ | |||
84 | placement="bottom auto" | 84 | placement="bottom auto" |
85 | > | 85 | > |
86 | <my-global-icon iconName="support"></my-global-icon> | 86 | <my-global-icon iconName="support"></my-global-icon> |
87 | <span class="icon-text" i18n>Support</span> | 87 | <span class="icon-text" i18n>SUPPORT</span> |
88 | </div> | 88 | </div> |
89 | 89 | ||
90 | <div (click)="showShareModal()" class="action-button" role="button"> | 90 | <div (click)="showShareModal()" class="action-button" role="button"> |
91 | <my-global-icon iconName="share"></my-global-icon> | 91 | <my-global-icon iconName="share"></my-global-icon> |
92 | <span class="icon-text" i18n>Share</span> | 92 | <span class="icon-text" i18n>SHARE</span> |
93 | </div> | 93 | </div> |
94 | 94 | ||
95 | <div | 95 | <div |
@@ -100,7 +100,7 @@ | |||
100 | > | 100 | > |
101 | <div class="action-button action-button-save" ngbDropdownToggle role="button"> | 101 | <div class="action-button action-button-save" ngbDropdownToggle role="button"> |
102 | <my-global-icon iconName="playlist-add"></my-global-icon> | 102 | <my-global-icon iconName="playlist-add"></my-global-icon> |
103 | <span class="icon-text" i18n>Save</span> | 103 | <span class="icon-text" i18n>SAVE</span> |
104 | </div> | 104 | </div> |
105 | 105 | ||
106 | <div ngbDropdownMenu> | 106 | <div ngbDropdownMenu> |
@@ -188,6 +188,11 @@ | |||
188 | <span class="video-attribute-value">{{ video.privacy.label }}</span> | 188 | <span class="video-attribute-value">{{ video.privacy.label }}</span> |
189 | </div> | 189 | </div> |
190 | 190 | ||
191 | <div *ngIf="video.isLocal === false" class="video-attribute"> | ||
192 | <span i18n class="video-attribute-label">Origin instance</span> | ||
193 | <a class="video-attribute-value" target="_blank" rel="noopener noreferrer" [href]="video.originInstanceUrl">{{ video.originInstanceHost }}</a> | ||
194 | </div> | ||
195 | |||
191 | <div *ngIf="!!video.originallyPublishedAt" class="video-attribute"> | 196 | <div *ngIf="!!video.originallyPublishedAt" class="video-attribute"> |
192 | <span i18n class="video-attribute-label">Originally published</span> | 197 | <span i18n class="video-attribute-label">Originally published</span> |
193 | <span class="video-attribute-value">{{ video.originallyPublishedAt | date: 'dd MMMM yyyy' }}</span> | 198 | <span class="video-attribute-value">{{ video.originallyPublishedAt | date: 'dd MMMM yyyy' }}</span> |
@@ -252,14 +257,16 @@ | |||
252 | 257 | ||
253 | <div class="row privacy-concerns" *ngIf="hasAlreadyAcceptedPrivacyConcern === false"> | 258 | <div class="row privacy-concerns" *ngIf="hasAlreadyAcceptedPrivacyConcern === false"> |
254 | <div class="privacy-concerns-text"> | 259 | <div class="privacy-concerns-text"> |
255 | <strong i18n>Friendly Reminder: </strong> | 260 | <span class="mr-2"> |
256 | <ng-container i18n> | 261 | <strong i18n>Friendly Reminder: </strong> |
257 | the sharing system used for this video implies that some technical information about your system (such as a public IP address) can be sent to other peers. | 262 | <ng-container i18n> |
258 | </ng-container> | 263 | the sharing system used for this video implies that some technical information about your system (such as a public IP address) can be sent to other peers. |
259 | <a i18n i18n-title title="Get more information" target="_blank" rel="noopener noreferrer" href="/about/peertube">More information</a> | 264 | </ng-container> |
265 | </span> | ||
266 | <a i18n i18n-title title="Get more information" target="_blank" rel="noopener noreferrer" href="/about/peertube#privacy">More information</a> | ||
260 | </div> | 267 | </div> |
261 | 268 | ||
262 | <div i18n class="privacy-concerns-okay" (click)="acceptedPrivacyConcern()"> | 269 | <div i18n class="privacy-concerns-button privacy-concerns-okay" (click)="acceptedPrivacyConcern()"> |
263 | OK | 270 | OK |
264 | </div> | 271 | </div> |
265 | </div> | 272 | </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 c92f773e4..977312a83 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.scss +++ b/client/src/app/videos/+video-watch/video-watch.component.scss | |||
@@ -53,7 +53,6 @@ $video-info-margin-left: 44px; | |||
53 | background-color: #000; | 53 | background-color: #000; |
54 | display: flex; | 54 | display: flex; |
55 | justify-content: center; | 55 | justify-content: center; |
56 | margin: 0 -15px; | ||
57 | 56 | ||
58 | #videojs-wrapper { | 57 | #videojs-wrapper { |
59 | display: flex; | 58 | display: flex; |
@@ -443,6 +442,7 @@ my-video-comments { | |||
443 | 442 | ||
444 | // If the view is not expanded, take into account the menu | 443 | // If the view is not expanded, take into account the menu |
445 | .privacy-concerns { | 444 | .privacy-concerns { |
445 | z-index: z(dropdown) + 1; | ||
446 | width: calc(100% - #{$menu-width}); | 446 | width: calc(100% - #{$menu-width}); |
447 | } | 447 | } |
448 | 448 | ||
@@ -462,13 +462,14 @@ my-video-comments { | |||
462 | .privacy-concerns { | 462 | .privacy-concerns { |
463 | position: fixed; | 463 | position: fixed; |
464 | bottom: 0; | 464 | bottom: 0; |
465 | z-index: z(privacymsg); | ||
465 | 466 | ||
466 | padding: 5px 15px; | 467 | padding: 5px 15px; |
467 | 468 | ||
468 | display: flex; | 469 | display: flex; |
469 | flex-wrap: nowrap; | 470 | flex-wrap: nowrap; |
470 | align-items: center; | 471 | align-items: center; |
471 | justify-content: flex-start; | 472 | justify-content: space-between; |
472 | background-color: rgba(0, 0, 0, 0.9); | 473 | background-color: rgba(0, 0, 0, 0.9); |
473 | color: #fff; | 474 | color: #fff; |
474 | 475 | ||
@@ -487,11 +488,11 @@ my-video-comments { | |||
487 | } | 488 | } |
488 | } | 489 | } |
489 | 490 | ||
490 | .privacy-concerns-okay { | 491 | .privacy-concerns-button { |
491 | background-color: var(--mainColor); | ||
492 | padding: 5px 8px 5px 7px; | 492 | padding: 5px 8px 5px 7px; |
493 | margin-left: auto; | 493 | margin-left: auto; |
494 | border-radius: 3px; | 494 | border-radius: 3px; |
495 | white-space: nowrap; | ||
495 | cursor: pointer; | 496 | cursor: pointer; |
496 | transition: background-color 0.3s; | 497 | transition: background-color 0.3s; |
497 | font-weight: $font-semibold; | 498 | font-weight: $font-semibold; |
@@ -500,6 +501,11 @@ my-video-comments { | |||
500 | background-color: #000; | 501 | background-color: #000; |
501 | } | 502 | } |
502 | } | 503 | } |
504 | |||
505 | .privacy-concerns-okay { | ||
506 | background-color: var(--mainColor); | ||
507 | margin-left: 10px; | ||
508 | } | ||
503 | } | 509 | } |
504 | 510 | ||
505 | @media screen and (max-width: 1600px) { | 511 | @media screen and (max-width: 1600px) { |
@@ -545,7 +551,8 @@ my-video-comments { | |||
545 | 551 | ||
546 | @media screen and (max-width: 600px) { | 552 | @media screen and (max-width: 600px) { |
547 | .video-bottom { | 553 | .video-bottom { |
548 | margin: 20px 0 0 0 !important; | 554 | margin-top: 20px !important; |
555 | margin-bottom: 20px !important; | ||
549 | 556 | ||
550 | .video-info { | 557 | .video-info { |
551 | padding: 0; | 558 | padding: 0; |
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 e09e44809..51e484275 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts | |||
@@ -2,7 +2,7 @@ import { catchError } from 'rxjs/operators' | |||
2 | import { ChangeDetectorRef, Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' | 2 | import { ChangeDetectorRef, Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' |
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { RedirectService } from '@app/core/routing/redirect.service' | 4 | import { RedirectService } from '@app/core/routing/redirect.service' |
5 | import { peertubeLocalStorage, peertubeSessionStorage } from '@app/shared/misc/peertube-web-storage' | 5 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-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 { AuthUser, Notifier, ServerService } from '@app/core' | 8 | import { AuthUser, Notifier, ServerService } from '@app/core' |
@@ -10,7 +10,7 @@ import { forkJoin, Observable, Subscription } from 'rxjs' | |||
10 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' | 10 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' |
11 | import { ServerConfig, UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '../../../../../shared' | 11 | import { ServerConfig, UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '../../../../../shared' |
12 | import { AuthService, ConfirmService } from '../../core' | 12 | import { AuthService, ConfirmService } from '../../core' |
13 | import { RestExtractor } from '../../shared' | 13 | import { RestExtractor, UserService } from '../../shared' |
14 | import { VideoDetails } from '../../shared/video/video-details.model' | 14 | import { VideoDetails } from '../../shared/video/video-details.model' |
15 | import { VideoService } from '../../shared/video/video.service' | 15 | import { VideoService } from '../../shared/video/video.service' |
16 | import { VideoShareComponent } from './modal/video-share.component' | 16 | import { VideoShareComponent } from './modal/video-share.component' |
@@ -35,7 +35,6 @@ import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watc | |||
35 | import { getStoredP2PEnabled, getStoredTheater } from '../../../assets/player/peertube-player-local-storage' | 35 | import { getStoredP2PEnabled, getStoredTheater } from '../../../assets/player/peertube-player-local-storage' |
36 | import { HooksService } from '@app/core/plugins/hooks.service' | 36 | import { HooksService } from '@app/core/plugins/hooks.service' |
37 | import { PlatformLocation } from '@angular/common' | 37 | import { PlatformLocation } from '@angular/common' |
38 | import { RecommendedVideosComponent } from '../recommendations/recommended-videos.component' | ||
39 | import { scrollToTop, isXPercentInViewport } from '@app/shared/misc/utils' | 38 | import { scrollToTop, isXPercentInViewport } from '@app/shared/misc/utils' |
40 | 39 | ||
41 | @Component({ | 40 | @Component({ |
@@ -47,9 +46,9 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
47 | private static LOCAL_STORAGE_PRIVACY_CONCERN_KEY = 'video-watch-privacy-concern' | 46 | private static LOCAL_STORAGE_PRIVACY_CONCERN_KEY = 'video-watch-privacy-concern' |
48 | 47 | ||
49 | @ViewChild('videoWatchPlaylist', { static: true }) videoWatchPlaylist: VideoWatchPlaylistComponent | 48 | @ViewChild('videoWatchPlaylist', { static: true }) videoWatchPlaylist: VideoWatchPlaylistComponent |
50 | @ViewChild('videoShareModal', { static: false }) videoShareModal: VideoShareComponent | 49 | @ViewChild('videoShareModal') videoShareModal: VideoShareComponent |
51 | @ViewChild('videoSupportModal', { static: false }) videoSupportModal: VideoSupportComponent | 50 | @ViewChild('videoSupportModal') videoSupportModal: VideoSupportComponent |
52 | @ViewChild('subscribeButton', { static: false }) subscribeButton: SubscribeButtonComponent | 51 | @ViewChild('subscribeButton') subscribeButton: SubscribeButtonComponent |
53 | 52 | ||
54 | player: any | 53 | player: any |
55 | playerElement: HTMLVideoElement | 54 | playerElement: HTMLVideoElement |
@@ -95,6 +94,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
95 | private confirmService: ConfirmService, | 94 | private confirmService: ConfirmService, |
96 | private metaService: MetaService, | 95 | private metaService: MetaService, |
97 | private authService: AuthService, | 96 | private authService: AuthService, |
97 | private userService: UserService, | ||
98 | private serverService: ServerService, | 98 | private serverService: ServerService, |
99 | private restExtractor: RestExtractor, | 99 | private restExtractor: RestExtractor, |
100 | private notifier: Notifier, | 100 | private notifier: Notifier, |
@@ -118,6 +118,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
118 | return this.authService.getUser() | 118 | return this.authService.getUser() |
119 | } | 119 | } |
120 | 120 | ||
121 | get anonymousUser () { | ||
122 | return this.userService.getAnonymousUser() | ||
123 | } | ||
124 | |||
121 | async ngOnInit () { | 125 | async ngOnInit () { |
122 | this.serverConfig = this.serverService.getTmpConfig() | 126 | this.serverConfig = this.serverService.getTmpConfig() |
123 | 127 | ||
@@ -266,6 +270,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
266 | this.redirectService.redirectToHomepage() | 270 | this.redirectService.redirectToHomepage() |
267 | } | 271 | } |
268 | 272 | ||
273 | declinedPrivacyConcern () { | ||
274 | peertubeLocalStorage.setItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY, 'false') | ||
275 | this.hasAlreadyAcceptedPrivacyConcern = false | ||
276 | } | ||
277 | |||
269 | acceptedPrivacyConcern () { | 278 | acceptedPrivacyConcern () { |
270 | peertubeLocalStorage.setItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY, 'true') | 279 | peertubeLocalStorage.setItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY, 'true') |
271 | this.hasAlreadyAcceptedPrivacyConcern = true | 280 | this.hasAlreadyAcceptedPrivacyConcern = true |
@@ -290,7 +299,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
290 | isAutoPlayEnabled () { | 299 | isAutoPlayEnabled () { |
291 | return ( | 300 | return ( |
292 | (this.user && this.user.autoPlayNextVideo) || | 301 | (this.user && this.user.autoPlayNextVideo) || |
293 | peertubeSessionStorage.getItem(RecommendedVideosComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true' | 302 | this.anonymousUser.autoPlayNextVideo |
294 | ) | 303 | ) |
295 | } | 304 | } |
296 | 305 | ||
@@ -302,7 +311,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
302 | isPlaylistAutoPlayEnabled () { | 311 | isPlaylistAutoPlayEnabled () { |
303 | return ( | 312 | return ( |
304 | (this.user && this.user.autoPlayNextVideoPlaylist) || | 313 | (this.user && this.user.autoPlayNextVideoPlaylist) || |
305 | peertubeSessionStorage.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true' | 314 | this.anonymousUser.autoPlayNextVideoPlaylist |
306 | ) | 315 | ) |
307 | } | 316 | } |
308 | 317 | ||
@@ -532,7 +541,9 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
532 | } | 541 | } |
533 | 542 | ||
534 | private autoplayNext () { | 543 | private autoplayNext () { |
535 | if (this.nextVideoUuid) { | 544 | if (this.playlist) { |
545 | this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo()) | ||
546 | } else if (this.nextVideoUuid) { | ||
536 | this.router.navigate([ '/videos/watch', this.nextVideoUuid ]) | 547 | this.router.navigate([ '/videos/watch', this.nextVideoUuid ]) |
537 | } | 548 | } |
538 | } | 549 | } |
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 5fa50ecbb..9b445269d 100644 --- a/client/src/app/videos/+video-watch/video-watch.module.ts +++ b/client/src/app/videos/+video-watch/video-watch.module.ts | |||
@@ -12,7 +12,6 @@ import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' | |||
12 | import { RecommendationsModule } from '@app/videos/recommendations/recommendations.module' | 12 | import { RecommendationsModule } from '@app/videos/recommendations/recommendations.module' |
13 | import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watch-playlist.component' | 13 | import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watch-playlist.component' |
14 | import { QRCodeModule } from 'angularx-qrcode' | 14 | import { QRCodeModule } from 'angularx-qrcode' |
15 | import { InputSwitchModule } from 'primeng/inputswitch' | ||
16 | import { TimestampRouteTransformerDirective } from '@app/shared/angular/timestamp-route-transformer.directive' | 15 | import { TimestampRouteTransformerDirective } from '@app/shared/angular/timestamp-route-transformer.directive' |
17 | 16 | ||
18 | @NgModule({ | 17 | @NgModule({ |
@@ -21,8 +20,7 @@ import { TimestampRouteTransformerDirective } from '@app/shared/angular/timestam | |||
21 | SharedModule, | 20 | SharedModule, |
22 | NgbTooltipModule, | 21 | NgbTooltipModule, |
23 | QRCodeModule, | 22 | QRCodeModule, |
24 | RecommendationsModule, | 23 | RecommendationsModule |
25 | InputSwitchModule | ||
26 | ], | 24 | ], |
27 | 25 | ||
28 | declarations: [ | 26 | declarations: [ |
diff --git a/client/src/app/videos/recommendations/recommended-videos.component.html b/client/src/app/videos/recommendations/recommended-videos.component.html index 476eca071..74f9ed2a5 100644 --- a/client/src/app/videos/recommendations/recommended-videos.component.html +++ b/client/src/app/videos/recommendations/recommended-videos.component.html | |||
@@ -7,8 +7,8 @@ | |||
7 | <div *ngIf="!playlist" class="title-page-autoplay" | 7 | <div *ngIf="!playlist" class="title-page-autoplay" |
8 | [ngbTooltip]="autoPlayNextVideoTooltip" placement="bottom-right auto" | 8 | [ngbTooltip]="autoPlayNextVideoTooltip" placement="bottom-right auto" |
9 | > | 9 | > |
10 | <span i18n>Autoplay</span> | 10 | <span i18n>AUTOPLAY</span> |
11 | <p-inputSwitch [(ngModel)]="autoPlayNextVideo" (ngModelChange)="switchAutoPlayNextVideo()"></p-inputSwitch> | 11 | <p-inputSwitch class="small" [(ngModel)]="autoPlayNextVideo" (ngModelChange)="switchAutoPlayNextVideo()"></p-inputSwitch> |
12 | </div> | 12 | </div> |
13 | </div> | 13 | </div> |
14 | 14 | ||
diff --git a/client/src/app/videos/recommendations/recommended-videos.component.scss b/client/src/app/videos/recommendations/recommended-videos.component.scss index 1ab0c47ff..cde62f87f 100644 --- a/client/src/app/videos/recommendations/recommended-videos.component.scss +++ b/client/src/app/videos/recommendations/recommended-videos.component.scss | |||
@@ -25,25 +25,3 @@ | |||
25 | font-weight: 600; | 25 | font-weight: 600; |
26 | } | 26 | } |
27 | } | 27 | } |
28 | |||
29 | /* p-inputSwitch styles to reduce the switch size */ | ||
30 | |||
31 | ::ng-deep { | ||
32 | p-inputswitch { | ||
33 | height: 20px; | ||
34 | } | ||
35 | |||
36 | .ui-inputswitch { | ||
37 | width: 2.5em !important; | ||
38 | height: 1.45em !important; | ||
39 | |||
40 | .ui-inputswitch-slider::before { | ||
41 | height: 1em !important; | ||
42 | width: 1em !important; | ||
43 | } | ||
44 | } | ||
45 | |||
46 | .ui-inputswitch-checked .ui-inputswitch-slider::before { | ||
47 | transform: translateX(1em) !important; | ||
48 | } | ||
49 | } | ||
diff --git a/client/src/app/videos/recommendations/recommended-videos.component.ts b/client/src/app/videos/recommendations/recommended-videos.component.ts index ada6d3433..d4b4c929b 100644 --- a/client/src/app/videos/recommendations/recommended-videos.component.ts +++ b/client/src/app/videos/recommendations/recommended-videos.component.ts | |||
@@ -7,8 +7,8 @@ import { RecommendedVideosStore } from '@app/videos/recommendations/recommended- | |||
7 | import { User } from '@app/shared' | 7 | import { User } from '@app/shared' |
8 | import { AuthService, Notifier } from '@app/core' | 8 | import { AuthService, Notifier } from '@app/core' |
9 | import { UserService } from '@app/shared/users/user.service' | 9 | import { UserService } from '@app/shared/users/user.service' |
10 | import { peertubeSessionStorage } from '@app/shared/misc/peertube-web-storage' | ||
11 | import { I18n } from '@ngx-translate/i18n-polyfill' | 10 | import { I18n } from '@ngx-translate/i18n-polyfill' |
11 | import { SessionStorageService } from '@app/shared/misc/storage.service' | ||
12 | 12 | ||
13 | @Component({ | 13 | @Component({ |
14 | selector: 'my-recommended-videos', | 14 | selector: 'my-recommended-videos', |
@@ -16,8 +16,6 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
16 | styleUrls: [ './recommended-videos.component.scss' ] | 16 | styleUrls: [ './recommended-videos.component.scss' ] |
17 | }) | 17 | }) |
18 | export class RecommendedVideosComponent implements OnChanges { | 18 | export class RecommendedVideosComponent implements OnChanges { |
19 | static SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO = 'auto_play_next_video' | ||
20 | |||
21 | @Input() inputRecommendation: RecommendationInfo | 19 | @Input() inputRecommendation: RecommendationInfo |
22 | @Input() user: User | 20 | @Input() user: User |
23 | @Input() playlist: VideoPlaylist | 21 | @Input() playlist: VideoPlaylist |
@@ -34,15 +32,21 @@ export class RecommendedVideosComponent implements OnChanges { | |||
34 | private authService: AuthService, | 32 | private authService: AuthService, |
35 | private notifier: Notifier, | 33 | private notifier: Notifier, |
36 | private i18n: I18n, | 34 | private i18n: I18n, |
37 | private store: RecommendedVideosStore | 35 | private store: RecommendedVideosStore, |
36 | private sessionStorageService: SessionStorageService | ||
38 | ) { | 37 | ) { |
39 | this.videos$ = this.store.recommendations$ | 38 | this.videos$ = this.store.recommendations$ |
40 | this.hasVideos$ = this.store.hasRecommendations$ | 39 | this.hasVideos$ = this.store.hasRecommendations$ |
41 | this.videos$.subscribe(videos => this.gotRecommendations.emit(videos)) | 40 | this.videos$.subscribe(videos => this.gotRecommendations.emit(videos)) |
42 | 41 | ||
43 | this.autoPlayNextVideo = this.authService.isLoggedIn() | 42 | if (this.authService.isLoggedIn()) { |
44 | ? this.authService.getUser().autoPlayNextVideo | 43 | this.autoPlayNextVideo = this.authService.getUser().autoPlayNextVideo |
45 | : peertubeSessionStorage.getItem(RecommendedVideosComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true' || false | 44 | } else { |
45 | this.autoPlayNextVideo = this.sessionStorageService.getItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true' || false | ||
46 | this.sessionStorageService.watch([User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO]).subscribe( | ||
47 | () => this.autoPlayNextVideo = this.sessionStorageService.getItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true' | ||
48 | ) | ||
49 | } | ||
46 | 50 | ||
47 | this.autoPlayNextVideoTooltip = this.i18n('When active, the next video is automatically played after the current one.') | 51 | this.autoPlayNextVideoTooltip = this.i18n('When active, the next video is automatically played after the current one.') |
48 | } | 52 | } |
@@ -58,7 +62,7 @@ export class RecommendedVideosComponent implements OnChanges { | |||
58 | } | 62 | } |
59 | 63 | ||
60 | switchAutoPlayNextVideo () { | 64 | switchAutoPlayNextVideo () { |
61 | peertubeSessionStorage.setItem(RecommendedVideosComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO, this.autoPlayNextVideo.toString()) | 65 | this.sessionStorageService.setItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO, this.autoPlayNextVideo.toString()) |
62 | 66 | ||
63 | if (this.authService.isLoggedIn()) { | 67 | if (this.authService.isLoggedIn()) { |
64 | const details = { | 68 | const details = { |
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 59f65f95c..757b0e498 100644 --- a/client/src/app/videos/video-list/video-local.component.ts +++ b/client/src/app/videos/video-list/video-local.component.ts | |||
@@ -11,6 +11,8 @@ import { ScreenService } from '@app/shared/misc/screen.service' | |||
11 | import { UserRight } from '../../../../../shared/models/users' | 11 | import { UserRight } from '../../../../../shared/models/users' |
12 | import { Notifier, ServerService } from '@app/core' | 12 | import { Notifier, ServerService } from '@app/core' |
13 | import { HooksService } from '@app/core/plugins/hooks.service' | 13 | import { HooksService } from '@app/core/plugins/hooks.service' |
14 | import { UserService } from '@app/shared' | ||
15 | import { LocalStorageService } from '@app/shared/misc/storage.service' | ||
14 | 16 | ||
15 | @Component({ | 17 | @Component({ |
16 | selector: 'my-videos-local', | 18 | selector: 'my-videos-local', |
@@ -31,7 +33,9 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On | |||
31 | protected route: ActivatedRoute, | 33 | protected route: ActivatedRoute, |
32 | protected notifier: Notifier, | 34 | protected notifier: Notifier, |
33 | protected authService: AuthService, | 35 | protected authService: AuthService, |
36 | protected userService: UserService, | ||
34 | protected screenService: ScreenService, | 37 | protected screenService: ScreenService, |
38 | protected storageService: LocalStorageService, | ||
35 | private videoService: VideoService, | 39 | private videoService: VideoService, |
36 | private hooks: HooksService | 40 | private hooks: HooksService |
37 | ) { | 41 | ) { |
diff --git a/client/src/app/videos/video-list/video-most-liked.component.ts b/client/src/app/videos/video-list/video-most-liked.component.ts index 6ff7a1e0e..b69fad05f 100644 --- a/client/src/app/videos/video-list/video-most-liked.component.ts +++ b/client/src/app/videos/video-list/video-most-liked.component.ts | |||
@@ -9,6 +9,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
9 | import { ScreenService } from '@app/shared/misc/screen.service' | 9 | import { ScreenService } from '@app/shared/misc/screen.service' |
10 | import { Notifier, ServerService } from '@app/core' | 10 | import { Notifier, ServerService } from '@app/core' |
11 | import { HooksService } from '@app/core/plugins/hooks.service' | 11 | import { HooksService } from '@app/core/plugins/hooks.service' |
12 | import { UserService } from '@app/shared' | ||
13 | import { LocalStorageService } from '@app/shared/misc/storage.service' | ||
12 | 14 | ||
13 | @Component({ | 15 | @Component({ |
14 | selector: 'my-videos-most-liked', | 16 | selector: 'my-videos-most-liked', |
@@ -28,7 +30,9 @@ export class VideoMostLikedComponent extends AbstractVideoList implements OnInit | |||
28 | protected route: ActivatedRoute, | 30 | protected route: ActivatedRoute, |
29 | protected notifier: Notifier, | 31 | protected notifier: Notifier, |
30 | protected authService: AuthService, | 32 | protected authService: AuthService, |
33 | protected userService: UserService, | ||
31 | protected screenService: ScreenService, | 34 | protected screenService: ScreenService, |
35 | protected storageService: LocalStorageService, | ||
32 | private videoService: VideoService, | 36 | private videoService: VideoService, |
33 | private hooks: HooksService | 37 | private hooks: HooksService |
34 | ) { | 38 | ) { |
diff --git a/client/src/app/videos/video-list/video-overview.component.html b/client/src/app/videos/video-list/video-overview.component.html index 5fe1f5c80..84999cfb2 100644 --- a/client/src/app/videos/video-list/video-overview.component.html +++ b/client/src/app/videos/video-list/video-overview.component.html | |||
@@ -2,35 +2,44 @@ | |||
2 | 2 | ||
3 | <div class="no-results" i18n *ngIf="notResults">No results.</div> | 3 | <div class="no-results" i18n *ngIf="notResults">No results.</div> |
4 | 4 | ||
5 | <div class="section" *ngFor="let object of overview.categories"> | 5 | <div |
6 | <div class="section-title"> | 6 | myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" [dataObservable]="onDataSubject.asObservable()" |
7 | <a routerLink="/search" [queryParams]="{ categoryOneOf: [ object.category.id ] }">{{ object.category.label }}</a> | 7 | > |
8 | </div> | 8 | <ng-container *ngFor="let overview of overviews"> |
9 | 9 | ||
10 | <my-video-miniature *ngFor="let video of buildVideos(object.videos)" [video]="video" [user]="user" [displayVideoActions]="false"> | 10 | <div class="section" *ngFor="let object of overview.categories"> |
11 | </my-video-miniature> | 11 | <div class="section-title"> |
12 | </div> | 12 | <a routerLink="/search" [queryParams]="{ categoryOneOf: [ object.category.id ] }">{{ object.category.label }}</a> |
13 | </div> | ||
13 | 14 | ||
14 | <div class="section" *ngFor="let object of overview.tags"> | 15 | <my-video-miniature *ngFor="let video of buildVideos(object.videos)" [video]="video" [user]="user" [displayVideoActions]="false"> |
15 | <div class="section-title"> | 16 | </my-video-miniature> |
16 | <a routerLink="/search" [queryParams]="{ tagsOneOf: [ object.tag ] }">#{{ object.tag }}</a> | 17 | </div> |
17 | </div> | ||
18 | 18 | ||
19 | <my-video-miniature *ngFor="let video of buildVideos(object.videos)" [video]="video" [user]="user" [displayVideoActions]="false"> | 19 | <div class="section" *ngFor="let object of overview.tags"> |
20 | </my-video-miniature> | 20 | <div class="section-title"> |
21 | </div> | 21 | <a routerLink="/search" [queryParams]="{ tagsOneOf: [ object.tag ] }">#{{ object.tag }}</a> |
22 | </div> | ||
23 | |||
24 | <my-video-miniature *ngFor="let video of buildVideos(object.videos)" [video]="video" [user]="user" [displayVideoActions]="false"> | ||
25 | </my-video-miniature> | ||
26 | </div> | ||
27 | |||
28 | <div class="section channel" *ngFor="let object of overview.channels"> | ||
29 | <div class="section-title"> | ||
30 | <a [routerLink]="[ '/video-channels', buildVideoChannelBy(object) ]"> | ||
31 | <img [src]="buildVideoChannelAvatarUrl(object)" alt="Avatar" /> | ||
32 | |||
33 | <div>{{ object.channel.displayName }}</div> | ||
34 | </a> | ||
35 | </div> | ||
22 | 36 | ||
23 | <div class="section channel" *ngFor="let object of overview.channels"> | 37 | <my-video-miniature *ngFor="let video of buildVideos(object.videos)" [video]="video" [user]="user" [displayVideoActions]="false"> |
24 | <div class="section-title"> | 38 | </my-video-miniature> |
25 | <a [routerLink]="[ '/video-channels', buildVideoChannelBy(object) ]"> | 39 | </div> |
26 | <img [src]="buildVideoChannelAvatarUrl(object)" alt="Avatar" /> | ||
27 | 40 | ||
28 | <div>{{ object.channel.displayName }}</div> | 41 | </ng-container> |
29 | </a> | ||
30 | </div> | ||
31 | 42 | ||
32 | <my-video-miniature *ngFor="let video of buildVideos(object.videos)" [video]="video" [user]="user" [displayVideoActions]="false"> | ||
33 | </my-video-miniature> | ||
34 | </div> | 43 | </div> |
35 | 44 | ||
36 | </div> | 45 | </div> |
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 4fee92d54..101073949 100644 --- a/client/src/app/videos/video-list/video-overview.component.ts +++ b/client/src/app/videos/video-list/video-overview.component.ts | |||
@@ -5,6 +5,7 @@ import { VideosOverview } from '@app/shared/overview/videos-overview.model' | |||
5 | import { OverviewService } from '@app/shared/overview' | 5 | import { OverviewService } from '@app/shared/overview' |
6 | import { Video } from '@app/shared/video/video.model' | 6 | import { Video } from '@app/shared/video/video.model' |
7 | import { ScreenService } from '@app/shared/misc/screen.service' | 7 | import { ScreenService } from '@app/shared/misc/screen.service' |
8 | import { Subject } from 'rxjs' | ||
8 | 9 | ||
9 | @Component({ | 10 | @Component({ |
10 | selector: 'my-video-overview', | 11 | selector: 'my-video-overview', |
@@ -12,13 +13,17 @@ import { ScreenService } from '@app/shared/misc/screen.service' | |||
12 | styleUrls: [ './video-overview.component.scss' ] | 13 | styleUrls: [ './video-overview.component.scss' ] |
13 | }) | 14 | }) |
14 | export class VideoOverviewComponent implements OnInit { | 15 | export class VideoOverviewComponent implements OnInit { |
15 | overview: VideosOverview = { | 16 | onDataSubject = new Subject<any>() |
16 | categories: [], | 17 | |
17 | channels: [], | 18 | overviews: VideosOverview[] = [] |
18 | tags: [] | ||
19 | } | ||
20 | notResults = false | 19 | notResults = false |
21 | 20 | ||
21 | private loaded = false | ||
22 | private currentPage = 1 | ||
23 | private maxPage = 20 | ||
24 | private lastWasEmpty = false | ||
25 | private isLoading = false | ||
26 | |||
22 | constructor ( | 27 | constructor ( |
23 | private i18n: I18n, | 28 | private i18n: I18n, |
24 | private notifier: Notifier, | 29 | private notifier: Notifier, |
@@ -32,20 +37,7 @@ export class VideoOverviewComponent implements OnInit { | |||
32 | } | 37 | } |
33 | 38 | ||
34 | ngOnInit () { | 39 | ngOnInit () { |
35 | this.overviewService.getVideosOverview() | 40 | this.loadMoreResults() |
36 | .subscribe( | ||
37 | overview => { | ||
38 | this.overview = overview | ||
39 | |||
40 | if ( | ||
41 | this.overview.categories.length === 0 && | ||
42 | this.overview.channels.length === 0 && | ||
43 | this.overview.tags.length === 0 | ||
44 | ) this.notResults = true | ||
45 | }, | ||
46 | |||
47 | err => this.notifier.error(err.message) | ||
48 | ) | ||
49 | } | 41 | } |
50 | 42 | ||
51 | buildVideoChannelBy (object: { videos: Video[] }) { | 43 | buildVideoChannelBy (object: { videos: Video[] }) { |
@@ -61,4 +53,41 @@ export class VideoOverviewComponent implements OnInit { | |||
61 | 53 | ||
62 | return videos.slice(0, numberOfVideos * 2) | 54 | return videos.slice(0, numberOfVideos * 2) |
63 | } | 55 | } |
56 | |||
57 | onNearOfBottom () { | ||
58 | if (this.currentPage >= this.maxPage) return | ||
59 | if (this.lastWasEmpty) return | ||
60 | if (this.isLoading) return | ||
61 | |||
62 | this.currentPage++ | ||
63 | this.loadMoreResults() | ||
64 | } | ||
65 | |||
66 | private loadMoreResults () { | ||
67 | this.isLoading = true | ||
68 | |||
69 | this.overviewService.getVideosOverview(this.currentPage) | ||
70 | .subscribe( | ||
71 | overview => { | ||
72 | this.isLoading = false | ||
73 | |||
74 | if (overview.tags.length === 0 && overview.channels.length === 0 && overview.categories.length === 0) { | ||
75 | this.lastWasEmpty = true | ||
76 | if (this.loaded === false) this.notResults = true | ||
77 | |||
78 | return | ||
79 | } | ||
80 | |||
81 | this.loaded = true | ||
82 | this.onDataSubject.next(overview) | ||
83 | |||
84 | this.overviews.push(overview) | ||
85 | }, | ||
86 | |||
87 | err => { | ||
88 | this.notifier.error(err.message) | ||
89 | this.isLoading = false | ||
90 | } | ||
91 | ) | ||
92 | } | ||
64 | } | 93 | } |
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 7568f4536..c1ddd4fd4 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 | |||
@@ -9,6 +9,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
9 | import { ScreenService } from '@app/shared/misc/screen.service' | 9 | import { ScreenService } from '@app/shared/misc/screen.service' |
10 | import { Notifier, ServerService } from '@app/core' | 10 | import { Notifier, ServerService } from '@app/core' |
11 | import { HooksService } from '@app/core/plugins/hooks.service' | 11 | import { HooksService } from '@app/core/plugins/hooks.service' |
12 | import { UserService } from '@app/shared' | ||
13 | import { LocalStorageService } from '@app/shared/misc/storage.service' | ||
12 | 14 | ||
13 | @Component({ | 15 | @Component({ |
14 | selector: 'my-videos-recently-added', | 16 | selector: 'my-videos-recently-added', |
@@ -29,7 +31,9 @@ export class VideoRecentlyAddedComponent extends AbstractVideoList implements On | |||
29 | protected router: Router, | 31 | protected router: Router, |
30 | protected notifier: Notifier, | 32 | protected notifier: Notifier, |
31 | protected authService: AuthService, | 33 | protected authService: AuthService, |
34 | protected userService: UserService, | ||
32 | protected screenService: ScreenService, | 35 | protected screenService: ScreenService, |
36 | protected storageService: LocalStorageService, | ||
33 | private videoService: VideoService, | 37 | private videoService: VideoService, |
34 | private hooks: HooksService | 38 | private hooks: HooksService |
35 | ) { | 39 | ) { |
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 e29830b5b..fbe052277 100644 --- a/client/src/app/videos/video-list/video-trending.component.ts +++ b/client/src/app/videos/video-list/video-trending.component.ts | |||
@@ -9,6 +9,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
9 | import { ScreenService } from '@app/shared/misc/screen.service' | 9 | import { ScreenService } from '@app/shared/misc/screen.service' |
10 | import { Notifier, ServerService } from '@app/core' | 10 | import { Notifier, ServerService } from '@app/core' |
11 | import { HooksService } from '@app/core/plugins/hooks.service' | 11 | import { HooksService } from '@app/core/plugins/hooks.service' |
12 | import { UserService } from '@app/shared' | ||
13 | import { LocalStorageService } from '@app/shared/misc/storage.service' | ||
12 | 14 | ||
13 | @Component({ | 15 | @Component({ |
14 | selector: 'my-videos-trending', | 16 | selector: 'my-videos-trending', |
@@ -28,7 +30,9 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit, | |||
28 | protected route: ActivatedRoute, | 30 | protected route: ActivatedRoute, |
29 | protected notifier: Notifier, | 31 | protected notifier: Notifier, |
30 | protected authService: AuthService, | 32 | protected authService: AuthService, |
33 | protected userService: UserService, | ||
31 | protected screenService: ScreenService, | 34 | protected screenService: ScreenService, |
35 | protected storageService: LocalStorageService, | ||
32 | private videoService: VideoService, | 36 | private videoService: VideoService, |
33 | private hooks: HooksService | 37 | private hooks: HooksService |
34 | ) { | 38 | ) { |
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 cf0b15054..036fd8dcb 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 | |||
@@ -10,6 +10,8 @@ import { ScreenService } from '@app/shared/misc/screen.service' | |||
10 | import { OwnerDisplayType } from '@app/shared/video/video-miniature.component' | 10 | import { OwnerDisplayType } from '@app/shared/video/video-miniature.component' |
11 | import { Notifier, ServerService } from '@app/core' | 11 | import { Notifier, ServerService } from '@app/core' |
12 | import { HooksService } from '@app/core/plugins/hooks.service' | 12 | import { HooksService } from '@app/core/plugins/hooks.service' |
13 | import { UserService } from '@app/shared' | ||
14 | import { LocalStorageService } from '@app/shared/misc/storage.service' | ||
13 | 15 | ||
14 | @Component({ | 16 | @Component({ |
15 | selector: 'my-videos-user-subscriptions', | 17 | selector: 'my-videos-user-subscriptions', |
@@ -29,7 +31,9 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement | |||
29 | protected route: ActivatedRoute, | 31 | protected route: ActivatedRoute, |
30 | protected notifier: Notifier, | 32 | protected notifier: Notifier, |
31 | protected authService: AuthService, | 33 | protected authService: AuthService, |
34 | protected userService: UserService, | ||
32 | protected screenService: ScreenService, | 35 | protected screenService: ScreenService, |
36 | protected storageService: LocalStorageService, | ||
33 | private videoService: VideoService, | 37 | private videoService: VideoService, |
34 | private hooks: HooksService | 38 | private hooks: HooksService |
35 | ) { | 39 | ) { |