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 { AdminConfigService } from '../../shared/shared-admin/admin-config.service'
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) => {
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 { PreviewUploadComponent } from '../../../shared/shared-forms/preview-upload.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 = {
hideInstanceName: FormControl<boolean>

View file

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

View file

@ -33,6 +33,13 @@
<div *ngIf="formErrors.shortDescription" class="form-error" role="alert">{{ formErrors.shortDescription }}</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">
<label i18n for="primaryColor">Primary color</label>

View file

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

View file

@ -11,22 +11,26 @@ import {
FormReactiveMessagesTyped
} from '@app/shared/form-validators/form-validator.model'
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 { ButtonComponent } from '../../../shared/shared-main/buttons/button.component'
import { findAppropriateImage } from '@peertube/peertube-core-utils'
import { logger } from '@root-helpers/logger'
type Form = {
platformName: FormControl<string>
shortDescription: FormControl<string>
avatar: FormControl<Blob>
primaryColor: FormControl<string>
}
export type FormInfo = FormDefaultTyped<Form>
export type FormEditInfo = FormDefaultTyped<Form>
@Component({
selector: 'my-admin-config-wizard-edit-info',
templateUrl: './admin-config-wizard-edit-info.component.html',
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 {
private server = inject(ServerService)
@ -38,7 +42,7 @@ export class AdminConfigWizardEditInfoComponent implements OnInit {
readonly showBack = input.required({ transform: booleanAttribute })
readonly back = output()
readonly next = output<FormInfo>()
readonly next = output<FormEditInfo>()
readonly hide = output()
form: FormGroup<Form>
@ -47,12 +51,23 @@ export class AdminConfigWizardEditInfoComponent implements OnInit {
ngOnInit () {
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 () {
const obj: BuildFormArgumentTyped<Form> = {
platformName: INSTANCE_NAME_VALIDATOR,
shortDescription: INSTANCE_SHORT_DESCRIPTION_VALIDATOR,
avatar: null,
primaryColor: null
}
@ -67,7 +82,7 @@ export class AdminConfigWizardEditInfoComponent implements OnInit {
this.validationMessages = validationMessages
}
private getDefaultValues (): FormDefaultTyped<Form> {
private getDefaultValues (): FormEditInfo {
const config = this.server.getHTMLConfig()
const primaryColorConfig = config.theme.customization.primaryColor
@ -78,6 +93,7 @@ export class AdminConfigWizardEditInfoComponent implements OnInit {
return {
platformName: config.instance.name,
shortDescription: config.instance.shortDescription,
avatar: undefined as Blob,
primaryColor
}
}

View file

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

View file

@ -1,5 +1,4 @@
import { AsyncValidatorFn, FormArray, FormControl, FormGroup, ValidatorFn } from '@angular/forms'
import { PartialDeep } from 'type-fest'
export type BuildFormValidator = {
VALIDATORS: ValidatorFn[]
@ -19,7 +18,7 @@ export type BuildFormArgumentTyped<Form> = ReplaceForm<Form, BuildFormValidator>
export type 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]>
}
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 FormControl<Blob> ? Blob :
T extends FormControl<infer U> ? U
: never

View file

@ -1,4 +1,6 @@
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)
for (const image of imagesSorted) {