Iceshrimp.NET/Iceshrimp.Frontend/Pages/Settings/Profile.razor

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();
}
}