177 lines
7.3 KiB
Plaintext
177 lines
7.3 KiB
Plaintext
@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."]);
|
||
|
|
}
|
||
|
|
}
|