From 21e493d4d8acb7a650eff3a30cd7e086b3cb8a28 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 10 Feb 2021 09:05:29 +0100 Subject: [PATCH] Add ability to set a custom quota --- .../edit-custom-config.component.html | 32 +++++++------ .../edit-custom-config.component.ts | 3 +- .../+admin/config/shared/config.service.ts | 47 ++++++++++--------- .../users/user-edit/user-create.component.ts | 4 +- .../users/user-edit/user-edit.component.html | 36 +++++++++----- .../users/user-edit/user-edit.component.scss | 14 ++++-- .../app/+admin/users/user-edit/user-edit.ts | 21 ++------- .../my-video-playlist-edit.ts | 3 +- .../shared/video-edit.component.ts | 5 +- .../video-add-components/video-send.ts | 3 +- .../+video-edit/video-update.component.ts | 3 +- client/src/app/helpers/utils.ts | 4 +- .../form-validators/form-validator.model.ts | 2 +- .../select/select-channel.component.ts | 25 +++++----- .../select/select-checkbox.component.ts | 2 +- .../select/select-custom-value.component.html | 6 ++- .../select/select-custom-value.component.ts | 4 +- .../select/select-options.component.ts | 9 +--- .../select/select-shared.component.scss | 7 ++- .../user-video-settings.component.ts | 3 +- client/src/types/select-options-item.model.ts | 13 +++++ 21 files changed, 141 insertions(+), 105 deletions(-) create mode 100644 client/src/types/select-options-item.model.ts diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html index 844620ca2..637203b96 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html @@ -402,25 +402,29 @@
-
- -
+ + +
{{ formErrors.user.videoQuota }}
-
- -
+ + +
{{ formErrors.user.videoQuotaDaily }}
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts index a9f72d7db..56be97e84 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts @@ -19,9 +19,10 @@ import { TRANSCODING_THREADS_VALIDATOR } from '@app/shared/form-validators/custom-config-validators' import { USER_VIDEO_QUOTA_DAILY_VALIDATOR, USER_VIDEO_QUOTA_VALIDATOR } from '@app/shared/form-validators/user-validators' -import { FormReactive, FormValidatorService, SelectOptionsItem } from '@app/shared/shared-forms' +import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' import { NgbNav } from '@ng-bootstrap/ng-bootstrap' import { CustomConfig, ServerConfig } from '@shared/models' +import { SelectOptionsItem } from 'src/types/select-options-item.model' @Component({ selector: 'my-edit-custom-config', diff --git a/client/src/app/+admin/config/shared/config.service.ts b/client/src/app/+admin/config/shared/config.service.ts index 5f98aa545..d29b752f7 100644 --- a/client/src/app/+admin/config/shared/config.service.ts +++ b/client/src/app/+admin/config/shared/config.service.ts @@ -3,43 +3,46 @@ import { HttpClient } from '@angular/common/http' import { Injectable } from '@angular/core' import { RestExtractor } from '@app/core' import { CustomConfig } from '@shared/models' +import { SelectOptionsItem } from '../../../../types/select-options-item.model' import { environment } from '../../../../environments/environment' @Injectable() export class ConfigService { private static BASE_APPLICATION_URL = environment.apiUrl + '/api/v1/config' - videoQuotaOptions: { value: number, label: string, disabled?: boolean }[] = [] - videoQuotaDailyOptions: { value: number, label: string, disabled?: boolean }[] = [] + videoQuotaOptions: SelectOptionsItem[] = [] + videoQuotaDailyOptions: SelectOptionsItem[] = [] constructor ( private authHttp: HttpClient, private restExtractor: RestExtractor ) { this.videoQuotaOptions = [ - { value: undefined, label: 'Default quota', disabled: true }, - { value: -1, label: $localize`Unlimited` }, - { value: undefined, label: '─────', disabled: true }, - { value: 0, label: $localize`None - no upload possible` }, - { value: 100 * 1024 * 1024, label: $localize`100MB` }, - { value: 500 * 1024 * 1024, label: $localize`500MB` }, - { value: 1024 * 1024 * 1024, label: $localize`1GB` }, - { value: 5 * 1024 * 1024 * 1024, label: $localize`5GB` }, - { value: 20 * 1024 * 1024 * 1024, label: $localize`20GB` }, - { value: 50 * 1024 * 1024 * 1024, label: $localize`50GB` } + { id: -1, label: $localize`Unlimited` }, + { id: 0, label: $localize`None - no upload possible` }, + { id: 100 * 1024 * 1024, label: $localize`100MB` }, + { id: 500 * 1024 * 1024, label: $localize`500MB` }, + { id: 1024 * 1024 * 1024, label: $localize`1GB` }, + { id: 5 * 1024 * 1024 * 1024, label: $localize`5GB` }, + { id: 20 * 1024 * 1024 * 1024, label: $localize`20GB` }, + { id: 50 * 1024 * 1024 * 1024, label: $localize`50GB` }, + { id: 100 * 1024 * 1024 * 1024, label: $localize`100GB` }, + { id: 200 * 1024 * 1024 * 1024, label: $localize`200GB` }, + { id: 500 * 1024 * 1024 * 1024, label: $localize`500GB` } ] this.videoQuotaDailyOptions = [ - { value: undefined, label: 'Default daily upload limit', disabled: true }, - { value: -1, label: $localize`Unlimited` }, - { value: undefined, label: '─────', disabled: true }, - { value: 0, label: $localize`None - no upload possible` }, - { value: 10 * 1024 * 1024, label: $localize`10MB` }, - { value: 50 * 1024 * 1024, label: $localize`50MB` }, - { value: 100 * 1024 * 1024, label: $localize`100MB` }, - { value: 500 * 1024 * 1024, label: $localize`500MB` }, - { value: 2 * 1024 * 1024 * 1024, label: $localize`2GB` }, - { value: 5 * 1024 * 1024 * 1024, label: $localize`5GB` } + { id: -1, label: $localize`Unlimited` }, + { id: 0, label: $localize`None - no upload possible` }, + { id: 10 * 1024 * 1024, label: $localize`10MB` }, + { id: 50 * 1024 * 1024, label: $localize`50MB` }, + { id: 100 * 1024 * 1024, label: $localize`100MB` }, + { id: 500 * 1024 * 1024, label: $localize`500MB` }, + { id: 2 * 1024 * 1024 * 1024, label: $localize`2GB` }, + { id: 5 * 1024 * 1024 * 1024, label: $localize`5GB` }, + { id: 10 * 1024 * 1024 * 1024, label: $localize`10GB` }, + { id: 20 * 1024 * 1024 * 1024, label: $localize`20GB` }, + { id: 50 * 1024 * 1024 * 1024, label: $localize`50GB` } ] } diff --git a/client/src/app/+admin/users/user-edit/user-create.component.ts b/client/src/app/+admin/users/user-edit/user-create.component.ts index d0aac1cb9..da333240c 100644 --- a/client/src/app/+admin/users/user-edit/user-create.component.ts +++ b/client/src/app/+admin/users/user-edit/user-create.component.ts @@ -45,8 +45,8 @@ export class UserCreateComponent extends UserEdit implements OnInit { const defaultValues = { role: UserRole.USER.toString(), - videoQuota: '-1', - videoQuotaDaily: '-1' + videoQuota: -1, + videoQuotaDaily: -1 } this.buildForm({ diff --git a/client/src/app/+admin/users/user-edit/user-edit.component.html b/client/src/app/+admin/users/user-edit/user-edit.component.html index fb34d6b22..243c6556a 100644 --- a/client/src/app/+admin/users/user-edit/user-edit.component.html +++ b/client/src/app/+admin/users/user-edit/user-edit.component.html @@ -149,28 +149,38 @@
-
- -
+ +
Transcoding is enabled. The video quota only takes into account original video size.
At most, this user could upload ~ {{ computeQuotaWithTranscoding() | bytes: 0 }}.
+ +
+ {{ formErrors.videoQuota }} +
-
- + + + +
+ {{ formErrors.videoQuotaDaily }}
diff --git a/client/src/app/+admin/users/user-edit/user-edit.component.scss b/client/src/app/+admin/users/user-edit/user-edit.component.scss index 3b7715062..aa87b8d6d 100644 --- a/client/src/app/+admin/users/user-edit/user-edit.component.scss +++ b/client/src/app/+admin/users/user-edit/user-edit.component.scss @@ -1,6 +1,8 @@ @import '_variables'; @import '_mixins'; +$form-base-input-width: 340px; + label { font-weight: $font-regular; font-size: 100%; @@ -15,18 +17,24 @@ label { } input:not([type=submit]) { - @include peertube-input-text(340px); + @include peertube-input-text($form-base-input-width); display: block; } my-input-toggle-hidden { - @include responsive-width(340px); + @include responsive-width($form-base-input-width); display: block; } .peertube-select-container { - @include peertube-select-container(340px); + @include peertube-select-container($form-base-input-width); +} + +my-select-custom-value { + @include responsive-width($form-base-input-width); + + display: block; } input[type=submit], button { diff --git a/client/src/app/+admin/users/user-edit/user-edit.ts b/client/src/app/+admin/users/user-edit/user-edit.ts index faa2f5ad8..2fc3c5d3b 100644 --- a/client/src/app/+admin/users/user-edit/user-edit.ts +++ b/client/src/app/+admin/users/user-edit/user-edit.ts @@ -4,12 +4,13 @@ import { AuthService, ScreenService, ServerService, User } from '@app/core' import { FormReactive } from '@app/shared/shared-forms' import { USER_ROLE_LABELS } from '@shared/core-utils/users' import { ServerConfig, UserAdminFlag, UserRole, VideoResolution } from '@shared/models' +import { SelectOptionsItem } from '../../../../types/select-options-item.model' @Directive() // tslint:disable-next-line: directive-class-suffix export abstract class UserEdit extends FormReactive implements OnInit { - videoQuotaOptions: { value: string, label: string, disabled?: boolean }[] = [] - videoQuotaDailyOptions: { value: string, label: string, disabled?: boolean }[] = [] + videoQuotaOptions: SelectOptionsItem[] = [] + videoQuotaDailyOptions: SelectOptionsItem[] = [] username: string user: User @@ -97,19 +98,7 @@ export abstract class UserEdit extends FormReactive implements OnInit { } protected buildQuotaOptions () { - // These are used by a HTML select, so convert key into strings - this.videoQuotaOptions = this.configService - .videoQuotaOptions.map(q => ({ - value: q.value?.toString(), - label: q.label, - disabled: q.disabled - })) - - this.videoQuotaDailyOptions = this.configService - .videoQuotaDailyOptions.map(q => ({ - value: q.value?.toString(), - label: q.label, - disabled: q.disabled - })) + this.videoQuotaOptions = this.configService.videoQuotaOptions + this.videoQuotaDailyOptions = this.configService.videoQuotaDailyOptions } } diff --git a/client/src/app/+my-library/my-video-playlists/my-video-playlist-edit.ts b/client/src/app/+my-library/my-video-playlists/my-video-playlist-edit.ts index 40ba23e75..71db0592a 100644 --- a/client/src/app/+my-library/my-video-playlists/my-video-playlist-edit.ts +++ b/client/src/app/+my-library/my-video-playlists/my-video-playlist-edit.ts @@ -1,6 +1,7 @@ -import { FormReactive, SelectChannelItem } from '@app/shared/shared-forms' +import { FormReactive } from '@app/shared/shared-forms' import { VideoConstant, VideoPlaylistPrivacy } from '@shared/models' import { VideoPlaylist } from '@shared/models/videos/playlist/video-playlist.model' +import { SelectChannelItem } from '../../../types/select-options-item.model' export abstract class MyVideoPlaylistEdit extends FormReactive { // Declare it here to avoid errors in create template diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts index 80b5dce46..f51f52160 100644 --- a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts +++ b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts @@ -1,5 +1,6 @@ import { forkJoin } from 'rxjs' import { map } from 'rxjs/operators' +import { SelectChannelItem } from 'src/types/select-options-item.model' import { Component, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output, ViewChild } from '@angular/core' import { FormArray, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms' import { HooksService, PluginService, ServerService } from '@app/core' @@ -17,10 +18,10 @@ import { VIDEO_SUPPORT_VALIDATOR, VIDEO_TAGS_ARRAY_VALIDATOR } from '@app/shared/form-validators/video-validators' -import { FormReactiveValidationMessages, FormValidatorService, SelectChannelItem } from '@app/shared/shared-forms' +import { FormReactiveValidationMessages, FormValidatorService } from '@app/shared/shared-forms' import { InstanceService } from '@app/shared/shared-instance' import { VideoCaptionEdit, VideoEdit, VideoService } from '@app/shared/shared-main' -import { ServerConfig, VideoConstant, LiveVideo, VideoPrivacy } from '@shared/models' +import { LiveVideo, ServerConfig, VideoConstant, VideoPrivacy } from '@shared/models' import { RegisterClientFormFieldOptions, RegisterClientVideoFieldOptions } from '@shared/models/plugins/register-client-form-field.model' import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service' import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-send.ts b/client/src/app/+videos/+video-edit/video-add-components/video-send.ts index 812936d7a..9a22024e5 100644 --- a/client/src/app/+videos/+video-edit/video-add-components/video-send.ts +++ b/client/src/app/+videos/+video-edit/video-add-components/video-send.ts @@ -1,8 +1,9 @@ import { catchError, switchMap, tap } from 'rxjs/operators' +import { SelectChannelItem } from 'src/types/select-options-item.model' import { Directive, EventEmitter, OnInit } from '@angular/core' import { AuthService, CanComponentDeactivateResult, Notifier, ServerService } from '@app/core' import { populateAsyncUserVideoChannels } from '@app/helpers' -import { FormReactive, SelectChannelItem } from '@app/shared/shared-forms' +import { FormReactive } from '@app/shared/shared-forms' import { VideoCaptionEdit, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main' import { LoadingBarService } from '@ngx-loading-bar/core' import { ServerConfig, VideoConstant, VideoPrivacy } from '@shared/models' diff --git a/client/src/app/+videos/+video-edit/video-update.component.ts b/client/src/app/+videos/+video-edit/video-update.component.ts index 654901798..2973c6840 100644 --- a/client/src/app/+videos/+video-edit/video-update.component.ts +++ b/client/src/app/+videos/+video-edit/video-update.component.ts @@ -1,9 +1,10 @@ import { of } from 'rxjs' import { map, switchMap } from 'rxjs/operators' +import { SelectChannelItem } from 'src/types/select-options-item.model' import { Component, HostListener, OnInit } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { Notifier } from '@app/core' -import { FormReactive, FormValidatorService, SelectChannelItem } from '@app/shared/shared-forms' +import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' import { VideoCaptionEdit, VideoCaptionService, VideoDetails, VideoEdit, VideoService } from '@app/shared/shared-main' import { LiveVideoService } from '@app/shared/shared-video-live' import { LoadingBarService } from '@ngx-loading-bar/core' diff --git a/client/src/app/helpers/utils.ts b/client/src/app/helpers/utils.ts index b4e26d792..6d7e76b11 100644 --- a/client/src/app/helpers/utils.ts +++ b/client/src/app/helpers/utils.ts @@ -1,10 +1,10 @@ +import { SelectChannelItem } from 'src/types/select-options-item.model' import { DatePipe } from '@angular/common' import { HttpErrorResponse } from '@angular/common/http' import { Notifier } from '@app/core' -import { SelectChannelItem } from '@app/shared/shared-forms' +import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' import { environment } from '../../environments/environment' import { AuthService } from '../core/auth' -import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' // Thanks: https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript function getParameterByName (name: string, url: string) { diff --git a/client/src/app/shared/form-validators/form-validator.model.ts b/client/src/app/shared/form-validators/form-validator.model.ts index 248a3b1d3..07b1ea075 100644 --- a/client/src/app/shared/form-validators/form-validator.model.ts +++ b/client/src/app/shared/form-validators/form-validator.model.ts @@ -10,5 +10,5 @@ export type BuildFormArgument = { } export type BuildFormDefaultValues = { - [ name: string ]: string | string[] | BuildFormDefaultValues + [ name: string ]: number | string | string[] | BuildFormDefaultValues } diff --git a/client/src/app/shared/shared-forms/select/select-channel.component.ts b/client/src/app/shared/shared-forms/select/select-channel.component.ts index 1d91d59bc..40a7c53bb 100644 --- a/client/src/app/shared/shared-forms/select/select-channel.component.ts +++ b/client/src/app/shared/shared-forms/select/select-channel.component.ts @@ -1,13 +1,7 @@ -import { Component, forwardRef, Input } from '@angular/core' +import { Component, forwardRef, Input, OnChanges } from '@angular/core' import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' import { VideoChannel } from '@app/shared/shared-main' - -export type SelectChannelItem = { - id: number - label: string - support: string - avatarPath?: string -} +import { SelectChannelItem } from '../../../../types/select-options-item.model' @Component({ selector: 'my-select-channel', @@ -21,9 +15,10 @@ export type SelectChannelItem = { } ] }) -export class SelectChannelComponent implements ControlValueAccessor { +export class SelectChannelComponent implements ControlValueAccessor, OnChanges { @Input() items: SelectChannelItem[] = [] + channels: SelectChannelItem[] = [] selectedId: number // ng-select options @@ -32,10 +27,14 @@ export class SelectChannelComponent implements ControlValueAccessor { clearable = false searchable = false - get channels () { - return this.items.map(c => Object.assign(c, { - avatarPath: c.avatarPath ? c.avatarPath : VideoChannel.GET_DEFAULT_AVATAR_URL() - })) + ngOnChanges () { + this.channels = this.items.map(c => { + const avatarPath = c.avatarPath + ? c.avatarPath + : VideoChannel.GET_DEFAULT_AVATAR_URL() + + return Object.assign({}, c, { avatarPath }) + }) } propagateChange = (_: any) => { /* empty */ } diff --git a/client/src/app/shared/shared-forms/select/select-checkbox.component.ts b/client/src/app/shared/shared-forms/select/select-checkbox.component.ts index eb0c49034..c2523f15c 100644 --- a/client/src/app/shared/shared-forms/select/select-checkbox.component.ts +++ b/client/src/app/shared/shared-forms/select/select-checkbox.component.ts @@ -1,6 +1,6 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core' import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' -import { SelectOptionsItem } from './select-options.component' +import { SelectOptionsItem } from '../../../../types/select-options-item.model' export type ItemSelectCheckboxValue = { id?: string | number, group?: string } | string diff --git a/client/src/app/shared/shared-forms/select/select-custom-value.component.html b/client/src/app/shared/shared-forms/select/select-custom-value.component.html index 5fdf432ff..9dc8c2ec2 100644 --- a/client/src/app/shared/shared-forms/select/select-custom-value.component.html +++ b/client/src/app/shared/shared-forms/select/select-custom-value.component.html @@ -10,5 +10,9 @@ (ngModelChange)="onModelChange()" > - + + + + {{ inputSuffix }} +
diff --git a/client/src/app/shared/shared-forms/select/select-custom-value.component.ts b/client/src/app/shared/shared-forms/select/select-custom-value.component.ts index a8e5ad0d3..bc6b863c7 100644 --- a/client/src/app/shared/shared-forms/select/select-custom-value.component.ts +++ b/client/src/app/shared/shared-forms/select/select-custom-value.component.ts @@ -1,6 +1,6 @@ import { Component, forwardRef, Input, OnChanges } from '@angular/core' import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' -import { SelectOptionsItem } from './select-options.component' +import { SelectOptionsItem } from '../../../../types/select-options-item.model' @Component({ selector: 'my-select-custom-value', @@ -20,6 +20,8 @@ export class SelectCustomValueComponent implements ControlValueAccessor, OnChang @Input() searchable = false @Input() groupBy: string @Input() labelForId: string + @Input() inputSuffix: string + @Input() inputType = 'text' customValue: number | string = '' selectedId: number | string diff --git a/client/src/app/shared/shared-forms/select/select-options.component.ts b/client/src/app/shared/shared-forms/select/select-options.component.ts index 51a3f515d..2890670e5 100644 --- a/client/src/app/shared/shared-forms/select/select-options.component.ts +++ b/client/src/app/shared/shared-forms/select/select-options.component.ts @@ -1,13 +1,6 @@ import { Component, forwardRef, Input } from '@angular/core' import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' - -export type SelectOptionsItem = { - id: string | number - label: string - description?: string - group?: string - groupLabel?: string -} +import { SelectOptionsItem } from '../../../../types/select-options-item.model' @Component({ selector: 'my-select-options', diff --git a/client/src/app/shared/shared-forms/select/select-shared.component.scss b/client/src/app/shared/shared-forms/select/select-shared.component.scss index 1a4192b55..80196b8df 100644 --- a/client/src/app/shared/shared-forms/select/select-shared.component.scss +++ b/client/src/app/shared/shared-forms/select/select-shared.component.scss @@ -33,15 +33,20 @@ ng-select ::ng-deep { .root { display:flex; + align-items: center; > my-select-options { flex-grow: 1; } } -input[type=text] { +my-select-options + input { margin-left: 5px; @include peertube-input-text($form-base-input-width); display: block; } + +.input-suffix { + margin-left: 5px; +} diff --git a/client/src/app/shared/shared-user-settings/user-video-settings.component.ts b/client/src/app/shared/shared-user-settings/user-video-settings.component.ts index 2497e001c..d74c2b2d8 100644 --- a/client/src/app/shared/shared-user-settings/user-video-settings.component.ts +++ b/client/src/app/shared/shared-user-settings/user-video-settings.component.ts @@ -3,9 +3,10 @@ import { forkJoin, Subject, Subscription } from 'rxjs' import { first } from 'rxjs/operators' import { Component, Input, OnDestroy, OnInit } from '@angular/core' import { AuthService, Notifier, ServerService, User, UserService } from '@app/core' -import { FormReactive, FormValidatorService, ItemSelectCheckboxValue, SelectOptionsItem } from '@app/shared/shared-forms' +import { FormReactive, FormValidatorService, ItemSelectCheckboxValue } from '@app/shared/shared-forms' import { UserUpdateMe } from '@shared/models' import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type' +import { SelectOptionsItem } from '../../../types/select-options-item.model' @Component({ selector: 'my-user-video-settings', diff --git a/client/src/types/select-options-item.model.ts b/client/src/types/select-options-item.model.ts new file mode 100644 index 000000000..895965a74 --- /dev/null +++ b/client/src/types/select-options-item.model.ts @@ -0,0 +1,13 @@ +export interface SelectOptionsItem { + id: string | number + label: string + description?: string + group?: string + groupLabel?: string +} + +export interface SelectChannelItem extends SelectOptionsItem { + id: number + support: string + avatarPath?: string +}