Support playlists in share modal
authorChocobozzz <me@florianbigard.com>
Thu, 5 Dec 2019 09:05:00 +0000 (10:05 +0100)
committerChocobozzz <me@florianbigard.com>
Thu, 5 Dec 2019 09:05:00 +0000 (10:05 +0100)
client/src/app/shared/forms/input-readonly-copy.component.html [new file with mode: 0644]
client/src/app/shared/forms/input-readonly-copy.component.scss [new file with mode: 0644]
client/src/app/shared/forms/input-readonly-copy.component.ts [new file with mode: 0644]
client/src/app/shared/shared.module.ts
client/src/app/videos/+video-watch/modal/video-share.component.html
client/src/app/videos/+video-watch/modal/video-share.component.scss
client/src/app/videos/+video-watch/modal/video-share.component.ts
client/src/app/videos/+video-watch/video-watch.component.html
client/src/assets/player/utils.ts

diff --git a/client/src/app/shared/forms/input-readonly-copy.component.html b/client/src/app/shared/forms/input-readonly-copy.component.html
new file mode 100644 (file)
index 0000000..27571b6
--- /dev/null
@@ -0,0 +1,9 @@
+<div class="input-group">
+  <input #urlInput (click)="urlInput.select()" type="text" class="form-control readonly" readonly [value]="value" />
+
+  <div class="input-group-append">
+    <button [ngxClipboard]="urlInput" (click)="activateCopiedMessage()" type="button" class="btn btn-outline-secondary">
+      <span class="glyphicon glyphicon-copy"></span>
+    </button>
+  </div>
+</div>
diff --git a/client/src/app/shared/forms/input-readonly-copy.component.scss b/client/src/app/shared/forms/input-readonly-copy.component.scss
new file mode 100644 (file)
index 0000000..8dc4f11
--- /dev/null
@@ -0,0 +1,3 @@
+input.readonly {
+  font-size: 15px;
+}
diff --git a/client/src/app/shared/forms/input-readonly-copy.component.ts b/client/src/app/shared/forms/input-readonly-copy.component.ts
new file mode 100644 (file)
index 0000000..7528fb7
--- /dev/null
@@ -0,0 +1,21 @@
+import { Component, Input } from '@angular/core'
+import { Notifier } from '@app/core'
+import { I18n } from '@ngx-translate/i18n-polyfill'
+
+@Component({
+  selector: 'my-input-readonly-copy',
+  templateUrl: './input-readonly-copy.component.html',
+  styleUrls: [ './input-readonly-copy.component.scss' ]
+})
+export class InputReadonlyCopyComponent {
+  @Input() value = ''
+
+  constructor (
+    private notifier: Notifier,
+    private i18n: I18n
+  ) { }
+
+  activateCopiedMessage () {
+    this.notifier.success(this.i18n('Copied'))
+  }
+}
index 8cbb15bfaf955ecb1ca66e5dc914ab03fb65e4d6..29ddf7b81642c03c8de07f03aadd0e3c6bab6021 100644 (file)
@@ -95,6 +95,7 @@ import { ClipboardModule } from 'ngx-clipboard'
 import { FollowService } from '@app/shared/instance/follow.service'
 import { MultiSelectModule } from 'primeng/multiselect'
 import { FeatureBooleanComponent } from '@app/shared/instance/feature-boolean.component'
+import { InputReadonlyCopyComponent } from '@app/shared/forms/input-readonly-copy.component'
 
 @NgModule({
   imports: [
@@ -155,6 +156,7 @@ import { FeatureBooleanComponent } from '@app/shared/instance/feature-boolean.co
     ReactiveFileComponent,
     PeertubeCheckboxComponent,
     TimestampInputComponent,
+    InputReadonlyCopyComponent,
 
     SubscribeButtonComponent,
     RemoteSubscribeComponent,
@@ -220,6 +222,7 @@ import { FeatureBooleanComponent } from '@app/shared/instance/feature-boolean.co
     InfiniteScrollerDirective,
     TextareaAutoResizeDirective,
     HelpComponent,
+    InputReadonlyCopyComponent,
 
     ReactiveFileComponent,
     PeertubeCheckboxComponent,
index e0be9f2654b9ac62448dd41abc90507ce7d4737b..549a9f30e35231216663bd1f08eada608f794014 100644 (file)
     <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
   </div>
 
+
   <div class="modal-body">
-    <ngb-tabset class="root-tabset bootstrap" (tabChange)="onTabChange($event)">
-
-      <ngb-tab i18n-title title="URL" id="url">
-        <ng-template ngbTabContent>
-
-          <div class="tab-content">
-            <div class="input-group">
-              <input #urlInput (click)="urlInput.select()" type="text" class="form-control readonly" readonly [value]="getVideoUrl()" />
-              <div class="input-group-append">
-                <button [ngxClipboard]="urlInput" (click)="activateCopiedMessage()" type="button" class="btn btn-outline-secondary">
-                  <span class="glyphicon glyphicon-copy"></span>
-                </button>
-              </div>
-            </div>
-          </div>
+    <div class="playlist" *ngIf="hasPlaylist()">
+      <div class="title-page title-page-single" i18n>Share the playlist</div>
 
-        </ng-template>
-      </ngb-tab>
+      <my-input-readonly-copy [value]="getPlaylistUrl()"></my-input-readonly-copy>
 
-      <ngb-tab i18n-title title="QR-Code" id="qrcode">
-        <ng-template ngbTabContent>
-          <div class="tab-content">
-            <qrcode [qrdata]="getVideoUrl()" size="256" level="Q"></qrcode>
-          </div>
-        </ng-template>
-      </ngb-tab>
-
-      <ngb-tab i18n-title title="Embed" id="embed">
-        <ng-template ngbTabContent>
-          <div class="tab-content">
-            <div class="input-group">
-              <input #shareInput (click)="shareInput.select()" type="text" class="form-control readonly" readonly [value]="getVideoIframeCode()" />
-              <div class="input-group-append">
-                <button [ngxClipboard]="shareInput" (click)="activateCopiedMessage()" type="button" class="btn btn-outline-secondary">
-                  <span class="glyphicon glyphicon-copy"></span>
-                </button>
-              </div>
-            </div>
+      <div class="filters">
 
-            <div i18n *ngIf="notSecure()" class="alert alert-warning">
-              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).
-            </div>
-          </div>
-        </ng-template>
-      </ngb-tab>
-
-    </ngb-tabset>
-
-    <div class="filters">
-      <div>
-        <div class="form-group start-at">
+        <div class="form-group">
           <my-peertube-checkbox
-            inputName="startAt" [(ngModel)]="customizations.startAtCheckbox"
-            i18n-labelText labelText="Start at"
+            inputName="includeVideoInPlaylist" [(ngModel)]="includeVideoInPlaylist"
+            i18n-labelText labelText="Share the playlist at this video position"
           ></my-peertube-checkbox>
-
-          <my-timestamp-input
-            [timestamp]="customizations.startAt"
-            [maxTimestamp]="video.duration"
-            [disabled]="!customizations.startAtCheckbox"
-            [(ngModel)]="customizations.startAt"
-          >
-          </my-timestamp-input>
         </div>
 
-        <div *ngIf="videoCaptions.length !== 0" class="form-group video-caption-block">
-          <my-peertube-checkbox
-            inputName="subtitleCheckbox" [(ngModel)]="customizations.subtitleCheckbox"
-            i18n-labelText labelText="Auto select subtitle"
-          ></my-peertube-checkbox>
-
-          <div class="peertube-select-container" [ngClass]="{ disabled: !customizations.subtitleCheckbox }">
-            <select [(ngModel)]="customizations.subtitle" [disabled]="!customizations.subtitleCheckbox">
-              <option *ngFor="let caption of videoCaptions" [value]="caption.language.id">{{ caption.language.label }}</option>
-            </select>
-          </div>
-        </div>
       </div>
+    </div>
 
-      <div (click)="isAdvancedCustomizationCollapsed = !isAdvancedCustomizationCollapsed" role="button" class="advanced-filters-button"
-           [attr.aria-expanded]="!isAdvancedCustomizationCollapsed" aria-controls="collapseBasic">
 
-        <ng-container *ngIf="isAdvancedCustomizationCollapsed">
-          <span class="glyphicon glyphicon-menu-down"></span>
+    <div class="video">
+      <div class="title-page title-page-single" *ngIf="hasPlaylist()" i18n>Share the video</div>
 
-          <ng-container i18n>
-            More customization
-          </ng-container>
-        </ng-container>
+      <ngb-tabset class="root-tabset bootstrap" (tabChange)="onTabChange($event)">
 
-        <ng-container *ngIf="!isAdvancedCustomizationCollapsed">
-          <span class="glyphicon glyphicon-menu-up"></span>
+        <ngb-tab i18n-title title="URL" id="url">
+          <ng-template ngbTabContent>
 
-          <ng-container i18n>
-            Less customization
-          </ng-container>
-        </ng-container>
-      </div>
+            <div class="tab-content">
+              <my-input-readonly-copy [value]="getVideoUrl()"></my-input-readonly-copy>
+            </div>
+
+          </ng-template>
+        </ngb-tab>
+
+        <ngb-tab i18n-title title="QR-Code" id="qrcode">
+          <ng-template ngbTabContent>
+            <div class="tab-content">
+              <qrcode [qrdata]="getVideoUrl()" size="256" level="Q"></qrcode>
+            </div>
+          </ng-template>
+        </ngb-tab>
 
-      <div class="advanced-filters collapse-transition" [ngbCollapse]="isAdvancedCustomizationCollapsed">
+        <ngb-tab i18n-title title="Embed" id="embed">
+          <ng-template ngbTabContent>
+            <div class="tab-content">
+              <my-input-readonly-copy [value]="getVideoIframeCode()"></my-input-readonly-copy>
+
+              <div i18n *ngIf="notSecure()" class="alert alert-warning">
+                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).
+              </div>
+            </div>
+          </ng-template>
+        </ngb-tab>
+
+      </ngb-tabset>
+
+      <div class="filters">
         <div>
-          <div class="form-group stop-at">
+          <div class="form-group start-at">
             <my-peertube-checkbox
-              inputName="stopAt" [(ngModel)]="customizations.stopAtCheckbox"
-              i18n-labelText labelText="Stop at"
+              inputName="startAt" [(ngModel)]="customizations.startAtCheckbox"
+              i18n-labelText labelText="Start at"
             ></my-peertube-checkbox>
 
             <my-timestamp-input
-              [timestamp]="customizations.stopAt"
+              [timestamp]="customizations.startAt"
               [maxTimestamp]="video.duration"
-              [disabled]="!customizations.stopAtCheckbox"
-              [(ngModel)]="customizations.stopAt"
+              [disabled]="!customizations.startAtCheckbox"
+              [(ngModel)]="customizations.startAt"
             >
             </my-timestamp-input>
           </div>
 
-          <div class="form-group">
+          <div *ngIf="videoCaptions.length !== 0" class="form-group video-caption-block">
             <my-peertube-checkbox
-              inputName="autoplay" [(ngModel)]="customizations.autoplay"
-              i18n-labelText labelText="Autoplay"
+              inputName="subtitleCheckbox" [(ngModel)]="customizations.subtitleCheckbox"
+              i18n-labelText labelText="Auto select subtitle"
             ></my-peertube-checkbox>
-          </div>
 
-          <div class="form-group">
-            <my-peertube-checkbox
-              inputName="muted" [(ngModel)]="customizations.muted"
-              i18n-labelText labelText="Muted"
-            ></my-peertube-checkbox>
+            <div class="peertube-select-container" [ngClass]="{ disabled: !customizations.subtitleCheckbox }">
+              <select [(ngModel)]="customizations.subtitle" [disabled]="!customizations.subtitleCheckbox">
+                <option *ngFor="let caption of videoCaptions" [value]="caption.language.id">{{ caption.language.label }}</option>
+              </select>
+            </div>
           </div>
+        </div>
 
-          <div class="form-group">
-            <my-peertube-checkbox
-              inputName="loop" [(ngModel)]="customizations.loop"
-              i18n-labelText labelText="Loop"
-            ></my-peertube-checkbox>
-          </div>
+        <div (click)="isAdvancedCustomizationCollapsed = !isAdvancedCustomizationCollapsed" role="button" class="advanced-filters-button"
+             [attr.aria-expanded]="!isAdvancedCustomizationCollapsed" aria-controls="collapseBasic">
+
+          <ng-container *ngIf="isAdvancedCustomizationCollapsed">
+            <span class="glyphicon glyphicon-menu-down"></span>
+
+            <ng-container i18n>
+              More customization
+            </ng-container>
+          </ng-container>
+
+          <ng-container *ngIf="!isAdvancedCustomizationCollapsed">
+            <span class="glyphicon glyphicon-menu-up"></span>
+
+            <ng-container i18n>
+              Less customization
+            </ng-container>
+          </ng-container>
         </div>
 
-        <ng-container *ngIf="isInEmbedTab()">
-          <div class="form-group">
-            <my-peertube-checkbox
-              inputName="title" [(ngModel)]="customizations.title"
-              i18n-labelText labelText="Display video title"
-            ></my-peertube-checkbox>
-          </div>
+        <div class="advanced-filters collapse-transition" [ngbCollapse]="isAdvancedCustomizationCollapsed">
+          <div>
+            <div class="form-group stop-at">
+              <my-peertube-checkbox
+                inputName="stopAt" [(ngModel)]="customizations.stopAtCheckbox"
+                i18n-labelText labelText="Stop at"
+              ></my-peertube-checkbox>
+
+              <my-timestamp-input
+                [timestamp]="customizations.stopAt"
+                [maxTimestamp]="video.duration"
+                [disabled]="!customizations.stopAtCheckbox"
+                [(ngModel)]="customizations.stopAt"
+              >
+              </my-timestamp-input>
+            </div>
 
-          <div class="form-group">
-            <my-peertube-checkbox
-              inputName="warningTitle" [(ngModel)]="customizations.warningTitle"
-              i18n-labelText labelText="Display privacy warning"
-            ></my-peertube-checkbox>
-          </div>
+            <div class="form-group">
+              <my-peertube-checkbox
+                inputName="autoplay" [(ngModel)]="customizations.autoplay"
+                i18n-labelText labelText="Autoplay"
+              ></my-peertube-checkbox>
+            </div>
 
-          <div class="form-group">
-            <my-peertube-checkbox
-              inputName="controls" [(ngModel)]="customizations.controls"
-              i18n-labelText labelText="Display player controls"
-            ></my-peertube-checkbox>
+            <div class="form-group">
+              <my-peertube-checkbox
+                inputName="muted" [(ngModel)]="customizations.muted"
+                i18n-labelText labelText="Muted"
+              ></my-peertube-checkbox>
+            </div>
+
+            <div class="form-group">
+              <my-peertube-checkbox
+                inputName="loop" [(ngModel)]="customizations.loop"
+                i18n-labelText labelText="Loop"
+              ></my-peertube-checkbox>
+            </div>
           </div>
-        </ng-container>
+
+          <ng-container *ngIf="isInEmbedTab()">
+            <div class="form-group">
+              <my-peertube-checkbox
+                inputName="title" [(ngModel)]="customizations.title"
+                i18n-labelText labelText="Display video title"
+              ></my-peertube-checkbox>
+            </div>
+
+            <div class="form-group">
+              <my-peertube-checkbox
+                inputName="warningTitle" [(ngModel)]="customizations.warningTitle"
+                i18n-labelText labelText="Display privacy warning"
+              ></my-peertube-checkbox>
+            </div>
+
+            <div class="form-group">
+              <my-peertube-checkbox
+                inputName="controls" [(ngModel)]="customizations.controls"
+                i18n-labelText labelText="Display player controls"
+              ></my-peertube-checkbox>
+            </div>
+          </ng-container>
+        </div>
       </div>
     </div>
   </div>
index c48abf9e05affa1be8474111f405ee5d30023bc6..8b5952da618d508af470656fd47b7793516c6b91 100644 (file)
@@ -1,6 +1,18 @@
 @import '_mixins';
 @import '_variables';
 
+my-input-readonly-copy {
+  width: 100%;
+}
+
+.title-page.title-page-single {
+  margin-top: 0;
+}
+
+.playlist {
+  margin-bottom: 50px;
+}
+
 .peertube-select-container {
   @include peertube-select-container(200px);
 }
   margin-top: 20px;
 }
 
-input.readonly {
-  font-size: 15px;
-}
-
 .filters {
   margin-top: 30px;
   padding-top: 30px;
index f45afccfb9010b232387d04cd57350fb5f2531aa..a9a7a0eabd63e9a3023d702ec3f95eb094599c4b 100644 (file)
@@ -5,6 +5,7 @@ import { buildVideoEmbed, buildVideoLink } from '../../../../assets/player/utils
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { NgbModal, NgbTabChangeEvent } from '@ng-bootstrap/ng-bootstrap'
 import { VideoCaption } from '@shared/models'
+import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
 
 type Customizations = {
   startAtCheckbox: boolean
@@ -34,18 +35,16 @@ export class VideoShareComponent {
 
   @Input() video: VideoDetails = null
   @Input() videoCaptions: VideoCaption[] = []
+  @Input() playlist: VideoPlaylist = null
 
   activeId: 'url' | 'qrcode' | 'embed'
   customizations: Customizations
   isAdvancedCustomizationCollapsed = true
+  includeVideoInPlaylist = false
 
   private currentVideoTimestamp: number
 
-  constructor (
-    private modalService: NgbModal,
-    private notifier: Notifier,
-    private i18n: I18n
-  ) { }
+  constructor (private modalService: NgbModal) { }
 
   show (currentVideoTimestamp?: number) {
     this.currentVideoTimestamp = currentVideoTimestamp
@@ -86,17 +85,22 @@ export class VideoShareComponent {
   }
 
   getVideoUrl () {
-    const options = this.getOptions()
+    const baseUrl = window.location.origin + '/videos/watch/' + this.video.uuid
+    const options = this.getOptions(baseUrl)
 
     return buildVideoLink(options)
   }
 
-  notSecure () {
-    return window.location.protocol === 'http:'
+  getPlaylistUrl () {
+    const base = window.location.origin + '/videos/watch/playlist/' + this.playlist.uuid
+
+    if (!this.includeVideoInPlaylist) return base
+
+    return base  + '?videoId=' + this.video.uuid
   }
 
-  activateCopiedMessage () {
-    this.notifier.success(this.i18n('Copied'))
+  notSecure () {
+    return window.location.protocol === 'http:'
   }
 
   onTabChange (event: NgbTabChangeEvent) {
@@ -107,6 +111,10 @@ export class VideoShareComponent {
     return this.activeId === 'embed'
   }
 
+  hasPlaylist () {
+    return !!this.playlist
+  }
+
   private getOptions (baseUrl?: string) {
     return {
       baseUrl,
index 38ad4e948e7ed3a070803be0f7b5939a122a35bb..5b2e91bc52996bfe32d91a2e9761db28a34f185c 100644 (file)
 
 <ng-container *ngIf="video !== null">
   <my-video-support #videoSupportModal [video]="video"></my-video-support>
-  <my-video-share #videoShareModal [video]="video" [videoCaptions]="videoCaptions"></my-video-share>
+  <my-video-share #videoShareModal [video]="video" [videoCaptions]="videoCaptions" [playlist]="playlist"></my-video-share>
 </ng-container>
index 777abb568efe89fd46940e35a1b04c3e7d19b98f..629634985b16d846d771e01d7302a8c17aef6b72 100644 (file)
@@ -51,6 +51,8 @@ function buildVideoLink (options: {
     : window.location.origin + window.location.pathname.replace('/embed/', '/watch/')
 
   const params = new URLSearchParams(window.location.search)
+  // Remove this unused parameter when we are on a playlist page
+  params.delete('videoId')
 
   if (options.startTime) {
     const startTimeInt = Math.floor(options.startTime)