Merge pull request #549 from Mbucari/master

Lots of Bug Fixes and 2 New Features.
This commit is contained in:
rmcrackan 2023-03-26 22:54:08 -04:00 committed by GitHub
commit 2cd9b86930
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
145 changed files with 3039 additions and 1322 deletions

BIN
Images/Plus Minus.psd Normal file

Binary file not shown.

View File

@ -12,6 +12,7 @@ using DtoImporterService;
using FileManager; using FileManager;
using LibationFileManager; using LibationFileManager;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NPOI.OpenXmlFormats.Spreadsheet;
using Serilog; using Serilog;
using static DtoImporterService.PerfLogger; using static DtoImporterService.PerfLogger;
@ -171,6 +172,59 @@ namespace ApplicationServices
} }
} }
public static async Task<int> ImportSingleToDbAsync(AudibleApi.Common.Item item, string accountId, string localeName)
{
ArgumentValidator.EnsureNotNull(item, "item");
ArgumentValidator.EnsureNotNull(accountId, "accountId");
ArgumentValidator.EnsureNotNull(localeName, "localeName");
var importItem = new ImportItem
{
DtoItem = item,
AccountId = accountId,
LocaleName = localeName
};
var importItems = new List<ImportItem> { importItem };
var validator = new LibraryValidator();
var exceptions = validator.Validate(importItems.Select(i => i.DtoItem));
if (exceptions?.Any() ?? false)
{
Log.Logger.Error(new AggregateException(exceptions), "Error validating library book. {@DebugInfo}", new { item, accountId, localeName });
return 0;
}
using var context = DbContexts.GetContext();
var bookImporter = new BookImporter(context);
await Task.Run(() => bookImporter.Import(importItems));
var book = await Task.Run(() => context.LibraryBooks.FirstOrDefault(lb => lb.Book.AudibleProductId == importItem.DtoItem.ProductId));
if (book is null)
{
book = new LibraryBook(bookImporter.Cache[importItem.DtoItem.ProductId], importItem.DtoItem.DateAdded, importItem.AccountId);
context.LibraryBooks.Add(book);
}
else
{
book.AbsentFromLastScan = false;
}
try
{
int qtyChanged = await Task.Run(() => SaveContext(context));
if (qtyChanged > 0)
await Task.Run(finalizeLibrarySizeChange);
return qtyChanged;
}
catch (Exception ex)
{
Log.Logger.Error(ex, "Error adding single library book to DB. {@DebugInfo}", new { item, accountId, localeName });
return 0;
}
}
private static async Task<List<ImportItem>> scanAccountsAsync(Func<Account, Task<ApiExtended>> apiExtendedfunc, Account[] accounts, LibraryOptions libraryOptions) private static async Task<List<ImportItem>> scanAccountsAsync(Func<Account, Task<ApiExtended>> apiExtendedfunc, Account[] accounts, LibraryOptions libraryOptions)
{ {
var tasks = new List<Task<List<ImportItem>>>(); var tasks = new List<Task<List<ImportItem>>>();
@ -183,6 +237,8 @@ namespace ApplicationServices
archiver?.DeleteAllButNewestN(20); archiver?.DeleteAllButNewestN(20);
foreach (var account in accounts) foreach (var account in accounts)
{
try
{ {
// get APIs in serial b/c of logins. do NOT move inside of parallel (Task.WhenAll) // get APIs in serial b/c of logins. do NOT move inside of parallel (Task.WhenAll)
var apiExtended = await apiExtendedfunc(account); var apiExtended = await apiExtendedfunc(account);
@ -190,6 +246,12 @@ namespace ApplicationServices
// add scanAccountAsync as a TASK: do not await // add scanAccountAsync as a TASK: do not await
tasks.Add(scanAccountAsync(apiExtended, account, libraryOptions, archiver)); tasks.Add(scanAccountAsync(apiExtended, account, libraryOptions, archiver));
} }
catch(Exception ex)
{
//Catch to allow other accounts to continue scanning.
Log.Logger.Error(ex, "Failed to scan account");
}
}
// import library in parallel // import library in parallel
var arrayOfLists = await Task.WhenAll(tasks); var arrayOfLists = await Task.WhenAll(tasks);
@ -208,8 +270,24 @@ namespace ApplicationServices
logTime($"pre scanAccountAsync {account.AccountName}"); logTime($"pre scanAccountAsync {account.AccountName}");
try
{
var dtoItems = await apiExtended.GetLibraryValidatedAsync(libraryOptions, Configuration.Instance.ImportEpisodes); var dtoItems = await apiExtended.GetLibraryValidatedAsync(libraryOptions, Configuration.Instance.ImportEpisodes);
logTime($"post scanAccountAsync {account.AccountName} qty: {dtoItems.Count}");
await logDtoItemsAsync(dtoItems);
return dtoItems.Select(d => new ImportItem { DtoItem = d, AccountId = account.AccountId, LocaleName = account.Locale?.Name }).ToList();
}
catch(ImportValidationException ex)
{
await logDtoItemsAsync(ex.Items, ex.InnerExceptions.ToArray());
throw;
}
async Task logDtoItemsAsync(IEnumerable<AudibleApi.Common.Item> dtoItems, IEnumerable<Exception> exceptions = null)
{
if (archiver is not null) if (archiver is not null)
{ {
var fileName = $"{DateTime.Now:u} {account.MaskedLogEntry}.json"; var fileName = $"{DateTime.Now:u} {account.MaskedLogEntry}.json";
@ -219,15 +297,16 @@ namespace ApplicationServices
{ {
{ "Account", account.MaskedLogEntry }, { "Account", account.MaskedLogEntry },
{ "ScannedDateTime", DateTime.Now.ToString("u") }, { "ScannedDateTime", DateTime.Now.ToString("u") },
{ "Items", items}
}; };
if (exceptions?.Any() is true)
scanFile.Add("Exceptions", JArray.FromObject(exceptions));
scanFile.Add("Items", items);
await archiver.AddFileAsync(fileName, scanFile); await archiver.AddFileAsync(fileName, scanFile);
} }
}
logTime($"post scanAccountAsync {account.AccountName} qty: {dtoItems.Count}");
return dtoItems.Select(d => new ImportItem { DtoItem = d, AccountId = account.AccountId, LocaleName = account.Locale?.Name }).ToList();
} }
private static async Task<int> importIntoDbAsync(List<ImportItem> importItems) private static async Task<int> importIntoDbAsync(List<ImportItem> importItems)

View File

@ -149,7 +149,7 @@ namespace AudibleUtilities
foreach (var parent in items.Where(i => i.IsSeriesParent)) foreach (var parent in items.Where(i => i.IsSeriesParent))
{ {
var children = items.Where(i => i.IsEpisodes && i.Relationships.Any(r => r.Asin == parent.Asin)); var children = items.Where(i => i.IsEpisodes && i.Relationships.Any(r => r.Asin == parent.Asin));
setSeries(parent, children); SetSeries(parent, children);
} }
sw.Stop(); sw.Stop();
@ -157,27 +157,13 @@ namespace AudibleUtilities
Serilog.Log.Logger.Information("Completed indexing series episodes after {elappsed_ms} ms.", sw.ElapsedMilliseconds); Serilog.Log.Logger.Information("Completed indexing series episodes after {elappsed_ms} ms.", sw.ElapsedMilliseconds);
Serilog.Log.Logger.Information($"Completed library scan in {totalTime.TotalMilliseconds:F0} ms."); Serilog.Log.Logger.Information($"Completed library scan in {totalTime.TotalMilliseconds:F0} ms.");
var validators = new List<IValidator>(); var allExceptions = IValidator.GetAllValidators().SelectMany(v => v.Validate(items));
validators.AddRange(getValidators()); if (allExceptions?.Any() is true)
foreach (var v in validators) throw new ImportValidationException(items, allExceptions);
{
var exceptions = v.Validate(items);
if (exceptions is not null && exceptions.Any())
throw new AggregateException(exceptions);
}
return items; return items;
} }
private static List<IValidator> getValidators()
{
var type = typeof(IValidator);
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => type.IsAssignableFrom(p) && !p.IsInterface);
return types.Select(t => Activator.CreateInstance(t) as IValidator).ToList();
}
#region episodes and podcasts #region episodes and podcasts
/// <summary> /// <summary>
@ -232,8 +218,11 @@ namespace AudibleUtilities
finally { semaphore.Release(); } finally { semaphore.Release(); }
} }
private static void setSeries(Item parent, IEnumerable<Item> children) public static void SetSeries(Item parent, IEnumerable<Item> children)
{ {
ArgumentValidator.EnsureNotNull(parent, nameof(parent));
ArgumentValidator.EnsureNotNull(children, nameof(children));
//A series parent will always have exactly 1 Series //A series parent will always have exactly 1 Series
parent.Series = new[] parent.Series = new[]
{ {
@ -246,7 +235,15 @@ namespace AudibleUtilities
}; };
if (parent.PurchaseDate == default) if (parent.PurchaseDate == default)
parent.PurchaseDate = children.Select(c => c.PurchaseDate).Order().First(); {
parent.PurchaseDate = children.Select(c => c.PurchaseDate).Order().FirstOrDefault(d => d != default);
if (parent.PurchaseDate == default)
{
Serilog.Log.Logger.Warning("{series} doesn't have a purchase date. Using UtcNow", parent);
parent.PurchaseDate = DateTimeOffset.UtcNow;
}
}
foreach (var child in children) foreach (var child in children)
{ {

View File

@ -8,7 +8,18 @@ namespace AudibleUtilities
public interface IValidator public interface IValidator
{ {
IEnumerable<Exception> Validate(IEnumerable<Item> items); IEnumerable<Exception> Validate(IEnumerable<Item> items);
public static IValidator[] GetAllValidators()
=> new IValidator[]
{
new LibraryValidator(),
new BookValidator(),
new CategoryValidator(),
new ContributorValidator(),
new SeriesValidator(),
};
} }
public class LibraryValidator : IValidator public class LibraryValidator : IValidator
{ {
public IEnumerable<Exception> Validate(IEnumerable<Item> items) public IEnumerable<Exception> Validate(IEnumerable<Item> items)

View File

@ -0,0 +1,15 @@
using AudibleApi.Common;
using System;
using System.Collections.Generic;
namespace AudibleUtilities
{
public class ImportValidationException : AggregateException
{
public List<Item> Items { get; }
public ImportValidationException(List<Item> items, IEnumerable<Exception> exceptions) : base(exceptions)
{
Items = items;
}
}
}

View File

@ -25,6 +25,8 @@ namespace DataLayer
Account = account; Account = account;
} }
public void SetAccount(string account) => Account = account;
public override string ToString() => $"{DateAdded:d} {Book}"; public override string ToString() => $"{DateAdded:d} {Book}";
} }
} }

View File

@ -18,9 +18,9 @@ namespace DataLayer
/// <summary>True if exists and IsLiberated. Else false</summary> /// <summary>True if exists and IsLiberated. Else false</summary>
public static bool PDF_Exists(this Book book) => book.UserDefinedItem.PdfStatus == LiberatedStatus.Liberated; public static bool PDF_Exists(this Book book) => book.UserDefinedItem.PdfStatus == LiberatedStatus.Liberated;
public static string SeriesSortable(this Book book) => Formatters.GetSortName(book.SeriesNames()); public static string SeriesSortable(this Book book) => Formatters.GetSortName(book.SeriesNames(true));
public static bool HasPdf(this Book book) => book.Supplements.Any(); public static bool HasPdf(this Book book) => book.Supplements.Any();
public static string SeriesNames(this Book book) public static string SeriesNames(this Book book, bool includeIndex = false)
{ {
if (book.SeriesLink is null) if (book.SeriesLink is null)
return ""; return "";
@ -28,7 +28,7 @@ namespace DataLayer
// first: alphabetical by name // first: alphabetical by name
var withNames = book.SeriesLink var withNames = book.SeriesLink
.Where(s => !string.IsNullOrWhiteSpace(s.Series.Name)) .Where(s => !string.IsNullOrWhiteSpace(s.Series.Name))
.Select(s => s.Series.Name) .Select(getSeriesNameString)
.OrderBy(a => a) .OrderBy(a => a)
.ToList(); .ToList();
// then un-named are alpha by series id // then un-named are alpha by series id
@ -40,6 +40,11 @@ namespace DataLayer
var all = withNames.Union(nullNames).ToList(); var all = withNames.Union(nullNames).ToList();
return string.Join(", ", all); return string.Join(", ", all);
string getSeriesNameString(SeriesBook sb)
=> includeIndex && !string.IsNullOrWhiteSpace(sb.Order) && sb.Order != "-1"
? $"{sb.Series.Name} (#{sb.Order})"
: sb.Series.Name;
} }
public static string[] CategoriesNames(this Book book) public static string[] CategoriesNames(this Book book)
=> book.Category is null ? new string[0] => book.Category is null ? new string[0]

View File

@ -41,34 +41,43 @@ namespace DtoImporterService
// //
// CURRENT SOLUTION: don't re-insert // CURRENT SOLUTION: don't re-insert
var newItems = importItems var existingEntries = DbContext.LibraryBooks.AsEnumerable().Where(l => l.Book is not null).ToDictionary(l => l.Book.AudibleProductId);
.ExceptBy(DbContext.LibraryBooks.Select(lb => lb.Book.AudibleProductId), imp => imp.DtoItem.ProductId) var hash = ToDictionarySafe(importItems, dto => dto.DtoItem.ProductId, tieBreak);
.ToList(); int qtyNew = 0;
// if 2 accounts try to import the same book in the same transaction: error since we're only tracking and pulling by asin. foreach (var item in hash.Values)
// just use the first
var hash = newItems.ToDictionarySafe(dto => dto.DtoItem.ProductId);
foreach (var kvp in hash)
{ {
var newItem = kvp.Value; if (existingEntries.TryGetValue(item.DtoItem.ProductId, out LibraryBook existing))
{
if (existing.Account != item.AccountId)
{
//Book is absent from the existing LibraryBook's account. Use the alternate account.
existing.SetAccount(item.AccountId);
}
existing.AbsentFromLastScan = isPlusTitleUnavailable(item);
}
else
{
var libraryBook = new LibraryBook( var libraryBook = new LibraryBook(
bookImporter.Cache[newItem.DtoItem.ProductId], bookImporter.Cache[item.DtoItem.ProductId],
newItem.DtoItem.DateAdded, item.DtoItem.DateAdded,
newItem.AccountId) item.AccountId)
{ {
AbsentFromLastScan = isPlusTitleUnavailable(newItem) AbsentFromLastScan = isPlusTitleUnavailable(item)
}; };
try try
{ {
DbContext.LibraryBooks.Add(libraryBook); DbContext.LibraryBooks.Add(libraryBook);
qtyNew++;
} }
catch (Exception ex) catch (Exception ex)
{ {
Serilog.Log.Logger.Error(ex, "Error adding library book. {@DebugInfo}", new { libraryBook.Book, libraryBook.Account }); Serilog.Log.Logger.Error(ex, "Error adding library book. {@DebugInfo}", new { libraryBook.Book, libraryBook.Account });
} }
} }
}
var scannedAccounts = importItems.Select(i => i.AccountId).Distinct().ToList(); var scannedAccounts = importItems.Select(i => i.AccountId).Distinct().ToList();
@ -77,20 +86,28 @@ namespace DtoImporterService
foreach (var nullBook in DbContext.LibraryBooks.AsEnumerable().Where(lb => lb.Book is null && lb.Account.In(scannedAccounts))) foreach (var nullBook in DbContext.LibraryBooks.AsEnumerable().Where(lb => lb.Book is null && lb.Account.In(scannedAccounts)))
nullBook.AbsentFromLastScan = true; nullBook.AbsentFromLastScan = true;
//Join importItems on LibraryBooks before iterating over LibraryBooks to avoid
//quadratic complexity caused by searching all of importItems for each LibraryBook.
//Join uses hashing, so complexity should approach O(N) instead of O(N^2).
var items_lbs
= importItems
.Join(DbContext.LibraryBooks, o => (o.AccountId, o.DtoItem.ProductId), i => (i.Account, i.Book?.AudibleProductId), (o, i) => (o, i));
foreach ((ImportItem item, LibraryBook lb) in items_lbs)
lb.AbsentFromLastScan = isPlusTitleUnavailable(item);
var qtyNew = hash.Count;
return qtyNew; return qtyNew;
} }
private static Dictionary<TKey, TSource> ToDictionarySafe<TKey, TSource>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TSource, TSource> tieBreaker)
{
var dictionary = new Dictionary<TKey, TSource>();
foreach (TSource newItem in source)
{
TKey key = keySelector(newItem);
dictionary[key]
= dictionary.TryGetValue(key, out TSource existingItem)
? tieBreaker(existingItem, newItem)
: newItem;
}
return dictionary;
}
private static ImportItem tieBreak(ImportItem item1, ImportItem item2)
=> isPlusTitleUnavailable(item1) && !isPlusTitleUnavailable(item2) ? item2 : item1;
private static bool isPlusTitleUnavailable(ImportItem item) private static bool isPlusTitleUnavailable(ImportItem item)
=> item.DtoItem.IsAyce is true => item.DtoItem.IsAyce is true
&& item.DtoItem.Plans?.Any(p => p.IsAyce) is not true; && item.DtoItem.Plans?.Any(p => p.IsAyce) is not true;

View File

@ -7,6 +7,6 @@
</Application.DataTemplates> </Application.DataTemplates>
<Application.Styles> <Application.Styles>
<FluentTheme Mode="Light"/> <FluentTheme/>
</Application.Styles> </Application.Styles>
</Application> </Application>

View File

@ -66,13 +66,13 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.0-preview4" /> <PackageReference Include="Avalonia" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-preview4" /> <PackageReference Include="Avalonia.Desktop" Version="11.0.0-preview6" />
<!--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.0-preview4" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview4" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-preview4" /> <PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.0-preview6" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.6.1" /> <PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-preview6" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\HangoverBase\HangoverBase.csproj" /> <ProjectReference Include="..\HangoverBase\HangoverBase.csproj" />

View File

@ -7,7 +7,7 @@ namespace HangoverAvalonia
{ {
public class ViewLocator : IDataTemplate public class ViewLocator : IDataTemplate
{ {
public IControl Build(object data) public Control Build(object data)
{ {
var name = data.GetType().FullName!.Replace("ViewModel", "View"); var name = data.GetType().FullName!.Replace("ViewModel", "View");
var type = Type.GetType(name); var type = Type.GetType(name);

View File

@ -64,7 +64,8 @@ namespace HangoverBase
try try
{ {
var sql = _commands.SqlInput().Trim(); var sql = _commands.SqlInput()?.Trim();
if (sql is null) return;
#region // explanation #region // explanation
// Routing statements to non-query is a convenience. // Routing statements to non-query is a convenience.

View File

@ -7,11 +7,66 @@
<local:ViewLocator/> <local:ViewLocator/>
</Application.DataTemplates> </Application.DataTemplates>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="SeriesEntryGridBackgroundBrush" Opacity="0.3" Color="#abffab" />
<SolidColorBrush x:Key="ProcessQueueBookFailedBrush" Color="LightCoral" />
<SolidColorBrush x:Key="ProcessQueueBookCompletedBrush" Color="PaleGreen" />
<SolidColorBrush x:Key="ProcessQueueBookCancelledBrush" Color="Khaki" />
<SolidColorBrush x:Key="HyperlinkNew" Color="Blue" />
<SolidColorBrush x:Key="HyperlinkVisited" Color="Purple" />
<SolidColorBrush x:Key="ProcessQueueBookDefaultBrush" Color="White" />
<SolidColorBrush x:Key="SystemOpaqueBase" Color="White" />
<SolidColorBrush x:Key="CancelRed" Color="FireBrick" />
<SolidColorBrush x:Key="IconFill" Color="#231F20" />
<SolidColorBrush x:Key="StoplightRed" Color="#F06060" />
<SolidColorBrush x:Key="StoplightYellow" Color="#F0E160" />
<SolidColorBrush x:Key="StoplightGreen" Color="#70FA70" />
<SolidColorBrush x:Key="DisabledGrayBrush" Opacity="0.4" Color="{StaticResource SystemChromeMediumColor}" />
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="SeriesEntryGridBackgroundBrush" Opacity="0.3" Color="#bed2fa" />
<SolidColorBrush x:Key="ProcessQueueBookFailedBrush" Color="#502727" />
<SolidColorBrush x:Key="ProcessQueueBookCompletedBrush" Color="#1c3e20" />
<SolidColorBrush x:Key="ProcessQueueBookCancelledBrush" Color="#4e4b15" />
<SolidColorBrush x:Key="HyperlinkNew" Color="CornflowerBlue" />
<SolidColorBrush x:Key="HyperlinkVisited" Color="Orchid" />
<SolidColorBrush x:Key="ProcessQueueBookDefaultBrush" Color="Black" />
<SolidColorBrush x:Key="SystemOpaqueBase" Color="Black" />
<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="DisabledGrayBrush" Opacity="0.4" Color="{StaticResource SystemChromeMediumColor}" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</Application.Resources>
<Application.Styles> <Application.Styles>
<FluentTheme Mode="Light"/> <FluentTheme />
<StyleInclude Source="avares://Avalonia.Themes.Fluent/FluentLight.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Fluent/FluentTheme.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml"/> <StyleInclude Source="/Assets/DataGridFluentTheme.xaml"/>
<StyleInclude Source="/Assets/DataGridTheme.xaml"/> <StyleInclude Source="/Assets/LibationVectorIcons.xaml"/>
<StyleInclude Source="/Assets/LibationStyles.xaml"/>
<Style Selector="TextBox[IsReadOnly=true]">
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundBaseLowBrush}" />
<Setter Property="CaretBrush" Value="{DynamicResource SystemControlTransparentBrush}" />
<Style Selector="^ /template/ Border#PART_BorderElement">
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundBaseLowBrush}" />
</Style>
</Style>
</Application.Styles> </Application.Styles>
</Application> </Application>

View File

@ -12,6 +12,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using ApplicationServices; using ApplicationServices;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Styling;
namespace LibationAvalonia namespace LibationAvalonia
{ {
@ -23,6 +24,7 @@ namespace LibationAvalonia
public static IBrush ProcessQueueBookCancelledBrush { get; private set; } public static IBrush ProcessQueueBookCancelledBrush { get; private set; }
public static IBrush ProcessQueueBookDefaultBrush { get; private set; } public static IBrush ProcessQueueBookDefaultBrush { get; private set; }
public static IBrush SeriesEntryGridBackgroundBrush { get; private set; } public static IBrush SeriesEntryGridBackgroundBrush { get; private set; }
public static IBrush HyperlinkVisited { get; private set; }
public static IAssetLoader AssetLoader { get; private set; } public static IAssetLoader AssetLoader { get; private set; }
@ -214,6 +216,10 @@ namespace LibationAvalonia
private static void ShowMainWindow(IClassicDesktopStyleApplicationLifetime desktop) private static void ShowMainWindow(IClassicDesktopStyleApplicationLifetime desktop)
{ {
Current.RequestedThemeVariant = Configuration.Instance.GetString(propertyName: nameof(ThemeVariant)) is "Dark" ? ThemeVariant.Dark : ThemeVariant.Light;
//Reload colors for current theme
LoadStyles();
var mainWindow = new MainWindow(); var mainWindow = new MainWindow();
desktop.MainWindow = MainWindow = mainWindow; desktop.MainWindow = MainWindow = mainWindow;
mainWindow.RestoreSizeAndLocation(Configuration.Instance); mainWindow.RestoreSizeAndLocation(Configuration.Instance);
@ -227,8 +233,9 @@ namespace LibationAvalonia
ProcessQueueBookFailedBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookFailedBrush"); ProcessQueueBookFailedBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookFailedBrush");
ProcessQueueBookCompletedBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookCompletedBrush"); ProcessQueueBookCompletedBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookCompletedBrush");
ProcessQueueBookCancelledBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookCancelledBrush"); ProcessQueueBookCancelledBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookCancelledBrush");
ProcessQueueBookDefaultBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookDefaultBrush");
SeriesEntryGridBackgroundBrush = AvaloniaUtils.GetBrushFromResources("SeriesEntryGridBackgroundBrush"); SeriesEntryGridBackgroundBrush = AvaloniaUtils.GetBrushFromResources("SeriesEntryGridBackgroundBrush");
ProcessQueueBookDefaultBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookDefaultBrush");
HyperlinkVisited = AvaloniaUtils.GetBrushFromResources(nameof(HyperlinkVisited));
} }
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 455 B

View File

@ -0,0 +1,588 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:collections="using:Avalonia.Collections">
<Styles.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="DataGridColumnHeaderForegroundBrush" Color="{DynamicResource SystemBaseMediumColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderBackgroundBrush" Color="{DynamicResource SystemAltHighColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderHoveredBackgroundBrush" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderPressedBackgroundBrush" Color="{DynamicResource SystemListMediumColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderDraggedBackgroundBrush" Color="{DynamicResource SystemChromeMediumLowColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderBackgroundBrush" Color="{DynamicResource SystemChromeMediumColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderPressedBackgroundBrush" Color="{DynamicResource SystemListMediumColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderForegroundBrush" Color="{DynamicResource SystemBaseHighColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderHoveredBackgroundBrush" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridRowHoveredBackgroundColor" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridRowInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" />
<SolidColorBrush x:Key="DataGridCellFocusVisualPrimaryBrush" Color="{DynamicResource SystemBaseHighColor}" />
<SolidColorBrush x:Key="DataGridCellFocusVisualSecondaryBrush" Color="{DynamicResource SystemAltMediumColor}" />
<SolidColorBrush x:Key="DataGridCellInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" />
<SolidColorBrush x:Key="DataGridGridLinesBrush" Opacity="0.4" Color="{DynamicResource SystemBaseMediumLowColor}" />
<SolidColorBrush x:Key="DataGridDetailsPresenterBackgroundBrush" Color="{DynamicResource SystemChromeMediumLowColor}" />
</ResourceDictionary>
<ResourceDictionary x:Key="Default">
<SolidColorBrush x:Key="DataGridColumnHeaderForegroundBrush" Color="{DynamicResource SystemBaseMediumColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderBackgroundBrush" Color="{DynamicResource SystemAltHighColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderHoveredBackgroundBrush" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderPressedBackgroundBrush" Color="{DynamicResource SystemListMediumColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderDraggedBackgroundBrush" Color="{DynamicResource SystemChromeMediumLowColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderBackgroundBrush" Color="{DynamicResource SystemChromeMediumColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderPressedBackgroundBrush" Color="{DynamicResource SystemListMediumColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderForegroundBrush" Color="{DynamicResource SystemBaseHighColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderHoveredBackgroundBrush" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridRowHoveredBackgroundColor" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridRowInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" />
<SolidColorBrush x:Key="DataGridCellFocusVisualPrimaryBrush" Color="{DynamicResource SystemBaseHighColor}" />
<SolidColorBrush x:Key="DataGridCellFocusVisualSecondaryBrush" Color="{DynamicResource SystemAltMediumColor}" />
<SolidColorBrush x:Key="DataGridCellInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" />
<SolidColorBrush x:Key="DataGridGridLinesBrush" Opacity="0.4" Color="{DynamicResource SystemBaseMediumLowColor}" />
<SolidColorBrush x:Key="DataGridDetailsPresenterBackgroundBrush" Color="{DynamicResource SystemChromeMediumLowColor}" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<x:Double x:Key="ListAccentLowOpacity">0.6</x:Double>
<x:Double x:Key="ListAccentMediumOpacity">0.8</x:Double>
<StreamGeometry x:Key="DataGridSortIconDescendingPath">M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z</StreamGeometry>
<StreamGeometry x:Key="DataGridSortIconAscendingPath">M1965 947l-941 -941l-941 941l90 90l787 -787v1798h128v-1798l787 787z</StreamGeometry>
<StreamGeometry x:Key="DataGridRowGroupHeaderIconClosedPath">M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z</StreamGeometry>
<StreamGeometry x:Key="DataGridRowGroupHeaderIconOpenedPath">M109 486 19 576 1024 1581 2029 576 1939 486 1024 1401z</StreamGeometry>
<StaticResource x:Key="DataGridRowBackgroundBrush" ResourceKey="SystemControlTransparentBrush" />
<SolidColorBrush x:Key="DataGridRowSelectedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedBackgroundOpacity" ResourceKey="ListAccentLowOpacity" />
<SolidColorBrush x:Key="DataGridRowSelectedHoveredBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedHoveredBackgroundOpacity" ResourceKey="ListAccentMediumOpacity" />
<SolidColorBrush x:Key="DataGridRowSelectedUnfocusedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedUnfocusedBackgroundOpacity" ResourceKey="ListAccentLowOpacity" />
<SolidColorBrush x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundOpacity" ResourceKey="ListAccentMediumOpacity" />
<StaticResource x:Key="DataGridCellBackgroundBrush" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="DataGridCurrencyVisualPrimaryBrush" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="DataGridFillerColumnGridLinesBrush" ResourceKey="SystemControlTransparentBrush" />
<ControlTheme x:Key="DataGridCellTextBlockTheme" TargetType="TextBlock">
<Setter Property="Margin" Value="12,0,12,0" />
<Setter Property="VerticalAlignment" Value="Center" />
</ControlTheme>
<ControlTheme x:Key="DataGridCellTextBoxTheme" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Background" Value="Transparent" />
<Style Selector="^ /template/ DataValidationErrors">
<Setter Property="Theme" Value="{StaticResource TooltipDataValidationErrors}" />
</Style>
</ControlTheme>
<ControlTheme x:Key="{x:Type DataGridCell}" TargetType="DataGridCell">
<Setter Property="Background" Value="{DynamicResource DataGridCellBackgroundBrush}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="FontSize" Value="15" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="Focusable" Value="False" />
<Setter Property="Template">
<ControlTemplate>
<Border x:Name="CellBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid x:Name="PART_CellRoot" ColumnDefinitions="*,Auto">
<Rectangle x:Name="CurrencyVisual"
IsVisible="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCurrencyVisualPrimaryBrush}"
StrokeThickness="1" />
<Grid Grid.Column="0" x:Name="FocusVisual" IsHitTestVisible="False"
IsVisible="False">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualPrimaryBrush}"
StrokeThickness="2" />
<Rectangle Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualSecondaryBrush}"
StrokeThickness="1" />
</Grid>
<ContentPresenter Grid.Column="0" Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Foreground="{TemplateBinding Foreground}" />
<Rectangle Grid.Column="0" x:Name="InvalidVisualElement"
IsVisible="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellInvalidBrush}"
StrokeThickness="1" />
<Rectangle Name="PART_RightGridLine"
Grid.Column="1"
Width="1"
VerticalAlignment="Stretch"
Fill="{DynamicResource DataGridFillerColumnGridLinesBrush}" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:current /template/ Rectangle#CurrencyVisual">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="^:focus /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="^:invalid /template/ Rectangle#InvalidVisualElement">
<Setter Property="IsVisible" Value="True" />
</Style>
</ControlTheme>
<ControlTheme x:Key="{x:Type DataGridColumnHeader}" TargetType="DataGridColumnHeader">
<Setter Property="Foreground" Value="{DynamicResource DataGridColumnHeaderForegroundBrush}" />
<Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderBackgroundBrush}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Focusable" Value="False" />
<Setter Property="SeparatorBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
<Setter Property="Padding" Value="8,0,0,0" />
<Setter Property="FontSize" Value="12" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="Template">
<ControlTemplate>
<Border x:Name="HeaderBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid Name="PART_ColumnHeaderRoot" ColumnDefinitions="*,Auto">
<Grid Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="16" />
</Grid.ColumnDefinitions>
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
<Path Name="SortIcon"
IsVisible="False"
Grid.Column="1"
Height="12"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Fill="{TemplateBinding Foreground}"
Stretch="Uniform" />
</Grid>
<Rectangle Name="VerticalSeparator"
Grid.Column="1"
Width="1"
VerticalAlignment="Stretch"
Fill="{TemplateBinding SeparatorBrush}"
IsVisible="{TemplateBinding AreSeparatorsVisible}" />
<Grid x:Name="FocusVisual" IsHitTestVisible="False"
IsVisible="False">
<Rectangle x:Name="FocusVisualPrimary"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualPrimaryBrush}"
StrokeThickness="2" />
<Rectangle x:Name="FocusVisualSecondary"
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualSecondaryBrush}"
StrokeThickness="1" />
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:focus-visible /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="^:pointerover /template/ Grid#PART_ColumnHeaderRoot">
<Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderHoveredBackgroundBrush}" />
</Style>
<Style Selector="^:pressed /template/ Grid#PART_ColumnHeaderRoot">
<Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderPressedBackgroundBrush}" />
</Style>
<Style Selector="^:dragIndicator">
<Setter Property="Opacity" Value="0.5" />
</Style>
<Style Selector="^:sortascending /template/ Path#SortIcon">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Data" Value="{StaticResource DataGridSortIconAscendingPath}" />
</Style>
<Style Selector="^:sortdescending /template/ Path#SortIcon">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Data" Value="{StaticResource DataGridSortIconDescendingPath}" />
</Style>
</ControlTheme>
<ControlTheme x:Key="DataGridTopLeftColumnHeader" TargetType="DataGridColumnHeader" BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
<Setter Property="Template">
<ControlTemplate>
<Grid x:Name="TopLeftHeaderRoot"
RowDefinitions="*,*,Auto">
<Border Grid.RowSpan="2"
BorderThickness="0,0,1,0"
BorderBrush="{DynamicResource DataGridGridLinesBrush}" />
<Rectangle Grid.Row="0" Grid.RowSpan="2"
VerticalAlignment="Bottom"
StrokeThickness="1"
Height="1"
Fill="{DynamicResource DataGridGridLinesBrush}" />
</Grid>
</ControlTemplate>
</Setter>
</ControlTheme>
<ControlTheme x:Key="{x:Type DataGridRowHeader}" TargetType="DataGridRowHeader">
<Setter Property="Focusable" Value="False" />
<Setter Property="SeparatorBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
<Setter Property="AreSeparatorsVisible" Value="False" />
<Setter Property="Template">
<ControlTemplate>
<Grid x:Name="PART_Root"
RowDefinitions="*,*,Auto"
ColumnDefinitions="Auto,*">
<Border Grid.RowSpan="3"
Grid.ColumnSpan="2"
BorderBrush="{TemplateBinding SeparatorBrush}"
BorderThickness="0,0,1,0">
<Grid Background="{TemplateBinding Background}">
<Rectangle x:Name="RowInvalidVisualElement"
Opacity="0"
Fill="{DynamicResource DataGridRowInvalidBrush}"
Stretch="Fill" />
<Rectangle x:Name="BackgroundRectangle"
Fill="{DynamicResource DataGridRowBackgroundBrush}"
Stretch="Fill" />
</Grid>
</Border>
<Rectangle x:Name="HorizontalSeparator"
Grid.Row="2"
Grid.ColumnSpan="2"
Height="1"
Margin="1,0,1,0"
HorizontalAlignment="Stretch"
Fill="{TemplateBinding SeparatorBrush}"
IsVisible="{TemplateBinding AreSeparatorsVisible}" />
<ContentPresenter Grid.RowSpan="2"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter>
</ControlTheme>
<ControlTheme x:Key="{x:Type DataGridRow}" TargetType="DataGridRow">
<Setter Property="Focusable" Value="False" />
<Setter Property="Background" Value="{Binding $parent[DataGrid].RowBackground}" />
<Setter Property="Template">
<ControlTemplate>
<Border x:Name="RowBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<DataGridFrozenGrid Name="PART_Root"
ColumnDefinitions="Auto,*"
RowDefinitions="*,Auto,Auto">
<Rectangle Name="BackgroundRectangle"
Fill="{DynamicResource DataGridRowBackgroundBrush}"
Grid.RowSpan="2"
Grid.ColumnSpan="2" />
<Rectangle x:Name="InvalidVisualElement"
Opacity="0"
Grid.ColumnSpan="2"
Fill="{DynamicResource DataGridRowInvalidBrush}" />
<DataGridRowHeader Name="PART_RowHeader"
Grid.RowSpan="3"
DataGridFrozenGrid.IsFrozen="True" />
<DataGridCellsPresenter Name="PART_CellsPresenter"
Grid.Column="1"
DataGridFrozenGrid.IsFrozen="True" />
<DataGridDetailsPresenter Name="PART_DetailsPresenter"
Grid.Row="1"
Grid.Column="1"
Background="{DynamicResource DataGridDetailsPresenterBackgroundBrush}" />
<Rectangle Name="PART_BottomGridLine"
Grid.Row="2"
Grid.Column="1"
Height="1"
HorizontalAlignment="Stretch" />
</DataGridFrozenGrid>
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:invalid">
<Style Selector="^ /template/ Rectangle#InvalidVisualElement">
<Setter Property="Opacity" Value="0.4" />
</Style>
<Style Selector="^ /template/ Rectangle#BackgroundRectangle">
<Setter Property="Opacity" Value="0" />
</Style>
</Style>
<Style Selector="^:pointerover /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowHoveredBackgroundColor}" />
</Style>
<Style Selector="^:selected">
<Style Selector="^ /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="^:pointerover /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="^:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedBackgroundOpacity}" />
</Style>
<Style Selector="^:pointerover:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundOpacity}" />
</Style>
</Style>
</ControlTheme>
<ControlTheme x:Key="FluentDataGridRowGroupExpanderButtonTheme" TargetType="ToggleButton">
<Setter Property="Template">
<ControlTemplate>
<Border Width="12"
Height="12"
Background="Transparent"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Path Fill="{TemplateBinding Foreground}"
Data="{StaticResource DataGridRowGroupHeaderIconClosedPath}"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Stretch="Uniform" />
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:checked /template/ Path">
<Setter Property="Data" Value="{StaticResource DataGridRowGroupHeaderIconOpenedPath}" />
</Style>
</ControlTheme>
<ControlTheme x:Key="{x:Type DataGridRowGroupHeader}" TargetType="DataGridRowGroupHeader">
<Setter Property="Focusable" Value="False" />
<Setter Property="Foreground" Value="{DynamicResource DataGridRowGroupHeaderForegroundBrush}" />
<Setter Property="Background" Value="{DynamicResource DataGridRowGroupHeaderBackgroundBrush}" />
<Setter Property="FontSize" Value="15" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="Template">
<ControlTemplate x:DataType="collections:DataGridCollectionViewGroup">
<DataGridFrozenGrid Name="PART_Root"
Background="{TemplateBinding Background}"
MinHeight="{TemplateBinding MinHeight}"
ColumnDefinitions="Auto,Auto,Auto,Auto,*"
RowDefinitions="*,Auto">
<Rectangle Name="PART_IndentSpacer"
Grid.Column="1" />
<ToggleButton Name="PART_ExpanderButton"
Grid.Column="2"
Width="12"
Height="12"
Margin="12,0,0,0"
Theme="{StaticResource FluentDataGridRowGroupExpanderButtonTheme}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="{TemplateBinding CornerRadius}"
Focusable="False"
Foreground="{TemplateBinding Foreground}" />
<StackPanel Grid.Column="3"
Orientation="Horizontal"
VerticalAlignment="Center"
Margin="12,0,0,0">
<TextBlock Name="PART_PropertyNameElement"
Margin="4,0,0,0"
IsVisible="{TemplateBinding IsPropertyNameVisible}"
Foreground="{TemplateBinding Foreground}" />
<TextBlock Margin="4,0,0,0"
Text="{Binding Key}"
Foreground="{TemplateBinding Foreground}" />
<TextBlock Name="PART_ItemCountElement"
Margin="4,0,0,0"
IsVisible="{TemplateBinding IsItemCountVisible}"
Foreground="{TemplateBinding Foreground}" />
</StackPanel>
<Rectangle x:Name="CurrencyVisual"
Grid.ColumnSpan="5"
IsVisible="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCurrencyVisualPrimaryBrush}"
StrokeThickness="1" />
<Grid x:Name="FocusVisual"
Grid.ColumnSpan="5"
IsVisible="False"
IsHitTestVisible="False">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualPrimaryBrush}"
StrokeThickness="2" />
<Rectangle Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualSecondaryBrush}"
StrokeThickness="1" />
</Grid>
<DataGridRowHeader Name="PART_RowHeader"
Grid.RowSpan="2"
DataGridFrozenGrid.IsFrozen="True" />
<Rectangle x:Name="PART_BottomGridLine"
Grid.Row="1"
Grid.ColumnSpan="5"
Height="1" />
</DataGridFrozenGrid>
</ControlTemplate>
</Setter>
</ControlTheme>
<ControlTheme x:Key="{x:Type DataGrid}" TargetType="DataGrid">
<Setter Property="RowBackground" Value="Transparent" />
<Setter Property="HeadersVisibility" Value="Column" />
<Setter Property="HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="SelectionMode" Value="Extended" />
<Setter Property="GridLinesVisibility" Value="None" />
<Setter Property="HorizontalGridLinesBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
<Setter Property="VerticalGridLinesBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
<Setter Property="DropLocationIndicatorTemplate">
<Template>
<Rectangle Fill="{DynamicResource DataGridDropLocationIndicatorBackground}"
Width="2" />
</Template>
</Setter>
<Setter Property="Template">
<ControlTemplate>
<Border x:Name="DataGridBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid ColumnDefinitions="Auto,*,Auto"
RowDefinitions="Auto,*,Auto,Auto"
ClipToBounds="True">
<DataGridColumnHeader Name="PART_TopLeftCornerHeader"
Theme="{StaticResource DataGridTopLeftColumnHeader}" />
<DataGridColumnHeadersPresenter Name="PART_ColumnHeadersPresenter"
Grid.Column="1"
Grid.Row="0" Grid.ColumnSpan="2" />
<Rectangle Name="PART_ColumnHeadersAndRowsSeparator"
Grid.Row="0" Grid.ColumnSpan="3" Grid.Column="0"
VerticalAlignment="Bottom"
Height="1"
Fill="{DynamicResource DataGridGridLinesBrush}" />
<DataGridRowsPresenter Name="PART_RowsPresenter"
Grid.Row="1"
Grid.RowSpan="2"
Grid.ColumnSpan="3" Grid.Column="0">
<DataGridRowsPresenter.GestureRecognizers>
<ScrollGestureRecognizer CanHorizontallyScroll="True" CanVerticallyScroll="True" />
</DataGridRowsPresenter.GestureRecognizers>
</DataGridRowsPresenter>
<Rectangle Name="PART_BottomRightCorner"
Fill="{DynamicResource DataGridScrollBarsSeparatorBackground}"
Grid.Column="2"
Grid.Row="2" />
<ScrollBar Name="PART_VerticalScrollbar"
Orientation="Vertical"
Grid.Column="2"
Grid.Row="1"
Width="{DynamicResource ScrollBarSize}" />
<Grid Grid.Column="1"
Grid.Row="2"
ColumnDefinitions="Auto,*">
<Rectangle Name="PART_FrozenColumnScrollBarSpacer" />
<ScrollBar Name="PART_HorizontalScrollbar"
Grid.Column="1"
Orientation="Horizontal"
Height="{DynamicResource ScrollBarSize}" />
</Grid>
<Border x:Name="PART_DisabledVisualElement"
Grid.ColumnSpan="3" Grid.Column="0"
Grid.Row="0" Grid.RowSpan="4"
IsHitTestVisible="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
CornerRadius="2"
Background="{DynamicResource DataGridDisabledVisualElementBackground}"
IsVisible="{Binding !$parent[DataGrid].IsEnabled}" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:empty-columns">
<Style Selector="^ /template/ DataGridColumnHeader#PART_TopLeftCornerHeader">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^ /template/ DataGridColumnHeadersPresenter#PART_ColumnHeadersPresenter">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^ /template/ Rectangle#PART_ColumnHeadersAndRowsSeparator">
<Setter Property="IsVisible" Value="False" />
</Style>
</Style>
</ControlTheme>
</ResourceDictionary>
</Styles.Resources>
</Styles>

View File

@ -1,658 +0,0 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles.Resources>
<x:Double x:Key="ListAccentLowOpacity">0.6</x:Double>
<x:Double x:Key="ListAccentMediumOpacity">0.8</x:Double>
<Thickness x:Key="DataGridTextColumnCellTextBlockMargin">12,0,12,0</Thickness>
<StreamGeometry x:Key="DataGridSortIconDescendingPath">M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z</StreamGeometry>
<StreamGeometry x:Key="DataGridRowGroupHeaderIconClosedPath">M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z</StreamGeometry>
<StreamGeometry x:Key="DataGridRowGroupHeaderIconOpenedPath">M1939 1581l90 -90l-1005 -1005l-1005 1005l90 90l915 -915z</StreamGeometry>
<SolidColorBrush x:Key="DataGridColumnHeaderForegroundBrush" Color="{DynamicResource SystemBaseMediumColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderBackgroundBrush" Color="{DynamicResource SystemAltHighColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderHoveredBackgroundBrush" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderPressedBackgroundBrush" Color="{DynamicResource SystemListMediumColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderDraggedBackgroundBrush" Color="{DynamicResource SystemChromeMediumLowColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderBackgroundBrush" Color="{DynamicResource SystemChromeMediumColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderPressedBackgroundBrush" Color="{DynamicResource SystemListMediumColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderForegroundBrush" Color="{DynamicResource SystemBaseHighColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderHoveredBackgroundBrush" Color="{DynamicResource SystemListLowColor}" />
<StaticResource x:Key="DataGridRowBackgroundBrush" ResourceKey="SystemControlTransparentBrush" />
<SolidColorBrush x:Key="DataGridRowSelectedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedBackgroundOpacity" ResourceKey="ListAccentLowOpacity" />
<SolidColorBrush x:Key="DataGridRowSelectedHoveredBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedHoveredBackgroundOpacity" ResourceKey="ListAccentMediumOpacity" />
<SolidColorBrush x:Key="DataGridRowSelectedUnfocusedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedUnfocusedBackgroundOpacity" ResourceKey="ListAccentLowOpacity" />
<SolidColorBrush x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundOpacity" ResourceKey="ListAccentMediumOpacity" />
<SolidColorBrush x:Key="DataGridRowHoveredBackgroundColor" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridRowInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" />
<SolidColorBrush x:Key="DataGridRowHeaderForegroundBrush" Color="{DynamicResource SystemBaseMediumColor}" />
<SolidColorBrush x:Key="DataGridRowHeaderBackgroundBrush" Color="{DynamicResource SystemAltHighColor}" />
<StaticResource x:Key="DataGridCellBackgroundBrush" ResourceKey="SystemControlTransparentBrush" />
<SolidColorBrush x:Key="DataGridCellFocusVisualPrimaryBrush" Color="{DynamicResource SystemBaseHighColor}" />
<SolidColorBrush x:Key="DataGridCellFocusVisualSecondaryBrush" Color="{DynamicResource SystemAltMediumColor}" />
<SolidColorBrush x:Key="DataGridCellInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" />
<SolidColorBrush x:Key="DataGridGridLinesBrush"
Opacity="0.4"
Color="{DynamicResource SystemBaseMediumLowColor}" />
<StaticResource x:Key="DataGridCurrencyVisualPrimaryBrush" ResourceKey="SystemControlTransparentBrush" />
<SolidColorBrush x:Key="DataGridDetailsPresenterBackgroundBrush" Color="{DynamicResource SystemChromeMediumLowColor}" />
<StaticResource x:Key="DataGridFillerColumnGridLinesBrush" ResourceKey="SystemControlTransparentBrush" />
</Styles.Resources>
<Style Selector="DataGridCell">
<Setter Property="Background" Value="{DynamicResource DataGridCellBackgroundBrush}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="FontSize" Value="12" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="Focusable" Value="False" />
<Setter Property="Template">
<ControlTemplate>
<Border x:Name="CellBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid x:Name="PART_CellRoot" ColumnDefinitions="*,Auto">
<Rectangle x:Name="CurrencyVisual"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCurrencyVisualPrimaryBrush}"
StrokeThickness="1" />
<Grid x:Name="FocusVisual" IsHitTestVisible="False">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualPrimaryBrush}"
StrokeThickness="2" />
<Rectangle Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualSecondaryBrush}"
StrokeThickness="1" />
</Grid>
<ContentPresenter Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"/>
<Rectangle x:Name="InvalidVisualElement"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellInvalidBrush}"
StrokeThickness="1" />
<Rectangle Name="PART_RightGridLine"
Grid.Column="1"
Width="1"
VerticalAlignment="Stretch"
Fill="{DynamicResource DataGridFillerColumnGridLinesBrush}" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DataGridCell > TextBlock#CellTextBlock">
<Setter Property="Margin" Value="{DynamicResource DataGridTextColumnCellTextBlockMargin}" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style Selector="DataGridCell /template/ Rectangle#CurrencyVisual">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGridCell /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGridCell:current /template/ Rectangle#CurrencyVisual">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="DataGrid:focus DataGridCell:current /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="DataGridCell /template/ Rectangle#InvalidVisualElement">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGridCell:invalid /template/ Rectangle#InvalidVisualElement">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="DataGridCell > TextBox DataValidationErrors">
<Setter Property="Template" Value="{DynamicResource TooltipDataValidationContentTemplate}" />
<Setter Property="ErrorTemplate" Value="{DynamicResource TooltipDataValidationErrorTemplate}" />
</Style>
<Style Selector="DataGridColumnHeader">
<Setter Property="Foreground" Value="{DynamicResource DataGridColumnHeaderForegroundBrush}" />
<Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderBackgroundBrush}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Focusable" Value="False" />
<Setter Property="SeparatorBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
<Setter Property="Padding" Value="6,0,0,0" />
<Setter Property="FontSize" Value="12" />
<Setter Property="MinHeight" Value="40" />
<Setter Property="Template">
<ControlTemplate>
<Border x:Name="HeaderBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid Name="PART_ColumnHeaderRoot" ColumnDefinitions="*,Auto">
<Grid Grid.Column="0" Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" ColumnDefinitions="*,12">
<ContentPresenter Grid.Column="0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
<Path Name="SortIcon"
Grid.Column="1"
Height="12"
Width="8"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Fill="{TemplateBinding Foreground}"
Stretch="Uniform"
Margin="0,0,4,0"
Data="F1 M -5.215,6.099L 5.215,6.099L 0,0L -5.215,6.099 Z "/>
</Grid>
<Rectangle Name="VerticalSeparator"
Grid.Column="1"
Width="1"
VerticalAlignment="Stretch"
Fill="{TemplateBinding SeparatorBrush}"
IsVisible="{TemplateBinding AreSeparatorsVisible}" />
<Grid x:Name="FocusVisual" IsHitTestVisible="False">
<Rectangle x:Name="FocusVisualPrimary"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualPrimaryBrush}"
StrokeThickness="2" />
<Rectangle x:Name="FocusVisualSecondary"
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualSecondaryBrush}"
StrokeThickness="1" />
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DataGridColumnHeader /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGridColumnHeader:focus-visible /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="DataGridColumnHeader:pointerover /template/ Grid#PART_ColumnHeaderRoot">
<Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderHoveredBackgroundBrush}" />
</Style>
<Style Selector="DataGridColumnHeader:pressed /template/ Grid#PART_ColumnHeaderRoot">
<Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderPressedBackgroundBrush}" />
</Style>
<Style Selector="DataGridColumnHeader:dragIndicator">
<Setter Property="Opacity" Value="0.5" />
</Style>
<Style Selector="DataGridColumnHeader /template/ Path#SortIcon">
<Setter Property="IsVisible" Value="False" />
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform ScaleX="0.9" ScaleY="0.9" />
</Setter.Value>
</Setter>
</Style>
<Style Selector="DataGridColumnHeader:sortascending /template/ Path#SortIcon">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="DataGridColumnHeader:sortdescending /template/ Path#SortIcon">
<Setter Property="IsVisible" Value="True" />
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform ScaleX="0.9" ScaleY="-0.9" />
</Setter.Value>
</Setter>
</Style>
<Style Selector="DataGridRow">
<Setter Property="Focusable" Value="False" />
<Setter Property="Template">
<ControlTemplate>
<Border x:Name="RowBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<DataGridFrozenGrid Name="PART_Root"
ColumnDefinitions="Auto,*"
RowDefinitions="*,Auto,Auto">
<Rectangle Name="BackgroundRectangle"
Grid.RowSpan="2"
Grid.ColumnSpan="2" />
<Rectangle x:Name="InvalidVisualElement"
Grid.ColumnSpan="2"
Fill="{DynamicResource DataGridRowInvalidBrush}" />
<DataGridRowHeader Name="PART_RowHeader"
Grid.RowSpan="3"
DataGridFrozenGrid.IsFrozen="True" />
<DataGridCellsPresenter Name="PART_CellsPresenter"
Grid.Column="1"
DataGridFrozenGrid.IsFrozen="True" />
<DataGridDetailsPresenter Name="PART_DetailsPresenter"
Grid.Row="1"
Grid.Column="1"
Background="{DynamicResource DataGridDetailsPresenterBackgroundBrush}" />
<Rectangle Name="PART_BottomGridLine"
Grid.Row="2"
Grid.Column="1"
Height="1"
HorizontalAlignment="Stretch" />
</DataGridFrozenGrid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DataGridRow">
<Setter Property="Background" Value="{Binding $parent[DataGrid].RowBackground}" />
</Style>
<Style Selector="DataGridRow:nth-child(even)">
<Setter Property="Background" Value="{Binding $parent[DataGrid].AlternatingRowBackground}" />
</Style>
<Style Selector="DataGridRow /template/ Rectangle#InvalidVisualElement">
<Setter Property="Opacity" Value="0" />
</Style>
<Style Selector="DataGridRow:invalid /template/ Rectangle#InvalidVisualElement">
<Setter Property="Opacity" Value="0.4" />
</Style>
<Style Selector="DataGridRow:invalid /template/ Rectangle#BackgroundRectangle">
<Setter Property="Opacity" Value="0" />
</Style>
<Style Selector="DataGridRow /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowBackgroundBrush}" />
</Style>
<Style Selector="DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowHoveredBackgroundColor}" />
</Style>
<Style Selector="DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:selected:pointerover /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:selected:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:selected:pointerover:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRowHeader">
<Setter Property="Foreground" Value="{DynamicResource DataGridRowHeaderForegroundBrush}" />
<Setter Property="Background" Value="{DynamicResource DataGridRowHeaderBackgroundBrush}" />
<Setter Property="Focusable" Value="False" />
<Setter Property="SeparatorBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
<Setter Property="AreSeparatorsVisible" Value="False" />
<Setter Property="Template">
<ControlTemplate>
<Grid x:Name="PART_Root"
RowDefinitions="*,*,Auto"
ColumnDefinitions="Auto,*">
<Border Grid.RowSpan="3"
Grid.ColumnSpan="2"
BorderBrush="{TemplateBinding SeparatorBrush}"
BorderThickness="0,0,1,0">
<Grid Background="{TemplateBinding Background}">
<Rectangle x:Name="RowInvalidVisualElement"
Fill="{DynamicResource DataGridRowInvalidBrush}"
Stretch="Fill" />
<Rectangle x:Name="BackgroundRectangle"
Stretch="Fill" />
</Grid>
</Border>
<Rectangle x:Name="HorizontalSeparator"
Grid.Row="2"
Grid.ColumnSpan="2"
Height="1"
Margin="1,0,1,0"
HorizontalAlignment="Stretch"
Fill="{TemplateBinding SeparatorBrush}"
IsVisible="{TemplateBinding AreSeparatorsVisible}" />
<ContentPresenter Grid.RowSpan="2"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DataGridRowHeader /template/ Rectangle#RowInvalidVisualElement">
<Setter Property="Opacity" Value="0" />
</Style>
<Style Selector="DataGridRowHeader:invalid /template/ Rectangle#RowInvalidVisualElement">
<Setter Property="Opacity" Value="0.4" />
</Style>
<Style Selector="DataGridRowHeader:invalid /template/ Rectangle#BackgroundRectangle">
<Setter Property="Opacity" Value="0" />
</Style>
<Style Selector="DataGridRowHeader /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowBackgroundBrush}" />
</Style>
<Style Selector="DataGridRow:pointerover DataGridRowHeader /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowHoveredBackgroundColor}" />
</Style>
<Style Selector="DataGridRowHeader:selected /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:pointerover DataGridRowHeader:selected /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRowHeader:selected:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:pointerover DataGridRowHeader:selected:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRowGroupHeader">
<Setter Property="Focusable" Value="False" />
<Setter Property="Foreground" Value="{DynamicResource DataGridRowGroupHeaderForegroundBrush}" />
<Setter Property="Background" Value="{DynamicResource DataGridRowGroupHeaderBackgroundBrush}" />
<Setter Property="FontSize" Value="15" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="Template">
<ControlTemplate>
<DataGridFrozenGrid Name="PART_Root"
MinHeight="{TemplateBinding MinHeight}"
ColumnDefinitions="Auto,Auto,Auto,Auto,*"
RowDefinitions="*,Auto">
<Rectangle Name="IndentSpacer"
Grid.Column="1" />
<ToggleButton Name="ExpanderButton"
Grid.Column="2"
Width="12"
Height="12"
Margin="12,0,0,0"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="{TemplateBinding CornerRadius}"
Focusable="False"
Foreground="{TemplateBinding Foreground}" />
<StackPanel Grid.Column="3"
Orientation="Horizontal"
VerticalAlignment="Center"
Margin="12,0,0,0">
<TextBlock Name="PropertyNameElement"
Margin="4,0,0,0"
IsVisible="{TemplateBinding IsPropertyNameVisible}"
Foreground="{TemplateBinding Foreground}" />
<TextBlock Margin="4,0,0,0"
Text="{Binding Key}"
Foreground="{TemplateBinding Foreground}" />
<TextBlock Name="ItemCountElement"
Margin="4,0,0,0"
IsVisible="{TemplateBinding IsItemCountVisible}"
Foreground="{TemplateBinding Foreground}" />
</StackPanel>
<Rectangle x:Name="CurrencyVisual"
Grid.ColumnSpan="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCurrencyVisualPrimaryBrush}"
StrokeThickness="1" />
<Grid x:Name="FocusVisual"
Grid.ColumnSpan="5"
IsHitTestVisible="False">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualPrimaryBrush}"
StrokeThickness="2" />
<Rectangle Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualSecondaryBrush}"
StrokeThickness="1" />
</Grid>
<DataGridRowHeader Name="PART_RowHeader"
Grid.RowSpan="2"
DataGridFrozenGrid.IsFrozen="True" />
<Rectangle x:Name="PART_BottomGridLine"
Grid.Row="1"
Grid.ColumnSpan="5"
Height="1" />
</DataGridFrozenGrid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DataGridRowGroupHeader /template/ ToggleButton#ExpanderButton">
<Setter Property="Template">
<ControlTemplate>
<Border Grid.Column="0"
Width="12"
Height="12"
Background="Transparent"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Path Fill="{TemplateBinding Foreground}"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Stretch="Uniform" />
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DataGridRowGroupHeader /template/ ToggleButton#ExpanderButton /template/ Path">
<Setter Property="Data" Value="{StaticResource DataGridRowGroupHeaderIconOpenedPath}" />
<Setter Property="Stretch" Value="Uniform" />
</Style>
<Style Selector="DataGridRowGroupHeader /template/ ToggleButton#ExpanderButton:checked /template/ Path">
<Setter Property="Data" Value="{StaticResource DataGridRowGroupHeaderIconClosedPath}" />
<Setter Property="Stretch" Value="UniformToFill" />
</Style>
<Style Selector="DataGridRowGroupHeader /template/ DataGridFrozenGrid#PART_Root">
<Setter Property="Background" Value="{Binding $parent[DataGridRowGroupHeader].Background}" />
</Style>
<Style Selector="DataGridRowGroupHeader:pointerover /template/ DataGridFrozenGrid#PART_Root">
<Setter Property="Background" Value="{DynamicResource DataGridRowGroupHeaderHoveredBackgroundBrush}" />
</Style>
<Style Selector="DataGridRowGroupHeader:pressed /template/ DataGridFrozenGrid#PART_Root">
<Setter Property="Background" Value="{DynamicResource DataGridRowGroupHeaderPressedBackgroundBrush}" />
</Style>
<Style Selector="DataGridRowGroupHeader /template/ Rectangle#CurrencyVisual">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGridRowGroupHeader /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGridRowGroupHeader:current /template/ Rectangle#CurrencyVisual">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="DataGrid:focus DataGridRowGroupHeader:current /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="DataGrid">
<Setter Property="RowBackground" Value="Transparent" />
<Setter Property="AlternatingRowBackground" Value="Transparent" />
<Setter Property="HeadersVisibility" Value="Column" />
<Setter Property="HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="SelectionMode" Value="Extended" />
<Setter Property="GridLinesVisibility" Value="None" />
<Setter Property="HorizontalGridLinesBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
<Setter Property="VerticalGridLinesBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
<Setter Property="DropLocationIndicatorTemplate">
<Template>
<Rectangle Fill="{DynamicResource DataGridDropLocationIndicatorBackground}"
Width="2" />
</Template>
</Setter>
<Setter Property="Template">
<ControlTemplate>
<Border x:Name="DataGridBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid ColumnDefinitions="Auto,*,Auto" RowDefinitions="Auto,*,Auto,Auto">
<Grid.Resources>
<ControlTemplate x:Key="TopLeftHeaderTemplate"
TargetType="DataGridColumnHeader">
<Grid x:Name="TopLeftHeaderRoot"
RowDefinitions="*,*,Auto">
<Border Grid.RowSpan="2"
BorderThickness="0,0,1,0"
BorderBrush="{DynamicResource DataGridGridLinesBrush}" />
<Rectangle Grid.RowSpan="2"
VerticalAlignment="Bottom"
StrokeThickness="1"
Height="1"
Fill="{DynamicResource DataGridGridLinesBrush}" />
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="TopRightHeaderTemplate"
TargetType="DataGridColumnHeader">
<Grid x:Name="RootElement" />
</ControlTemplate>
</Grid.Resources>
<DataGridColumnHeader Name="PART_TopLeftCornerHeader"
Template="{StaticResource TopLeftHeaderTemplate}" />
<DataGridColumnHeadersPresenter Name="PART_ColumnHeadersPresenter"
Grid.Column="1"
Grid.ColumnSpan="2" />
<!--<DataGridColumnHeader Name="PART_TopRightCornerHeader"
Grid.Column="2"
Template="{StaticResource TopRightHeaderTemplate}" />-->
<Rectangle Name="PART_ColumnHeadersAndRowsSeparator"
Grid.ColumnSpan="3"
VerticalAlignment="Bottom"
Height="1"
Fill="{DynamicResource DataGridGridLinesBrush}" />
<DataGridRowsPresenter Name="PART_RowsPresenter"
Grid.Row="1"
Grid.RowSpan="2"
Grid.ColumnSpan="3">
<DataGridRowsPresenter.GestureRecognizers>
<ScrollGestureRecognizer CanHorizontallyScroll="True" CanVerticallyScroll="True" />
</DataGridRowsPresenter.GestureRecognizers>
</DataGridRowsPresenter>
<Rectangle Name="PART_BottomRightCorner"
Fill="{DynamicResource DataGridScrollBarsSeparatorBackground}"
Grid.Column="2"
Grid.Row="2" />
<!--<Rectangle Name="BottomLeftCorner"
Fill="{DynamicResource DataGridScrollBarsSeparatorBackground}"
Grid.Row="2"
Grid.ColumnSpan="2" />-->
<ScrollBar Name="PART_VerticalScrollbar"
Orientation="Vertical"
Grid.Column="2"
Grid.Row="1"
Width="{DynamicResource ScrollBarSize}" />
<Grid Grid.Column="1"
Grid.Row="2"
ColumnDefinitions="Auto,*">
<Rectangle Name="PART_FrozenColumnScrollBarSpacer" />
<ScrollBar Name="PART_HorizontalScrollbar"
Grid.Column="1"
Orientation="Horizontal"
Height="{DynamicResource ScrollBarSize}" />
</Grid>
<Border x:Name="PART_DisabledVisualElement"
Grid.ColumnSpan="3"
Grid.RowSpan="4"
IsHitTestVisible="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
CornerRadius="2"
Background="{DynamicResource DataGridDisabledVisualElementBackground}"
IsVisible="{Binding !$parent[DataGrid].IsEnabled}" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DataGrid:empty-columns /template/ DataGridColumnHeader#PART_TopLeftCornerHeader">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGrid:empty-columns /template/ DataGridColumnHeadersPresenter#PART_ColumnHeadersPresenter">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGrid:empty-columns /template/ Rectangle#PART_ColumnHeadersAndRowsSeparator">
<Setter Property="IsVisible" Value="False" />
</Style>
</Styles>

View File

@ -1,18 +0,0 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles.Resources>
<Color x:Key="SeriesEntryGridBackgroundColor">#cdffcd</Color>
<SolidColorBrush x:Key="SeriesEntryGridBackgroundBrush" Opacity="0.5" Color="{StaticResource SeriesEntryGridBackgroundColor}" />
<SolidColorBrush x:Key="ProcessQueueBookFailedBrush" Color="LightCoral" />
<SolidColorBrush x:Key="ProcessQueueBookCompletedBrush" Color="PaleGreen" />
<SolidColorBrush x:Key="ProcessQueueBookCancelledBrush" Color="Khaki" />
<SolidColorBrush x:Key="ProcessQueueBookDefaultBrush" Color="{StaticResource SystemAltHighColor}" />
<SolidColorBrush x:Key="ProcessQueueBookBorderBrush" Color="Gray" />
<SolidColorBrush x:Key="DisabledGrayBrush" Color="#60D3D3D3" />
</Styles.Resources>
<Style Selector="TextBox[IsReadOnly=true]">
<Setter Property="Background" Value="LightGray" />
<Setter Property="CaretBrush" Value="#00000000" />
</Style>
</Styles>

View File

@ -0,0 +1,69 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:collections="using:Avalonia.Collections">
<Styles.Resources>
<ResourceDictionary>
<StreamGeometry x:Key="LeftArrows">M30,0 H60 L30,50 L60,100 H30 L0,50 M85,0 H115 L85,50 L115,100 H85 L55,50 M140,0 H170 L140,50 L170,100 H140 L110,50</StreamGeometry>
<StreamGeometry x:Key="FirstButtonIcon">M0,0 H100 V12 H53 L100,46 H0 L47,12 H0</StreamGeometry>
<StreamGeometry x:Key="UpButtonIcon">M0,36.66 L50,0 L100,36.66</StreamGeometry>
<StreamGeometry x:Key="DownButtonIcon">M0,0 L100,0 L50,36.66</StreamGeometry>
<StreamGeometry x:Key="LastButtonIcon">M0,0 H100 L53,34 H100 V46 H0 V34 H47</StreamGeometry>
<StreamGeometry x:Key="CancelButtonIcon">M30,0 H50 V30 H80 V50 H50 V80 H30 V50 H0 V30 H30</StreamGeometry>
<StreamGeometry x:Key="QueuedIcon">M31,0 H49 V100 H31 M58,0 H76 V100 H58 M85,0 H103 V100 H85 M8,85 V122 H129 V85 H117 V109 H20 V85 H8 M0,36 V66 L24,51 M114,36 V66 L138,51</StreamGeometry>
<StreamGeometry x:Key="QueueCompletedIcon">M0,0 H100 V100 H0 V0 M2,50 L36,82 L 93,27 L81,15 L36,59 L14,38</StreamGeometry>
<StreamGeometry x:Key="QueueErrorIcon">M0,0 H100 V100 H0 V0 M15,71 L29,85 L50,64 L71,85 L85,71 L64,50 L85,29 L71,15 L50,36 L29,15 L15,29 L36,50</StreamGeometry>
<StreamGeometry x:Key="BookErrorIcon">M32,0 a 32,32 0 0 1 0,64 a 32,32 0 0 1 0,-64 m 0,4 a 28,28 0 0 1 0,56 a 28,28 0 0 1 0,-56 m-21,24 h42 a 1,1 0 0 1 1,1 v6 a 1,1 0 0 1 -1,1 h-42 a 1,1 0 0 1 -1,-1 v-6 a 1,1 0 0 1 1,-1</StreamGeometry>
<RotateTransform x:Key="Rotate45Transform" Angle="45" />
<StreamGeometry x:Key="EditTagsIcon">
M39,35 L50,24 H11
A 11,11 0 0 0 0,35 V89 A 11,11 0 0 0 11,100 H64 A 11,11 0 0 0 75,89 V52 L64,63 V89 H11 V35
M 51,65 H36 V50
M 90.5,26.5 L55,62 L 39,45 L74,10
M 78,6 L81.5,2.5 A 8,8 0 0 1 91.5,2 L98.5,9 A 8,8 0 0 1 97.5,19.5 L94,23
</StreamGeometry>
<StreamGeometry x:Key="CollapseIcon">
M0,2 A 2,2 0 0 1 2,0 H62 A2,2 0 0 1 64,2 V62 A 2,2 0 0 1 62,64 H 2 A 2,2 0 0 1 0,62 V2
M 2,2 H62 V62 H2 V2
M11,28 h42 a 1,1 0 0 1 1,1 v6 a 1,1 0 0 1 -1,1 h-42 a 1,1 0 0 1 -1,-1 v-6 a 1,1 0 0 1 1,-1
</StreamGeometry>
<StreamGeometry x:Key="VerticalBarIcon">M28,53 v-42 a 1,1 0 0 1 1,-1 h6 a 1,1 0 0 1 1,1 v42 a 1,1 0 0 1 -1,1 h-6 a 1,1 0 0 1 -1,-1</StreamGeometry>
<CombinedGeometry x:Key="ExpandIcon" Geometry1="{StaticResource CollapseIcon}" Geometry2="{StaticResource VerticalBarIcon}" />
<StreamGeometry x:Key="StoplightBodyIcon">
M0,12 A 12,12 0 0 1 12,0 H34 A 12,12 0 0 1 46,12 V88 A 12,12 0 0 1 34,100 H12 A 12,12 0 0 1 0,88 V12
M20,8 H26 A 12,12 0 0 1 26,32 H20 A 12,12 0 0 1 20,8
M20,38 H26 A 12,12 0 0 1 26,62 H20 A 12,12 0 0 1 20,38
M20,68 H26 A 12,12 0 0 1 26,92 H20 A 12,12 0 0 1 20,68
</StreamGeometry>
<StreamGeometry x:Key="PdfDownloadedIcon">
M4,38.5 H3 A 3,3 0 0 1 0,35.5 V21.4 A 3,3 0 0 1 3,18.4 H4 V2 A 2,2 0 0 1 6,0 H30.5 L41,12 V18.4 A 3,3 0 0 1 45,21.4 V35.5 A 3,3 0 0 1 42,38.5 H41 V48.5 A 2,2 0 0 1 39,50.5 H6 A 2,2 0 0 1 4,48.5
M6,38.5 H39 V48.5 H6 V38.5
M6,18.4 V2 H29 V12 A 1,1 0 0 0 30,13 H39 V18.4
M 4.3179,36 c 0,0 0.122,-14.969 0.122,-14.969 1.469,-0.194 2.939,-0.388 4.5,-0.362 1.561,0.026 3.214,0.27 4.357,0.944 1.143,0.674 1.775,1.776 2.015,2.959 0.24,1.184 0.087,2.449 -0.5,3.52 -0.587,1.071 -1.607,1.949 -2.816,2.352 -1.209,0.403 -2.607,0.332 -4.005,0.26 0,0 -0.031,5.265 -0.031,5.265 0,0 -3.673,0.122 -3.673,0.122 0,0 0.031,-0.092 0.031,-0.092
m 3.643,-12.428 c 0,0 0.031,4.286 0.031,4.286 0.735,0.051 1.47,0.102 2.107,-0.056 0.638,-0.158 1.178,-0.526 1.459,-1.122 0.281,-0.597 0.301,-1.423 0.01,-2.005 -0.291,-0.582 -0.893,-0.918 -1.546,-1.061 -0.653,-0.143 -1.357,-0.092 -1.709,-0.066 -0.352,0.026 -0.352,0.026 -0.352,0.026
m 9.428,12.428 c 2.265,0.245 4.531,0.49 6.674,0.066 2.143,-0.424 4.163,-1.515 5.285,-3.081 1.122,-1.566 1.347,-3.607 1.27,-5.306 -0.076,-1.699 -0.454,-3.056 -1.454,-4.219 -1,-1.163 -2.622,-2.133 -4.704,-2.505 -2.082,-0.373 -4.623,-0.148 -7.164,0.076 0,0 0.092,14.969 0.092,14.969
m 3.49,-12.398 c 0,0 0,9.673 0,9.673 0.888,0.02 1.776,0.041 2.653,-0.179 0.877,-0.219 1.745,-0.679 2.367,-1.541 0.622,-0.862 1,-2.127 0.98,-3.403 -0.02,-1.275 -0.439,-2.561 -1.193,-3.337 -0.755,-0.776 -1.847,-1.041 -2.704,-1.158 -0.857,-0.117 -1.48,-0.087 -2.102,-0.056
m 11.908,12.245 v-14.785 h8.969 v2.51 h-5.786 v3.612 h5.388 v2.51 h-5.449 v6.092
</StreamGeometry>
<StreamGeometry x:Key="PdfDownArrowIcon">
M29,44 V58.7498 H35.0491 A 1.5,1.5 0 0 1 36.1342,61.2861 L23.5607,73.8595 A 1.5,1.5 0 0 1 21.4393,73.8595 L8.8658,61.2861 A 1.5,1.5 0 0 1 9.9509,58.7498 H16 V44 A 1.5,1.5 0 0 1 17.5,42.5 H27.5 A 1.5,1.5 0 0 1 29,44
</StreamGeometry>
<CombinedGeometry x:Key="PdfNotDownloadedIcon" Geometry1="{StaticResource PdfDownloadedIcon}" Geometry2="{StaticResource PdfDownArrowIcon}" />
<StreamGeometry x:Key="ImportIcon">
M5.65,4.3 h-2.75 a2.9,2.25 0 0 0 -2.9,2.25 v7.2
a2.9,2.25 0 0 0 2.9,2.25 h10.2 a2.9,2.25 0 0 0 2.9,-2.25 v-7.2 a2.9,2.25 0 0 0 -2.9,-2.25
h-2.75 v1.6 h2.75 a1.3,0.65 0 0 1 1.3,0.65 v7.2 a1.3,0.65 0 0 1 -1.3,0.65 h-10.2 a1.3,0.65 0 0 1 -1.3,-0.65 v-7.2 a1.3,0.65 0 0 1 1.3,-0.65 h2.75 v-1.6
M7.2,0.8 a 0.8,0.8 0 0 1 1.6,0 v8 l0.9929,-0.9929 a 0.8,0.8 0 0 1 1.1314,1.1314 l-2.3586,2.3586
a 0.8,0.8 0 0 1 -1.1314,0 l-2.3586,-2.3586 a 0.8,0.8 0 0 1 1.1314,-1.1314 l0.9929,0.9929 v8
</StreamGeometry>
</ResourceDictionary>
</Styles.Resources>
</Styles>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 573 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 747 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 813 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 482 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,6 +1,7 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.VisualTree;
using LibationAvalonia.Dialogs; using LibationAvalonia.Dialogs;
using LibationFileManager; using LibationFileManager;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -13,7 +14,7 @@ namespace LibationAvalonia
=> GetBrushFromResources(name, Brushes.Transparent); => GetBrushFromResources(name, Brushes.Transparent);
public static IBrush GetBrushFromResources(string name, IBrush defaultBrush) public static IBrush GetBrushFromResources(string name, IBrush defaultBrush)
{ {
if (App.Current.Styles.TryGetResource(name, out var value) && value is IBrush brush) if (App.Current.TryGetResource(name, App.Current.ActualThemeVariant, out var value) && value is IBrush brush)
return brush; return brush;
return defaultBrush; return defaultBrush;
} }
@ -21,7 +22,7 @@ namespace LibationAvalonia
public static Task<DialogResult> ShowDialogAsync(this DialogWindow dialogWindow, Window owner = null) public static Task<DialogResult> ShowDialogAsync(this DialogWindow dialogWindow, Window owner = null)
=> dialogWindow.ShowDialog<DialogResult>(owner ?? App.MainWindow); => dialogWindow.ShowDialog<DialogResult>(owner ?? App.MainWindow);
public static Window GetParentWindow(this IControl control) => control.VisualRoot as Window; public static Window GetParentWindow(this Control control) => control.GetVisualRoot() as Window;
private static Bitmap defaultImage; private static Bitmap defaultImage;

View File

@ -5,7 +5,7 @@ namespace LibationAvalonia.Controls
{ {
public class DataGridCheckBoxColumnExt : DataGridCheckBoxColumn public class DataGridCheckBoxColumnExt : DataGridCheckBoxColumn
{ {
protected override IControl GenerateEditingElementDirect(DataGridCell cell, object dataItem) protected override Control GenerateEditingElementDirect(DataGridCell cell, object dataItem)
{ {
//Only SeriesEntry types have three-state checks, individual LibraryEntry books are binary. //Only SeriesEntry types have three-state checks, individual LibraryEntry books are binary.
var ele = base.GenerateEditingElementDirect(cell, dataItem) as CheckBox; var ele = base.GenerateEditingElementDirect(cell, dataItem) as CheckBox;

View File

@ -18,7 +18,7 @@ namespace LibationAvalonia.Controls
BindingTarget = MyRatingCellEditor.RatingProperty; BindingTarget = MyRatingCellEditor.RatingProperty;
} }
protected override IControl GenerateElement(DataGridCell cell, object dataItem) protected override Control GenerateElement(DataGridCell cell, object dataItem)
{ {
var myRatingElement = new MyRatingCellEditor var myRatingElement = new MyRatingCellEditor
{ {
@ -41,7 +41,7 @@ namespace LibationAvalonia.Controls
return myRatingElement; return myRatingElement;
} }
protected override IControl GenerateEditingElementDirect(DataGridCell cell, object dataItem) protected override Control GenerateEditingElementDirect(DataGridCell cell, object dataItem)
{ {
var myRatingElement = new MyRatingCellEditor var myRatingElement = new MyRatingCellEditor
{ {
@ -57,12 +57,12 @@ namespace LibationAvalonia.Controls
return myRatingElement; return myRatingElement;
} }
protected override object PrepareCellForEdit(IControl editingElement, RoutedEventArgs editingEventArgs) protected override object PrepareCellForEdit(Control editingElement, RoutedEventArgs editingEventArgs)
=> editingElement is MyRatingCellEditor myRating => editingElement is MyRatingCellEditor myRating
? myRating.Rating ? myRating.Rating
: DefaultRating; : DefaultRating;
protected override void CancelCellEdit(IControl editingElement, object uneditedValue) protected override void CancelCellEdit(Control editingElement, object uneditedValue)
{ {
if (editingElement is MyRatingCellEditor myRating) if (editingElement is MyRatingCellEditor myRating)
{ {

View File

@ -6,7 +6,7 @@ namespace LibationAvalonia.Controls
{ {
public partial class DataGridTemplateColumnExt : DataGridTemplateColumn public partial class DataGridTemplateColumnExt : DataGridTemplateColumn
{ {
protected override IControl GenerateElement(DataGridCell cell, object dataItem) protected override Control GenerateElement(DataGridCell cell, object dataItem)
{ {
cell?.AttachContextMenu(); cell?.AttachContextMenu();
return base.GenerateElement(cell, dataItem); return base.GenerateElement(cell, dataItem);

View File

@ -97,12 +97,7 @@ namespace LibationAvalonia.Controls
var selectedFolders = await (VisualRoot as Window).StorageProvider.OpenFolderPickerAsync(options); var selectedFolders = await (VisualRoot as Window).StorageProvider.OpenFolderPickerAsync(options);
customStates.CustomDir = customStates.CustomDir = selectedFolders.SingleOrDefault()?.Path?.LocalPath ?? customStates.CustomDir;
selectedFolders
.SingleOrDefault()?.
TryGetUri(out var uri) is true
? uri.LocalPath
: customStates.CustomDir;
} }
private void CheckStates_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) private void CheckStates_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
@ -180,10 +175,5 @@ namespace LibationAvalonia.Controls
return path; return path;
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }
} }

View File

@ -90,10 +90,5 @@ namespace LibationAvalonia.Controls
get => GetValue(SubDirectoryProperty); get => GetValue(SubDirectoryProperty);
set => SetValue(SubDirectoryProperty, value); set => SetValue(SubDirectoryProperty, value);
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }
} }

View File

@ -27,7 +27,7 @@
VerticalAlignment="Top"> VerticalAlignment="Top">
<TextBlock <TextBlock
Padding="4,0,4,0" Padding="4,0,4,0"
Background="{StaticResource SystemAltHighColor}" Background="{DynamicResource SystemAltHighColor}"
Text="{TemplateBinding Label}" Text="{TemplateBinding Label}"
/> />
</Grid> </Grid>

View File

@ -29,10 +29,5 @@ namespace LibationAvalonia.Controls
get { return GetValue(LabelProperty); } get { return GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); } set { SetValue(LabelProperty, value); }
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }
} }

View File

@ -6,7 +6,7 @@
x:Class="LibationAvalonia.Controls.LinkLabel"> x:Class="LibationAvalonia.Controls.LinkLabel">
<TextBlock.Styles> <TextBlock.Styles>
<Style Selector="TextBlock"> <Style Selector="TextBlock">
<Setter Property="Foreground" Value="Blue"/> <Setter Property="Foreground" Value="{DynamicResource HyperlinkNew}"/>
<Setter Property="TextDecorations" Value="Underline"/> <Setter Property="TextDecorations" Value="Underline"/>
</Style> </Style>
</TextBlock.Styles> </TextBlock.Styles>

View File

@ -2,6 +2,7 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Styling; using Avalonia.Styling;
using System; using System;
@ -14,7 +15,14 @@ namespace LibationAvalonia.Controls
public LinkLabel() public LinkLabel()
{ {
InitializeComponent(); InitializeComponent();
Tapped += LinkLabel_Tapped;
} }
private void LinkLabel_Tapped(object sender, TappedEventArgs e)
{
Foreground = App.HyperlinkVisited;
}
protected override void OnPointerEntered(PointerEventArgs e) protected override void OnPointerEntered(PointerEventArgs e)
{ {
this.Cursor = HandCursor; this.Cursor = HandCursor;
@ -25,10 +33,5 @@ namespace LibationAvalonia.Controls
this.Cursor = Cursor.Default; this.Cursor = Cursor.Default;
base.OnPointerExited(e); base.OnPointerExited(e);
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }
} }

View File

@ -26,10 +26,5 @@ namespace LibationAvalonia.Controls
base.OnPointerWheelChanged(e); base.OnPointerWheelChanged(e);
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }
} }

View File

@ -33,9 +33,9 @@
<DataTemplate> <DataTemplate>
<Button <Button
Width="60"
Height="30"
Content="X" Content="X"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center" HorizontalContentAlignment="Center"
IsEnabled="{Binding !IsDefault}" IsEnabled="{Binding !IsDefault}"
Click="DeleteButton_Clicked" /> Click="DeleteButton_Clicked" />
@ -49,9 +49,10 @@
<DataTemplate> <DataTemplate>
<Button <Button
Width="60"
Height="30"
Content="Export" Content="Export"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
IsEnabled="{Binding !IsDefault}" IsEnabled="{Binding !IsDefault}"
ToolTip.Tip="Export account authorization to audible-cli" ToolTip.Tip="Export account authorization to audible-cli"
Click="ExportButton_Clicked" /> Click="ExportButton_Clicked" />

View File

@ -129,16 +129,16 @@ namespace LibationAvalonia.Dialogs
string audibleAppDataDir = GetAudibleCliAppDataPath(); string audibleAppDataDir = GetAudibleCliAppDataPath();
if (Directory.Exists(audibleAppDataDir)) if (Directory.Exists(audibleAppDataDir))
openFileDialogOptions.SuggestedStartLocation = new BclStorageFolder(audibleAppDataDir); openFileDialogOptions.SuggestedStartLocation = await StorageProvider.TryGetFolderFromPathAsync(audibleAppDataDir);
var selectedFiles = await StorageProvider.OpenFilePickerAsync(openFileDialogOptions); var selectedFiles = await StorageProvider.OpenFilePickerAsync(openFileDialogOptions);
var selectedFile = selectedFiles.SingleOrDefault(); var selectedFile = selectedFiles.SingleOrDefault()?.TryGetLocalPath();
if (selectedFile?.TryGetUri(out var uri) is not true) return; if (selectedFile is null) return;
try try
{ {
var jsonText = File.ReadAllText(uri.LocalPath); var jsonText = File.ReadAllText(selectedFile);
var mkbAuth = Mkb79Auth.FromJson(jsonText); var mkbAuth = Mkb79Auth.FromJson(jsonText);
var account = await mkbAuth.ToAccountAsync(); var account = await mkbAuth.ToAccountAsync();
@ -159,7 +159,7 @@ namespace LibationAvalonia.Dialogs
{ {
await MessageBox.ShowAdminAlert( await MessageBox.ShowAdminAlert(
this, this,
$"An error occurred while importing an account from:\r\n{uri.LocalPath}\r\n\r\nIs the file encrypted?", $"An error occurred while importing an account from:\r\n{selectedFile}\r\n\r\nIs the file encrypted?",
"Error Importing Account", "Error Importing Account",
ex); ex);
} }
@ -196,12 +196,6 @@ namespace LibationAvalonia.Dialogs
public async void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) public async void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await SaveAndCloseAsync(); => await SaveAndCloseAsync();
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void persist(AccountsSettings accountsSettings) private void persist(AccountsSettings accountsSettings)
{ {
var existingAccounts = accountsSettings.Accounts; var existingAccounts = accountsSettings.Accounts;
@ -293,20 +287,20 @@ namespace LibationAvalonia.Dialogs
string audibleAppDataDir = GetAudibleCliAppDataPath(); string audibleAppDataDir = GetAudibleCliAppDataPath();
if (Directory.Exists(audibleAppDataDir)) if (Directory.Exists(audibleAppDataDir))
options.SuggestedStartLocation = new BclStorageFolder(audibleAppDataDir); options.SuggestedStartLocation = await StorageProvider.TryGetFolderFromPathAsync(audibleAppDataDir);
var selectedFile = await StorageProvider.SaveFilePickerAsync(options); var selectedFile = (await StorageProvider.SaveFilePickerAsync(options))?.TryGetLocalPath();
if (selectedFile?.TryGetUri(out var uri) is not true) return; if (selectedFile is null) return;
try try
{ {
var mkbAuth = Mkb79Auth.FromAccount(account); var mkbAuth = Mkb79Auth.FromAccount(account);
var jsonText = mkbAuth.ToJson(); var jsonText = mkbAuth.ToJson();
File.WriteAllText(uri.LocalPath, jsonText); File.WriteAllText(selectedFile, jsonText);
await MessageBox.Show(this, $"Successfully exported {account.AccountName} to\r\n\r\n{uri.LocalPath}", "Success!"); await MessageBox.Show(this, $"Successfully exported {account.AccountName} to\r\n\r\n{selectedFile}", "Success!");
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -63,11 +63,6 @@ namespace LibationAvalonia.Dialogs
public void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) public void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> SaveAndClose(); => SaveAndClose();
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private class BookDetailsDialogViewModel : ViewModelBase private class BookDetailsDialogViewModel : ViewModelBase
{ {
public class liberatedComboBoxItem public class liberatedComboBoxItem

View File

@ -172,23 +172,23 @@ namespace LibationAvalonia.Dialogs
} }
}); });
var selectedFile = await StorageProvider.SaveFilePickerAsync(saveFileDialog); var selectedFile = (await StorageProvider.SaveFilePickerAsync(saveFileDialog))?.TryGetLocalPath();
if (selectedFile?.TryGetUri(out var uri) is not true) return; if (selectedFile is null) return;
var ext = System.IO.Path.GetExtension(uri.LocalPath).ToLowerInvariant(); var ext = System.IO.Path.GetExtension(selectedFile).ToLowerInvariant();
switch (ext) switch (ext)
{ {
case ".xlsx": case ".xlsx":
default: default:
await Task.Run(() => RecordExporter.ToXlsx(uri.LocalPath, records)); await Task.Run(() => RecordExporter.ToXlsx(selectedFile, records));
break; break;
case ".csv": case ".csv":
await Task.Run(() => RecordExporter.ToCsv(uri.LocalPath, records)); await Task.Run(() => RecordExporter.ToCsv(selectedFile, records));
break; break;
case ".json": case ".json":
await Task.Run(() => RecordExporter.ToJson(uri.LocalPath, libraryBook, records)); await Task.Run(() => RecordExporter.ToJson(selectedFile, libraryBook, records));
break; break;
} }
} }

View File

@ -7,6 +7,11 @@
SystemDecorations="None" SystemDecorations="None"
Title="DescriptionDisplay"> Title="DescriptionDisplay">
<Window.Styles>
<Style Selector="TextBox[IsReadOnly=true] /template/ Border#PART_BorderElement">
<Setter Property="Background" Value="{DynamicResource TextControlBackground}" />
</Style>
</Window.Styles>
<TextBox <TextBox
Text="{Binding DescriptionText}" Text="{Binding DescriptionText}"
IsReadOnly="True" IsReadOnly="True"

View File

@ -52,11 +52,5 @@ namespace LibationAvalonia.Dialogs
{ {
Close(); Close();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }
} }

View File

@ -33,9 +33,10 @@
<DataTemplate> <DataTemplate>
<Button <Button
Width="55"
Height="30"
Content="X" Content="X"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
IsEnabled="{Binding !IsDefault}" IsEnabled="{Binding !IsDefault}"
Click="DeleteButton_Clicked" /> Click="DeleteButton_Clicked" />
@ -49,15 +50,15 @@
Binding="{Binding FilterString, Mode=TwoWay}" Binding="{Binding FilterString, Mode=TwoWay}"
Header="Filter"/> Header="Filter"/>
<DataGridTemplateColumn Header="Move&#xa;Up"> <DataGridTemplateColumn Header="Move&#xa;Up">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate> <DataTemplate>
<Button <Button
Width="50"
Height="30"
Content="▲" Content="▲"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
IsEnabled="{Binding !IsDefault}" IsEnabled="{Binding !IsDefault}"
ToolTip.Tip="Export account authorization to audible-cli" ToolTip.Tip="Export account authorization to audible-cli"
Click="MoveUpButton_Clicked" /> Click="MoveUpButton_Clicked" />
@ -73,9 +74,10 @@
<DataTemplate> <DataTemplate>
<Button <Button
Width="50"
Height="30"
Content="▼" Content="▼"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
IsEnabled="{Binding !IsDefault}" IsEnabled="{Binding !IsDefault}"
ToolTip.Tip="Export account authorization to audible-cli" ToolTip.Tip="Export account authorization to audible-cli"
Click="MoveDownButton_Clicked" /> Click="MoveDownButton_Clicked" />

View File

@ -170,10 +170,5 @@ namespace LibationAvalonia.Dialogs
public char Character => string.IsNullOrEmpty(_characterToReplace) ? default : _characterToReplace[0]; public char Character => string.IsNullOrEmpty(_characterToReplace) ? default : _characterToReplace[0];
public bool IsDefault { get; private set; } public bool IsDefault { get; private set; }
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }
} }

View File

@ -23,7 +23,6 @@
Grid.Column="0" Grid.Column="0"
Grid.Row="1" Grid.Row="1"
Name="userEditTbox" Name="userEditTbox"
FontFamily="{Binding FontFamily}"
Text="{Binding UserTemplateText, Mode=TwoWay}" /> Text="{Binding UserTemplateText, Mode=TwoWay}" />
<Button <Button

View File

@ -75,7 +75,6 @@ namespace LibationAvalonia.Dialogs
private class EditTemplateViewModel : ViewModels.ViewModelBase private class EditTemplateViewModel : ViewModels.ViewModelBase
{ {
private readonly Configuration config; private readonly Configuration config;
public FontFamily FontFamily { get; } = FontManager.Current.DefaultFontFamilyName;
public InlineCollection Inlines { get; } = new(); public InlineCollection Inlines { get; } = new();
public ITemplateEditor TemplateEditor { get; } public ITemplateEditor TemplateEditor { get; }
public EditTemplateViewModel(Configuration configuration, ITemplateEditor templates) public EditTemplateViewModel(Configuration configuration, ITemplateEditor templates)
@ -91,7 +90,7 @@ namespace LibationAvalonia.Dialogs
.Cast<TemplateTags>() .Cast<TemplateTags>()
.Select( .Select(
t => new Tuple<string, string, string>( t => new Tuple<string, string, string>(
$"<{t.TagName.Replace("->", "-\x200C>").Replace("<-", "<\x200C-")}>", $"<{t.TagName}>",
t.Description, t.Description,
t.DefaultValue) t.DefaultValue)
) )

View File

@ -20,12 +20,6 @@ namespace LibationAvalonia.Dialogs
DataContext = _bitmapHolder; DataContext = _bitmapHolder;
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void SetCoverBytes(byte[] cover) public void SetCoverBytes(byte[] cover)
{ {
_bitmapHolder.CoverImage = AvaloniaUtils.TryLoadImageOrDefault(cover); _bitmapHolder.CoverImage = AvaloniaUtils.TryLoadImageOrDefault(cover);
@ -36,7 +30,7 @@ namespace LibationAvalonia.Dialogs
var options = new FilePickerSaveOptions var options = new FilePickerSaveOptions
{ {
Title = $"Save Sover Image", Title = $"Save Sover Image",
SuggestedStartLocation = new Avalonia.Platform.Storage.FileIO.BclStorageFolder(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)), SuggestedStartLocation = await StorageProvider.TryGetFolderFromPathAsync(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)),
SuggestedFileName = PictureFileName, SuggestedFileName = PictureFileName,
DefaultExtension = "jpg", DefaultExtension = "jpg",
ShowOverwritePrompt = true, ShowOverwritePrompt = true,
@ -50,17 +44,17 @@ namespace LibationAvalonia.Dialogs
} }
}; };
var selectedFile = await StorageProvider.SaveFilePickerAsync(options); var selectedFile = (await StorageProvider.SaveFilePickerAsync(options))?.TryGetLocalPath();
if (selectedFile?.TryGetUri(out var uri) is not true) return; if (selectedFile is null) return;
try try
{ {
_bitmapHolder.CoverImage.Save(uri.LocalPath); _bitmapHolder.CoverImage.Save(selectedFile);
} }
catch (Exception ex) catch (Exception ex)
{ {
Serilog.Log.Logger.Error(ex, $"Failed to save picture to {uri.LocalPath}"); Serilog.Log.Logger.Error(ex, $"Failed to save picture to {selectedFile}");
await MessageBox.Show(this, $"An error was encountered while trying to save the picture\r\n\r\n{ex.Message}", "Failed to save picture", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1); await MessageBox.Show(this, $"An error was encountered while trying to save the picture\r\n\r\n{ex.Message}", "Failed to save picture", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
} }
} }

View File

@ -48,10 +48,5 @@ namespace LibationAvalonia.Dialogs
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
Close(DialogResult); Close(DialogResult);
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }
} }

View File

@ -12,10 +12,6 @@ namespace LibationAvalonia.Dialogs
InitializeComponent(); InitializeComponent();
DataContext = this; DataContext = this;
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) public void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> SaveAndClose(); => SaveAndClose();

View File

@ -46,10 +46,6 @@ namespace LibationAvalonia.Dialogs
SelectedItem = BookStatuses[0] as liberatedComboBoxItem; SelectedItem = BookStatuses[0] as liberatedComboBoxItem;
DataContext = this; DataContext = this;
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) public void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> SaveAndClose(); => SaveAndClose();

View File

@ -70,12 +70,12 @@ namespace LibationAvalonia.Dialogs
{ {
Title = "Select the folder to search for audiobooks", Title = "Select the folder to search for audiobooks",
AllowMultiple = false, AllowMultiple = false,
SuggestedStartLocation = new BclStorageFolder(Configuration.Instance.Books.PathWithoutPrefix) SuggestedStartLocation = await StorageProvider.TryGetFolderFromPathAsync(Configuration.Instance.Books.PathWithoutPrefix)
}; };
var selectedFolder = await StorageProvider.OpenFolderPickerAsync(folderPicker); var selectedFolder = (await StorageProvider.OpenFolderPickerAsync(folderPicker))?.SingleOrDefault()?.TryGetLocalPath();
if (selectedFolder.FirstOrDefault().TryGetUri(out var uri) is not true || !Directory.Exists(uri.LocalPath)) if (selectedFolder is null || !Directory.Exists(selectedFolder))
{ {
await CancelAndCloseAsync(); await CancelAndCloseAsync();
return; return;
@ -83,7 +83,7 @@ namespace LibationAvalonia.Dialogs
using var context = DbContexts.GetContext(); using var context = DbContexts.GetContext();
await foreach (var book in AudioFileStorage.FindAudiobooksAsync(uri.LocalPath, tokenSource.Token)) await foreach (var book in AudioFileStorage.FindAudiobooksAsync(selectedFolder, tokenSource.Token))
{ {
try try
{ {

View File

@ -12,11 +12,6 @@ namespace LibationAvalonia.Dialogs.Login
InitializeComponent(); InitializeComponent();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
protected override Task SaveAndCloseAsync() protected override Task SaveAndCloseAsync()
{ {
Serilog.Log.Logger.Information("Approve button clicked"); Serilog.Log.Logger.Information("Approve button clicked");

View File

@ -25,7 +25,7 @@ namespace LibationAvalonia.Dialogs.Login
{ {
var dialog = new LoginChoiceEagerDialog(_account); var dialog = new LoginChoiceEagerDialog(_account);
if (await dialog.ShowDialogAsync() is not DialogResult.OK) if (await dialog.ShowDialogAsync() is not DialogResult.OK || string.IsNullOrWhiteSpace(dialog.Password))
return null; return null;
switch (dialog.LoginMethod) switch (dialog.LoginMethod)

View File

@ -49,11 +49,6 @@ namespace LibationAvalonia.Dialogs.Login
Opened += (_, _) => (string.IsNullOrEmpty(password) ? passwordBox : captchaBox).Focus(); Opened += (_, _) => (string.IsNullOrEmpty(password) ? passwordBox : captchaBox).Focus();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
protected override async Task SaveAndCloseAsync() protected override async Task SaveAndCloseAsync()
{ {
if (string.IsNullOrWhiteSpace(_viewModel.Password)) if (string.IsNullOrWhiteSpace(_viewModel.Password))

View File

@ -31,12 +31,6 @@ namespace LibationAvalonia.Dialogs.Login
DataContext = this; DataContext = this;
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
protected override Task SaveAndCloseAsync() protected override Task SaveAndCloseAsync()
{ {
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { email = Account?.AccountId?.ToMask(), passwordLength = Password?.Length }); Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { email = Account?.AccountId?.ToMask(), passwordLength = Password?.Length });

View File

@ -4,6 +4,7 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
namespace LibationAvalonia.Dialogs.Login namespace LibationAvalonia.Dialogs.Login
{ {
@ -31,15 +32,21 @@ namespace LibationAvalonia.Dialogs.Login
DataContext = this; DataContext = this;
} }
protected override async Task SaveAndCloseAsync()
{
if (string.IsNullOrWhiteSpace(Password))
{
await MessageBox.Show(this, "Please enter your password");
return;
}
await base.SaveAndCloseAsync();
}
public async void ExternalLoginLink_Tapped(object sender, Avalonia.Input.TappedEventArgs e) public async void ExternalLoginLink_Tapped(object sender, Avalonia.Input.TappedEventArgs e)
{ {
LoginMethod = LoginMethod.External; LoginMethod = LoginMethod.External;
await SaveAndCloseAsync(); await SaveAndCloseAsync();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }
} }

View File

@ -40,10 +40,6 @@ namespace LibationAvalonia.Dialogs.Login
Account = account; Account = account;
DataContext = this; DataContext = this;
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
protected override async Task SaveAndCloseAsync() protected override async Task SaveAndCloseAsync()
{ {

View File

@ -65,11 +65,6 @@ namespace LibationAvalonia.Dialogs.Login
} }
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
protected override async Task SaveAndCloseAsync() protected override async Task SaveAndCloseAsync()
{ {
var selected = Values.CheckedButton; var selected = Values.CheckedButton;

View File

@ -60,11 +60,6 @@ namespace LibationAvalonia.Dialogs
} }
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void OkButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) public void OkButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{ {
SaveAndClose(); SaveAndClose();

View File

@ -10,7 +10,7 @@
Icon="/Assets/1x1.png"> Icon="/Assets/1x1.png">
<Grid ColumnDefinitions="*" RowDefinitions="*,Auto"> <Grid ColumnDefinitions="*" RowDefinitions="*,Auto">
<DockPanel Margin="5,10,10,20" Grid.Row="0" Background="White"> <DockPanel Margin="5,10,10,20" Grid.Row="0">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" <StackPanel DockPanel.Dock="Top" Orientation="Horizontal"
VerticalAlignment="Top"> VerticalAlignment="Top">
@ -26,7 +26,7 @@
</StackPanel> </StackPanel>
</DockPanel> </DockPanel>
<DockPanel Height="45" Grid.Row="1" Background="WhiteSmoke"> <DockPanel Height="45" Grid.Row="1" Background="{DynamicResource SystemChromeMediumLowColor}">
<DockPanel.Styles> <DockPanel.Styles>
<Style Selector="Button:focus"> <Style Selector="Button:focus">
<Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColor}" /> <Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColor}" />

View File

@ -17,11 +17,6 @@ namespace LibationAvalonia.Dialogs
InitializeComponent(); InitializeComponent();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
protected override void CancelAndClose() => Close(DialogResult.None); protected override void CancelAndClose() => Close(DialogResult.None);
protected override void SaveAndClose() { } protected override void SaveAndClose() { }

View File

@ -26,6 +26,10 @@ namespace LibationAvalonia.Dialogs
public ScanAccountsDialog() public ScanAccountsDialog()
{ {
InitializeComponent(); InitializeComponent();
this.HideMinMaxBtns();
this.Opened += ScanAccountsDialog_Opened;
LoadAccounts(); LoadAccounts();
} }
@ -45,13 +49,6 @@ namespace LibationAvalonia.Dialogs
DataContext = this; DataContext = this;
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
this.HideMinMaxBtns();
this.Opened += ScanAccountsDialog_Opened;
}
private void ScanAccountsDialog_Opened(object sender, System.EventArgs e) private void ScanAccountsDialog_Opened(object sender, System.EventArgs e)
{ {
this.FindControl<Button>(nameof(ImportButton)).Focus(); this.FindControl<Button>(nameof(ImportButton)).Focus();

View File

@ -12,6 +12,8 @@ namespace LibationAvalonia.Dialogs
{ {
InitializeComponent(); InitializeComponent();
this.HideMinMaxBtns();
StringFields = @" StringFields = @"
Search for wizard of oz: Search for wizard of oz:
title:oz title:oz
@ -52,12 +54,5 @@ for the ID field
DataContext = this; DataContext = this;
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
this.HideMinMaxBtns();
}
} }
} }

View File

@ -113,6 +113,32 @@
</CheckBox> </CheckBox>
--> -->
<Grid
Grid.Row="2"
ColumnDefinitions="Auto,Auto,*"
Margin="10"
VerticalAlignment="Bottom">
<TextBlock
Grid.Column="0"
FontSize="16"
VerticalAlignment="Center"
Text="Theme: "/>
<controls:WheelComboBox
Grid.Column="1"
SelectedItem="{Binding ImportantSettings.ThemeVariant, Mode=TwoWay}"
Items="{Binding ImportantSettings.Themes}" />
<TextBlock
Grid.Column="2"
FontSize="16"
FontWeight="Bold"
Margin="10,0,0,0"
VerticalAlignment="Center"
IsVisible="{Binding ImportantSettings.SelectionChanged}"
Text="Theme change takes effect on restart"/>
</Grid>
</Grid> </Grid>
</Border> </Border>
</TabItem> </TabItem>

View File

@ -1,6 +1,4 @@
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using LibationFileManager; using LibationFileManager;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -9,7 +7,6 @@ using ReactiveUI;
using Dinah.Core; using Dinah.Core;
using System.Linq; using System.Linq;
using FileManager; using FileManager;
using System.IO;
using Avalonia.Collections; using Avalonia.Collections;
using LibationUiBase; using LibationUiBase;
@ -29,11 +26,6 @@ namespace LibationAvalonia.Dialogs
DataContext = settingsDisp = new(config); DataContext = settingsDisp = new(config);
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
protected override async Task SaveAndCloseAsync() protected override async Task SaveAndCloseAsync()
{ {
if (!await settingsDisp.SaveSettingsAsync(config)) if (!await settingsDisp.SaveSettingsAsync(config))
@ -134,7 +126,7 @@ namespace LibationAvalonia.Dialogs
} }
} }
public class ImportantSettings : ISettingsDisplay public class ImportantSettings : ViewModels.ViewModelBase, ISettingsDisplay
{ {
public ImportantSettings(Configuration config) public ImportantSettings(Configuration config)
{ {
@ -147,6 +139,10 @@ namespace LibationAvalonia.Dialogs
SavePodcastsToParentFolder = config.SavePodcastsToParentFolder; SavePodcastsToParentFolder = config.SavePodcastsToParentFolder;
LoggingLevel = config.LogLevel; LoggingLevel = config.LogLevel;
BetaOptIn = config.BetaOptIn; BetaOptIn = config.BetaOptIn;
ThemeVariant = InitialThemeVariant
= Configuration.Instance.GetString(propertyName: nameof(ThemeVariant)) is nameof(Avalonia.Styling.ThemeVariant.Dark)
? nameof(Avalonia.Styling.ThemeVariant.Dark)
: nameof(Avalonia.Styling.ThemeVariant.Light);
} }
public async Task<bool> SaveSettingsAsync(Configuration config) public async Task<bool> SaveSettingsAsync(Configuration config)
@ -168,6 +164,7 @@ namespace LibationAvalonia.Dialogs
config.SavePodcastsToParentFolder = SavePodcastsToParentFolder; config.SavePodcastsToParentFolder = SavePodcastsToParentFolder;
config.LogLevel = LoggingLevel; config.LogLevel = LoggingLevel;
config.BetaOptIn = BetaOptIn; config.BetaOptIn = BetaOptIn;
Configuration.Instance.SetString(ThemeVariant, nameof(ThemeVariant));
return true; return true;
} }
@ -184,11 +181,27 @@ namespace LibationAvalonia.Dialogs
public string SavePodcastsToParentFolderText { get; } = Configuration.GetDescription(nameof(Configuration.SavePodcastsToParentFolder)); public string SavePodcastsToParentFolderText { get; } = Configuration.GetDescription(nameof(Configuration.SavePodcastsToParentFolder));
public Serilog.Events.LogEventLevel[] LoggingLevels { get; } = Enum.GetValues<Serilog.Events.LogEventLevel>(); public Serilog.Events.LogEventLevel[] LoggingLevels { get; } = Enum.GetValues<Serilog.Events.LogEventLevel>();
public string BetaOptInText { get; } = Configuration.GetDescription(nameof(Configuration.BetaOptIn)); public string BetaOptInText { get; } = Configuration.GetDescription(nameof(Configuration.BetaOptIn));
public string[] Themes { get; } = { nameof(Avalonia.Styling.ThemeVariant.Light), nameof(Avalonia.Styling.ThemeVariant.Dark) };
public string BooksDirectory { get; set; } public string BooksDirectory { get; set; }
public bool SavePodcastsToParentFolder { get; set; } public bool SavePodcastsToParentFolder { get; set; }
public Serilog.Events.LogEventLevel LoggingLevel { get; set; } public Serilog.Events.LogEventLevel LoggingLevel { get; set; }
public bool BetaOptIn { get; set; } public bool BetaOptIn { get; set; }
private string themeVariant;
public string ThemeVariant
{
get => themeVariant;
set
{
this.RaiseAndSetIfChanged(ref themeVariant, value);
SelectionChanged = ThemeVariant != InitialThemeVariant;
this.RaisePropertyChanged(nameof(SelectionChanged));
}
}
public string InitialThemeVariant { get; private set; }
public bool SelectionChanged { get; private set; }
} }
public class ImportSettings : ISettingsDisplay public class ImportSettings : ISettingsDisplay

View File

@ -30,10 +30,5 @@ namespace LibationAvalonia.Dialogs
IsReturningUser = true; IsReturningUser = true;
Close(DialogResult.OK); Close(DialogResult.OK);
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }
} }

View File

@ -15,11 +15,6 @@ namespace LibationAvalonia.Dialogs
DataContext = this; DataContext = this;
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) public void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> SaveAndClose(); => SaveAndClose();
} }

View File

@ -5,7 +5,7 @@
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport> <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationIcon>libation.ico</ApplicationIcon> <ApplicationIcon>Assets/libation.ico</ApplicationIcon>
<AssemblyName>Libation</AssemblyName> <AssemblyName>Libation</AssemblyName>
<IsPublishable>true</IsPublishable> <IsPublishable>true</IsPublishable>
<PublishReadyToRun>true</PublishReadyToRun> <PublishReadyToRun>true</PublishReadyToRun>
@ -31,47 +31,15 @@
<ItemGroup> <ItemGroup>
<AvaloniaResource Include="Assets\**" /> <AvaloniaResource Include="Assets\**" />
<None Remove=".gitignore" /> <None Remove=".gitignore" />
<None Remove="Assets\Arrows_left.png" /> <None Remove="Assets\DataGridFluentTheme.xaml" />
<None Remove="Assets\Arrows_right.png" />
<None Remove="Assets\Asterisk.png" />
<None Remove="Assets\cancel.png" />
<None Remove="Assets\completed.png" />
<None Remove="Assets\down.png" />
<None Remove="Assets\download-arrow.png" />
<None Remove="Assets\edit-tags-25x25.png" />
<None Remove="Assets\edit-tags-50x50.png" />
<None Remove="Assets\edit_25x25.png" />
<None Remove="Assets\edit_64x64.png" />
<None Remove="Assets\error.png" />
<None Remove="Assets\errored.png" />
<None Remove="Assets\Exclamation.png" />
<None Remove="Assets\first.png" />
<None Remove="Assets\glass-with-glow_16.png" />
<None Remove="Assets\img-coverart-prod-unavailable_300x300.jpg" /> <None Remove="Assets\img-coverart-prod-unavailable_300x300.jpg" />
<None Remove="Assets\img-coverart-prod-unavailable_500x500.jpg" /> <None Remove="Assets\img-coverart-prod-unavailable_500x500.jpg" />
<None Remove="Assets\img-coverart-prod-unavailable_80x80.jpg" /> <None Remove="Assets\img-coverart-prod-unavailable_80x80.jpg" />
<None Remove="Assets\import_16x16.png" /> <None Remove="Assets\1x1.png" />
<None Remove="Assets\last.png" />
<None Remove="Assets\libation.ico" />
<None Remove="Assets\LibationStyles.xaml" />
<None Remove="Assets\liberate_green.png" />
<None Remove="Assets\liberate_green_pdf_no.png" />
<None Remove="Assets\liberate_green_pdf_yes.png" />
<None Remove="Assets\liberate_red.png" />
<None Remove="Assets\liberate_red_pdf_no.png" />
<None Remove="Assets\liberate_red_pdf_yes.png" />
<None Remove="Assets\liberate_yellow.png" />
<None Remove="Assets\liberate_yellow_pdf_no.png" />
<None Remove="Assets\liberate_yellow_pdf_yes.png" />
<None Remove="Assets\MBIcons\Asterisk.png" /> <None Remove="Assets\MBIcons\Asterisk.png" />
<None Remove="Assets\MBIcons\error.png" /> <None Remove="Assets\MBIcons\error.png" />
<None Remove="Assets\MBIcons\Exclamation.png" /> <None Remove="Assets\MBIcons\Exclamation.png" />
<None Remove="Assets\MBIcons\Question.png" /> <None Remove="Assets\MBIcons\Question.png" />
<None Remove="Assets\minus.png" />
<None Remove="Assets\plus.png" />
<None Remove="Assets\Question.png" />
<None Remove="Assets\queued.png" />
<None Remove="Assets\up.png" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -80,6 +48,9 @@
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon> <DependentUpon>Resources.resx</DependentUpon>
</Compile> </Compile>
<Compile Update="Views\LiberateStatusButton.axaml.cs">
<DependentUpon>LiberateStatusButton.axaml</DependentUpon>
</Compile>
<Compile Update="Views\MainWindow.*.cs"> <Compile Update="Views\MainWindow.*.cs">
<DependentUpon>MainWindow.axaml</DependentUpon> <DependentUpon>MainWindow.axaml</DependentUpon>
</Compile> </Compile>
@ -97,13 +68,13 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.0-preview4" /> <PackageReference Include="Avalonia.Diagnostics" Version="11.0.0-preview6" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.0-preview4" /> <PackageReference Include="Avalonia" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-preview4" /> <PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.0-preview4 " /> <PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview4" /> <PackageReference Include="Avalonia.Desktop" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-preview4" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview6" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.6.1" /> <PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-preview6" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -7,7 +7,7 @@ namespace LibationAvalonia
{ {
public class ViewLocator : IDataTemplate public class ViewLocator : IDataTemplate
{ {
public IControl Build(object data) public Control Build(object data)
{ {
var name = data.GetType().FullName!.Replace("ViewModel", "View"); var name = data.GetType().FullName!.Replace("ViewModel", "View");
var type = Type.GetType(name); var type = Type.GetType(name);

View File

@ -16,11 +16,7 @@ namespace LibationAvalonia.ViewModels
protected override Bitmap LoadImage(byte[] picture) protected override Bitmap LoadImage(byte[] picture)
=> AvaloniaUtils.TryLoadImageOrDefault(picture, LibationFileManager.PictureSize._80x80); => AvaloniaUtils.TryLoadImageOrDefault(picture, LibationFileManager.PictureSize._80x80);
protected override Bitmap GetResourceImage(string rescName) //Button icons are handled by LiberateStatusButton
{ protected override Bitmap GetResourceImage(string rescName) => null;
//These images are assest, so assume they will never corrupt.
using var stream = App.OpenAsset(rescName + ".png");
return new Bitmap(stream);
}
} }
} }

View File

@ -0,0 +1,27 @@
using ReactiveUI;
namespace LibationAvalonia.ViewModels
{
public class LiberateStatusButtonViewModel : ViewModelBase
{
private bool isSeries;
private bool isError;
private bool isButtonEnabled;
private bool expanded;
private bool redVisible = true;
private bool yellowVisible;
private bool greenVisible;
private bool pdfNotDownloadedVisible;
private bool pdfDownloadedVisible;
public bool IsError { get => isError; set => this.RaiseAndSetIfChanged(ref isError, value); }
public bool IsButtonEnabled { get => isButtonEnabled; set => this.RaiseAndSetIfChanged(ref isButtonEnabled, value); }
public bool IsSeries { get => isSeries; set => this.RaiseAndSetIfChanged(ref isSeries, value); }
public bool Expanded { get => expanded; set => this.RaiseAndSetIfChanged(ref expanded, value); }
public bool RedVisible { get => redVisible; set => this.RaiseAndSetIfChanged(ref redVisible, value); }
public bool YellowVisible { get => yellowVisible; set => this.RaiseAndSetIfChanged(ref yellowVisible, value); }
public bool GreenVisible { get => greenVisible; set => this.RaiseAndSetIfChanged(ref greenVisible, value); }
public bool PdfDownloadedVisible { get => pdfDownloadedVisible; set => this.RaiseAndSetIfChanged(ref pdfDownloadedVisible, value); }
public bool PdfNotDownloadedVisible { get => pdfNotDownloadedVisible; set => this.RaiseAndSetIfChanged(ref pdfNotDownloadedVisible, value); }
}
}

View File

@ -134,8 +134,11 @@ namespace LibationAvalonia.ViewModels
set set
{ {
this.RaiseAndSetIfChanged(ref _queueOpen, value); this.RaiseAndSetIfChanged(ref _queueOpen, value);
QueueButtonAngle = value ? 180 : 0;
this.RaisePropertyChanged(nameof(QueueButtonAngle));
} }
} }
public double QueueButtonAngle { get; private set; }
/// <summary> The number of books visible in the Product Display </summary> /// <summary> The number of books visible in the Product Display </summary>

View File

@ -31,7 +31,11 @@ namespace LibationAvalonia.ViewModels
public bool RemoveColumnVisivle { get => _removeColumnVisivle; private set => this.RaiseAndSetIfChanged(ref _removeColumnVisivle, value); } public bool RemoveColumnVisivle { get => _removeColumnVisivle; private set => this.RaiseAndSetIfChanged(ref _removeColumnVisivle, value); }
public List<LibraryBook> GetVisibleBookEntries() public List<LibraryBook> GetVisibleBookEntries()
=> GridEntries => FilteredInGridEntries?
.OfType<ILibraryBookEntry>()
.Select(lbe => lbe.LibraryBook)
.ToList()
?? SOURCE
.OfType<ILibraryBookEntry>() .OfType<ILibraryBookEntry>()
.Select(lbe => lbe.LibraryBook) .Select(lbe => lbe.LibraryBook)
.ToList(); .ToList();
@ -110,16 +114,31 @@ namespace LibationAvalonia.ViewModels
seriesEntry.Liberate.Expanded = false; seriesEntry.Liberate.Expanded = false;
geList.Add(seriesEntry); geList.Add(seriesEntry);
geList.AddRange(seriesEntry.Children);
} }
//Create the filtered-in list before adding entries to avoid a refresh //Create the filtered-in list before adding entries to avoid a refresh
FilteredInGridEntries = QueryResults(geList, FilterString); FilteredInGridEntries = QueryResults(geList, FilterString);
SOURCE.AddRange(geList.OrderByDescending(e => e.DateAdded)); SOURCE.AddRange(geList.OrderByDescending(e => e.DateAdded));
GridEntries.CollectionChanged += (_, _)
=> VisibleCountChanged?.Invoke(this, GridEntries.OfType<ILibraryBookEntry>().Count());
VisibleCountChanged?.Invoke(this, GridEntries.OfType<ILibraryBookEntry>().Count()); //Add all children beneath their parent
foreach (var series in SOURCE.OfType<ISeriesEntry>().ToList())
{
var seriesIndex = SOURCE.IndexOf(series);
foreach (var child in series.Children)
SOURCE.Insert(++seriesIndex, child);
}
GridEntries.CollectionChanged += GridEntries_CollectionChanged;
GridEntries_CollectionChanged();
}
private void GridEntries_CollectionChanged(object sender = null, EventArgs e = null)
{
var count
= FilteredInGridEntries?.OfType<ILibraryBookEntry>().Count()
?? SOURCE.OfType<ILibraryBookEntry>().Count();
VisibleCountChanged?.Invoke(this, count);
} }
/// <summary> /// <summary>
@ -127,6 +146,8 @@ namespace LibationAvalonia.ViewModels
/// </summary> /// </summary>
internal async Task UpdateGridAsync(List<LibraryBook> dbBooks) internal async Task UpdateGridAsync(List<LibraryBook> dbBooks)
{ {
GridEntries.CollectionChanged -= GridEntries_CollectionChanged;
#region Add new or update existing grid entries #region Add new or update existing grid entries
//Add absent entries to grid, or update existing entry //Add absent entries to grid, or update existing entry
@ -176,6 +197,9 @@ namespace LibationAvalonia.ViewModels
await Filter(FilterString); await Filter(FilterString);
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true);
GridEntries.CollectionChanged += GridEntries_CollectionChanged;
GridEntries_CollectionChanged();
} }
private void RemoveBooks(IEnumerable<ILibraryBookEntry> removedBooks, IEnumerable<ISeriesEntry> removedSeries) private void RemoveBooks(IEnumerable<ILibraryBookEntry> removedBooks, IEnumerable<ISeriesEntry> removedSeries)
@ -237,13 +261,15 @@ namespace LibationAvalonia.ViewModels
//Series exists. Create and add episode child then update the SeriesEntry //Series exists. Create and add episode child then update the SeriesEntry
episodeEntry = new LibraryBookEntry<AvaloniaEntryStatus>(episodeBook, seriesEntry); episodeEntry = new LibraryBookEntry<AvaloniaEntryStatus>(episodeBook, seriesEntry);
seriesEntry.Children.Add(episodeEntry); seriesEntry.Children.Add(episodeEntry);
seriesEntry.Children.Sort((c1, c2) => c1.SeriesIndex.CompareTo(c2.SeriesIndex));
var seriesBook = dbBooks.Single(lb => lb.Book.AudibleProductId == seriesEntry.LibraryBook.Book.AudibleProductId); var seriesBook = dbBooks.Single(lb => lb.Book.AudibleProductId == seriesEntry.LibraryBook.Book.AudibleProductId);
seriesEntry.UpdateLibraryBook(seriesBook); seriesEntry.UpdateLibraryBook(seriesBook);
} }
//Add episode to the grid beneath the parent //Add episode to the grid beneath the parent
int seriesIndex = SOURCE.IndexOf(seriesEntry); int seriesIndex = SOURCE.IndexOf(seriesEntry);
SOURCE.Insert(seriesIndex + 1, episodeEntry); int episodeIndex = seriesEntry.Children.IndexOf(episodeEntry);
SOURCE.Insert(seriesIndex + 1 + episodeIndex, episodeEntry);
} }
else else
existingEpisodeEntry.UpdateLibraryBook(episodeBook); existingEpisodeEntry.UpdateLibraryBook(episodeBook);
@ -401,6 +427,10 @@ namespace LibationAvalonia.ViewModels
foreach (var r in removable) foreach (var r in removable)
r.Remove = true; r.Remove = true;
} }
catch (OperationCanceledException)
{
Serilog.Log.Information("Audible login attempt cancelled by user");
}
catch (Exception ex) catch (Exception ex)
{ {
await MessageBox.ShowAdminAlert( await MessageBox.ShowAdminAlert(

View File

@ -0,0 +1,82 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:LibationAvalonia.ViewModels"
mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="200" MinWidth="64" MinHeight="64"
Background="Transparent"
x:DataType="vm:LiberateStatusButtonViewModel"
x:Class="LibationAvalonia.Views.LiberateStatusButton">
<UserControl.Styles>
<Style Selector="Path">
<Setter Property="Fill" Value="{DynamicResource IconFill}" />
<Setter Property="Stretch" Value="Uniform" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style Selector="Rectangle">
<Setter Property="Width" Value="20" />
<Setter Property="Height" Value="18" />
</Style>
<Style Selector="Grid > Path">
<Setter Property="Margin" Value="4,0,0,0" />
<Setter Property="Width" Value="28.8" />
</Style>
<Style Selector="Button:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
</Style>
</UserControl.Styles>
<Button
Name="button"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsEnabled="{Binding IsButtonEnabled}" Padding="0" Click="Button_Click" >
<Panel>
<Panel
Width="64" Height="64"
IsVisible="{CompiledBinding !IsError}">
<Panel IsVisible="{CompiledBinding IsSeries}">
<Path IsVisible="{CompiledBinding Expanded}" Data="{StaticResource CollapseIcon}" />
<Path IsVisible="{CompiledBinding !Expanded}" Data="{StaticResource ExpandIcon}" />
</Panel>
<Grid
IsVisible="{CompiledBinding !IsSeries}"
HorizontalAlignment="Center"
ColumnDefinitions="Auto,Auto">
<Canvas Width="29.44" Height="64">
<Rectangle Canvas.Left="5" Canvas.Top="5" IsVisible="{CompiledBinding RedVisible}" Fill="{DynamicResource StoplightRed}" />
<Rectangle Canvas.Left="5" Canvas.Top="23" IsVisible="{CompiledBinding YellowVisible}" Fill="{DynamicResource StoplightYellow}" />
<Rectangle Canvas.Left="5" Canvas.Top="42" IsVisible="{CompiledBinding GreenVisible}" Fill="{DynamicResource StoplightGreen}" />
<Path Height="64" Stretch="Uniform" Data="{StaticResource StoplightBodyIcon}"/>
</Canvas>
<Path Grid.Column="1" IsVisible="{CompiledBinding PdfDownloadedVisible}" Data="{StaticResource PdfDownloadedIcon}"/>
<Path Grid.Column="1" IsVisible="{CompiledBinding PdfNotDownloadedVisible}" Data="{StaticResource PdfNotDownloadedIcon}"/>
</Grid>
</Panel>
<Path
Stretch="None" Width="64"
IsVisible="{CompiledBinding IsError}"
Fill="{DynamicResource CancelRed}"
Data="{StaticResource BookErrorIcon}" />
<Path
Stretch="Fill"
IsVisible="{CompiledBinding !IsButtonEnabled}"
Fill="{DynamicResource DisabledGrayBrush}"
Data="M0,0 H1 V1 H0" />
</Panel>
</Button>
</UserControl>

View File

@ -0,0 +1,80 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using DataLayer;
using LibationAvalonia.ViewModels;
using System;
namespace LibationAvalonia.Views
{
public partial class LiberateStatusButton : UserControl
{
public event EventHandler Click;
public static readonly StyledProperty<LiberatedStatus> BookStatusProperty =
AvaloniaProperty.Register<LiberateStatusButton, LiberatedStatus>(nameof(BookStatus));
public static readonly StyledProperty<LiberatedStatus?> PdfStatusProperty =
AvaloniaProperty.Register<LiberateStatusButton, LiberatedStatus?>(nameof(PdfStatus));
public static readonly StyledProperty<bool> IsUnavailableProperty =
AvaloniaProperty.Register<LiberateStatusButton, bool>(nameof(IsUnavailable));
public static readonly StyledProperty<bool> ExpandedProperty =
AvaloniaProperty.Register<LiberateStatusButton, bool>(nameof(Expanded));
public static readonly StyledProperty<bool> IsSeriesProperty =
AvaloniaProperty.Register<LiberateStatusButton, bool>(nameof(IsSeries));
public LiberatedStatus BookStatus { get => GetValue(BookStatusProperty); set => SetValue(BookStatusProperty, value); }
public LiberatedStatus? PdfStatus { get => GetValue(PdfStatusProperty); set => SetValue(PdfStatusProperty, value); }
public bool IsUnavailable { get => GetValue(IsUnavailableProperty); set => SetValue(IsUnavailableProperty, value); }
public bool Expanded { get => GetValue(ExpandedProperty); set => SetValue(ExpandedProperty, value); }
public bool IsSeries { get => GetValue(IsSeriesProperty); set => SetValue(IsSeriesProperty, value); }
private readonly LiberateStatusButtonViewModel viewModel = new();
public LiberateStatusButton()
{
InitializeComponent();
button.DataContext = viewModel;
if (Design.IsDesignMode)
{
BookStatus = LiberatedStatus.PartialDownload;
PdfStatus = null;
IsSeries = true;
}
}
private void Button_Click(object sender, RoutedEventArgs e) => Click?.Invoke(this, EventArgs.Empty);
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (change.Property == BookStatusProperty)
{
viewModel.IsError = BookStatus is LiberatedStatus.Error;
viewModel.RedVisible = BookStatus is LiberatedStatus.NotLiberated;
viewModel.YellowVisible = BookStatus is LiberatedStatus.PartialDownload;
viewModel.GreenVisible = BookStatus is LiberatedStatus.Liberated;
}
else if (change.Property == PdfStatusProperty)
{
viewModel.PdfDownloadedVisible = PdfStatus is LiberatedStatus.Liberated;
viewModel.PdfNotDownloadedVisible = PdfStatus is LiberatedStatus.NotLiberated;
}
else if (change.Property == IsSeriesProperty)
{
viewModel.IsSeries = IsSeries;
}
else if (change.Property == ExpandedProperty)
{
viewModel.Expanded = Expanded;
}
viewModel.IsButtonEnabled = !viewModel.IsError && (!IsUnavailable || (BookStatus is LiberatedStatus.Liberated && PdfStatus is null or LiberatedStatus.Liberated));
base.OnPropertyChanged(change);
}
}
}

View File

@ -20,7 +20,7 @@ namespace LibationAvalonia.Views
var options = new FilePickerSaveOptions var options = new FilePickerSaveOptions
{ {
Title = "Where to export Library", Title = "Where to export Library",
SuggestedStartLocation = new Avalonia.Platform.Storage.FileIO.BclStorageFolder(Configuration.Instance.Books.PathWithoutPrefix), SuggestedStartLocation = await StorageProvider.TryGetFolderFromPathAsync(Configuration.Instance.Books.PathWithoutPrefix),
SuggestedFileName = $"Libation Library Export {DateTime.Now:yyyy-MM-dd}", SuggestedFileName = $"Libation Library Export {DateTime.Now:yyyy-MM-dd}",
DefaultExtension = "xlsx", DefaultExtension = "xlsx",
ShowOverwritePrompt = true, ShowOverwritePrompt = true,
@ -46,26 +46,26 @@ namespace LibationAvalonia.Views
} }
}; };
var selectedFile = await StorageProvider.SaveFilePickerAsync(options); var selectedFile = (await StorageProvider.SaveFilePickerAsync(options))?.TryGetLocalPath();
if (selectedFile?.TryGetUri(out var uri) is not true) return; if (selectedFile is null) return;
var ext = FileUtility.GetStandardizedExtension(System.IO.Path.GetExtension(uri.LocalPath)); var ext = FileUtility.GetStandardizedExtension(System.IO.Path.GetExtension(selectedFile));
switch (ext) switch (ext)
{ {
case ".xlsx": // xlsx case ".xlsx": // xlsx
default: default:
LibraryExporter.ToXlsx(uri.LocalPath); LibraryExporter.ToXlsx(selectedFile);
break; break;
case ".csv": // csv case ".csv": // csv
LibraryExporter.ToCsv(uri.LocalPath); LibraryExporter.ToCsv(selectedFile);
break; break;
case ".json": // json case ".json": // json
LibraryExporter.ToJson(uri.LocalPath); LibraryExporter.ToJson(selectedFile);
break; break;
} }
await MessageBox.Show("Library exported to:\r\n" + uri.LocalPath, "Library Exported"); await MessageBox.Show("Library exported to:\r\n" + selectedFile, "Library Exported");
} }
catch (Exception ex) catch (Exception ex)
{ {

Some files were not shown because too many files have changed in this diff Show More