Files
DodoSSH/src/DodoSSH.Client.App/Views/SettingsGroupsPage.axaml
T

195 lines
12 KiB
XML

<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:DodoSSH.Client.Shell.ViewModels"
xmlns:views="using:DodoSSH.Client.App.Views"
x:Class="DodoSSH.Client.App.Views.SettingsGroupsPage"
x:DataType="vm:MainWindowViewModel"
x:Name="Root">
<!--
v5c-2: Groups, against Settings-Groups.dc.html — a real management page over VaultViewModel's own group
commands (Groups, NewGroup, EditGroup, DeleteGroup), given its own row here rather than only living inside
the hosts board's sidebar. DataContext.Vault (VaultViewModel) is nullable — locked, or the settings mode
reached before an unlock — so every binding below is {Binding Vault.*}, and the empty-vault caption covers
the null case the same way the vaults page's own does.
── THE INTRO SENTENCE IS NOT THE MOCK'S ────────────────────────────────────────────────────────────────
The design says "the order here is the order there" and draws drag handles to back it up. Neither is true:
HostGroupRowViewModel — see VaultViewModel.RebuildGroups — orders groups by the vault new items go into
first, then by vault name, then by label; there is no manual order to drag into and nothing this page
could do would make one real. The sentence below says what is actually true instead, and there are no
drag handles.
── EDIT AND DELETE ──────────────────────────────────────────────────────────────────────────────────────
EditGroupCommand and DeleteGroupCommand already take the row as their argument — see
VaultViewModel.EditGroup and VaultViewModel.EditGroupFromHeading's own remark on why — so this card's
buttons bind straight to them with CommandParameter="{Binding}"; no wrapper commands were needed the way
the tags page needs one. Editing opens the same drawer form the hosts board's own GROUPS section does
(HostDrawer.axaml's IsEditingGroup panel); it is not reproduced here as a second form, on the reasoning
the vaults page gives for reusing VaultsViewModel's commands rather than rebuilding them: one set of
fields, one place they can drift from what SaveGroupCommand actually writes.
── THE "NO GROUP" FOOTER ────────────────────────────────────────────────────────────────────────────────
Real, all three facts: VaultViewModel.UngroupedHostCount counts hosts whose group has gone or was never
set, "always listed first" is true of both FlattenIntoSections call sites (RebuildSidebarRows passes
ungroupedFirst: false — it is drawn last there — but the desktop's own board, RebuildHostSections, passes
true), and it genuinely cannot be renamed or deleted: there is no HostGroupSecret behind it for either
command to act on.
-->
<ScrollViewer>
<StackPanel MaxWidth="1100" Margin="40" HorizontalAlignment="Stretch">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="14" VerticalAlignment="Center">
<TextBlock Classes="settingstitle" Text="Groups" />
<Border Background="{StaticResource Chip}" CornerRadius="9" MinWidth="34" Height="30"
IsVisible="{Binding Vault, Converter={x:Static ObjectConverters.IsNotNull}}">
<TextBlock Text="{Binding Vault.Groups.Count}" FontSize="13.5" FontWeight="SemiBold"
Foreground="{StaticResource TextFaint}" Margin="8,0"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</StackPanel>
<Button Grid.Column="1" Classes="accent" Height="40" Content="+ NEW GROUP"
Command="{Binding Vault.NewGroupCommand}"
IsEnabled="{Binding Vault, Converter={x:Static ObjectConverters.IsNotNull}}" />
</Grid>
<TextBlock Classes="settingsrowcaption" Margin="0,12,0,0" TextWrapping="Wrap"
Text="Groups order by which vault new items go into, then by vault, then by label — there is no manual order to set." />
<!-- The group editor, in place: HostDrawer's own IsEditingGroup form, restyled into this page's card
idiom rather than duplicated. Opened by + NEW GROUP above or an EDIT icon below. -->
<Border Classes="settingscard" Margin="0,20,0,0" Padding="20"
IsVisible="{Binding Vault.IsEditingGroup}">
<StackPanel Spacing="10">
<TextBlock Classes="settingsrowtitle" Text="{Binding Vault.DrawerTitle}" />
<TextBlock Classes="settingsrowcaption"
Text="A heading for the hosts board, and the defaults every host under it inherits when it says nothing itself." />
<TextBox PlaceholderText="group name" Text="{Binding Vault.GroupEditorLabel}" />
<StackPanel Spacing="4" IsVisible="{Binding Vault.ShowsGroupEditorVaultChoice}">
<ComboBox HorizontalAlignment="Stretch" ItemsSource="{Binding Vault.GroupEditorVaultChoices}"
SelectedItem="{Binding Vault.GroupEditorSelectedVault}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:VaultChoiceViewModel">
<TextBlock Text="{Binding Display}" FontSize="12" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
<ComboBox HorizontalAlignment="Stretch" ItemsSource="{Binding Vault.GroupEditorParentChoices}"
SelectedItem="{Binding Vault.GroupEditorSelectedParent}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:GroupChoice">
<TextBlock Text="{Binding Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Grid ColumnDefinitions="*,8,*">
<NumericUpDown Grid.Column="0" Value="{Binding Vault.GroupEditorDefaultPort}" Minimum="1"
Maximum="65535" FormatString="0" ShowButtonSpinner="False"
PlaceholderText="default port" />
<TextBox Grid.Column="2" Text="{Binding Vault.GroupEditorDefaultUsername}"
PlaceholderText="default username" />
</Grid>
<ComboBox HorizontalAlignment="Stretch" ItemsSource="{Binding Vault.GroupEditorAuthenticationChoices}"
SelectedItem="{Binding Vault.GroupEditorSelectedAuthentication}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:AuthenticationChoice">
<TextBlock Text="{Binding Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<StackPanel Orientation="Horizontal" Spacing="8">
<Button Classes="accent" Height="36" Content="{Binding Vault.GroupSaveLabel}"
Command="{Binding Vault.SaveGroupCommand}" />
<Button Classes="ghost" Height="36" Content="CANCEL"
Command="{Binding Vault.CancelGroupEditCommand}" />
</StackPanel>
</StackPanel>
</Border>
<!-- The delete confirmation, in place. The same control the hosts board's GROUPS section and the
vaults page both reuse. -->
<Border Margin="0,20,0,0" Padding="20" CornerRadius="12"
Background="{StaticResource DangerWash}" BorderBrush="{StaticResource DangerSoft}"
BorderThickness="1"
IsVisible="{Binding Vault.PendingDeletion, Converter={x:Static ObjectConverters.IsNotNull}}">
<views:ConfirmDeleteCard DataContext="{Binding Vault}" />
</Border>
<StackPanel Margin="0,24,0,40" Spacing="10">
<ItemsControl ItemsSource="{Binding Vault.Groups}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Spacing="10" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:HostGroupRowViewModel">
<Border Classes="settingscard" Padding="20,16">
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
<Border Grid.Column="0" Width="38" Height="38" CornerRadius="10"
Background="{StaticResource AvatarGradient}">
<TextBlock Text="{Binding Initial}" FontWeight="Bold" FontSize="14"
Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<StackPanel Grid.Column="1" Spacing="5" Margin="14,0" VerticalAlignment="Center">
<TextBlock Text="{Binding Label}" FontSize="15" FontWeight="Bold" LetterSpacing="-0.2"
Foreground="{StaticResource Text}" TextTrimming="CharacterEllipsis" />
<TextBlock Classes="mono" FontSize="11.5" Foreground="{StaticResource TextFaint}"
Text="{Binding Description}" />
</StackPanel>
<Border Grid.Column="2" Classes="chip" Margin="0,0,14,0" VerticalAlignment="Center"
IsVisible="{Binding HasVaultBadge}">
<TextBlock Text="{Binding VaultBadge}" />
</Border>
<StackPanel Grid.Column="3" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
<Button Classes="paneicon" Width="34" Height="34" FontSize="15"
Command="{Binding #Root.((vm:MainWindowViewModel)DataContext).Vault.EditGroupCommand}"
CommandParameter="{Binding}" ToolTip.Tip="Rename this group or change what its hosts inherit.">
<TextBlock FontFamily="{StaticResource IconFont}" Text="&#xE3C9;" />
</Button>
<Button Classes="paneicon danger" Width="34" Height="34" FontSize="15"
Command="{Binding #Root.((vm:MainWindowViewModel)DataContext).Vault.DeleteGroupCommand}"
CommandParameter="{Binding}" ToolTip.Tip="Delete this group. The hosts filed under it are asked about separately.">
<TextBlock FontFamily="{StaticResource IconFont}" Text="&#xE872;" />
</Button>
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- The "No group" footer row. Always drawn, even with zero groups, because it is true whether or
not any group exists — it is just as often the only row on this page. -->
<Border Classes="settingscard" Padding="20,16" Opacity="0.7"
IsVisible="{Binding Vault, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="14">
<TextBlock FontFamily="{StaticResource IconFont}" FontSize="17" Text="&#xE14B;"
Foreground="{StaticResource TextGhost}" VerticalAlignment="Center" />
<StackPanel Spacing="5">
<TextBlock Text="No group" FontSize="14" FontWeight="SemiBold" LetterSpacing="-0.2"
Foreground="{StaticResource TextFaint}" />
<TextBlock Classes="mono" FontSize="11.5" Foreground="{StaticResource TextGhost}"
Text="{Binding Vault.UngroupedHostCount, StringFormat='{}{0} hosts · always listed first · cannot be renamed or deleted'}" />
</StackPanel>
</StackPanel>
</Border>
<TextBlock Classes="settingsrowcaption" TextWrapping="Wrap"
IsVisible="{Binding Vault, Converter={x:Static ObjectConverters.IsNull}}"
Text="Unlock your keychain to see and manage your groups." />
</StackPanel>
</StackPanel>
</ScrollViewer>
</UserControl>