Public Access
469 lines
28 KiB
XML
469 lines
28 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.Android.Views"
|
||
x:Class="DodoSSH.Client.Android.Views.TerminalScreen"
|
||
x:DataType="vm:MainWindowViewModel"
|
||
Background="{StaticResource Canvas}">
|
||
|
||
<!--
|
||
Design 03 — TERMINAL.
|
||
|
||
One WebView for every session, exactly as on the desktop and for the same reason: each session is a pane
|
||
inside the one page, so switching tabs is a single frame rather than a second browser. On a phone the
|
||
argument is stronger — a WebView per tab would be several hundred megabytes on a device that will
|
||
reclaim them.
|
||
|
||
The tab strip is horizontal-scrolling rather than wrapping. Wrapping would reflow the terminal every
|
||
time a tab opened, which is the one thing a terminal must not do while output is arriving.
|
||
|
||
── the screen a shell gets ───────────────────────────────────────────────────────────────────────────
|
||
A connected phone shows one bar and then the terminal. The vault header, the shells strip and the
|
||
three-entry bottom bar are all collapsed by PhoneShell while a shell is showing, and what replaces them
|
||
is the row below: back, the sessions, and the way to open another one.
|
||
|
||
With nothing running it is only the header and the strip. This surface is then the connect page below —
|
||
a box and the machines connected to before — which is not what the screen was being cleared for, and is
|
||
the one screen reachable by closing your last tab, so the nav stays. See PhoneShell.RefreshChrome.
|
||
|
||
That is a trade, and the thing bought is the only one a terminal really wants. At 360dp the chrome this
|
||
screen used to sit inside came to 254 pixels of a roughly 780-pixel display — a third of it — and every
|
||
one of those rows was about somewhere the user was not. What is given up is the bottom bar's one-tap
|
||
reach to Hosts, Keychain and MORE; back and the + between them lead to all of it, and the system back
|
||
gesture does the same thing the arrow does.
|
||
-->
|
||
|
||
<Panel>
|
||
|
||
<Grid RowDefinitions="Auto,*,Auto">
|
||
|
||
<!-- ============ the bar ============ -->
|
||
<!--
|
||
Everything the phone draws above a shell. It is a bar rather than a strip because it now carries the
|
||
two controls the collapsed chrome took with it, one at each end, with the sessions between them.
|
||
|
||
Both are outside the ScrollViewer deliberately. They are the way out of this surface and the way to
|
||
another host, and a tenth tab must not be able to push either of them off the right-hand edge.
|
||
|
||
35 tall, a third off the 52 it opened at. Every height inside it came down with it — the icon squares
|
||
to 34, the pills to 30 — because a bar that shrank around controls that did not would only have moved
|
||
the clipping somewhere harder to see.
|
||
-->
|
||
<!-- DeepChrome rather than Chrome, since v5: this bar is what the vault header, the strip and the
|
||
bottom bar collapse into while a shell is showing, so it takes their surface along with their
|
||
job — see the remark on the header in PhoneShell.axaml. -->
|
||
<Border Grid.Row="0" Height="35" Background="{StaticResource DeepChrome}"
|
||
BorderBrush="{StaticResource Border}" BorderThickness="0,0,0,1">
|
||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||
|
||
<!--
|
||
Back, and it goes to the page this terminal was opened over rather than to Hosts by name. The
|
||
system back gesture already does exactly that — see PhoneShell.axaml.cs — and an arrow that
|
||
landed somewhere else would be the second of two answers to one question.
|
||
|
||
Height overridden and width left alone. Button.icon is a 44 square, which is taller than this bar;
|
||
the 44 that matters is the horizontal one, since nothing in a row of this shape is hard to hit
|
||
above or below.
|
||
-->
|
||
<Button Grid.Column="0" Classes="icon" Content="←" Height="34" Margin="4,0,0,0"
|
||
Command="{Binding ShowScreenCommand}" CommandParameter="{Binding Screen}" />
|
||
|
||
<!--
|
||
v2 draws these as pills rather than as a segmented strip, so the row is transparent and each
|
||
session carries its own outline. The close cross moved inside the pill with the name, which is
|
||
what makes it read as one object you can dismiss rather than as two adjacent targets.
|
||
-->
|
||
<ScrollViewer Grid.Column="1" HorizontalScrollBarVisibility="Auto"
|
||
VerticalScrollBarVisibility="Disabled">
|
||
<ItemsControl ItemsSource="{Binding Tabs}" Margin="6,0" VerticalAlignment="Center">
|
||
<ItemsControl.ItemsPanel>
|
||
<ItemsPanelTemplate><StackPanel Orientation="Horizontal" Spacing="6" /></ItemsPanelTemplate>
|
||
</ItemsControl.ItemsPanel>
|
||
<ItemsControl.ItemTemplate>
|
||
<DataTemplate x:DataType="vm:TerminalTabViewModel">
|
||
<!--
|
||
30 tall, in a bar of 35. It was 44, and the argument for 44 was that a pill you only
|
||
select can be chip-sized while one containing the control that ends a shell cannot — the
|
||
close cross is the one control on this head that is both destructive and has neither
|
||
confirmation nor undo. That argument is now carried by width rather than by height: the
|
||
cross keeps its full 44-pixel column, and what it lost is 14 pixels of vertical slack in a
|
||
row where nothing sits above or below it to be hit by mistake.
|
||
-->
|
||
<Border Background="{StaticResource Panel}" BorderBrush="{StaticResource BorderMid}"
|
||
BorderThickness="1" CornerRadius="10" Height="30">
|
||
<StackPanel Orientation="Horizontal">
|
||
<Button Classes="row" MinHeight="28" Padding="11,0" CornerRadius="10"
|
||
VerticalContentAlignment="Center"
|
||
Command="{Binding $parent[views:TerminalScreen].((vm:MainWindowViewModel)DataContext).SelectTabCommand}"
|
||
CommandParameter="{Binding}">
|
||
<StackPanel Orientation="Horizontal" Spacing="7" VerticalAlignment="Center">
|
||
<!-- Green only while there is a shell behind it; see the same dot in PhoneShell. -->
|
||
<Ellipse Classes="dot" Classes.live="{Binding IsLive}" Width="6" Height="6"
|
||
VerticalAlignment="Center" />
|
||
<TextBlock Classes="mono" FontSize="12" FontWeight="SemiBold"
|
||
Text="{Binding Label}" />
|
||
</StackPanel>
|
||
</Button>
|
||
<!--
|
||
The close cross is inside the tab, which the plan calls out: a strip-level close would
|
||
act on whichever tab happened to be selected, and on a phone that is a mis-tap away from
|
||
killing the wrong shell.
|
||
|
||
The hairline down its left edge is not decoration. The two targets are flush inside one
|
||
pill, so without a visible seam there is nothing telling a thumb where "switch to this
|
||
shell" stops and "end it" starts.
|
||
|
||
Both alignments are stated, and the vertical one is not decoration either: Button.row
|
||
sets HorizontalContentAlignment and says nothing about the other axis, so the cross
|
||
was sitting against the top of its own column rather than in the middle of the pill.
|
||
It reads as a misprint, which for the control that ends a session is the wrong thing
|
||
to look like.
|
||
-->
|
||
<Button Classes="row" MinHeight="28" Width="44" Padding="0" CornerRadius="0,10,10,0"
|
||
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
|
||
BorderBrush="{StaticResource BorderMid}" BorderThickness="1,0,0,0"
|
||
Command="{Binding $parent[views:TerminalScreen].((vm:MainWindowViewModel)DataContext).CloseTabCommand}"
|
||
CommandParameter="{Binding}">
|
||
<TextBlock Text="×" Foreground="{StaticResource TextFaint}" FontSize="14"
|
||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||
TextAlignment="Center" />
|
||
</Button>
|
||
</StackPanel>
|
||
</Border>
|
||
</DataTemplate>
|
||
</ItemsControl.ItemTemplate>
|
||
</ItemsControl>
|
||
</ScrollViewer>
|
||
|
||
<!--
|
||
Another connection, and the three kinds this application can make. A menu rather than a straight
|
||
jump to Hosts because SFTP and S3 used to be two taps through the bottom bar's MORE, and the bar
|
||
is not on screen here — so the control that replaces it has to lead to all three or it has quietly
|
||
removed two of them.
|
||
|
||
The desktop's own + refuses a flyout on this reasoning, in TerminalTabs.axaml: a popup dropping
|
||
into the renderer's rectangle may or may not composite above a native child window, and that is
|
||
not a claim to make without a screenshot. It is answered here rather than dodged — this is a sheet
|
||
at the bottom of the screen and opening it collapses the renderer outright, exactly as the
|
||
palette does on the desktop. Nothing is drawn over the WebView.
|
||
|
||
Plain Button.icon, the same as the arrow across from it, rather than the accent variant. The two
|
||
are a matched pair at either end of one bar — one leaves this surface, one adds to it — and an
|
||
accented + would rank itself above the way out. The accent fill belongs to the floating + on
|
||
HOSTS, which is the only action on its screen; this one is not.
|
||
|
||
Click as well as Command, and the handler does one thing the command cannot: put the software
|
||
keyboard away. See OnConnectMenuRequested.
|
||
-->
|
||
<Button Grid.Column="2" Classes="icon" Content="+" Height="34" Margin="0,0,4,0"
|
||
Click="OnConnectMenuRequested"
|
||
Command="{Binding OpenConnectSheetCommand}" />
|
||
</Grid>
|
||
</Border>
|
||
|
||
<!-- ============ the renderer ============ -->
|
||
<Panel Grid.Row="1">
|
||
|
||
<!--
|
||
============ nothing open ============
|
||
|
||
Not an empty state any more, and that is what the bottom bar's word change is about: with no shell
|
||
running this surface is where a connection is made rather than a page apologising for having
|
||
nothing on it. Two things are offered, in the order somebody reaches for them.
|
||
|
||
A box, because the case this application had no answer for is the address somebody has just been
|
||
given. Everything else here starts from a keychain item, which is right for a machine used twice and
|
||
is several screens of work for one used once.
|
||
|
||
And the machines already connected to, under it, because that is the commoner half by far: the
|
||
thing most likely to be wanted is the thing that was wanted yesterday. They come out of the vault's
|
||
own connection log — see MainWindowViewModel.RecentConnections — so they survive a restart and
|
||
arrive on a new phone with the keychain, which a list kept in this process would not.
|
||
-->
|
||
<ScrollViewer IsVisible="{Binding !HasTabs}">
|
||
<StackPanel Margin="20,18,20,24" Spacing="10">
|
||
|
||
<TextBlock Classes="title" FontSize="13" Text="CONNECT TO A MACHINE" />
|
||
|
||
<!--
|
||
The manual target. One box in the grammar people already have — user@host, or user@host:port —
|
||
rather than three, because three boxes on a phone is three taps between three keyboards for
|
||
something that arrives as one string.
|
||
-->
|
||
<TextBox Classes="field" Text="{Binding Vault.ManualTarget}"
|
||
PlaceholderText="user@host or user@host:port">
|
||
<TextBox.KeyBindings>
|
||
<KeyBinding Gesture="Enter" Command="{Binding Vault.ConnectManuallyCommand}" />
|
||
</TextBox.KeyBindings>
|
||
</TextBox>
|
||
|
||
<!--
|
||
A typed password and nothing else. Offering the keychain's keys here would be a second binding
|
||
resolution beside the one the connect path already has, and a key is the reason to save the
|
||
machine as a host rather than dial it from a box.
|
||
|
||
Classes="secret" and not merely PasswordChar: the class is what also tells the software keyboard
|
||
this is not prose, which is the difference between dots on screen and dots on screen with the
|
||
password in the IME's dictionary. See Theme/Phone.axaml.
|
||
-->
|
||
<TextBox Classes="field secret" Text="{Binding Vault.ManualPassword}"
|
||
PlaceholderText="password">
|
||
<TextBox.KeyBindings>
|
||
<KeyBinding Gesture="Enter" Command="{Binding Vault.ConnectManuallyCommand}" />
|
||
</TextBox.KeyBindings>
|
||
</TextBox>
|
||
|
||
<Button Classes="primary" Content="CONNECT" Command="{Binding Vault.ConnectManuallyCommand}" />
|
||
|
||
<!--
|
||
Beside the box rather than on the shell's status line. What fails here is about what was typed —
|
||
a missing account, a port that is not a number — and a sentence about a text box belongs under
|
||
the text box.
|
||
-->
|
||
<TextBlock Classes="detail" TextWrapping="Wrap" Foreground="{StaticResource WarnText}"
|
||
Text="{Binding Vault.ManualStatus}"
|
||
IsVisible="{Binding Vault.ManualStatus, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
||
|
||
<TextBlock Classes="body"
|
||
Text="Nothing typed here is saved. A machine worth keeping belongs on HOSTS, where it can carry a key, a group's defaults and a name." />
|
||
|
||
<!-- ============ where you have been ============ -->
|
||
<StackPanel Spacing="2" Margin="0,14,0,0" IsVisible="{Binding HasRecentConnections}">
|
||
|
||
<TextBlock Classes="label" Text="RECENT" Margin="0,0,0,4" />
|
||
|
||
<ItemsControl ItemsSource="{Binding RecentConnections}">
|
||
<ItemsControl.ItemTemplate>
|
||
<DataTemplate x:DataType="vm:ConnectionLogRowViewModel">
|
||
<!--
|
||
Tapping one does not connect, and that is deliberate rather than a missing step. A row
|
||
that names a keychain host goes to that host with its connect bar — which is where its
|
||
key, or its password box, or its refusal lives — and one that names only an address goes
|
||
back into the box above, without the password, which was never stored. Either way what
|
||
happens next is visible before anything is dialled. See ConnectToRecentCommand.
|
||
-->
|
||
<Button Classes="row" MinHeight="52" Padding="12,8" CornerRadius="10"
|
||
Command="{Binding $parent[views:TerminalScreen].((vm:MainWindowViewModel)DataContext).ConnectToRecentCommand}"
|
||
CommandParameter="{Binding}">
|
||
<Grid ColumnDefinitions="*,Auto">
|
||
<StackPanel Grid.Column="0" Spacing="2" VerticalAlignment="Center">
|
||
<TextBlock Classes="mono" FontSize="13" FontWeight="SemiBold"
|
||
Text="{Binding HostLabel}" TextTrimming="CharacterEllipsis" />
|
||
<TextBlock Classes="detail" FontSize="10.5" Text="{Binding Address}"
|
||
TextTrimming="CharacterEllipsis" />
|
||
</StackPanel>
|
||
<TextBlock Grid.Column="1" Classes="detail" FontSize="10"
|
||
VerticalAlignment="Center" Text="{Binding Started}" />
|
||
</Grid>
|
||
</Button>
|
||
</DataTemplate>
|
||
</ItemsControl.ItemTemplate>
|
||
</ItemsControl>
|
||
</StackPanel>
|
||
|
||
<TextBlock Classes="body" Margin="0,10,0,0"
|
||
Text="A shell opened here keeps running while the app is in the background, and keeps running after the keychain is locked — a notification says so for as long as one is alive." />
|
||
|
||
</StackPanel>
|
||
</ScrollViewer>
|
||
|
||
<!--
|
||
A tab with no pane behind it: one still connecting, or one that was refused. The desktop head draws
|
||
the same thing in its own ConnectingCard, and it exists for the same reason — connecting does not
|
||
hold the application still any more, so there is a stretch in which a tab is selected and there is
|
||
nothing yet to render in it. A phone needs it more than a desktop does: mobile links are slower, and
|
||
the alternative is a black rectangle.
|
||
|
||
It carries the address, which is where that fact went when the connection line was folded into the
|
||
bar above. This is the moment it is worth reading — what is being dialled, before anything has
|
||
answered — and once a shell is open its own prompt says the same thing more accurately than a header
|
||
derived from the keychain ever did.
|
||
-->
|
||
<StackPanel IsVisible="{Binding IsConnectingShowing}" VerticalAlignment="Center" Margin="24"
|
||
Spacing="10">
|
||
<TextBlock Classes="title" FontSize="13" Text="{Binding SelectedTab.Label}" />
|
||
<TextBlock Classes="detail" FontSize="11" Foreground="{StaticResource TextDim}"
|
||
TextWrapping="Wrap" Text="{Binding SelectedTab.Address}" />
|
||
<TextBlock Classes="body" Text="{Binding SelectedTab.Status}" />
|
||
<Button Classes="row" MinHeight="44" Padding="14,0" HorizontalAlignment="Left"
|
||
Command="{Binding CloseTabCommand}" CommandParameter="{Binding SelectedTab}">
|
||
<TextBlock Classes="label" FontSize="9" Text="CLOSE THIS TAB" />
|
||
</Button>
|
||
</StackPanel>
|
||
|
||
<!--
|
||
Collapsed rather than merely covered when there is no pane to show. On Windows this control is a
|
||
native child view that composites above everything Avalonia draws, which is why the desktop head
|
||
hides it explicitly; whether Android's WebView does the same is recorded as unverified in
|
||
docs/android-port.md. Hiding it either way costs nothing and is correct under both answers.
|
||
|
||
IsTerminalShowing rather than HasTabs, which are no longer the same question: a tab that is still
|
||
connecting has no pane, and showing the renderer for it would show the previous session's output
|
||
under the name of a machine nothing has connected to yet. It is also what the connect sheet turns
|
||
off — see MainWindowViewModel.IsTerminalShowing.
|
||
|
||
v2 insets this behind a 14-pixel radius. Not done, and not an oversight: this is a native child view
|
||
composited above everything Avalonia draws, so a rounded Border behind it clips nothing — the
|
||
corners would stay square over a frame pretending otherwise. What the design was really after is
|
||
that the terminal reads as the lit surface, and that comes from its own background, which now is the
|
||
design's #171A26. See WebAssets/terminal.css.
|
||
-->
|
||
<NativeWebView x:Name="Renderer" IsVisible="{Binding IsTerminalShowing, FallbackValue=False}" />
|
||
</Panel>
|
||
|
||
<!-- ============ accessory keys ============ -->
|
||
<!--
|
||
The row every Android SSH client ships, and the reason is not preference: a software keyboard has no
|
||
Ctrl, no Esc, no Tab and no arrows, and without them a phone cannot interrupt a process, complete a
|
||
path, or reach the previous command. Ctrl and Alt latch — pressed once they apply to the next key and
|
||
then release, because holding a modifier while typing is not possible one-thumbed.
|
||
-->
|
||
<!-- Only with a pane to type into: the keys send bytes at a session, and a connecting tab has none. -->
|
||
<!--
|
||
33 tall rather than 50, a third off it like the bar at the top, with the keys inside coming down from
|
||
38 to 30 for the reason the pills did: a row that shrank around its own contents would clip them.
|
||
|
||
The five pixels above it are a gap and not a border. The renderer is a native child view, so nothing
|
||
Avalonia draws can sit on top of it — a hairline between the two would have to be a row of its own —
|
||
and a terminal whose last line of output is flush against a row of grey keys reads as one surface
|
||
that has gone wrong rather than as two that are different things.
|
||
-->
|
||
<Border Grid.Row="2" IsVisible="{Binding IsTerminalShowing}" Height="33" Margin="0,5,0,0">
|
||
<Grid ColumnDefinitions="*,Auto">
|
||
|
||
<ScrollViewer Grid.Column="0" HorizontalScrollBarVisibility="Auto"
|
||
VerticalScrollBarVisibility="Disabled">
|
||
<StackPanel x:Name="AccessoryKeys" Orientation="Horizontal" Spacing="5" Margin="12,0"
|
||
VerticalAlignment="Center" />
|
||
</ScrollViewer>
|
||
|
||
<!--
|
||
The text-size buttons, pinned at this row's right-hand end rather than scrolling with the keys
|
||
beside them.
|
||
|
||
They used to have a row of their own above the terminal, on the argument that the accessory row
|
||
scrolls and these two must never be off-screen — a terminal too small to read is exactly the state
|
||
in which hunting for the control that fixes it is worst. That argument is answered rather than
|
||
abandoned: outside the ScrollViewer they cannot scroll away, and the row they had costs 36 pixels
|
||
on a surface this change exists to give back.
|
||
|
||
A phone cannot press Ctrl+plus. The desktop head has that chord and needs no buttons; this head
|
||
has no keyboard to press it with, which is why the two heads differ here and nowhere else in this
|
||
screen.
|
||
|
||
Disabled at the ends rather than clamping silently. A button that keeps accepting taps and does
|
||
nothing reads as the terminal having stopped responding, which is the one thing this screen must
|
||
never look like.
|
||
|
||
◆ Focusable="False", for the reason the accessory keys carry in code — see
|
||
TerminalScreen.axaml.cs. Every button on this row sits over a terminal somebody is typing into,
|
||
and an ordinary one takes Avalonia's focus off the NativeWebView on tap, which makes the package
|
||
resign the page's focus. Making the text smaller must not cost the keyboard.
|
||
-->
|
||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="5" Margin="8,0,12,0"
|
||
VerticalAlignment="Center">
|
||
<Border Width="1" Height="18" Background="{StaticResource Border}" Margin="0,0,3,0"
|
||
VerticalAlignment="Center" />
|
||
|
||
<Button Classes="row" MinHeight="30" Height="30" MinWidth="40" Padding="0" CornerRadius="10"
|
||
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
|
||
Background="{StaticResource Panel}" BorderBrush="{StaticResource BorderMid}"
|
||
BorderThickness="1" Focusable="False"
|
||
Command="{Binding ShrinkTerminalFontCommand}"
|
||
IsEnabled="{Binding CanShrinkTerminalFont}">
|
||
<TextBlock Classes="mono" FontSize="13" Text="A−" />
|
||
</Button>
|
||
|
||
<Button Classes="row" MinHeight="30" Height="30" MinWidth="40" Padding="0" CornerRadius="10"
|
||
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
|
||
Background="{StaticResource Panel}" BorderBrush="{StaticResource BorderMid}"
|
||
BorderThickness="1" Focusable="False"
|
||
Command="{Binding EnlargeTerminalFontCommand}"
|
||
IsEnabled="{Binding CanEnlargeTerminalFont}">
|
||
<TextBlock Classes="mono" FontSize="15" Text="A+" />
|
||
</Button>
|
||
</StackPanel>
|
||
</Grid>
|
||
</Border>
|
||
|
||
</Grid>
|
||
|
||
<!-- ============ the connect sheet ============ -->
|
||
<!--
|
||
Follows the add sheet on HostsScreen, which follows HostKeySheet: a scrim, a bottom-aligned panel with
|
||
the top two corners rounded, and a grab handle that is decoration. Dismissible, like the add sheet and
|
||
unlike the host-key one — "which kind of connection" has no wrong answer and no answer at all is one
|
||
of them.
|
||
|
||
It lives here rather than in PhoneShell for the reason the add sheet lives in its own screen: nothing
|
||
but this surface raises it. The scrim reaching only the screen area is the full display while a shell
|
||
is showing, because the bottom bar is collapsed for it. Raised from the connect page it is not, and
|
||
that lands on the add sheet's own answer: the three destinations under the scrim stay tappable, and for
|
||
a menu of places to connect from, navigating away is a perfectly good way to decide not to.
|
||
|
||
Every row navigates away from the terminal. That is not a side effect of the menu, it is the menu:
|
||
each of the three destinations is a picker, and the shell they open lands back on this surface as a
|
||
new tab in the bar above.
|
||
-->
|
||
<Panel IsVisible="{Binding IsConnectSheetOpen}">
|
||
|
||
<!--
|
||
Declared before the sheet so the sheet draws over it: a Panel stacks its children in declaration
|
||
order. See the .scrim style for why a tap on it must not light anything up.
|
||
-->
|
||
<Button Classes="scrim" Command="{Binding CloseConnectSheetCommand}" />
|
||
|
||
<Border VerticalAlignment="Bottom" Background="{StaticResource Panel}"
|
||
BorderBrush="{StaticResource BorderMid}" BorderThickness="0,1,0,0"
|
||
CornerRadius="22,22,0,0" Padding="20,18,20,16">
|
||
<StackPanel Spacing="0">
|
||
|
||
<Border Width="38" Height="4" CornerRadius="2" Background="{StaticResource BorderMid}"
|
||
HorizontalAlignment="Center" Margin="0,0,0,16" />
|
||
|
||
<TextBlock Classes="title" Text="CONNECT" FontSize="13" />
|
||
|
||
<Button Classes="row" Margin="0,10,0,0" Command="{Binding ShowScreenCommand}"
|
||
CommandParameter="{x:Static vm:ShellScreen.Hosts}">
|
||
<StackPanel Spacing="3" VerticalAlignment="Center">
|
||
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="Connect" />
|
||
<TextBlock Classes="detail" FontSize="10.5"
|
||
Text="Another shell, on this host or any other in the keychain." />
|
||
</StackPanel>
|
||
</Button>
|
||
|
||
<!--
|
||
These two go through ShowFiles rather than ShowScreen, as the MORE hub's own rows do: one screen
|
||
over one view model, and which kind of remote it offers is the thing being chosen. It can refuse
|
||
— there is a single transfer session behind both — and refusing lands on the screen the open one
|
||
belongs to with a sentence saying why, which is a better place to read it than a sheet that has
|
||
just closed.
|
||
-->
|
||
<Button Classes="row" Command="{Binding ShowFilesCommand}"
|
||
CommandParameter="{x:Static vm:RemoteKind.Host}">
|
||
<StackPanel Spacing="3" VerticalAlignment="Center">
|
||
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="Connect via SFTP" />
|
||
<TextBlock Classes="detail" FontSize="10.5" Text="Browse a host's files." />
|
||
</StackPanel>
|
||
</Button>
|
||
|
||
<Button Classes="row" Command="{Binding ShowFilesCommand}"
|
||
CommandParameter="{x:Static vm:RemoteKind.Bucket}">
|
||
<StackPanel Spacing="3" VerticalAlignment="Center">
|
||
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="Connect via S3" />
|
||
<TextBlock Classes="detail" FontSize="10.5"
|
||
Text="Objects in an S3-compatible bucket from the keychain." />
|
||
</StackPanel>
|
||
</Button>
|
||
|
||
<Button Classes="secondary" Content="CANCEL" Margin="0,12,0,0"
|
||
Command="{Binding CloseConnectSheetCommand}" />
|
||
|
||
</StackPanel>
|
||
</Border>
|
||
</Panel>
|
||
|
||
</Panel>
|
||
|
||
</UserControl>
|