Files
decePubClient/Components/Admin/RulesPanel.razor
T

177 lines
7.3 KiB
Plaintext
Raw Normal View History

@inherits LocalizableComponentBase
@* The server's rules (/clientapi/admin/settings): written, explained, put in order and removed here, then saved together.
A rule keeps its id while it is listed, so the reports that name it keep their meaning; one removed is kept, greyed
below, as old reports name it, and can come back. Saving starts from the settings PrivaPub has now
(InstanceAdminService.SaveSettings), so what Instance or Media limits saved stays. *@
<div class="flex flex-col gap-3 w-full" data-rules>
@if (IsLoading)
{
<p class="flex w-full items-center justify-center gap-2">
<DLoading Variant="DLoadingVariant.Dots" LabelText="@Localizer["loading"]" />
@Localizer["loading"]
</p>
}
else if (settings is null)
{
<div class="flex flex-wrap items-center gap-2">
<p class="text-sm text-error">@Localizer["The server's settings could not be read."]</p>
<DButton Size="DSize.Sm" Disabled="IsDefaultDisabled" OnClick="Retry">@Localizer["Retry"]</DButton>
</div>
}
else
{
<p class="text-xs opacity-70">@Localizer["Everyone reads them on the About page, and a report of a violation names the ones broken."]</p>
@if (rows.Count == 0)
{
<p class="text-sm opacity-70" data-rules-empty>@Localizer["The server has no rules."]</p>
}
<ol class="flex flex-col gap-2">
@foreach (var (row, position) in Numbered)
{
<li @key="row" class="flex flex-col gap-2 p-2 rounded-lg neo-raised neo-2xs" data-rule="@position" data-rule-id="@(row.Id ?? "new")">
<div class="flex items-start gap-2 pointer-coarse:gap-2">
<span class="mt-2 w-6 flex-none text-right text-sm font-semibold tabular-nums">@(position + 1).</span>
<div class="flex flex-col gap-2 flex-1 min-w-0">
<DTextarea TValue="string" class="w-full" @bind-Value="row.Text" Immediate="true" rows="2" autocomplete="off" maxlength="500"
placeholder="@Localizer["The rule"]" aria-label="@Localizer["The rule"]" data-rule-text />
<DInput TValue="string" class="w-full" Size="DSize.Sm" @bind-Value="row.Hint" Immediate="true" autocomplete="off" maxlength="1000"
placeholder="@Localizer["What it means, if it needs saying (optional)"]" aria-label="@Localizer["Explanation"]" data-rule-hint />
</div>
<div class="flex flex-col gap-1 pointer-coarse:gap-2 flex-none">
<DButton Size="DSize.Xs" Disabled="IsDefaultDisabled || isSaving || position == 0" OnClick="() => Move(position, -1)"
aria-label="@Localizer["Move up"]" title="@Localizer["Move up"]" data-rule-up>
<i class="ion-md-arrow-up" aria-hidden="true"></i>
</DButton>
<DButton Size="DSize.Xs" Disabled="IsDefaultDisabled || isSaving || position == rows.Count - 1" OnClick="() => Move(position, 1)"
aria-label="@Localizer["Move down"]" title="@Localizer["Move down"]" data-rule-down>
<i class="ion-md-arrow-down" aria-hidden="true"></i>
</DButton>
<DButton Size="DSize.Xs" Disabled="IsDefaultDisabled || isSaving" OnClick="() => Remove(row)"
aria-label="@Localizer["Remove the rule"]" title="@Localizer["Remove the rule"]" data-rule-delete>
<i class="ion-md-trash text-error" aria-hidden="true"></i>
</DButton>
</div>
</div>
</li>
}
</ol>
<div class="flex flex-wrap items-center gap-2 pointer-coarse:gap-2">
<DButton Size="DSize.Sm" Disabled="IsDefaultDisabled || isSaving || rows.Count >= ServerSettingsForm.MaxRules" OnClick="Add" data-rule-add>
<i class="ion-md-add" aria-hidden="true"></i><span>@Localizer["Add a rule"]</span>
</DButton>
<span class="flex-1"></span>
<DButton Size="DSize.Sm" Disabled="IsDefaultDisabled || isSaving" OnClick="Reset" data-rule-reset>@Localizer["Discard changes"]</DButton>
<DButton Size="DSize.Sm" Color="DColor.Primary" Disabled="IsDefaultDisabled || isSaving || rows.Any(r => string.IsNullOrWhiteSpace(r.Text))" OnClick="Save"
data-rule-save>
@if (isSaving)
{
<DLoading Size="DSize.Sm" LabelText="@Localizer["loading"]" />
}
<span>@Localizer["Save the rules"]</span>
</DButton>
</div>
@if (rows.Count >= ServerSettingsForm.MaxRules)
{
<p class="text-xs opacity-70">@Localizer["A server has {0} rules at most.", ServerSettingsForm.MaxRules]</p>
}
@if (Removed.Count > 0)
{
<div class="flex flex-col gap-2 mt-2" data-rules-deleted>
<b class="text-sm">@Localizer["Removed rules, kept as old reports name them"]</b>
@foreach (var rule in Removed)
{
<div class="flex flex-wrap items-center gap-2 p-2 rounded-lg neo-inset neo-2xs opacity-60" data-rule-deleted="@rule.Id">
<span class="flex flex-col flex-1 min-w-0">
<s class="text-sm break-words">@rule.Text</s>
@if (rule.DeletedAt is { } deletedAt)
{
<span class="text-xs" title="@deletedAt.ToLocalTime().ToString(SUtility.dateTimeFormatExt)">@Localizer["Removed {0} ago", deletedAt.GetPassedTime(Localizer)]</span>
}
</span>
<DButton Size="DSize.Xs" Disabled="IsDefaultDisabled || isSaving || rows.Count >= ServerSettingsForm.MaxRules" OnClick="() => Restore(rule)"
data-rule-restore>
<i class="ion-md-undo" aria-hidden="true"></i><span>@Localizer["Bring it back"]</span>
</DButton>
</div>
}
</div>
}
}
</div>
@code {
[Inject] InstanceAdminService InstanceAdminService { get; set; }
ViewServerSettings settings;
// the rules as being written, in their order: a listed rule keeps its id, a new one has none
List<ServerRuleForm> rows = [];
bool isSaving;
IEnumerable<(ServerRuleForm Row, int Position)> Numbered => rows.Select((row, position) => (row, position));
// the rules the server keeps deleted, unless they are listed again above
List<ViewServerRule> Removed => [.. (settings?.Rules ?? []).Where(r => r.IsDeleted && rows.All(row => row.Id != r.Id))];
protected override async Task OnInitializedAsync()
{
if (CascadingState.IsOffline)
{
WhenOnline(LoadData);
StartOnlinePolling();
IsLoading = false;
return;
}
await LoadData();
IsLoading = false;
}
async Task LoadData()
{
settings = await InstanceAdminService.Settings();
Reset();
}
async Task Retry()
{
IsLoading = true;
await LoadData();
IsLoading = false;
}
void Reset() => rows = settings is null ? [] : InstanceAdminService.FormOf(settings).Rules;
void Add() => rows.Add(new ServerRuleForm());
// gone from the list, it is removed when the rules are saved (kept, marked, for the reports naming it)
void Remove(ServerRuleForm row) => rows.Remove(row);
void Move(int position, int by)
{
var row = rows[position];
rows.RemoveAt(position);
rows.Insert(position + by, row);
}
// listed again with its id, it is the same rule: the reports naming it name it still
void Restore(ViewServerRule rule) => rows.Add(new ServerRuleForm { Id = rule.Id, Text = rule.Text, Hint = rule.Hint });
async Task Save()
{
var rules = rows.Select(r => new ServerRuleForm { Id = r.Id, Text = r.Text?.Trim(), Hint = string.IsNullOrWhiteSpace(r.Hint) ? null : r.Hint.Trim() }).ToList();
isSaving = true;
var result = await InstanceAdminService.SaveSettings(latest => latest.Rules = rules);
isSaving = false;
if (result.IsInvalid)
{
Toast.AddResponseError(result);
return;
}
settings = (ViewServerSettings)result.Data;
Reset();
Toast.AddResponseSuccess(Localizer["The rules are saved."]);
}
}