commit
415e6e7bc6
@ -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">
|
||||
<path id="glass" d=
|
||||
"M139,2
|
||||
A 192,200 0 0 0 103,84
|
||||
A 222,334 41 0 0 241,320
|
||||
V478
|
||||
H160
|
||||
A 16,16 0 0 0 160,510
|
||||
H352
|
||||
A16 16 0 0 0 352,478
|
||||
H271
|
||||
V320
|
||||
A 222,334 -41 0 0 409,84
|
||||
A 192,200 0 0 0 373,2
|
||||
M355,32
|
||||
A 192,200 0 0 1 381,127
|
||||
A 187.5,334 -35 0 1 256,286
|
||||
A 187.5,334 35 0 1 131,127
|
||||
A 192,200 0 0 1 157,32
|
||||
H355
|
||||
z" />
|
||||
|
||||
<path id="wine-level" d=
|
||||
"M146,128
|
||||
A 168,300 35 0 0 256,270
|
||||
A 168,300 -35 0 0 366,128
|
||||
z"/>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 524 524" enable-background="new 0 0 524 524">
|
||||
<defs>
|
||||
<g id="glass">
|
||||
<path fill-rule="evenodd" d=
|
||||
"M262,8
|
||||
h-117
|
||||
a 192,200 0 0 0 -36,82
|
||||
a 222,334 41 0 0 138,236
|
||||
v158
|
||||
h-81
|
||||
a 16,16 0 0 0 0,32
|
||||
h192
|
||||
a 16 16 0 0 0 0,-32
|
||||
h-81
|
||||
v-158
|
||||
a 222,334 -41 0 0 138,-236
|
||||
a 192,200 0 0 0 -36,-82
|
||||
h-117
|
||||
m-99,30
|
||||
a 192,200 0 0 0 -26,95
|
||||
a 187.5,334 35 0 0 125,159
|
||||
a 187.5,334 -35 0 0 125,-159
|
||||
a 192,200 0 0 0 -26,-95
|
||||
h-198
|
||||
z"/>
|
||||
</g>
|
||||
<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>
|
||||
|
||||
|
Before Width: | Height: | Size: 585 B After Width: | Height: | Size: 968 B |
@ -106,6 +106,7 @@ namespace DataLayer
|
||||
ReplaceAuthors(authors);
|
||||
ReplaceNarrators(narrators);
|
||||
}
|
||||
|
||||
public void UpdateTitle(string title, string subtitle)
|
||||
{
|
||||
Title = title?.Trim() ?? "";
|
||||
@ -113,8 +114,11 @@ namespace DataLayer
|
||||
_titleWithSubtitle = null;
|
||||
}
|
||||
|
||||
#region contributors, authors, narrators
|
||||
internal HashSet<BookContributor> ContributorsLink { get; private set; }
|
||||
public void UpdateLengthInMinutes(int lengthInMinutes)
|
||||
=> LengthInMinutes = lengthInMinutes;
|
||||
|
||||
#region contributors, authors, narrators
|
||||
internal HashSet<BookContributor> ContributorsLink { get; private set; }
|
||||
|
||||
public IEnumerable<Contributor> Authors => ContributorsLink.ByRole(Role.Author).Select(bc => bc.Contributor).ToList();
|
||||
public IEnumerable<Contributor> Narrators => ContributorsLink.ByRole(Role.Narrator).Select(bc => bc.Contributor).ToList();
|
||||
|
||||
@ -149,6 +149,8 @@ namespace DtoImporterService
|
||||
{
|
||||
var item = importItem.DtoItem;
|
||||
|
||||
book.UpdateLengthInMinutes(item.LengthInMinutes);
|
||||
|
||||
// Update the book titles, since formatting can change
|
||||
book.UpdateTitle(item.Title, item.Subtitle);
|
||||
|
||||
|
||||
@ -21,7 +21,8 @@ namespace FileLiberator
|
||||
var series = libraryBook.Book.SeriesLink.SingleOrDefault();
|
||||
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)
|
||||
{
|
||||
|
||||
@ -67,13 +67,13 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
<PackageReference Include="Avalonia" Version="11.0.2" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.0.2" />
|
||||
<PackageReference Include="Avalonia" Version="11.0.3" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.0.3" />
|
||||
<!--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 Include="Avalonia.ReactiveUI" Version="11.0.2" />
|
||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.2" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.2" />
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.3" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.3" />
|
||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.3" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\HangoverBase\HangoverBase.csproj" />
|
||||
|
||||
@ -44,9 +44,9 @@
|
||||
|
||||
<SolidColorBrush x:Key="CancelRed" Color="#802727" />
|
||||
<SolidColorBrush x:Key="IconFill" Color="#DCE0DF" />
|
||||
<SolidColorBrush x:Key="StoplightRed" Color="#5F0707" />
|
||||
<SolidColorBrush x:Key="StoplightYellow" Color="#5F5B1A" />
|
||||
<SolidColorBrush x:Key="StoplightGreen" Color="#174E15" />
|
||||
<SolidColorBrush x:Key="StoplightRed" Color="#7d1f1f" />
|
||||
<SolidColorBrush x:Key="StoplightYellow" Color="#7d7d1f" />
|
||||
<SolidColorBrush x:Key="StoplightGreen" Color="#1f7d1f" />
|
||||
|
||||
|
||||
<SolidColorBrush x:Key="DisabledGrayBrush" Opacity="0.4" Color="{StaticResource SystemChromeMediumColor}" />
|
||||
|
||||
@ -70,13 +70,13 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.2" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
|
||||
<PackageReference Include="Avalonia" Version="11.0.2" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.2" />
|
||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.2" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.0.2" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.2" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.2" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.3" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
|
||||
<PackageReference Include="Avalonia" Version="11.0.3" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.3" />
|
||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.3" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.0.3" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.3" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@ -177,9 +177,12 @@ Libation.
|
||||
tbx.MinWidth = vm.TextBlockMinWidth;
|
||||
tbx.Text = message;
|
||||
|
||||
var thisScreen = owner.Screens.ScreenFromVisual(owner);
|
||||
|
||||
var thisScreen = owner.Screens?.ScreenFromVisual(owner);
|
||||
|
||||
var maxSize = new Size(0.20 * thisScreen.Bounds.Width, 0.9 * thisScreen.Bounds.Height - 55);
|
||||
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);
|
||||
|
||||
|
||||
@ -1,15 +1,18 @@
|
||||
using ApplicationServices;
|
||||
using AudibleUtilities;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
using DataLayer;
|
||||
using LibationAvalonia.Dialogs.Login;
|
||||
using LibationFileManager;
|
||||
using LibationUiBase.GridView;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.ViewModels
|
||||
@ -27,8 +30,8 @@ namespace LibationAvalonia.ViewModels
|
||||
public string FilterString { get; private set; }
|
||||
public DataGridCollectionView GridEntries { get; private set; }
|
||||
|
||||
private bool _removeColumnVisivle;
|
||||
public bool RemoveColumnVisivle { get => _removeColumnVisivle; private set => this.RaiseAndSetIfChanged(ref _removeColumnVisivle, value); }
|
||||
private bool _removeColumnVisible;
|
||||
public bool RemoveColumnVisible { get => _removeColumnVisible; private set => this.RaiseAndSetIfChanged(ref _removeColumnVisible, value); }
|
||||
|
||||
public List<LibraryBook> GetVisibleBookEntries()
|
||||
=> FilteredInGridEntries?
|
||||
@ -321,7 +324,7 @@ namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
foreach (var item in SOURCE)
|
||||
item.PropertyChanged -= GridEntry_PropertyChanged;
|
||||
RemoveColumnVisivle = false;
|
||||
RemoveColumnVisible = false;
|
||||
}
|
||||
|
||||
public async Task RemoveCheckedBooksAsync()
|
||||
@ -376,7 +379,7 @@ namespace LibationAvalonia.ViewModels
|
||||
item.PropertyChanged += GridEntry_PropertyChanged;
|
||||
}
|
||||
|
||||
RemoveColumnVisivle = true;
|
||||
RemoveColumnVisible = true;
|
||||
RemovableCountChanged?.Invoke(this, 0);
|
||||
|
||||
try
|
||||
@ -421,5 +424,44 @@ namespace LibationAvalonia.ViewModels
|
||||
}
|
||||
|
||||
#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
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ namespace LibationAvalonia.Views
|
||||
InitializeComponent();
|
||||
Configure_Upgrade();
|
||||
|
||||
Loaded += MainWindow_Loaded;
|
||||
Opened += MainWindow_Opened;
|
||||
Closing += MainWindow_Closing;
|
||||
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)
|
||||
{
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
AutoGenerateColumns="False"
|
||||
ItemsSource="{Binding GridEntries}"
|
||||
CanUserSortColumns="True" BorderThickness="3"
|
||||
CanUserResizeColumns="True"
|
||||
CanUserReorderColumns="True">
|
||||
|
||||
<DataGrid.Styles>
|
||||
@ -45,10 +46,11 @@
|
||||
</DataGrid.Styles>
|
||||
|
||||
<DataGrid.Columns>
|
||||
|
||||
|
||||
<DataGridTemplateColumn
|
||||
CanUserSort="True"
|
||||
IsVisible="{Binding RemoveColumnVisivle}"
|
||||
CanUserResize="False"
|
||||
IsVisible="{Binding RemoveColumnVisible}"
|
||||
PropertyChanged="RemoveColumn_PropertyChanged"
|
||||
Header="Remove"
|
||||
IsReadOnly="False"
|
||||
@ -65,7 +67,7 @@
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</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>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<views:LiberateStatusButton
|
||||
@ -80,15 +82,15 @@
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</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>
|
||||
<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" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</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>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
|
||||
@ -98,7 +100,7 @@
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</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>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
|
||||
@ -108,7 +110,7 @@
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</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>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
|
||||
@ -118,7 +120,7 @@
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</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>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
|
||||
@ -128,7 +130,7 @@
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</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>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
|
||||
@ -138,7 +140,7 @@
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</controls:DataGridTemplateColumnExt>
|
||||
|
||||
<controls:DataGridTemplateColumnExt Width="Auto" Header="Series
Order" CanUserSort="True" SortMemberPath="SeriesOrder" ClipboardContentBinding="{Binding Series}">
|
||||
<controls:DataGridTemplateColumnExt Header="Series
Order" MinWidth="10" Width="{Binding SeriesOrderWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="SeriesOrder" ClipboardContentBinding="{Binding Series}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
|
||||
@ -148,7 +150,7 @@
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</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>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<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>
|
||||
</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>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
|
||||
@ -172,14 +174,14 @@
|
||||
x:DataType="uibase:IGridEntry"
|
||||
Header="Product
Rating"
|
||||
IsReadOnly="true"
|
||||
Width="115"
|
||||
MinWidth="10" Width="{Binding ProductRatingWidth, Mode=TwoWay}"
|
||||
SortMemberPath="ProductRating" CanUserSort="True"
|
||||
OpacityBinding="{CompiledBinding Liberate.Opacity}"
|
||||
BackgroundBinding="{CompiledBinding Liberate.BackgroundBrush}"
|
||||
ClipboardContentBinding="{CompiledBinding ProductRating}"
|
||||
Binding="{CompiledBinding ProductRating}" />
|
||||
|
||||
<controls:DataGridTemplateColumnExt Width="90" Header="Purchase
Date" CanUserSort="True" SortMemberPath="PurchaseDate" ClipboardContentBinding="{Binding PurchaseDate}">
|
||||
<controls:DataGridTemplateColumnExt Header="Purchase
Date" MinWidth="10" Width="{Binding PurchaseDateWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="PurchaseDate" ClipboardContentBinding="{Binding PurchaseDate}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
|
||||
@ -191,16 +193,16 @@
|
||||
|
||||
<controls:DataGridMyRatingColumn
|
||||
x:DataType="uibase:IGridEntry"
|
||||
Header="My Rating"
|
||||
Header="My Rating"
|
||||
IsReadOnly="false"
|
||||
Width="115"
|
||||
MinWidth="10" Width="{Binding MyRatingWidth, Mode=TwoWay}"
|
||||
SortMemberPath="MyRating" CanUserSort="True"
|
||||
OpacityBinding="{CompiledBinding Liberate.Opacity}"
|
||||
BackgroundBinding="{CompiledBinding Liberate.BackgroundBrush}"
|
||||
ClipboardContentBinding="{CompiledBinding MyRating}"
|
||||
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>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
|
||||
@ -210,7 +212,7 @@
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</controls:DataGridTemplateColumnExt>
|
||||
|
||||
<controls:DataGridTemplateColumnExt Width="102" Header="Last
Download" CanUserSort="True" SortMemberPath="LastDownload" ClipboardContentBinding="{Binding LastDownload}">
|
||||
<controls:DataGridTemplateColumnExt Header="Last
Download" MinWidth="10" Width="{Binding LastDownloadWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="LastDownload" ClipboardContentBinding="{Binding LastDownload}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}" ToolTip.Tip="{CompiledBinding LastDownload.ToolTipText}" DoubleTapped="Version_DoubleClick">
|
||||
@ -220,7 +222,7 @@
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</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>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<Button
|
||||
|
||||
@ -11,6 +11,7 @@ using LibationAvalonia.Dialogs;
|
||||
using LibationAvalonia.ViewModels;
|
||||
using LibationFileManager;
|
||||
using LibationUiBase.GridView;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -172,126 +173,107 @@ namespace LibationAvalonia.Views
|
||||
|
||||
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")
|
||||
{
|
||||
var menuItem = new MenuItem { Header = "_Copy Cell Contents" };
|
||||
|
||||
menuItem.Click += async (s, e)
|
||||
=> await App.MainWindow.Clipboard.SetTextAsync(args.CellClipboardContents);
|
||||
|
||||
args.ContextMenuItems.Add(menuItem);
|
||||
args.ContextMenuItems.Add(new MenuItem
|
||||
{
|
||||
Header = ctx.CopyCellText,
|
||||
Command = ReactiveCommand.CreateFromTask(() => App.MainWindow.Clipboard.SetTextAsync(args.CellClipboardContents))
|
||||
});
|
||||
args.ContextMenuItems.Add(new Separator());
|
||||
}
|
||||
var entry = args.GridEntry;
|
||||
|
||||
#region Liberate all Episodes
|
||||
|
||||
if (entry.Liberate.IsSeries)
|
||||
{
|
||||
var liberateEpisodesMenuItem = new MenuItem()
|
||||
args.ContextMenuItems.Add(new MenuItem()
|
||||
{
|
||||
Header = "_Liberate All Episodes",
|
||||
IsEnabled = ((ISeriesEntry)entry).Children.Any(c => c.Liberate.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload)
|
||||
};
|
||||
|
||||
args.ContextMenuItems.Add(liberateEpisodesMenuItem);
|
||||
|
||||
liberateEpisodesMenuItem.Click += (_, _) => LiberateSeriesClicked?.Invoke(this, ((ISeriesEntry)entry));
|
||||
Header = ctx.LiberateEpisodesText,
|
||||
IsEnabled = ctx.LiberateEpisodesEnabled,
|
||||
Command = ReactiveCommand.Create(() => LiberateSeriesClicked?.Invoke(this, (ISeriesEntry)entry))
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region Set Download status to Downloaded
|
||||
|
||||
var setDownloadMenuItem = new MenuItem()
|
||||
args.ContextMenuItems.Add(new MenuItem()
|
||||
{
|
||||
Header = "Set Download status to '_Downloaded'",
|
||||
IsEnabled = entry.Book.UserDefinedItem.BookStatus != LiberatedStatus.Liberated || entry.Liberate.IsSeries
|
||||
};
|
||||
|
||||
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);
|
||||
Header = ctx.SetDownloadedText,
|
||||
IsEnabled = ctx.SetDownloadedEnabled,
|
||||
Command = ReactiveCommand.Create(ctx.SetDownloaded)
|
||||
});
|
||||
|
||||
#endregion
|
||||
#region Set Download status to Not Downloaded
|
||||
|
||||
var setNotDownloadMenuItem = new MenuItem()
|
||||
args.ContextMenuItems.Add(new MenuItem()
|
||||
{
|
||||
Header = "Set Download status to '_Not Downloaded'",
|
||||
IsEnabled = entry.Book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated || entry.Liberate.IsSeries
|
||||
};
|
||||
|
||||
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);
|
||||
Header = ctx.SetNotDownloadedText,
|
||||
IsEnabled = ctx.SetNotDownloadedEnabled,
|
||||
Command = ReactiveCommand.Create(ctx.SetNotDownloaded)
|
||||
});
|
||||
|
||||
#endregion
|
||||
#region Remove from library
|
||||
|
||||
var removeMenuItem = new MenuItem() { Header = "_Remove from library" };
|
||||
|
||||
args.ContextMenuItems.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);
|
||||
args.ContextMenuItems.Add(new MenuItem
|
||||
{
|
||||
Header = ctx.RemoveText,
|
||||
Command = ReactiveCommand.CreateFromTask(ctx.RemoveAsync)
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
if (!entry.Liberate.IsSeries)
|
||||
{
|
||||
#region Locate file
|
||||
var locateFileMenuItem = new MenuItem() { Header = "_Locate file..." };
|
||||
|
||||
args.ContextMenuItems.Add(locateFileMenuItem);
|
||||
|
||||
locateFileMenuItem.Click += async (_, __) =>
|
||||
args.ContextMenuItems.Add(new MenuItem
|
||||
{
|
||||
try
|
||||
Header = ctx.LocateFileText,
|
||||
Command = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var window = this.GetParentWindow();
|
||||
|
||||
var openFileDialogOptions = new FilePickerOpenOptions
|
||||
try
|
||||
{
|
||||
Title = $"Locate the audio file for '{entry.Book.TitleWithSubtitle}'",
|
||||
AllowMultiple = false,
|
||||
SuggestedStartLocation = await window.StorageProvider.TryGetFolderFromPathAsync(Configuration.Instance.Books.PathWithoutPrefix),
|
||||
FileTypeFilter = new FilePickerFileType[]
|
||||
var window = this.GetParentWindow();
|
||||
|
||||
var openFileDialogOptions = new FilePickerOpenOptions
|
||||
{
|
||||
new("All files (*.*)") { Patterns = new[] { "*" } },
|
||||
}
|
||||
};
|
||||
Title = ctx.LocateFileDialogTitle,
|
||||
AllowMultiple = false,
|
||||
SuggestedStartLocation = await window.StorageProvider.TryGetFolderFromPathAsync(Configuration.Instance.Books.PathWithoutPrefix),
|
||||
FileTypeFilter = new FilePickerFileType[]
|
||||
{
|
||||
new("All files (*.*)") { Patterns = new[] { "*" } },
|
||||
}
|
||||
};
|
||||
|
||||
var selectedFiles = await window.StorageProvider.OpenFilePickerAsync(openFileDialogOptions);
|
||||
var selectedFile = selectedFiles.SingleOrDefault()?.TryGetLocalPath();
|
||||
var selectedFiles = await window.StorageProvider.OpenFilePickerAsync(openFileDialogOptions);
|
||||
var selectedFile = selectedFiles.SingleOrDefault()?.TryGetLocalPath();
|
||||
|
||||
if (selectedFile is not null)
|
||||
FilePathCache.Insert(entry.AudibleProductId, selectedFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = "Error saving book's location";
|
||||
await MessageBox.ShowAdminAlert(null, msg, msg, ex);
|
||||
}
|
||||
};
|
||||
if (selectedFile is not null)
|
||||
FilePathCache.Insert(entry.AudibleProductId, selectedFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await MessageBox.ShowAdminAlert(null, ctx.LocateFileErrorMessage, ctx.LocateFileErrorMessage, ex);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
#endregion
|
||||
#region Convert to Mp3
|
||||
var convertToMp3MenuItem = new MenuItem
|
||||
args.ContextMenuItems.Add(new MenuItem
|
||||
{
|
||||
Header = "_Convert to Mp3",
|
||||
IsEnabled = entry.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated
|
||||
};
|
||||
args.ContextMenuItems.Add(convertToMp3MenuItem);
|
||||
|
||||
convertToMp3MenuItem.Click += (_, _) => ConvertToMp3Clicked?.Invoke(this, entry.LibraryBook);
|
||||
Header = ctx.ConvertToMp3Text,
|
||||
IsEnabled = ctx.ConvertToMp3Enabled,
|
||||
Command = ReactiveCommand.Create(() => ConvertToMp3Clicked?.Invoke(this, entry.LibraryBook))
|
||||
});
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -299,34 +281,72 @@ namespace LibationAvalonia.Views
|
||||
#region Force Re-Download
|
||||
if (!entry.Liberate.IsSeries)
|
||||
{
|
||||
var reDownloadMenuItem = new MenuItem()
|
||||
args.ContextMenuItems.Add(new MenuItem()
|
||||
{
|
||||
Header = "Re-download this audiobook",
|
||||
IsEnabled = entry.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated
|
||||
};
|
||||
|
||||
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
|
||||
entry.Book.UserDefinedItem.BookStatus = LiberatedStatus.NotLiberated;
|
||||
LiberateClicked?.Invoke(s, entry.LibraryBook);
|
||||
};
|
||||
Header = ctx.ReDownloadText,
|
||||
IsEnabled = ctx.ReDownloadEnabled,
|
||||
Command = ReactiveCommand.Create(() =>
|
||||
{
|
||||
//No need to persist this change. It only needs to last long for the file to start downloading
|
||||
entry.Book.UserDefinedItem.BookStatus = LiberatedStatus.NotLiberated;
|
||||
LiberateClicked?.Invoke(this, entry.LibraryBook);
|
||||
})
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
|
||||
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
|
||||
|
||||
if (!entry.Liberate.IsSeries)
|
||||
{
|
||||
|
||||
var bookRecordMenuItem = new MenuItem { Header = "View _Bookmarks/Clips" };
|
||||
|
||||
args.ContextMenuItems.Add(bookRecordMenuItem);
|
||||
|
||||
bookRecordMenuItem.Click += async (_, _) => await new BookRecordsDialog(entry.LibraryBook).ShowDialog(VisualRoot as Window);
|
||||
args.ContextMenuItems.Add(new MenuItem
|
||||
{
|
||||
Header = ctx.ViewBookmarksText,
|
||||
Command = ReactiveCommand.CreateFromTask(() => new BookRecordsDialog(entry.LibraryBook).ShowDialog(VisualRoot as Window))
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -334,12 +354,11 @@ namespace LibationAvalonia.Views
|
||||
|
||||
if (entry.Book.SeriesLink.Any())
|
||||
{
|
||||
var header = entry.Liberate.IsSeries ? "View All Episodes in Series" : "View All Books in Series";
|
||||
var viewSeriesMenuItem = new MenuItem { Header = header };
|
||||
|
||||
args.ContextMenuItems.Add(viewSeriesMenuItem);
|
||||
|
||||
viewSeriesMenuItem.Click += (_, _) => new SeriesViewDialog(entry.LibraryBook).Show();
|
||||
args.ContextMenuItems.Add(new MenuItem
|
||||
{
|
||||
Header = ctx.ViewSeriesText,
|
||||
Command = ReactiveCommand.Create(() => new SeriesViewDialog(entry.LibraryBook).Show())
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -51,7 +51,10 @@ namespace LibationFileManager
|
||||
return false;
|
||||
}
|
||||
|
||||
private static readonly LibraryBookDto libraryBookDto
|
||||
public LibraryBookDto FolderBook { get; }
|
||||
public LibraryBookDto LibraryBook { get; }
|
||||
|
||||
private static readonly LibraryBookDto DefaultLibraryBook
|
||||
= new()
|
||||
{
|
||||
Account = "myaccount@example.co",
|
||||
@ -74,7 +77,7 @@ namespace LibationFileManager
|
||||
Language = "English"
|
||||
};
|
||||
|
||||
private static readonly MultiConvertFileProperties partFileProperties
|
||||
private static readonly MultiConvertFileProperties DefaultMultipartProperties
|
||||
= new()
|
||||
{
|
||||
OutputFileName = "",
|
||||
@ -91,23 +94,27 @@ namespace LibationFileManager
|
||||
* subdirectories. Without rooting, we won't be allowed to create a
|
||||
* relative path longer than MAX_PATH.
|
||||
*/
|
||||
var dir = Folder?.GetFilename(libraryBookDto, BaseDirectory, "");
|
||||
var dir = Folder?.GetFilename(FolderBook, BaseDirectory, "");
|
||||
if (dir is null) return null;
|
||||
return Path.GetRelativePath(BaseDirectory, dir);
|
||||
}
|
||||
|
||||
public string? GetFileName()
|
||||
=> File?.GetFilename(libraryBookDto, partFileProperties, "", "");
|
||||
=> File?.GetFilename(LibraryBook, DefaultMultipartProperties, "", "");
|
||||
public string? GetName()
|
||||
=> Name?.GetName(libraryBookDto, partFileProperties);
|
||||
=> Name?.GetName(LibraryBook, DefaultMultipartProperties);
|
||||
|
||||
private TemplateEditor(
|
||||
LibraryBookDto? folderDto,
|
||||
LibraryBookDto? fileDto,
|
||||
Templates editingTemplate,
|
||||
LongPath baseDirectory,
|
||||
string defaultTemplate,
|
||||
string templateName,
|
||||
string templateDescription)
|
||||
{
|
||||
FolderBook = folderDto ?? DefaultLibraryBook;
|
||||
LibraryBook = fileDto ?? DefaultLibraryBook;
|
||||
_editingTemplate = editingTemplate;
|
||||
BaseDirectory = baseDirectory;
|
||||
DefaultTemplate = defaultTemplate;
|
||||
@ -115,12 +122,12 @@ namespace LibationFileManager
|
||||
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))
|
||||
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)
|
||||
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;
|
||||
}
|
||||
|
||||
public static ITemplateEditor CreateNameEditor(string templateText)
|
||||
public static ITemplateEditor CreateNameEditor(string templateText, LibraryBookDto? libraryBookDto = null)
|
||||
{
|
||||
if (!Templates.TryGetTemplate<T>(templateText, out var nameTemplate))
|
||||
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)
|
||||
throw new InvalidOperationException($"This method is only for name templates. Use {nameof(CreateFilenameEditor)} for file templates");
|
||||
|
||||
114
Source/LibationUiBase/GridView/GridContextMenu.cs
Normal file
114
Source/LibationUiBase/GridView/GridContextMenu.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@ -37,7 +37,7 @@ namespace LibationUiBase.GridView
|
||||
private string _purchasedate;
|
||||
private string _length;
|
||||
private LastDownloadStatus _lastDownload;
|
||||
private object _cover;
|
||||
private Lazy<object> _lazyCover;
|
||||
private string _series;
|
||||
private SeriesOrder _seriesOrder;
|
||||
private string _title;
|
||||
@ -55,7 +55,7 @@ namespace LibationUiBase.GridView
|
||||
public string PurchaseDate { get => _purchasedate; protected set => RaiseAndSetIfChanged(ref _purchasedate, value); }
|
||||
public string Length { get => _length; protected set => RaiseAndSetIfChanged(ref _length, 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 SeriesOrder SeriesOrder { get => _seriesOrder; private set => RaiseAndSetIfChanged(ref _seriesOrder, value); }
|
||||
public string Title { get => _title; private set => RaiseAndSetIfChanged(ref _title, value); }
|
||||
@ -132,7 +132,7 @@ namespace LibationUiBase.GridView
|
||||
int bookLenMins = GetLengthInMinutes();
|
||||
return bookLenMins == 0 ? "" : $"{bookLenMins / 60} hr {bookLenMins % 60} min";
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region detect changes to the model, update the view.
|
||||
@ -200,36 +200,31 @@ namespace LibationUiBase.GridView
|
||||
|
||||
#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(Series), () => Book.SeriesSortable() },
|
||||
{ nameof(SeriesOrder), () => SeriesOrder },
|
||||
{ nameof(Length), () => GetLengthInMinutes() },
|
||||
{ nameof(MyRating), () => Book.UserDefinedItem.Rating },
|
||||
{ nameof(PurchaseDate), () => GetPurchaseDate() },
|
||||
{ nameof(ProductRating), () => Book.Rating },
|
||||
{ nameof(Authors), () => Authors },
|
||||
{ nameof(Narrators), () => Narrators },
|
||||
{ nameof(Description), () => Description },
|
||||
{ nameof(Category), () => Category },
|
||||
{ nameof(Misc), () => Misc },
|
||||
{ nameof(LastDownload), () => LastDownload },
|
||||
{ nameof(BookTags), () => BookTags ?? string.Empty },
|
||||
{ nameof(Liberate), () => Liberate },
|
||||
{ nameof(DateAdded), () => DateAdded },
|
||||
};
|
||||
}
|
||||
nameof(Remove) => Remove.HasValue ? Remove.Value ? RemoveStatus.Removed : RemoveStatus.NotRemoved : RemoveStatus.SomeRemoved,
|
||||
nameof(Title) => Book.TitleSortable(),
|
||||
nameof(Series) => Book.SeriesSortable(),
|
||||
nameof(SeriesOrder) => SeriesOrder,
|
||||
nameof(Length) => GetLengthInMinutes(),
|
||||
nameof(MyRating) => Book.UserDefinedItem.Rating,
|
||||
nameof(PurchaseDate) => GetPurchaseDate(),
|
||||
nameof(ProductRating) => Book.Rating,
|
||||
nameof(Authors) => Authors,
|
||||
nameof(Narrators) => Narrators,
|
||||
nameof(Description) => Description,
|
||||
nameof(Category) => Category,
|
||||
nameof(Misc) => Misc,
|
||||
nameof(LastDownload) => LastDownload,
|
||||
nameof(BookTags) => BookTags ?? string.Empty,
|
||||
nameof(Liberate) => Liberate,
|
||||
nameof(DateAdded) => DateAdded,
|
||||
_ => null
|
||||
};
|
||||
|
||||
public object GetMemberValue(string memberName) => memberValues[memberName]();
|
||||
public IComparer GetMemberComparer(Type memberType)
|
||||
=> 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.
|
||||
private static readonly Dictionary<Type, IComparer> memberTypeComparers = new()
|
||||
{
|
||||
@ -258,7 +253,7 @@ namespace LibationUiBase.GridView
|
||||
PictureStorage.PictureCached += PictureStorage_PictureCached;
|
||||
|
||||
// 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)
|
||||
@ -272,7 +267,8 @@ namespace LibationUiBase.GridView
|
||||
// logic validation
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,19 +102,19 @@ namespace LibationWinForms.GridView
|
||||
|
||||
private void productsGrid_CellContextMenuStripNeeded(IGridEntry entry, ContextMenuStrip ctxMenu)
|
||||
{
|
||||
var ctx = new GridContextMenu(entry, '&');
|
||||
#region Liberate all Episodes
|
||||
|
||||
if (entry.Liberate.IsSeries)
|
||||
{
|
||||
var liberateEpisodesMenuItem = new ToolStripMenuItem()
|
||||
{
|
||||
Text = "&Liberate All Episodes",
|
||||
Enabled = ((ISeriesEntry)entry).Children.Any(c => c.Liberate.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload)
|
||||
Text = ctx.LiberateEpisodesText,
|
||||
Enabled = ctx.LiberateEpisodesEnabled
|
||||
};
|
||||
|
||||
ctxMenu.Items.Add(liberateEpisodesMenuItem);
|
||||
|
||||
liberateEpisodesMenuItem.Click += (_, _) => LiberateSeriesClicked?.Invoke(this, (ISeriesEntry)entry);
|
||||
ctxMenu.Items.Add(liberateEpisodesMenuItem);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -122,61 +122,44 @@ namespace LibationWinForms.GridView
|
||||
|
||||
var setDownloadMenuItem = new ToolStripMenuItem()
|
||||
{
|
||||
Text = "Set Download status to '&Downloaded'",
|
||||
Enabled = entry.Book.UserDefinedItem.BookStatus != LiberatedStatus.Liberated || entry.Liberate.IsSeries
|
||||
Text = ctx.SetDownloadedText,
|
||||
Enabled = ctx.SetDownloadedEnabled
|
||||
};
|
||||
|
||||
setDownloadMenuItem.Click += (_, _) => ctx.SetDownloaded();
|
||||
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
|
||||
#region Set Download status to Not Downloaded
|
||||
|
||||
var setNotDownloadMenuItem = new ToolStripMenuItem()
|
||||
{
|
||||
Text = "Set Download status to '&Not Downloaded'",
|
||||
Enabled = entry.Book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated || entry.Liberate.IsSeries
|
||||
Text = ctx.SetNotDownloadedText,
|
||||
Enabled = ctx.SetNotDownloadedEnabled
|
||||
};
|
||||
|
||||
setNotDownloadMenuItem.Click += (_, _) => ctx.SetNotDownloaded();
|
||||
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
|
||||
#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);
|
||||
|
||||
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
|
||||
|
||||
if (!entry.Liberate.IsSeries)
|
||||
{
|
||||
#region Locate file
|
||||
var locateFileMenuItem = new ToolStripMenuItem() { Text = "&Locate file..." };
|
||||
|
||||
var locateFileMenuItem = new ToolStripMenuItem() { Text = ctx.LocateFileText };
|
||||
ctxMenu.Items.Add(locateFileMenuItem);
|
||||
|
||||
locateFileMenuItem.Click += (_, _) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var openFileDialog = new OpenFileDialog
|
||||
{
|
||||
Title = $"Locate the audio file for '{entry.Book.TitleWithSubtitle}'",
|
||||
Title = ctx.LocateFileDialogTitle,
|
||||
Filter = "All files (*.*)|*.*",
|
||||
FilterIndex = 1
|
||||
};
|
||||
@ -185,8 +168,7 @@ namespace LibationWinForms.GridView
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = "Error saving book's location";
|
||||
MessageBoxLib.ShowAdminAlert(this, msg, msg, ex);
|
||||
MessageBoxLib.ShowAdminAlert(this, ctx.LocateFileErrorMessage, ctx.LocateFileErrorMessage, ex);
|
||||
}
|
||||
};
|
||||
|
||||
@ -195,13 +177,11 @@ namespace LibationWinForms.GridView
|
||||
|
||||
var convertToMp3MenuItem = new ToolStripMenuItem
|
||||
{
|
||||
Text = "&Convert to Mp3",
|
||||
Enabled = entry.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated
|
||||
Text = ctx.ConvertToMp3Text,
|
||||
Enabled = ctx.ConvertToMp3Enabled
|
||||
};
|
||||
|
||||
ctxMenu.Items.Add(convertToMp3MenuItem);
|
||||
|
||||
convertToMp3MenuItem.Click += (_, e) => ConvertToMp3Clicked?.Invoke(this, entry.LibraryBook);
|
||||
ctxMenu.Items.Add(convertToMp3MenuItem);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -211,10 +191,9 @@ namespace LibationWinForms.GridView
|
||||
{
|
||||
var reDownloadMenuItem = new ToolStripMenuItem()
|
||||
{
|
||||
Text = "Re-download this audiobook",
|
||||
Enabled = entry.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated
|
||||
Text = ctx.ReDownloadText,
|
||||
Enabled = ctx.ReDownloadEnabled
|
||||
};
|
||||
|
||||
ctxMenu.Items.Add(reDownloadMenuItem);
|
||||
reDownloadMenuItem.Click += (s, _) =>
|
||||
{
|
||||
@ -223,6 +202,35 @@ namespace LibationWinForms.GridView
|
||||
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
|
||||
|
||||
ctxMenu.Items.Add(new ToolStripSeparator());
|
||||
@ -231,11 +239,9 @@ namespace LibationWinForms.GridView
|
||||
|
||||
if (!entry.Liberate.IsSeries)
|
||||
{
|
||||
var bookRecordMenuItem = new ToolStripMenuItem { Text = "View &Bookmarks/Clips" };
|
||||
|
||||
ctxMenu.Items.Add(bookRecordMenuItem);
|
||||
|
||||
var bookRecordMenuItem = new ToolStripMenuItem { Text = ctx.ViewBookmarksText };
|
||||
bookRecordMenuItem.Click += (_, _) => new BookRecordsDialog(entry.LibraryBook).ShowDialog(this);
|
||||
ctxMenu.Items.Add(bookRecordMenuItem);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -243,13 +249,9 @@ namespace LibationWinForms.GridView
|
||||
|
||||
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 = header };
|
||||
|
||||
ctxMenu.Items.Add(viewSeriesMenuItem);
|
||||
|
||||
var viewSeriesMenuItem = new ToolStripMenuItem { Text = ctx.ViewSeriesText };
|
||||
viewSeriesMenuItem.Click += (_, _) => new SeriesViewDialog(entry.LibraryBook).Show();
|
||||
ctxMenu.Items.Add(viewSeriesMenuItem);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -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">
|
||||
<path id="glass" d=
|
||||
"M139,2
|
||||
A 192,200 0 0 0 103,84
|
||||
A 222,334 41 0 0 241,320
|
||||
V478
|
||||
H160
|
||||
A 16,16 0 0 0 160,510
|
||||
H352
|
||||
A16 16 0 0 0 352,478
|
||||
H271
|
||||
V320
|
||||
A 222,334 -41 0 0 409,84
|
||||
A 192,200 0 0 0 373,2
|
||||
M355,32
|
||||
A 192,200 0 0 1 381,127
|
||||
A 187.5,334 -35 0 1 256,286
|
||||
A 187.5,334 35 0 1 131,127
|
||||
A 192,200 0 0 1 157,32
|
||||
H355
|
||||
z" />
|
||||
|
||||
<path id="wine-level" d=
|
||||
"M146,128
|
||||
A 168,300 35 0 0 256,270
|
||||
A 168,300 -35 0 0 366,128
|
||||
z"/>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 524 524" enable-background="new 0 0 524 524">
|
||||
<defs>
|
||||
<g id="glass">
|
||||
<path fill-rule="evenodd" d=
|
||||
"M262,8
|
||||
h-117
|
||||
a 192,200 0 0 0 -36,82
|
||||
a 222,334 41 0 0 138,236
|
||||
v158
|
||||
h-81
|
||||
a 16,16 0 0 0 0,32
|
||||
h192
|
||||
a 16 16 0 0 0 0,-32
|
||||
h-81
|
||||
v-158
|
||||
a 222,334 -41 0 0 138,-236
|
||||
a 192,200 0 0 0 -36,-82
|
||||
h-117
|
||||
m-99,30
|
||||
a 192,200 0 0 0 -26,95
|
||||
a 187.5,334 35 0 0 125,159
|
||||
a 187.5,334 -35 0 0 125,-159
|
||||
a 192,200 0 0 0 -26,-95
|
||||
h-198
|
||||
z"/>
|
||||
</g>
|
||||
<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>
|
||||
|
||||
|
Before Width: | Height: | Size: 618 B After Width: | Height: | Size: 968 B |
Loading…
x
Reference in New Issue
Block a user