Skip to content
This repository was archived by the owner on Apr 2, 2026. It is now read-only.

Commit 8a0ce65

Browse files
committed
feat: add paging
1 parent 20498b9 commit 8a0ce65

2 files changed

Lines changed: 156 additions & 36 deletions

File tree

src/AStar.Dev.File.App/ViewModels/MainWindowViewModel.cs

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using CommunityToolkit.Mvvm.Input;
55
using Microsoft.EntityFrameworkCore;
66
using System;
7+
using System.Collections.Generic;
78
using System.Collections.ObjectModel;
89
using System.Linq;
910
using System.Threading;
@@ -17,6 +18,10 @@ public partial class MainWindowViewModel : ViewModelBase
1718
private readonly IFolderPickerService _folderPickerService;
1819
private readonly IDbContextFactory<FileAppDbContext> _dbContextFactory;
1920
private CancellationTokenSource? _cts;
21+
// Guards against cascading reloads when programmatically resetting CurrentPage
22+
private bool _suppressPageReload;
23+
24+
public static IReadOnlyList<int> PageSizes { get; } = [25, 50, 75, 100, 125, 150, 175, 200];
2025

2126
[ObservableProperty]
2227
[NotifyPropertyChangedFor(nameof(CanScan))]
@@ -36,6 +41,32 @@ public partial class MainWindowViewModel : ViewModelBase
3641
[ObservableProperty]
3742
private int _totalFilesProcessed;
3843

44+
[ObservableProperty]
45+
[NotifyPropertyChangedFor(nameof(TotalPages), nameof(PagingInfo))]
46+
[NotifyCanExecuteChangedFor(nameof(FirstPageCommand))]
47+
[NotifyCanExecuteChangedFor(nameof(PreviousPageCommand))]
48+
[NotifyCanExecuteChangedFor(nameof(NextPageCommand))]
49+
[NotifyCanExecuteChangedFor(nameof(LastPageCommand))]
50+
private int _pageSize = 50;
51+
52+
[ObservableProperty]
53+
[NotifyPropertyChangedFor(nameof(PagingInfo))]
54+
[NotifyCanExecuteChangedFor(nameof(FirstPageCommand))]
55+
[NotifyCanExecuteChangedFor(nameof(PreviousPageCommand))]
56+
[NotifyCanExecuteChangedFor(nameof(NextPageCommand))]
57+
[NotifyCanExecuteChangedFor(nameof(LastPageCommand))]
58+
private int _currentPage = 1;
59+
60+
[ObservableProperty]
61+
[NotifyPropertyChangedFor(nameof(TotalPages), nameof(PagingInfo))]
62+
[NotifyCanExecuteChangedFor(nameof(NextPageCommand))]
63+
[NotifyCanExecuteChangedFor(nameof(LastPageCommand))]
64+
private int _totalFileCount;
65+
66+
public int TotalPages => TotalFileCount == 0 ? 1 : (int)Math.Ceiling((double)TotalFileCount / PageSize);
67+
68+
public string PagingInfo => $"PAGE {CurrentPage} OF {TotalPages} [{TotalFileCount} FILES]";
69+
3970
public bool CanScan => !IsScanning && !string.IsNullOrWhiteSpace(SelectedFolderPath);
4071

4172
public ObservableCollection<string> StatusMessages { get; } = [];
@@ -71,7 +102,11 @@ private async Task StartScan()
71102
StatusMessages.Clear();
72103
ScannedFiles.Clear();
73104
TotalFilesProcessed = 0;
105+
TotalFileCount = 0;
74106
CurrentScanFolder = string.Empty;
107+
_suppressPageReload = true;
108+
CurrentPage = 1;
109+
_suppressPageReload = false;
75110

76111
_cts = new CancellationTokenSource();
77112

@@ -107,19 +142,65 @@ private void Cancel()
107142
_cts?.Cancel();
108143
}
109144

145+
[RelayCommand(CanExecute = nameof(CanGoFirst))]
146+
private void FirstPage() { CurrentPage = 1; }
147+
private bool CanGoFirst() => CurrentPage > 1;
148+
149+
[RelayCommand(CanExecute = nameof(CanGoPrevious))]
150+
private void PreviousPage() { CurrentPage--; }
151+
private bool CanGoPrevious() => CurrentPage > 1;
152+
153+
[RelayCommand(CanExecute = nameof(CanGoNext))]
154+
private void NextPage() { CurrentPage++; }
155+
private bool CanGoNext() => CurrentPage < TotalPages;
156+
157+
[RelayCommand(CanExecute = nameof(CanGoLast))]
158+
private void LastPage() { CurrentPage = TotalPages; }
159+
private bool CanGoLast() => CurrentPage < TotalPages;
160+
161+
partial void OnCurrentPageChanged(int value)
162+
{
163+
if (!_suppressPageReload)
164+
_ = LoadScannedFilesAsync();
165+
}
166+
167+
partial void OnPageSizeChanged(int value)
168+
{
169+
_suppressPageReload = true;
170+
CurrentPage = 1;
171+
_suppressPageReload = false;
172+
_ = LoadScannedFilesAsync();
173+
}
174+
110175
private async Task LoadScannedFilesAsync()
111176
{
112-
var root = SelectedFolderPath;
113-
// Normalise so the StartsWith check works regardless of trailing separator
114-
var prefix = root.TrimEnd(System.IO.Path.DirectorySeparatorChar,
115-
System.IO.Path.AltDirectorySeparatorChar)
177+
if (string.IsNullOrWhiteSpace(SelectedFolderPath))
178+
return;
179+
180+
var prefix = SelectedFolderPath.TrimEnd(System.IO.Path.DirectorySeparatorChar,
181+
System.IO.Path.AltDirectorySeparatorChar)
116182
+ System.IO.Path.DirectorySeparatorChar;
117183

118184
await using var db = await _dbContextFactory.CreateDbContextAsync();
119-
var files = await db.ScannedFiles
185+
186+
var query = db.ScannedFiles
120187
.Where(f => f.FullPath.StartsWith(prefix))
121188
.OrderBy(f => f.FolderPath)
122-
.ThenBy(f => f.FileName)
189+
.ThenBy(f => f.FileName);
190+
191+
TotalFileCount = await query.CountAsync();
192+
193+
// Clamp current page if the page count shrank (e.g. after a page-size increase)
194+
if (CurrentPage > TotalPages)
195+
{
196+
_suppressPageReload = true;
197+
CurrentPage = TotalPages;
198+
_suppressPageReload = false;
199+
}
200+
201+
var files = await query
202+
.Skip((CurrentPage - 1) * PageSize)
203+
.Take(PageSize)
123204
.ToListAsync();
124205

125206
ScannedFiles.Clear();

src/AStar.Dev.File.App/Views/MainWindow.axaml

Lines changed: 69 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -63,37 +63,76 @@
6363
<!-- Main content -->
6464
<Grid ColumnDefinitions="*,4,260">
6565

66-
<!-- DataGrid -->
67-
<DataGrid Grid.Column="0"
68-
ItemsSource="{Binding ScannedFiles}"
69-
AutoGenerateColumns="False"
70-
IsReadOnly="True"
71-
CanUserSortColumns="True">
72-
<DataGrid.Columns>
73-
<DataGridTextColumn Header="FILE NAME"
74-
Binding="{Binding FileName}"
75-
Width="300" />
76-
<DataGridTextColumn Header="FOLDER"
77-
Binding="{Binding FolderPath}"
78-
Width="*" />
79-
<DataGridTextColumn Header="SIZE"
80-
Binding="{Binding FormattedSize}"
81-
Width="90" />
82-
<DataGridTextColumn Header="TYPE"
83-
Binding="{Binding FileType}"
84-
Width="110" />
85-
<DataGridTextColumn Header="LAST MODIFIED"
86-
Binding="{Binding LastModified}"
87-
Width="180" />
88-
<DataGridTextColumn Header="LAST VIEWED"
89-
Binding="{Binding LastViewed}"
90-
Width="160" />
91-
<DataGridCheckBoxColumn Header="DELETE PENDING"
92-
Binding="{Binding PendingDelete}"
93-
Width="150" />
94-
</DataGrid.Columns>
95-
</DataGrid>
66+
<!-- DataGrid + paging toolbar -->
67+
<DockPanel Grid.Column="0">
9668

69+
<!-- Paging toolbar -->
70+
<Border DockPanel.Dock="Bottom"
71+
BorderThickness="0,1,0,0"
72+
BorderBrush="{StaticResource HackerDimGreen}"
73+
Height="30"
74+
Padding="6,0">
75+
<Grid ColumnDefinitions="Auto,*,Auto" VerticalAlignment="Center">
76+
77+
<!-- Navigation buttons + page info -->
78+
<StackPanel Grid.Column="0"
79+
Orientation="Horizontal"
80+
Spacing="4"
81+
VerticalAlignment="Center">
82+
<Button Content="[|&lt;]" Command="{Binding FirstPageCommand}" Padding="6,2" />
83+
<Button Content="[&lt;]" Command="{Binding PreviousPageCommand}" Padding="6,2" />
84+
<TextBlock Text="{Binding PagingInfo}"
85+
VerticalAlignment="Center"
86+
Margin="10,0" />
87+
<Button Content="[&gt;]" Command="{Binding NextPageCommand}" Padding="6,2" />
88+
<Button Content="[&gt;|]" Command="{Binding LastPageCommand}" Padding="6,2" />
89+
</StackPanel>
90+
91+
<!-- Page size selector -->
92+
<StackPanel Grid.Column="2"
93+
Orientation="Horizontal"
94+
Spacing="6"
95+
VerticalAlignment="Center">
96+
<TextBlock Text="PAGE SIZE:"
97+
VerticalAlignment="Center" />
98+
<ComboBox ItemsSource="{Binding PageSizes}"
99+
SelectedItem="{Binding PageSize}"
100+
Width="75" />
101+
</StackPanel>
102+
103+
</Grid>
104+
</Border>
105+
106+
<DataGrid ItemsSource="{Binding ScannedFiles}"
107+
AutoGenerateColumns="False"
108+
IsReadOnly="True"
109+
CanUserSortColumns="True">
110+
<DataGrid.Columns>
111+
<DataGridTextColumn Header="FILE NAME"
112+
Binding="{Binding FileName}"
113+
Width="300" />
114+
<DataGridTextColumn Header="FOLDER"
115+
Binding="{Binding FolderPath}"
116+
Width="*" />
117+
<DataGridTextColumn Header="SIZE"
118+
Binding="{Binding FormattedSize}"
119+
Width="90" />
120+
<DataGridTextColumn Header="TYPE"
121+
Binding="{Binding FileType}"
122+
Width="110" />
123+
<DataGridTextColumn Header="LAST MODIFIED"
124+
Binding="{Binding LastModified}"
125+
Width="180" />
126+
<DataGridTextColumn Header="LAST VIEWED"
127+
Binding="{Binding LastViewed}"
128+
Width="160" />
129+
<DataGridCheckBoxColumn Header="DELETE PENDING"
130+
Binding="{Binding PendingDelete}"
131+
Width="150" />
132+
</DataGrid.Columns>
133+
</DataGrid>
134+
135+
</DockPanel>
97136
<!-- Splitter -->
98137
<GridSplitter Grid.Column="1"
99138
Background="{StaticResource HackerDimGreen}"

0 commit comments

Comments
 (0)