272 lines
10 KiB
Text
272 lines
10 KiB
Text
@page "/settings/profile"
|
|
@using Iceshrimp.Assets.PhosphorIcons
|
|
@using Iceshrimp.Frontend.Components
|
|
@using Iceshrimp.Frontend.Core.Miscellaneous
|
|
@using Iceshrimp.Frontend.Core.Services
|
|
@using Iceshrimp.Frontend.Localization
|
|
@using Iceshrimp.Shared.Schemas.Web
|
|
@using Microsoft.AspNetCore.Authorization
|
|
@using Microsoft.Extensions.Localization
|
|
@using Microsoft.AspNetCore.Components.Sections
|
|
@attribute [Authorize]
|
|
@layout SettingsLayout
|
|
@inject ApiService Api;
|
|
@inject ILogger<Profile> Logger;
|
|
@inject IStringLocalizer<Localization> Loc;
|
|
@inject IJSRuntime Js
|
|
@inject GlobalComponentSvc GlobalComponentSvc
|
|
|
|
<SectionContent SectionName="top-bar">
|
|
<Icon Name="Icons.User"></Icon>
|
|
@Loc["Profile"]
|
|
</SectionContent>
|
|
|
|
<div class="body">
|
|
@if (State is State.Loaded)
|
|
{
|
|
<div class="section">
|
|
<h3>@Loc["Banner"]</h3>
|
|
@if (Banner != null)
|
|
{
|
|
<img class="banner" src="@Banner.Url" alt="@Banner.Description" title="@Banner.Description"/>
|
|
<div>
|
|
<input type="checkbox" id="delete-banner" @bind="@DelBanner">
|
|
<label for="delete-banner">@Loc["Remove Banner"]</label>
|
|
</div>
|
|
}
|
|
<InputFile class="input" OnChange="OnBannerFileChange" accept="image/*" disabled="@DelBanner"/>
|
|
@if (Banner != null || BannerFile != null)
|
|
{
|
|
<label for="banner-alt">@(BannerFile != null ? Loc["Set alt text for your new banner"] : Loc["Set alt text for your banner"])</label>
|
|
<textarea class="input alt-text" @bind="UserProfile.BannerAlt" id="banner-alt" rows="3" placeholder="@Loc["Alt text"]" disabled="@DelBanner"></textarea>
|
|
}
|
|
</div>
|
|
<div class="section">
|
|
<h3>@Loc["Avatar"]</h3>
|
|
@if (Avatar != null)
|
|
{
|
|
<img class="avatar" src="@Avatar.Url" alt="@Avatar.Description" title="@Avatar.Description"/>
|
|
<div>
|
|
<input type="checkbox" id="delete-avatar" @bind="@DelAvatar">
|
|
<label for="delete-avatar">@Loc["Remove Avatar"]</label>
|
|
</div>
|
|
}
|
|
<InputFile class="input" OnChange="OnAvatarFileChange" accept="image/*" disabled="@DelAvatar"/>
|
|
@if (Avatar != null || AvatarFile != null)
|
|
{
|
|
<label for="avatar-alt">@(AvatarFile != null ? Loc["Set alt text for your new avatar"] : Loc["Set alt text for your avatar"])</label>
|
|
<textarea class="input alt-text" @bind="UserProfile.AvatarAlt" id="avatar-alt" rows="3" placeholder="@Loc["Alt text"]" disabled="@DelAvatar"></textarea>
|
|
}
|
|
</div>
|
|
<div class="section">
|
|
<h3>@Loc["Display Name"]</h3><input class="input" @bind="@UserProfile.DisplayName"/>
|
|
</div>
|
|
<div class="section">
|
|
<h3>@Loc["Bio"]</h3><textarea class="input" @ref="Description" @bind="@UserProfile.Description" rows="5"></textarea>
|
|
<button @ref="EmojiButton" class="button" title="@Loc["Emoji"]" @onclick="ToggleEmojiPicker"
|
|
aria-label="emoji">
|
|
<Icon Name="Icons.Smiley" Size="1.3rem"></Icon>
|
|
</button>
|
|
</div>
|
|
<div class="section">
|
|
<h3>@Loc["Birthday"]</h3>
|
|
<div>
|
|
<input type="checkbox" id="set-birthday" @bind="@SetBirthday">
|
|
<label for="set-birthday">@Loc["Set birthday"]</label>
|
|
</div>
|
|
<input type="date" class="input" @bind="@Birthday" disabled="@(!SetBirthday)"/>
|
|
</div>
|
|
<div class="section">
|
|
<h3>@Loc["Location"]</h3><input class="input" @bind="@UserProfile.Location"/>
|
|
</div>
|
|
<div class="user-fields">
|
|
<h3>@Loc["Additional Fields"]</h3>
|
|
<div class="fields">
|
|
@foreach (var entry in UserProfile.Fields)
|
|
{
|
|
<div class="field">
|
|
<input class="input" placeholder="@Loc["Name"]" @bind="@entry.Name"/>
|
|
<input class="input" placeholder="@Loc["Value"]" @bind="@entry.Value"/>
|
|
<button class="button" title="@Loc["Delete Field"]" @onclick="() => DeleteField(entry)">
|
|
<Icon Name="Icons.Trash"/>
|
|
</button>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="new-field">
|
|
<input class="input" placeholder="@Loc["Name"]" @bind="@FieldName"/>
|
|
<input class="input" placeholder="@Loc["Value"]" @bind="@FieldValue"/>
|
|
<button class="button" title="@Loc["Add Field"]" @onclick="AddField">
|
|
<Icon Name="Icons.Plus"/>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>@Loc["Other"]</h3>
|
|
<div>
|
|
<input type="checkbox" id="is-bot" @bind="@UserProfile.IsBot">
|
|
<label for="is-bot">@Loc["Mark as an automated (bot) account"]</label>
|
|
</div>
|
|
<div>
|
|
<input type="checkbox" id="is-cat" @bind="@UserProfile.IsCat">
|
|
<label for="is-cat">@Loc["Mark as a cat"]</label>
|
|
</div>
|
|
<div>
|
|
<input type="checkbox" id="speak-cat" @bind="@UserProfile.SpeakAsCat" disabled="@(!UserProfile.IsCat)">
|
|
<label for="speak-cat">@Loc["Speak as a cat"]</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<StateButton OnClick="SaveChanges" ExtraClasses="button" @ref="SaveButton">
|
|
<Initial>
|
|
<Icon Name="Icons.FloppyDisk"/>@Loc["Save"]
|
|
</Initial>
|
|
<Loading>
|
|
<Icon Name="Icons.Spinner"/>
|
|
</Loading>
|
|
<Failed>
|
|
<Icon Name="Icons.X"/>@Loc["Error"]
|
|
</Failed>
|
|
<Success>
|
|
<Icon Name="Icons.Check"/>@Loc["Saved"]
|
|
</Success>
|
|
</StateButton>
|
|
</div>
|
|
}
|
|
@if (State is State.Loading)
|
|
{
|
|
<div>Loading!</div>
|
|
}
|
|
</div>
|
|
|
|
@code {
|
|
private UserProfileEntity UserProfile { get; set; } = null!;
|
|
private State State { get; set; } = State.Loading;
|
|
private string FieldName { get; set; } = "";
|
|
private string FieldValue { get; set; } = "";
|
|
private StateButton SaveButton { get; set; } = null!;
|
|
private IBrowserFile? AvatarFile { get; set; }
|
|
private bool DelAvatar { get; set; }
|
|
private IBrowserFile? BannerFile { get; set; }
|
|
private bool DelBanner { get; set; }
|
|
private DateTime Birthday { get; set; } = DateTime.Now;
|
|
private bool SetBirthday { get; set; }
|
|
private ElementReference Description { get; set; }
|
|
private ElementReference EmojiButton { get; set; }
|
|
|
|
private DriveFileResponse? Avatar { get; set; }
|
|
private DriveFileResponse? Banner { get; set; }
|
|
|
|
private IJSObjectReference _module = null!;
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
_module = await Js.InvokeAsync<IJSObjectReference>("import",
|
|
"./Pages/Settings/Profile.razor.js");
|
|
try
|
|
{
|
|
UserProfile = await Api.Profile.GetProfileAsync();
|
|
if (UserProfile.Birthday != null)
|
|
{
|
|
Birthday = DateTime.Parse(UserProfile.Birthday);
|
|
SetBirthday = true;
|
|
}
|
|
|
|
State = State.Loaded;
|
|
Avatar = await Api.Profile.GetAvatarAsync();
|
|
Banner = await Api.Profile.GetBannerAsync();
|
|
|
|
UserProfile.AvatarAlt = Avatar?.Description;
|
|
UserProfile.BannerAlt = Banner?.Description;
|
|
}
|
|
catch (ApiException e)
|
|
{
|
|
Logger.LogError($"Profile load failed: {e.Message}");
|
|
State = State.Error;
|
|
}
|
|
}
|
|
|
|
private void AddField()
|
|
{
|
|
UserProfile.Fields.Add(new UserProfileEntity.Field { Name = FieldName, Value = FieldValue });
|
|
FieldName = "";
|
|
FieldValue = "";
|
|
}
|
|
|
|
private void DeleteField(UserProfileEntity.Field field)
|
|
{
|
|
UserProfile.Fields.Remove(field);
|
|
}
|
|
|
|
private async Task SaveChanges()
|
|
{
|
|
try
|
|
{
|
|
SaveButton.State = StateButton.StateEnum.Loading;
|
|
|
|
if (!SetBirthday)
|
|
UserProfile.Birthday = null;
|
|
else
|
|
UserProfile.Birthday = Birthday.ToString("yyyy-MM-dd");
|
|
|
|
if (DelAvatar)
|
|
{
|
|
await Api.Profile.DeleteAvatarAsync();
|
|
UserProfile.AvatarAlt = null;
|
|
}
|
|
else if (AvatarFile != null)
|
|
{
|
|
await Api.Profile.UpdateAvatarAsync(AvatarFile, UserProfile.AvatarAlt);
|
|
UserProfile.AvatarAlt = null;
|
|
}
|
|
|
|
if (DelBanner)
|
|
{
|
|
await Api.Profile.DeleteBannerAsync();
|
|
UserProfile.BannerAlt = null;
|
|
}
|
|
else if (BannerFile != null)
|
|
{
|
|
await Api.Profile.UpdateBannerAsync(BannerFile, UserProfile.BannerAlt);
|
|
UserProfile.BannerAlt = null;
|
|
}
|
|
|
|
await Api.Profile.UpdateProfileAsync(UserProfile);
|
|
SaveButton.State = StateButton.StateEnum.Success;
|
|
}
|
|
catch (ApiException e)
|
|
{
|
|
Logger.LogError($"Failed to update profile: {e.Message}");
|
|
SaveButton.State = StateButton.StateEnum.Failed;
|
|
}
|
|
}
|
|
|
|
private void OnAvatarFileChange(InputFileChangeEventArgs e)
|
|
{
|
|
AvatarFile = e.GetMultipleFiles().First(p => p.ContentType.StartsWith("image/"));
|
|
UserProfile.AvatarAlt = "";
|
|
}
|
|
|
|
private void OnBannerFileChange(InputFileChangeEventArgs e)
|
|
{
|
|
BannerFile = e.GetMultipleFiles().First(p => p.ContentType.StartsWith("image/"));
|
|
UserProfile.BannerAlt = "";
|
|
}
|
|
|
|
private void ToggleEmojiPicker()
|
|
{
|
|
GlobalComponentSvc.EmojiPicker?.Open(EmojiButton, new EventCallback<EmojiResponse>(this, AddEmoji));
|
|
}
|
|
|
|
private async Task AddEmoji(EmojiResponse emoji)
|
|
{
|
|
var pos = await _module.InvokeAsync<int>("getSelectionStart", Description);
|
|
var text = UserProfile.Description ?? "";
|
|
var emojiString = $":{emoji.Name}: ";
|
|
UserProfile.Description = text.Insert(pos, emojiString);
|
|
StateHasChanged();
|
|
}
|
|
}
|