Public Access
Merge branch 'main' into the desktop updater, and give way on two numbers
Main landed a realtime push feature while this branch was building the updater, and the two collided in three places. Every one of them resolves the same way: main got there first, so this branch moves. **Two ADRs were both numbered 0012.** Main's is realtime push; this one is now [ADR 0013](docs/adr/0013-desktop-distribution-and-updates.md). Git did not call this a conflict — the filenames differ — so it would have merged quietly and left the directory with two 0012s and every cross-reference ambiguous. Renumbered here along with the nine places that point at it. **Two manual-check phases were both numbered 15**, and that one git did catch. Main's "Changes that arrive without a timer" keeps 15; installing and updating the desktop client becomes Phase 16, with its checks and every reference to them renumbered. The file's own rule is that a number is for life, which is exactly why the one that had not been pushed is the one that gives way. **The merge rewrote several files with CRLF**, and `.editorconfig` asks for LF on everything except `*.ps1`. That is not cosmetic here: IDE0055 is an error and `EnforceCodeStyleInBuild` is on, so it failed the build on three lines of App.axaml.cs whose only change in this branch was an ADR number in a comment. Forty-six files normalised back to LF; the release script keeps CRLF, which is what `.gitattributes` and `.editorconfig` both already say for a PowerShell file. Nothing else conflicted. The updater does not touch the sync loop or the event stream, and the one file both sides edited heavily — MainWindowViewModel — merged without a hunk in common. Verified after merging: the solution restores locked and builds clean, and 304 shell, 100 layout, 54 session, 28 client-api and 25 contracts tests pass. The first two counts are higher than before the merge because main's own tests came with it and pass alongside these.
This commit is contained in:
@@ -41,6 +41,21 @@
|
||||
Text="{Binding PendingDeletion.Usage}" />
|
||||
</Border>
|
||||
|
||||
<!--
|
||||
The second question, and only a group's deletion has one: whether the machines filed under it go with
|
||||
it. See VaultViewModel.DeleteGroup for why it is asked rather than stated, and DeletionTakesTheHostsToo
|
||||
for why it is a tick rather than a pair of options — the two answers are not equally weighted, and the
|
||||
recoverable one is the one that needs no decision.
|
||||
|
||||
Below the usage box on purpose: that box is the count this is about, and a tick offered above the
|
||||
number it applies to is a tick somebody answers before reading how many.
|
||||
-->
|
||||
<CheckBox IsVisible="{Binding PendingDeletion.HasChoice, FallbackValue=False}"
|
||||
IsChecked="{Binding DeletionTakesTheHostsToo}">
|
||||
<TextBlock Foreground="{StaticResource WarnText}" FontSize="12" TextWrapping="Wrap"
|
||||
Text="{Binding PendingDeletion.Choice}" />
|
||||
</CheckBox>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button Classes="danger" Content="DELETE" Command="{Binding ConfirmDeleteCommand}"
|
||||
IsEnabled="{Binding !IsBusy}" />
|
||||
|
||||
@@ -55,10 +55,11 @@
|
||||
renderer. They are listed in docs/design-import-gaps.md with what ships instead, and none of them is
|
||||
drawn disabled.
|
||||
|
||||
The design's fifth missing control was the vault picker's chevron, and half of it now exists: a host
|
||||
being *created* is asked which vault it goes into, in the editor below. What still does not exist is
|
||||
the other half — moving an existing host — because the two vaults are encrypted under different keys,
|
||||
so that is a delete and a retype rather than an edit.
|
||||
The design's fifth missing control was the vault picker's chevron, and both halves of what it stood for
|
||||
now exist without it: a host being *created* is asked which vault it goes into, in the editor below, and
|
||||
an existing one is moved from the pane's ⋯ menu. The chevron itself stays undrawn, because a chevron on
|
||||
a subtitle implies an edit and this is not one — the two vaults are encrypted under different keys, so a
|
||||
move is a re-seal into one and a tombstone in the other, and the host takes a new id.
|
||||
-->
|
||||
|
||||
<Border Width="304" Background="{StaticResource Sidebar}"
|
||||
@@ -71,11 +72,12 @@
|
||||
One row for all three panels, which is why what it says is on the view model rather than repeated
|
||||
three times here. See VaultViewModel.DrawerTitle.
|
||||
|
||||
The subtitle is the vault this host is filed in, and the design's chevron beside it is not drawn:
|
||||
an item cannot be moved between vaults — the two are encrypted under different keys, so moving one
|
||||
is a delete and a retype — and a picker offering the move would be offering something no layer below
|
||||
this can do. Choosing the vault at the moment a host is created is a different question and does
|
||||
have an answer; it is in the editor, beside the name.
|
||||
The subtitle is the vault this host is filed in, and the design's chevron beside it is still not
|
||||
drawn although a host can now be moved. The two are encrypted under different keys, so a move is a
|
||||
re-seal into one vault and a tombstone in the other — it leaves the host's group and tags behind and
|
||||
gives it a new id, none of which a chevron on a subtitle would lead anybody to expect. It is in the
|
||||
menu instead, next to the two other things that happen to a whole host. Choosing the vault at the
|
||||
moment a host is created is a different question, and it is in the editor beside the name.
|
||||
-->
|
||||
<Border Grid.Row="0" Padding="14,10" Background="{StaticResource Panel}"
|
||||
BorderBrush="{StaticResource Border}" BorderThickness="0,0,0,1">
|
||||
@@ -91,10 +93,15 @@
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
The host's own two actions, behind a menu rather than as a row of buttons under the pane. They
|
||||
are what EDIT and DELETE were; a pane whose footer is CONNECT has one action worth a button, and
|
||||
the other two are things you go looking for. It hides with the deletion question for the reason
|
||||
the buttons did — see VaultViewModel.ShowsHostPaneActions.
|
||||
The host's own actions, behind a menu rather than as a row of buttons under the pane. They are
|
||||
what EDIT and DELETE were; a pane whose footer is CONNECT has one action worth a button, and the
|
||||
rest are things you go looking for. It hides with the deletion question and with the move panel
|
||||
for the reason the buttons did — see VaultViewModel.ShowsHostPaneActions.
|
||||
|
||||
Moving is here rather than in the editor, and the separator says which side of the line it is
|
||||
on: it is not a field of the host. The two vaults are encrypted under different keys, so it is a
|
||||
re-seal into one and a tombstone in the other — nothing a SAVE could do — and a picker inside
|
||||
the form would let somebody correcting a port move a machine by leaving it where they found it.
|
||||
-->
|
||||
<Button Grid.Column="1" Classes="flat paneicon" Content="⋯"
|
||||
IsVisible="{Binding ShowsHostPaneActions}"
|
||||
@@ -102,6 +109,7 @@
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuItem Header="Edit…" Command="{Binding EditSelectedHostCommand}" />
|
||||
<MenuItem Header="Move to another vault…" Command="{Binding MoveHostCommand}" />
|
||||
<Separator />
|
||||
<MenuItem Header="Delete…" Command="{Binding DeleteHostCommand}" />
|
||||
</MenuFlyout>
|
||||
@@ -365,12 +373,16 @@
|
||||
<TextBox Text="{Binding EditorLabel}" PlaceholderText="name" />
|
||||
|
||||
<!--
|
||||
◆ WHICH VAULT THIS HOST WILL LIVE IN, asked here because it is the one decision on this
|
||||
form that cannot be changed afterwards: the vaults are encrypted under different keys, so
|
||||
moving an item between them is a delete and a retype. It is a field of the host rather
|
||||
than the keychain screen's standing "new items go to" preference, and it is a separate
|
||||
selection from it — moving this one does not move that one, and a click over there cannot
|
||||
move a host half-typed here.
|
||||
◆ WHICH VAULT THIS HOST WILL LIVE IN, asked here because it decides who can read it and
|
||||
because it is the one thing on this form that no later SAVE can change: the vaults are
|
||||
encrypted under different keys, so changing it is a re-seal into one and a tombstone in
|
||||
the other. That is offered — "Move to another vault…" in the pane's own menu — and it is
|
||||
deliberately not this control, because a picker inside the form would move a machine as a
|
||||
side effect of correcting a port.
|
||||
|
||||
It is a field of the host rather than the keychain screen's standing "new items go to"
|
||||
preference, and it is a separate selection from it — moving this one does not move that
|
||||
one, and a click over there cannot move a host half-typed here.
|
||||
|
||||
Shown only while adding, and only where there is more than one vault that can be written
|
||||
to. An existing host's row is not drawn at all rather than drawn disabled; the drawer's
|
||||
@@ -390,7 +402,7 @@
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<TextBlock Classes="hint" FontSize="11" TextWrapping="Wrap"
|
||||
Text="A host in a shared vault is readable by everybody holding that vault's key, and it cannot be moved out afterwards." />
|
||||
Text="A host in a shared vault is readable by everybody holding that vault's key. It can be moved out later, from this pane's own menu — what it cannot do is become unreadable to somebody who has already synced it." />
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
@@ -554,6 +566,36 @@
|
||||
|
||||
<TextBox Text="{Binding GroupEditorLabel}" PlaceholderText="group name" />
|
||||
|
||||
<!--
|
||||
◆ WHICH VAULT THIS GROUP WILL LIVE IN, on the same terms as the host editor's picker
|
||||
above: asked while adding, hidden where there is only one vault to write to, and never
|
||||
offered for an existing group. Not because the group is stuck — the card's own menu takes
|
||||
it to another vault, with everything on the shelf — but because moving it is a
|
||||
re-seal of every item involved into new ids, which is nothing a SAVE on this form could
|
||||
do, and a picker here would do it as a side effect of correcting a default port.
|
||||
|
||||
A group in a shared vault is what gives a team an arrangement rather than a heap: the
|
||||
people holding that vault's key see the folder, and the hosts inside it inherit its port,
|
||||
its username and its key.
|
||||
|
||||
The parent picker below follows it, for the reason the host's group picker follows the
|
||||
host's vault — a parent in another vault would be a level half the readers cannot resolve.
|
||||
See VaultViewModel.ShowsGroupEditorVaultChoice.
|
||||
-->
|
||||
<StackPanel Spacing="4" IsVisible="{Binding ShowsGroupEditorVaultChoice}">
|
||||
<ComboBox ItemsSource="{Binding GroupEditorVaultChoices}"
|
||||
SelectedItem="{Binding GroupEditorSelectedVault}"
|
||||
HorizontalAlignment="Stretch">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:VaultChoiceViewModel">
|
||||
<TextBlock Text="{Binding Display}" FontSize="12" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<TextBlock Classes="hint" FontSize="11" TextWrapping="Wrap"
|
||||
Text="A group in a shared vault is visible to everybody holding that vault's key, and only hosts in the same vault can be filed under it." />
|
||||
</StackPanel>
|
||||
|
||||
<ComboBox ItemsSource="{Binding GroupEditorParentChoices}"
|
||||
SelectedItem="{Binding GroupEditorSelectedParent}"
|
||||
HorizontalAlignment="Stretch">
|
||||
@@ -602,13 +644,13 @@
|
||||
|
||||
<!-- ============ THE FOOTER ============ -->
|
||||
<!--
|
||||
One row, and exactly one of its four contents is showing — the same by-construction exclusivity the
|
||||
One row, and exactly one of its five contents is showing — the same by-construction exclusivity the
|
||||
panels above have, from the same flags. It is what each panel is for: connecting, saving a host,
|
||||
saving a group, or answering the question about deleting one.
|
||||
saving a group, answering the question about deleting one, or choosing where to move one.
|
||||
|
||||
◆ THE QUESTION TAKES CONNECT'S PLACE rather than stacking under it, as it always did with the row of
|
||||
buttons this footer replaced, so that DELETE cannot be pressed again while its own question is on
|
||||
screen. See VaultViewModel.ShowsHostPaneActions.
|
||||
screen. See VaultViewModel.ShowsHostPaneActions. The move panel takes it for the same reason.
|
||||
-->
|
||||
<Border Grid.Row="2" Padding="12" Background="{StaticResource Panel}"
|
||||
BorderBrush="{StaticResource Border}" BorderThickness="0,1,0,0">
|
||||
@@ -624,6 +666,36 @@
|
||||
<views:ConfirmDeleteCard />
|
||||
</Border>
|
||||
|
||||
<!--
|
||||
◆ MOVING THE HOST TO ANOTHER VAULT. A picker and two buttons, not a question with a yes: what
|
||||
is being asked is which vault, and a move is undone by moving it back rather than by being
|
||||
careful — so this is not drawn in the danger colours the deletion question uses.
|
||||
|
||||
The sentence under it is the part worth keeping. A group and a tag are items of the vault the
|
||||
host is leaving, so neither can come; saying so here rather than only in the status line
|
||||
afterwards is the difference between a warning and a surprise. What it deliberately does not
|
||||
promise is anything about the key or password the host authenticates with — those resolve
|
||||
across vaults, they are kept, and the status line names one that is left outside.
|
||||
-->
|
||||
<StackPanel Spacing="8" IsVisible="{Binding IsMovingHost}">
|
||||
<TextBlock Classes="label" Text="MOVE TO VAULT" />
|
||||
<ComboBox HorizontalAlignment="Stretch" ItemsSource="{Binding MoveVaultChoices}"
|
||||
SelectedItem="{Binding SelectedMoveVault}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:VaultChoiceViewModel">
|
||||
<TextBlock Text="{Binding Display}" FontSize="12" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap"
|
||||
Text="The host is re-encrypted with the other vault's key, so everybody who holds that key can read it and nobody else can. Its group and tags stay behind — both belong to the vault it is leaving." />
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button Classes="accent" Content="MOVE" Command="{Binding ConfirmMoveHostCommand}"
|
||||
IsEnabled="{Binding !IsBusy}" />
|
||||
<Button Classes="ghost" Content="CANCEL" Command="{Binding CancelMoveHostCommand}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="6" IsVisible="{Binding IsEditing}">
|
||||
<Button Classes="accent" Content="SAVE" Command="{Binding SaveHostCommand}" />
|
||||
<Button Classes="ghost" Content="CANCEL" Command="{Binding CancelEditCommand}" />
|
||||
|
||||
@@ -226,31 +226,59 @@
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="label" Text="GROUPS"
|
||||
Foreground="{StaticResource TextDim}" VerticalAlignment="Center" />
|
||||
|
||||
<!--
|
||||
The group's own two actions, beside the group cards rather than in the toolbar, because they
|
||||
act on the card that is selected — and this is where the selection is made. Hidden rather
|
||||
than disabled while DELETE's question is up, as every other pair in this application is, so
|
||||
it cannot be pressed twice, and hidden again when there is nothing for them to act on. What
|
||||
that is, with no card selected, is the group the trail above ends with; see
|
||||
VaultViewModel.GroupTarget.
|
||||
-->
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="6">
|
||||
<Button Classes="ghost" Content="EDIT" Command="{Binding EditGroupCommand}"
|
||||
IsVisible="{Binding ShowsGroupActions}" />
|
||||
<Button Classes="ghost" Content="DELETE" Command="{Binding DeleteGroupCommand}"
|
||||
IsVisible="{Binding ShowsGroupActions}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<!--
|
||||
The heading and nothing else. This used to be a row with EDIT and DELETE at the far end of it,
|
||||
and the card's own menu is where those live now — see the note on it below. They were a second
|
||||
control for the job that menu already does, and the harder of the two to read: a button beside
|
||||
a heading has to say which card it means, and with none selected it meant the group the trail
|
||||
ends with rather than anything on screen. Moving a group is on that menu and never had a button
|
||||
here, for the same reason and without the intervening mistake.
|
||||
-->
|
||||
<TextBlock Classes="label" Text="GROUPS" Foreground="{StaticResource TextDim}" />
|
||||
|
||||
<Border Padding="10" Background="{StaticResource DangerWash}" CornerRadius="6"
|
||||
IsVisible="{Binding IsConfirmingGroupDeletion}">
|
||||
<views:ConfirmDeleteCard />
|
||||
</Border>
|
||||
|
||||
<!--
|
||||
◆ MOVING THE GROUP TO ANOTHER VAULT. The host drawer's move panel, one level up, and under
|
||||
the heading rather than in the drawer because a group has no drawer of its own. 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 — so it is not drawn in the danger colours the deletion question
|
||||
above it uses. It sits beside that question and never with it: MoveGroup disarms a pending
|
||||
deletion and DeleteGroup folds this away, so the section draws at most one of the two.
|
||||
|
||||
The sentence is what the panel is for. A group cannot go anywhere alone: the machines under it
|
||||
are items of the vault it is leaving, and so are the groups nested inside it, so all of them
|
||||
are re-sealed under the destination's key and all of them take new ids. What cannot come is
|
||||
the group it is nested under, which is why it arrives at the top level. Saying that here
|
||||
rather than only in the status line afterwards is the difference between a warning and a
|
||||
surprise.
|
||||
-->
|
||||
<Border Padding="10" Background="{StaticResource Panel}" CornerRadius="6"
|
||||
BorderBrush="{StaticResource Border}" BorderThickness="1"
|
||||
IsVisible="{Binding IsMovingGroup}">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Classes="label" Text="MOVE GROUP TO VAULT" />
|
||||
<ComboBox HorizontalAlignment="Stretch" ItemsSource="{Binding MoveGroupVaultChoices}"
|
||||
SelectedItem="{Binding SelectedMoveGroupVault}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:VaultChoiceViewModel">
|
||||
<TextBlock Text="{Binding Display}" FontSize="12" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap"
|
||||
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." />
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button Classes="accent" Content="MOVE" Command="{Binding ConfirmMoveGroupCommand}"
|
||||
IsEnabled="{Binding !IsBusy}" />
|
||||
<Button Classes="ghost" Content="CANCEL" Command="{Binding CancelMoveGroupCommand}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!--
|
||||
A ListBox rather than an ItemsControl of buttons, and that is what marks the chosen group for
|
||||
free: selection is a state the control already has, and the tile style draws it the same way
|
||||
@@ -262,10 +290,15 @@
|
||||
opens a shell on a host. One click used to mean both, and a card was then the only place a
|
||||
group could be named while also being the control that threw the rest of the grid away.
|
||||
|
||||
◆ ONE SELECTION, TWO LISTS. Selecting a card here takes the mark off the host grid below, and
|
||||
selecting a host takes it off this one. Two controls each keep their own SelectedItem, so the
|
||||
vault is what joins them — see VaultViewModel.OnSelectedHostChanged. Without it both grids
|
||||
could be lit at once, which is two chosen things under two pairs of buttons.
|
||||
|
||||
◆ THIS IS ONE LEVEL, NOT EVERY GROUP. It binds VisibleGroups: what is inside the group the
|
||||
trail above ends with, or the outermost groups when it ends at ALL HOSTS. Folded away entirely
|
||||
at a group with nothing inside it, which is an ordinary thing to open — the trail and the two
|
||||
buttons stay, because leaving it is a gesture and editing it is a button.
|
||||
at a group with nothing inside it, which is an ordinary thing to open — the trail stays, and
|
||||
it is also the way back to the level where that group has a card of its own to be edited from.
|
||||
|
||||
◆ THESE CARDS ARE THE DROP TARGET. A host card dragged onto one is filed under that group, and
|
||||
that is the whole of what a drag does on this screen. It used to be a heading inside the host
|
||||
@@ -282,6 +315,40 @@
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate><WrapPanel /></ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
|
||||
<!--
|
||||
The three things you can do to a group, on the group itself — the same menu the host grid
|
||||
below has, for the same reasons. On the list rather than in the item template, because the
|
||||
commands are the vault's and a menu inside a DataTemplate would have the row for its data
|
||||
context; the code-behind selects whatever was right-clicked before the menu opens, and
|
||||
cancels it outright over the space around the cards.
|
||||
|
||||
Open is here as well as on the double-click, which is not a duplicate for its own sake: a
|
||||
gesture is unreachable without a pointer. Edit and Delete are here and nowhere else — they
|
||||
were also a pair of buttons beside the heading above, which acted on GroupTarget and so with
|
||||
no card selected meant the group the trail ends with rather than any card on screen. All
|
||||
three act on the card that was right-clicked, which is the whole of what this menu is for.
|
||||
|
||||
Only Open takes a parameter, because OpenGroupCommand's null means ALL HOSTS rather than
|
||||
nothing; the other three read GroupTarget, which the selection the code-behind has just made
|
||||
is the first half of.
|
||||
|
||||
Moving is above the separator that Delete sits below, exactly as it is in the host pane's
|
||||
menu, and for the same reason: it is not a field of the group and it is not a deletion
|
||||
either. The two vaults are encrypted under different keys, so it is a re-seal of the whole
|
||||
shelf into one and a tombstone per item in the other.
|
||||
-->
|
||||
<ListBox.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Open" Command="{Binding OpenGroupCommand}"
|
||||
CommandParameter="{Binding SelectedGroup}" />
|
||||
<MenuItem Header="Edit…" Command="{Binding EditGroupCommand}" />
|
||||
<MenuItem Header="Move to another vault…" Command="{Binding MoveGroupCommand}" />
|
||||
<Separator />
|
||||
<MenuItem Header="Delete…" Command="{Binding DeleteGroupCommand}" />
|
||||
</ContextMenu>
|
||||
</ListBox.ContextMenu>
|
||||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:HostGroupRowViewModel">
|
||||
<Border Classes="tile">
|
||||
@@ -304,6 +371,17 @@
|
||||
</Grid>
|
||||
<TextBlock Text="{Binding Description}" FontSize="11"
|
||||
Foreground="{StaticResource TextFaint}" />
|
||||
<!--
|
||||
Which vault this group is in, on the same rule and in the same place as the host
|
||||
card's: only where there is more than one vault to be in. It matters more on a
|
||||
folder than on a machine — two vaults may each hold a "production", and without
|
||||
this the cards are two identical folders side by side, one of which a colleague
|
||||
can read. It is also what says a group is shared at all.
|
||||
-->
|
||||
<TextBlock Classes="mono" Text="{Binding VaultBadge}" FontSize="10"
|
||||
Foreground="{StaticResource TextFaint}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
IsVisible="{Binding HasVaultBadge}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@@ -96,6 +96,11 @@ internal sealed partial class HostsScreen : UserControl
|
||||
HostGrid.AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Tunnel);
|
||||
HostGrid.AddHandler(ContextRequestedEvent, OnContextRequested, RoutingStrategies.Tunnel);
|
||||
|
||||
// And the group cards answer a right click the same way, for the same reason: their menu's commands
|
||||
// read the vault's group selection, and without this they would act on whichever card was selected
|
||||
// before — or, with none, on the group the trail ends with, which is not on screen at all.
|
||||
GroupGrid.AddHandler(ContextRequestedEvent, OnGroupContextRequested, RoutingStrategies.Tunnel);
|
||||
|
||||
HostGrid.PointerMoved += OnPointerMoved;
|
||||
HostGrid.PointerReleased += OnPointerReleased;
|
||||
HostGrid.PointerCaptureLost += OnPointerCaptureLost;
|
||||
@@ -204,6 +209,33 @@ internal sealed partial class HostsScreen : UserControl
|
||||
vault.SelectedSidebarRow = row;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Points the group menu at whatever was right-clicked.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The host grid's rule, applied to the cards above it — see <see cref="OnContextRequested"/>. What is
|
||||
/// different is what an unaimed menu would have done: <c>GroupTarget</c> falls back to the open group
|
||||
/// when no card is selected, so Edit and Delete over a card would have been offered about the group whose
|
||||
/// contents are showing rather than the one the pointer is on. This menu is the only way to either of
|
||||
/// them now, so aiming it is the whole of aiming them.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Cancelled outright over the space around the cards, as the host grid's is. That is not a group, and
|
||||
/// the fallback is exactly what would make the menu look like it worked there.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private void OnGroupContextRequested(object? sender, ContextRequestedEventArgs e)
|
||||
{
|
||||
if (Vault is not { } vault || RowUnder(e.Source) is not HostGroupRowViewModel row)
|
||||
{
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
vault.SelectedGroup = row;
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Remembered rather than acted on. Whether this press is a click or the start of a drag is not known
|
||||
/// until the pointer moves, so this is the point at which both are still possible.
|
||||
|
||||
Reference in New Issue
Block a user