diff --git a/Directory.Packages.props b/Directory.Packages.props index b279b97..a6cec88 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -100,6 +100,22 @@ ProxyJump both go through a loopback TCP bridge. See docs/adr/. --> + + + + + + + + + + + + + + + + + - - + ItemsSource="{Binding SidebarRows}" + SelectedItem="{Binding SelectedSidebarRow}"> + + + + + + + + + @@ -106,7 +133,8 @@ - + + @@ -148,6 +176,20 @@ + + + + + + + + + IsVisible="{Binding IsConfirmingHostDeletion}"> diff --git a/src/DodoSSH.Client.App/Views/HostsScreen.axaml b/src/DodoSSH.Client.App/Views/HostsScreen.axaml new file mode 100644 index 0000000..d5ba257 --- /dev/null +++ b/src/DodoSSH.Client.App/Views/HostsScreen.axaml @@ -0,0 +1,252 @@ + + + + + + + + + + + + + + + + + + + + - - - - + + - + + + + + diff --git a/src/DodoSSH.Client.App/Views/TerminalTabs.axaml.cs b/src/DodoSSH.Client.App/Views/TerminalTabs.axaml.cs index ee94c91..a270624 100644 --- a/src/DodoSSH.Client.App/Views/TerminalTabs.axaml.cs +++ b/src/DodoSSH.Client.App/Views/TerminalTabs.axaml.cs @@ -1,9 +1,56 @@ +using Avalonia; using Avalonia.Controls; +using Avalonia.Input; +using DodoSSH.Client.App.ViewModels; namespace DodoSSH.Client.App.Views; -/// The tab strip above the terminal. +/// The tab strip, above every screen. internal sealed partial class TerminalTabs : UserControl { public TerminalTabs() => InitializeComponent(); + + /// + /// Closes a tab on a middle click. + /// + /// + /// + /// Wired on the tab's own template root, which is the whole answer to "and not on the strip itself". + /// A middle press on the background, on the sentence, or on the button that opens a connection reaches + /// no handler at all, because there is none there to reach. Nothing has to test what was clicked. + /// + /// + /// PointerUpdateKind, not IsMiddleButtonPressed. The latter reports button + /// state: it is equally true for a left press made while the middle button happens to be held, + /// and for every press during a middle drag. The question here is which button caused this press, and + /// that is the one thing only PointerUpdateKind answers. + /// + /// + /// On press rather than on release, which is what every browser and every terminal does. Matching a + /// release to its press would need capture tracking, to buy the ability to change your mind about a + /// middle click — a gesture nobody makes by accident and nobody aborts. + /// + /// + private void OnTabPointerPressed(object? sender, PointerPressedEventArgs e) + { + if (sender is not Visual { DataContext: TerminalTabViewModel tab } + || DataContext is not MainWindowViewModel shell) + { + return; + } + + if (e.GetCurrentPoint((Visual)sender).Properties.PointerUpdateKind + is not PointerUpdateKind.MiddleButtonPressed) + { + return; + } + + // Handled, so the strip's ScrollViewer does not also take this as the start of a pan. + e.Handled = true; + + // Fire-and-forget, as the host sidebar's double-tap connect is: CloseTabCommand is asynchronous — + // it waits for the workspace to tear the session down — and an event handler has nowhere to await + // it. Its failures are the workspace's to report, not this strip's. + shell.CloseTabCommand.Execute(tab); + } } diff --git a/src/DodoSSH.Client.App/Views/TransfersScreen.axaml b/src/DodoSSH.Client.App/Views/TransfersScreen.axaml index 72cd513..27fab76 100644 --- a/src/DodoSSH.Client.App/Views/TransfersScreen.axaml +++ b/src/DodoSSH.Client.App/Views/TransfersScreen.axaml @@ -36,15 +36,32 @@ - + - + + + @@ -93,7 +102,7 @@ Foreground="{StaticResource Text}" VerticalAlignment="Center" /> + Text="One keychain, because the server grants access to your own and refuses the rest. Sharing is a later milestone." /> +