1
0
Fork 0
mirror of https://github.com/Chocobozzz/PeerTube.git synced 2025-10-03 09:49:20 +02:00

Add ability to set square icon in welcome wizard

This commit is contained in:
Chocobozzz 2025-07-28 15:48:53 +02:00
parent 37da276f9c
commit c9905ecd3a
No known key found for this signature in database
GPG key ID: 583A612D890159BE
11 changed files with 53 additions and 15 deletions

View file

@ -16,7 +16,7 @@ import {
import { AdminConfigCustomizationComponent } from './pages/admin-config-customization.component' import { AdminConfigCustomizationComponent } from './pages/admin-config-customization.component'
import { AdminConfigService } from '../../shared/shared-admin/admin-config.service' import { AdminConfigService } from '../../shared/shared-admin/admin-config.service'
import { AdminConfigLogoComponent } from './pages/admin-config-logo.component' import { AdminConfigLogoComponent } from './pages/admin-config-logo.component'
import { InstanceLogoService } from './shared/instance-logo.service' import { InstanceLogoService } from '../../shared/shared-instance/instance-logo.service'
export const customConfigResolver: ResolveFn<CustomConfig> = (_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => { export const customConfigResolver: ResolveFn<CustomConfig> = (_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => {
return inject(AdminConfigService).getCustomConfig() return inject(AdminConfigService).getCustomConfig()

View file

@ -15,7 +15,7 @@ import { of, Subscription, switchMap, tap } from 'rxjs'
import { AdminConfigService } from '../../../shared/shared-admin/admin-config.service' import { AdminConfigService } from '../../../shared/shared-admin/admin-config.service'
import { PreviewUploadComponent } from '../../../shared/shared-forms/preview-upload.component' import { PreviewUploadComponent } from '../../../shared/shared-forms/preview-upload.component'
import { AdminSaveBarComponent } from '../shared/admin-save-bar.component' import { AdminSaveBarComponent } from '../shared/admin-save-bar.component'
import { InstanceLogoService } from '../shared/instance-logo.service' import { InstanceLogoService } from '../../../shared/shared-instance/instance-logo.service'
type Form = { type Form = {
hideInstanceName: FormControl<boolean> hideInstanceName: FormControl<boolean>

View file

@ -225,7 +225,7 @@
<div class="content-col"> <div class="content-col">
<div class="danger-zone"> <div class="danger-zone">
<div class="form-group"> <div class="form-group">
<div class="mb-1 fw-bold" i18n>Send a link to reset the password by email to the user</div> <div class="mb-1 fw-bold" i18n>Send a link to reset the password by email to the user</div>
<button class="peertube-button danger-button" (click)="resetPassword()" i18n>Ask for new password</button> <button class="peertube-button danger-button" (click)="resetPassword()" i18n>Ask for new password</button>
</div> </div>

View file

@ -7,7 +7,7 @@ import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { AdminConfigWizardStepperComponent } from './admin-config-wizard-stepper.component' import { AdminConfigWizardStepperComponent } from './admin-config-wizard-stepper.component'
import { getNoWelcomeModalLocalStorageKey } from './shared/admin-config-wizard-modal-utils' import { getNoWelcomeModalLocalStorageKey } from './shared/admin-config-wizard-modal-utils'
import { AdminConfigWizardDocumentationComponent } from './steps/admin-config-wizard-documentation.component' import { AdminConfigWizardDocumentationComponent } from './steps/admin-config-wizard-documentation.component'
import { AdminConfigWizardEditInfoComponent, FormInfo } from './steps/admin-config-wizard-edit-info.component' import { AdminConfigWizardEditInfoComponent, FormEditInfo } from './steps/admin-config-wizard-edit-info.component'
import { AdminConfigWizardFormComponent } from './steps/admin-config-wizard-form.component' import { AdminConfigWizardFormComponent } from './steps/admin-config-wizard-form.component'
import { AdminConfigWizardPreviewComponent } from './steps/admin-config-wizard-preview.component' import { AdminConfigWizardPreviewComponent } from './steps/admin-config-wizard-preview.component'
import { AdminConfigWizardWelcomeComponent } from './steps/admin-config-wizard-welcome.component' import { AdminConfigWizardWelcomeComponent } from './steps/admin-config-wizard-welcome.component'
@ -37,7 +37,7 @@ export class AdminConfigWizardModalComponent implements OnInit {
readonly created = output() readonly created = output()
usageType: UsageType usageType: UsageType
instanceInfo: FormInfo instanceInfo: FormEditInfo
showWelcome: boolean showWelcome: boolean
dryRun: boolean dryRun: boolean

View file

@ -33,6 +33,13 @@
<div *ngIf="formErrors.shortDescription" class="form-error" role="alert">{{ formErrors.shortDescription }}</div> <div *ngIf="formErrors.shortDescription" class="form-error" role="alert">{{ formErrors.shortDescription }}</div>
</div> </div>
<div class="form-group">
<label i18n for="avatarfile">Platform icon</label>
<div class="form-group-description" i18n>Used in various places such as on the website, mobile application and social media</div>
<my-preview-upload class="avatar-preview" formControlName="avatar" inputName="avatar" displayDelete="true"></my-preview-upload>
</div>
<div class="form-group"> <div class="form-group">
<label i18n for="primaryColor">Primary color</label> <label i18n for="primaryColor">Primary color</label>

View file

@ -11,6 +11,11 @@ form {
height: 190px; height: 190px;
} }
.avatar-preview {
width: 128px;
height: 128px;
}
@media screen and (max-width: $small-view) { @media screen and (max-width: $small-view) {
.mascot { .mascot {
display: none; display: none;

View file

@ -11,22 +11,26 @@ import {
FormReactiveMessagesTyped FormReactiveMessagesTyped
} from '@app/shared/form-validators/form-validator.model' } from '@app/shared/form-validators/form-validator.model'
import { FormReactiveService } from '@app/shared/shared-forms/form-reactive.service' import { FormReactiveService } from '@app/shared/shared-forms/form-reactive.service'
import { PreviewUploadComponent } from '@app/shared/shared-forms/preview-upload.component'
import { ColorPickerModule } from 'primeng/colorpicker' import { ColorPickerModule } from 'primeng/colorpicker'
import { ButtonComponent } from '../../../shared/shared-main/buttons/button.component' import { ButtonComponent } from '../../../shared/shared-main/buttons/button.component'
import { findAppropriateImage } from '@peertube/peertube-core-utils'
import { logger } from '@root-helpers/logger'
type Form = { type Form = {
platformName: FormControl<string> platformName: FormControl<string>
shortDescription: FormControl<string> shortDescription: FormControl<string>
avatar: FormControl<Blob>
primaryColor: FormControl<string> primaryColor: FormControl<string>
} }
export type FormInfo = FormDefaultTyped<Form> export type FormEditInfo = FormDefaultTyped<Form>
@Component({ @Component({
selector: 'my-admin-config-wizard-edit-info', selector: 'my-admin-config-wizard-edit-info',
templateUrl: './admin-config-wizard-edit-info.component.html', templateUrl: './admin-config-wizard-edit-info.component.html',
styleUrls: [ './admin-config-wizard-edit-info.component.scss', '../shared/admin-config-wizard-modal-common.scss' ], styleUrls: [ './admin-config-wizard-edit-info.component.scss', '../shared/admin-config-wizard-modal-common.scss' ],
imports: [ CommonModule, FormsModule, ReactiveFormsModule, ColorPickerModule, CdkStepperModule, ButtonComponent ] imports: [ CommonModule, FormsModule, ReactiveFormsModule, ColorPickerModule, CdkStepperModule, ButtonComponent, PreviewUploadComponent ]
}) })
export class AdminConfigWizardEditInfoComponent implements OnInit { export class AdminConfigWizardEditInfoComponent implements OnInit {
private server = inject(ServerService) private server = inject(ServerService)
@ -38,7 +42,7 @@ export class AdminConfigWizardEditInfoComponent implements OnInit {
readonly showBack = input.required({ transform: booleanAttribute }) readonly showBack = input.required({ transform: booleanAttribute })
readonly back = output() readonly back = output()
readonly next = output<FormInfo>() readonly next = output<FormEditInfo>()
readonly hide = output() readonly hide = output()
form: FormGroup<Form> form: FormGroup<Form>
@ -47,12 +51,23 @@ export class AdminConfigWizardEditInfoComponent implements OnInit {
ngOnInit () { ngOnInit () {
this.buildForm() this.buildForm()
const avatar = findAppropriateImage(this.server.getHTMLConfig().instance.avatars, 128)
if (avatar) {
fetch(avatar.fileUrl)
.then(response => response.blob())
.then(blob => this.form.patchValue({ avatar: blob }))
.catch(() => {
logger.error('Could not fetch instance avatar')
})
}
} }
private buildForm () { private buildForm () {
const obj: BuildFormArgumentTyped<Form> = { const obj: BuildFormArgumentTyped<Form> = {
platformName: INSTANCE_NAME_VALIDATOR, platformName: INSTANCE_NAME_VALIDATOR,
shortDescription: INSTANCE_SHORT_DESCRIPTION_VALIDATOR, shortDescription: INSTANCE_SHORT_DESCRIPTION_VALIDATOR,
avatar: null,
primaryColor: null primaryColor: null
} }
@ -67,7 +82,7 @@ export class AdminConfigWizardEditInfoComponent implements OnInit {
this.validationMessages = validationMessages this.validationMessages = validationMessages
} }
private getDefaultValues (): FormDefaultTyped<Form> { private getDefaultValues (): FormEditInfo {
const config = this.server.getHTMLConfig() const config = this.server.getHTMLConfig()
const primaryColorConfig = config.theme.customization.primaryColor const primaryColorConfig = config.theme.customization.primaryColor
@ -78,6 +93,7 @@ export class AdminConfigWizardEditInfoComponent implements OnInit {
return { return {
platformName: config.instance.name, platformName: config.instance.name,
shortDescription: config.instance.shortDescription, shortDescription: config.instance.shortDescription,
avatar: undefined as Blob,
primaryColor primaryColor
} }
} }

View file

@ -11,8 +11,9 @@ import { ColorPickerModule } from 'primeng/colorpicker'
import { concatMap, from, switchMap, toArray } from 'rxjs' import { concatMap, from, switchMap, toArray } from 'rxjs'
import { PartialDeep } from 'type-fest' import { PartialDeep } from 'type-fest'
import { ButtonComponent } from '../../../shared/shared-main/buttons/button.component' import { ButtonComponent } from '../../../shared/shared-main/buttons/button.component'
import { FormInfo } from './admin-config-wizard-edit-info.component' import { FormEditInfo } from './admin-config-wizard-edit-info.component'
import { UsageType } from './usage-type/usage-type.model' import { UsageType } from './usage-type/usage-type.model'
import { InstanceLogoService } from '@app/shared/shared-instance/instance-logo.service'
@Component({ @Component({
selector: 'my-admin-config-wizard-preview', selector: 'my-admin-config-wizard-preview',
@ -26,7 +27,7 @@ import { UsageType } from './usage-type/usage-type.model'
CdkStepperModule, CdkStepperModule,
ButtonComponent ButtonComponent
], ],
providers: [ AdminConfigService, PluginApiService ] providers: [ AdminConfigService, PluginApiService, InstanceLogoService ]
}) })
export class AdminConfigWizardPreviewComponent implements OnChanges { export class AdminConfigWizardPreviewComponent implements OnChanges {
private adminConfig = inject(AdminConfigService) private adminConfig = inject(AdminConfigService)
@ -34,11 +35,12 @@ export class AdminConfigWizardPreviewComponent implements OnChanges {
private notifier = inject(Notifier) private notifier = inject(Notifier)
private html = inject(HtmlRendererService) private html = inject(HtmlRendererService)
private server = inject(ServerService) private server = inject(ServerService)
private instanceLogo = inject(InstanceLogoService)
readonly currentStep = input.required({ transform: numberAttribute }) readonly currentStep = input.required({ transform: numberAttribute })
readonly totalSteps = input.required({ transform: numberAttribute }) readonly totalSteps = input.required({ transform: numberAttribute })
readonly usageType = input.required<UsageType>() readonly usageType = input.required<UsageType>()
readonly instanceInfo = input.required<FormInfo>() readonly instanceInfo = input.required<FormEditInfo>()
readonly dryRun = input.required({ transform: booleanAttribute }) readonly dryRun = input.required({ transform: booleanAttribute })
readonly back = output() readonly back = output()
@ -84,6 +86,12 @@ export class AdminConfigWizardPreviewComponent implements OnChanges {
this.updating = true this.updating = true
this.adminConfig.updateCustomConfig(this.config) this.adminConfig.updateCustomConfig(this.config)
.pipe(() => {
const avatar = this.instanceInfo().avatar
if (avatar) return this.instanceLogo.updateAvatar(avatar)
return this.instanceLogo.deleteAvatar()
})
.pipe( .pipe(
switchMap(() => this.server.resetConfig()), switchMap(() => this.server.resetConfig()),
switchMap(() => { switchMap(() => {

View file

@ -1,5 +1,4 @@
import { AsyncValidatorFn, FormArray, FormControl, FormGroup, ValidatorFn } from '@angular/forms' import { AsyncValidatorFn, FormArray, FormControl, FormGroup, ValidatorFn } from '@angular/forms'
import { PartialDeep } from 'type-fest'
export type BuildFormValidator = { export type BuildFormValidator = {
VALIDATORS: ValidatorFn[] VALIDATORS: ValidatorFn[]
@ -19,7 +18,7 @@ export type BuildFormArgumentTyped<Form> = ReplaceForm<Form, BuildFormValidator>
export type FormDefault = { export type FormDefault = {
[name: string]: Blob | Date | boolean | number | number[] | string | string[] | FormDefault [name: string]: Blob | Date | boolean | number | number[] | string | string[] | FormDefault
} }
export type FormDefaultTyped<Form> = PartialDeep<UnwrapForm<Form>> export type FormDefaultTyped<Form> = Partial<UnwrapForm<Form>>
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -42,8 +41,9 @@ export type UnwrapForm<Form> = {
[K in keyof Form]: _UnwrapForm<Form[K]> [K in keyof Form]: _UnwrapForm<Form[K]>
} }
type _UnwrapForm<T> = T extends FormGroup<infer U> ? UnwrapForm<U> : type _UnwrapForm<T> = T extends FormGroup<infer U> ? Partial<UnwrapForm<U>> :
T extends FormArray<infer U> ? _UnwrapForm<U>[] : T extends FormArray<infer U> ? _UnwrapForm<U>[] :
T extends FormControl<Blob> ? Blob :
T extends FormControl<infer U> ? U T extends FormControl<infer U> ? U
: never : never

View file

@ -1,4 +1,6 @@
export function findAppropriateImage<T extends { width: number, height: number }> (images: T[], wantedWidth: number) { export function findAppropriateImage<T extends { width: number, height: number }> (images: T[], wantedWidth: number) {
if (!images || images.length === 0) return undefined
const imagesSorted = images.sort((a, b) => a.width - b.width) const imagesSorted = images.sort((a, b) => a.width - b.width)
for (const image of imagesSorted) { for (const image of imagesSorted) {