Add book records dialog
This commit is contained in:
parent
ddd6a3b279
commit
6417aee780
143
Source/ApplicationServices/RecordExporter.cs
Normal file
143
Source/ApplicationServices/RecordExporter.cs
Normal file
@ -0,0 +1,143 @@
|
||||
using AudibleApi.Common;
|
||||
using CsvHelper;
|
||||
using DataLayer;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NPOI.XSSF.UserModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace ApplicationServices
|
||||
{
|
||||
public static class RecordExporter
|
||||
{
|
||||
public static void ToXlsx(string saveFilePath, IEnumerable<IRecord> records)
|
||||
{
|
||||
if (!records.Any())
|
||||
return;
|
||||
|
||||
using var workbook = new XSSFWorkbook();
|
||||
var sheet = workbook.CreateSheet("Records");
|
||||
|
||||
var detailSubtotalFont = workbook.CreateFont();
|
||||
detailSubtotalFont.IsBold = true;
|
||||
|
||||
var detailSubtotalCellStyle = workbook.CreateCellStyle();
|
||||
detailSubtotalCellStyle.SetFont(detailSubtotalFont);
|
||||
|
||||
// headers
|
||||
var rowIndex = 0;
|
||||
var row = sheet.CreateRow(rowIndex);
|
||||
|
||||
var columns = new List<string>
|
||||
{
|
||||
nameof(IRecord.RecordType),
|
||||
nameof(IRecord.Created),
|
||||
nameof(IRecord.Start) + "_ms",
|
||||
};
|
||||
|
||||
if (records.OfType<IAnnotation>().Any())
|
||||
{
|
||||
columns.Add(nameof(IAnnotation.AnnotationId));
|
||||
columns.Add(nameof(IAnnotation.LastModified));
|
||||
}
|
||||
if (records.OfType<IRangeAnnotation>().Any())
|
||||
{
|
||||
columns.Add(nameof(IRangeAnnotation.End) + "_ms");
|
||||
columns.Add(nameof(IRangeAnnotation.Text));
|
||||
}
|
||||
if (records.OfType<Clip>().Any())
|
||||
columns.Add(nameof(Clip.Title));
|
||||
|
||||
var col = 0;
|
||||
foreach (var c in columns)
|
||||
{
|
||||
var cell = row.CreateCell(col++);
|
||||
cell.SetCellValue(c);
|
||||
cell.CellStyle = detailSubtotalCellStyle;
|
||||
}
|
||||
|
||||
var dateFormat = workbook.CreateDataFormat();
|
||||
var dateStyle = workbook.CreateCellStyle();
|
||||
dateStyle.DataFormat = dateFormat.GetFormat("MM/dd/yyyy HH:mm:ss");
|
||||
|
||||
// Add data rows
|
||||
foreach (var record in records)
|
||||
{
|
||||
col = 0;
|
||||
|
||||
row = sheet.CreateRow(++rowIndex);
|
||||
|
||||
row.CreateCell(col++).SetCellValue(record.RecordType);
|
||||
|
||||
var dateCreatedCell = row.CreateCell(col++);
|
||||
dateCreatedCell.CellStyle = dateStyle;
|
||||
dateCreatedCell.SetCellValue(record.Created.DateTime);
|
||||
|
||||
row.CreateCell(col++).SetCellValue(record.Start.TotalMilliseconds);
|
||||
|
||||
if (record is IAnnotation annotation)
|
||||
{
|
||||
row.CreateCell(col++).SetCellValue(annotation.AnnotationId);
|
||||
|
||||
var lastModifiedCell = row.CreateCell(col++);
|
||||
lastModifiedCell.CellStyle = dateStyle;
|
||||
lastModifiedCell.SetCellValue(annotation.LastModified.DateTime);
|
||||
|
||||
if (annotation is IRangeAnnotation rangeAnnotation)
|
||||
{
|
||||
row.CreateCell(col++).SetCellValue(rangeAnnotation.End.TotalMilliseconds);
|
||||
row.CreateCell(col++).SetCellValue(rangeAnnotation.Text);
|
||||
|
||||
if (rangeAnnotation is Clip clip)
|
||||
row.CreateCell(col++).SetCellValue(clip.Title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using var fileData = new System.IO.FileStream(saveFilePath, System.IO.FileMode.Create);
|
||||
workbook.Write(fileData);
|
||||
}
|
||||
|
||||
public static void ToJson(string saveFilePath, LibraryBook libraryBook, IEnumerable<IRecord> records)
|
||||
{
|
||||
if (!records.Any())
|
||||
return;
|
||||
|
||||
var recordsObj = new JObject
|
||||
{
|
||||
{ "title", libraryBook.Book.Title},
|
||||
{ "asin", libraryBook.Book.AudibleProductId},
|
||||
{ "exportTime", DateTime.Now},
|
||||
{ "records", JArray.FromObject(records) }
|
||||
};
|
||||
|
||||
System.IO.File.WriteAllText(saveFilePath, recordsObj.ToString(Newtonsoft.Json.Formatting.Indented));
|
||||
}
|
||||
|
||||
public static void ToCsv(string saveFilePath, IEnumerable<IRecord> records)
|
||||
{
|
||||
if (!records.Any())
|
||||
return;
|
||||
|
||||
using var writer = new System.IO.StreamWriter(saveFilePath);
|
||||
using var csv = new CsvWriter(writer, System.Globalization.CultureInfo.CurrentCulture);
|
||||
|
||||
//Write headers for the present type that has the most properties
|
||||
if (records.OfType<Clip>().Any())
|
||||
csv.WriteHeader(typeof(Clip));
|
||||
else if (records.OfType<IRangeAnnotation>().Any())
|
||||
csv.WriteHeader(typeof(IRangeAnnotation));
|
||||
else if (records.OfType<IAnnotation>().Any())
|
||||
csv.WriteHeader(typeof(IAnnotation));
|
||||
else
|
||||
csv.WriteHeader(typeof(IRecord));
|
||||
|
||||
csv.NextRecord();
|
||||
csv.WriteRecords(records.OfType<Clip>());
|
||||
csv.WriteRecords(records.OfType<Note>());
|
||||
csv.WriteRecords(records.OfType<Bookmark>());
|
||||
csv.WriteRecords(records.OfType<LastHeard>());
|
||||
}
|
||||
}
|
||||
}
|
||||
231
Source/LibationWinForms/Dialogs/BookRecordsDialog.Designer.cs
generated
Normal file
231
Source/LibationWinForms/Dialogs/BookRecordsDialog.Designer.cs
generated
Normal file
@ -0,0 +1,231 @@
|
||||
namespace LibationWinForms.Dialogs
|
||||
{
|
||||
partial class BookRecordsDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.syncBindingSource = new LibationWinForms.GridView.SyncBindingSource(this.components);
|
||||
this.dataGridView1 = new System.Windows.Forms.DataGridView();
|
||||
this.checkboxColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn();
|
||||
this.typeColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.createdColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.startTimeColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.modifiedColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.endTimeColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.noteColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.titleColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.checkAllBtn = new System.Windows.Forms.Button();
|
||||
this.uncheckAllBtn = new System.Windows.Forms.Button();
|
||||
this.deleteCheckedBtn = new System.Windows.Forms.Button();
|
||||
this.exportAllBtn = new System.Windows.Forms.Button();
|
||||
this.exportCheckedBtn = new System.Windows.Forms.Button();
|
||||
((System.ComponentModel.ISupportInitialize)(this.syncBindingSource)).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// dataGridView1
|
||||
//
|
||||
this.dataGridView1.AllowUserToAddRows = false;
|
||||
this.dataGridView1.AllowUserToDeleteRows = false;
|
||||
this.dataGridView1.AllowUserToResizeRows = false;
|
||||
this.dataGridView1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
||||
this.checkboxColumn,
|
||||
this.typeColumn,
|
||||
this.createdColumn,
|
||||
this.startTimeColumn,
|
||||
this.modifiedColumn,
|
||||
this.endTimeColumn,
|
||||
this.noteColumn,
|
||||
this.titleColumn});
|
||||
this.dataGridView1.Location = new System.Drawing.Point(0, 0);
|
||||
this.dataGridView1.Name = "dataGridView1";
|
||||
this.dataGridView1.RowHeadersVisible = false;
|
||||
this.dataGridView1.RowTemplate.Height = 25;
|
||||
this.dataGridView1.Size = new System.Drawing.Size(334, 291);
|
||||
this.dataGridView1.TabIndex = 0;
|
||||
//
|
||||
// checkboxColumn
|
||||
//
|
||||
this.checkboxColumn.DataPropertyName = "IsChecked";
|
||||
this.checkboxColumn.HeaderText = "Checked";
|
||||
this.checkboxColumn.Name = "checkboxColumn";
|
||||
this.checkboxColumn.Width = 60;
|
||||
//
|
||||
// typeColumn
|
||||
//
|
||||
this.typeColumn.DataPropertyName = "Type";
|
||||
this.typeColumn.HeaderText = "Type";
|
||||
this.typeColumn.Name = "typeColumn";
|
||||
this.typeColumn.ReadOnly = true;
|
||||
this.typeColumn.Width = 80;
|
||||
//
|
||||
// createdColumn
|
||||
//
|
||||
this.createdColumn.DataPropertyName = "Created";
|
||||
this.createdColumn.HeaderText = "Created";
|
||||
this.createdColumn.Name = "createdColumn";
|
||||
this.createdColumn.ReadOnly = true;
|
||||
//
|
||||
// startTimeColumn
|
||||
//
|
||||
this.startTimeColumn.DataPropertyName = "Start";
|
||||
this.startTimeColumn.HeaderText = "Start";
|
||||
this.startTimeColumn.Name = "startTimeColumn";
|
||||
this.startTimeColumn.ReadOnly = true;
|
||||
//
|
||||
// modifiedColumn
|
||||
//
|
||||
this.modifiedColumn.DataPropertyName = "Modified";
|
||||
this.modifiedColumn.HeaderText = "Modified";
|
||||
this.modifiedColumn.Name = "modifiedColumn";
|
||||
this.modifiedColumn.ReadOnly = true;
|
||||
//
|
||||
// endTimeColumn
|
||||
//
|
||||
this.endTimeColumn.DataPropertyName = "End";
|
||||
this.endTimeColumn.HeaderText = "End";
|
||||
this.endTimeColumn.Name = "endTimeColumn";
|
||||
this.endTimeColumn.ReadOnly = true;
|
||||
//
|
||||
// noteColumn
|
||||
//
|
||||
this.noteColumn.DataPropertyName = "Note";
|
||||
this.noteColumn.HeaderText = "Note";
|
||||
this.noteColumn.Name = "noteColumn";
|
||||
this.noteColumn.ReadOnly = true;
|
||||
//
|
||||
// titleColumn
|
||||
//
|
||||
this.titleColumn.DataPropertyName = "Title";
|
||||
this.titleColumn.HeaderText = "Title";
|
||||
this.titleColumn.Name = "titleColumn";
|
||||
this.titleColumn.ReadOnly = true;
|
||||
//
|
||||
// checkAllBtn
|
||||
//
|
||||
this.checkAllBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.checkAllBtn.Location = new System.Drawing.Point(12, 297);
|
||||
this.checkAllBtn.Name = "checkAllBtn";
|
||||
this.checkAllBtn.Size = new System.Drawing.Size(80, 23);
|
||||
this.checkAllBtn.TabIndex = 1;
|
||||
this.checkAllBtn.Text = "Check All";
|
||||
this.checkAllBtn.UseVisualStyleBackColor = true;
|
||||
this.checkAllBtn.Click += new System.EventHandler(this.checkAllBtn_Click);
|
||||
//
|
||||
// uncheckAllBtn
|
||||
//
|
||||
this.uncheckAllBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.uncheckAllBtn.Location = new System.Drawing.Point(12, 326);
|
||||
this.uncheckAllBtn.Name = "uncheckAllBtn";
|
||||
this.uncheckAllBtn.Size = new System.Drawing.Size(80, 23);
|
||||
this.uncheckAllBtn.TabIndex = 2;
|
||||
this.uncheckAllBtn.Text = "Uncheck All";
|
||||
this.uncheckAllBtn.UseVisualStyleBackColor = true;
|
||||
this.uncheckAllBtn.Click += new System.EventHandler(this.uncheckAllBtn_Click);
|
||||
//
|
||||
// deleteCheckedBtn
|
||||
//
|
||||
this.deleteCheckedBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.deleteCheckedBtn.Location = new System.Drawing.Point(115, 297);
|
||||
this.deleteCheckedBtn.Margin = new System.Windows.Forms.Padding(20, 3, 3, 3);
|
||||
this.deleteCheckedBtn.Name = "deleteCheckedBtn";
|
||||
this.deleteCheckedBtn.Size = new System.Drawing.Size(61, 52);
|
||||
this.deleteCheckedBtn.TabIndex = 3;
|
||||
this.deleteCheckedBtn.Text = "Delete Checked";
|
||||
this.deleteCheckedBtn.UseVisualStyleBackColor = true;
|
||||
this.deleteCheckedBtn.Click += new System.EventHandler(this.deleteCheckedBtn_Click);
|
||||
//
|
||||
// exportAllBtn
|
||||
//
|
||||
this.exportAllBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.exportAllBtn.Location = new System.Drawing.Point(221, 326);
|
||||
this.exportAllBtn.Name = "exportAllBtn";
|
||||
this.exportAllBtn.Size = new System.Drawing.Size(101, 23);
|
||||
this.exportAllBtn.TabIndex = 4;
|
||||
this.exportAllBtn.Text = "Export All";
|
||||
this.exportAllBtn.UseVisualStyleBackColor = true;
|
||||
this.exportAllBtn.Click += new System.EventHandler(this.exportAllBtn_Click);
|
||||
//
|
||||
// exportCheckedBtn
|
||||
//
|
||||
this.exportCheckedBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.exportCheckedBtn.Location = new System.Drawing.Point(221, 297);
|
||||
this.exportCheckedBtn.Name = "exportCheckedBtn";
|
||||
this.exportCheckedBtn.Size = new System.Drawing.Size(101, 23);
|
||||
this.exportCheckedBtn.TabIndex = 5;
|
||||
this.exportCheckedBtn.Text = "Export Checked";
|
||||
this.exportCheckedBtn.UseVisualStyleBackColor = true;
|
||||
this.exportCheckedBtn.Click += new System.EventHandler(this.exportCheckedBtn_Click);
|
||||
//
|
||||
// BookRecordsDialog
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(334, 361);
|
||||
this.Controls.Add(this.exportCheckedBtn);
|
||||
this.Controls.Add(this.exportAllBtn);
|
||||
this.Controls.Add(this.deleteCheckedBtn);
|
||||
this.Controls.Add(this.uncheckAllBtn);
|
||||
this.Controls.Add(this.checkAllBtn);
|
||||
this.Controls.Add(this.dataGridView1);
|
||||
this.KeyPreview = true;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.MinimumSize = new System.Drawing.Size(350, 400);
|
||||
this.Name = "BookRecordsDialog";
|
||||
this.Text = "Book Dialog";
|
||||
this.Shown += new System.EventHandler(this.BookRecordsDialog_Shown);
|
||||
((System.ComponentModel.ISupportInitialize)(this.syncBindingSource)).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit();
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.DataGridView dataGridView1;
|
||||
private LibationWinForms.GridView.SyncBindingSource syncBindingSource;
|
||||
private System.Windows.Forms.Button checkAllBtn;
|
||||
private System.Windows.Forms.Button uncheckAllBtn;
|
||||
private System.Windows.Forms.Button deleteCheckedBtn;
|
||||
private System.Windows.Forms.Button exportAllBtn;
|
||||
private System.Windows.Forms.Button exportCheckedBtn;
|
||||
private System.Windows.Forms.DataGridViewCheckBoxColumn checkboxColumn;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn typeColumn;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn createdColumn;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn startTimeColumn;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn modifiedColumn;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn endTimeColumn;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn noteColumn;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn titleColumn;
|
||||
}
|
||||
}
|
||||
227
Source/LibationWinForms/Dialogs/BookRecordsDialog.cs
Normal file
227
Source/LibationWinForms/Dialogs/BookRecordsDialog.cs
Normal file
@ -0,0 +1,227 @@
|
||||
using ApplicationServices;
|
||||
using AudibleApi.Common;
|
||||
using DataLayer;
|
||||
using FileLiberator;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms.Dialogs
|
||||
{
|
||||
public partial class BookRecordsDialog : Form
|
||||
{
|
||||
private readonly Func<ScrollBar> VScrollBar;
|
||||
private readonly LibraryBook libraryBook;
|
||||
private BookRecordBindingList bookRecordEntries;
|
||||
|
||||
public BookRecordsDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
if (!DesignMode)
|
||||
{
|
||||
//Prevent the designer from auto-generating columns
|
||||
dataGridView1.AutoGenerateColumns = false;
|
||||
dataGridView1.DataSource = syncBindingSource;
|
||||
}
|
||||
|
||||
this.SetLibationIcon();
|
||||
|
||||
VScrollBar =
|
||||
typeof(DataGridView)
|
||||
.GetProperty("VerticalScrollBar", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
|
||||
.GetMethod
|
||||
.CreateDelegate<Func<ScrollBar>>(dataGridView1);
|
||||
|
||||
this.RestoreSizeAndLocation(LibationFileManager.Configuration.Instance);
|
||||
FormClosing += (_, _) => this.SaveSizeAndLocation(LibationFileManager.Configuration.Instance);
|
||||
}
|
||||
|
||||
public BookRecordsDialog(LibraryBook libraryBook) : this()
|
||||
{
|
||||
this.libraryBook = libraryBook;
|
||||
|
||||
Text = $"{libraryBook.Book.Title} - Clips and Bookmarks";
|
||||
}
|
||||
|
||||
private async void BookRecordsDialog_Shown(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var api = await libraryBook.GetApiAsync();
|
||||
var records = await api.GetRecordsAsync(libraryBook.Book.AudibleProductId);
|
||||
|
||||
bookRecordEntries = new BookRecordBindingList(records.Select(r => new BookRecordEntry(r)));
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex, "Failed to retrieve records for {libraryBook}", libraryBook);
|
||||
bookRecordEntries = new();
|
||||
}
|
||||
finally
|
||||
{
|
||||
syncBindingSource.DataSource = bookRecordEntries;
|
||||
|
||||
//Autosize columns and resize form to column width so no horizontal scroll bar is necessary.
|
||||
dataGridView1.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
|
||||
var columnWidth = dataGridView1.Columns.OfType<DataGridViewColumn>().Sum(c => c.Width);
|
||||
Width = Width - dataGridView1.Width + columnWidth + dataGridView1.Margin.Right + (VScrollBar().Visible? VScrollBar().ClientSize.Width : 0);
|
||||
}
|
||||
}
|
||||
|
||||
#region Buttons
|
||||
|
||||
private void exportCheckedBtn_Click(object sender, EventArgs e)
|
||||
=> saveRecords(bookRecordEntries.Where(r => r.IsChecked).Select(r => r.Record));
|
||||
|
||||
private void exportAllBtn_Click(object sender, EventArgs e)
|
||||
=> saveRecords(bookRecordEntries.Select(r => r.Record));
|
||||
|
||||
private void uncheckAllBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
foreach (var record in bookRecordEntries)
|
||||
record.IsChecked = false;
|
||||
}
|
||||
|
||||
private void checkAllBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
foreach (var record in bookRecordEntries)
|
||||
record.IsChecked = true;
|
||||
}
|
||||
|
||||
private async void deleteCheckedBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
var records = bookRecordEntries.Where(r => r.IsChecked).Select(r => r.Record).ToList();
|
||||
|
||||
if (!records.Any()) return;
|
||||
|
||||
bool success = false;
|
||||
try
|
||||
{
|
||||
var api = await libraryBook.GetApiAsync();
|
||||
success = await api.DeleteRecordsAsync(libraryBook.Book.AudibleProductId, records);
|
||||
records = await api.GetRecordsAsync(libraryBook.Book.AudibleProductId);
|
||||
|
||||
var removed = bookRecordEntries.ExceptBy(records, r => r.Record).ToList();
|
||||
|
||||
foreach (var r in removed)
|
||||
bookRecordEntries.Remove(r);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex, ex.Message);
|
||||
}
|
||||
if (!success)
|
||||
MessageBox.Show(this, $"Libation was unable to delete the {records.Count} selected records", "Deletion Failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void saveRecords(IEnumerable<IRecord> records)
|
||||
{
|
||||
try
|
||||
{
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
Title = "Where to export records",
|
||||
AddExtension = true,
|
||||
FileName = $"{libraryBook.Book.Title} - Records",
|
||||
DefaultExt = "xlsx",
|
||||
Filter = "Excel Workbook (*.xlsx)|*.xlsx|CSV files (*.csv)|*.csv|JSON files (*.json)|*.json" // + "|All files (*.*)|*.*"
|
||||
};
|
||||
|
||||
if (saveFileDialog.ShowDialog() != DialogResult.OK)
|
||||
return;
|
||||
|
||||
// FilterIndex is 1-based, NOT 0-based
|
||||
switch (saveFileDialog.FilterIndex)
|
||||
{
|
||||
case 1: // xlsx
|
||||
default:
|
||||
RecordExporter.ToXlsx(saveFileDialog.FileName, records);
|
||||
break;
|
||||
case 2: // csv
|
||||
RecordExporter.ToCsv(saveFileDialog.FileName, records);
|
||||
break;
|
||||
case 3: // json
|
||||
RecordExporter.ToJson(saveFileDialog.FileName, libraryBook, records);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBoxLib.ShowAdminAlert(this, "Error attempting to export your library.", "Error exporting", ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
if (e.KeyCode == Keys.Escape) Close();
|
||||
base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
#region dataGridView Bindings
|
||||
|
||||
private class BookRecordBindingList : BindingList<BookRecordEntry>
|
||||
{
|
||||
private PropertyDescriptor _propertyDescriptor;
|
||||
private ListSortDirection _listSortDirection;
|
||||
private bool _isSortedCore;
|
||||
|
||||
protected override PropertyDescriptor SortPropertyCore => _propertyDescriptor;
|
||||
protected override ListSortDirection SortDirectionCore => _listSortDirection;
|
||||
protected override bool IsSortedCore => _isSortedCore;
|
||||
protected override bool SupportsSortingCore => true;
|
||||
public BookRecordBindingList() : base(new List<BookRecordEntry>()) { }
|
||||
public BookRecordBindingList(IEnumerable<BookRecordEntry> records) : base(records.ToList()) { }
|
||||
protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
|
||||
{
|
||||
var itemsList = (List<BookRecordEntry>)Items;
|
||||
|
||||
var sorted =
|
||||
direction is ListSortDirection.Ascending ? itemsList.OrderBy(prop.GetValue).ToList()
|
||||
: itemsList.OrderByDescending(prop.GetValue).ToList();
|
||||
|
||||
itemsList.Clear();
|
||||
itemsList.AddRange(sorted);
|
||||
|
||||
_propertyDescriptor = prop;
|
||||
_listSortDirection = direction;
|
||||
_isSortedCore = true;
|
||||
|
||||
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
|
||||
}
|
||||
}
|
||||
|
||||
private class BookRecordEntry : GridView.AsyncNotifyPropertyChanged
|
||||
{
|
||||
private const string DateFormat = "yyyy-MM-dd HH\\:mm";
|
||||
private bool _ischecked;
|
||||
public IRecord Record { get; }
|
||||
public bool IsChecked { get => _ischecked; set { _ischecked = value; NotifyPropertyChanged(); } }
|
||||
public string Type => Record.GetType().Name;
|
||||
public string Start => formatTimeSpan(Record.Start);
|
||||
public string Created => Record.Created.ToString(DateFormat);
|
||||
public string Modified => Record is IAnnotation annotation ? annotation.Created.ToString(DateFormat) : string.Empty;
|
||||
public string End => Record is IRangeAnnotation range ? formatTimeSpan(range.End) : string.Empty;
|
||||
public string Note => Record is IRangeAnnotation range ? range.Text : string.Empty;
|
||||
public string Title => Record is Clip range ? range.Title : string.Empty;
|
||||
public BookRecordEntry(IRecord record) => Record = record;
|
||||
|
||||
private static string formatTimeSpan(TimeSpan timeSpan)
|
||||
{
|
||||
int h = (int)timeSpan.TotalHours;
|
||||
int m = timeSpan.Minutes;
|
||||
int s = timeSpan.Seconds;
|
||||
int ms = timeSpan.Milliseconds;
|
||||
|
||||
return ms == 0 ? $"{h:d2}:{m:d2}:{s:d2}" : $"{h:d2}:{m:d2}:{s:d2}.{ms:d3}";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
87
Source/LibationWinForms/Dialogs/BookRecordsDialog.resx
Normal file
87
Source/LibationWinForms/Dialogs/BookRecordsDialog.resx
Normal file
@ -0,0 +1,87 @@
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="syncBindingSource.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
<metadata name="checkboxColumn.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="typeColumn.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="createdColumn.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="startTimeColumn.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="modifiedColumn.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="endTimeColumn.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="noteColumn.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="titleColumn.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
</root>
|
||||
@ -8,6 +8,7 @@ using ApplicationServices;
|
||||
using DataLayer;
|
||||
using Dinah.Core.WindowsDesktop.Forms;
|
||||
using LibationFileManager;
|
||||
using LibationWinForms.Dialogs;
|
||||
|
||||
namespace LibationWinForms.GridView
|
||||
{
|
||||
@ -174,11 +175,16 @@ namespace LibationWinForms.GridView
|
||||
}
|
||||
};
|
||||
|
||||
var bookRecordMenuItem = new ToolStripMenuItem { Text = "View Bookmarks/Clips" };
|
||||
bookRecordMenuItem.Click += (_, _) => new BookRecordsDialog(entry.LibraryBook).ShowDialog(this);
|
||||
|
||||
var stopLightContextMenu = new ContextMenuStrip();
|
||||
stopLightContextMenu.Items.Add(setDownloadMenuItem);
|
||||
stopLightContextMenu.Items.Add(setNotDownloadMenuItem);
|
||||
stopLightContextMenu.Items.Add(removeMenuItem);
|
||||
stopLightContextMenu.Items.Add(locateFileMenuItem);
|
||||
stopLightContextMenu.Items.Add(new ToolStripSeparator());
|
||||
stopLightContextMenu.Items.Add(bookRecordMenuItem);
|
||||
|
||||
e.ContextMenuStrip = stopLightContextMenu;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user