aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2019-06-12 12:40:24 +0200
committerChocobozzz <me@florianbigard.com>2019-06-12 16:44:15 +0200
commit2f4c784a92ac50cacef07f4925e284b4041422f4 (patch)
tree159e744c68c5f5cd8054b5f63eb389553e13bf56 /client
parent011e1e6b37e15a44624b2d0e50263e16382060d2 (diff)
downloadPeerTube-2f4c784a92ac50cacef07f4925e284b4041422f4.tar.gz
PeerTube-2f4c784a92ac50cacef07f4925e284b4041422f4.tar.zst
PeerTube-2f4c784a92ac50cacef07f4925e284b4041422f4.zip
Add params to share modal
Diffstat (limited to 'client')
-rw-r--r--client/src/app/search/search.component.html2
-rw-r--r--client/src/app/search/search.component.scss12
-rw-r--r--client/src/app/search/search.module.ts5
-rw-r--r--client/src/app/shared/shared.module.ts11
-rw-r--r--client/src/app/videos/+video-watch/modal/video-share.component.html192
-rw-r--r--client/src/app/videos/+video-watch/modal/video-share.component.scss68
-rw-r--r--client/src/app/videos/+video-watch/modal/video-share.component.ts91
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.html2
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.ts5
-rw-r--r--client/src/assets/player/peertube-player-manager.ts2
-rw-r--r--client/src/assets/player/utils.ts51
-rw-r--r--client/src/assets/player/videojs-components/peertube-link-button.ts2
-rw-r--r--client/src/sass/application.scss118
-rw-r--r--client/src/sass/bootstrap.scss138
-rw-r--r--client/src/sass/include/_mixins.scss18
15 files changed, 506 insertions, 211 deletions
diff --git a/client/src/app/search/search.component.html b/client/src/app/search/search.component.html
index 0a9f78cb2..055f64cc8 100644
--- a/client/src/app/search/search.component.html
+++ b/client/src/app/search/search.component.html
@@ -20,7 +20,7 @@
20 </div> 20 </div>
21 </div> 21 </div>
22 22
23 <div class="results-filter" [ngbCollapse]="isSearchFilterCollapsed"> 23 <div class="results-filter collapse-transition" [ngbCollapse]="isSearchFilterCollapsed">
24 <my-search-filters [advancedSearch]="advancedSearch" (filtered)="onFiltered()"></my-search-filters> 24 <my-search-filters [advancedSearch]="advancedSearch" (filtered)="onFiltered()"></my-search-filters>
25 </div> 25 </div>
26 </div> 26 </div>
diff --git a/client/src/app/search/search.component.scss b/client/src/app/search/search.component.scss
index 4e3ce1c96..3343a276d 100644
--- a/client/src/app/search/search.component.scss
+++ b/client/src/app/search/search.component.scss
@@ -35,18 +35,6 @@
35 } 35 }
36 } 36 }
37 } 37 }
38
39 .results-filter {
40 // Animation when we show/hide the filters
41 transition: max-height 0.3s;
42 display: block !important;
43 overflow: hidden !important;
44 max-height: 0;
45
46 &.show {
47 max-height: 1500px;
48 }
49 }
50 } 38 }
51 39
52 .entry { 40 .entry {
diff --git a/client/src/app/search/search.module.ts b/client/src/app/search/search.module.ts
index 0411fbe24..8b791621e 100644
--- a/client/src/app/search/search.module.ts
+++ b/client/src/app/search/search.module.ts
@@ -4,14 +4,11 @@ import { SearchComponent } from '@app/search/search.component'
4import { SearchService } from '@app/search/search.service' 4import { SearchService } from '@app/search/search.service'
5import { SearchRoutingModule } from '@app/search/search-routing.module' 5import { SearchRoutingModule } from '@app/search/search-routing.module'
6import { SearchFiltersComponent } from '@app/search/search-filters.component' 6import { SearchFiltersComponent } from '@app/search/search-filters.component'
7import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap'
8 7
9@NgModule({ 8@NgModule({
10 imports: [ 9 imports: [
11 SearchRoutingModule, 10 SearchRoutingModule,
12 SharedModule, 11 SharedModule
13
14 NgbCollapseModule
15 ], 12 ],
16 13
17 declarations: [ 14 declarations: [
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 1d49c7bc8..eb57a2fff 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -53,7 +53,14 @@ import { VideoCaptionService } from '@app/shared/video-caption'
53import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.component' 53import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.component'
54import { VideoImportService } from '@app/shared/video-import/video-import.service' 54import { VideoImportService } from '@app/shared/video-import/video-import.service'
55import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component' 55import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component'
56import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' 56import {
57 NgbCollapseModule,
58 NgbDropdownModule,
59 NgbModalModule,
60 NgbPopoverModule,
61 NgbTabsetModule,
62 NgbTooltipModule
63} from '@ng-bootstrap/ng-bootstrap'
57import { RemoteSubscribeComponent, SubscribeButtonComponent, UserSubscriptionService } from '@app/shared/user-subscription' 64import { RemoteSubscribeComponent, SubscribeButtonComponent, UserSubscriptionService } from '@app/shared/user-subscription'
58import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-features-table.component' 65import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-features-table.component'
59import { OverviewService } from '@app/shared/overview' 66import { OverviewService } from '@app/shared/overview'
@@ -100,6 +107,7 @@ import { FollowService } from '@app/shared/instance/follow.service'
100 NgbPopoverModule, 107 NgbPopoverModule,
101 NgbTabsetModule, 108 NgbTabsetModule,
102 NgbTooltipModule, 109 NgbTooltipModule,
110 NgbCollapseModule,
103 111
104 ClipboardModule, 112 ClipboardModule,
105 113
@@ -170,6 +178,7 @@ import { FollowService } from '@app/shared/instance/follow.service'
170 NgbPopoverModule, 178 NgbPopoverModule,
171 NgbTabsetModule, 179 NgbTabsetModule,
172 NgbTooltipModule, 180 NgbTooltipModule,
181 NgbCollapseModule,
173 182
174 ClipboardModule, 183 ClipboardModule,
175 184
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 955b2b80c..82e59d04d 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
@@ -5,53 +5,167 @@
5 </div> 5 </div>
6 6
7 <div class="modal-body"> 7 <div class="modal-body">
8 <ngb-tabset class="root-tabset bootstrap" (tabChange)="onTabChange($event)">
8 9
9 <div class="start-at"> 10 <ngb-tab i18n-title title="URL" id="url">
10 <my-peertube-checkbox 11 <ng-template ngbTabContent>
11 inputName="startAt" [(ngModel)]="startAtCheckbox" 12
12 i18n-labelText labelText="Start at" 13 <div class="tab-content">
13 ></my-peertube-checkbox> 14 <div class="input-group">
14 15 <input #urlInput (click)="urlInput.select()" type="text" class="form-control readonly" readonly [value]="getVideoUrl()" />
15 <my-timestamp-input 16 <div class="input-group-append">
16 [timestamp]="currentVideoTimestamp" 17 <button [ngxClipboard]="urlInput" (click)="activateCopiedMessage()" type="button" class="btn btn-outline-secondary">
17 [maxTimestamp]="video.duration" 18 <span class="glyphicon glyphicon-copy"></span>
18 [disabled]="!startAtCheckbox" 19 </button>
19 [(ngModel)]="currentVideoTimestamp" 20 </div>
20 > 21 </div>
21 </my-timestamp-input> 22 </div>
22 </div> 23
24 </ng-template>
25 </ngb-tab>
26
27 <ngb-tab i18n-title title="QR-Code" id="qrcode">
28 <ng-template ngbTabContent>
29 <div class="tab-content">
30 <ngx-qrcode qrc-element-type="url" [qrc-value]="getVideoUrl()" qrc-errorCorrectionLevel="Q"></ngx-qrcode>
31 </div>
32 </ng-template>
33 </ngb-tab>
34
35 <ngb-tab i18n-title title="Embed" id="embed">
36 <ng-template ngbTabContent>
37 <div class="tab-content">
38 <div class="input-group">
39 <input #shareInput (click)="shareInput.select()" type="text" class="form-control readonly" readonly [value]="getVideoIframeCode()" />
40 <div class="input-group-append">
41 <button [ngxClipboard]="shareInput" (click)="activateCopiedMessage()" type="button" class="btn btn-outline-secondary">
42 <span class="glyphicon glyphicon-copy"></span>
43 </button>
44 </div>
45 </div>
46
47 <div i18n *ngIf="notSecure()" class="alert alert-warning">
48 The url is not secured (no HTTPS), so the embed video won't work on HTTPS websites (web browsers block non secured HTTP requests on HTTPS websites).
49 </div>
50 </div>
51 </ng-template>
52 </ngb-tab>
53
54 </ngb-tabset>
23 55
24 <div class="form-group"> 56 <div class="filters">
25 <label i18n>URL</label> 57 <div>
26 <div class="input-group input-group-sm"> 58 <div class="form-group start-at">
27 <input #urlInput (click)="urlInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="getVideoUrl()" /> 59 <my-peertube-checkbox
28 <div class="input-group-append"> 60 inputName="startAt" [(ngModel)]="customizations.startAtCheckbox"
29 <button [ngxClipboard]="urlInput" (click)="activateCopiedMessage()" type="button" class="btn btn-outline-secondary"> 61 i18n-labelText labelText="Start at"
30 <span class="glyphicon glyphicon-copy"></span> 62 ></my-peertube-checkbox>
31 </button> 63
64 <my-timestamp-input
65 [timestamp]="customizations.startAt"
66 [maxTimestamp]="video.duration"
67 [disabled]="!customizations.startAtCheckbox"
68 [(ngModel)]="customizations.startAt"
69 >
70 </my-timestamp-input>
32 </div> 71 </div>
33 </div>
34 </div>
35 72
36 <div class="form-group qr-code-group"> 73 <div *ngIf="videoCaptions.length !== 0" class="form-group video-caption-block">
37 <label i18n>QR-Code</label> 74 <my-peertube-checkbox
38 <ngx-qrcode qrc-element-type="url" [qrc-value]="getVideoUrl()" qrc-errorCorrectionLevel="Q"></ngx-qrcode> 75 inputName="subtitleCheckbox" [(ngModel)]="customizations.subtitleCheckbox"
39 </div> 76 i18n-labelText labelText="Auto select subtitle"
77 ></my-peertube-checkbox>
40 78
41 <div class="form-group"> 79 <div class="peertube-select-container" [ngClass]="{ disabled: !customizations.subtitleCheckbox }">
42 <label i18n>Embed</label> 80 <select [(ngModel)]="customizations.subtitle" [disabled]="!customizations.subtitleCheckbox">
43 <div class="input-group input-group-sm"> 81 <option *ngFor="let caption of videoCaptions" [value]="caption.language.id">{{ caption.language.label }}</option>
44 <input #shareInput (click)="shareInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="getVideoIframeCode()" /> 82 </select>
45 <div class="input-group-append"> 83 </div>
46 <button [ngxClipboard]="shareInput" (click)="activateCopiedMessage()" type="button" class="btn btn-outline-secondary">
47 <span class="glyphicon glyphicon-copy"></span>
48 </button>
49 </div> 84 </div>
50 </div> 85 </div>
51 </div>
52 86
53 <div i18n *ngIf="notSecure()" class="alert alert-warning"> 87 <div (click)="isAdvancedCustomizationCollapsed = !isAdvancedCustomizationCollapsed" role="button" class="advanced-filters-button"
54 The url is not secured (no HTTPS), so the embed video won't work on HTTPS websites (web browsers block non secured HTTP requests on HTTPS websites). 88 [attr.aria-expanded]="!isAdvancedCustomizationCollapsed" aria-controls="collapseBasic">
89
90 <ng-container *ngIf="isAdvancedCustomizationCollapsed">
91 <span class="glyphicon glyphicon-menu-down"></span>
92
93 <ng-container i18n>
94 More customization
95 </ng-container>
96 </ng-container>
97
98 <ng-container *ngIf="!isAdvancedCustomizationCollapsed">
99 <span class="glyphicon glyphicon-menu-up"></span>
100
101 <ng-container i18n>
102 Less customization
103 </ng-container>
104 </ng-container>
105 </div>
106
107 <div class="advanced-filters collapse-transition" [ngbCollapse]="isAdvancedCustomizationCollapsed">
108 <div>
109 <div class="form-group stop-at">
110 <my-peertube-checkbox
111 inputName="stopAt" [(ngModel)]="customizations.stopAtCheckbox"
112 i18n-labelText labelText="Stop at"
113 ></my-peertube-checkbox>
114
115 <my-timestamp-input
116 [timestamp]="customizations.stopAt"
117 [maxTimestamp]="video.duration"
118 [disabled]="!customizations.stopAtCheckbox"
119 [(ngModel)]="customizations.stopAt"
120 >
121 </my-timestamp-input>
122 </div>
123
124 <div class="form-group">
125 <my-peertube-checkbox
126 inputName="autoplay" [(ngModel)]="customizations.autoplay"
127 i18n-labelText labelText="Autoplay"
128 ></my-peertube-checkbox>
129 </div>
130
131 <div class="form-group">
132 <my-peertube-checkbox
133 inputName="muted" [(ngModel)]="customizations.muted"
134 i18n-labelText labelText="Muted"
135 ></my-peertube-checkbox>
136 </div>
137
138 <div class="form-group">
139 <my-peertube-checkbox
140 inputName="loop" [(ngModel)]="customizations.loop"
141 i18n-labelText labelText="Loop"
142 ></my-peertube-checkbox>
143 </div>
144 </div>
145
146 <ng-container *ngIf="isInEmbedTab()">
147 <div class="form-group">
148 <my-peertube-checkbox
149 inputName="title" [(ngModel)]="customizations.title"
150 i18n-labelText labelText="Display video title"
151 ></my-peertube-checkbox>
152 </div>
153
154 <div class="form-group">
155 <my-peertube-checkbox
156 inputName="warningTitle" [(ngModel)]="customizations.warningTitle"
157 i18n-labelText labelText="Display privacy warning"
158 ></my-peertube-checkbox>
159 </div>
160
161 <div class="form-group">
162 <my-peertube-checkbox
163 inputName="controls" [(ngModel)]="customizations.controls"
164 i18n-labelText labelText="Display player controls"
165 ></my-peertube-checkbox>
166 </div>
167 </ng-container>
168 </div>
55 </div> 169 </div>
56 </div> 170 </div>
57 171
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 472a45920..c48abf9e0 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
@@ -1,5 +1,9 @@
1@import '~bootstrap/scss/functions'; 1@import '_mixins';
2@import '~bootstrap/scss/variables'; 2@import '_variables';
3
4.peertube-select-container {
5 @include peertube-select-container(200px);
6}
3 7
4.action-button-cancel { 8.action-button-cancel {
5 margin-right: 0 !important; 9 margin-right: 0 !important;
@@ -9,13 +13,65 @@
9 text-align: center; 13 text-align: center;
10} 14}
11 15
12.start-at { 16.tab-content {
17 margin-top: 30px;
13 display: flex; 18 display: flex;
14 justify-content: center; 19 justify-content: center;
15 margin-top: 10px;
16 align-items: center; 20 align-items: center;
21 flex-direction: column;
22}
23
24.alert {
25 margin-top: 20px;
26}
27
28input.readonly {
29 font-size: 15px;
30}
31
32.filters {
33 margin-top: 30px;
34 padding-top: 30px;
35 border-top: 1px solid $separator-border-color;
36
37 .advanced-filters-button {
38 display: flex;
39 justify-content: center;
40 align-items: center;
41 margin-top: 30px;
42 font-size: 16px;
43 font-weight: $font-semibold;
44 cursor: pointer;
45
46 .glyphicon {
47 margin-right: 5px;
48 }
49 }
50
51 .form-group {
52 margin-bottom: 0;
53 height: 34px;
54 display: flex;
55 align-items: center;
56 }
57
58 .video-caption-block {
59 display: flex;
60 align-items: center;
61
62 .peertube-select-container {
63 margin-left: 10px;
64 }
65 }
66
67 .start-at,
68 .stop-at {
69 width: 300px;
70 display: flex;
71 align-items: center;
17 72
18 my-timestamp-input { 73 my-timestamp-input {
19 margin-left: 10px; 74 margin-left: 10px;
75 }
20 } 76 }
21} 77}
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 6565d7f88..eaaf6b902 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
@@ -3,8 +3,26 @@ import { Notifier } from '@app/core'
3import { VideoDetails } from '../../../shared/video/video-details.model' 3import { VideoDetails } from '../../../shared/video/video-details.model'
4import { buildVideoEmbed, buildVideoLink } from '../../../../assets/player/utils' 4import { buildVideoEmbed, buildVideoLink } from '../../../../assets/player/utils'
5import { I18n } from '@ngx-translate/i18n-polyfill' 5import { I18n } from '@ngx-translate/i18n-polyfill'
6import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 6import { NgbModal, NgbTabChangeEvent } from '@ng-bootstrap/ng-bootstrap'
7import { durationToString } from '@app/shared/misc/utils' 7import { VideoCaption } from '@shared/models'
8
9type Customizations = {
10 startAtCheckbox: boolean
11 startAt: number
12
13 stopAtCheckbox: boolean
14 stopAt: number
15
16 subtitleCheckbox: boolean
17 subtitle: string
18
19 loop: boolean
20 autoplay: boolean
21 muted: boolean
22 title: boolean
23 warningTitle: boolean
24 controls: boolean
25}
8 26
9@Component({ 27@Component({
10 selector: 'my-video-share', 28 selector: 'my-video-share',
@@ -15,9 +33,13 @@ export class VideoShareComponent {
15 @ViewChild('modal') modal: ElementRef 33 @ViewChild('modal') modal: ElementRef
16 34
17 @Input() video: VideoDetails = null 35 @Input() video: VideoDetails = null
36 @Input() videoCaptions: VideoCaption[] = []
18 37
19 currentVideoTimestamp: number 38 activeId: 'url' | 'qrcode' | 'embed'
20 startAtCheckbox = false 39 customizations: Customizations
40 isAdvancedCustomizationCollapsed = true
41
42 private currentVideoTimestamp: number
21 43
22 constructor ( 44 constructor (
23 private modalService: NgbModal, 45 private modalService: NgbModal,
@@ -26,19 +48,47 @@ export class VideoShareComponent {
26 ) { } 48 ) { }
27 49
28 show (currentVideoTimestamp?: number) { 50 show (currentVideoTimestamp?: number) {
29 this.currentVideoTimestamp = currentVideoTimestamp ? Math.floor(currentVideoTimestamp) : 0 51 this.currentVideoTimestamp = currentVideoTimestamp
52
53 let subtitle: string
54 if (this.videoCaptions.length !== 0) {
55 subtitle = this.videoCaptions[0].language.id
56 }
57
58 this.customizations = {
59 startAtCheckbox: false,
60 startAt: currentVideoTimestamp ? Math.floor(currentVideoTimestamp) : 0,
61
62 stopAtCheckbox: false,
63 stopAt: this.video.duration,
64
65 subtitleCheckbox: false,
66 subtitle,
67
68 loop: false,
69 autoplay: false,
70 muted: false,
71
72 // Embed options
73 title: true,
74 warningTitle: true,
75 controls: true
76 }
30 77
31 this.modalService.open(this.modal) 78 this.modalService.open(this.modal)
32 } 79 }
33 80
34 getVideoIframeCode () { 81 getVideoIframeCode () {
35 const embedUrl = buildVideoLink(this.getVideoTimestampIfEnabled(), this.video.embedUrl) 82 const options = this.getOptions(this.video.embedUrl)
36 83
84 const embedUrl = buildVideoLink(options)
37 return buildVideoEmbed(embedUrl) 85 return buildVideoEmbed(embedUrl)
38 } 86 }
39 87
40 getVideoUrl () { 88 getVideoUrl () {
41 return buildVideoLink(this.getVideoTimestampIfEnabled()) 89 const options = this.getOptions()
90
91 return buildVideoLink(options)
42 } 92 }
43 93
44 notSecure () { 94 notSecure () {
@@ -49,9 +99,30 @@ export class VideoShareComponent {
49 this.notifier.success(this.i18n('Copied')) 99 this.notifier.success(this.i18n('Copied'))
50 } 100 }
51 101
52 private getVideoTimestampIfEnabled () { 102 onTabChange (event: NgbTabChangeEvent) {
53 if (this.startAtCheckbox === true) return this.currentVideoTimestamp 103 this.activeId = event.nextId as any
104 }
105
106 isInEmbedTab () {
107 return this.activeId === 'embed'
108 }
109
110 private getOptions (baseUrl?: string) {
111 return {
112 baseUrl,
113
114 startTime: this.customizations.startAtCheckbox ? this.customizations.startAt : undefined,
115 stopTime: this.customizations.stopAtCheckbox ? this.customizations.stopAt : undefined,
116
117 subtitle: this.customizations.subtitleCheckbox ? this.customizations.subtitle : undefined,
118
119 loop: this.customizations.loop,
120 autoplay: this.customizations.autoplay,
121 muted: this.customizations.muted,
54 122
55 return undefined 123 title: this.customizations.title,
124 warningTitle: this.customizations.warningTitle,
125 controls: this.customizations.controls
126 }
56 } 127 }
57} 128}
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 2e39b9c6b..6a02f630a 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -219,5 +219,5 @@
219 219
220<ng-template [ngIf]="video !== null"> 220<ng-template [ngIf]="video !== null">
221 <my-video-support #videoSupportModal [video]="video"></my-video-support> 221 <my-video-support #videoSupportModal [video]="video"></my-video-support>
222 <my-video-share #videoShareModal [video]="video"></my-video-share> 222 <my-video-share #videoShareModal [video]="video" [videoCaptions]="videoCaptions"></my-video-share>
223</ng-template> 223</ng-template>
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 29c472a42..3f1a98f89 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -50,9 +50,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
50 playerElement: HTMLVideoElement 50 playerElement: HTMLVideoElement
51 theaterEnabled = false 51 theaterEnabled = false
52 userRating: UserVideoRateType = null 52 userRating: UserVideoRateType = null
53 video: VideoDetails = null
54 descriptionLoading = false 53 descriptionLoading = false
55 54
55 video: VideoDetails = null
56 videoCaptions: VideoCaption[] = []
57
56 playlist: VideoPlaylist = null 58 playlist: VideoPlaylist = null
57 59
58 completeDescriptionShown = false 60 completeDescriptionShown = false
@@ -339,6 +341,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
339 urlOptions: CustomizationOptions & { playerMode: PlayerMode } 341 urlOptions: CustomizationOptions & { playerMode: PlayerMode }
340 ) { 342 ) {
341 this.video = video 343 this.video = video
344 this.videoCaptions = videoCaptions
342 345
343 // Re init attributes 346 // Re init attributes
344 this.descriptionLoading = false 347 this.descriptionLoading = false
diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts
index 8f6237326..083c621d2 100644
--- a/client/src/assets/player/peertube-player-manager.ts
+++ b/client/src/assets/player/peertube-player-manager.ts
@@ -446,7 +446,7 @@ export class PeertubePlayerManager {
446 label: player.localize('Copy the video URL at the current time'), 446 label: player.localize('Copy the video URL at the current time'),
447 listener: function () { 447 listener: function () {
448 const player = this as videojs.Player 448 const player = this as videojs.Player
449 copyToClipboard(buildVideoLink(player.currentTime())) 449 copyToClipboard(buildVideoLink({ startTime: player.currentTime() }))
450 } 450 }
451 }, 451 },
452 { 452 {
diff --git a/client/src/assets/player/utils.ts b/client/src/assets/player/utils.ts
index 366689962..777abb568 100644
--- a/client/src/assets/player/utils.ts
+++ b/client/src/assets/player/utils.ts
@@ -27,18 +27,55 @@ function isMobile () {
27 return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent) 27 return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent)
28} 28}
29 29
30function buildVideoLink (time?: number, url?: string) { 30function buildVideoLink (options: {
31 if (!url) url = window.location.origin + window.location.pathname.replace('/embed/', '/watch/') 31 baseUrl?: string,
32 32
33 if (time) { 33 startTime?: number,
34 const timeInt = Math.floor(time) 34 stopTime?: number,
35 35
36 const params = new URLSearchParams(window.location.search) 36 subtitle?: string,
37 params.set('start', secondsToTime(timeInt))
38 37
39 return url + '?' + params.toString() 38 loop?: boolean,
39 autoplay?: boolean,
40 muted?: boolean,
41
42 // Embed options
43 title?: boolean,
44 warningTitle?: boolean,
45 controls?: boolean
46} = {}) {
47 const { baseUrl } = options
48
49 const url = baseUrl
50 ? baseUrl
51 : window.location.origin + window.location.pathname.replace('/embed/', '/watch/')
52
53 const params = new URLSearchParams(window.location.search)
54
55 if (options.startTime) {
56 const startTimeInt = Math.floor(options.startTime)
57 params.set('start', secondsToTime(startTimeInt))
58 }
59
60 if (options.stopTime) {
61 const stopTimeInt = Math.floor(options.stopTime)
62 params.set('stop', secondsToTime(stopTimeInt))
40 } 63 }
41 64
65 if (options.subtitle) params.set('subtitle', options.subtitle)
66
67 if (options.loop === true) params.set('loop', '1')
68 if (options.autoplay === true) params.set('autoplay', '1')
69 if (options.muted === true) params.set('muted', '1')
70 if (options.title === false) params.set('title', '0')
71 if (options.warningTitle === false) params.set('warningTitle', '0')
72 if (options.controls === false) params.set('controls', '0')
73
74 let hasParams = false
75 params.forEach(() => hasParams = true)
76
77 if (hasParams) return url + '?' + params.toString()
78
42 return url 79 return url
43} 80}
44 81
diff --git a/client/src/assets/player/videojs-components/peertube-link-button.ts b/client/src/assets/player/videojs-components/peertube-link-button.ts
index fed8ea33e..4d0ea37f5 100644
--- a/client/src/assets/player/videojs-components/peertube-link-button.ts
+++ b/client/src/assets/player/videojs-components/peertube-link-button.ts
@@ -16,7 +16,7 @@ class PeerTubeLinkButton extends Button {
16 } 16 }
17 17
18 updateHref () { 18 updateHref () {
19 this.el().setAttribute('href', buildVideoLink(this.player().currentTime())) 19 this.el().setAttribute('href', buildVideoLink({ startTime: this.player().currentTime() }))
20 } 20 }
21 21
22 handleClick () { 22 handleClick () {
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index d84766240..c64a8ebf8 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -12,6 +12,7 @@ $assets-path: '../assets/';
12@import './player/index'; 12@import './player/index';
13@import './loading-bar'; 13@import './loading-bar';
14 14
15@import './bootstrap';
15@import './primeng-custom'; 16@import './primeng-custom';
16 17
17[hidden] { 18[hidden] {
@@ -181,128 +182,11 @@ label {
181 font-weight: bold; 182 font-weight: bold;
182} 183}
183 184
184// Thanks https://gist.github.com/alexandrevicenzi/680147013e902a4eaa5d
185.glyphicon-refresh-animate {
186 animation: spin .7s infinite linear;
187}
188
189@keyframes spin { 185@keyframes spin {
190 from { transform: scale(1) rotate(0deg);} 186 from { transform: scale(1) rotate(0deg);}
191 to { transform: scale(1) rotate(360deg);} 187 to { transform: scale(1) rotate(360deg);}
192} 188}
193 189
194// Bootstrap customizations
195.dropdown-menu {
196 border-radius: 3px;
197 box-shadow: 0 3px 6px;
198 font-size: 15px;
199
200 .dropdown-item {
201 padding: 3px 15px;
202
203 &:active {
204 color: #000 !important;
205 }
206 }
207
208 button {
209 @include disable-default-a-behaviour;
210 }
211
212 a {
213 @include disable-default-a-behaviour;
214 color: #000 !important;
215 }
216}
217
218.modal {
219 .modal-content {
220 background-color: var(--mainBackgroundColor);
221 }
222
223 .modal-header {
224 border-bottom: none;
225 margin-bottom: 5px;
226
227 .modal-title {
228 font-size: 20px;
229 font-weight: $font-semibold;
230 }
231
232 my-global-icon {
233 @include icon(24px);
234
235 position: relative;
236 top: 3px;
237 float: right;
238
239 margin: 0;
240 padding: 0;
241 opacity: 1;
242 }
243 }
244
245 .inputs {
246 margin-bottom: 0;
247 text-align: right;
248
249 .action-button-cancel {
250 @include peertube-button;
251 @include grey-button;
252
253 display: inline-block;
254 margin-right: 10px;
255 }
256
257 .action-button-submit {
258 @include peertube-button;
259 @include orange-button;
260 }
261 }
262}
263
264// Nav customizations
265.nav .nav-link {
266 display: flex !important;
267 align-items: center;
268 height: 30px !important;
269 padding: 10px 15px !important;
270}
271
272.nav.nav-pills {
273 font-size: 16px !important;
274
275 .nav-link.active {
276 font-weight: $font-semibold !important;
277 }
278
279 a {
280 @include disable-default-a-behaviour;
281
282 color: var(--mainForegroundColor);
283 }
284}
285
286ngb-tabset.bootstrap {
287
288 .nav-link {
289 &, & a {
290 @include disable-default-a-behaviour;
291
292 color: var(--mainForegroundColor) !important;
293 }
294 }
295
296 .nav-pills .nav-link.active {
297 color: #000 !important;
298 }
299}
300
301.nav-tabs .nav-link.active {
302 background-color: var(--mainBackgroundColor) !important;
303 border-bottom: none;
304}
305
306.orange-button { 190.orange-button {
307 @include peertube-button; 191 @include peertube-button;
308 @include orange-button; 192 @include orange-button;
diff --git a/client/src/sass/bootstrap.scss b/client/src/sass/bootstrap.scss
new file mode 100644
index 000000000..12e73278a
--- /dev/null
+++ b/client/src/sass/bootstrap.scss
@@ -0,0 +1,138 @@
1$icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
2@import '_bootstrap';
3
4@import '_variables';
5@import '_mixins';
6
7// Thanks https://gist.github.com/alexandrevicenzi/680147013e902a4eaa5d
8.glyphicon-refresh-animate {
9 animation: spin .7s infinite linear;
10}
11
12@keyframes spin {
13 from { transform: scale(1) rotate(0deg);}
14 to { transform: scale(1) rotate(360deg);}
15}
16
17.dropdown-menu {
18 border-radius: 3px;
19 box-shadow: 0 3px 6px;
20 font-size: 15px;
21
22 .dropdown-item {
23 padding: 3px 15px;
24
25 &:active {
26 color: #000 !important;
27 }
28 }
29
30 button {
31 @include disable-default-a-behaviour;
32 }
33
34 a {
35 @include disable-default-a-behaviour;
36 color: #000 !important;
37 }
38}
39
40.modal {
41 .modal-content {
42 background-color: var(--mainBackgroundColor);
43 }
44
45 .modal-header {
46 border-bottom: none;
47 margin-bottom: 5px;
48
49 .modal-title {
50 font-size: 20px;
51 font-weight: $font-semibold;
52 }
53
54 my-global-icon {
55 @include icon(24px);
56
57 position: relative;
58 top: 3px;
59 float: right;
60
61 margin: 0;
62 padding: 0;
63 opacity: 1;
64 }
65 }
66
67 .inputs {
68 margin-bottom: 0;
69 text-align: right;
70
71 .action-button-cancel {
72 @include peertube-button;
73 @include grey-button;
74
75 display: inline-block;
76 margin-right: 10px;
77 }
78
79 .action-button-submit {
80 @include peertube-button;
81 @include orange-button;
82 }
83 }
84}
85
86// Nav customizations
87.nav .nav-link {
88 display: flex !important;
89 align-items: center;
90 height: 30px !important;
91 padding: 10px 15px !important;
92}
93
94.nav.nav-pills {
95 font-size: 16px !important;
96
97 .nav-link.active {
98 font-weight: $font-semibold !important;
99 }
100
101 a {
102 @include disable-default-a-behaviour;
103
104 color: var(--mainForegroundColor);
105 }
106}
107
108ngb-tabset.bootstrap {
109
110 .nav-link {
111 &, & a {
112 @include disable-default-a-behaviour;
113
114 color: var(--mainForegroundColor) !important;
115 }
116 }
117
118 .nav-pills .nav-link.active {
119 color: #000 !important;
120 }
121}
122
123.nav-tabs .nav-link.active {
124 background-color: var(--mainBackgroundColor) !important;
125 border-bottom: none;
126}
127
128.collapse-transition {
129 // Animation when we show/hide the filters
130 transition: max-height 0.3s;
131 display: block !important;
132 overflow: hidden !important;
133 max-height: 0;
134
135 &.show {
136 max-height: 1500px;
137 }
138}
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss
index d4a2269a1..f608e9299 100644
--- a/client/src/sass/include/_mixins.scss
+++ b/client/src/sass/include/_mixins.scss
@@ -235,6 +235,14 @@
235 position: relative; 235 position: relative;
236 font-size: 15px; 236 font-size: 15px;
237 237
238 &.disabled {
239 background-color: #E5E5E5;
240
241 select {
242 cursor: default;
243 }
244 }
245
238 @media screen and (max-width: $width) { 246 @media screen and (max-width: $width) {
239 width: 100%; 247 width: 100%;
240 } 248 }
@@ -282,16 +290,6 @@
282 } 290 }
283} 291}
284 292
285@mixin peertube-select-disabled-container ($width) {
286 @include peertube-select-container($width);
287
288 background-color: #E5E5E5;
289
290 select {
291 cursor: default;
292 }
293}
294
295// Thanks: https://codepen.io/triss90/pen/XNEdRe/ 293// Thanks: https://codepen.io/triss90/pen/XNEdRe/
296@mixin peertube-radio-container { 294@mixin peertube-radio-container {
297 input[type="radio"] { 295 input[type="radio"] {