refactor(desktop): sort paged server lists from the header in BrowsePager

The drawing library, drawing sets, saved nests, add-release and edit-set
grids each repeated the left-click header check that sorts on the
server. BrowsePager.SortByHeaderAsync now does it for the grid and
session it already drives. No behavior change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ajandClaude Opus 5.5 committed 2026-11-01 10:21:27 -05:00
1 parent 7bff9baa23
commit e271580f62
7 files changed
+156 -27

No files matched your search

@@ -0,0 +1,135 @@
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using OpenNest.Controls;
using OpenNest.Data;
namespace OpenNest.WinForms.Tests.Controls;
public class BrowsePagerTests
{
private enum Field { Name, Size }
private enum OtherField { Size }
[Fact]
public void LeftClickOnASortColumnSortsOnTheServerAndReversesOnTheSameColumn() => RunSta(() =>
{
using var host = new Host();
host.Click(host.SizeColumn, MouseButtons.Left);
Assert.Equal(1, host.Session.Queries);
Assert.Equal(Field.Size, host.Session.Sort);
Assert.False(host.Session.Descending);
Assert.Equal(1, host.Populated);
host.Click(host.SizeColumn, MouseButtons.Left);
Assert.Equal(2, host.Session.Queries);
Assert.True(host.Session.Descending);
});
[Fact]
public void OtherButtonsDoNotSort() => RunSta(() =>
{
using var host = new Host();
host.Click(host.SizeColumn, MouseButtons.Right);
host.Click(host.SizeColumn, MouseButtons.Middle);
Assert.Equal(0, host.Session.Queries);
Assert.Equal(Field.Name, host.Session.Sort);
});
[Fact]
public void ColumnsWithoutThisListsSortFieldDoNotSort() => RunSta(() =>
{
using var host = new Host();
host.Click(host.UntaggedColumn, MouseButtons.Left);
host.Click(host.ForeignColumn, MouseButtons.Left);
Assert.Equal(0, host.Session.Queries);
Assert.Equal(Field.Name, host.Session.Sort);
});
private static void RunSta(Action action) =>
StaTestThread.Run(action, TimeSpan.FromSeconds(30), "The STA test did not complete.");
private sealed class Host : IDisposable
{
private readonly DataGridView grid = new();
private readonly Label summary = new();
private readonly Button previous = new();
private readonly Button next = new();
private readonly BrowsePager<Page, Field> pager;
public Host()
{
UntaggedColumn = grid.Columns.Add("untagged", "Untagged");
SizeColumn = grid.Columns.Add("size", "Size");
ForeignColumn = grid.Columns.Add("foreign", "Foreign");
grid.Columns[SizeColumn].Tag = Field.Size;
grid.Columns[ForeignColumn].Tag = OtherField.Size;
pager = new BrowsePager<Page, Field>(
Session, grid, summary, previous, next, "the test list")
{
Populate = _ => Populated++,
};
}
public TestSession Session { get; } = new();
public int UntaggedColumn { get; }
public int SizeColumn { get; }
public int ForeignColumn { get; }
public int Populated { get; private set; }
public void Click(int column, MouseButtons button)
{
var mouse = new MouseEventArgs(button, 1, 0, 0, 0);
pager.SortByHeaderAsync(new DataGridViewCellMouseEventArgs(column, -1, 0, 0, mouse))
.GetAwaiter().GetResult();
}
public void Dispose()
{
Session.Dispose();
grid.Dispose();
summary.Dispose();
previous.Dispose();
next.Dispose();
}
}
private sealed class Page : IBrowsePage
{
public int Offset => 0;
public int Total => 0;
public int ItemCount => 0;
}
private sealed class TestSession : BrowseSession<Page, Field>
{
public TestSession()
: base(pageSize: 10, maxPageSize: 100, Field.Name)
{
}
public int Queries { get; private set; }
public override string Summary => "";
protected override Task<Page> QueryAsync(CancellationToken cancellationToken)
{
Queries++;
return Task.FromResult(new Page());
}
}
}
+9
View File
@@ -84,6 +84,15 @@ namespace OpenNest.Controls
}
}
/// <summary>
/// Sorts by a left-clicked column whose <see cref="DataGridViewColumn.Tag"/> is a sort
/// field. Sorting a page locally would misrepresent the list; the server sorts all matches.
/// </summary>
public Task SortByHeaderAsync(DataGridViewCellMouseEventArgs e) =>
e.Button == MouseButtons.Left && grid.Columns[e.ColumnIndex].Tag is TSort field
? RunAsync(() => session.SortByAsync(field))
: Task.CompletedTask;
public void UpdateNavigation()
{
previous.Enabled = !session.IsLoading && session.CanGoPrevious;
+3 -6
View File
@@ -216,12 +216,9 @@ namespace OpenNest.Controls
await RunAsync(() => session.SetShowAllAsync(showAll));
}
private async void DrawingGrid_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
// Sorting a page locally would misrepresent the library; the server sorts all matches.
if (e.Button == MouseButtons.Left && drawingGrid.Columns[e.ColumnIndex].Tag is DrawingSortField field)
await RunAsync(() => session.SortByAsync(field));
}
private async void DrawingGrid_ColumnHeaderMouseClick(
object sender, DataGridViewCellMouseEventArgs e) =>
await pager.SortByHeaderAsync(e);
private async void PreviousButton_Click(object sender, EventArgs e) =>
await RunAsync(session.PreviousPageAsync);
+2 -3
View File
@@ -242,9 +242,8 @@ namespace OpenNest.Controls
private async void SetGrid_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
// Sorting a page locally would misrepresent the list; the server sorts all matches.
if (session != null && e.Button == MouseButtons.Left && setGrid.Columns[e.ColumnIndex].Tag is DrawingSetSortField field)
await RunAsync(() => session.SortByAsync(field));
if (session != null)
await pager.SortByHeaderAsync(e);
}
private async void PreviousButton_Click(object sender, EventArgs e) =>
+3 -6
View File
@@ -72,12 +72,9 @@ namespace OpenNest.Forms
await RunLibraryAsync(() => librarySession.SetSearchAsync(searchBox.Text));
}
private async void LibraryGrid_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
// Sorting a page locally would misrepresent the library; the server sorts all matches.
if (e.Button == MouseButtons.Left && libraryGrid.Columns[e.ColumnIndex].Tag is DrawingSortField field)
await RunLibraryAsync(() => librarySession.SortByAsync(field));
}
private async void LibraryGrid_ColumnHeaderMouseClick(
object sender, DataGridViewCellMouseEventArgs e) =>
await libraryPager.SortByHeaderAsync(e);
private async void PreviousButton_Click(object sender, EventArgs e) =>
await RunLibraryAsync(librarySession.PreviousPageAsync);
+3 -6
View File
@@ -171,12 +171,9 @@ namespace OpenNest.Forms
await RunLibraryAsync(() => librarySession.SetSearchAsync(searchBox.Text));
}
private async void LibraryGrid_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
// Sorting a page locally would misrepresent the library; the server sorts all matches.
if (e.Button == MouseButtons.Left && libraryGrid.Columns[e.ColumnIndex].Tag is DrawingSortField field)
await RunLibraryAsync(() => librarySession.SortByAsync(field));
}
private async void LibraryGrid_ColumnHeaderMouseClick(
object sender, DataGridViewCellMouseEventArgs e) =>
await libraryPager.SortByHeaderAsync(e);
private async void PreviousButton_Click(object sender, EventArgs e) =>
await RunLibraryAsync(librarySession.PreviousPageAsync);
+1 -6
View File
@@ -128,12 +128,7 @@ public sealed class SavedNestsForm : Form
Visible = false,
SortMode = DataGridViewColumnSortMode.NotSortable,
});
// Sorting a page locally would misrepresent the archive; the server sorts all matches.
nestGrid.ColumnHeaderMouseClick += async (_, e) =>
{
if (e.Button == MouseButtons.Left && nestGrid.Columns[e.ColumnIndex].Tag is NestSortField field)
await RunAsync(() => session.SortByAsync(field));
};
nestGrid.ColumnHeaderMouseClick += async (_, e) => await pager.SortByHeaderAsync(e);
nestGrid.CellDoubleClick += (_, e) =>
{
if (e.RowIndex >= 0)