Files
MilkyShots/MilkStream.Client/Components/Pages/Settings.razor
T
REDCODE 92fb5a0149 fix: add ConfirmDialog to Settings folder deletion flow
FolderBox now delegates deletion to parent via OnDeleteRequested
callback instead of deleting directly. Settings.razor shows a
ConfirmDialog before removing the folder.
2026-07-16 15:11:33 +02:00

200 lines
7.7 KiB
Plaintext

@page "/Settings"
@using Butter.Dtos.Folder
@using Butter.Dtos.Settings
@using Butter.Settings
@using Butter.Types
@using MilkStream.Client.Components.SettingBoxes
@using System.Linq
@inject NavigationManager NavigationManager
@inject LoginService LoginService
@inject SettingsService SettingsService
@inject FoldersService FoldersService
<PageTitle>Settings</PageTitle>
<div class="settings-layout">
<nav class="settings-sidebar">
@foreach (var group in _groups) {
<button class="settings-nav-item @(_activeGroup == group.Key ? "active" : "")"
@onclick="() => SelectGroup(group.Key)">
<i class="bi @group.Value.Icon"></i>
<span>@group.Key</span>
</button>
}
</nav>
<main class="settings-content">
@if (_isLoading) {
<div class="d-flex justify-content-center align-items-center h-100">
<LoadSpinner/>
</div>
} else if (_activeGroup == "Folders") {
<div class="d-flex align-items-center justify-content-between mb-3">
<h4 class="m-0"><i class="bi bi-folder2-open"></i> Folders</h4>
<button class="btn btn-primary btn-sm" @onclick="OnAddFolder">
<i class="bi bi-folder-plus"></i> Add folder
</button>
</div>
@if (_folders.Any()) {
@foreach (var folder in _folders) {
<FolderBox Folder="folder" OnDeleteRequested="RequestFolderDelete"/>
}
} else {
<div class="alert alert-secondary">No folders configured yet.</div>
}
} else {
<h4 class="mb-3"><i class="bi @_groups[_activeGroup].Icon"></i> @_activeGroup</h4>
@if (_groupedSettings.TryGetValue(_activeGroup, out var settings) && settings.Count > 0) {
<div class="settings-grid">
@{ settings.Sort((dto, other) => string.Compare(dto.Name, other.Name, StringComparison.OrdinalIgnoreCase)); }
@foreach (var setting in settings) {
@switch (setting.DisplayType) {
case DisplayType.Switch:
<SettingSwitch Setting="setting"/>
break;
case DisplayType.IntNumber:
case DisplayType.FloatNumber:
<SettingNumber Setting="setting"/>
break;
case DisplayType.Text:
case DisplayType.Password:
<SettingString Setting="setting"/>
break;
case DisplayType.TimePicker:
<SettingRange Setting="setting"
MinValue="Convert.ToInt32(setting.Options?[0])"
MaxValue="Convert.ToInt32(setting.Options?[1])"
Step="Convert.ToInt32(setting.Options?[2])"/>
break;
case DisplayType.Po2W:
<SettingRange Setting="setting" IsPowerOfTwo="true"
MinPowerValue="Convert.ToInt32(setting.Options?[0])"
MaxPowerValue="Convert.ToInt32(setting.Options?[1])"
Step="Convert.ToInt32(setting.Options?[2])"/>
break;
case DisplayType.UserSelect:
<SettingUserSelect Setting="setting"/>
break;
default:
<div class="alert alert-warning py-1 small">Unsupported: @setting.DisplayType</div>
break;
}
}
</div>
} else {
<div class="alert alert-secondary">No settings in this category.</div>
}
}
</main>
</div>
<ConfirmDialog Show="_showDeleteConfirm"
Title="Confirm Deletion"
Message="@_deleteConfirmMessage"
ConfirmText="Delete"
ConfirmButtonClass="btn-danger"
OnConfirm="ExecuteFolderDelete"
OnCancel="() => _showDeleteConfirm = false" />
@code {
private record GroupInfo(string Icon, string[] SettingNames);
private static readonly Dictionary<string, GroupInfo> _groups = new() {
["General"] = new("bi-gear-wide-connected", ["User Registration Enabled"]),
["Scanning"] = new("bi-arrow-repeat", ["Folder Scan Enabled", "Folder Scan Interval", "System Uploader Id"]),
["Upload"] = new("bi-upload", ["File Upload Enabled", "File Upload MaxSize"]),
["Paths"] = new("bi-folder2", ["Thumbnail Path", "Preview Path"]),
["Jobs"] = new("bi-kanban-fill", ["Thumbnail Size", "Preview Size", "Thumbnail Quality", "Preview Quality", "Max Concurrent Jobs", "Job Batch Size", "Job Retention Days"]),
["Folders"] = new("bi-folder2-open", [])
};
private Dictionary<string, List<SettingDto>> _groupedSettings = new();
private List<FolderFullDto> _folders = new();
private string _activeGroup = "General";
private bool _isLoading = true;
bool _showDeleteConfirm;
string _deleteConfirmMessage = "";
Guid _pendingDeleteFolderId;
protected override async Task OnInitializedAsync() {
if (!LoginService.IsLoggedIn) {
NavigationManager.NavigateToLogin();
return;
}
if (!LoginService.IsAdmin) {
NavigationManager.NavigateTo("/");
return;
}
await LoadData();
}
async Task LoadData() {
_isLoading = true;
StateHasChanged();
var settingsTask = SettingsService.GetAllSettings();
var foldersTask = FoldersService.GetAllFolders();
await Task.WhenAll(settingsTask, foldersTask);
var settings = settingsTask.Result;
var folders = foldersTask.Result;
if (settings != null) {
_groupedSettings = GroupSettings(settings);
}
if (folders != null) {
_folders = folders;
}
_isLoading = false;
StateHasChanged();
}
static Dictionary<string, List<SettingDto>> GroupSettings(List<SettingDto> settings) {
var lookup = _groups
.Where(g => g.Key != "Folders")
.SelectMany(g => g.Value.SettingNames.Select(name => (Group: g.Key, Name: name)))
.ToDictionary(x => x.Name, x => x.Group);
return settings
.GroupBy(s => lookup.GetValueOrDefault(s.Name, "Other"))
.ToDictionary(g => g.Key, g => g.ToList());
}
void SelectGroup(string group) {
_activeGroup = group;
}
async Task OnAddFolder() {
await FoldersService.CreateFolderAsync(new FolderCreateDto { BasePath = "New Folder" });
await LoadData();
}
void RequestFolderDelete(Guid id) {
var folder = _folders.FirstOrDefault(f => f.Id == id);
_deleteConfirmMessage = $"Delete folder '{folder?.BasePath}'? This cannot be undone.";
_pendingDeleteFolderId = id;
_showDeleteConfirm = true;
}
async Task ExecuteFolderDelete() {
_showDeleteConfirm = false;
await FoldersService.RemoveFolderAsync(_pendingDeleteFolderId);
HandleFolderDeleted(_pendingDeleteFolderId);
}
void HandleFolderDeleted(Guid id) {
_folders.RemoveAll(f => f.Id == id);
StateHasChanged();
}
}