mirror of https://github.com/Chocobozzz/PeerTube
Simplify and improve subscribe button
parent
d9a1d170f1
commit
34957c5a18
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue