</div>
</div>
- <div class="results-filter" [ngbCollapse]="isSearchFilterCollapsed">
+ <div class="results-filter collapse-transition" [ngbCollapse]="isSearchFilterCollapsed">
<my-search-filters [advancedSearch]="advancedSearch" (filtered)="onFiltered()"></my-search-filters>
</div>
</div>
}
}
}
-
- .results-filter {
- // Animation when we show/hide the filters
- transition: max-height 0.3s;
- display: block !important;
- overflow: hidden !important;
- max-height: 0;
-
- &.show {
- max-height: 1500px;
- }
- }
}
.entry {
import { SearchService } from '@app/search/search.service'
import { SearchRoutingModule } from '@app/search/search-routing.module'
import { SearchFiltersComponent } from '@app/search/search-filters.component'
-import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap'
@NgModule({
imports: [
SearchRoutingModule,
- SharedModule,
-
- NgbCollapseModule
+ SharedModule
],
declarations: [
import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.component'
import { VideoImportService } from '@app/shared/video-import/video-import.service'
import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component'
-import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
+import {
+ NgbCollapseModule,
+ NgbDropdownModule,
+ NgbModalModule,
+ NgbPopoverModule,
+ NgbTabsetModule,
+ NgbTooltipModule
+} from '@ng-bootstrap/ng-bootstrap'
import { RemoteSubscribeComponent, SubscribeButtonComponent, UserSubscriptionService } from '@app/shared/user-subscription'
import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-features-table.component'
import { OverviewService } from '@app/shared/overview'
NgbPopoverModule,
NgbTabsetModule,
NgbTooltipModule,
+ NgbCollapseModule,
ClipboardModule,
NgbPopoverModule,
NgbTabsetModule,
NgbTooltipModule,
+ NgbCollapseModule,
ClipboardModule,
</div>
<div class="modal-body">
+ <ngb-tabset class="root-tabset bootstrap" (tabChange)="onTabChange($event)">
- <div class="start-at">
- <my-peertube-checkbox
- inputName="startAt" [(ngModel)]="startAtCheckbox"
- i18n-labelText labelText="Start at"
- ></my-peertube-checkbox>
-
- <my-timestamp-input
- [timestamp]="currentVideoTimestamp"
- [maxTimestamp]="video.duration"
- [disabled]="!startAtCheckbox"
- [(ngModel)]="currentVideoTimestamp"
- >
- </my-timestamp-input>
- </div>
+ <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>
+
+ </ng-template>
+ </ngb-tab>
+
+ <ngb-tab i18n-title title="QR-Code" id="qrcode">
+ <ng-template ngbTabContent>
+ <div class="tab-content">
+ <ngx-qrcode qrc-element-type="url" [qrc-value]="getVideoUrl()" qrc-errorCorrectionLevel="Q"></ngx-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 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="form-group">
- <label i18n>URL</label>
- <div class="input-group input-group-sm">
- <input #urlInput (click)="urlInput.select()" type="text" class="form-control input-sm 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 class="filters">
+ <div>
+ <div class="form-group start-at">
+ <my-peertube-checkbox
+ inputName="startAt" [(ngModel)]="customizations.startAtCheckbox"
+ i18n-labelText labelText="Start at"
+ ></my-peertube-checkbox>
+
+ <my-timestamp-input
+ [timestamp]="customizations.startAt"
+ [maxTimestamp]="video.duration"
+ [disabled]="!customizations.startAtCheckbox"
+ [(ngModel)]="customizations.startAt"
+ >
+ </my-timestamp-input>
</div>
- </div>
- </div>
- <div class="form-group qr-code-group">
- <label i18n>QR-Code</label>
- <ngx-qrcode qrc-element-type="url" [qrc-value]="getVideoUrl()" qrc-errorCorrectionLevel="Q"></ngx-qrcode>
- </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="form-group">
- <label i18n>Embed</label>
- <div class="input-group input-group-sm">
- <input #shareInput (click)="shareInput.select()" type="text" class="form-control input-sm 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 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 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 (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>
+
+ <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="autoplay" [(ngModel)]="customizations.autoplay"
+ i18n-labelText labelText="Autoplay"
+ ></my-peertube-checkbox>
+ </div>
+
+ <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 *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>
-@import '~bootstrap/scss/functions';
-@import '~bootstrap/scss/variables';
+@import '_mixins';
+@import '_variables';
+
+.peertube-select-container {
+ @include peertube-select-container(200px);
+}
.action-button-cancel {
margin-right: 0 !important;
text-align: center;
}
-.start-at {
+.tab-content {
+ margin-top: 30px;
display: flex;
justify-content: center;
- margin-top: 10px;
align-items: center;
+ flex-direction: column;
+}
+
+.alert {
+ margin-top: 20px;
+}
+
+input.readonly {
+ font-size: 15px;
+}
+
+.filters {
+ margin-top: 30px;
+ padding-top: 30px;
+ border-top: 1px solid $separator-border-color;
+
+ .advanced-filters-button {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-top: 30px;
+ font-size: 16px;
+ font-weight: $font-semibold;
+ cursor: pointer;
+
+ .glyphicon {
+ margin-right: 5px;
+ }
+ }
+
+ .form-group {
+ margin-bottom: 0;
+ height: 34px;
+ display: flex;
+ align-items: center;
+ }
+
+ .video-caption-block {
+ display: flex;
+ align-items: center;
+
+ .peertube-select-container {
+ margin-left: 10px;
+ }
+ }
+
+ .start-at,
+ .stop-at {
+ width: 300px;
+ display: flex;
+ align-items: center;
- my-timestamp-input {
- margin-left: 10px;
+ my-timestamp-input {
+ margin-left: 10px;
+ }
}
}
import { VideoDetails } from '../../../shared/video/video-details.model'
import { buildVideoEmbed, buildVideoLink } from '../../../../assets/player/utils'
import { I18n } from '@ngx-translate/i18n-polyfill'
-import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
-import { durationToString } from '@app/shared/misc/utils'
+import { NgbModal, NgbTabChangeEvent } from '@ng-bootstrap/ng-bootstrap'
+import { VideoCaption } from '@shared/models'
+
+type Customizations = {
+ startAtCheckbox: boolean
+ startAt: number
+
+ stopAtCheckbox: boolean
+ stopAt: number
+
+ subtitleCheckbox: boolean
+ subtitle: string
+
+ loop: boolean
+ autoplay: boolean
+ muted: boolean
+ title: boolean
+ warningTitle: boolean
+ controls: boolean
+}
@Component({
selector: 'my-video-share',
@ViewChild('modal') modal: ElementRef
@Input() video: VideoDetails = null
+ @Input() videoCaptions: VideoCaption[] = []
- currentVideoTimestamp: number
- startAtCheckbox = false
+ activeId: 'url' | 'qrcode' | 'embed'
+ customizations: Customizations
+ isAdvancedCustomizationCollapsed = true
+
+ private currentVideoTimestamp: number
constructor (
private modalService: NgbModal,
) { }
show (currentVideoTimestamp?: number) {
- this.currentVideoTimestamp = currentVideoTimestamp ? Math.floor(currentVideoTimestamp) : 0
+ this.currentVideoTimestamp = currentVideoTimestamp
+
+ let subtitle: string
+ if (this.videoCaptions.length !== 0) {
+ subtitle = this.videoCaptions[0].language.id
+ }
+
+ this.customizations = {
+ startAtCheckbox: false,
+ startAt: currentVideoTimestamp ? Math.floor(currentVideoTimestamp) : 0,
+
+ stopAtCheckbox: false,
+ stopAt: this.video.duration,
+
+ subtitleCheckbox: false,
+ subtitle,
+
+ loop: false,
+ autoplay: false,
+ muted: false,
+
+ // Embed options
+ title: true,
+ warningTitle: true,
+ controls: true
+ }
this.modalService.open(this.modal)
}
getVideoIframeCode () {
- const embedUrl = buildVideoLink(this.getVideoTimestampIfEnabled(), this.video.embedUrl)
+ const options = this.getOptions(this.video.embedUrl)
+ const embedUrl = buildVideoLink(options)
return buildVideoEmbed(embedUrl)
}
getVideoUrl () {
- return buildVideoLink(this.getVideoTimestampIfEnabled())
+ const options = this.getOptions()
+
+ return buildVideoLink(options)
}
notSecure () {
this.notifier.success(this.i18n('Copied'))
}
- private getVideoTimestampIfEnabled () {
- if (this.startAtCheckbox === true) return this.currentVideoTimestamp
+ onTabChange (event: NgbTabChangeEvent) {
+ this.activeId = event.nextId as any
+ }
+
+ isInEmbedTab () {
+ return this.activeId === 'embed'
+ }
+
+ private getOptions (baseUrl?: string) {
+ return {
+ baseUrl,
+
+ startTime: this.customizations.startAtCheckbox ? this.customizations.startAt : undefined,
+ stopTime: this.customizations.stopAtCheckbox ? this.customizations.stopAt : undefined,
+
+ subtitle: this.customizations.subtitleCheckbox ? this.customizations.subtitle : undefined,
+
+ loop: this.customizations.loop,
+ autoplay: this.customizations.autoplay,
+ muted: this.customizations.muted,
- return undefined
+ title: this.customizations.title,
+ warningTitle: this.customizations.warningTitle,
+ controls: this.customizations.controls
+ }
}
}
<ng-template [ngIf]="video !== null">
<my-video-support #videoSupportModal [video]="video"></my-video-support>
- <my-video-share #videoShareModal [video]="video"></my-video-share>
+ <my-video-share #videoShareModal [video]="video" [videoCaptions]="videoCaptions"></my-video-share>
</ng-template>
playerElement: HTMLVideoElement
theaterEnabled = false
userRating: UserVideoRateType = null
- video: VideoDetails = null
descriptionLoading = false
+ video: VideoDetails = null
+ videoCaptions: VideoCaption[] = []
+
playlist: VideoPlaylist = null
completeDescriptionShown = false
urlOptions: CustomizationOptions & { playerMode: PlayerMode }
) {
this.video = video
+ this.videoCaptions = videoCaptions
// Re init attributes
this.descriptionLoading = false
label: player.localize('Copy the video URL at the current time'),
listener: function () {
const player = this as videojs.Player
- copyToClipboard(buildVideoLink(player.currentTime()))
+ copyToClipboard(buildVideoLink({ startTime: player.currentTime() }))
}
},
{
return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent)
}
-function buildVideoLink (time?: number, url?: string) {
- if (!url) url = window.location.origin + window.location.pathname.replace('/embed/', '/watch/')
+function buildVideoLink (options: {
+ baseUrl?: string,
- if (time) {
- const timeInt = Math.floor(time)
+ startTime?: number,
+ stopTime?: number,
- const params = new URLSearchParams(window.location.search)
- params.set('start', secondsToTime(timeInt))
+ subtitle?: string,
- return url + '?' + params.toString()
+ loop?: boolean,
+ autoplay?: boolean,
+ muted?: boolean,
+
+ // Embed options
+ title?: boolean,
+ warningTitle?: boolean,
+ controls?: boolean
+} = {}) {
+ const { baseUrl } = options
+
+ const url = baseUrl
+ ? baseUrl
+ : window.location.origin + window.location.pathname.replace('/embed/', '/watch/')
+
+ const params = new URLSearchParams(window.location.search)
+
+ if (options.startTime) {
+ const startTimeInt = Math.floor(options.startTime)
+ params.set('start', secondsToTime(startTimeInt))
+ }
+
+ if (options.stopTime) {
+ const stopTimeInt = Math.floor(options.stopTime)
+ params.set('stop', secondsToTime(stopTimeInt))
}
+ if (options.subtitle) params.set('subtitle', options.subtitle)
+
+ if (options.loop === true) params.set('loop', '1')
+ if (options.autoplay === true) params.set('autoplay', '1')
+ if (options.muted === true) params.set('muted', '1')
+ if (options.title === false) params.set('title', '0')
+ if (options.warningTitle === false) params.set('warningTitle', '0')
+ if (options.controls === false) params.set('controls', '0')
+
+ let hasParams = false
+ params.forEach(() => hasParams = true)
+
+ if (hasParams) return url + '?' + params.toString()
+
return url
}
}
updateHref () {
- this.el().setAttribute('href', buildVideoLink(this.player().currentTime()))
+ this.el().setAttribute('href', buildVideoLink({ startTime: this.player().currentTime() }))
}
handleClick () {
@import './player/index';
@import './loading-bar';
+@import './bootstrap';
@import './primeng-custom';
[hidden] {
font-weight: bold;
}
-// Thanks https://gist.github.com/alexandrevicenzi/680147013e902a4eaa5d
-.glyphicon-refresh-animate {
- animation: spin .7s infinite linear;
-}
-
@keyframes spin {
from { transform: scale(1) rotate(0deg);}
to { transform: scale(1) rotate(360deg);}
}
-// Bootstrap customizations
-.dropdown-menu {
- border-radius: 3px;
- box-shadow: 0 3px 6px;
- font-size: 15px;
-
- .dropdown-item {
- padding: 3px 15px;
-
- &:active {
- color: #000 !important;
- }
- }
-
- button {
- @include disable-default-a-behaviour;
- }
-
- a {
- @include disable-default-a-behaviour;
- color: #000 !important;
- }
-}
-
-.modal {
- .modal-content {
- background-color: var(--mainBackgroundColor);
- }
-
- .modal-header {
- border-bottom: none;
- margin-bottom: 5px;
-
- .modal-title {
- font-size: 20px;
- font-weight: $font-semibold;
- }
-
- my-global-icon {
- @include icon(24px);
-
- position: relative;
- top: 3px;
- float: right;
-
- margin: 0;
- padding: 0;
- opacity: 1;
- }
- }
-
- .inputs {
- margin-bottom: 0;
- text-align: right;
-
- .action-button-cancel {
- @include peertube-button;
- @include grey-button;
-
- display: inline-block;
- margin-right: 10px;
- }
-
- .action-button-submit {
- @include peertube-button;
- @include orange-button;
- }
- }
-}
-
-// Nav customizations
-.nav .nav-link {
- display: flex !important;
- align-items: center;
- height: 30px !important;
- padding: 10px 15px !important;
-}
-
-.nav.nav-pills {
- font-size: 16px !important;
-
- .nav-link.active {
- font-weight: $font-semibold !important;
- }
-
- a {
- @include disable-default-a-behaviour;
-
- color: var(--mainForegroundColor);
- }
-}
-
-ngb-tabset.bootstrap {
-
- .nav-link {
- &, & a {
- @include disable-default-a-behaviour;
-
- color: var(--mainForegroundColor) !important;
- }
- }
-
- .nav-pills .nav-link.active {
- color: #000 !important;
- }
-}
-
-.nav-tabs .nav-link.active {
- background-color: var(--mainBackgroundColor) !important;
- border-bottom: none;
-}
-
.orange-button {
@include peertube-button;
@include orange-button;
--- /dev/null
+$icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
+@import '_bootstrap';
+
+@import '_variables';
+@import '_mixins';
+
+// Thanks https://gist.github.com/alexandrevicenzi/680147013e902a4eaa5d
+.glyphicon-refresh-animate {
+ animation: spin .7s infinite linear;
+}
+
+@keyframes spin {
+ from { transform: scale(1) rotate(0deg);}
+ to { transform: scale(1) rotate(360deg);}
+}
+
+.dropdown-menu {
+ border-radius: 3px;
+ box-shadow: 0 3px 6px;
+ font-size: 15px;
+
+ .dropdown-item {
+ padding: 3px 15px;
+
+ &:active {
+ color: #000 !important;
+ }
+ }
+
+ button {
+ @include disable-default-a-behaviour;
+ }
+
+ a {
+ @include disable-default-a-behaviour;
+ color: #000 !important;
+ }
+}
+
+.modal {
+ .modal-content {
+ background-color: var(--mainBackgroundColor);
+ }
+
+ .modal-header {
+ border-bottom: none;
+ margin-bottom: 5px;
+
+ .modal-title {
+ font-size: 20px;
+ font-weight: $font-semibold;
+ }
+
+ my-global-icon {
+ @include icon(24px);
+
+ position: relative;
+ top: 3px;
+ float: right;
+
+ margin: 0;
+ padding: 0;
+ opacity: 1;
+ }
+ }
+
+ .inputs {
+ margin-bottom: 0;
+ text-align: right;
+
+ .action-button-cancel {
+ @include peertube-button;
+ @include grey-button;
+
+ display: inline-block;
+ margin-right: 10px;
+ }
+
+ .action-button-submit {
+ @include peertube-button;
+ @include orange-button;
+ }
+ }
+}
+
+// Nav customizations
+.nav .nav-link {
+ display: flex !important;
+ align-items: center;
+ height: 30px !important;
+ padding: 10px 15px !important;
+}
+
+.nav.nav-pills {
+ font-size: 16px !important;
+
+ .nav-link.active {
+ font-weight: $font-semibold !important;
+ }
+
+ a {
+ @include disable-default-a-behaviour;
+
+ color: var(--mainForegroundColor);
+ }
+}
+
+ngb-tabset.bootstrap {
+
+ .nav-link {
+ &, & a {
+ @include disable-default-a-behaviour;
+
+ color: var(--mainForegroundColor) !important;
+ }
+ }
+
+ .nav-pills .nav-link.active {
+ color: #000 !important;
+ }
+}
+
+.nav-tabs .nav-link.active {
+ background-color: var(--mainBackgroundColor) !important;
+ border-bottom: none;
+}
+
+.collapse-transition {
+ // Animation when we show/hide the filters
+ transition: max-height 0.3s;
+ display: block !important;
+ overflow: hidden !important;
+ max-height: 0;
+
+ &.show {
+ max-height: 1500px;
+ }
+}
position: relative;
font-size: 15px;
+ &.disabled {
+ background-color: #E5E5E5;
+
+ select {
+ cursor: default;
+ }
+ }
+
@media screen and (max-width: $width) {
width: 100%;
}
}
}
-@mixin peertube-select-disabled-container ($width) {
- @include peertube-select-container($width);
-
- background-color: #E5E5E5;
-
- select {
- cursor: default;
- }
-}
-
// Thanks: https://codepen.io/triss90/pen/XNEdRe/
@mixin peertube-radio-container {
input[type="radio"] {