From 223b24e618146f85b20b5bf365bc18d14a5964cd Mon Sep 17 00:00:00 2001 From: Rigel Kent <sendmemail@rigelk.eu> Date: Fri, 20 Dec 2019 17:49:57 +0100 Subject: [PATCH] Fix upnext, refactor avatar menu, add to playlist overflow --- .../src/app/+accounts/accounts.component.html | 2 +- .../menu/avatar-notification.component.html | 2 +- .../menu/avatar-notification.component.scss | 2 +- client/src/app/menu/menu.component.html | 18 +++++-- client/src/app/menu/menu.component.scss | 1 + client/src/app/shared/misc/utils.ts | 24 ++++++++- .../video-add-to-playlist.component.html | 14 ++--- .../video-add-to-playlist.component.scss | 10 ++-- .../+video-watch/video-watch.component.html | 4 +- .../+video-watch/video-watch.component.scss | 6 +-- .../+video-watch/video-watch.component.ts | 16 +++++- .../src/assets/player/upnext/upnext-plugin.ts | 54 ++++++++++--------- client/src/sass/player/upnext.scss | 6 +++ 13 files changed, 108 insertions(+), 51 deletions(-) diff --git a/client/src/app/+accounts/accounts.component.html b/client/src/app/+accounts/accounts.component.html index 6269091df6..70257162d5 100644 --- a/client/src/app/+accounts/accounts.component.html +++ b/client/src/app/+accounts/accounts.component.html @@ -23,7 +23,7 @@ <span *ngIf="account.mutedServerByInstance" class="badge badge-danger" i18n>Instance muted by your instance</span> <my-user-moderation-dropdown - buttonSize="small" [account]="account" [user]="user" + buttonSize="small" [account]="account" [user]="user" placement="bottom-right auto" (userChanged)="onUserChanged()" (userDeleted)="onUserDeleted()" > </my-user-moderation-dropdown> diff --git a/client/src/app/menu/avatar-notification.component.html b/client/src/app/menu/avatar-notification.component.html index 1b6e6dcf8b..8ffec46da6 100644 --- a/client/src/app/menu/avatar-notification.component.html +++ b/client/src/app/menu/avatar-notification.component.html @@ -25,7 +25,7 @@ </div> </div> - <div *ngIf="!loaded" class="loader"> + <div *ngIf="!loaded" class="loader mt-4"> <my-loader [loading]="!loaded"></my-loader> </div> diff --git a/client/src/app/menu/avatar-notification.component.scss b/client/src/app/menu/avatar-notification.component.scss index 713ac7cb91..2ca7f24dca 100644 --- a/client/src/app/menu/avatar-notification.component.scss +++ b/client/src/app/menu/avatar-notification.component.scss @@ -45,7 +45,7 @@ align-items: center; padding: 0 10px; font-size: 16px; - height: 50px; + min-height: 50px; a { @include disable-default-a-behaviour; diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html index 3f406586e4..848f9949f9 100644 --- a/client/src/app/menu/menu.component.html +++ b/client/src/app/menu/menu.component.html @@ -5,8 +5,10 @@ <my-avatar-notification [user]="user"></my-avatar-notification> <div class="logged-in-info"> - <a routerLink="/my-account/settings" class="logged-in-display-name">{{ user.account?.displayName }}</a> - <div class="logged-in-username">{{ user.username }}</div> + <a *ngIf="user.account" [routerLink]="[ '/accounts', user.account.nameWithHost ]" class="logged-in-display-name">{{ user.account?.displayName }}</a> + <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 class="logged-in-more" ngbDropdown placement="bottom-right auto"> @@ -14,13 +16,21 @@ <div ngbDropdownMenu> <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> + <div class="dropdown-divider"></div> + <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 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="#"> <my-global-icon iconName="sign-out"></my-global-icon> <ng-container i18n>Log out</ng-container> </a> diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss index 79a28d258f..2963d4d191 100644 --- a/client/src/app/menu/menu.component.scss +++ b/client/src/app/menu/menu.component.scss @@ -69,6 +69,7 @@ menu { font-size: 13px; color: #C6C6C6; max-width: 140px; + cursor: pointer; } } diff --git a/client/src/app/shared/misc/utils.ts b/client/src/app/shared/misc/utils.ts index f26240d216..b1d1fc0b52 100644 --- a/client/src/app/shared/misc/utils.ts +++ b/client/src/app/shared/misc/utils.ts @@ -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 { sortBy, durationToString, @@ -183,5 +203,7 @@ export { objectLineFeedToHtml, removeElementFromArray, importModule, - scrollToTop + scrollToTop, + isInViewport, + isXPercentInViewport } diff --git a/client/src/app/shared/video-playlist/video-add-to-playlist.component.html b/client/src/app/shared/video-playlist/video-add-to-playlist.component.html index 648d580fa3..0cc8af3452 100644 --- a/client/src/app/shared/video-playlist/video-add-to-playlist.component.html +++ b/client/src/app/shared/video-playlist/video-add-to-playlist.component.html @@ -41,14 +41,16 @@ </div> </div> - <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> + <div class="playlists"> + <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> - <div class="display-name"> - {{ playlist.displayName }} + <div class="display-name"> + {{ playlist.displayName }} - <div *ngIf="playlist.inPlaylist && (playlist.startTimestamp || playlist.stopTimestamp)" class="timestamp-info"> - {{ formatTimestamp(playlist) }} + <div *ngIf="playlist.inPlaylist && (playlist.startTimestamp || playlist.stopTimestamp)" class="timestamp-info"> + {{ formatTimestamp(playlist) }} + </div> </div> </div> </div> diff --git a/client/src/app/shared/video-playlist/video-add-to-playlist.component.scss b/client/src/app/shared/video-playlist/video-add-to-playlist.component.scss index c677fea6ce..090b530cf1 100644 --- a/client/src/app/shared/video-playlist/video-add-to-playlist.component.scss +++ b/client/src/app/shared/video-playlist/video-add-to-playlist.component.scss @@ -1,11 +1,6 @@ @import '_variables'; @import '_mixins'; -.root { - max-height: 300px; - overflow-y: auto; -} - .header { min-width: 240px; padding: 6px 24px 10px 24px; @@ -53,6 +48,11 @@ padding: 6px 24px; } +.playlists { + max-height: 180px; + overflow-y: auto; +} + .playlist { display: flex; cursor: pointer; 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 7a9f00a505..9e69033e18 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.html +++ b/client/src/app/videos/+video-watch/video-watch.component.html @@ -114,9 +114,9 @@ ></my-video-actions-dropdown> </div> - <div class="video-info-likes-dislikes-bar-outerContainer"> + <div class="video-info-likes-dislikes-bar-outer-container"> <div - class="video-info-likes-dislikes-bar-innerContainer" + class="video-info-likes-dislikes-bar-inner-container" *ngIf="video.likes !== 0 || video.dislikes !== 0" [ngbTooltip]="likesBarTooltipText" placement="bottom" diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss index 180b7c6ad2..f9ff83c342 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.scss +++ b/client/src/app/videos/+video-watch/video-watch.component.scss @@ -297,13 +297,13 @@ $video-info-margin-left: 44px; } } - .video-info-likes-dislikes-bar-outerContainer { + .video-info-likes-dislikes-bar-outer-container { position: relative; } - .video-info-likes-dislikes-bar-innerContainer { + .video-info-likes-dislikes-bar-inner-container { position: absolute; - height: 30px; + height: 20px; } .video-info-likes-dislikes-bar { 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 bad0144bf8..dcceb14008 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts @@ -37,7 +37,7 @@ import { PluginService } from '@app/core/plugins/plugin.service' import { HooksService } from '@app/core/plugins/hooks.service' import { PlatformLocation } from '@angular/common' import { RecommendedVideosComponent } from '../recommendations/recommended-videos.component' -import { scrollToTop } from '@app/shared/misc/utils' +import { scrollToTop, isInViewport, isXPercentInViewport } from '@app/shared/misc/utils' @Component({ selector: 'my-video-watch', @@ -478,12 +478,18 @@ export class VideoWatchComponent implements OnInit, OnDestroy { /** * 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({ timeout: 10000, // 10s headText: this.i18n('Up Next'), cancelText: this.i18n('Cancel'), + suspendedText: this.i18n('Autoplay is suspended'), getTitle: () => this.nextVideoTitle, next: () => this.zone.run(() => this.autoplayNext()), condition: () => { @@ -496,6 +502,12 @@ export class VideoWatchComponent implements OnInit, OnDestroy { return true // upnext will trigger } 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) + ) } }) diff --git a/client/src/assets/player/upnext/upnext-plugin.ts b/client/src/assets/player/upnext/upnext-plugin.ts index ba9afbe3de..a3747b25fa 100644 --- a/client/src/assets/player/upnext/upnext-plugin.ts +++ b/client/src/assets/player/upnext/upnext-plugin.ts @@ -18,6 +18,7 @@ function getMainTemplate (options: any) { <span class="vjs-upnext-cancel"> <button class="vjs-upnext-cancel-button" tabindex="0" aria-label="Cancel autoplay">${options.cancelText}</button> </span> + <span class="vjs-upnext-suspended">${options.suspendedText}</span> </span> ` } @@ -26,40 +27,34 @@ function getMainTemplate (options: any) { const Component = videojs.getComponent('Component') class EndCard extends Component { options_: any - getTitle: Function - next: Function - condition: Function dashOffsetTotal = 586 dashOffsetStart = 293 interval = 50 upNextEvents = new videojs.EventTarget() - chunkSize: number + ticks = 0 + totalTicks: number container: HTMLElement title: HTMLElement autoplayRing: HTMLElement cancelButton: HTMLElement + suspendedMessage: HTMLElement nextButton: HTMLElement constructor (player: videojs.Player, options: any) { super(player, options) - this.options_ = options - this.getTitle = this.options_.getTitle - this.next = this.options_.next - this.condition = this.options_.condition - - this.chunkSize = (this.dashOffsetTotal - this.dashOffsetStart) / (this.options_.timeout / this.interval) + this.totalTicks = this.options_.timeout / this.interval player.on('ended', (_: any) => { - if (!this.condition()) return + if (!this.options_.condition()) return player.addClass('vjs-upnext--showing') this.showCard((canceled: boolean) => { player.removeClass('vjs-upnext--showing') this.container.style.display = 'none' 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.title = container.getElementsByClassName('vjs-upnext-title')[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.cancelButton.onclick = () => { @@ -96,14 +92,11 @@ class EndCard extends Component { showCard (cb: Function) { let timeout: any - let start: number - let now: number - let newOffset: number this.autoplayRing.setAttribute('stroke-dasharray', '' + this.dashOffsetStart) this.autoplayRing.setAttribute('stroke-dashoffset', '' + -this.dashOffsetStart) - this.title.innerHTML = this.getTitle() + this.title.innerHTML = this.options_.getTitle() this.upNextEvents.one('cancel', () => { clearTimeout(timeout) @@ -120,23 +113,32 @@ class EndCard extends Component { cb(false) }) - const update = () => { - now = this.options_.timeout - (new Date().getTime() - start) + const goToPercent = (percent: number) => { + 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) cb(false) } else { - const strokeDashOffset = parseInt(this.autoplayRing.getAttribute('stroke-dashoffset'), 10) - newOffset = Math.max(-this.dashOffsetTotal, strokeDashOffset - this.chunkSize) - this.autoplayRing.setAttribute('stroke-dashoffset', '' + newOffset) + this.suspendedMessage.innerText = '' + tick() timeout = setTimeout(update.bind(this), this.interval) } - } this.container.style.display = 'block' - start = new Date().getTime() timeout = setTimeout(update.bind(this), this.interval) } } @@ -153,7 +155,9 @@ class UpNextPlugin extends Plugin { timeout: options.timeout || 5000, cancelText: options.cancelText || 'Cancel', headText: options.headText || 'Up Next', - condition: options.condition + suspendedText: options.suspendedText || 'Autoplay is suspended', + condition: options.condition, + suspended: options.suspended } super(player, settings) diff --git a/client/src/sass/player/upnext.scss b/client/src/sass/player/upnext.scss index f1f2e0fe24..7614bb3b67 100644 --- a/client/src/sass/player/upnext.scss +++ b/client/src/sass/player/upnext.scss @@ -40,12 +40,18 @@ $browser-context: 16; margin-top: 52px; } + .vjs-upnext-suspended, .vjs-upnext-cancel { display: block; float: none; text-align: center; } + .vjs-upnext-suspended { + font-size: 50%; + margin-top: 1rem; + } + .vjs-upnext-headtext { display: block; font-size: 14px; -- GitLab