Add instance banner on login page
This commit is contained in:
parent
cbfe10a43e
commit
93f9677463
|
@ -2,7 +2,6 @@ import { SelectOptionsItem } from 'src/types/select-options-item.model'
|
|||
import { Component, Input, OnInit } from '@angular/core'
|
||||
import { FormGroup } from '@angular/forms'
|
||||
import { CustomMarkupService } from '@app/shared/shared-custom-markup'
|
||||
import { ConfigService } from '../shared/config.service'
|
||||
import { Notifier } from '@app/core'
|
||||
import { HttpErrorResponse } from '@angular/common/http'
|
||||
import { genericUploadErrorHandler } from '@app/helpers'
|
||||
|
@ -24,9 +23,8 @@ export class EditInstanceInformationComponent implements OnInit {
|
|||
|
||||
constructor (
|
||||
private customMarkup: CustomMarkupService,
|
||||
private configService: ConfigService,
|
||||
private notifier: Notifier,
|
||||
private instance: InstanceService
|
||||
private instanceService: InstanceService
|
||||
) {
|
||||
|
||||
}
|
||||
|
@ -40,9 +38,9 @@ export class EditInstanceInformationComponent implements OnInit {
|
|||
}
|
||||
|
||||
onBannerChange (formData: FormData) {
|
||||
this.configService.updateInstanceBanner(formData)
|
||||
this.instanceService.updateInstanceBanner(formData)
|
||||
.subscribe({
|
||||
next: data => {
|
||||
next: () => {
|
||||
this.notifier.success($localize`Banner changed.`)
|
||||
|
||||
this.resetBannerUrl()
|
||||
|
@ -53,7 +51,7 @@ export class EditInstanceInformationComponent implements OnInit {
|
|||
}
|
||||
|
||||
onBannerDelete () {
|
||||
this.configService.deleteInstanceBanner()
|
||||
this.instanceService.deleteInstanceBanner()
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.notifier.success($localize`Banner deleted.`)
|
||||
|
@ -66,15 +64,9 @@ export class EditInstanceInformationComponent implements OnInit {
|
|||
}
|
||||
|
||||
private resetBannerUrl () {
|
||||
this.instance.getAbout()
|
||||
.subscribe(about => {
|
||||
const banners = about.instance.banners
|
||||
if (banners.length === 0) {
|
||||
this.instanceBannerUrl = undefined
|
||||
return
|
||||
}
|
||||
|
||||
this.instanceBannerUrl = banners[0].path
|
||||
this.instanceService.getInstanceBannerUrl()
|
||||
.subscribe(instanceBannerUrl => {
|
||||
this.instanceBannerUrl = instanceBannerUrl
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,20 +67,4 @@ export class ConfigService {
|
|||
return this.authHttp.put<CustomConfig>(ConfigService.BASE_APPLICATION_URL + '/custom', data)
|
||||
.pipe(catchError(res => this.restExtractor.handleError(res)))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
updateInstanceBanner (formData: FormData) {
|
||||
const url = ConfigService.BASE_APPLICATION_URL + '/instance-banner/pick'
|
||||
|
||||
return this.authHttp.post(url, formData)
|
||||
.pipe(catchError(err => this.restExtractor.handleError(err)))
|
||||
}
|
||||
|
||||
deleteInstanceBanner () {
|
||||
const url = ConfigService.BASE_APPLICATION_URL + '/instance-banner'
|
||||
|
||||
return this.authHttp.delete(url)
|
||||
.pipe(catchError(err => this.restExtractor.handleError(err)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@
|
|||
</div>
|
||||
</form>
|
||||
|
||||
<div class="external-login-blocks" *ngIf="getExternalLogins().length !== 0">
|
||||
<div class="external-login-blocks" *ngIf="hasExternalLogins()">
|
||||
<div class="fw-semibold" i18n>Or sign in with</div>
|
||||
|
||||
<div>
|
||||
|
@ -107,6 +107,8 @@
|
|||
</div>
|
||||
|
||||
<div #instanceInformation class="instance-information">
|
||||
<my-instance-banner class="rounded"></my-instance-banner>
|
||||
|
||||
<my-instance-about-accordion
|
||||
#instanceAboutAccordion
|
||||
[displayInstanceName]="false"
|
||||
|
|
|
@ -32,6 +32,8 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni
|
|||
externalAuthError = false
|
||||
externalLogins: string[] = []
|
||||
|
||||
instanceBannerUrl: string
|
||||
|
||||
instanceInformationPanels = {
|
||||
terms: true,
|
||||
administrators: false,
|
||||
|
@ -120,6 +122,10 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni
|
|||
return this.serverConfig.plugin.registeredExternalAuths
|
||||
}
|
||||
|
||||
hasExternalLogins () {
|
||||
return this.getExternalLogins().length !== 0
|
||||
}
|
||||
|
||||
getAuthHref (auth: RegisteredExternalAuthConfig) {
|
||||
return getExternalAuthHref(environment.apiUrl, auth)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { firstValueFrom } from 'rxjs'
|
||||
import { ComponentRef, Injectable } from '@angular/core'
|
||||
import { MarkdownService } from '@app/core'
|
||||
import { logger } from '@root-helpers/logger'
|
||||
|
@ -24,7 +23,7 @@ import {
|
|||
} from './peertube-custom-tags'
|
||||
import { CustomMarkupComponent } from './peertube-custom-tags/shared'
|
||||
|
||||
type AngularBuilderFunction = (el: HTMLElement) => ComponentRef<CustomMarkupComponent>
|
||||
type AngularBuilderFunction = (el: HTMLElement) => { component: ComponentRef<CustomMarkupComponent>, loadedPromise: Promise<boolean> }
|
||||
type HTMLBuilderFunction = (el: HTMLElement) => HTMLElement
|
||||
|
||||
@Injectable()
|
||||
|
@ -85,12 +84,8 @@ export class CustomMarkupService {
|
|||
rootElement.querySelectorAll(selector)
|
||||
.forEach((e: HTMLElement) => {
|
||||
try {
|
||||
const component = this.execAngularBuilder(selector, e)
|
||||
|
||||
if (component.instance.loaded) {
|
||||
const p = firstValueFrom(component.instance.loaded)
|
||||
loadedPromises.push(p)
|
||||
}
|
||||
const { component, loadedPromise } = this.execAngularBuilder(selector, e)
|
||||
if (loadedPromise) loadedPromises.push(loadedPromise)
|
||||
|
||||
this.dynamicElementService.injectElement(e, component)
|
||||
} catch (err) {
|
||||
|
@ -117,25 +112,25 @@ export class CustomMarkupService {
|
|||
|
||||
private embedBuilder (el: HTMLElement, type: 'video' | 'playlist') {
|
||||
const data = el.dataset as EmbedMarkupData
|
||||
const component = this.dynamicElementService.createElement(EmbedMarkupComponent)
|
||||
const { component, loadedPromise } = this.dynamicElementService.createElement(EmbedMarkupComponent)
|
||||
|
||||
this.dynamicElementService.setModel(component, { uuid: data.uuid, type })
|
||||
|
||||
return component
|
||||
return { component, loadedPromise }
|
||||
}
|
||||
|
||||
private playlistMiniatureBuilder (el: HTMLElement) {
|
||||
const data = el.dataset as PlaylistMiniatureMarkupData
|
||||
const component = this.dynamicElementService.createElement(PlaylistMiniatureMarkupComponent)
|
||||
const { component, loadedPromise } = this.dynamicElementService.createElement(PlaylistMiniatureMarkupComponent)
|
||||
|
||||
this.dynamicElementService.setModel(component, { uuid: data.uuid })
|
||||
|
||||
return component
|
||||
return { component, loadedPromise }
|
||||
}
|
||||
|
||||
private channelMiniatureBuilder (el: HTMLElement) {
|
||||
const data = el.dataset as ChannelMiniatureMarkupData
|
||||
const component = this.dynamicElementService.createElement(ChannelMiniatureMarkupComponent)
|
||||
const { component, loadedPromise } = this.dynamicElementService.createElement(ChannelMiniatureMarkupComponent)
|
||||
|
||||
const model = {
|
||||
name: data.name,
|
||||
|
@ -145,12 +140,12 @@ export class CustomMarkupService {
|
|||
|
||||
this.dynamicElementService.setModel(component, model)
|
||||
|
||||
return component
|
||||
return { component, loadedPromise }
|
||||
}
|
||||
|
||||
private buttonBuilder (el: HTMLElement) {
|
||||
const data = el.dataset as ButtonMarkupData
|
||||
const component = this.dynamicElementService.createElement(ButtonMarkupComponent)
|
||||
const { component, loadedPromise } = this.dynamicElementService.createElement(ButtonMarkupComponent)
|
||||
|
||||
const model = {
|
||||
theme: data.theme ?? 'primary',
|
||||
|
@ -160,12 +155,12 @@ export class CustomMarkupService {
|
|||
}
|
||||
this.dynamicElementService.setModel(component, model)
|
||||
|
||||
return component
|
||||
return { component, loadedPromise }
|
||||
}
|
||||
|
||||
private instanceBannerBuilder (el: HTMLElement) {
|
||||
const data = el.dataset as InstanceBannerMarkupData
|
||||
const component = this.dynamicElementService.createElement(InstanceBannerMarkupComponent)
|
||||
const { component, loadedPromise } = this.dynamicElementService.createElement(InstanceBannerMarkupComponent)
|
||||
|
||||
const model = {
|
||||
revertHomePaddingTop: this.buildBoolean(data.revertHomePaddingTop) ?? true
|
||||
|
@ -173,12 +168,12 @@ export class CustomMarkupService {
|
|||
|
||||
this.dynamicElementService.setModel(component, model)
|
||||
|
||||
return component
|
||||
return { component, loadedPromise }
|
||||
}
|
||||
|
||||
private videoMiniatureBuilder (el: HTMLElement) {
|
||||
const data = el.dataset as VideoMiniatureMarkupData
|
||||
const component = this.dynamicElementService.createElement(VideoMiniatureMarkupComponent)
|
||||
const { component, loadedPromise } = this.dynamicElementService.createElement(VideoMiniatureMarkupComponent)
|
||||
|
||||
const model = {
|
||||
uuid: data.uuid,
|
||||
|
@ -187,12 +182,12 @@ export class CustomMarkupService {
|
|||
|
||||
this.dynamicElementService.setModel(component, model)
|
||||
|
||||
return component
|
||||
return { component, loadedPromise }
|
||||
}
|
||||
|
||||
private videosListBuilder (el: HTMLElement) {
|
||||
const data = el.dataset as VideosListMarkupData
|
||||
const component = this.dynamicElementService.createElement(VideosListMarkupComponent)
|
||||
const { component, loadedPromise } = this.dynamicElementService.createElement(VideosListMarkupComponent)
|
||||
|
||||
const model = {
|
||||
onlyDisplayTitle: this.buildBoolean(data.onlyDisplayTitle) ?? false,
|
||||
|
@ -214,7 +209,7 @@ export class CustomMarkupService {
|
|||
|
||||
this.dynamicElementService.setModel(component, model)
|
||||
|
||||
return component
|
||||
return { component, loadedPromise }
|
||||
}
|
||||
|
||||
private containerBuilder (el: HTMLElement) {
|
||||
|
|
|
@ -11,6 +11,8 @@ import {
|
|||
Type
|
||||
} from '@angular/core'
|
||||
import { objectKeysTyped } from '@peertube/peertube-core-utils'
|
||||
import { CustomMarkupComponent } from './peertube-custom-tags/shared'
|
||||
import { firstValueFrom } from 'rxjs'
|
||||
|
||||
@Injectable()
|
||||
export class DynamicElementService {
|
||||
|
@ -20,7 +22,7 @@ export class DynamicElementService {
|
|||
private applicationRef: ApplicationRef
|
||||
) { }
|
||||
|
||||
createElement <T> (ofComponent: Type<T>) {
|
||||
createElement <T extends CustomMarkupComponent> (ofComponent: Type<T>) {
|
||||
const div = document.createElement('div')
|
||||
|
||||
const component = createComponent(ofComponent, {
|
||||
|
@ -29,7 +31,11 @@ export class DynamicElementService {
|
|||
hostElement: div
|
||||
})
|
||||
|
||||
return component
|
||||
const loadedPromise = component.instance.loaded
|
||||
? firstValueFrom(component.instance.loaded)
|
||||
: undefined
|
||||
|
||||
return { component, loadedPromise }
|
||||
}
|
||||
|
||||
injectElement <T> (wrapper: HTMLElement, componentRef: ComponentRef<T>) {
|
||||
|
|
|
@ -25,12 +25,10 @@ export class InstanceBannerMarkupComponent implements OnInit, CustomMarkupCompon
|
|||
) {}
|
||||
|
||||
ngOnInit () {
|
||||
this.instance.getAbout()
|
||||
this.instance.getInstanceBannerUrl()
|
||||
.pipe(finalize(() => this.loaded.emit(true)))
|
||||
.subscribe(about => {
|
||||
if (about.instance.banners.length === 0) return
|
||||
|
||||
this.instanceBannerUrl = about.instance.banners[0].path
|
||||
.subscribe(instanceBannerUrl => {
|
||||
this.instanceBannerUrl = instanceBannerUrl
|
||||
this.cd.markForCheck()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export * from './feature-boolean.component'
|
||||
export * from './instance-about-accordion.component'
|
||||
export * from './instance-banner.component'
|
||||
export * from './instance-features-table.component'
|
||||
export * from './instance-follow.service'
|
||||
export * from './instance.service'
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<div class="banner" [ngClass]="{ rounded }">
|
||||
<img class="rounded" [src]="instanceBannerUrl" alt="Instance banner">
|
||||
</div>
|
|
@ -0,0 +1,21 @@
|
|||
import { Component, Input, OnInit, booleanAttribute } from '@angular/core'
|
||||
import { InstanceService } from './instance.service'
|
||||
|
||||
@Component({
|
||||
selector: 'my-instance-banner',
|
||||
templateUrl: './instance-banner.component.html'
|
||||
})
|
||||
export class InstanceBannerComponent implements OnInit {
|
||||
@Input({ transform: booleanAttribute }) rounded: boolean
|
||||
|
||||
instanceBannerUrl: string
|
||||
|
||||
constructor (private instanceService: InstanceService) {
|
||||
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
this.instanceService.getInstanceBannerUrl()
|
||||
.subscribe(instanceBannerUrl => this.instanceBannerUrl = instanceBannerUrl)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { forkJoin } from 'rxjs'
|
||||
import { catchError, map } from 'rxjs/operators'
|
||||
import { forkJoin, of } from 'rxjs'
|
||||
import { catchError, map, tap } from 'rxjs/operators'
|
||||
import { HttpClient } from '@angular/common/http'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { MarkdownService, RestExtractor, ServerService } from '@app/core'
|
||||
|
@ -18,6 +18,8 @@ export class InstanceService {
|
|||
private static BASE_CONFIG_URL = environment.apiUrl + '/api/v1/config'
|
||||
private static BASE_SERVER_URL = environment.apiUrl + '/api/v1/server'
|
||||
|
||||
private instanceBannerUrl: string
|
||||
|
||||
constructor (
|
||||
private authHttp: HttpClient,
|
||||
private restExtractor: RestExtractor,
|
||||
|
@ -28,9 +30,46 @@ export class InstanceService {
|
|||
|
||||
getAbout () {
|
||||
return this.authHttp.get<About>(InstanceService.BASE_CONFIG_URL + '/about')
|
||||
.pipe(catchError(res => this.restExtractor.handleError(res)))
|
||||
.pipe(
|
||||
tap(about => {
|
||||
const banners = about.instance.banners
|
||||
if (banners.length !== 0) this.instanceBannerUrl = banners[0].path
|
||||
}),
|
||||
catchError(res => this.restExtractor.handleError(res))
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
getInstanceBannerUrl () {
|
||||
if (this.instanceBannerUrl || this.instanceBannerUrl === null) {
|
||||
return of(this.instanceBannerUrl)
|
||||
}
|
||||
|
||||
return this.getAbout()
|
||||
.pipe(map(() => this.instanceBannerUrl))
|
||||
}
|
||||
|
||||
updateInstanceBanner (formData: FormData) {
|
||||
this.instanceBannerUrl = undefined
|
||||
|
||||
const url = InstanceService.BASE_CONFIG_URL + '/instance-banner/pick'
|
||||
|
||||
return this.authHttp.post(url, formData)
|
||||
.pipe(catchError(err => this.restExtractor.handleError(err)))
|
||||
}
|
||||
|
||||
deleteInstanceBanner () {
|
||||
this.instanceBannerUrl = null
|
||||
|
||||
const url = InstanceService.BASE_CONFIG_URL + '/instance-banner'
|
||||
|
||||
return this.authHttp.delete(url)
|
||||
.pipe(catchError(err => this.restExtractor.handleError(err)))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
contactAdministrator (fromEmail: string, fromName: string, subject: string, message: string) {
|
||||
const body = {
|
||||
fromEmail,
|
||||
|
|
|
@ -7,6 +7,7 @@ import { InstanceAboutAccordionComponent } from './instance-about-accordion.comp
|
|||
import { InstanceFeaturesTableComponent } from './instance-features-table.component'
|
||||
import { InstanceFollowService } from './instance-follow.service'
|
||||
import { InstanceService } from './instance.service'
|
||||
import { InstanceBannerComponent } from './instance-banner.component'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -18,13 +19,15 @@ import { InstanceService } from './instance.service'
|
|||
declarations: [
|
||||
FeatureBooleanComponent,
|
||||
InstanceAboutAccordionComponent,
|
||||
InstanceFeaturesTableComponent
|
||||
InstanceFeaturesTableComponent,
|
||||
InstanceBannerComponent
|
||||
],
|
||||
|
||||
exports: [
|
||||
FeatureBooleanComponent,
|
||||
InstanceAboutAccordionComponent,
|
||||
InstanceFeaturesTableComponent
|
||||
InstanceFeaturesTableComponent,
|
||||
InstanceBannerComponent
|
||||
],
|
||||
|
||||
providers: [
|
||||
|
|
Loading…
Reference in New Issue