Fix upnext, refactor avatar menu, add to playlist overflow

This commit is contained in:
Rigel Kent 2019-12-20 17:49:57 +01:00
parent 1dc240a948
commit 223b24e618
No known key found for this signature in database
GPG Key ID: 5E53E96A494E452F
13 changed files with 108 additions and 51 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -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>

View File

@ -69,6 +69,7 @@ menu {
font-size: 13px; font-size: 13px;
color: #C6C6C6; color: #C6C6C6;
max-width: 140px; max-width: 140px;
cursor: pointer;
} }
} }

View File

@ -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
} }

View File

@ -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>

View File

@ -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;

View File

@ -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"

View File

@ -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 {

View File

@ -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)
)
} }
}) })

View File

@ -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)

View File

@ -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;