diff --git a/Source/AaxDecrypter/AaxDecrypter.csproj b/Source/AaxDecrypter/AaxDecrypter.csproj index c53c1baf..f385aa37 100644 --- a/Source/AaxDecrypter/AaxDecrypter.csproj +++ b/Source/AaxDecrypter/AaxDecrypter.csproj @@ -1,13 +1,9 @@  - net6.0-windows + net6.0 - - - - embedded @@ -16,6 +12,10 @@ embedded + + + + diff --git a/Source/AppScaffolding/AppScaffolding.csproj b/Source/AppScaffolding/AppScaffolding.csproj index edc6deec..86a61359 100644 --- a/Source/AppScaffolding/AppScaffolding.csproj +++ b/Source/AppScaffolding/AppScaffolding.csproj @@ -1,7 +1,7 @@ - net6.0-windows + net6.0 8.2.5.1 diff --git a/Source/ApplicationServices/ApplicationServices.csproj b/Source/ApplicationServices/ApplicationServices.csproj index 439fc19f..8045df40 100644 --- a/Source/ApplicationServices/ApplicationServices.csproj +++ b/Source/ApplicationServices/ApplicationServices.csproj @@ -1,7 +1,7 @@  - net6.0-windows + net6.0 diff --git a/Source/AudibleUtilities/AudibleUtilities.csproj b/Source/AudibleUtilities/AudibleUtilities.csproj index b835a2e6..e7cc2d81 100644 --- a/Source/AudibleUtilities/AudibleUtilities.csproj +++ b/Source/AudibleUtilities/AudibleUtilities.csproj @@ -1,7 +1,7 @@  - net6.0-windows + net6.0 diff --git a/Source/DataLayer/DataLayer.csproj b/Source/DataLayer/DataLayer.csproj index 9ee149f7..3bfe8ee2 100644 --- a/Source/DataLayer/DataLayer.csproj +++ b/Source/DataLayer/DataLayer.csproj @@ -1,7 +1,7 @@  - net6.0-windows + net6.0 diff --git a/Source/DtoImporterService/DtoImporterService.csproj b/Source/DtoImporterService/DtoImporterService.csproj index d784be6f..aba836a6 100644 --- a/Source/DtoImporterService/DtoImporterService.csproj +++ b/Source/DtoImporterService/DtoImporterService.csproj @@ -1,7 +1,7 @@  - net6.0-windows + net6.0 diff --git a/Source/FileLiberator/FileLiberator.csproj b/Source/FileLiberator/FileLiberator.csproj index 65b461a1..524efe12 100644 --- a/Source/FileLiberator/FileLiberator.csproj +++ b/Source/FileLiberator/FileLiberator.csproj @@ -1,7 +1,7 @@  - net6.0-windows + net6.0 diff --git a/Source/FileManager/LongPath.cs b/Source/FileManager/LongPath.cs index 1c1bd08a..01a391a9 100644 --- a/Source/FileManager/LongPath.cs +++ b/Source/FileManager/LongPath.cs @@ -20,8 +20,13 @@ namespace FileManager public string Path { get; init; } public override string ToString() => Path; + private static readonly PlatformID PlatformID = Environment.OSVersion.Platform; + + public static implicit operator LongPath(string path) { + if (PlatformID is PlatformID.Unix) return new LongPath { Path = path }; + if (path is null) return null; //File I/O functions in the Windows API convert "/" to "\" as part of converting @@ -58,6 +63,8 @@ namespace FileManager { get { + if (PlatformID is PlatformID.Unix) return Path; + //Short Path names are useful for navigating to the file in windows explorer, //which will not recognize paths longer than MAX_PATH. Short path names are not //always enabled on every volume. So to check if a volume enables short path @@ -96,6 +103,7 @@ namespace FileManager { get { + if (PlatformID is PlatformID.Unix) return Path; if (Path is null) return null; StringBuilder longPathBuffer = new(MaxPathLength); @@ -106,15 +114,21 @@ namespace FileManager [JsonIgnore] public string PathWithoutPrefix - => Path?.StartsWith(LONG_PATH_PREFIX) == true ? - Path.Remove(0, LONG_PATH_PREFIX.Length) : - Path; - + { + get + { + if (PlatformID is PlatformID.Unix) return Path; + return + Path?.StartsWith(LONG_PATH_PREFIX) == true ? Path.Remove(0, LONG_PATH_PREFIX.Length) + :Path; + } + } [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] private static extern int GetShortPathName([MarshalAs(UnmanagedType.LPWStr)] string path, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder shortPath, int shortPathLength); [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] private static extern int GetLongPathName([MarshalAs(UnmanagedType.LPWStr)] string lpszShortPath, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpszLongPath, int cchBuffer); + } } diff --git a/Source/FileManager/ReplacementCharacters.cs b/Source/FileManager/ReplacementCharacters.cs index 3146cd40..550a54c6 100644 --- a/Source/FileManager/ReplacementCharacters.cs +++ b/Source/FileManager/ReplacementCharacters.cs @@ -14,7 +14,7 @@ namespace FileManager internal const char QUOTE_MARK = '"'; [JsonIgnore] public bool Mandatory { get; internal set; } [JsonProperty] public char CharacterToReplace { get; private set; } - [JsonProperty] public string ReplacementString { get; private set; } + [JsonProperty] public string ReplacementString { get; set; } [JsonProperty] public string Description { get; private set; } public override string ToString() => $"{CharacterToReplace} → {ReplacementString} ({Description})"; diff --git a/Source/Libation.sln b/Source/Libation.sln index e15d074a..c5d96dd1 100644 --- a/Source/Libation.sln +++ b/Source/Libation.sln @@ -66,6 +66,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibationFileManager.Tests", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hangover", "Hangover\Hangover.csproj", "{40C67036-C1A7-4FDF-AA83-8EC902E257F3}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibationAvalonia", "LibationAvalonia\LibationAvalonia.csproj", "{F612D06F-3134-4B9B-95CD-EB3FC798AE60}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -144,6 +146,10 @@ Global {40C67036-C1A7-4FDF-AA83-8EC902E257F3}.Debug|Any CPU.Build.0 = Debug|Any CPU {40C67036-C1A7-4FDF-AA83-8EC902E257F3}.Release|Any CPU.ActiveCfg = Release|Any CPU {40C67036-C1A7-4FDF-AA83-8EC902E257F3}.Release|Any CPU.Build.0 = Release|Any CPU + {F612D06F-3134-4B9B-95CD-EB3FC798AE60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F612D06F-3134-4B9B-95CD-EB3FC798AE60}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F612D06F-3134-4B9B-95CD-EB3FC798AE60}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F612D06F-3134-4B9B-95CD-EB3FC798AE60}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -167,6 +173,7 @@ Global {F2E04270-4551-41C4-99FF-E7125BED708C} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53} {EB781571-8548-477E-82AD-FB9FAB548D2F} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53} {40C67036-C1A7-4FDF-AA83-8EC902E257F3} = {8679CAC8-9164-4007-BDD2-F004810EDA14} + {F612D06F-3134-4B9B-95CD-EB3FC798AE60} = {8679CAC8-9164-4007-BDD2-F004810EDA14} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {615E00ED-BAEF-4E8E-A92A-9B82D87942A9} diff --git a/Source/LibationAvalonia/AvaloniaUI/App.axaml b/Source/LibationAvalonia/AvaloniaUI/App.axaml new file mode 100644 index 00000000..75ff7054 --- /dev/null +++ b/Source/LibationAvalonia/AvaloniaUI/App.axaml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/LibationAvalonia/AvaloniaUI/App.axaml.cs b/Source/LibationAvalonia/AvaloniaUI/App.axaml.cs new file mode 100644 index 00000000..978c33c4 --- /dev/null +++ b/Source/LibationAvalonia/AvaloniaUI/App.axaml.cs @@ -0,0 +1,60 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using Avalonia.Media; +using LibationFileManager; +using LibationAvalonia.AvaloniaUI.Views; +using System; +using Avalonia.Platform; + +namespace LibationAvalonia.AvaloniaUI +{ + public class App : Application + { + public static IBrush ProcessQueueBookFailedBrush { get; private set; } + public static IBrush ProcessQueueBookCompletedBrush { get; private set; } + public static IBrush ProcessQueueBookCancelledBrush { get; private set; } + public static IBrush ProcessQueueBookDefaultBrush { get; private set; } + public static IBrush SeriesEntryGridBackgroundBrush { get; private set; } + + public static IAssetLoader AssetLoader { get; private set; } + + public static readonly Uri AssetUriBase = new Uri("avares://Libation/AvaloniaUI/Assets/"); + public static System.IO.Stream OpenAsset(string assetRelativePath) + => AssetLoader.Open(new Uri(AssetUriBase, assetRelativePath)); + + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + AssetLoader = AvaloniaLocator.Current.GetService(); + } + + public override void OnFrameworkInitializationCompleted() + { + LoadStyles(); + + var SEGOEUI = new Typeface(new FontFamily(new Uri("avares://Libation/AvaloniaUI/Assets/WINGDING.TTF"), "SEGOEUI_Local")); + var gtf = FontManager.Current.GetOrAddGlyphTypeface(SEGOEUI); + + + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + var mainWindow = new MainWindow(); + desktop.MainWindow = mainWindow; + mainWindow.RestoreSizeAndLocation(Configuration.Instance); + mainWindow.OnLoad(); + } + + base.OnFrameworkInitializationCompleted(); + } + + private void LoadStyles() + { + ProcessQueueBookFailedBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookFailedBrush"); + ProcessQueueBookCompletedBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookCompletedBrush"); + ProcessQueueBookCancelledBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookCancelledBrush"); + ProcessQueueBookDefaultBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookDefaultBrush"); + SeriesEntryGridBackgroundBrush = AvaloniaUtils.GetBrushFromResources("SeriesEntryGridBackgroundBrush"); + } + } +} \ No newline at end of file diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/1x1.png b/Source/LibationAvalonia/AvaloniaUI/Assets/1x1.png new file mode 100644 index 00000000..1914264c Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/1x1.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/DataGridTheme.xaml b/Source/LibationAvalonia/AvaloniaUI/Assets/DataGridTheme.xaml new file mode 100644 index 00000000..904b6a2b --- /dev/null +++ b/Source/LibationAvalonia/AvaloniaUI/Assets/DataGridTheme.xaml @@ -0,0 +1,658 @@ + + + 0.6 + 0.8 + 12,0,12,0 + + M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z + M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z + M1939 1581l90 -90l-1005 -1005l-1005 1005l90 90l915 -915z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/LibationStyles.xaml b/Source/LibationAvalonia/AvaloniaUI/Assets/LibationStyles.xaml new file mode 100644 index 00000000..e1e3f5ce --- /dev/null +++ b/Source/LibationAvalonia/AvaloniaUI/Assets/LibationStyles.xaml @@ -0,0 +1,12 @@ + + + #FFE6FFE6 + + + + + + + + + \ No newline at end of file diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/MBIcons/Asterisk.png b/Source/LibationAvalonia/AvaloniaUI/Assets/MBIcons/Asterisk.png new file mode 100644 index 00000000..c345a8f9 Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/MBIcons/Asterisk.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/MBIcons/Exclamation.png b/Source/LibationAvalonia/AvaloniaUI/Assets/MBIcons/Exclamation.png new file mode 100644 index 00000000..cc884984 Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/MBIcons/Exclamation.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/MBIcons/Question.png b/Source/LibationAvalonia/AvaloniaUI/Assets/MBIcons/Question.png new file mode 100644 index 00000000..3aeb017c Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/MBIcons/Question.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/MBIcons/error.png b/Source/LibationAvalonia/AvaloniaUI/Assets/MBIcons/error.png new file mode 100644 index 00000000..916e14f1 Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/MBIcons/error.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/SEGOEUI.TTF b/Source/LibationAvalonia/AvaloniaUI/Assets/SEGOEUI.TTF new file mode 100644 index 00000000..0f52cbd9 Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/SEGOEUI.TTF differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/WINGDING.TTF b/Source/LibationAvalonia/AvaloniaUI/Assets/WINGDING.TTF new file mode 100644 index 00000000..6e38f7fd Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/WINGDING.TTF differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/cancel.png b/Source/LibationAvalonia/AvaloniaUI/Assets/cancel.png new file mode 100644 index 00000000..fa34f935 Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/cancel.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/completed.png b/Source/LibationAvalonia/AvaloniaUI/Assets/completed.png new file mode 100644 index 00000000..3cd61981 Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/completed.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/down.png b/Source/LibationAvalonia/AvaloniaUI/Assets/down.png new file mode 100644 index 00000000..2536c961 Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/down.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/download-arrow.png b/Source/LibationAvalonia/AvaloniaUI/Assets/download-arrow.png new file mode 100644 index 00000000..16617998 Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/download-arrow.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/edit-tags-25x25.png b/Source/LibationAvalonia/AvaloniaUI/Assets/edit-tags-25x25.png new file mode 100644 index 00000000..82b24209 Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/edit-tags-25x25.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/edit-tags-50x50.png b/Source/LibationAvalonia/AvaloniaUI/Assets/edit-tags-50x50.png new file mode 100644 index 00000000..7b0043ac Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/edit-tags-50x50.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/edit_25x25.png b/Source/LibationAvalonia/AvaloniaUI/Assets/edit_25x25.png new file mode 100644 index 00000000..12e70d0f Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/edit_25x25.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/edit_64x64.png b/Source/LibationAvalonia/AvaloniaUI/Assets/edit_64x64.png new file mode 100644 index 00000000..1d9e5f83 Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/edit_64x64.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/error.png b/Source/LibationAvalonia/AvaloniaUI/Assets/error.png new file mode 100644 index 00000000..700ce41e Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/error.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/errored.png b/Source/LibationAvalonia/AvaloniaUI/Assets/errored.png new file mode 100644 index 00000000..bb8ba7ef Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/errored.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/first.png b/Source/LibationAvalonia/AvaloniaUI/Assets/first.png new file mode 100644 index 00000000..e470c697 Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/first.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/glass-with-glow_16.png b/Source/LibationAvalonia/AvaloniaUI/Assets/glass-with-glow_16.png new file mode 100644 index 00000000..05e40bec Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/glass-with-glow_16.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/img-coverart-prod-unavailable_300x300.jpg b/Source/LibationAvalonia/AvaloniaUI/Assets/img-coverart-prod-unavailable_300x300.jpg new file mode 100644 index 00000000..9b3d864f Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/img-coverart-prod-unavailable_300x300.jpg differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/img-coverart-prod-unavailable_500x500.jpg b/Source/LibationAvalonia/AvaloniaUI/Assets/img-coverart-prod-unavailable_500x500.jpg new file mode 100644 index 00000000..16486568 Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/img-coverart-prod-unavailable_500x500.jpg differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/img-coverart-prod-unavailable_80x80.jpg b/Source/LibationAvalonia/AvaloniaUI/Assets/img-coverart-prod-unavailable_80x80.jpg new file mode 100644 index 00000000..7c49cd3e Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/img-coverart-prod-unavailable_80x80.jpg differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/import_16x16.png b/Source/LibationAvalonia/AvaloniaUI/Assets/import_16x16.png new file mode 100644 index 00000000..40b582b1 Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/import_16x16.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/last.png b/Source/LibationAvalonia/AvaloniaUI/Assets/last.png new file mode 100644 index 00000000..3c3ea886 Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/last.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/libation.ico b/Source/LibationAvalonia/AvaloniaUI/Assets/libation.ico new file mode 100644 index 00000000..d3e00443 Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/libation.ico differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_green.png b/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_green.png new file mode 100644 index 00000000..86171e0c Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_green.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_green_pdf_no.png b/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_green_pdf_no.png new file mode 100644 index 00000000..a128c088 Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_green_pdf_no.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_green_pdf_yes.png b/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_green_pdf_yes.png new file mode 100644 index 00000000..baac0151 Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_green_pdf_yes.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_red.png b/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_red.png new file mode 100644 index 00000000..8e4b34e4 Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_red.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_red_pdf_no.png b/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_red_pdf_no.png new file mode 100644 index 00000000..6506603c Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_red_pdf_no.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_red_pdf_yes.png b/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_red_pdf_yes.png new file mode 100644 index 00000000..0d5b5eb6 Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_red_pdf_yes.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_yellow.png b/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_yellow.png new file mode 100644 index 00000000..8b3e8aab Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_yellow.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_yellow_pdf_no.png b/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_yellow_pdf_no.png new file mode 100644 index 00000000..2bddcffd Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_yellow_pdf_no.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_yellow_pdf_yes.png b/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_yellow_pdf_yes.png new file mode 100644 index 00000000..b51a28ad Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/liberate_yellow_pdf_yes.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/minus.png b/Source/LibationAvalonia/AvaloniaUI/Assets/minus.png new file mode 100644 index 00000000..c0c5d15c Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/minus.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/plus.png b/Source/LibationAvalonia/AvaloniaUI/Assets/plus.png new file mode 100644 index 00000000..1cd1c630 Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/plus.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/queued.png b/Source/LibationAvalonia/AvaloniaUI/Assets/queued.png new file mode 100644 index 00000000..f30221c3 Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/queued.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/Assets/up.png b/Source/LibationAvalonia/AvaloniaUI/Assets/up.png new file mode 100644 index 00000000..7c00155a Binary files /dev/null and b/Source/LibationAvalonia/AvaloniaUI/Assets/up.png differ diff --git a/Source/LibationAvalonia/AvaloniaUI/AvaloniaUtils.cs b/Source/LibationAvalonia/AvaloniaUI/AvaloniaUtils.cs new file mode 100644 index 00000000..2117f8bb --- /dev/null +++ b/Source/LibationAvalonia/AvaloniaUI/AvaloniaUtils.cs @@ -0,0 +1,28 @@ +using Avalonia.Media; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace LibationAvalonia.AvaloniaUI +{ + internal static class AvaloniaUtils + { + public static IBrush GetBrushFromResources(string name) + => GetBrushFromResources(name, Brushes.Transparent); + public static IBrush GetBrushFromResources(string name, IBrush defaultBrush) + { + if (App.Current.Styles.TryGetResource(name, out var value) && value is IBrush brush) + return brush; + return defaultBrush; + } + + public static T ShowDialogSynchronously(this Avalonia.Controls.Window window, Avalonia.Controls.Window owner) + { + using var source = new CancellationTokenSource(); + var dialogTask = window.ShowDialog(owner); + dialogTask.ContinueWith(t => source.Cancel(), TaskScheduler.FromCurrentSynchronizationContext()); + Avalonia.Threading.Dispatcher.UIThread.MainLoop(source.Token); + return dialogTask.Result; + } + } +} diff --git a/Source/LibationAvalonia/AvaloniaUI/Controls/DataGridCheckBoxColumnExt.axaml b/Source/LibationAvalonia/AvaloniaUI/Controls/DataGridCheckBoxColumnExt.axaml new file mode 100644 index 00000000..b5eb1864 --- /dev/null +++ b/Source/LibationAvalonia/AvaloniaUI/Controls/DataGridCheckBoxColumnExt.axaml @@ -0,0 +1,5 @@ + + + diff --git a/Source/LibationAvalonia/AvaloniaUI/Controls/DataGridCheckBoxColumnExt.axaml.cs b/Source/LibationAvalonia/AvaloniaUI/Controls/DataGridCheckBoxColumnExt.axaml.cs new file mode 100644 index 00000000..cb94668f --- /dev/null +++ b/Source/LibationAvalonia/AvaloniaUI/Controls/DataGridCheckBoxColumnExt.axaml.cs @@ -0,0 +1,17 @@ +using Avalonia.Controls; +using LibationAvalonia.AvaloniaUI.ViewModels; +using System; + +namespace LibationAvalonia.AvaloniaUI.Controls +{ + public partial class DataGridCheckBoxColumnExt : DataGridCheckBoxColumn + { + protected override IControl GenerateEditingElementDirect(DataGridCell cell, object dataItem) + { + //Only SeriesEntry types have three-state checks, individual LibraryEntry books are binary. + var ele = base.GenerateEditingElementDirect(cell, dataItem) as CheckBox; + ele.IsThreeState = dataItem is SeriesEntry; + return ele; + } + } +} diff --git a/Source/LibationAvalonia/AvaloniaUI/Controls/DirectoryOrCustomSelectControl.axaml b/Source/LibationAvalonia/AvaloniaUI/Controls/DirectoryOrCustomSelectControl.axaml new file mode 100644 index 00000000..5eefc960 --- /dev/null +++ b/Source/LibationAvalonia/AvaloniaUI/Controls/DirectoryOrCustomSelectControl.axaml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + diff --git a/Source/LibationAvalonia/AvaloniaUI/Views/Dialogs/MessageBoxWindow.axaml.cs b/Source/LibationAvalonia/AvaloniaUI/Views/Dialogs/MessageBoxWindow.axaml.cs new file mode 100644 index 00000000..96e40b08 --- /dev/null +++ b/Source/LibationAvalonia/AvaloniaUI/Views/Dialogs/MessageBoxWindow.axaml.cs @@ -0,0 +1,71 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using LibationAvalonia.AvaloniaUI.ViewModels.Dialogs; + +namespace LibationAvalonia.AvaloniaUI.Views.Dialogs +{ + + public partial class MessageBoxWindow : DialogWindow + { + public MessageBoxWindow() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + protected override void CancelAndClose() => Close(DialogResult.None); + + protected override void SaveAndClose() { } + + public DialogResult DialogResult { get; private set; } + + public void Button1_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args) + { + var vm = DataContext as MessageBoxViewModel; + DialogResult = vm.Buttons switch + { + MessageBoxButtons.OK => DialogResult.OK, + MessageBoxButtons.OKCancel => DialogResult.OK, + MessageBoxButtons.AbortRetryIgnore => DialogResult.Abort, + MessageBoxButtons.YesNoCancel => DialogResult.Yes, + MessageBoxButtons.YesNo => DialogResult.Yes, + MessageBoxButtons.RetryCancel => DialogResult.Retry, + MessageBoxButtons.CancelTryContinue => DialogResult.Cancel, + _ => DialogResult.None + }; + Close(DialogResult); + } + public void Button2_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args) + { + var vm = DataContext as MessageBoxViewModel; + DialogResult = vm.Buttons switch + { + MessageBoxButtons.OKCancel => DialogResult.Cancel, + MessageBoxButtons.AbortRetryIgnore => DialogResult.Retry, + MessageBoxButtons.YesNoCancel => DialogResult.No, + MessageBoxButtons.YesNo => DialogResult.No, + MessageBoxButtons.RetryCancel => DialogResult.Cancel, + MessageBoxButtons.CancelTryContinue => DialogResult.TryAgain, + _ => DialogResult.None + }; + Close(DialogResult); + } + public void Button3_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args) + { + var vm = DataContext as MessageBoxViewModel; + DialogResult = vm.Buttons switch + { + MessageBoxButtons.AbortRetryIgnore => DialogResult.Ignore, + MessageBoxButtons.YesNoCancel => DialogResult.Cancel, + MessageBoxButtons.CancelTryContinue => DialogResult.Continue, + _ => DialogResult.None + }; + Close(DialogResult); + } + } +} diff --git a/Source/LibationAvalonia/AvaloniaUI/Views/Dialogs/ScanAccountsDialog.axaml b/Source/LibationAvalonia/AvaloniaUI/Views/Dialogs/ScanAccountsDialog.axaml new file mode 100644 index 00000000..73534ca1 --- /dev/null +++ b/Source/LibationAvalonia/AvaloniaUI/Views/Dialogs/ScanAccountsDialog.axaml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/LibationAvalonia/AvaloniaUI/Views/ProcessBookControl.axaml.cs b/Source/LibationAvalonia/AvaloniaUI/Views/ProcessBookControl.axaml.cs new file mode 100644 index 00000000..28794e5c --- /dev/null +++ b/Source/LibationAvalonia/AvaloniaUI/Views/ProcessBookControl.axaml.cs @@ -0,0 +1,50 @@ +using Avalonia; +using System; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using LibationAvalonia.AvaloniaUI.ViewModels; +using ApplicationServices; +using DataLayer; + +namespace LibationAvalonia.AvaloniaUI.Views +{ + public delegate void QueueItemPositionButtonClicked(ProcessBookViewModel item, QueuePosition queueButton); + public delegate void QueueItemCancelButtonClicked(ProcessBookViewModel item); + public partial class ProcessBookControl : UserControl + { + public static event QueueItemPositionButtonClicked PositionButtonClicked; + public static event QueueItemCancelButtonClicked CancelButtonClicked; + public ProcessBookControl() + { + InitializeComponent(); + + if (Design.IsDesignMode) + { + using var context = DbContexts.GetContext(); + DataContext = new ProcessBookViewModel( + context.GetLibraryBook_Flat_NoTracking("B017V4IM1G"), + LogMe.RegisterForm(default(ILogForm)) + ); + return; + } + } + + private ProcessBookViewModel DataItem => DataContext is null ? null : DataContext as ProcessBookViewModel; + + public void Cancel_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) + => CancelButtonClicked?.Invoke(DataItem); + public void MoveFirst_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) + => PositionButtonClicked?.Invoke(DataItem, QueuePosition.Fisrt); + public void MoveUp_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) + => PositionButtonClicked?.Invoke(DataItem, QueuePosition.OneUp); + public void MoveDown_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) + => PositionButtonClicked?.Invoke(DataItem, QueuePosition.OneDown); + public void MoveLast_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) + => PositionButtonClicked?.Invoke(DataItem, QueuePosition.Last); + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/Source/LibationAvalonia/AvaloniaUI/Views/ProcessQueueControl.axaml b/Source/LibationAvalonia/AvaloniaUI/Views/ProcessQueueControl.axaml new file mode 100644 index 00000000..a515c97f --- /dev/null +++ b/Source/LibationAvalonia/AvaloniaUI/Views/ProcessQueueControl.axaml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + Process Queue + + + + + + + + + + + + + + + + + Queue Log + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/LibationAvalonia/AvaloniaUI/Views/ProcessQueueControl.axaml.cs b/Source/LibationAvalonia/AvaloniaUI/Views/ProcessQueueControl.axaml.cs new file mode 100644 index 00000000..51634ee9 --- /dev/null +++ b/Source/LibationAvalonia/AvaloniaUI/Views/ProcessQueueControl.axaml.cs @@ -0,0 +1,151 @@ +using ApplicationServices; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using DataLayer; +using LibationAvalonia.AvaloniaUI.ViewModels; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace LibationAvalonia.AvaloniaUI.Views +{ + public partial class ProcessQueueControl : UserControl + { + private TrackedQueue Queue => _viewModel.Items; + private ProcessQueueViewModel _viewModel => DataContext as ProcessQueueViewModel; + + public ProcessQueueControl() + { + InitializeComponent(); + + ProcessBookControl.PositionButtonClicked += ProcessBookControl2_ButtonClicked; + ProcessBookControl.CancelButtonClicked += ProcessBookControl2_CancelButtonClicked; + + #region Design Mode Testing + if (Design.IsDesignMode) + { + var vm = new ProcessQueueViewModel(); + var Logger = LogMe.RegisterForm(vm); + DataContext = vm; + using var context = DbContexts.GetContext(); + List testList = new() + { + new ProcessBookViewModel(context.GetLibraryBook_Flat_NoTracking("B017V4IM1G"), Logger) + { + Result = ProcessBookResult.FailedAbort, + Status = ProcessBookStatus.Failed, + }, + new ProcessBookViewModel(context.GetLibraryBook_Flat_NoTracking("B017V4IWVG"), Logger) + { + Result = ProcessBookResult.FailedSkip, + Status = ProcessBookStatus.Failed, + }, + new ProcessBookViewModel(context.GetLibraryBook_Flat_NoTracking("B017V4JA2Q"), Logger) + { + Result = ProcessBookResult.FailedRetry, + Status = ProcessBookStatus.Failed, + }, + new ProcessBookViewModel(context.GetLibraryBook_Flat_NoTracking("B017V4NUPO"), Logger) + { + Result = ProcessBookResult.ValidationFail, + Status = ProcessBookStatus.Failed, + }, + new ProcessBookViewModel(context.GetLibraryBook_Flat_NoTracking("B017V4NMX4"), Logger) + { + Result = ProcessBookResult.Cancelled, + Status = ProcessBookStatus.Cancelled, + }, + new ProcessBookViewModel(context.GetLibraryBook_Flat_NoTracking("B017V4NOZ0"), Logger) + { + Result = ProcessBookResult.Success, + Status = ProcessBookStatus.Completed, + }, + new ProcessBookViewModel(context.GetLibraryBook_Flat_NoTracking("B017WJ5ZK6"), Logger) + { + Result = ProcessBookResult.None, + Status = ProcessBookStatus.Working, + }, + new ProcessBookViewModel(context.GetLibraryBook_Flat_NoTracking("B017V4IM1G"), Logger) + { + Result = ProcessBookResult.None, + Status = ProcessBookStatus.Queued, + }, + }; + + vm.Items.Enqueue(testList); + vm.Items.MoveNext(); + vm.Items.MoveNext(); + vm.Items.MoveNext(); + vm.Items.MoveNext(); + vm.Items.MoveNext(); + vm.Items.MoveNext(); + vm.Items.MoveNext(); + return; + } + #endregion + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + #region Control event handlers + + private async void ProcessBookControl2_CancelButtonClicked(ProcessBookViewModel item) + { + if (item is not null) + await item.CancelAsync(); + Queue.RemoveQueued(item); + } + + private void ProcessBookControl2_ButtonClicked(ProcessBookViewModel item, QueuePosition queueButton) + { + Queue.MoveQueuePosition(item, queueButton); + } + + public async void CancelAllBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) + { + Queue.ClearQueue(); + if (Queue.Current is not null) + await Queue.Current.CancelAsync(); + } + + public void ClearFinishedBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) + { + Queue.ClearCompleted(); + + if (!_viewModel.Running) + _viewModel.RunningTime = string.Empty; + } + + public void ClearLogBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) + { + _viewModel.LogEntries.Clear(); + } + + private async void LogCopyBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) + { + string logText = string.Join("\r\n", _viewModel.LogEntries.Select(r => $"{r.LogDate.ToShortDateString()} {r.LogDate.ToShortTimeString()}\t{r.LogMessage}")); + await Application.Current.Clipboard.SetTextAsync(logText); + } + + private async void cancelAllBtn_Click(object sender, EventArgs e) + { + Queue.ClearQueue(); + if (Queue.Current is not null) + await Queue.Current.CancelAsync(); + } + + private void btnClearFinished_Click(object sender, EventArgs e) + { + Queue.ClearCompleted(); + + if (!_viewModel.Running) + _viewModel.RunningTime = string.Empty; + } + + #endregion + } +} diff --git a/Source/LibationAvalonia/AvaloniaUI/Views/ProductsDisplay.axaml b/Source/LibationAvalonia/AvaloniaUI/Views/ProductsDisplay.axaml new file mode 100644 index 00000000..767f1b73 --- /dev/null +++ b/Source/LibationAvalonia/AvaloniaUI/Views/ProductsDisplay.axaml @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/LibationAvalonia/AvaloniaUI/Views/ProductsDisplay.axaml.cs b/Source/LibationAvalonia/AvaloniaUI/Views/ProductsDisplay.axaml.cs new file mode 100644 index 00000000..57c2dfe1 --- /dev/null +++ b/Source/LibationAvalonia/AvaloniaUI/Views/ProductsDisplay.axaml.cs @@ -0,0 +1,300 @@ +using ApplicationServices; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using DataLayer; +using FileLiberator; +using LibationFileManager; +using LibationAvalonia.AvaloniaUI.ViewModels; +using LibationAvalonia.AvaloniaUI.Views.Dialogs; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace LibationAvalonia.AvaloniaUI.Views +{ + public partial class ProductsDisplay : UserControl + { + public event EventHandler LiberateClicked; + + private ProductsDisplayViewModel _viewModel => DataContext as ProductsDisplayViewModel; + ImageDisplayDialog imageDisplayDialog; + + public ProductsDisplay() + { + InitializeComponent(); + + if (Design.IsDesignMode) + { + using var context = DbContexts.GetContext(); + List sampleEntries = new() + { + new LibraryBookEntry(context.GetLibraryBook_Flat_NoTracking("B017V4IM1G")), + new LibraryBookEntry(context.GetLibraryBook_Flat_NoTracking("B017V4IWVG")), + new LibraryBookEntry(context.GetLibraryBook_Flat_NoTracking("B017V4JA2Q")), + new LibraryBookEntry(context.GetLibraryBook_Flat_NoTracking("B017V4NUPO")), + new LibraryBookEntry(context.GetLibraryBook_Flat_NoTracking("B017V4NMX4")), + new LibraryBookEntry(context.GetLibraryBook_Flat_NoTracking("B017V4NOZ0")), + new LibraryBookEntry(context.GetLibraryBook_Flat_NoTracking("B017WJ5ZK6")), + }; + DataContext = new ProductsDisplayViewModel(sampleEntries); + return; + } + + Configure_ColumnCustomization(); + foreach (var column in productsGrid.Columns) + { + column.CustomSortComparer = new RowComparer(column); + } + } + + private void ProductsGrid_Sorting(object sender, DataGridColumnEventArgs e) + { + _viewModel.Sort(e.Column); + } + + private void RemoveColumn_PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) + { + if (sender is DataGridColumn col && e.Property.Name == nameof(DataGridColumn.IsVisible)) + { + col.DisplayIndex = 0; + col.CanUserReorder = false; + } + } + + public void DataGrid_CopyToClipboard(object sender, DataGridRowClipboardEventArgs e) + { + + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + + productsGrid = this.FindControl(nameof(productsGrid)); + } + + #region Column Customizations + + private void Configure_ColumnCustomization() + { + if (Design.IsDesignMode) return; + + productsGrid.ColumnDisplayIndexChanged += ProductsGrid_ColumnDisplayIndexChanged; + + var config = Configuration.Instance; + var gridColumnsVisibilities = config.GridColumnsVisibilities; + var displayIndices = config.GridColumnsDisplayIndices; + + var contextMenu = new ContextMenu(); + contextMenu.MenuClosed += ContextMenu_MenuClosed; + contextMenu.ContextMenuOpening += ContextMenu_ContextMenuOpening; + List menuItems = new(); + contextMenu.Items = menuItems; + + menuItems.Add(new MenuItem { Header = "Show / Hide Columns" }); + menuItems.Add(new MenuItem { Header = "-" }); + + var HeaderCell_PI = typeof(DataGridColumn).GetProperty("HeaderCell", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + + foreach (var column in productsGrid.Columns) + { + var itemName = column.SortMemberPath; + + if (itemName == nameof(GridEntry.Remove)) + continue; + + menuItems.Add + ( + new MenuItem + { + Header = ((string)column.Header).Replace((char)0xa, ' '), + Tag = column, + Margin = new Thickness(6, 0), + Icon = new CheckBox + { + Width = 50, + } + } + ); + + var headercell = HeaderCell_PI.GetValue(column) as DataGridColumnHeader; + headercell.ContextMenu = contextMenu; + + column.IsVisible = gridColumnsVisibilities.GetValueOrDefault(itemName, true); + } + + //We must set DisplayIndex properties in ascending order + foreach (var itemName in displayIndices.OrderBy(i => i.Value).Select(i => i.Key)) + { + if (!productsGrid.Columns.Any(c => c.SortMemberPath == itemName)) + continue; + + var column = productsGrid.Columns + .Single(c => c.SortMemberPath == itemName); + + column.DisplayIndex = displayIndices.GetValueOrDefault(itemName, productsGrid.Columns.IndexOf(column)); + } + } + + private void ContextMenu_ContextMenuOpening(object sender, System.ComponentModel.CancelEventArgs e) + { + var contextMenu = sender as ContextMenu; + foreach (var mi in contextMenu.Items.OfType()) + { + if (mi.Tag is DataGridColumn column) + { + var cbox = mi.Icon as CheckBox; + cbox.IsChecked = column.IsVisible; + } + } + } + + private void ContextMenu_MenuClosed(object sender, Avalonia.Interactivity.RoutedEventArgs e) + { + var contextMenu = sender as ContextMenu; + var config = Configuration.Instance; + var dictionary = config.GridColumnsVisibilities; + + foreach (var mi in contextMenu.Items.OfType()) + { + if (mi.Tag is DataGridColumn column) + { + var cbox = mi.Icon as CheckBox; + column.IsVisible = cbox.IsChecked == true; + dictionary[column.SortMemberPath] = cbox.IsChecked == true; + } + } + + //If all columns are hidden, register the context menu on the grid so users can unhide. + if (!productsGrid.Columns.Any(c => c.IsVisible)) + productsGrid.ContextMenu = contextMenu; + else + productsGrid.ContextMenu = null; + + config.GridColumnsVisibilities = dictionary; + } + + private void ProductsGrid_ColumnDisplayIndexChanged(object sender, DataGridColumnEventArgs e) + { + var config = Configuration.Instance; + + var dictionary = config.GridColumnsDisplayIndices; + dictionary[e.Column.SortMemberPath] = e.Column.DisplayIndex; + config.GridColumnsDisplayIndices = dictionary; + } + + #endregion + + #region Button Click Handlers + + public void LiberateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args) + { + var button = args.Source as Button; + + if (button.DataContext is SeriesEntry sEntry) + { + _viewModel.ToggleSeriesExpanded(sEntry); + + //Expanding and collapsing reset the list, which will cause focus to shift + //to the topright cell. Reset focus onto the clicked button's cell. + ((sender as Control).Parent.Parent as DataGridCell)?.Focus(); + } + else if (button.DataContext is LibraryBookEntry lbEntry) + { + LiberateClicked?.Invoke(this, lbEntry.LibraryBook); + } + } + + public void CloseImageDisplay() + { + if (imageDisplayDialog is not null && imageDisplayDialog.IsVisible) + imageDisplayDialog.Close(); + } + + public void Cover_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args) + { + if (sender is not Image tblock || tblock.DataContext is not GridEntry gEntry) + return; + + + if (imageDisplayDialog is null || !imageDisplayDialog.IsVisible) + { + imageDisplayDialog = new ImageDisplayDialog(); + } + + var picDef = new PictureDefinition(gEntry.LibraryBook.Book.PictureLarge ?? gEntry.LibraryBook.Book.PictureId, PictureSize.Native); + + void PictureCached(object sender, PictureCachedEventArgs e) + { + if (e.Definition.PictureId == picDef.PictureId) + imageDisplayDialog.CoverBytes = e.Picture; + + PictureStorage.PictureCached -= PictureCached; + } + + PictureStorage.PictureCached += PictureCached; + (bool isDefault, byte[] initialImageBts) = PictureStorage.GetPicture(picDef); + + + var windowTitle = $"{gEntry.Title} - Cover"; + + + imageDisplayDialog.BookSaveDirectory = AudibleFileStorage.Audio.GetDestinationDirectory(gEntry.LibraryBook); + imageDisplayDialog.PictureFileName = System.IO.Path.GetFileName(AudibleFileStorage.Audio.GetBooksDirectoryFilename(gEntry.LibraryBook, ".jpg")); + imageDisplayDialog.Title = windowTitle; + imageDisplayDialog.CoverBytes = initialImageBts; + + if (!isDefault) + PictureStorage.PictureCached -= PictureCached; + + if (!imageDisplayDialog.IsVisible) + imageDisplayDialog.Show(); + } + + public void Description_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args) + { + if (sender is TextBlock tblock && tblock.DataContext is GridEntry gEntry) + { + var pt = tblock.Parent.PointToScreen(tblock.Parent.Bounds.TopRight); + var displayWindow = new DescriptionDisplayDialog + { + SpawnLocation = new Point(pt.X, pt.Y), + DescriptionText = gEntry.LongDescription, + }; + + void CloseWindow(object o, DataGridRowEventArgs e) + { + displayWindow.Close(); + } + productsGrid.LoadingRow += CloseWindow; + displayWindow.Closing += (_, _) => + { + productsGrid.LoadingRow -= CloseWindow; + }; + + displayWindow.Show(); + } + } + + BookDetailsDialog bookDetailsForm; + + public void OnTagsButtonClick(object sender, Avalonia.Interactivity.RoutedEventArgs args) + { + var button = args.Source as Button; + + if (button.DataContext is LibraryBookEntry lbEntry && VisualRoot is Window window) + { + if (bookDetailsForm is null || !bookDetailsForm.IsVisible) + { + bookDetailsForm = new BookDetailsDialog(lbEntry.LibraryBook); + bookDetailsForm.Show(window); + } + else + bookDetailsForm.LibraryBook = lbEntry.LibraryBook; + } + } + + #endregion + } +} diff --git a/Source/LibationAvalonia/LibationAvalonia.csproj b/Source/LibationAvalonia/LibationAvalonia.csproj new file mode 100644 index 00000000..3ed812e0 --- /dev/null +++ b/Source/LibationAvalonia/LibationAvalonia.csproj @@ -0,0 +1,129 @@ + + + + + Exe + net6.0 + + copyused + true + libation.ico + Libation + + true + false + false + + + + + ..\bin\Debug + embedded + + + + ..\bin\Release + embedded + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ProcessBookControl.axaml + + + ProcessQueueControl.axaml + + + ProductsDisplay.axaml + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/LibationAvalonia/ObjectComparer[T].cs b/Source/LibationAvalonia/ObjectComparer[T].cs new file mode 100644 index 00000000..5e3b23bb --- /dev/null +++ b/Source/LibationAvalonia/ObjectComparer[T].cs @@ -0,0 +1,10 @@ +using System; +using System.Collections; + +namespace LibationAvalonia +{ + internal class ObjectComparer : IComparer where T : IComparable + { + public int Compare(object x, object y) => ((T)x).CompareTo(y); + } +} diff --git a/Source/LibationAvalonia/Program.cs b/Source/LibationAvalonia/Program.cs new file mode 100644 index 00000000..541e40c6 --- /dev/null +++ b/Source/LibationAvalonia/Program.cs @@ -0,0 +1,88 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using ApplicationServices; +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using Avalonia.ReactiveUI; +using LibationFileManager; + +namespace LibationAvalonia +{ + static class Program + { + static void Main() + { + var sw = System.Diagnostics.Stopwatch.StartNew(); + var config = LoadLibationConfig(); + + if (config is null) return; + + //Start as much work in parallel as possible. + var runDbMigrationsTask = Task.Run(() => RunDbMigrations(config)); + var classicLifetimeTask = Task.Run(() => new ClassicDesktopStyleApplicationLifetime()); + var appBuilderTask = Task.Run(BuildAvaloniaApp); + + if (!runDbMigrationsTask.GetAwaiter().GetResult()) + return; + + var dbLibraryTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true)); + + appBuilderTask.GetAwaiter().GetResult().SetupWithLifetime(classicLifetimeTask.GetAwaiter().GetResult()); + + var form1 = (AvaloniaUI.Views.MainWindow)classicLifetimeTask.Result.MainWindow; + + form1.OnLibraryLoaded(dbLibraryTask.GetAwaiter().GetResult()); + + var assets = AvaloniaLocator.Current.GetService(); + + classicLifetimeTask.Result.Start(null); + } + + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .LogToTrace() + .UseReactiveUI(); + + private static Configuration LoadLibationConfig() + { + try + { + //***********************************************// + // // + // do not use Configuration before this line // + // // + //***********************************************// + // Migrations which must occur before configuration is loaded for the first time. Usually ones which alter the Configuration + var config = AppScaffolding.LibationScaffolding.RunPreConfigMigrations(); + AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists(); + return config; + } + catch (Exception ex) + { + + return null; + } + } + + private static bool RunDbMigrations(Configuration config) + { + try + { + // most migrations go in here + AppScaffolding.LibationScaffolding.RunPostConfigMigrations(config); + AppScaffolding.LibationScaffolding.RunPostMigrationScaffolding(config); + + return true; + } + catch (Exception ex) + { + return false; + } + } + } +} diff --git a/Source/LibationAvalonia/libation.ico b/Source/LibationAvalonia/libation.ico new file mode 100644 index 00000000..d3e00443 Binary files /dev/null and b/Source/LibationAvalonia/libation.ico differ diff --git a/Source/LibationCli/LibationCli.csproj b/Source/LibationCli/LibationCli.csproj index 035b0aa6..9aa6ab5f 100644 --- a/Source/LibationCli/LibationCli.csproj +++ b/Source/LibationCli/LibationCli.csproj @@ -3,7 +3,7 @@ Exe - net6.0-windows + net6.0 true win-x64 false @@ -46,6 +46,7 @@ + diff --git a/Source/LibationFileManager/Configuration.cs b/Source/LibationFileManager/Configuration.cs index cf4d0a3b..a86343c0 100644 --- a/Source/LibationFileManager/Configuration.cs +++ b/Source/LibationFileManager/Configuration.cs @@ -451,7 +451,7 @@ namespace LibationFileManager #endregion #region LibationFiles - private static string APPSETTINGS_JSON { get; } = Path.Combine(Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName), "appsettings.json"); + private static string APPSETTINGS_JSON { get; } = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "appsettings.json"); private const string LIBATION_FILES_KEY = "LibationFiles"; [Description("Location for storage of program-created files")] diff --git a/Source/LibationFileManager/LibationFileManager.csproj b/Source/LibationFileManager/LibationFileManager.csproj index 808f9029..cc29e0b3 100644 --- a/Source/LibationFileManager/LibationFileManager.csproj +++ b/Source/LibationFileManager/LibationFileManager.csproj @@ -1,7 +1,7 @@  - net6.0-windows + net6.0 diff --git a/Source/LibationSearchEngine/LibationSearchEngine.csproj b/Source/LibationSearchEngine/LibationSearchEngine.csproj index 5e811683..5ad8dd69 100644 --- a/Source/LibationSearchEngine/LibationSearchEngine.csproj +++ b/Source/LibationSearchEngine/LibationSearchEngine.csproj @@ -1,7 +1,7 @@  - net6.0-windows + net6.0 diff --git a/Source/LibationWinForms/AvaloniaUI/Views/Dialogs/EditReplacementChars.axaml b/Source/LibationWinForms/AvaloniaUI/Views/Dialogs/EditReplacementChars.axaml index 119e3aaf..5ef059da 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/Dialogs/EditReplacementChars.axaml +++ b/Source/LibationWinForms/AvaloniaUI/Views/Dialogs/EditReplacementChars.axaml @@ -20,10 +20,8 @@ Height="18" Margin="10,0,10,0" VerticalAlignment="Center" - FontFamily="avares://Libation/AvaloniaUI/Assets/SEGOEUI.TTF" - FontStyle="Normal" - FontWeight="Normal" - Text="{Binding CharacterToReplace}" /> + FontFamily="SEGOEUI_Local" + Text="{Binding Replacement.CharacterToReplace}" /> @@ -31,15 +29,28 @@ - + + + + + diff --git a/Source/LibationWinForms/AvaloniaUI/Views/Dialogs/EditReplacementChars.axaml.cs b/Source/LibationWinForms/AvaloniaUI/Views/Dialogs/EditReplacementChars.axaml.cs index b3f359b9..aa27d502 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/Dialogs/EditReplacementChars.axaml.cs +++ b/Source/LibationWinForms/AvaloniaUI/Views/Dialogs/EditReplacementChars.axaml.cs @@ -5,13 +5,15 @@ using FileManager; using LibationFileManager; using System.Collections.Generic; using System.Collections.ObjectModel; +using ReactiveUI; +using System.Linq; namespace LibationWinForms.AvaloniaUI.Views.Dialogs { public partial class EditReplacementChars : DialogWindow { Configuration config = Configuration.Instance; - public ObservableCollection replacements { get; } + public ObservableCollection replacements { get; } public EditReplacementChars() { InitializeComponent(); @@ -19,13 +21,23 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs if (Design.IsDesignMode) AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists(); - replacements = new(config.ReplacementCharacters.Replacements); + replacements = new(config.ReplacementCharacters.Replacements.Select(r => new ReplacementsExt { Replacement = r })); DataContext = this; } - public void Tb_GotFocus(object sender, Avalonia.Input.GotFocusEventArgs e) - { + public class ReplacementsExt : ViewModels.ViewModelBase + { + public Replacement Replacement { get; init; } + public string ReplacementText + { + get => Replacement.ReplacementString; + set + { + Replacement.ReplacementString = value; + this.RaisePropertyChanged(nameof(ReplacementText)); + } + } } private void InitializeComponent() diff --git a/Source/LibationWinForms/AvaloniaUI/Views/Dialogs/EditTemplateDialog.axaml.cs b/Source/LibationWinForms/AvaloniaUI/Views/Dialogs/EditTemplateDialog.axaml.cs index 354ace26..0f54dbee 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/Dialogs/EditTemplateDialog.axaml.cs +++ b/Source/LibationWinForms/AvaloniaUI/Views/Dialogs/EditTemplateDialog.axaml.cs @@ -43,9 +43,6 @@ namespace LibationWinForms.AvaloniaUI.Views.Dialogs public EditTemplateDialog() { InitializeComponent(); -#if DEBUG - this.AttachDevTools(); -#endif _viewModel = new(Configuration.Instance, this.Find(nameof(wrapPanel))); } diff --git a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.axaml.cs b/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.axaml.cs index 9f6bd583..747cd4a0 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.axaml.cs +++ b/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.axaml.cs @@ -65,9 +65,13 @@ namespace LibationWinForms.AvaloniaUI.Views productsDisplay?.CloseImageDisplay(); } - private void MainWindow_Opened(object sender, EventArgs e) + private async void MainWindow_Opened(object sender, EventArgs e) { + /* + var charReplace = new EditReplacementChars(); + await charReplace.ShowDialog(this); + */ } public void ProductsDisplay_Initialized1(object sender, EventArgs e) diff --git a/Source/LibationWinForms/LibationWinForms.csproj b/Source/LibationWinForms/LibationWinForms.csproj index f6bc7e6d..6b70a8d1 100644 --- a/Source/LibationWinForms/LibationWinForms.csproj +++ b/Source/LibationWinForms/LibationWinForms.csproj @@ -112,21 +112,6 @@ - - DescriptionDisplayDialog.axaml - - - EditQuickFilters.axaml - - - BookDetailsDialog.axaml - - - SearchSyntaxDialog.axaml - - - ImageDisplayDialog.axaml - ProcessBookControl.axaml