11using Avalonia ;
22using Avalonia . Controls ;
3+ using Avalonia . Controls . Templates ;
34using Avalonia . Media ;
5+ using Avalonia . Platform . Storage ;
46using Avalonia . Threading ;
57using Avalonia . VisualTree ;
8+ using AkkornStudio . UI . Services . SqlEditor . Reports ;
69using AkkornStudio . UI . ViewModels ;
710using System . ComponentModel ;
811using System . Data ;
12+ using System . IO ;
913
1014namespace AkkornStudio . UI . Controls . SqlEditor ;
1115
1216public partial class SqlResultPageControl : UserControl
1317{
1418 private SqlResultPageViewModel ? _subscribedViewModel ;
19+ private readonly SqlEditorReportExportService _reportExportService = new ( ) ;
1520 private readonly IBrush _pendingCellBackgroundBrush = ResolveBrush (
1621 "BtnWarningBgBrush" ,
1722 new SolidColorBrush ( Color . Parse ( "#3A2A12" ) ) ) ;
@@ -32,16 +37,19 @@ private void OnDataContextChanged(object? sender, EventArgs e)
3237 if ( _subscribedViewModel is not null )
3338 {
3439 _subscribedViewModel . ClipboardCopyRequested -= OnClipboardCopyRequested ;
40+ _subscribedViewModel . ExportRequested -= OnExportRequested ;
3541 _subscribedViewModel . PropertyChanged -= OnViewModelPropertyChanged ;
3642 }
3743
3844 _subscribedViewModel = DataContext as SqlResultPageViewModel ;
3945 if ( _subscribedViewModel is not null )
4046 {
4147 _subscribedViewModel . ClipboardCopyRequested += OnClipboardCopyRequested ;
48+ _subscribedViewModel . ExportRequested += OnExportRequested ;
4249 _subscribedViewModel . PropertyChanged += OnViewModelPropertyChanged ;
4350 }
4451
52+ RebuildResultGridColumns ( ) ;
4553 Dispatcher . UIThread . Post ( ApplyPendingHighlightsForLoadedRows ) ;
4654 }
4755
@@ -54,36 +62,48 @@ private async void OnClipboardCopyRequested(string text)
5462 await topLevel . Clipboard . SetTextAsync ( text ) ;
5563 }
5664
57- private void ResultGrid_OnCellPointerPressed ( object ? sender , DataGridCellPointerPressedEventArgs e )
65+ private async void OnExportRequested ( SqlResultPageViewModel . SqlResultExportRequest request )
5866 {
59- _ = sender ;
60- if ( DataContext is not SqlResultPageViewModel viewModel )
67+ TopLevel ? topLevel = TopLevel . GetTopLevel ( this ) ;
68+ if ( topLevel ? . StorageProvider is null )
6169 return ;
6270
63- if ( e . Row ? . DataContext is not DataRowView rowView )
64- return ;
71+ var fileType = new FilePickerFileType ( request . FileTypeTitle )
72+ {
73+ Patterns = request . Patterns . ToList ( ) ,
74+ MimeTypes = request . MimeTypes . ToList ( ) ,
75+ } ;
6576
66- string ? columnName = e . Column ? . SortMemberPath ;
67- if ( string . IsNullOrWhiteSpace ( columnName ) )
77+ IStorageFile ? file = await topLevel . StorageProvider . SaveFilePickerAsync (
78+ new FilePickerSaveOptions
79+ {
80+ Title = "Export SQL Result" ,
81+ DefaultExtension = request . DefaultExtension ,
82+ SuggestedFileName = request . SuggestedFileName ,
83+ FileTypeChoices = [ fileType ] ,
84+ } ) ;
85+
86+ string ? path = file ? . TryGetLocalPath ( ) ;
87+ if ( string . IsNullOrWhiteSpace ( path ) )
6888 return ;
6989
70- viewModel . SelectCell ( rowView , columnName ) ;
90+ await File . WriteAllTextAsync ( path , request . Content ) ;
7191 }
7292
73- private void ResultGrid_OnAutoGeneratingColumn ( object ? sender , DataGridAutoGeneratingColumnEventArgs e )
93+ private void ResultGrid_OnCellPointerPressed ( object ? sender , DataGridCellPointerPressedEventArgs e )
7494 {
7595 _ = sender ;
7696 if ( DataContext is not SqlResultPageViewModel viewModel )
7797 return ;
7898
79- string columnName = string . IsNullOrWhiteSpace ( e . PropertyName )
80- ? e . Column . SortMemberPath
81- : e . PropertyName ;
99+ if ( e . Row ? . DataContext is not DataRowView rowView )
100+ return ;
101+
102+ string ? columnName = e . Column ? . SortMemberPath ;
82103 if ( string . IsNullOrWhiteSpace ( columnName ) )
83104 return ;
84105
85- e . Column . SortMemberPath = columnName ;
86- e . Column . IsReadOnly = ! viewModel . IsColumnEditable ( columnName ) ;
106+ viewModel . SelectCell ( rowView , columnName ) ;
87107 }
88108
89109 private void ResultGrid_OnLoadingRow ( object ? sender , DataGridRowEventArgs e )
@@ -128,12 +148,14 @@ private void ResultGrid_OnCellEditEnding(object? sender, DataGridCellEditEndingE
128148 private void OnViewModelPropertyChanged ( object ? sender , PropertyChangedEventArgs e )
129149 {
130150 _ = sender ;
151+ if ( e . PropertyName is nameof ( SqlResultPageViewModel . RowsView )
152+ or nameof ( SqlResultPageViewModel . Session ) )
153+ RebuildResultGridColumns ( ) ;
154+
131155 if ( e . PropertyName is nameof ( SqlResultPageViewModel . PendingEditsCount )
132156 or nameof ( SqlResultPageViewModel . RowsView )
133157 or nameof ( SqlResultPageViewModel . Session ) )
134- {
135158 Dispatcher . UIThread . Post ( ApplyPendingHighlightsForLoadedRows ) ;
136- }
137159 }
138160
139161 private void ApplyPendingHighlightsForLoadedRows ( )
@@ -199,6 +221,113 @@ private static bool TryExtractEditedText(Control editingElement, out string? edi
199221 }
200222 }
201223
224+ private async void ExportReportButton_Click ( object ? sender , Avalonia . Interactivity . RoutedEventArgs e )
225+ {
226+ _ = sender ;
227+ _ = e ;
228+ if ( DataContext is not SqlResultPageViewModel viewModel )
229+ return ;
230+
231+ if ( ! viewModel . TryBuildReportExportContext ( out SqlEditorReportExportContext ? context ) || context is null )
232+ return ;
233+
234+ TopLevel ? topLevel = TopLevel . GetTopLevel ( this ) ;
235+ if ( topLevel is not Window owner || topLevel . StorageProvider is null )
236+ return ;
237+
238+ var dialogVm = new SqlEditorReportExportDialogViewModel ( context . TabTitle ) ;
239+ var dialog = new SqlEditorReportExportDialogWindow ( dialogVm ) ;
240+ await dialog . ShowDialog ( owner ) ;
241+ if ( ! dialog . WasConfirmed )
242+ return ;
243+
244+ string normalizedExtension = dialogVm . SuggestedExtension . TrimStart ( '.' ) ;
245+ FilePickerFileType reportFileType = GetExportFileType ( dialogVm . SelectedType ? . Type ?? SqlEditorReportType . HtmlFullFeature ) ;
246+ IStorageFile ? file = await topLevel . StorageProvider . SaveFilePickerAsync (
247+ new FilePickerSaveOptions
248+ {
249+ Title = "Export SQL Report" ,
250+ DefaultExtension = normalizedExtension ,
251+ SuggestedFileName = dialogVm . FileName ,
252+ FileTypeChoices = [ reportFileType ] ,
253+ } ) ;
254+
255+ string ? outputPath = file ? . TryGetLocalPath ( ) ;
256+ if ( string . IsNullOrWhiteSpace ( outputPath ) )
257+ return ;
258+
259+ SqlEditorReportExportRequest request = dialogVm . BuildRequest ( outputPath ) ;
260+ await _reportExportService . ExportAsync ( context , request ) ;
261+ }
262+
263+ private void RebuildResultGridColumns ( )
264+ {
265+ ResultGrid . Columns . Clear ( ) ;
266+ if ( DataContext is not SqlResultPageViewModel viewModel || viewModel . RowsView ? . Table is not DataTable table )
267+ return ;
268+
269+ foreach ( DataColumn column in table . Columns )
270+ {
271+ string columnName = column . ColumnName ;
272+ ResultGrid . Columns . Add ( new DataGridTemplateColumn
273+ {
274+ Header = columnName ,
275+ SortMemberPath = columnName ,
276+ IsReadOnly = ! viewModel . IsColumnEditable ( columnName ) ,
277+ CellTemplate = new FuncDataTemplate < DataRowView > ( ( row , _ ) =>
278+ new TextBlock
279+ {
280+ Text = GetCellDisplayText ( row , columnName ) ,
281+ TextTrimming = TextTrimming . CharacterEllipsis ,
282+ } ) ,
283+ CellEditingTemplate = new FuncDataTemplate < DataRowView > ( ( row , _ ) =>
284+ new TextBox
285+ {
286+ Text = GetCellDisplayText ( row , columnName ) ,
287+ } ) ,
288+ } ) ;
289+ }
290+ }
291+
292+ private static string GetCellDisplayText ( DataRowView row , string columnName )
293+ {
294+ if ( ! row . Row . Table . Columns . Contains ( columnName ) )
295+ return string . Empty ;
296+
297+ object ? value = row . Row [ columnName ] ;
298+ if ( value is null || value == DBNull . Value )
299+ return string . Empty ;
300+
301+ return value . ToString ( ) ?? string . Empty ;
302+ }
303+
304+ private static FilePickerFileType GetExportFileType ( SqlEditorReportType reportType )
305+ {
306+ return reportType switch
307+ {
308+ SqlEditorReportType . JsonContract => new FilePickerFileType ( "JSON" )
309+ {
310+ Patterns = [ "*.json" ] ,
311+ MimeTypes = [ "application/json" , "text/plain" ] ,
312+ } ,
313+ SqlEditorReportType . CsvData => new FilePickerFileType ( "CSV" )
314+ {
315+ Patterns = [ "*.csv" ] ,
316+ MimeTypes = [ "text/csv" , "text/plain" ] ,
317+ } ,
318+ SqlEditorReportType . ExcelWorkbook => new FilePickerFileType ( "Excel" )
319+ {
320+ Patterns = [ "*.xlsx" ] ,
321+ MimeTypes = [ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ] ,
322+ } ,
323+ _ => new FilePickerFileType ( "HTML" )
324+ {
325+ Patterns = [ "*.html" , "*.htm" ] ,
326+ MimeTypes = [ "text/html" , "text/plain" ] ,
327+ } ,
328+ } ;
329+ }
330+
202331 private static IBrush ResolveBrush ( string key , IBrush fallback )
203332 {
204333 if ( Application . Current ? . TryFindResource ( key , out object ? resource ) == true && resource is IBrush brush )
0 commit comments