Merge pull request #710 from Mbucari/master

Batch of Fixxed Issues
This commit is contained in:
rmcrackan 2023-08-12 21:20:37 -04:00 committed by GitHub
commit 415e6e7bc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 507 additions and 293 deletions

View File

@ -1,28 +1,39 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" enable-background="new 0 0 512 512"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 524 524" enable-background="new 0 0 524 524">
<path id="glass" d= <defs>
"M139,2 <g id="glass">
A 192,200 0 0 0 103,84 <path fill-rule="evenodd" d=
A 222,334 41 0 0 241,320 "M262,8
V478 h-117
H160 a 192,200 0 0 0 -36,82
A 16,16 0 0 0 160,510 a 222,334 41 0 0 138,236
H352 v158
A16 16 0 0 0 352,478 h-81
H271 a 16,16 0 0 0 0,32
V320 h192
A 222,334 -41 0 0 409,84 a 16 16 0 0 0 0,-32
A 192,200 0 0 0 373,2 h-81
M355,32 v-158
A 192,200 0 0 1 381,127 a 222,334 -41 0 0 138,-236
A 187.5,334 -35 0 1 256,286 a 192,200 0 0 0 -36,-82
A 187.5,334 35 0 1 131,127 h-117
A 192,200 0 0 1 157,32 m-99,30
H355 a 192,200 0 0 0 -26,95
z" /> a 187.5,334 35 0 0 125,159
a 187.5,334 -35 0 0 125,-159
<path id="wine-level" d= a 192,200 0 0 0 -26,-95
"M146,128 h-198
A 168,300 35 0 0 256,270 z"/>
A 168,300 -35 0 0 366,128 </g>
z"/> <g id="wine-level">
<path d=
"M158,136
a 168,305 35 0 0 104,136
a 168,305 -35 0 0 104,-136
z"/>
</g>
</defs>
<use href="#glass" stroke="#ffffffa0" stroke-width="16" fill="Transparent" />
<use href="#wine-level" stroke="#ffffffa0" stroke-width="16" fill="Transparent" />
<use href="#glass" fill="Black" />
<use href="#wine-level" fill="Black" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 585 B

After

Width:  |  Height:  |  Size: 968 B

View File

@ -106,6 +106,7 @@ namespace DataLayer
ReplaceAuthors(authors); ReplaceAuthors(authors);
ReplaceNarrators(narrators); ReplaceNarrators(narrators);
} }
public void UpdateTitle(string title, string subtitle) public void UpdateTitle(string title, string subtitle)
{ {
Title = title?.Trim() ?? ""; Title = title?.Trim() ?? "";
@ -113,6 +114,9 @@ namespace DataLayer
_titleWithSubtitle = null; _titleWithSubtitle = null;
} }
public void UpdateLengthInMinutes(int lengthInMinutes)
=> LengthInMinutes = lengthInMinutes;
#region contributors, authors, narrators #region contributors, authors, narrators
internal HashSet<BookContributor> ContributorsLink { get; private set; } internal HashSet<BookContributor> ContributorsLink { get; private set; }

View File

@ -149,6 +149,8 @@ namespace DtoImporterService
{ {
var item = importItem.DtoItem; var item = importItem.DtoItem;
book.UpdateLengthInMinutes(item.LengthInMinutes);
// Update the book titles, since formatting can change // Update the book titles, since formatting can change
book.UpdateTitle(item.Title, item.Subtitle); book.UpdateTitle(item.Title, item.Subtitle);

View File

@ -21,7 +21,8 @@ namespace FileLiberator
var series = libraryBook.Book.SeriesLink.SingleOrDefault(); var series = libraryBook.Book.SeriesLink.SingleOrDefault();
if (series is not null) if (series is not null)
{ {
var seriesParent = ApplicationServices.DbContexts.GetContext().GetLibraryBook_Flat_NoTracking(series.Series.AudibleSeriesId); using var context = ApplicationServices.DbContexts.GetContext();
var seriesParent = context.GetLibraryBook_Flat_NoTracking(series.Series.AudibleSeriesId);
if (seriesParent is not null) if (seriesParent is not null)
{ {

View File

@ -67,13 +67,13 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.2" /> <PackageReference Include="Avalonia" Version="11.0.3" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.2" /> <PackageReference Include="Avalonia.Desktop" Version="11.0.3" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.2" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.3" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.2" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.3" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.2" /> <PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.3" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.2" /> <PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\HangoverBase\HangoverBase.csproj" /> <ProjectReference Include="..\HangoverBase\HangoverBase.csproj" />

View File

@ -44,9 +44,9 @@
<SolidColorBrush x:Key="CancelRed" Color="#802727" /> <SolidColorBrush x:Key="CancelRed" Color="#802727" />
<SolidColorBrush x:Key="IconFill" Color="#DCE0DF" /> <SolidColorBrush x:Key="IconFill" Color="#DCE0DF" />
<SolidColorBrush x:Key="StoplightRed" Color="#5F0707" /> <SolidColorBrush x:Key="StoplightRed" Color="#7d1f1f" />
<SolidColorBrush x:Key="StoplightYellow" Color="#5F5B1A" /> <SolidColorBrush x:Key="StoplightYellow" Color="#7d7d1f" />
<SolidColorBrush x:Key="StoplightGreen" Color="#174E15" /> <SolidColorBrush x:Key="StoplightGreen" Color="#1f7d1f" />
<SolidColorBrush x:Key="DisabledGrayBrush" Opacity="0.4" Color="{StaticResource SystemChromeMediumColor}" /> <SolidColorBrush x:Key="DisabledGrayBrush" Opacity="0.4" Color="{StaticResource SystemChromeMediumColor}" />

View File

@ -70,13 +70,13 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.2" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" /> <PackageReference Include="Avalonia.Diagnostics" Version="11.0.3" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
<PackageReference Include="Avalonia" Version="11.0.2" /> <PackageReference Include="Avalonia" Version="11.0.3" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.2" /> <PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.3" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.2" /> <PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.3" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.2" /> <PackageReference Include="Avalonia.Desktop" Version="11.0.3" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.2" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.3" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.2" /> <PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -177,9 +177,12 @@ Libation.
tbx.MinWidth = vm.TextBlockMinWidth; tbx.MinWidth = vm.TextBlockMinWidth;
tbx.Text = message; tbx.Text = message;
var thisScreen = owner.Screens.ScreenFromVisual(owner);
var maxSize = new Size(0.20 * thisScreen.Bounds.Width, 0.9 * thisScreen.Bounds.Height - 55); var thisScreen = owner.Screens?.ScreenFromVisual(owner);
var maxSize
= thisScreen is null ? owner.ClientSize
: new Size(0.20 * thisScreen.Bounds.Width, 0.9 * thisScreen.Bounds.Height - 55);
var desiredMax = new Size(maxSize.Width, maxSize.Height); var desiredMax = new Size(maxSize.Width, maxSize.Height);

View File

@ -1,15 +1,18 @@
using ApplicationServices; using ApplicationServices;
using AudibleUtilities; using AudibleUtilities;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Threading; using Avalonia.Threading;
using DataLayer; using DataLayer;
using LibationAvalonia.Dialogs.Login; using LibationAvalonia.Dialogs.Login;
using LibationFileManager;
using LibationUiBase.GridView; using LibationUiBase.GridView;
using ReactiveUI; using ReactiveUI;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace LibationAvalonia.ViewModels namespace LibationAvalonia.ViewModels
@ -27,8 +30,8 @@ namespace LibationAvalonia.ViewModels
public string FilterString { get; private set; } public string FilterString { get; private set; }
public DataGridCollectionView GridEntries { get; private set; } public DataGridCollectionView GridEntries { get; private set; }
private bool _removeColumnVisivle; private bool _removeColumnVisible;
public bool RemoveColumnVisivle { get => _removeColumnVisivle; private set => this.RaiseAndSetIfChanged(ref _removeColumnVisivle, value); } public bool RemoveColumnVisible { get => _removeColumnVisible; private set => this.RaiseAndSetIfChanged(ref _removeColumnVisible, value); }
public List<LibraryBook> GetVisibleBookEntries() public List<LibraryBook> GetVisibleBookEntries()
=> FilteredInGridEntries? => FilteredInGridEntries?
@ -321,7 +324,7 @@ namespace LibationAvalonia.ViewModels
{ {
foreach (var item in SOURCE) foreach (var item in SOURCE)
item.PropertyChanged -= GridEntry_PropertyChanged; item.PropertyChanged -= GridEntry_PropertyChanged;
RemoveColumnVisivle = false; RemoveColumnVisible = false;
} }
public async Task RemoveCheckedBooksAsync() public async Task RemoveCheckedBooksAsync()
@ -376,7 +379,7 @@ namespace LibationAvalonia.ViewModels
item.PropertyChanged += GridEntry_PropertyChanged; item.PropertyChanged += GridEntry_PropertyChanged;
} }
RemoveColumnVisivle = true; RemoveColumnVisible = true;
RemovableCountChanged?.Invoke(this, 0); RemovableCountChanged?.Invoke(this, 0);
try try
@ -421,5 +424,44 @@ namespace LibationAvalonia.ViewModels
} }
#endregion #endregion
#region Column Widths
public DataGridLength TitleWidth { get => getColumnWidth("Title", 200); set => setColumnWidth("Title", value); }
public DataGridLength AuthorsWidth { get => getColumnWidth("Authors", 100); set => setColumnWidth("Authors", value); }
public DataGridLength NarratorsWidth { get => getColumnWidth("Narrators", 100); set => setColumnWidth("Narrators", value); }
public DataGridLength LengthWidth { get => getColumnWidth("Length", 80); set => setColumnWidth("Length", value); }
public DataGridLength SeriesWidth { get => getColumnWidth("Series", 100); set => setColumnWidth("Series", value); }
public DataGridLength SeriesOrderWidth { get => getColumnWidth("SeriesOrder", 60); set => setColumnWidth("SeriesOrder", value); }
public DataGridLength DescriptionWidth { get => getColumnWidth("Description", 100); set => setColumnWidth("Description", value); }
public DataGridLength CategoryWidth { get => getColumnWidth("Category", 100); set => setColumnWidth("Category", value); }
public DataGridLength ProductRatingWidth { get => getColumnWidth("ProductRating", 95); set => setColumnWidth("ProductRating", value); }
public DataGridLength PurchaseDateWidth { get => getColumnWidth("PurchaseDate", 75); set => setColumnWidth("PurchaseDate", value); }
public DataGridLength MyRatingWidth { get => getColumnWidth("MyRating", 95); set => setColumnWidth("MyRating", value); }
public DataGridLength MiscWidth { get => getColumnWidth("Misc", 140); set => setColumnWidth("Misc", value); }
public DataGridLength LastDownloadWidth { get => getColumnWidth("LastDownload", 100); set => setColumnWidth("LastDownload", value); }
public DataGridLength BookTagsWidth { get => getColumnWidth("BookTags", 100); set => setColumnWidth("BookTags", value); }
private static DataGridLength getColumnWidth(string columnName, double defaultWidth)
=> Configuration.Instance.GridColumnsWidths.TryGetValue(columnName, out var val)
? new DataGridLength(val)
: new DataGridLength(defaultWidth);
private void setColumnWidth(string columnName, DataGridLength width, [CallerMemberName] string propertyName = "")
{
var dictionary = Configuration.Instance.GridColumnsWidths;
var newValue = (int)width.DisplayValue;
var valueSame = dictionary.TryGetValue(columnName, out var val) && val == newValue;
dictionary[columnName] = newValue;
if (!valueSame)
{
Configuration.Instance.GridColumnsWidths = dictionary;
this.RaisePropertyChanged(propertyName);
}
}
#endregion
} }
} }

View File

@ -21,7 +21,7 @@ namespace LibationAvalonia.Views
InitializeComponent(); InitializeComponent();
Configure_Upgrade(); Configure_Upgrade();
Loaded += MainWindow_Loaded; Opened += MainWindow_Opened;
Closing += MainWindow_Closing; Closing += MainWindow_Closing;
LibraryLoaded += MainWindow_LibraryLoaded; LibraryLoaded += MainWindow_LibraryLoaded;
@ -35,7 +35,7 @@ namespace LibationAvalonia.Views
} }
} }
private async void MainWindow_Loaded(object sender, EventArgs e) private async void MainWindow_Opened(object sender, EventArgs e)
{ {
if (Configuration.Instance.FirstLaunch) if (Configuration.Instance.FirstLaunch)
{ {

View File

@ -17,6 +17,7 @@
AutoGenerateColumns="False" AutoGenerateColumns="False"
ItemsSource="{Binding GridEntries}" ItemsSource="{Binding GridEntries}"
CanUserSortColumns="True" BorderThickness="3" CanUserSortColumns="True" BorderThickness="3"
CanUserResizeColumns="True"
CanUserReorderColumns="True"> CanUserReorderColumns="True">
<DataGrid.Styles> <DataGrid.Styles>
@ -48,7 +49,8 @@
<DataGridTemplateColumn <DataGridTemplateColumn
CanUserSort="True" CanUserSort="True"
IsVisible="{Binding RemoveColumnVisivle}" CanUserResize="False"
IsVisible="{Binding RemoveColumnVisible}"
PropertyChanged="RemoveColumn_PropertyChanged" PropertyChanged="RemoveColumn_PropertyChanged"
Header="Remove" Header="Remove"
IsReadOnly="False" IsReadOnly="False"
@ -65,7 +67,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn> </DataGridTemplateColumn>
<controls:DataGridTemplateColumnExt CanUserSort="True" Header="Liberate" SortMemberPath="Liberate" ClipboardContentBinding="{Binding Liberate.ToolTip}"> <controls:DataGridTemplateColumnExt CanUserResize="False" CanUserSort="True" Header="Liberate" SortMemberPath="Liberate" ClipboardContentBinding="{Binding Liberate.ToolTip}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<views:LiberateStatusButton <views:LiberateStatusButton
@ -80,7 +82,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt CanUserSort="False" Header="Cover" SortMemberPath="Cover" ClipboardContentBinding="{Binding LibraryBook.Book.PictureLarge}"> <controls:DataGridTemplateColumnExt CanUserResize="False" CanUserSort="False" Header="Cover" SortMemberPath="Cover" ClipboardContentBinding="{Binding LibraryBook.Book.PictureLarge}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Image Opacity="{CompiledBinding Liberate.Opacity}" Tapped="Cover_Click" Source="{CompiledBinding Cover}" ToolTip.Tip="Click to see full size" /> <Image Opacity="{CompiledBinding Liberate.Opacity}" Tapped="Cover_Click" Source="{CompiledBinding Cover}" ToolTip.Tip="Click to see full size" />
@ -88,7 +90,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt MinWidth="150" Width="2*" Header="Title" CanUserSort="True" SortMemberPath="Title" ClipboardContentBinding="{Binding Title}"> <controls:DataGridTemplateColumnExt Header="Title" MinWidth="10" Width="{Binding TitleWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Title" ClipboardContentBinding="{Binding Title}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
@ -98,7 +100,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt MinWidth="80" Width="1*" Header="Authors" CanUserSort="True" SortMemberPath="Authors" ClipboardContentBinding="{Binding Authors}"> <controls:DataGridTemplateColumnExt Header="Authors" MinWidth="10" Width="{Binding AuthorsWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Authors" ClipboardContentBinding="{Binding Authors}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
@ -108,7 +110,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt MinWidth="80" Width="1*" Header="Narrators" CanUserSort="True" SortMemberPath="Narrators" ClipboardContentBinding="{Binding Narrators}"> <controls:DataGridTemplateColumnExt Header="Narrators" MinWidth="10" Width="{Binding NarratorsWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Narrators" ClipboardContentBinding="{Binding Narrators}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
@ -118,7 +120,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt Width="90" Header="Length" CanUserSort="True" SortMemberPath="Length" ClipboardContentBinding="{Binding Length}"> <controls:DataGridTemplateColumnExt Header="Length" MinWidth="10" Width="{Binding LengthWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Length" ClipboardContentBinding="{Binding Length}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
@ -128,7 +130,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt MinWidth="80" Width="1*" Header="Series" CanUserSort="True" SortMemberPath="Series" ClipboardContentBinding="{Binding Series}"> <controls:DataGridTemplateColumnExt Header="Series" MinWidth="10" Width="{Binding SeriesWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Series" ClipboardContentBinding="{Binding Series}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
@ -138,7 +140,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt Width="Auto" Header="Series&#xA;Order" CanUserSort="True" SortMemberPath="SeriesOrder" ClipboardContentBinding="{Binding Series}"> <controls:DataGridTemplateColumnExt Header="Series&#xA;Order" MinWidth="10" Width="{Binding SeriesOrderWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="SeriesOrder" ClipboardContentBinding="{Binding Series}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
@ -148,7 +150,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt MinWidth="100" Width="1*" Header="Description" CanUserSort="True" SortMemberPath="Description" ClipboardContentBinding="{Binding Description}"> <controls:DataGridTemplateColumnExt Header="Description" MinWidth="10" Width="{Binding DescriptionWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Description" ClipboardContentBinding="{Binding Description}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}" Tapped="Description_Click" ToolTip.Tip="Click to see full description" > <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}" Tapped="Description_Click" ToolTip.Tip="Click to see full description" >
@ -158,7 +160,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt Width="100" Header="Category" CanUserSort="True" SortMemberPath="Category" ClipboardContentBinding="{Binding Category}"> <controls:DataGridTemplateColumnExt Header="Category" MinWidth="10" Width="{Binding CategoryWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Category" ClipboardContentBinding="{Binding Category}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
@ -172,14 +174,14 @@
x:DataType="uibase:IGridEntry" x:DataType="uibase:IGridEntry"
Header="Product&#xA;Rating" Header="Product&#xA;Rating"
IsReadOnly="true" IsReadOnly="true"
Width="115" MinWidth="10" Width="{Binding ProductRatingWidth, Mode=TwoWay}"
SortMemberPath="ProductRating" CanUserSort="True" SortMemberPath="ProductRating" CanUserSort="True"
OpacityBinding="{CompiledBinding Liberate.Opacity}" OpacityBinding="{CompiledBinding Liberate.Opacity}"
BackgroundBinding="{CompiledBinding Liberate.BackgroundBrush}" BackgroundBinding="{CompiledBinding Liberate.BackgroundBrush}"
ClipboardContentBinding="{CompiledBinding ProductRating}" ClipboardContentBinding="{CompiledBinding ProductRating}"
Binding="{CompiledBinding ProductRating}" /> Binding="{CompiledBinding ProductRating}" />
<controls:DataGridTemplateColumnExt Width="90" Header="Purchase&#xA;Date" CanUserSort="True" SortMemberPath="PurchaseDate" ClipboardContentBinding="{Binding PurchaseDate}"> <controls:DataGridTemplateColumnExt Header="Purchase&#xA;Date" MinWidth="10" Width="{Binding PurchaseDateWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="PurchaseDate" ClipboardContentBinding="{Binding PurchaseDate}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
@ -193,14 +195,14 @@
x:DataType="uibase:IGridEntry" x:DataType="uibase:IGridEntry"
Header="My Rating" Header="My Rating"
IsReadOnly="false" IsReadOnly="false"
Width="115" MinWidth="10" Width="{Binding MyRatingWidth, Mode=TwoWay}"
SortMemberPath="MyRating" CanUserSort="True" SortMemberPath="MyRating" CanUserSort="True"
OpacityBinding="{CompiledBinding Liberate.Opacity}" OpacityBinding="{CompiledBinding Liberate.Opacity}"
BackgroundBinding="{CompiledBinding Liberate.BackgroundBrush}" BackgroundBinding="{CompiledBinding Liberate.BackgroundBrush}"
ClipboardContentBinding="{CompiledBinding MyRating}" ClipboardContentBinding="{CompiledBinding MyRating}"
Binding="{CompiledBinding MyRating, Mode=TwoWay}" /> Binding="{CompiledBinding MyRating, Mode=TwoWay}" />
<controls:DataGridTemplateColumnExt Width="135" Header="Misc" CanUserSort="True" SortMemberPath="Misc" ClipboardContentBinding="{Binding Misc}"> <controls:DataGridTemplateColumnExt Header="Misc" MinWidth="10" Width="{Binding MiscWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Misc" ClipboardContentBinding="{Binding Misc}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
@ -210,7 +212,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt Width="102" Header="Last&#xA;Download" CanUserSort="True" SortMemberPath="LastDownload" ClipboardContentBinding="{Binding LastDownload}"> <controls:DataGridTemplateColumnExt Header="Last&#xA;Download" MinWidth="10" Width="{Binding LastDownloadWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="LastDownload" ClipboardContentBinding="{Binding LastDownload}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}" ToolTip.Tip="{CompiledBinding LastDownload.ToolTipText}" DoubleTapped="Version_DoubleClick"> <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}" ToolTip.Tip="{CompiledBinding LastDownload.ToolTipText}" DoubleTapped="Version_DoubleClick">
@ -220,7 +222,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt CanUserSort="True" Width="100" Header="Tags" SortMemberPath="BookTags" ClipboardContentBinding="{Binding BookTags}"> <controls:DataGridTemplateColumnExt Header="Tags" MinWidth="10" Width="{Binding BookTagsWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="BookTags" ClipboardContentBinding="{Binding BookTags}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Button <Button

View File

@ -11,6 +11,7 @@ using LibationAvalonia.Dialogs;
using LibationAvalonia.ViewModels; using LibationAvalonia.ViewModels;
using LibationFileManager; using LibationFileManager;
using LibationUiBase.GridView; using LibationUiBase.GridView;
using ReactiveUI;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -172,87 +173,70 @@ namespace LibationAvalonia.Views
public void ProductsGrid_CellContextMenuStripNeeded(object sender, DataGridCellContextMenuStripNeededEventArgs args) public void ProductsGrid_CellContextMenuStripNeeded(object sender, DataGridCellContextMenuStripNeededEventArgs args)
{ {
var entry = args.GridEntry;
var ctx = new GridContextMenu(entry, '_');
if (args.Column.SortMemberPath is not "Liberate" and not "Cover") if (args.Column.SortMemberPath is not "Liberate" and not "Cover")
{ {
var menuItem = new MenuItem { Header = "_Copy Cell Contents" }; args.ContextMenuItems.Add(new MenuItem
{
menuItem.Click += async (s, e) Header = ctx.CopyCellText,
=> await App.MainWindow.Clipboard.SetTextAsync(args.CellClipboardContents); Command = ReactiveCommand.CreateFromTask(() => App.MainWindow.Clipboard.SetTextAsync(args.CellClipboardContents))
});
args.ContextMenuItems.Add(menuItem);
args.ContextMenuItems.Add(new Separator()); args.ContextMenuItems.Add(new Separator());
} }
var entry = args.GridEntry;
#region Liberate all Episodes #region Liberate all Episodes
if (entry.Liberate.IsSeries) if (entry.Liberate.IsSeries)
{ {
var liberateEpisodesMenuItem = new MenuItem() args.ContextMenuItems.Add(new MenuItem()
{ {
Header = "_Liberate All Episodes", Header = ctx.LiberateEpisodesText,
IsEnabled = ((ISeriesEntry)entry).Children.Any(c => c.Liberate.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload) IsEnabled = ctx.LiberateEpisodesEnabled,
}; Command = ReactiveCommand.Create(() => LiberateSeriesClicked?.Invoke(this, (ISeriesEntry)entry))
});
args.ContextMenuItems.Add(liberateEpisodesMenuItem);
liberateEpisodesMenuItem.Click += (_, _) => LiberateSeriesClicked?.Invoke(this, ((ISeriesEntry)entry));
} }
#endregion #endregion
#region Set Download status to Downloaded #region Set Download status to Downloaded
var setDownloadMenuItem = new MenuItem() args.ContextMenuItems.Add(new MenuItem()
{ {
Header = "Set Download status to '_Downloaded'", Header = ctx.SetDownloadedText,
IsEnabled = entry.Book.UserDefinedItem.BookStatus != LiberatedStatus.Liberated || entry.Liberate.IsSeries IsEnabled = ctx.SetDownloadedEnabled,
}; Command = ReactiveCommand.Create(ctx.SetDownloaded)
});
args.ContextMenuItems.Add(setDownloadMenuItem);
if (entry.Liberate.IsSeries)
setDownloadMenuItem.Click += (_, __) => ((ISeriesEntry)entry).Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.Liberated);
else
setDownloadMenuItem.Click += (_, __) => entry.LibraryBook.UpdateBookStatus(LiberatedStatus.Liberated);
#endregion #endregion
#region Set Download status to Not Downloaded #region Set Download status to Not Downloaded
var setNotDownloadMenuItem = new MenuItem() args.ContextMenuItems.Add(new MenuItem()
{ {
Header = "Set Download status to '_Not Downloaded'", Header = ctx.SetNotDownloadedText,
IsEnabled = entry.Book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated || entry.Liberate.IsSeries IsEnabled = ctx.SetNotDownloadedEnabled,
}; Command = ReactiveCommand.Create(ctx.SetNotDownloaded)
});
args.ContextMenuItems.Add(setNotDownloadMenuItem);
if (entry.Liberate.IsSeries)
setNotDownloadMenuItem.Click += (_, __) => ((ISeriesEntry)entry).Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.NotLiberated);
else
setNotDownloadMenuItem.Click += (_, __) => entry.LibraryBook.UpdateBookStatus(LiberatedStatus.NotLiberated);
#endregion #endregion
#region Remove from library #region Remove from library
var removeMenuItem = new MenuItem() { Header = "_Remove from library" }; args.ContextMenuItems.Add(new MenuItem
{
args.ContextMenuItems.Add(removeMenuItem); Header = ctx.RemoveText,
Command = ReactiveCommand.CreateFromTask(ctx.RemoveAsync)
if (entry.Liberate.IsSeries) });
removeMenuItem.Click += async (_, __) => await ((ISeriesEntry)entry).Children.Select(c => c.LibraryBook).RemoveBooksAsync();
else
removeMenuItem.Click += async (_, __) => await Task.Run(entry.LibraryBook.RemoveBook);
#endregion #endregion
if (!entry.Liberate.IsSeries) if (!entry.Liberate.IsSeries)
{ {
#region Locate file #region Locate file
var locateFileMenuItem = new MenuItem() { Header = "_Locate file..." };
args.ContextMenuItems.Add(locateFileMenuItem); args.ContextMenuItems.Add(new MenuItem
{
locateFileMenuItem.Click += async (_, __) => Header = ctx.LocateFileText,
Command = ReactiveCommand.CreateFromTask(async () =>
{ {
try try
{ {
@ -260,7 +244,7 @@ namespace LibationAvalonia.Views
var openFileDialogOptions = new FilePickerOpenOptions var openFileDialogOptions = new FilePickerOpenOptions
{ {
Title = $"Locate the audio file for '{entry.Book.TitleWithSubtitle}'", Title = ctx.LocateFileDialogTitle,
AllowMultiple = false, AllowMultiple = false,
SuggestedStartLocation = await window.StorageProvider.TryGetFolderFromPathAsync(Configuration.Instance.Books.PathWithoutPrefix), SuggestedStartLocation = await window.StorageProvider.TryGetFolderFromPathAsync(Configuration.Instance.Books.PathWithoutPrefix),
FileTypeFilter = new FilePickerFileType[] FileTypeFilter = new FilePickerFileType[]
@ -277,21 +261,19 @@ namespace LibationAvalonia.Views
} }
catch (Exception ex) catch (Exception ex)
{ {
var msg = "Error saving book's location"; await MessageBox.ShowAdminAlert(null, ctx.LocateFileErrorMessage, ctx.LocateFileErrorMessage, ex);
await MessageBox.ShowAdminAlert(null, msg, msg, ex);
} }
}; })
});
#endregion #endregion
#region Convert to Mp3 #region Convert to Mp3
var convertToMp3MenuItem = new MenuItem args.ContextMenuItems.Add(new MenuItem
{ {
Header = "_Convert to Mp3", Header = ctx.ConvertToMp3Text,
IsEnabled = entry.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated IsEnabled = ctx.ConvertToMp3Enabled,
}; Command = ReactiveCommand.Create(() => ConvertToMp3Clicked?.Invoke(this, entry.LibraryBook))
args.ContextMenuItems.Add(convertToMp3MenuItem); });
convertToMp3MenuItem.Click += (_, _) => ConvertToMp3Clicked?.Invoke(this, entry.LibraryBook);
#endregion #endregion
} }
@ -299,34 +281,72 @@ namespace LibationAvalonia.Views
#region Force Re-Download #region Force Re-Download
if (!entry.Liberate.IsSeries) if (!entry.Liberate.IsSeries)
{ {
var reDownloadMenuItem = new MenuItem() args.ContextMenuItems.Add(new MenuItem()
{ {
Header = "Re-download this audiobook", Header = ctx.ReDownloadText,
IsEnabled = entry.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated IsEnabled = ctx.ReDownloadEnabled,
}; Command = ReactiveCommand.Create(() =>
args.ContextMenuItems.Add(reDownloadMenuItem);
reDownloadMenuItem.Click += (s, _) =>
{ {
//No need to persist this change. It only needs to last long for the file to start downloading //No need to persist this change. It only needs to last long for the file to start downloading
entry.Book.UserDefinedItem.BookStatus = LiberatedStatus.NotLiberated; entry.Book.UserDefinedItem.BookStatus = LiberatedStatus.NotLiberated;
LiberateClicked?.Invoke(s, entry.LibraryBook); LiberateClicked?.Invoke(this, entry.LibraryBook);
}; })
});
} }
#endregion #endregion
args.ContextMenuItems.Add(new Separator()); args.ContextMenuItems.Add(new Separator());
#region Edit Templates
async Task editTemplate<T>(LibraryBook libraryBook, string existingTemplate, Action<string> setNewTemplate)
where T : Templates, LibationFileManager.ITemplate, new()
{
var template = ctx.CreateTemplateEditor<T>(libraryBook, existingTemplate);
var form = new EditTemplateDialog(template);
if (await form.ShowDialog<DialogResult>(this.GetParentWindow()) == DialogResult.OK)
{
setNewTemplate(template.EditingTemplate.TemplateText);
}
}
if (!entry.Liberate.IsSeries)
{
args.ContextMenuItems.Add(new MenuItem
{
Header = ctx.EditTemplatesText,
ItemsSource = new[]
{
new MenuItem
{
Header = ctx.FolderTemplateText,
Command = ReactiveCommand.CreateFromTask(() => editTemplate<Templates.FolderTemplate>(entry.LibraryBook, Configuration.Instance.FolderTemplate, t => Configuration.Instance.FolderTemplate = t))
},
new MenuItem
{
Header = ctx.FileTemplateText,
Command = ReactiveCommand.CreateFromTask(() => editTemplate<Templates.FileTemplate>(entry.LibraryBook, Configuration.Instance.FileTemplate, t => Configuration.Instance.FileTemplate = t))
},
new MenuItem
{
Header = ctx.MultipartTemplateText,
Command = ReactiveCommand.CreateFromTask(() => editTemplate<Templates.ChapterFileTemplate>(entry.LibraryBook, Configuration.Instance.ChapterFileTemplate, t => Configuration.Instance.ChapterFileTemplate = t))
}
}
});
args.ContextMenuItems.Add(new Separator());
}
#endregion
#region View Bookmarks/Clips #region View Bookmarks/Clips
if (!entry.Liberate.IsSeries) if (!entry.Liberate.IsSeries)
{ {
args.ContextMenuItems.Add(new MenuItem
var bookRecordMenuItem = new MenuItem { Header = "View _Bookmarks/Clips" }; {
Header = ctx.ViewBookmarksText,
args.ContextMenuItems.Add(bookRecordMenuItem); Command = ReactiveCommand.CreateFromTask(() => new BookRecordsDialog(entry.LibraryBook).ShowDialog(VisualRoot as Window))
});
bookRecordMenuItem.Click += async (_, _) => await new BookRecordsDialog(entry.LibraryBook).ShowDialog(VisualRoot as Window);
} }
#endregion #endregion
@ -334,12 +354,11 @@ namespace LibationAvalonia.Views
if (entry.Book.SeriesLink.Any()) if (entry.Book.SeriesLink.Any())
{ {
var header = entry.Liberate.IsSeries ? "View All Episodes in Series" : "View All Books in Series"; args.ContextMenuItems.Add(new MenuItem
var viewSeriesMenuItem = new MenuItem { Header = header }; {
Header = ctx.ViewSeriesText,
args.ContextMenuItems.Add(viewSeriesMenuItem); Command = ReactiveCommand.Create(() => new SeriesViewDialog(entry.LibraryBook).Show())
});
viewSeriesMenuItem.Click += (_, _) => new SeriesViewDialog(entry.LibraryBook).Show();
} }
#endregion #endregion

View File

@ -51,7 +51,10 @@ namespace LibationFileManager
return false; return false;
} }
private static readonly LibraryBookDto libraryBookDto public LibraryBookDto FolderBook { get; }
public LibraryBookDto LibraryBook { get; }
private static readonly LibraryBookDto DefaultLibraryBook
= new() = new()
{ {
Account = "myaccount@example.co", Account = "myaccount@example.co",
@ -74,7 +77,7 @@ namespace LibationFileManager
Language = "English" Language = "English"
}; };
private static readonly MultiConvertFileProperties partFileProperties private static readonly MultiConvertFileProperties DefaultMultipartProperties
= new() = new()
{ {
OutputFileName = "", OutputFileName = "",
@ -91,23 +94,27 @@ namespace LibationFileManager
* subdirectories. Without rooting, we won't be allowed to create a * subdirectories. Without rooting, we won't be allowed to create a
* relative path longer than MAX_PATH. * relative path longer than MAX_PATH.
*/ */
var dir = Folder?.GetFilename(libraryBookDto, BaseDirectory, ""); var dir = Folder?.GetFilename(FolderBook, BaseDirectory, "");
if (dir is null) return null; if (dir is null) return null;
return Path.GetRelativePath(BaseDirectory, dir); return Path.GetRelativePath(BaseDirectory, dir);
} }
public string? GetFileName() public string? GetFileName()
=> File?.GetFilename(libraryBookDto, partFileProperties, "", ""); => File?.GetFilename(LibraryBook, DefaultMultipartProperties, "", "");
public string? GetName() public string? GetName()
=> Name?.GetName(libraryBookDto, partFileProperties); => Name?.GetName(LibraryBook, DefaultMultipartProperties);
private TemplateEditor( private TemplateEditor(
LibraryBookDto? folderDto,
LibraryBookDto? fileDto,
Templates editingTemplate, Templates editingTemplate,
LongPath baseDirectory, LongPath baseDirectory,
string defaultTemplate, string defaultTemplate,
string templateName, string templateName,
string templateDescription) string templateDescription)
{ {
FolderBook = folderDto ?? DefaultLibraryBook;
LibraryBook = fileDto ?? DefaultLibraryBook;
_editingTemplate = editingTemplate; _editingTemplate = editingTemplate;
BaseDirectory = baseDirectory; BaseDirectory = baseDirectory;
DefaultTemplate = defaultTemplate; DefaultTemplate = defaultTemplate;
@ -115,12 +122,12 @@ namespace LibationFileManager
TemplateDescription = templateDescription; TemplateDescription = templateDescription;
} }
public static ITemplateEditor CreateFilenameEditor(LongPath baseDir, string templateText) public static ITemplateEditor CreateFilenameEditor(LongPath baseDir, string templateText, LibraryBookDto? folderDto = null, LibraryBookDto? fileDto = null)
{ {
if (!Templates.TryGetTemplate<T>(templateText, out var template)) if (!Templates.TryGetTemplate<T>(templateText, out var template))
throw new ArgumentException($"Failed to parse {nameof(templateText)}"); throw new ArgumentException($"Failed to parse {nameof(templateText)}");
var templateEditor = new TemplateEditor<T>(template, baseDir, T.DefaultTemplate, T.Name, T.Description); var templateEditor = new TemplateEditor<T>(folderDto, fileDto, template, baseDir, T.DefaultTemplate, T.Name, T.Description);
if (!templateEditor.IsFolder && !templateEditor.IsFilePath) if (!templateEditor.IsFolder && !templateEditor.IsFilePath)
throw new InvalidOperationException($"This method is only for File and Folder templates. Use {nameof(CreateNameEditor)} for name templates"); throw new InvalidOperationException($"This method is only for File and Folder templates. Use {nameof(CreateNameEditor)} for name templates");
@ -133,12 +140,12 @@ namespace LibationFileManager
return templateEditor; return templateEditor;
} }
public static ITemplateEditor CreateNameEditor(string templateText) public static ITemplateEditor CreateNameEditor(string templateText, LibraryBookDto? libraryBookDto = null)
{ {
if (!Templates.TryGetTemplate<T>(templateText, out var nameTemplate)) if (!Templates.TryGetTemplate<T>(templateText, out var nameTemplate))
throw new ArgumentException($"Failed to parse {nameof(templateText)}"); throw new ArgumentException($"Failed to parse {nameof(templateText)}");
var templateEditor = new TemplateEditor<T>(nameTemplate, "", T.DefaultTemplate, T.Name, T.Description); var templateEditor = new TemplateEditor<T>(null, libraryBookDto, nameTemplate, "", T.DefaultTemplate, T.Name, T.Description);
if (templateEditor.IsFolder || templateEditor.IsFilePath) if (templateEditor.IsFolder || templateEditor.IsFilePath)
throw new InvalidOperationException($"This method is only for name templates. Use {nameof(CreateFilenameEditor)} for file templates"); throw new InvalidOperationException($"This method is only for name templates. Use {nameof(CreateFilenameEditor)} for file templates");

View File

@ -0,0 +1,114 @@
using ApplicationServices;
using DataLayer;
using FileLiberator;
using LibationFileManager;
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
namespace LibationUiBase.GridView;
public class GridContextMenu
{
public string CopyCellText => $"{Accelerator}Copy Cell Contents";
public string LiberateEpisodesText => $"{Accelerator}Liberate All Episodes";
public string SetDownloadedText => $"Set Download status to '{Accelerator}Downloaded'";
public string SetNotDownloadedText => $"Set Download status to '{Accelerator}Not Downloaded'";
public string RemoveText => $"{Accelerator}Remove from library";
public string LocateFileText => $"{Accelerator}Locate file...";
public string LocateFileDialogTitle => $"Locate the audio file for '{GridEntry.Book.TitleWithSubtitle}'";
public string LocateFileErrorMessage => "Error saving book's location";
public string ConvertToMp3Text => $"{Accelerator}Convert to Mp3";
public string ReDownloadText => "Re-download this audiobook";
public string EditTemplatesText => "Edit Templates";
public string FolderTemplateText => "Folder Template";
public string FileTemplateText => "File Template";
public string MultipartTemplateText => "Multipart File Template";
public string ViewBookmarksText => "View _Bookmarks/Clips";
public string ViewSeriesText => GridEntry.Liberate.IsSeries ? "View All Episodes in Series" : "View All Books in Series";
public bool LiberateEpisodesEnabled => GridEntry is ISeriesEntry sEntry && sEntry.Children.Any(c => c.Liberate.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload);
public bool SetDownloadedEnabled => GridEntry.Book.UserDefinedItem.BookStatus != LiberatedStatus.Liberated || GridEntry.Liberate.IsSeries;
public bool SetNotDownloadedEnabled => GridEntry.Book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated || GridEntry.Liberate.IsSeries;
public bool ConvertToMp3Enabled => GridEntry.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated;
public bool ReDownloadEnabled => GridEntry.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated;
public IGridEntry GridEntry { get; }
public char Accelerator { get; }
public GridContextMenu(IGridEntry gridEntry, char accelerator)
{
GridEntry = gridEntry;
Accelerator = accelerator;
}
public void SetDownloaded()
{
if (GridEntry is ISeriesEntry series)
{
series.Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.Liberated);
}
else
{
GridEntry.LibraryBook.UpdateBookStatus(LiberatedStatus.Liberated);
}
}
public void SetNotDownloaded()
{
if (GridEntry is ISeriesEntry series)
{
series.Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.NotLiberated);
}
else
{
GridEntry.LibraryBook.UpdateBookStatus(LiberatedStatus.NotLiberated);
}
}
public async Task RemoveAsync()
{
if (GridEntry is ISeriesEntry series)
{
await series.Children.Select(c => c.LibraryBook).RemoveBooksAsync();
}
else
{
await Task.Run(GridEntry.LibraryBook.RemoveBook);
}
}
public ITemplateEditor CreateTemplateEditor<T>(LibraryBook libraryBook, string existingTemplate)
where T : Templates, ITemplate, new()
{
LibraryBookDto fileDto = libraryBook.ToDto(), folderDto = fileDto;
if (libraryBook.Book.IsEpisodeChild() &&
Configuration.Instance.SavePodcastsToParentFolder &&
libraryBook.Book.SeriesLink.SingleOrDefault() is SeriesBook series)
{
using var context = DbContexts.GetContext();
var seriesParent = context.GetLibraryBook_Flat_NoTracking(series.Series.AudibleSeriesId);
folderDto = seriesParent?.ToDto() ?? fileDto;
}
return TemplateEditor<T>.CreateFilenameEditor(Configuration.Instance.Books, existingTemplate, folderDto, fileDto);
}
}
class Command : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
throw new NotImplementedException();
}
public void Execute(object parameter)
{
throw new NotImplementedException();
}
}

View File

@ -37,7 +37,7 @@ namespace LibationUiBase.GridView
private string _purchasedate; private string _purchasedate;
private string _length; private string _length;
private LastDownloadStatus _lastDownload; private LastDownloadStatus _lastDownload;
private object _cover; private Lazy<object> _lazyCover;
private string _series; private string _series;
private SeriesOrder _seriesOrder; private SeriesOrder _seriesOrder;
private string _title; private string _title;
@ -55,7 +55,7 @@ namespace LibationUiBase.GridView
public string PurchaseDate { get => _purchasedate; protected set => RaiseAndSetIfChanged(ref _purchasedate, value); } public string PurchaseDate { get => _purchasedate; protected set => RaiseAndSetIfChanged(ref _purchasedate, value); }
public string Length { get => _length; protected set => RaiseAndSetIfChanged(ref _length, value); } public string Length { get => _length; protected set => RaiseAndSetIfChanged(ref _length, value); }
public LastDownloadStatus LastDownload { get => _lastDownload; protected set => RaiseAndSetIfChanged(ref _lastDownload, value); } public LastDownloadStatus LastDownload { get => _lastDownload; protected set => RaiseAndSetIfChanged(ref _lastDownload, value); }
public object Cover { get => _cover; private set => RaiseAndSetIfChanged(ref _cover, value); } public object Cover { get => _lazyCover.Value; }
public string Series { get => _series; private set => RaiseAndSetIfChanged(ref _series, value); } public string Series { get => _series; private set => RaiseAndSetIfChanged(ref _series, value); }
public SeriesOrder SeriesOrder { get => _seriesOrder; private set => RaiseAndSetIfChanged(ref _seriesOrder, value); } public SeriesOrder SeriesOrder { get => _seriesOrder; private set => RaiseAndSetIfChanged(ref _seriesOrder, value); }
public string Title { get => _title; private set => RaiseAndSetIfChanged(ref _title, value); } public string Title { get => _title; private set => RaiseAndSetIfChanged(ref _title, value); }
@ -200,36 +200,31 @@ namespace LibationUiBase.GridView
#region Sorting #region Sorting
public GridEntry() public object GetMemberValue(string memberName) => memberName switch
{ {
memberValues = new() nameof(Remove) => Remove.HasValue ? Remove.Value ? RemoveStatus.Removed : RemoveStatus.NotRemoved : RemoveStatus.SomeRemoved,
{ nameof(Title) => Book.TitleSortable(),
{ nameof(Remove), () => Remove.HasValue ? Remove.Value ? RemoveStatus.Removed : RemoveStatus.NotRemoved : RemoveStatus.SomeRemoved }, nameof(Series) => Book.SeriesSortable(),
{ nameof(Title), () => Book.TitleSortable() }, nameof(SeriesOrder) => SeriesOrder,
{ nameof(Series), () => Book.SeriesSortable() }, nameof(Length) => GetLengthInMinutes(),
{ nameof(SeriesOrder), () => SeriesOrder }, nameof(MyRating) => Book.UserDefinedItem.Rating,
{ nameof(Length), () => GetLengthInMinutes() }, nameof(PurchaseDate) => GetPurchaseDate(),
{ nameof(MyRating), () => Book.UserDefinedItem.Rating }, nameof(ProductRating) => Book.Rating,
{ nameof(PurchaseDate), () => GetPurchaseDate() }, nameof(Authors) => Authors,
{ nameof(ProductRating), () => Book.Rating }, nameof(Narrators) => Narrators,
{ nameof(Authors), () => Authors }, nameof(Description) => Description,
{ nameof(Narrators), () => Narrators }, nameof(Category) => Category,
{ nameof(Description), () => Description }, nameof(Misc) => Misc,
{ nameof(Category), () => Category }, nameof(LastDownload) => LastDownload,
{ nameof(Misc), () => Misc }, nameof(BookTags) => BookTags ?? string.Empty,
{ nameof(LastDownload), () => LastDownload }, nameof(Liberate) => Liberate,
{ nameof(BookTags), () => BookTags ?? string.Empty }, nameof(DateAdded) => DateAdded,
{ nameof(Liberate), () => Liberate }, _ => null
{ nameof(DateAdded), () => DateAdded },
}; };
}
public object GetMemberValue(string memberName) => memberValues[memberName]();
public IComparer GetMemberComparer(Type memberType) public IComparer GetMemberComparer(Type memberType)
=> memberTypeComparers.TryGetValue(memberType, out IComparer value) ? value : memberTypeComparers[memberType.BaseType]; => memberTypeComparers.TryGetValue(memberType, out IComparer value) ? value : memberTypeComparers[memberType.BaseType];
private readonly Dictionary<string, Func<object>> memberValues;
// Instantiate comparers for every exposed member object type. // Instantiate comparers for every exposed member object type.
private static readonly Dictionary<Type, IComparer> memberTypeComparers = new() private static readonly Dictionary<Type, IComparer> memberTypeComparers = new()
{ {
@ -258,7 +253,7 @@ namespace LibationUiBase.GridView
PictureStorage.PictureCached += PictureStorage_PictureCached; PictureStorage.PictureCached += PictureStorage_PictureCached;
// Mutable property. Set the field so PropertyChanged isn't fired. // Mutable property. Set the field so PropertyChanged isn't fired.
_cover = Liberate.LoadImage(picture); _lazyCover = new Lazy<object>(() => Liberate.LoadImage(picture));
} }
private void PictureStorage_PictureCached(object sender, PictureCachedEventArgs e) private void PictureStorage_PictureCached(object sender, PictureCachedEventArgs e)
@ -272,7 +267,8 @@ namespace LibationUiBase.GridView
// logic validation // logic validation
if (e.Definition.PictureId == Book.PictureId) if (e.Definition.PictureId == Book.PictureId)
{ {
Cover = Liberate.LoadImage(e.Picture); _lazyCover = new Lazy<object>(() => Liberate.LoadImage(e.Picture));
RaisePropertyChanged(nameof(Cover));
PictureStorage.PictureCached -= PictureStorage_PictureCached; PictureStorage.PictureCached -= PictureStorage_PictureCached;
} }
} }

View File

@ -102,19 +102,19 @@ namespace LibationWinForms.GridView
private void productsGrid_CellContextMenuStripNeeded(IGridEntry entry, ContextMenuStrip ctxMenu) private void productsGrid_CellContextMenuStripNeeded(IGridEntry entry, ContextMenuStrip ctxMenu)
{ {
var ctx = new GridContextMenu(entry, '&');
#region Liberate all Episodes #region Liberate all Episodes
if (entry.Liberate.IsSeries) if (entry.Liberate.IsSeries)
{ {
var liberateEpisodesMenuItem = new ToolStripMenuItem() var liberateEpisodesMenuItem = new ToolStripMenuItem()
{ {
Text = "&Liberate All Episodes", Text = ctx.LiberateEpisodesText,
Enabled = ((ISeriesEntry)entry).Children.Any(c => c.Liberate.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload) Enabled = ctx.LiberateEpisodesEnabled
}; };
ctxMenu.Items.Add(liberateEpisodesMenuItem);
liberateEpisodesMenuItem.Click += (_, _) => LiberateSeriesClicked?.Invoke(this, (ISeriesEntry)entry); liberateEpisodesMenuItem.Click += (_, _) => LiberateSeriesClicked?.Invoke(this, (ISeriesEntry)entry);
ctxMenu.Items.Add(liberateEpisodesMenuItem);
} }
#endregion #endregion
@ -122,61 +122,44 @@ namespace LibationWinForms.GridView
var setDownloadMenuItem = new ToolStripMenuItem() var setDownloadMenuItem = new ToolStripMenuItem()
{ {
Text = "Set Download status to '&Downloaded'", Text = ctx.SetDownloadedText,
Enabled = entry.Book.UserDefinedItem.BookStatus != LiberatedStatus.Liberated || entry.Liberate.IsSeries Enabled = ctx.SetDownloadedEnabled
}; };
setDownloadMenuItem.Click += (_, _) => ctx.SetDownloaded();
ctxMenu.Items.Add(setDownloadMenuItem); ctxMenu.Items.Add(setDownloadMenuItem);
if (entry.Liberate.IsSeries)
setDownloadMenuItem.Click += (_, _) => ((ISeriesEntry)entry).Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.Liberated);
else
setDownloadMenuItem.Click += (_, _) => entry.LibraryBook.UpdateBookStatus(LiberatedStatus.Liberated);
#endregion #endregion
#region Set Download status to Not Downloaded #region Set Download status to Not Downloaded
var setNotDownloadMenuItem = new ToolStripMenuItem() var setNotDownloadMenuItem = new ToolStripMenuItem()
{ {
Text = "Set Download status to '&Not Downloaded'", Text = ctx.SetNotDownloadedText,
Enabled = entry.Book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated || entry.Liberate.IsSeries Enabled = ctx.SetNotDownloadedEnabled
}; };
setNotDownloadMenuItem.Click += (_, _) => ctx.SetNotDownloaded();
ctxMenu.Items.Add(setNotDownloadMenuItem); ctxMenu.Items.Add(setNotDownloadMenuItem);
if (entry.Liberate.IsSeries)
setNotDownloadMenuItem.Click += (_, _) => ((ISeriesEntry)entry).Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.NotLiberated);
else
setNotDownloadMenuItem.Click += (_, _) => entry.LibraryBook.UpdateBookStatus(LiberatedStatus.NotLiberated);
#endregion #endregion
#region Remove from library #region Remove from library
var removeMenuItem = new ToolStripMenuItem() { Text = "&Remove from library" }; var removeMenuItem = new ToolStripMenuItem() { Text = ctx.RemoveText };
removeMenuItem.Click += async (_, _) => await ctx.RemoveAsync();
ctxMenu.Items.Add(removeMenuItem); ctxMenu.Items.Add(removeMenuItem);
if (entry.Liberate.IsSeries)
removeMenuItem.Click += async (_, _) => await ((ISeriesEntry)entry).Children.Select(c => c.LibraryBook).RemoveBooksAsync();
else
removeMenuItem.Click += async (_, _) => await Task.Run(entry.LibraryBook.RemoveBook);
#endregion #endregion
if (!entry.Liberate.IsSeries) if (!entry.Liberate.IsSeries)
{ {
#region Locate file #region Locate file
var locateFileMenuItem = new ToolStripMenuItem() { Text = "&Locate file..." }; var locateFileMenuItem = new ToolStripMenuItem() { Text = ctx.LocateFileText };
ctxMenu.Items.Add(locateFileMenuItem); ctxMenu.Items.Add(locateFileMenuItem);
locateFileMenuItem.Click += (_, _) => locateFileMenuItem.Click += (_, _) =>
{ {
try try
{ {
var openFileDialog = new OpenFileDialog var openFileDialog = new OpenFileDialog
{ {
Title = $"Locate the audio file for '{entry.Book.TitleWithSubtitle}'", Title = ctx.LocateFileDialogTitle,
Filter = "All files (*.*)|*.*", Filter = "All files (*.*)|*.*",
FilterIndex = 1 FilterIndex = 1
}; };
@ -185,8 +168,7 @@ namespace LibationWinForms.GridView
} }
catch (Exception ex) catch (Exception ex)
{ {
var msg = "Error saving book's location"; MessageBoxLib.ShowAdminAlert(this, ctx.LocateFileErrorMessage, ctx.LocateFileErrorMessage, ex);
MessageBoxLib.ShowAdminAlert(this, msg, msg, ex);
} }
}; };
@ -195,13 +177,11 @@ namespace LibationWinForms.GridView
var convertToMp3MenuItem = new ToolStripMenuItem var convertToMp3MenuItem = new ToolStripMenuItem
{ {
Text = "&Convert to Mp3", Text = ctx.ConvertToMp3Text,
Enabled = entry.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated Enabled = ctx.ConvertToMp3Enabled
}; };
ctxMenu.Items.Add(convertToMp3MenuItem);
convertToMp3MenuItem.Click += (_, e) => ConvertToMp3Clicked?.Invoke(this, entry.LibraryBook); convertToMp3MenuItem.Click += (_, e) => ConvertToMp3Clicked?.Invoke(this, entry.LibraryBook);
ctxMenu.Items.Add(convertToMp3MenuItem);
#endregion #endregion
} }
@ -211,10 +191,9 @@ namespace LibationWinForms.GridView
{ {
var reDownloadMenuItem = new ToolStripMenuItem() var reDownloadMenuItem = new ToolStripMenuItem()
{ {
Text = "Re-download this audiobook", Text = ctx.ReDownloadText,
Enabled = entry.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated Enabled = ctx.ReDownloadEnabled
}; };
ctxMenu.Items.Add(reDownloadMenuItem); ctxMenu.Items.Add(reDownloadMenuItem);
reDownloadMenuItem.Click += (s, _) => reDownloadMenuItem.Click += (s, _) =>
{ {
@ -223,6 +202,35 @@ namespace LibationWinForms.GridView
LiberateClicked?.Invoke(s, entry.LibraryBook); LiberateClicked?.Invoke(s, entry.LibraryBook);
}; };
} }
#endregion
#region Edit Templates
void editTemplate<T>(LibraryBook libraryBook, string existingTemplate, Action<string> setNewTemplate)
where T : Templates, LibationFileManager.ITemplate, new()
{
var template = ctx.CreateTemplateEditor<T>(libraryBook, existingTemplate);
var form = new EditTemplateDialog(template);
if (form.ShowDialog(this) == DialogResult.OK)
{
setNewTemplate(template.EditingTemplate.TemplateText);
}
}
if (!entry.Liberate.IsSeries)
{
var folderTemplateMenuItem = new ToolStripMenuItem { Text = ctx.FolderTemplateText };
var fileTemplateMenuItem = new ToolStripMenuItem { Text = ctx.FileTemplateText };
var multiFileTemplateMenuItem = new ToolStripMenuItem { Text = ctx.MultipartTemplateText };
folderTemplateMenuItem.Click += (s, _) => editTemplate<Templates.FolderTemplate>(entry.LibraryBook, Configuration.Instance.FolderTemplate, t => Configuration.Instance.FolderTemplate = t);
fileTemplateMenuItem.Click += (s, _) => editTemplate<Templates.FileTemplate>(entry.LibraryBook, Configuration.Instance.FileTemplate, t => Configuration.Instance.FileTemplate = t);
multiFileTemplateMenuItem.Click += (s, _) => editTemplate<Templates.ChapterFileTemplate>(entry.LibraryBook, Configuration.Instance.ChapterFileTemplate, t => Configuration.Instance.ChapterFileTemplate = t);
var editTemplatesMenuItem = new ToolStripMenuItem { Text = ctx.EditTemplatesText };
editTemplatesMenuItem.DropDownItems.AddRange(new[] { folderTemplateMenuItem, fileTemplateMenuItem, multiFileTemplateMenuItem });
ctxMenu.Items.Add(new ToolStripSeparator());
ctxMenu.Items.Add(editTemplatesMenuItem);
}
#endregion #endregion
ctxMenu.Items.Add(new ToolStripSeparator()); ctxMenu.Items.Add(new ToolStripSeparator());
@ -231,11 +239,9 @@ namespace LibationWinForms.GridView
if (!entry.Liberate.IsSeries) if (!entry.Liberate.IsSeries)
{ {
var bookRecordMenuItem = new ToolStripMenuItem { Text = "View &Bookmarks/Clips" }; var bookRecordMenuItem = new ToolStripMenuItem { Text = ctx.ViewBookmarksText };
ctxMenu.Items.Add(bookRecordMenuItem);
bookRecordMenuItem.Click += (_, _) => new BookRecordsDialog(entry.LibraryBook).ShowDialog(this); bookRecordMenuItem.Click += (_, _) => new BookRecordsDialog(entry.LibraryBook).ShowDialog(this);
ctxMenu.Items.Add(bookRecordMenuItem);
} }
#endregion #endregion
@ -243,13 +249,9 @@ namespace LibationWinForms.GridView
if (entry.Book.SeriesLink.Any()) if (entry.Book.SeriesLink.Any())
{ {
var header = entry.Liberate.IsSeries ? "View All Episodes in Series" : "View All Books in Series"; var viewSeriesMenuItem = new ToolStripMenuItem { Text = ctx.ViewSeriesText };
var viewSeriesMenuItem = new ToolStripMenuItem { Text = header };
ctxMenu.Items.Add(viewSeriesMenuItem);
viewSeriesMenuItem.Click += (_, _) => new SeriesViewDialog(entry.LibraryBook).Show(); viewSeriesMenuItem.Click += (_, _) => new SeriesViewDialog(entry.LibraryBook).Show();
ctxMenu.Items.Add(viewSeriesMenuItem);
} }
#endregion #endregion

View File

@ -1,28 +1,39 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="256.0px" height="256.0px" viewBox="0 0 512 512" enable-background="new 0 0 512 512"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 524 524" enable-background="new 0 0 524 524">
<path id="glass" d= <defs>
"M139,2 <g id="glass">
A 192,200 0 0 0 103,84 <path fill-rule="evenodd" d=
A 222,334 41 0 0 241,320 "M262,8
V478 h-117
H160 a 192,200 0 0 0 -36,82
A 16,16 0 0 0 160,510 a 222,334 41 0 0 138,236
H352 v158
A16 16 0 0 0 352,478 h-81
H271 a 16,16 0 0 0 0,32
V320 h192
A 222,334 -41 0 0 409,84 a 16 16 0 0 0 0,-32
A 192,200 0 0 0 373,2 h-81
M355,32 v-158
A 192,200 0 0 1 381,127 a 222,334 -41 0 0 138,-236
A 187.5,334 -35 0 1 256,286 a 192,200 0 0 0 -36,-82
A 187.5,334 35 0 1 131,127 h-117
A 192,200 0 0 1 157,32 m-99,30
H355 a 192,200 0 0 0 -26,95
z" /> a 187.5,334 35 0 0 125,159
a 187.5,334 -35 0 0 125,-159
<path id="wine-level" d= a 192,200 0 0 0 -26,-95
"M146,128 h-198
A 168,300 35 0 0 256,270 z"/>
A 168,300 -35 0 0 366,128 </g>
z"/> <g id="wine-level">
<path d=
"M158,136
a 168,305 35 0 0 104,136
a 168,305 -35 0 0 104,-136
z"/>
</g>
</defs>
<use href="#glass" stroke="#ffffffa0" stroke-width="16" fill="Transparent" />
<use href="#wine-level" stroke="#ffffffa0" stroke-width="16" fill="Transparent" />
<use href="#glass" fill="Black" />
<use href="#wine-level" fill="Black" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 618 B

After

Width:  |  Height:  |  Size: 968 B