Give the application a settings area built from what really exists

This commit is contained in:
2026-08-08 14:17:19 +02:00
parent 422d5ca10e
commit c8507b44fe
42 changed files with 3810 additions and 1083 deletions
@@ -107,6 +107,9 @@ internal sealed record VaultRowViewModel(
};
internal bool HasState => State.Length > 0;
/// <summary>The single letter the settings page's card draws on this vault's tile.</summary>
internal string Initial => Name.Length > 0 ? Name[..1].ToUpperInvariant() : "?";
}
/// <summary>One member of a vault, as a row in the members table.</summary>
@@ -153,6 +156,32 @@ internal sealed record VaultMemberRowViewModel(TeamMemberSummary Member, bool Is
internal bool CanBeRemoved => Member.Role != TeamMemberRole.Owner;
/// <summary>
/// The two letters the members panel draws on this row's avatar.
/// </summary>
/// <remarks>
/// The same rule <c>HostRowViewModel.Monogram</c> draws a card's monogram by — the first letters of the
/// first two words in the name, or the first two characters where it is one word — except uppercase,
/// which is how the design draws a person's initials rather than a host's.
/// </remarks>
internal string Initials
{
get
{
var words = Name.Split(
InitialsWordSeparators,
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
var letters = words.Length >= 2
? string.Concat(words[0][0], words[1][0])
: Name.Length >= 2 ? Name[..2] : Name;
return letters.ToUpperInvariant();
}
}
private static readonly char[] InitialsWordSeparators = [' ', '-', '_', '.'];
/// <summary>Whether this member's role can be changed at all.</summary>
/// <remarks>
/// The owner's cannot, and not for want of an endpoint: ownership is sole, so demoting them is
@@ -312,6 +341,33 @@ internal sealed partial class VaultsViewModel(
[ObservableProperty]
private bool isBusy;
/// <summary>
/// Whether the members panel — v5c's settings-page overlay over the selected vault's people — is open.
/// </summary>
/// <remarks>
/// Desktop-only presentation state, not a fact about any vault: it says which rectangle is on screen and
/// nothing else. Selecting a vault is what actually reads its members, in
/// <see cref="OnSelectedVaultChanged"/>; opening this panel over one already selected re-reads nothing.
/// </remarks>
[ObservableProperty]
private bool isMembersPanelOpen;
/// <summary>Opens the members panel, selecting the vault it is about first if it is not selected already.</summary>
[RelayCommand]
private void OpenMembersPanel(VaultRowViewModel? vault)
{
if (vault is not null && !ReferenceEquals(vault, SelectedVault))
{
SelectedVault = vault;
}
IsMembersPanelOpen = true;
}
/// <summary>Closes the members panel.</summary>
[RelayCommand]
private void CloseMembersPanel() => IsMembersPanelOpen = false;
// ---- Creating a vault ----
[ObservableProperty]
@@ -824,6 +880,38 @@ internal sealed partial class VaultsViewModel(
Status = string.Empty;
}
/// <summary>Opens the rename form for a specific vault, from the settings page's per-card edit icon.</summary>
/// <remarks>
/// A thin wrapper around <see cref="RenameVault"/> rather than a second implementation: the settings
/// page draws one card per vault with no list selection to lean on the way this screen used to have, so
/// this selects the row first and then asks the command that already knows how to open the form.
/// </remarks>
/// <param name="vault">The vault to rename.</param>
[RelayCommand]
private void RenameVaultRow(VaultRowViewModel? vault)
{
if (vault is not null)
{
SelectedVault = vault;
}
RenameVaultCommand.Execute(null);
}
/// <summary>Arms the delete confirmation for a specific vault, from the settings page's per-card delete icon.</summary>
/// <inheritdoc cref="RenameVaultRow" path="/remarks" />
/// <param name="vault">The vault to ask about deleting.</param>
[RelayCommand]
private void DeleteVaultRow(VaultRowViewModel? vault)
{
if (vault is not null)
{
SelectedVault = vault;
}
DeleteVaultCommand.Execute(null);
}
/// <summary>
/// Saves the renamed vault.
/// </summary>
@@ -1538,6 +1626,13 @@ internal sealed partial class VaultsViewModel(
PendingAction = null;
IsRenamingVault = false;
// The members panel is drawn over one vault's people; a selection that clears altogether — the
// vault it was showing got deleted, or the list emptied — leaves nothing for it to be about.
if (value is null)
{
IsMembersPanelOpen = false;
}
if (isReselecting)
{
return;