Simplify and improve subscribe button

pull/6711/head
Chocobozzz 2024-10-24 10:07:02 +02:00
parent d9a1d170f1
commit 34957c5a18
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
6 changed files with 138 additions and 109 deletions

View File

@ -9,6 +9,11 @@ input {
.btn { .btn {
@include button-with-icon(18px); @include button-with-icon(18px);
& {
line-height: 18px;
top: 0;
}
} }
.copy-text { .copy-text {

View File

@ -1,71 +1,63 @@
<div <div
class="btn-group-subscribe btn-group" role="group" class="btn-group" role="group"
[ngClass]="{'subscribe-button': !isAllChannelsSubscribed, 'unsubscribe-button': isAllChannelsSubscribed, 'big': isBigButton }" [ngClass]="{ 'big': isBigButton }"
ngbDropdown="dropdown" autoClose="outside" placement="bottom-right bottom-left bottom auto"
role="group" aria-label="Multiple ways to subscribe to the current channel" i18n-aria-label
> >
<ng-template #userLoggedOut> <ng-template #subscribeContent>
<div [ngClass]="{ 'extra-text': isAtLeastOneChannelSubscribed }"> <div class="d-inline-block" [ngClass]="{ 'extra-text': isAtLeastOneChannelSubscribed }">
@if (isSingleSubscribe) { @if (isSingleSubscribe) {
<ng-container i18n>Subscribe</ng-container> <ng-container i18n>Subscribe</ng-container>
} @else { } @else {
<div i18n>Subscribe to all channels</div> <div i18n>Subscribe to all channels</div>
<div class="mt-1 fs-8" *ngIf="isAtLeastOneChannelSubscribed" i18n> <div class="fs-8 fw-normal lh-1" *ngIf="isAtLeastOneChannelSubscribed" i18n>
{{ subscribeStatus(true).length }}/{{ subscribed.size }} channels subscribed {{ subscribeStatus(true).length }}/{{ subscribed.size }} channels subscribed
</div> </div>
} }
</div> </div>
<span *ngIf="!isBigButton && displayFollowers && videoChannels.length > 1 && videoChannel.followersCount !== 0" class="followers-count">
{{ videoChannels[0].followersCount | myNumberFormatter }}
</span>
</ng-template> </ng-template>
<ng-template #userLoggedIn> @if (isUserLoggedIn()) {
@if (isAllChannelsSubscribed) { @if (isAllChannelsSubscribed) {
<button type="button" class="btn" role="button" (click)="unsubscribe()"> <button type="button" [ngClass]="buttonClasses" class="btn" (click)="unsubscribe()">
<ng-container i18n>{account + "", select, undefined {Unsubscribe} other {Unsubscribe from all channels}}</ng-container> <ng-container i18n>{account + "", select, undefined {Unsubscribe} other {Unsubscribe from all channels}}</ng-container>
</button> </button>
} @else { } @else {
<button type="button" class="btn" (click)="subscribe()"> <button type="button" [ngClass]="buttonClasses" class="btn" (click)="subscribe()">
<ng-template [ngTemplateOutlet]="userLoggedOut"></ng-template> <ng-template [ngTemplateOutlet]="subscribeContent"></ng-template>
</button> </button>
<button type="button" [ngClass]="buttonClasses" class="btn dropdown-toggle-split" ngbDropdownToggle aria-label="Open subscription dropdown" i18n-ariaLabel></button>
} }
</ng-template> } @else {
<button ngbDropdownToggle [ngClass]="buttonClasses" type="button" class="btn">
<ng-template [ngTemplateOutlet]="subscribeContent"></ng-template>
</button>
}
<ng-container <div class="dropdown-menu" ngbDropdownMenu>
*ngIf="isUserLoggedIn(); then userLoggedIn">
</ng-container>
<div <h6 class="dropdown-header" i18n>Using an ActivityPub account</h6>
class="btn-group" ngbDropdown autoClose="outside" placement="bottom-right bottom-left bottom auto"
role="group" aria-label="Multiple ways to subscribe to the current channel" i18n-aria-label <button type="button" class="dropdown-item" (click)="subscribe()">
> @if (isUserLoggedIn()) {
<button class="btn dropdown-toggle-split last-in-group" ngbDropdownToggle aria-label="Open subscription dropdown" i18n-aria-label> <span i18n>Subscribe with your local account</span>
<ng-container } @else {
*ngIf="!isUserLoggedIn(); then userLoggedOut"> <span i18n>Subscribe with an account on this instance</span>
</ng-container> }
</button> </button>
<div class="dropdown-menu" ngbDropdownMenu> <div type="button" *ngIf="isRemoteSubscribeAvailable()" class="dropdown-item dropdown-item-neutral">
<div class="mb-1" i18n>Subscribe with a remote account:</div>
<h6 class="dropdown-header" i18n>Using an ActivityPub account</h6> <my-remote-subscribe [showHelp]="true" [uri]="uri"></my-remote-subscribe>
<button class="dropdown-item" (click)="subscribe()">
<span *ngIf="!isUserLoggedIn()" i18n>Subscribe with an account on this instance</span>
<span *ngIf="isUserLoggedIn()" i18n>Subscribe with your local account</span>
</button>
<button *ngIf="isRemoteSubscribeAvailable()" class="dropdown-item dropdown-item-neutral">
<div class="mb-1" i18n>Subscribe with a remote account:</div>
<my-remote-subscribe [showHelp]="true" [uri]="uri"></my-remote-subscribe>
</button>
<div class="dropdown-divider"></div>
<h6 class="dropdown-header" i18n>Using a syndication feed</h6>
<a [href]="rssUri" target="_blank" class="dropdown-item" i18n>Subscribe via RSS</a>
</div> </div>
<div class="dropdown-divider"></div>
<h6 class="dropdown-header" i18n>Using a syndication feed</h6>
<a [href]="rssUri" target="_blank" class="dropdown-item" i18n>Subscribe via RSS</a>
</div> </div>
</div> </div>

View File

@ -1,14 +1,26 @@
@use '_variables' as *; @use '_variables' as *;
@use '_mixins' as *; @use '_mixins' as *;
.btn-group-subscribe { .btn-group {
padding: 0; padding: 0;
@include peertube-button; .peertube-button {
// Prevent weird border radius blur on chrome
z-index: unset !important;
}
button.dropdown-toggle { .dropdown-toggle::after {
font-size: $button-font-size; position: relative;
line-height: 1.2; top: 1px;
}
.dropdown-toggle:not(.dropdown-toggle-split)::after {
margin-left: 0.5rem;
}
.dropdown-toggle-split {
padding-left: 8px;
padding-right: 8px;
} }
&:not(.big) { &:not(.big) {
@ -20,45 +32,6 @@
min-width: 175px; min-width: 175px;
} }
// Unlogged
> .dropdown > .dropdown-toggle span {
@include padding-right(5px);
}
// Logged
> .btn {
@include padding-right(4px);
+ .dropdown > button {
@include padding-left(2px);
&::after {
position: relative;
top: 1px;
}
}
}
&.subscribe-button {
.btn {
font-weight: 600;
@include orange-button;
}
span.followers-count {
@include padding-left(5px);
}
}
&.unsubscribe-button {
.btn {
font-weight: 600;
@include grey-button;
}
}
.dropdown-menu { .dropdown-menu {
cursor: default; cursor: default;

View File

@ -1,15 +1,15 @@
import { concat, forkJoin, merge } from 'rxjs'
import { Component, Input, OnChanges, OnInit } from '@angular/core'
import { AuthService, Notifier, RedirectService } from '@app/core'
import { FeedFormat } from '@peertube/peertube-models'
import { UserSubscriptionService } from './user-subscription.service'
import { NumberFormatterPipe } from '../shared-main/common/number-formatter.pipe'
import { RemoteSubscribeComponent } from './remote-subscribe.component'
import { NgbDropdown, NgbDropdownToggle, NgbDropdownMenu } from '@ng-bootstrap/ng-bootstrap'
import { NgClass, NgIf, NgTemplateOutlet } from '@angular/common' import { NgClass, NgIf, NgTemplateOutlet } from '@angular/common'
import { VideoChannel } from '../shared-main/channel/video-channel.model' import { Component, Input, OnChanges, ViewChild } from '@angular/core'
import { AuthService, Notifier, RedirectService } from '@app/core'
import { NgbDropdown, NgbDropdownItem, NgbDropdownMenu, NgbDropdownToggle } from '@ng-bootstrap/ng-bootstrap'
import { FeedFormat } from '@peertube/peertube-models'
import { concat, forkJoin, merge } from 'rxjs'
import { Account } from '../shared-main/account/account.model' import { Account } from '../shared-main/account/account.model'
import { VideoChannel } from '../shared-main/channel/video-channel.model'
import { NumberFormatterPipe } from '../shared-main/common/number-formatter.pipe'
import { VideoService } from '../shared-main/video/video.service' import { VideoService } from '../shared-main/video/video.service'
import { RemoteSubscribeComponent } from './remote-subscribe.component'
import { UserSubscriptionService } from './user-subscription.service'
@Component({ @Component({
selector: 'my-subscribe-button', selector: 'my-subscribe-button',
@ -23,11 +23,12 @@ import { VideoService } from '../shared-main/video/video.service'
NgbDropdown, NgbDropdown,
NgbDropdownToggle, NgbDropdownToggle,
NgbDropdownMenu, NgbDropdownMenu,
NgbDropdownItem,
RemoteSubscribeComponent, RemoteSubscribeComponent,
NumberFormatterPipe NumberFormatterPipe
] ]
}) })
export class SubscribeButtonComponent implements OnInit, OnChanges { export class SubscribeButtonComponent implements OnChanges {
/** /**
* SubscribeButtonComponent can be used with a single VideoChannel passed as [VideoChannel], * SubscribeButtonComponent can be used with a single VideoChannel passed as [VideoChannel],
* or with an account and a full list of that account's videoChannels. The latter is intended * or with an account and a full list of that account's videoChannels. The latter is intended
@ -36,11 +37,14 @@ export class SubscribeButtonComponent implements OnInit, OnChanges {
*/ */
@Input() account: Account @Input() account: Account
@Input() videoChannels: VideoChannel[] @Input() videoChannels: VideoChannel[]
@Input() displayFollowers = false
@Input() size: 'small' | 'normal' = 'normal' @Input() size: 'small' | 'normal' = 'normal'
@ViewChild('dropdown') dropdown: NgbDropdown
subscribed = new Map<string, boolean>() subscribed = new Map<string, boolean>()
buttonClasses: Record<string, boolean> = {}
constructor ( constructor (
private authService: AuthService, private authService: AuthService,
private redirectService: RedirectService, private redirectService: RedirectService,
@ -97,15 +101,16 @@ export class SubscribeButtonComponent implements OnInit, OnChanges {
return !this.account return !this.account
} }
ngOnInit () { ngOnChanges () {
this.loadSubscribedStatus() this.loadSubscribedStatus()
this.buildClasses()
} }
ngOnChanges () { // ---------------------------------------------------------------------------
this.ngOnInit()
}
subscribe () { subscribe () {
if (this.dropdown) this.dropdown.close()
if (this.isUserLoggedIn()) { if (this.isUserLoggedIn()) {
return this.localSubscribe() return this.localSubscribe()
} }
@ -113,7 +118,7 @@ export class SubscribeButtonComponent implements OnInit, OnChanges {
return this.gotoLogin() return this.gotoLogin()
} }
localSubscribe () { private localSubscribe () {
const subscribedStatus = this.subscribeStatus(false) const subscribedStatus = this.subscribeStatus(false)
const observableBatch = this.videoChannels const observableBatch = this.videoChannels
@ -124,6 +129,8 @@ export class SubscribeButtonComponent implements OnInit, OnChanges {
forkJoin(observableBatch) forkJoin(observableBatch)
.subscribe({ .subscribe({
next: () => { next: () => {
this.buildClasses()
this.notifier.success( this.notifier.success(
this.account this.account
? $localize`Subscribed to all current channels of ${this.account.displayName}. You will be notified of all their new videos.` ? $localize`Subscribed to all current channels of ${this.account.displayName}. You will be notified of all their new videos.`
@ -137,13 +144,17 @@ export class SubscribeButtonComponent implements OnInit, OnChanges {
}) })
} }
// ---------------------------------------------------------------------------
unsubscribe () { unsubscribe () {
if (this.dropdown) this.dropdown.close()
if (this.isUserLoggedIn()) { if (this.isUserLoggedIn()) {
this.localUnsubscribe() this.localUnsubscribe()
} }
} }
localUnsubscribe () { private localUnsubscribe () {
const subscribeStatus = this.subscribeStatus(true) const subscribeStatus = this.subscribeStatus(true)
const observableBatch = this.videoChannels const observableBatch = this.videoChannels
@ -154,6 +165,8 @@ export class SubscribeButtonComponent implements OnInit, OnChanges {
concat(...observableBatch) concat(...observableBatch)
.subscribe({ .subscribe({
complete: () => { complete: () => {
this.buildClasses()
this.notifier.success( this.notifier.success(
this.account this.account
? $localize`Unsubscribed from all channels of ${this.account.nameWithHost}` ? $localize`Unsubscribed from all channels of ${this.account.nameWithHost}`
@ -167,6 +180,8 @@ export class SubscribeButtonComponent implements OnInit, OnChanges {
}) })
} }
// ---------------------------------------------------------------------------
isUserLoggedIn () { isUserLoggedIn () {
return this.authService.isLoggedIn() return this.authService.isLoggedIn()
} }
@ -207,10 +222,22 @@ export class SubscribeButtonComponent implements OnInit, OnChanges {
this.userSubscriptionService.listenToSubscriptionCacheChange(handle), this.userSubscriptionService.listenToSubscriptionCacheChange(handle),
this.userSubscriptionService.doesSubscriptionExist(handle) this.userSubscriptionService.doesSubscriptionExist(handle)
).subscribe({ ).subscribe({
next: res => this.subscribed.set(handle, res), next: res => {
this.subscribed.set(handle, res)
this.buildClasses()
},
error: err => this.notifier.error(err.message) error: err => this.notifier.error(err.message)
}) })
} }
} }
private buildClasses () {
this.buttonClasses = {
'peertube-button': true,
'orange-button': !this.isAllChannelsSubscribed,
'grey-button': this.isAllChannelsSubscribed
}
}
} }

View File

@ -56,6 +56,12 @@ body {
--bs-btn-color: #{pvar(--greyForegroundColor)}; --bs-btn-color: #{pvar(--greyForegroundColor)};
} }
.btn {
--bs-btn-active-color: inherit;
--bs-btn-active-bg: inherit;
--bs-btn-active-border-color: inherit;
}
.flex-auto { .flex-auto {
flex: auto; flex: auto;
} }
@ -291,7 +297,6 @@ body {
.btn:not(.btn-sm) { .btn:not(.btn-sm) {
font-size: $button-font-size; font-size: $button-font-size;
line-height: 1.2;
} }
.btn-outline-secondary { .btn-outline-secondary {

View File

@ -127,6 +127,16 @@
&:focus { &:focus {
color: #fff; color: #fff;
background-color: pvar(--mainColor); background-color: pvar(--mainColor);
border-color: inherit;
}
// Override bootstrap
&.btn:active,
&.btn:focus-visible,
&.btn.show {
color: #fff !important;
background-color: pvar(--mainColor) !important;
border-color: inherit !important;
} }
&:hover { &:hover {
@ -159,6 +169,15 @@
background-color: pvar(--mainBackgroundColor); background-color: pvar(--mainBackgroundColor);
} }
// Override bootstrap
&.btn:active,
&.btn:focus-visible,
&.btn.show {
color: pvar(--mainColor);
background-color: pvar(--mainBackgroundColor);
border-color: inherit !important;
}
&:hover { &:hover {
color: pvar(--mainColor); color: pvar(--mainColor);
background-color: pvar(--mainColorLightest); background-color: pvar(--mainColorLightest);
@ -204,6 +223,15 @@
background-color: pvar(--greySecondaryBackgroundColor); background-color: pvar(--greySecondaryBackgroundColor);
} }
// Override bootstrap
&.btn:active,
&.btn:focus-visible,
&.btn.show {
color: pvar(--greyForegroundColor);
background-color: pvar(--greySecondaryBackgroundColor);
border-color: inherit !important;
}
&[disabled] { &[disabled] {
cursor: default; cursor: default;
} }
@ -241,8 +269,7 @@
border: 0; border: 0;
font-weight: $font-semibold; font-weight: $font-semibold;
// Because of primeng that redefines border-radius of all input[type="..."] border-radius: 3px;
border-radius: 3px !important;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;