Public Access
Merge branch 'main' into the Android head
Main grew the screens the host-management plan called for — hosts, pins, snippets, logs, import, teams — plus the ObjectStore and Import projects behind two of them, and moved WindowsDeviceKeyStore into the desktop head's Platform folder. Five of those view models landed in a directory this branch had already moved, so they join the rest in DodoSSH.Client.Shell: git spotted the rename and put them there, and the namespaces followed. Shell picks up ObjectStore and Import as a result, which the Android head then gets transitively and will use neither of at first — scoped storage means there is no ~/.ssh/config to import, and file transfer is out of its first scope. Desktop suites green at 155 and 64.
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:DodoSSH.Client.Shell.ViewModels"
|
||||
x:Class="DodoSSH.Client.App.Views.SnippetsScreen"
|
||||
x:DataType="vm:SnippetsViewModel">
|
||||
|
||||
<!--
|
||||
Commands somebody has saved, and how to get one into a terminal.
|
||||
|
||||
The list and the writing belong to the vault, as every other item kind's do; this screen is the filter,
|
||||
the editor and the insert over the top. See SnippetsViewModel.
|
||||
|
||||
The two buttons at the bottom right are the whole safety design, and their wording is load-bearing.
|
||||
A terminal is one input stream with no notion of being at a prompt — the remote may be inside vi, or at
|
||||
a sudo password prompt with echo off — so this application cannot say "run this command", only "type
|
||||
this into whatever is there". RUN appears solely for a snippet whose own flag says it runs, which makes
|
||||
that a decision taken once while writing it rather than a button beside every one of them.
|
||||
-->
|
||||
|
||||
<Grid ColumnDefinitions="*,300">
|
||||
|
||||
<Grid Grid.Column="0" RowDefinitions="Auto,*,Auto">
|
||||
|
||||
<Border Grid.Row="0" Padding="14,0" Height="44"
|
||||
BorderBrush="{StaticResource Border}" BorderThickness="0,0,0,1">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" VerticalAlignment="Center">
|
||||
<TextBlock Grid.Column="0" Classes="mono" Text="SNIPPETS" FontSize="11"
|
||||
FontWeight="SemiBold" LetterSpacing="1" Foreground="{StaticResource Text}"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="1" Classes="mono" Text="{Binding Status}" FontSize="9.5"
|
||||
Foreground="{StaticResource TextFaint}" Margin="10,0,0,0"
|
||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
||||
|
||||
<!--
|
||||
The command is searched as well as the name: half of what anybody remembers about a saved
|
||||
command is a word that was inside it.
|
||||
-->
|
||||
<TextBox Grid.Column="2" x:Name="SnippetFilter" Text="{Binding Filter}" Width="240"
|
||||
PlaceholderText="filter by name or command" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<ListBox Grid.Row="1" x:Name="SnippetList" Focusable="True"
|
||||
ItemsSource="{Binding Visible}"
|
||||
SelectedItem="{Binding Selected}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:SnippetRowViewModel">
|
||||
<Grid ColumnDefinitions="2,*" Margin="0,7,14,7">
|
||||
<Border Grid.Column="0" Classes="rowmark" />
|
||||
<StackPanel Grid.Column="1" Margin="12,0,0,0" Spacing="2">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<TextBlock Classes="mono" Text="{Binding Label}" FontSize="11" FontWeight="Medium"
|
||||
Foreground="{StaticResource Text}" TextTrimming="CharacterEllipsis" />
|
||||
<!--
|
||||
The flag, where the decision is made. A snippet that presses Enter for you is not the
|
||||
same kind of thing as one that does not, and the list is where somebody chooses between
|
||||
them.
|
||||
-->
|
||||
<Border Classes="chip warn" Padding="4,0" IsVisible="{Binding RunsOnInsert}">
|
||||
<TextBlock Text="runs immediately" FontSize="8.5" />
|
||||
</Border>
|
||||
<Border Classes="chip warn" Padding="4,0"
|
||||
IsVisible="{Binding Badge, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
||||
<TextBlock Text="{Binding Badge}" FontSize="8.5" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
<!--
|
||||
Newlines shown as ⏎ rather than dropped. A three-line snippet flattened into one run of
|
||||
text reads as a single command, which is the thing being decided about on this row.
|
||||
-->
|
||||
<TextBlock Classes="mono" Text="{Binding Preview}" FontSize="9.5"
|
||||
Foreground="{StaticResource TextFaint}" TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<TextBlock Grid.Row="1" Classes="hint" Text="{Binding EmptyMessage}" FontSize="11"
|
||||
Margin="24" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
TextAlignment="Center" MaxWidth="360"
|
||||
IsVisible="{Binding !HasVisible}" />
|
||||
|
||||
<Border Grid.Row="2" Padding="14,8" BorderBrush="{StaticResource BorderSubtle}"
|
||||
BorderThickness="0,1,0,0">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button Classes="ghost" Content="+ NEW SNIPPET" Command="{Binding NewCommand}" />
|
||||
<Button Classes="ghost" Content="EDIT" Command="{Binding EditCommand}"
|
||||
IsEnabled="{Binding HasSelection}" />
|
||||
<Button Classes="ghost" Content="DELETE" Command="{Binding DeleteCommand}"
|
||||
IsEnabled="{Binding HasSelection}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Border Grid.Column="1" Background="{StaticResource Sidebar}"
|
||||
BorderBrush="{StaticResource Border}" BorderThickness="1,0,0,0">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="14,16" Spacing="8">
|
||||
|
||||
<!-- ============ The editor ============ -->
|
||||
<StackPanel Spacing="6" IsVisible="{Binding IsEditing}">
|
||||
<TextBox Text="{Binding EditorLabel}" PlaceholderText="name" />
|
||||
<!--
|
||||
Stored exactly as typed — no trimming, no newline normalisation. A here-document's terminator
|
||||
has to arrive on a line of its own, and tidying the trailing newline off it leaves the shell
|
||||
waiting for one that never comes.
|
||||
-->
|
||||
<TextBox Text="{Binding EditorCommand}" PlaceholderText="the command" AcceptsReturn="True"
|
||||
Height="140" TextWrapping="NoWrap" FontFamily="{StaticResource MonoFont}"
|
||||
FontSize="11" />
|
||||
<TextBox Text="{Binding EditorNotes}" PlaceholderText="notes" AcceptsReturn="True"
|
||||
Height="48" TextWrapping="Wrap" />
|
||||
<CheckBox IsChecked="{Binding EditorRunsOnInsert}"
|
||||
Content="Press Enter after inserting this" />
|
||||
<TextBlock Classes="hint" FontSize="10" TextWrapping="Wrap"
|
||||
Text="Off means the command is typed at the prompt and waits for you. That single Enter is the only thing standing between a saved command and a running one, so leave it off unless you meant it." />
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button Classes="accent" Content="SAVE" Command="{Binding SaveCommand}" />
|
||||
<Button Classes="ghost" Content="CANCEL" Command="{Binding CancelCommand}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- ============ The selected snippet ============ -->
|
||||
<StackPanel Spacing="6" IsVisible="{Binding !IsEditing}">
|
||||
|
||||
<TextBlock Classes="hint" FontSize="11"
|
||||
Text="Choose a snippet to see it in full and put it into a terminal."
|
||||
IsVisible="{Binding !HasSelection}" />
|
||||
|
||||
<StackPanel Spacing="6" IsVisible="{Binding HasSelection}">
|
||||
<TextBlock Classes="mono" Text="{Binding Selected.Label}" FontSize="12"
|
||||
FontWeight="SemiBold" Foreground="{StaticResource Text}" TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock Classes="label" Text="COMMAND" Margin="0,10,0,4" />
|
||||
<Border Background="{StaticResource Raised}" BorderBrush="{StaticResource Border}"
|
||||
BorderThickness="1" CornerRadius="4" Padding="8">
|
||||
<SelectableTextBlock Classes="mono" Text="{Binding Selected.Snippet.Command}"
|
||||
FontSize="9.5" Foreground="{StaticResource TextDim}"
|
||||
TextWrapping="Wrap" />
|
||||
</Border>
|
||||
|
||||
<TextBlock Classes="mono" Text="{Binding Selected.Snippet.Notes}" FontSize="10"
|
||||
Foreground="{StaticResource TextFaint}" TextWrapping="Wrap" Margin="0,6,0,0"
|
||||
IsVisible="{Binding Selected.Snippet.Notes, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
||||
|
||||
<!--
|
||||
The button names the tab it will type into. This screen is not the terminal — the strip
|
||||
above it is — so "INSERT" alone would leave somebody working out which of six open tabs is
|
||||
about to receive a command, at the moment that is worst to be wrong about.
|
||||
-->
|
||||
<Button Classes="accent" Content="{Binding InsertLabel}" Margin="0,14,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
Command="{Binding InsertCommand}" IsEnabled="{Binding CanInsert}"
|
||||
ToolTip.Tip="Types the command at the prompt and stops. Nothing runs until you press Enter there." />
|
||||
|
||||
<Button Classes="danger" Content="{Binding RunLabel}" HorizontalAlignment="Left"
|
||||
Command="{Binding RunCommand}"
|
||||
IsVisible="{Binding SelectionRuns}" IsEnabled="{Binding CanInsert}"
|
||||
ToolTip.Tip="Types the command and presses Enter. Offered because this snippet is marked as one that runs." />
|
||||
|
||||
<TextBlock Classes="hint" FontSize="10" TextWrapping="Wrap" Margin="0,10,0,0"
|
||||
Text="Whatever is in the terminal receives this. Nothing here can tell whether that is a shell prompt, an editor, or a password prompt with the echo off — so check the tab before you insert." />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
Reference in New Issue
Block a user