Public Access
257 lines
14 KiB
XML
257 lines
14 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.SnippetsScreen"
|
|
x:DataType="vm:SnippetsViewModel"
|
|
Background="{StaticResource Canvas}">
|
|
|
|
<!--
|
|
Design 05 — SNIPPETS, under MORE.
|
|
|
|
Two departures from the mock-up, both because of what a snippet actually is here.
|
|
|
|
The design puts a ▶ Run button on every card. There are two operations rather than one — type the
|
|
command at the prompt, and type it *and* press Enter — and which of them a snippet allows is a property
|
|
of the snippet, set by a checkbox in its editor. ◆ That checkbox is the whole safety property: a
|
|
snippet that does not run on insert leaves the command sitting at the prompt for a human to read before
|
|
committing to it. Collapsing the two into one button called Run would throw that away, so the actions
|
|
are a bar over the bottom of the list, they name the terminal they will type into, and RUN only appears
|
|
for a snippet that is marked as running.
|
|
|
|
And the design's per-card footer — the hosts it ran on, the time, `exit 0` — is not drawn. Nothing here
|
|
records where a snippet was run, when, or what it returned; a snippet is a saved string, and the shell
|
|
it is typed into never reports back. See docs/design-import-gaps.md.
|
|
-->
|
|
|
|
<Grid RowDefinitions="Auto,Auto,Auto,*,Auto">
|
|
|
|
<!-- ============ header ============ -->
|
|
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto" Height="56" Margin="8,0">
|
|
<!--
|
|
◆ Gone on a wide surface, where the rail is how you leave and the hub it points at is not drawn at
|
|
all. An arrow to a screen nothing else can reach would be the one control on this header that leads
|
|
somewhere the layout has removed. See PhoneShell.IsWide.
|
|
-->
|
|
<Button Grid.Column="0" Classes="icon" Content="←"
|
|
IsVisible="{Binding !$parent[views:PhoneShell].IsWide}"
|
|
Command="{Binding $parent[views:PhoneShell].((vm:MainWindowViewModel)DataContext).ShowScreenCommand}"
|
|
CommandParameter="{x:Static vm:ShellScreen.More}" />
|
|
<TextBlock Grid.Column="1" Classes="heading" Text="Snippets" Margin="4,0" />
|
|
<Button Grid.Column="2" Classes="icon accent" Content="+" Command="{Binding NewCommand}" />
|
|
</Grid>
|
|
|
|
<!-- ============ filter ============ -->
|
|
<!-- Matches the command text as well as the name: the workflow is "the one that vacuums", not its label. -->
|
|
<TextBox Grid.Row="1" Classes="field" Margin="14,2,14,8" Text="{Binding Filter}"
|
|
PlaceholderText="search snippets" IsVisible="{Binding HasSnippets}" />
|
|
|
|
<TextBlock Grid.Row="2" Classes="detail" Margin="18,0,18,6" TextWrapping="Wrap"
|
|
Text="{Binding Status}" />
|
|
|
|
<!-- ============ the editor ============ -->
|
|
<!--
|
|
In place, above the list, rather than as a screen of its own — the same decision the keychain's
|
|
delete confirmation makes and for the same reason: a form stacked over the list hides what it is
|
|
about.
|
|
-->
|
|
<Border Grid.Row="3" Classes="card" Margin="12,0" IsVisible="{Binding IsEditing}"
|
|
VerticalAlignment="Top">
|
|
<ScrollViewer>
|
|
<StackPanel Spacing="10">
|
|
<TextBlock Classes="label" Text="NEW SNIPPET" IsVisible="{Binding IsCreating}" />
|
|
<TextBlock Classes="label" Text="EDIT SNIPPET" IsVisible="{Binding !IsCreating}" />
|
|
|
|
<TextBox Classes="field" Text="{Binding EditorLabel}" PlaceholderText="name" />
|
|
|
|
<!--
|
|
◆ Which vault a *new* snippet is filed into. Hidden for an existing one — its vault is not a
|
|
field of this form, and changing it is MOVE below — and hidden entirely where there is only one
|
|
vault to choose between, which is where most people stay.
|
|
-->
|
|
<StackPanel Spacing="6" IsVisible="{Binding ShowsEditorVaultChoice}">
|
|
<TextBlock Classes="label" Text="VAULT" />
|
|
<ComboBox HorizontalAlignment="Stretch" MinHeight="44"
|
|
ItemsSource="{Binding EditorVaultChoices}"
|
|
SelectedItem="{Binding EditorSelectedVault}">
|
|
<ComboBox.ItemTemplate>
|
|
<DataTemplate x:DataType="vm:VaultChoiceViewModel">
|
|
<TextBlock Classes="mono" FontSize="12" Text="{Binding Display}" />
|
|
</DataTemplate>
|
|
</ComboBox.ItemTemplate>
|
|
</ComboBox>
|
|
<TextBlock Classes="body"
|
|
Text="A shared vault means everybody holding its key can read this command and insert it into their own terminals." />
|
|
</StackPanel>
|
|
|
|
<TextBox Classes="field" Text="{Binding EditorCommand}" PlaceholderText="command"
|
|
Height="120" AcceptsReturn="True" TextWrapping="Wrap"
|
|
VerticalContentAlignment="Top" Padding="14,10" />
|
|
|
|
<TextBox Classes="field" Text="{Binding EditorNotes}" PlaceholderText="notes" />
|
|
|
|
<!--
|
|
◆ The checkbox the whole screen is arranged around, with the paragraph that says why leaving it
|
|
off is the safe default. It is not a preference — it is the difference between a command a human
|
|
reads before committing to it and one that has already run.
|
|
-->
|
|
<CheckBox IsChecked="{Binding EditorRunsOnInsert}" MinHeight="44">
|
|
<TextBlock Classes="mono" FontSize="11.5" Text="Press Enter after inserting this"
|
|
TextWrapping="Wrap" />
|
|
</CheckBox>
|
|
<TextBlock Classes="body"
|
|
Text="Left off, inserting types the command at the prompt and stops, so it can be read and edited before it runs. Nothing here can tell whether the terminal is even at a prompt, which is why that is the default." />
|
|
|
|
<Grid ColumnDefinitions="*,8,*">
|
|
<Button Grid.Column="0" Classes="primary" Height="44" Content="SAVE"
|
|
Command="{Binding SaveCommand}" />
|
|
<Button Grid.Column="2" Classes="secondary" Height="44" Content="CANCEL"
|
|
Command="{Binding CancelCommand}" />
|
|
</Grid>
|
|
</StackPanel>
|
|
</ScrollViewer>
|
|
</Border>
|
|
|
|
<!-- ============ the list ============ -->
|
|
<Panel Grid.Row="3" IsVisible="{Binding !IsEditing}">
|
|
|
|
<!-- Written by the view model, and it says what a snippet is for rather than that there are none. -->
|
|
<TextBlock Classes="body" IsVisible="{Binding !HasVisible}" Margin="24,20" VerticalAlignment="Top"
|
|
Text="{Binding EmptyMessage}" />
|
|
|
|
<ListBox ItemsSource="{Binding Visible}" SelectedItem="{Binding Selected}"
|
|
Background="Transparent" BorderThickness="0" IsVisible="{Binding HasVisible}">
|
|
<ListBox.Styles>
|
|
<Style Selector="ListBoxItem">
|
|
<Setter Property="Padding" Value="0" />
|
|
<Setter Property="MinHeight" Value="0" />
|
|
</Style>
|
|
</ListBox.Styles>
|
|
<ListBox.ItemTemplate>
|
|
<DataTemplate x:DataType="vm:SnippetRowViewModel">
|
|
<Border Classes="card" Margin="12,5">
|
|
<StackPanel Spacing="9">
|
|
<StackPanel Orientation="Horizontal" Spacing="7">
|
|
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="{Binding Label}"
|
|
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
|
|
|
<!-- ◆ The chip that says this one does not wait to be read. -->
|
|
<Border Classes="tag" IsVisible="{Binding RunsOnInsert}"
|
|
Background="{StaticResource WarnWash}">
|
|
<TextBlock Text="RUNS" Foreground="{StaticResource Warn}" />
|
|
</Border>
|
|
|
|
<Border Classes="tag outline"
|
|
IsVisible="{Binding Badge, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
|
<TextBlock Text="{Binding Badge}" />
|
|
</Border>
|
|
|
|
<!--
|
|
◆ Which vault this one is in, drawn only where there is more than one to be in. A shared
|
|
snippet is a command the rest of a team can read and insert into their own terminals,
|
|
and this card is the only place that fact appears before somebody taps EDIT.
|
|
-->
|
|
<Border Classes="tag outline" IsVisible="{Binding HasVaultBadge}">
|
|
<TextBlock Text="{Binding VaultBadge}" />
|
|
</Border>
|
|
</StackPanel>
|
|
|
|
<!-- The command, on the surface every block of monospace in this design is drawn on.
|
|
No radius override any more: it diverged from Border.output's own 14 under v2 for a
|
|
smaller card-nested block, and now that the style's own value has moved to 12 there is
|
|
no gap left worth a second number for — it takes the class's, like every other output
|
|
block on this head. -->
|
|
<Border Classes="output" Padding="11,9">
|
|
<TextBlock Classes="mono" FontSize="11" Text="{Binding Preview}"
|
|
Foreground="{StaticResource Text}" TextTrimming="CharacterEllipsis" />
|
|
</Border>
|
|
</StackPanel>
|
|
</Border>
|
|
</DataTemplate>
|
|
</ListBox.ItemTemplate>
|
|
</ListBox>
|
|
</Panel>
|
|
|
|
<!-- ============ what to do with the chosen one ============ -->
|
|
<!--
|
|
Raised over the list, as the hosts screen raises its connect bar, and for the same reason: there is no
|
|
second column to put it in. Both buttons name the terminal — "TYPE INTO pg-primary" — because on a
|
|
phone the tab strip may be scrolled away and "whatever is in the terminal receives this" is not a
|
|
sentence anybody should have to guess the subject of.
|
|
|
|
DeepChrome rather than Chrome, since v5: this bar is chrome furniture in the same sense the bottom
|
|
nav bar it sits above is — a raised strip of frame rather than a pane of content — and joins the same
|
|
DeepChrome decision PhoneShell's header, strip and bar made. See the remark there.
|
|
-->
|
|
<Border Grid.Row="4" IsVisible="{Binding HasSelection}" Background="{StaticResource DeepChrome}"
|
|
BorderBrush="{StaticResource Border}" BorderThickness="0,1,0,0" Padding="14,12"
|
|
IsEnabled="{Binding !IsEditing}">
|
|
<StackPanel Spacing="9">
|
|
|
|
<StackPanel Spacing="9" IsVisible="{Binding ShowsSelectionActions}">
|
|
<Button Classes="primary" Content="{Binding InsertLabel}" Command="{Binding InsertCommand}"
|
|
IsEnabled="{Binding CanInsert}" />
|
|
|
|
<!-- Only for a snippet marked as running. For every other one there is nothing this could mean. -->
|
|
<Button Classes="secondary" Height="44" Content="{Binding RunLabel}"
|
|
Command="{Binding RunCommand}" IsVisible="{Binding SelectionRuns}"
|
|
IsEnabled="{Binding CanInsert}" />
|
|
|
|
<Grid ColumnDefinitions="*,8,*">
|
|
<Button Grid.Column="0" Classes="secondary" Height="44" Content="EDIT"
|
|
Command="{Binding EditCommand}" />
|
|
<!--
|
|
◆ Sharing the snippet. Beside EDIT rather than inside it, which is the line the hosts screen
|
|
draws too: the two vaults are encrypted under different keys, so this is a re-seal into one and
|
|
a tombstone in the other — nothing a SAVE could do. It shows only where there is somewhere to
|
|
move to; see SnippetsViewModel.CanMove.
|
|
-->
|
|
<Button Grid.Column="2" Classes="secondary" Height="44" Content="MOVE"
|
|
IsVisible="{Binding CanMove}" Command="{Binding MoveCommand}" />
|
|
</Grid>
|
|
|
|
<!--
|
|
◆ A row of its own, under the pair rather than beside them, as the hosts screen puts it. A phone
|
|
has no hover and no tooltip, so the only thing separating a destructive button from an ordinary
|
|
one is where a thumb lands.
|
|
-->
|
|
<Button Classes="danger" Height="44" Content="DELETE" Command="{Binding DeleteCommand}" />
|
|
</StackPanel>
|
|
|
|
<!--
|
|
◆ MOVING THE SNIPPET TO ANOTHER VAULT, in the place the insert controls were. A picker and two
|
|
buttons rather than a question with a yes: what is being asked is which vault, and a move is undone
|
|
by moving it back.
|
|
|
|
The sentence is not decoration. Unlike a host, a snippet crosses whole — there is no group and no
|
|
tag to leave behind — so what there is to say is who can read it afterwards; and on a phone, where
|
|
the status line is one line at the bottom of a screen somebody has already navigated away from,
|
|
before the tap is the only place that reliably gets read.
|
|
-->
|
|
<StackPanel Spacing="10" IsVisible="{Binding IsMoving}">
|
|
<TextBlock Classes="label" Text="MOVE TO VAULT" />
|
|
<ComboBox HorizontalAlignment="Stretch" MinHeight="44"
|
|
ItemsSource="{Binding MoveVaultChoices}"
|
|
SelectedItem="{Binding SelectedMoveVault}">
|
|
<ComboBox.ItemTemplate>
|
|
<DataTemplate x:DataType="vm:VaultChoiceViewModel">
|
|
<TextBlock Classes="mono" FontSize="12" Text="{Binding Display}" />
|
|
</DataTemplate>
|
|
</ComboBox.ItemTemplate>
|
|
</ComboBox>
|
|
<TextBlock Classes="body"
|
|
Text="The snippet is re-encrypted with the other vault's key, so everybody who holds that key can read this command and insert it — and nobody else can. Nothing else about it changes." />
|
|
<Grid ColumnDefinitions="*,8,*">
|
|
<Button Grid.Column="0" Classes="primary" Height="44" Content="MOVE"
|
|
Command="{Binding ConfirmMoveCommand}" />
|
|
<Button Grid.Column="2" Classes="secondary" Height="44" Content="CANCEL"
|
|
Command="{Binding CancelMoveCommand}" />
|
|
</Grid>
|
|
</StackPanel>
|
|
</StackPanel>
|
|
</Border>
|
|
|
|
</Grid>
|
|
|
|
</UserControl>
|