Files
decePubClient/Components/Settings/MutesAndBlocks.razor
T

229 lines
6.7 KiB
Plaintext
Raw Normal View History

@inherits LocalizableComponentBase
@* The accounts the current persona muted or blocked (/api/v1/mutes, /api/v1/blocks), and the servers it blocked
(/api/v1/domain_blocks), each one undone here. A server blocked hides everything from it and removes the persona's
followers there. *@
@if (IsLoading)
{
<p class="flex w-full items-center justify-center gap-2">
<DLoading Variant="DLoadingVariant.Dots" LabelText="@Localizer["loading"]" />
@Localizer["loading"]
</p>
}
else
{
<div class="flex flex-col gap-5 w-full">
<section class="flex flex-col gap-2" data-mutes>
<h3 class="font-semibold text-sm">@Localizer["Muted accounts"]</h3>
@if (mutes.Count == 0)
{
<p class="text-sm opacity-70">@Localizer["Nobody is muted."]</p>
}
@foreach (var account in mutes)
{
<div class="flex items-center gap-2 min-w-0" data-account-id="@account.Id">
<img class="size-8 rounded-full object-cover" src="@account.Avatar" alt="" />
<a class="flex-1 min-w-0 truncate link link-hover" href="@($"account/{Uri.EscapeDataString(account.Id)}")">
<b>@account.Name</b> <span class="text-xs opacity-70">@@@account.Acct</span>
</a>
<DButton Size="DSize.Sm" Disabled="@(IsDefaultDisabled || busy.Contains(account.Id))" OnClick="() => Undo(account, AccountAction.Unmute)">
@Localizer["Unmute"]
</DButton>
</div>
}
@if (mutesMaxId is not null)
{
<DButton Size="DSize.Sm" class="self-start" Disabled="IsDefaultDisabled" OnClick="MoreMutes">@Localizer["Load more"]</DButton>
}
</section>
<section class="flex flex-col gap-2" data-blocks>
<h3 class="font-semibold text-sm">@Localizer["Blocked accounts"]</h3>
@if (blocks.Count == 0)
{
<p class="text-sm opacity-70">@Localizer["Nobody is blocked."]</p>
}
@foreach (var account in blocks)
{
<div class="flex items-center gap-2 min-w-0" data-account-id="@account.Id">
<img class="size-8 rounded-full object-cover" src="@account.Avatar" alt="" />
<a class="flex-1 min-w-0 truncate link link-hover" href="@($"account/{Uri.EscapeDataString(account.Id)}")">
<b>@account.Name</b> <span class="text-xs opacity-70">@@@account.Acct</span>
</a>
<DButton Size="DSize.Sm" Disabled="@(IsDefaultDisabled || busy.Contains(account.Id))" OnClick="() => Undo(account, AccountAction.Unblock)">
@Localizer["Unblock"]
</DButton>
</div>
}
@if (blocksMaxId is not null)
{
<DButton Size="DSize.Sm" class="self-start" Disabled="IsDefaultDisabled" OnClick="MoreBlocks">@Localizer["Load more"]</DButton>
}
</section>
<section class="flex flex-col gap-2" data-domain-blocks>
<h3 class="font-semibold text-sm">@Localizer["Blocked servers"]</h3>
<EditForm Model="draft" OnValidSubmit="BlockDomain" class="flex flex-wrap gap-2 items-center">
<DInput TValue="string" class="flex-1 min-w-48" @bind-Value="draft.Domain" Immediate="true" autocomplete="off" maxlength="253"
placeholder="@Localizer["A server, e.g. example.social"]" data-domain-new>
<Prefix><i class="ion-md-globe"></i></Prefix>
</DInput>
<DButton Type="submit" Size="DSize.Sm" Color="DColor.Error" Disabled="@(IsDefaultDisabled || string.IsNullOrWhiteSpace(draft.Domain) || busy.Contains(Domain(draft.Domain)))">
@Localizer["Block server"]
</DButton>
</EditForm>
@if (domains.Count == 0)
{
<p class="text-sm opacity-70">@Localizer["No server is blocked."]</p>
}
@foreach (var domain in domains)
{
<div class="flex items-center gap-2 min-w-0" data-domain="@domain">
<i class="ion-md-globe opacity-70"></i>
<span class="flex-1 min-w-0 truncate">@domain</span>
<DButton Size="DSize.Sm" Disabled="@(IsDefaultDisabled || busy.Contains(domain))" OnClick="() => UnblockDomain(domain)">
@Localizer["Unblock"]
</DButton>
</div>
}
</section>
</div>
}
@code {
[Inject] AccountService AccountService { get; set; }
sealed class DomainDraft
{
public string Domain { get; set; }
}
List<Account> mutes = [];
List<Account> blocks = [];
List<string> domains = [];
string mutesMaxId;
string blocksMaxId;
DomainDraft draft = new();
// the accounts and servers whose undo is on its way
HashSet<string> busy = [];
string loadedPersonaId;
protected override async Task OnParametersSetAsync()
{
if (CascadingState.PersonaId == loadedPersonaId)
return;
loadedPersonaId = CascadingState.PersonaId;
if (CascadingState.IsOffline)
{
WhenOnline(LoadData);
StartOnlinePolling();
IsLoading = false;
return;
}
await LoadData();
IsLoading = false;
}
async Task LoadData()
{
mutes = [];
blocks = [];
mutesMaxId = null;
blocksMaxId = null;
await Task.WhenAll(MoreMutes(), MoreBlocks());
domains = await AccountService.DomainBlocks();
}
async Task MoreMutes()
{
var result = await AccountService.Mutes(mutesMaxId);
if (result.IsInvalid)
{
Toast.AddResponseError(result);
return;
}
var page = (AccountPage)result.Data;
mutes.AddRange(page.Accounts.Where(a => mutes.All(m => m.Id != a.Id)));
mutesMaxId = page.MaxId;
}
async Task MoreBlocks()
{
var result = await AccountService.Blocks(blocksMaxId);
if (result.IsInvalid)
{
Toast.AddResponseError(result);
return;
}
var page = (AccountPage)result.Data;
blocks.AddRange(page.Accounts.Where(a => blocks.All(b => b.Id != a.Id)));
blocksMaxId = page.MaxId;
}
async Task Undo(Account account, AccountAction action)
{
busy.Add(account.Id);
var result = await AccountService.Act(account.Id, action);
busy.Remove(account.Id);
if (result.IsInvalid)
{
Toast.AddResponseError(result);
return;
}
if (action is AccountAction.Unmute)
mutes.RemoveAll(m => m.Id == account.Id);
else
blocks.RemoveAll(b => b.Id == account.Id);
}
// what a person types as a server (a URL, a handle's host, any case) as the bare host PrivaPub keys it by
static string Domain(string typed)
{
var text = typed?.Trim().TrimStart('@') ?? "";
if (Uri.TryCreate(text.Contains("://") ? text : "https://" + text, UriKind.Absolute, out var uri))
text = uri.Host;
return text.ToLowerInvariant();
}
async Task BlockDomain()
{
var domain = Domain(draft.Domain);
if (domain.Length == 0 || domains.Contains(domain))
{
draft = new();
return;
}
busy.Add(domain);
var result = await AccountService.BlockDomain(domain);
busy.Remove(domain);
if (result.IsInvalid)
{
Toast.AddResponseError(result);
return;
}
draft = new();
domains = [.. domains.Append(domain).Order()];
Toast.AddResponseSuccess(Localizer["{0} is blocked.", domain]);
}
async Task UnblockDomain(string domain)
{
busy.Add(domain);
var result = await AccountService.UnblockDomain(domain);
busy.Remove(domain);
if (result.IsInvalid)
{
Toast.AddResponseError(result);
return;
}
domains.Remove(domain);
}
}