Fix upnext, refactor avatar menu, add to playlist overflow
This commit is contained in:
parent
1dc240a948
commit
223b24e618
|
@ -23,7 +23,7 @@
|
||||||
<span *ngIf="account.mutedServerByInstance" class="badge badge-danger" i18n>Instance muted by your instance</span>
|
<span *ngIf="account.mutedServerByInstance" class="badge badge-danger" i18n>Instance muted by your instance</span>
|
||||||
|
|
||||||
<my-user-moderation-dropdown
|
<my-user-moderation-dropdown
|
||||||
buttonSize="small" [account]="account" [user]="user"
|
buttonSize="small" [account]="account" [user]="user" placement="bottom-right auto"
|
||||||
(userChanged)="onUserChanged()" (userDeleted)="onUserDeleted()"
|
(userChanged)="onUserChanged()" (userDeleted)="onUserDeleted()"
|
||||||
>
|
>
|
||||||
</my-user-moderation-dropdown>
|
</my-user-moderation-dropdown>
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="!loaded" class="loader">
|
<div *ngIf="!loaded" class="loader mt-4">
|
||||||
<my-loader [loading]="!loaded"></my-loader>
|
<my-loader [loading]="!loaded"></my-loader>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
height: 50px;
|
min-height: 50px;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@include disable-default-a-behaviour;
|
@include disable-default-a-behaviour;
|
||||||
|
|
|
@ -5,8 +5,10 @@
|
||||||
<my-avatar-notification [user]="user"></my-avatar-notification>
|
<my-avatar-notification [user]="user"></my-avatar-notification>
|
||||||
|
|
||||||
<div class="logged-in-info">
|
<div class="logged-in-info">
|
||||||
<a routerLink="/my-account/settings" class="logged-in-display-name">{{ user.account?.displayName }}</a>
|
<a *ngIf="user.account" [routerLink]="[ '/accounts', user.account.nameWithHost ]" class="logged-in-display-name">{{ user.account?.displayName }}</a>
|
||||||
<div class="logged-in-username">{{ user.username }}</div>
|
<a *ngIf="!user.account" routerLink="/my-account/settings" class="logged-in-display-name">{{ user.account?.displayName }}</a>
|
||||||
|
|
||||||
|
<div ngxClipboard [cbContent]="user.account?.nameWithHost" class="logged-in-username">{{ user.username }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="logged-in-more" ngbDropdown placement="bottom-right auto">
|
<div class="logged-in-more" ngbDropdown placement="bottom-right auto">
|
||||||
|
@ -14,13 +16,21 @@
|
||||||
|
|
||||||
<div ngbDropdownMenu>
|
<div ngbDropdownMenu>
|
||||||
<a *ngIf="user.account" [routerLink]="[ '/accounts', user.account.nameWithHost ]" class="dropdown-item">
|
<a *ngIf="user.account" [routerLink]="[ '/accounts', user.account.nameWithHost ]" class="dropdown-item">
|
||||||
<my-global-icon iconName="go"></my-global-icon> <ng-container i18n>My public profile</ng-container>
|
<my-global-icon iconName="go"></my-global-icon> <ng-container i18n>Public profile</ng-container>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
|
||||||
<a routerLink="/my-account" class="dropdown-item">
|
<a routerLink="/my-account" class="dropdown-item">
|
||||||
<my-global-icon iconName="user"></my-global-icon> <ng-container i18n>My account</ng-container>
|
<my-global-icon iconName="user"></my-global-icon> <ng-container i18n>Account settings</ng-container>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a routerLink="/my-account/video-channels" class="dropdown-item">
|
||||||
|
<my-global-icon iconName="folder"></my-global-icon> <ng-container i18n>Channels settings</ng-container>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
|
||||||
<a (click)="logout($event)" class="dropdown-item" href="#">
|
<a (click)="logout($event)" class="dropdown-item" href="#">
|
||||||
<my-global-icon iconName="sign-out"></my-global-icon> <ng-container i18n>Log out</ng-container>
|
<my-global-icon iconName="sign-out"></my-global-icon> <ng-container i18n>Log out</ng-container>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -69,6 +69,7 @@ menu {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #C6C6C6;
|
color: #C6C6C6;
|
||||||
max-width: 140px;
|
max-width: 140px;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -169,6 +169,26 @@ function importModule (path: string) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isInViewport (el: HTMLElement) {
|
||||||
|
const bounding = el.getBoundingClientRect()
|
||||||
|
return (
|
||||||
|
bounding.top >= 0 &&
|
||||||
|
bounding.left >= 0 &&
|
||||||
|
bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
||||||
|
bounding.right <= (window.innerWidth || document.documentElement.clientWidth)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isXPercentInViewport (el: HTMLElement, percentVisible: number) {
|
||||||
|
const rect = el.getBoundingClientRect()
|
||||||
|
const windowHeight = (window.innerHeight || document.documentElement.clientHeight)
|
||||||
|
|
||||||
|
return !(
|
||||||
|
Math.floor(100 - (((rect.top >= 0 ? 0 : rect.top) / +-(rect.height / 1)) * 100)) < percentVisible ||
|
||||||
|
Math.floor(100 - ((rect.bottom - windowHeight) / rect.height) * 100) < percentVisible
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
sortBy,
|
sortBy,
|
||||||
durationToString,
|
durationToString,
|
||||||
|
@ -183,5 +203,7 @@ export {
|
||||||
objectLineFeedToHtml,
|
objectLineFeedToHtml,
|
||||||
removeElementFromArray,
|
removeElementFromArray,
|
||||||
importModule,
|
importModule,
|
||||||
scrollToTop
|
scrollToTop,
|
||||||
|
isInViewport,
|
||||||
|
isXPercentInViewport
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="playlists">
|
||||||
<div class="playlist dropdown-item" *ngFor="let playlist of videoPlaylists" (click)="togglePlaylist($event, playlist)">
|
<div class="playlist dropdown-item" *ngFor="let playlist of videoPlaylists" (click)="togglePlaylist($event, playlist)">
|
||||||
<my-peertube-checkbox [inputName]="'in-playlist-' + playlist.id" [(ngModel)]="playlist.inPlaylist" [onPushWorkaround]="true"></my-peertube-checkbox>
|
<my-peertube-checkbox [inputName]="'in-playlist-' + playlist.id" [(ngModel)]="playlist.inPlaylist" [onPushWorkaround]="true"></my-peertube-checkbox>
|
||||||
|
|
||||||
|
@ -52,6 +53,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="new-playlist-button dropdown-item" (click)="openCreateBlock($event)" [hidden]="isNewPlaylistBlockOpened">
|
<div class="new-playlist-button dropdown-item" (click)="openCreateBlock($event)" [hidden]="isNewPlaylistBlockOpened">
|
||||||
<my-global-icon iconName="add"></my-global-icon>
|
<my-global-icon iconName="add"></my-global-icon>
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
@import '_variables';
|
@import '_variables';
|
||||||
@import '_mixins';
|
@import '_mixins';
|
||||||
|
|
||||||
.root {
|
|
||||||
max-height: 300px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
min-width: 240px;
|
min-width: 240px;
|
||||||
padding: 6px 24px 10px 24px;
|
padding: 6px 24px 10px 24px;
|
||||||
|
@ -53,6 +48,11 @@
|
||||||
padding: 6px 24px;
|
padding: 6px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.playlists {
|
||||||
|
max-height: 180px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.playlist {
|
.playlist {
|
||||||
display: flex;
|
display: flex;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -114,9 +114,9 @@
|
||||||
></my-video-actions-dropdown>
|
></my-video-actions-dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="video-info-likes-dislikes-bar-outerContainer">
|
<div class="video-info-likes-dislikes-bar-outer-container">
|
||||||
<div
|
<div
|
||||||
class="video-info-likes-dislikes-bar-innerContainer"
|
class="video-info-likes-dislikes-bar-inner-container"
|
||||||
*ngIf="video.likes !== 0 || video.dislikes !== 0"
|
*ngIf="video.likes !== 0 || video.dislikes !== 0"
|
||||||
[ngbTooltip]="likesBarTooltipText"
|
[ngbTooltip]="likesBarTooltipText"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
|
|
|
@ -297,13 +297,13 @@ $video-info-margin-left: 44px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-info-likes-dislikes-bar-outerContainer {
|
.video-info-likes-dislikes-bar-outer-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-info-likes-dislikes-bar-innerContainer {
|
.video-info-likes-dislikes-bar-inner-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 30px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-info-likes-dislikes-bar {
|
.video-info-likes-dislikes-bar {
|
||||||
|
|
|
@ -37,7 +37,7 @@ import { PluginService } from '@app/core/plugins/plugin.service'
|
||||||
import { HooksService } from '@app/core/plugins/hooks.service'
|
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||||
import { PlatformLocation } from '@angular/common'
|
import { PlatformLocation } from '@angular/common'
|
||||||
import { RecommendedVideosComponent } from '../recommendations/recommended-videos.component'
|
import { RecommendedVideosComponent } from '../recommendations/recommended-videos.component'
|
||||||
import { scrollToTop } from '@app/shared/misc/utils'
|
import { scrollToTop, isInViewport, isXPercentInViewport } from '@app/shared/misc/utils'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-video-watch',
|
selector: 'my-video-watch',
|
||||||
|
@ -478,12 +478,18 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* replaces this.player.one('ended')
|
* replaces this.player.one('ended')
|
||||||
* define 'condition(next)' to return true to wait, false to stop
|
* 'condition()': true to make the upnext functionality trigger,
|
||||||
|
* false to disable the upnext functionality
|
||||||
|
* go to the next video in 'condition()' if you don't want of the timer.
|
||||||
|
* 'next': function triggered at the end of the timer.
|
||||||
|
* 'suspended': function used at each clic of the timer checking if we need
|
||||||
|
* to reset progress and wait until 'suspended' becomes truthy again.
|
||||||
*/
|
*/
|
||||||
this.player.upnext({
|
this.player.upnext({
|
||||||
timeout: 10000, // 10s
|
timeout: 10000, // 10s
|
||||||
headText: this.i18n('Up Next'),
|
headText: this.i18n('Up Next'),
|
||||||
cancelText: this.i18n('Cancel'),
|
cancelText: this.i18n('Cancel'),
|
||||||
|
suspendedText: this.i18n('Autoplay is suspended'),
|
||||||
getTitle: () => this.nextVideoTitle,
|
getTitle: () => this.nextVideoTitle,
|
||||||
next: () => this.zone.run(() => this.autoplayNext()),
|
next: () => this.zone.run(() => this.autoplayNext()),
|
||||||
condition: () => {
|
condition: () => {
|
||||||
|
@ -496,6 +502,12 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
||||||
return true // upnext will trigger
|
return true // upnext will trigger
|
||||||
}
|
}
|
||||||
return false // upnext will not trigger, and instead leave the video stopping
|
return false // upnext will not trigger, and instead leave the video stopping
|
||||||
|
},
|
||||||
|
suspended: () => {
|
||||||
|
return (
|
||||||
|
!isXPercentInViewport(this.player.el(), 80) ||
|
||||||
|
!document.getElementById('content').contains(document.activeElement)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ function getMainTemplate (options: any) {
|
||||||
<span class="vjs-upnext-cancel">
|
<span class="vjs-upnext-cancel">
|
||||||
<button class="vjs-upnext-cancel-button" tabindex="0" aria-label="Cancel autoplay">${options.cancelText}</button>
|
<button class="vjs-upnext-cancel-button" tabindex="0" aria-label="Cancel autoplay">${options.cancelText}</button>
|
||||||
</span>
|
</span>
|
||||||
|
<span class="vjs-upnext-suspended">${options.suspendedText}</span>
|
||||||
</span>
|
</span>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
@ -26,40 +27,34 @@ function getMainTemplate (options: any) {
|
||||||
const Component = videojs.getComponent('Component')
|
const Component = videojs.getComponent('Component')
|
||||||
class EndCard extends Component {
|
class EndCard extends Component {
|
||||||
options_: any
|
options_: any
|
||||||
getTitle: Function
|
|
||||||
next: Function
|
|
||||||
condition: Function
|
|
||||||
dashOffsetTotal = 586
|
dashOffsetTotal = 586
|
||||||
dashOffsetStart = 293
|
dashOffsetStart = 293
|
||||||
interval = 50
|
interval = 50
|
||||||
upNextEvents = new videojs.EventTarget()
|
upNextEvents = new videojs.EventTarget()
|
||||||
chunkSize: number
|
ticks = 0
|
||||||
|
totalTicks: number
|
||||||
|
|
||||||
container: HTMLElement
|
container: HTMLElement
|
||||||
title: HTMLElement
|
title: HTMLElement
|
||||||
autoplayRing: HTMLElement
|
autoplayRing: HTMLElement
|
||||||
cancelButton: HTMLElement
|
cancelButton: HTMLElement
|
||||||
|
suspendedMessage: HTMLElement
|
||||||
nextButton: HTMLElement
|
nextButton: HTMLElement
|
||||||
|
|
||||||
constructor (player: videojs.Player, options: any) {
|
constructor (player: videojs.Player, options: any) {
|
||||||
super(player, options)
|
super(player, options)
|
||||||
this.options_ = options
|
|
||||||
|
|
||||||
this.getTitle = this.options_.getTitle
|
this.totalTicks = this.options_.timeout / this.interval
|
||||||
this.next = this.options_.next
|
|
||||||
this.condition = this.options_.condition
|
|
||||||
|
|
||||||
this.chunkSize = (this.dashOffsetTotal - this.dashOffsetStart) / (this.options_.timeout / this.interval)
|
|
||||||
|
|
||||||
player.on('ended', (_: any) => {
|
player.on('ended', (_: any) => {
|
||||||
if (!this.condition()) return
|
if (!this.options_.condition()) return
|
||||||
|
|
||||||
player.addClass('vjs-upnext--showing')
|
player.addClass('vjs-upnext--showing')
|
||||||
this.showCard((canceled: boolean) => {
|
this.showCard((canceled: boolean) => {
|
||||||
player.removeClass('vjs-upnext--showing')
|
player.removeClass('vjs-upnext--showing')
|
||||||
this.container.style.display = 'none'
|
this.container.style.display = 'none'
|
||||||
if (!canceled) {
|
if (!canceled) {
|
||||||
this.next()
|
this.options_.next()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -81,6 +76,7 @@ class EndCard extends Component {
|
||||||
this.autoplayRing = container.getElementsByClassName('vjs-upnext-svg-autoplay-ring')[0]
|
this.autoplayRing = container.getElementsByClassName('vjs-upnext-svg-autoplay-ring')[0]
|
||||||
this.title = container.getElementsByClassName('vjs-upnext-title')[0]
|
this.title = container.getElementsByClassName('vjs-upnext-title')[0]
|
||||||
this.cancelButton = container.getElementsByClassName('vjs-upnext-cancel-button')[0]
|
this.cancelButton = container.getElementsByClassName('vjs-upnext-cancel-button')[0]
|
||||||
|
this.suspendedMessage = container.getElementsByClassName('vjs-upnext-suspended')[0]
|
||||||
this.nextButton = container.getElementsByClassName('vjs-upnext-autoplay-icon')[0]
|
this.nextButton = container.getElementsByClassName('vjs-upnext-autoplay-icon')[0]
|
||||||
|
|
||||||
this.cancelButton.onclick = () => {
|
this.cancelButton.onclick = () => {
|
||||||
|
@ -96,14 +92,11 @@ class EndCard extends Component {
|
||||||
|
|
||||||
showCard (cb: Function) {
|
showCard (cb: Function) {
|
||||||
let timeout: any
|
let timeout: any
|
||||||
let start: number
|
|
||||||
let now: number
|
|
||||||
let newOffset: number
|
|
||||||
|
|
||||||
this.autoplayRing.setAttribute('stroke-dasharray', '' + this.dashOffsetStart)
|
this.autoplayRing.setAttribute('stroke-dasharray', '' + this.dashOffsetStart)
|
||||||
this.autoplayRing.setAttribute('stroke-dashoffset', '' + -this.dashOffsetStart)
|
this.autoplayRing.setAttribute('stroke-dashoffset', '' + -this.dashOffsetStart)
|
||||||
|
|
||||||
this.title.innerHTML = this.getTitle()
|
this.title.innerHTML = this.options_.getTitle()
|
||||||
|
|
||||||
this.upNextEvents.one('cancel', () => {
|
this.upNextEvents.one('cancel', () => {
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
|
@ -120,23 +113,32 @@ class EndCard extends Component {
|
||||||
cb(false)
|
cb(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
const update = () => {
|
const goToPercent = (percent: number) => {
|
||||||
now = this.options_.timeout - (new Date().getTime() - start)
|
const newOffset = Math.max(-this.dashOffsetTotal, - this.dashOffsetStart - percent * this.dashOffsetTotal / 2 / 100)
|
||||||
|
this.autoplayRing.setAttribute('stroke-dashoffset', '' + newOffset)
|
||||||
|
}
|
||||||
|
|
||||||
if (now <= 0) {
|
const tick = () => {
|
||||||
|
goToPercent((this.ticks++) * 100 / this.totalTicks)
|
||||||
|
}
|
||||||
|
|
||||||
|
const update = () => {
|
||||||
|
if (this.options_.suspended()) {
|
||||||
|
this.suspendedMessage.innerText = this.options_.suspendedText
|
||||||
|
goToPercent(0)
|
||||||
|
this.ticks = 0
|
||||||
|
timeout = setTimeout(update.bind(this), 300) // checks once supsended can be a bit longer
|
||||||
|
} else if (this.ticks >= this.totalTicks) {
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
cb(false)
|
cb(false)
|
||||||
} else {
|
} else {
|
||||||
const strokeDashOffset = parseInt(this.autoplayRing.getAttribute('stroke-dashoffset'), 10)
|
this.suspendedMessage.innerText = ''
|
||||||
newOffset = Math.max(-this.dashOffsetTotal, strokeDashOffset - this.chunkSize)
|
tick()
|
||||||
this.autoplayRing.setAttribute('stroke-dashoffset', '' + newOffset)
|
|
||||||
timeout = setTimeout(update.bind(this), this.interval)
|
timeout = setTimeout(update.bind(this), this.interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.container.style.display = 'block'
|
this.container.style.display = 'block'
|
||||||
start = new Date().getTime()
|
|
||||||
timeout = setTimeout(update.bind(this), this.interval)
|
timeout = setTimeout(update.bind(this), this.interval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,7 +155,9 @@ class UpNextPlugin extends Plugin {
|
||||||
timeout: options.timeout || 5000,
|
timeout: options.timeout || 5000,
|
||||||
cancelText: options.cancelText || 'Cancel',
|
cancelText: options.cancelText || 'Cancel',
|
||||||
headText: options.headText || 'Up Next',
|
headText: options.headText || 'Up Next',
|
||||||
condition: options.condition
|
suspendedText: options.suspendedText || 'Autoplay is suspended',
|
||||||
|
condition: options.condition,
|
||||||
|
suspended: options.suspended
|
||||||
}
|
}
|
||||||
|
|
||||||
super(player, settings)
|
super(player, settings)
|
||||||
|
|
|
@ -40,12 +40,18 @@ $browser-context: 16;
|
||||||
margin-top: 52px;
|
margin-top: 52px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vjs-upnext-suspended,
|
||||||
.vjs-upnext-cancel {
|
.vjs-upnext-cancel {
|
||||||
display: block;
|
display: block;
|
||||||
float: none;
|
float: none;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vjs-upnext-suspended {
|
||||||
|
font-size: 50%;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.vjs-upnext-headtext {
|
.vjs-upnext-headtext {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
Loading…
Reference in New Issue