Public Access
Let the phone delete a host, and move or remove a group
The desktop gained three things the phone did not follow: moving a group to another vault, the second question asking whether a group's deletion takes its hosts with it, and — since long before either — deleting a host at all. What that left was a head whose v3 + can fill a keychain and whose editors can correct one, with no way to empty either. The commands could not simply be bound. DeleteGroup and MoveGroup aim at GroupTarget, which is the selected card or the open group, and the phone has neither: its list draws headings, and a heading's selection deliberately bounces back to the host. Called bare on that head they would have returned having done nothing — a DELETE that appears to have been pressed and has not. Both now take the row and fall back to GroupTarget for the desktop's menu, and ConfirmMoveGroupAsync resolves from the panel's own movingGroupId rather than from the selection, which is also the honester answer on the desktop: what moves is the shelf the panel was opened on. The heading's pencil became a menu. Three icons after a chevron, a name, a vault badge and a count is what would be left of the name at 360dp, so the ⋯ raises the add sheet's shape carrying Edit, Move to another vault, a rule, and Delete — the desktop's card menu, in the one idiom this screen already has. It does not carry Open: the desktop's grid holds one level of the group tree and this list holds all of it flattened, so there is nowhere to open a group into. DELETE under a host sits on a row of its own beneath EDIT and MOVE rather than beside them. A phone has no hover and no tooltip, so where a thumb lands is the only thing separating a destructive control from an ordinary one. Both questions take the controls that asked them — ShowsConnectControls, which is the phone's half of the rule ShowsHostPaneActions already carries for the desktop's drawer — so DELETE cannot be pressed a second time underneath its own confirmation. Preferences gained the running version, and the sentence saying this head does not replace itself and that no DodoSSH server will ever offer one. It reads Updates.CurrentVersion off the same view model the desktop's UPDATES section does, over the null channel that reports itself unsupported. Nothing was needed for the realtime push: it is composed in ServerConnection, which both heads use. Seven tests, all phone-shaped — a group acted on with nothing selected, the menu waved away leaving nothing armed, the ungrouped heading raising none, and the bar's three states. The rectangles remain unmeasurable for the reason phase 8 gives; the checks for them are 8.10 to 8.13 and 13.6.
This commit is contained in:
@@ -89,6 +89,94 @@
|
||||
<TextBlock Classes="detail" Foreground="{StaticResource DangerText}" TextWrapping="Wrap"
|
||||
Text="{Binding UnreadableSummary}" />
|
||||
</Border>
|
||||
|
||||
<!-- ============ ◆ what the sheet raised about a group ============ -->
|
||||
<!--
|
||||
Both of these are about a heading in the list below, and both are drawn above it rather than over it
|
||||
— which is what KEYCHAIN does with its own confirmation and for the reason written there: a card on
|
||||
top of the list hides the row it is talking about, and the row is the information the question exists
|
||||
to give. Neither replaces the list, because neither replaces the heading either.
|
||||
|
||||
At most one of the two is ever up. MoveGroup disarms a deletion and DeleteGroup folds the move away,
|
||||
which is the same pairing the desktop's GROUPS section makes.
|
||||
|
||||
◆ THE DELETION QUESTION, and the tick under it is the whole reason it is a question rather than a
|
||||
warning. Off is "the machines stay and turn up under UNGROUPED", which is recoverable; on is not, and
|
||||
it is reset to off on every question — see VaultViewModel.DeletionTakesTheHostsToo. Below the count
|
||||
rather than above it, so nobody answers it before reading how many.
|
||||
-->
|
||||
<Border IsVisible="{Binding IsConfirmingGroupDeletion}" Background="{StaticResource DangerWash}"
|
||||
BorderBrush="{StaticResource DangerSoft}" BorderThickness="1" CornerRadius="12" Padding="13,12">
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock Classes="mono" FontSize="12" TextWrapping="Wrap"
|
||||
Text="{Binding PendingDeletion.Question}" />
|
||||
<TextBlock Classes="body" Foreground="{StaticResource DangerText}"
|
||||
Text="{Binding PendingDeletion.Consequence}" />
|
||||
<TextBlock Classes="body" Foreground="{StaticResource DangerText}"
|
||||
IsVisible="{Binding PendingDeletion.HasUsage}"
|
||||
Text="{Binding PendingDeletion.Usage}" />
|
||||
|
||||
<CheckBox MinHeight="44" IsVisible="{Binding PendingDeletion.HasChoice}"
|
||||
IsChecked="{Binding DeletionTakesTheHostsToo}">
|
||||
<TextBlock Classes="mono" FontSize="11.5" TextWrapping="Wrap"
|
||||
Foreground="{StaticResource WarnText}" Text="{Binding PendingDeletion.Choice}" />
|
||||
</CheckBox>
|
||||
|
||||
<Grid ColumnDefinitions="*,8,*" Margin="0,4,0,0">
|
||||
<Button Grid.Column="0" Classes="danger" Height="44" Content="DELETE"
|
||||
Command="{Binding ConfirmDeleteCommand}" IsEnabled="{Binding !IsBusy}" />
|
||||
<Button Grid.Column="2" Classes="secondary" Height="44" Content="KEEP" FontSize="10.5"
|
||||
Command="{Binding CancelDeleteCommand}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!--
|
||||
◆ MOVING THE GROUP TO ANOTHER VAULT. The host's panel one level up, and not drawn in the danger
|
||||
colours beside it: what is being asked is which vault, and a move is undone by moving it back.
|
||||
|
||||
It names the group, which the desktop's copy of this panel does not have to — there the group's own
|
||||
card is on screen underneath the picker, and here the panel is above a list the heading has scrolled
|
||||
out of. Same reason the connect bar names the host. See VaultViewModel.MovingGroupLabel.
|
||||
|
||||
The sentence is not decoration. A group cannot go anywhere alone: the groups nested inside it and
|
||||
every machine filed under any of them are items of the vault it is leaving, so all of them are
|
||||
re-sealed under the destination's key. What cannot come is the group it was nested under, which is why
|
||||
it arrives at the top level, and the hosts' tags stay behind with it. On a phone the status line
|
||||
afterwards is one line at the bottom of a screen somebody has already navigated away from, so before
|
||||
the tap is the only place this reliably gets read.
|
||||
-->
|
||||
<Border IsVisible="{Binding IsMovingGroup}" Background="{StaticResource Panel}"
|
||||
BorderBrush="{StaticResource BorderMid}" BorderThickness="1" CornerRadius="12"
|
||||
Padding="13,12">
|
||||
<StackPanel Spacing="9">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock Classes="label" Text="MOVE GROUP" />
|
||||
<TextBlock Classes="mono" FontSize="11" Text="{Binding MovingGroupLabel}"
|
||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
|
||||
<ComboBox HorizontalAlignment="Stretch" MinHeight="44"
|
||||
ItemsSource="{Binding MoveGroupVaultChoices}"
|
||||
SelectedItem="{Binding SelectedMoveGroupVault}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:VaultChoiceViewModel">
|
||||
<TextBlock Classes="mono" FontSize="12" Text="{Binding Display}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Classes="body"
|
||||
Text="The group goes with the groups inside it and every host filed under them, all re-encrypted with the other vault's key. Any group it is nested under stays behind, so it arrives at the top level, and the hosts' tags stay behind with it." />
|
||||
|
||||
<Grid ColumnDefinitions="*,8,*">
|
||||
<Button Grid.Column="0" Classes="primary" Height="44" Content="MOVE"
|
||||
Command="{Binding ConfirmMoveGroupCommand}" IsEnabled="{Binding !IsBusy}" />
|
||||
<Button Grid.Column="2" Classes="secondary" Height="44" Content="CANCEL"
|
||||
Command="{Binding CancelMoveGroupCommand}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- ============ the list ============ -->
|
||||
@@ -144,16 +232,21 @@
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<!--
|
||||
The phone's only way into a group's editor. The desktop reaches it from a groups panel this
|
||||
head does not draw, and a heading's own selection deliberately bounces back to the host — so
|
||||
without this a group could be created here and never corrected, which matters more for a group
|
||||
than for a host: its defaults are what every host beneath it falls back to.
|
||||
◆ The phone's only way to do anything to a group. The desktop reaches all three from a menu on
|
||||
the group's own card, which this head does not draw, and a heading's own selection deliberately
|
||||
bounces back to the host — so without this a group could be created here and never corrected,
|
||||
moved or removed. It matters more for a group than for a host: its defaults are what every host
|
||||
beneath it falls back to, and it is the one thing on this screen that can take hosts with it.
|
||||
|
||||
Hidden on the ungrouped heading, which has no group behind it to edit.
|
||||
It was the pencil alone, and became a menu when there were three things behind it rather than
|
||||
one. Three icons after a chevron, a name, a vault badge and a count is what would be left of the
|
||||
name at 360dp; see VaultViewModel.OpenGroupSheet.
|
||||
|
||||
Hidden on the ungrouped heading, which has no group behind it to act on.
|
||||
-->
|
||||
<Button Grid.Column="3" Classes="icon" FontSize="13" Content="✎" Margin="4,0,0,0"
|
||||
<Button Grid.Column="3" Classes="icon" FontSize="15" Content="⋯" Margin="4,0,0,0"
|
||||
IsVisible="{Binding GroupId, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
Command="{Binding $parent[views:HostsScreen].((vm:VaultViewModel)DataContext).EditGroupFromHeadingCommand}"
|
||||
Command="{Binding $parent[views:HostsScreen].((vm:VaultViewModel)DataContext).OpenGroupSheetCommand}"
|
||||
CommandParameter="{Binding}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
@@ -470,12 +563,14 @@
|
||||
<StackPanel Spacing="10">
|
||||
|
||||
<!--
|
||||
Everything about connecting, in one group so that the move panel below can take the bar rather
|
||||
Everything about connecting, in one group so that the two panels below can take the bar rather
|
||||
than appear underneath it. A password box and a CONNECT button under a form asking which vault to
|
||||
move the host into would be two unrelated questions in one bar, and the taller of the two would
|
||||
push the other off a phone screen.
|
||||
push the other off a phone screen. The deletion question is the second such panel and needs it
|
||||
more: DELETE must not be pressable underneath its own confirmation. See
|
||||
VaultViewModel.ShowsConnectControls, which is the one question both of them ask.
|
||||
-->
|
||||
<StackPanel Spacing="10" IsVisible="{Binding !IsMovingHost}">
|
||||
<StackPanel Spacing="10" IsVisible="{Binding ShowsConnectControls}">
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock Classes="label" Text="CONNECT TO" />
|
||||
@@ -522,7 +617,7 @@
|
||||
so it 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 VaultViewModel.CanMoveSelectedHost.
|
||||
-->
|
||||
<Grid ColumnDefinitions="*,8,*" IsVisible="{Binding !IsMovingHost}">
|
||||
<Grid ColumnDefinitions="*,8,*" IsVisible="{Binding ShowsConnectControls}">
|
||||
<Button Grid.Column="0" Classes="secondary" Height="44" Content="EDIT"
|
||||
Command="{Binding EditSelectedHostCommand}" />
|
||||
<Button Grid.Column="2" Classes="secondary" Height="44" Content="MOVE"
|
||||
@@ -530,6 +625,40 @@
|
||||
Command="{Binding MoveHostCommand}" />
|
||||
</Grid>
|
||||
|
||||
<!--
|
||||
◆ A row of its own, under the pair rather than beside them, and that is the point of 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 — and DELETE sharing a row with EDIT would put the two a few millimetres
|
||||
apart. Full width in the danger colour, below both, is the same arrangement KEYCHAIN uses for the
|
||||
one action it has.
|
||||
|
||||
It was the last thing this head could not do to a host. The + creates them and EDIT corrects them,
|
||||
which left a screen that could fill a keychain and never empty one.
|
||||
-->
|
||||
<Button Classes="danger" Height="44" Content="DELETE" IsVisible="{Binding ShowsConnectControls}"
|
||||
Command="{Binding DeleteHostCommand}" />
|
||||
|
||||
<!--
|
||||
◆ THE QUESTION, in the place the buttons that asked it were. The count is the whole difference
|
||||
between a sentence somebody reads and one they press past — here it is the terminal still open on
|
||||
the machine, which a deletion deliberately does not close.
|
||||
-->
|
||||
<StackPanel Spacing="7" IsVisible="{Binding IsConfirmingHostDeletion}">
|
||||
<TextBlock Classes="mono" FontSize="12" TextWrapping="Wrap"
|
||||
Text="{Binding PendingDeletion.Question}" />
|
||||
<TextBlock Classes="body" Foreground="{StaticResource DangerText}"
|
||||
Text="{Binding PendingDeletion.Consequence}" />
|
||||
<TextBlock Classes="body" Foreground="{StaticResource DangerText}"
|
||||
IsVisible="{Binding PendingDeletion.HasUsage}"
|
||||
Text="{Binding PendingDeletion.Usage}" />
|
||||
<Grid ColumnDefinitions="*,8,*">
|
||||
<Button Grid.Column="0" Classes="danger" Height="44" Content="DELETE"
|
||||
Command="{Binding ConfirmDeleteCommand}" IsEnabled="{Binding !IsBusy}" />
|
||||
<Button Grid.Column="2" Classes="secondary" Height="44" Content="KEEP" FontSize="10.5"
|
||||
Command="{Binding CancelDeleteCommand}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
◆ MOVING THE HOST TO ANOTHER VAULT, in the place the connect 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
|
||||
@@ -646,6 +775,87 @@
|
||||
</Border>
|
||||
</Panel>
|
||||
|
||||
<!-- ============ ◆ the group sheet ============ -->
|
||||
<!--
|
||||
The three things that can be done to a group, raised from the ⋯ on its heading. The desktop draws the
|
||||
same three as a right-click menu on the group's card; this head has no card and no right-click, so it
|
||||
is the add sheet's shape doing the same job — a scrim, a bottom panel with the top two corners rounded,
|
||||
and a grab handle that is decoration.
|
||||
|
||||
Dismissible, on the add sheet's terms and not the host key sheet's: "which of these three" has no wrong
|
||||
answer and none of them is a perfectly good outcome.
|
||||
|
||||
It names the group in its own title, which the desktop's menu does not have to — a right-click menu
|
||||
opens over the card it is about, and this opens over a list the heading may have scrolled out of. Two
|
||||
vaults may each hold a "production", so the badge on the heading is the other half of that answer.
|
||||
|
||||
◆ Delete is below a rule and Move is above it, which is the desktop's own order and its own separator:
|
||||
moving is not a field of the group and it is not a deletion either. The two vaults are encrypted under
|
||||
different keys, so a move is a re-seal of the whole shelf into one and a tombstone per item in the
|
||||
other — the entry says so rather than reading as a filing gesture.
|
||||
|
||||
Open is the entry the desktop has and this does not. Its grid holds one level of the tree at a time and
|
||||
this list holds all of it flattened, so there is nothing here to open a group into.
|
||||
-->
|
||||
<Panel IsVisible="{Binding GroupSheet, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
|
||||
<Button Classes="scrim" Command="{Binding CloseGroupSheetCommand}" />
|
||||
|
||||
<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" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock Classes="title" Text="GROUP" FontSize="13" />
|
||||
<TextBlock Classes="mono" FontSize="12" Text="{Binding GroupSheetLabel}"
|
||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
|
||||
<Button Classes="row" Margin="0,10,0,0"
|
||||
Command="{Binding EditGroupFromHeadingCommand}"
|
||||
CommandParameter="{Binding GroupSheet}">
|
||||
<StackPanel Spacing="3" VerticalAlignment="Center">
|
||||
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="Edit" />
|
||||
<TextBlock Classes="detail" FontSize="10.5"
|
||||
Text="Its name, where it sits, and the port, user and key its hosts fall back to." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<Button Classes="row"
|
||||
Command="{Binding MoveGroupFromHeadingCommand}"
|
||||
CommandParameter="{Binding GroupSheet}">
|
||||
<StackPanel Spacing="3" VerticalAlignment="Center">
|
||||
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold"
|
||||
Text="Move to another vault" />
|
||||
<TextBlock Classes="detail" FontSize="10.5" TextWrapping="Wrap"
|
||||
Text="Takes the groups inside it and every host filed under them, re-encrypted with the other vault's key." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<Border Height="1" Background="{StaticResource Border}" Margin="0,8" />
|
||||
|
||||
<Button Classes="row"
|
||||
Command="{Binding DeleteGroupFromHeadingCommand}"
|
||||
CommandParameter="{Binding GroupSheet}">
|
||||
<StackPanel Spacing="3" VerticalAlignment="Center">
|
||||
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource Danger}" Text="Delete" />
|
||||
<TextBlock Classes="detail" FontSize="10.5" TextWrapping="Wrap"
|
||||
Text="Asks first, and asks separately whether the hosts filed under it go too." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<Button Classes="secondary" Content="CANCEL" Margin="0,12,0,0"
|
||||
Command="{Binding CloseGroupSheetCommand}" />
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Panel>
|
||||
|
||||
</Panel>
|
||||
|
||||
</UserControl>
|
||||
|
||||
Reference in New Issue
Block a user