mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-01-01 02:18:06 -05:00
Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b76da0dbe | ||
|
|
0a749d2d88 | ||
|
|
4521c5d5ed | ||
|
|
eb39f994e1 | ||
|
|
c19833b34e | ||
|
|
6dcf456d06 | ||
|
|
8a87462cf5 | ||
|
|
9da2a44eff | ||
|
|
7af8d8aa70 | ||
|
|
4801f37e7c | ||
|
|
4f5df44d40 | ||
|
|
63e28b13c1 | ||
|
|
f92b2b65b2 | ||
|
|
f7a4a95e3b | ||
|
|
71b8e9e51c | ||
|
|
c6788ccb48 | ||
|
|
0503ee1404 | ||
|
|
303192c6c3 | ||
|
|
6e21e96aa2 | ||
|
|
d1d0a7e487 | ||
|
|
2fd8ea91e1 | ||
|
|
92ee0b2e6d | ||
|
|
f0b5ae1cdc | ||
|
|
eb659cc7d7 | ||
|
|
cdd5f229d3 | ||
|
|
29edfb7c3f | ||
|
|
c0cb454d45 | ||
|
|
970a77c9e9 | ||
|
|
3488c8e0f5 | ||
|
|
5133720cc8 | ||
|
|
4150746f45 | ||
|
|
3a95e1e72f | ||
|
|
c74e26e1af | ||
|
|
ae54c95d46 | ||
|
|
56e6bd164b | ||
|
|
9b28bdceaa | ||
|
|
b7f7d9004d | ||
|
|
1be0991e62 | ||
|
|
ff8a2e59c5 | ||
|
|
453904261b | ||
|
|
f9340db90a | ||
|
|
5cd329dd26 | ||
|
|
b2a882b79d | ||
|
|
75df78a2f7 | ||
|
|
3ad52cbecc | ||
|
|
27b2fe741c | ||
|
|
d19fe2250c | ||
|
|
d16d0c8de2 | ||
|
|
c213d5d9f6 | ||
|
|
c73a023572 | ||
|
|
67389917fd | ||
|
|
b3264d5f42 | ||
|
|
962d9b550f | ||
|
|
91ce7272ae | ||
|
|
2f64ca6856 | ||
|
|
cfe5db436c | ||
|
|
3653fc8094 | ||
|
|
662c0ec871 | ||
|
|
44d39eabdb | ||
|
|
a0550d5c97 | ||
|
|
14eaca6d45 | ||
|
|
93ccc206ef | ||
|
|
d3f0fd711e | ||
|
|
3f4604e877 | ||
|
|
c316709af8 | ||
|
|
221d5c7f1c | ||
|
|
5a86a1a27b | ||
|
|
dc5e55de68 | ||
|
|
ee37864a42 | ||
|
|
efe347667c | ||
|
|
f27a18bdbb |
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -45,9 +45,9 @@ jobs:
|
||||
|
||||
- name: Release
|
||||
id: release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
name: Libation v${{ needs.prerelease.outputs.version }}
|
||||
name: Libation ${{ needs.prerelease.outputs.version }}
|
||||
body: <Put a body here>
|
||||
draft: true
|
||||
prerelease: false
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Dockerfile
|
||||
FROM mcr.microsoft.com/dotnet/sdk:7.0 as build-env
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 as build-env
|
||||
|
||||
COPY Source /Source
|
||||
RUN dotnet publish -c Release -o /Source/bin/Publish/Linux-chardonnay /Source/LibationCli/LibationCli.csproj -p:PublishProfile=/Source/LibationCli/Properties/PublishProfiles/LinuxProfile.pubxml
|
||||
COPY Docker/liberate.sh /Source/bin/Publish/Linux-chardonnay
|
||||
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/runtime:7.0
|
||||
FROM mcr.microsoft.com/dotnet/runtime:8.0
|
||||
|
||||
ENV SLEEP_TIME "30m"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
|
||||
|
||||
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/MBucari?locale.x=en_us)
|
||||
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
|
||||
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
|
||||
|
||||
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/MBucari?locale.x=en_us)
|
||||
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
|
||||
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
|
||||
|
||||
|
||||
### Disclaimer
|
||||
|
||||
# Disclaimer
|
||||
The docker image is provided as-is. We hope it can be useful to you but it is not officially supported.
|
||||
|
||||
### Setup
|
||||
@@ -12,7 +13,15 @@ In order to use the docker image, you'll need to provide it with a copy of the `
|
||||
|
||||
In Settings.json, make the following changes:
|
||||
* Change `Books` to `/data`
|
||||
* Change `InProgress` to `/tmp`
|
||||
* Change `InProgress` to `/tmp` *
|
||||
|
||||
*You may have to paste the following at the end of your your Settings.json file if `InProgess` is not present:
|
||||
|
||||
```
|
||||
"InProgress": "/tmp"
|
||||
```
|
||||

|
||||
|
||||
|
||||
### Running
|
||||
Once the configuration files are copied and edited, the docker image can be run with the following command.
|
||||
|
||||
@@ -1,16 +1,38 @@
|
||||
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
|
||||
|
||||
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/MBucari?locale.x=en_us)
|
||||
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
|
||||
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
|
||||
|
||||
|
||||
### Frequently Asked Questions
|
||||
|
||||
**Q: Now that I've downloaded my books, how can I listen to them?**
|
||||
# Frequently Asked Questions
|
||||
|
||||
## Q: What's the difference between 'Classic' and 'Chardonnay'?
|
||||
|
||||
**A:** First and most importantly: Classic and Chardonnay have the exact same features.
|
||||
|
||||
* **Classic** is Windows only. Its older 'grey boxes' look has a compact design which allows for more information on the screen. Notably, Classic was written using an older, more mature technology which has built-in support for screenreaders.
|
||||
|
||||
* **Chardonnay** is available for Windows, Mac, and Linux. Its modern design has a more open look and feel.
|
||||
|
||||
## Q: Now that I've downloaded my books, how can I listen to them?
|
||||
|
||||
**A:** You can use any app which plays m4b files (or mp3 files if you used that setting). Here are just a few ideas. Disclaimer: I have no affiliation with any of these companies:
|
||||
|
||||
* iOS: [BookPlayer](https://apps.apple.com/us/app/bookplayer/id1138219998)
|
||||
* iOS: [Bound](https://apps.apple.com/us/app/bound-audiobook-player/id1041727137)
|
||||
* Android: [Smart AudioBook Player](https://play.google.com/store/apps/details?id=ak.alizandro.smartaudiobookplayer&hl=en_US&gl=US)
|
||||
* Desktop: [VLC](https://www.videolan.org/)
|
||||
* Android: [Listen](https://play.google.com/store/apps/details?id=ru.litres.android.audio&hl=en_US&gl=US)
|
||||
* Desktop: [VLC](https://www.videolan.org/)
|
||||
* Windows Desktop: [Audibly](https://github.com/rstewa/Audibly) -- a desktop player build specifically for audiobooks
|
||||
|
||||
Self-hosting online:
|
||||
|
||||
* [audiobookshelf](https://www.audiobookshelf.org). On [reddit](https://www.reddit.com/r/audiobookshelf/)
|
||||
* [plex](https://www.plex.tv/). Listen with [Prologue](https://prologue.audio/) (iOS)
|
||||
|
||||
## Q: How do I use Libation with a South Africa account?
|
||||
|
||||
**A:** Like many countries, amazon gives South Africa it's own amazon site. [Unlike many other regions](https://www.audible.com/ep/country-selector) there is not South Africa specific audible site. Use `US` for your region -- ie: audible.com.
|
||||
|
||||
(Not exactly a *frequently* asked question but it's come up more than once)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
|
||||
|
||||
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/MBucari?locale.x=en_us)
|
||||
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
|
||||
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
|
||||
|
||||
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/MBucari?locale.x=en_us)
|
||||
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
|
||||
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
|
||||
|
||||
|
||||
### Install and Run Libation on Ubuntu
|
||||
|
||||
# Install and Run Libation on Ubuntu
|
||||
|
||||
New Libation releases are automatically packed into .deb and .rpm package and are available from the Libation repository's releases page.
|
||||
|
||||
|
||||
Run this command in your terminal to dowbnload and install Libation, replacing the url with the latest Libation package url:
|
||||
Run this command in your terminal to download and install Libation, replacing the url with the latest Libation package url:
|
||||
|
||||
- Debian
|
||||
```Console
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
|
||||
|
||||
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/MBucari?locale.x=en_us)
|
||||
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
|
||||
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
|
||||
|
||||
|
||||
|
||||
# Run Libation on MacOS
|
||||
This walkthrough should get you up and running with Libation on your Mac.
|
||||
|
||||
@@ -15,13 +16,29 @@ This walkthrough should get you up and running with Libation on your Mac.
|
||||
- Apple Silicon (M1, M2, ...): `Libation.9.4.2-macOS-chardonnay-`**arm64**`.tgz`
|
||||
- Intel: `Libation.x.x.x-macOS-chardonnay-`**x64**`.tgz`
|
||||
- Move the extracted Libation app bundle to your applications folder.
|
||||
- Open a terminal (Go > Utilities > Terminal)
|
||||
- Copy/paste/run the following command (you'll be prompted to enter your password)
|
||||
|
||||
- Right-click on Libation and then click on open
|
||||
- The first time, it will not immediately show you an option to open it. Just dismiss the dialog and do the same thing again (right-click -> open) then you will get an option to run the unsigned application. This takes about 10 seconds.
|
||||
|
||||
## If this doesn't work
|
||||
|
||||
You can add Libation as a safe app without touching Gatekeeper.
|
||||
|
||||
- Copy/paste/run the following command. Adjust the file path to the Libation.app on your computer if necessary.
|
||||
|
||||
```Console
|
||||
xattr -r -d com.apple.quarantine ~/Downloads/Libation.app
|
||||
```
|
||||
- Close the terminal and use Libation!
|
||||
|
||||
## If this still doesn't work
|
||||
|
||||
- Copy/paste/run the following command (you'll be prompted to enter your Mac password)
|
||||
|
||||
```Console
|
||||
sudo spctl --master-disable && sudo spctl --add --label "Libation" /Applications/Libation.app && open /Applications/Libation.app && sudo spctl --master-enable
|
||||
```
|
||||
- Close the terminal and use Libation!
|
||||
```
|
||||
|
||||
* Close the terminal and use Libation!
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -41,7 +58,7 @@ Libation comes with a recovery app called Hangover. You can start it by running
|
||||
open /Applications/Libation.app --args hangover
|
||||
```
|
||||
|
||||
## Runnign LibationCli
|
||||
## Running LibationCli
|
||||
|
||||
Libation comes with a command-line interface. Unfortunately, due to the way apps are sandboxed on mac, its use is somewhat limited. To open a new sandboxed terminal in LibationCli's directory, run the following command:
|
||||
```Console
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
|
||||
|
||||
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
|
||||
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
|
||||
|
||||
|
||||
|
||||
# Naming Templates
|
||||
File and Folder names can be customized using Libation's built-in tag template naming engine. To edit how folder and file names are created, go to Settings \> Download/Decrypt and edit the naming templates. If you're splitting your audiobook into multiple files by chapter, you can also use a custom template to set each chapter's title metadata tag by editing the template in Settings \> Audio File Options.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
|
||||
|
||||
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/MBucari?locale.x=en_us)
|
||||
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
|
||||
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
|
||||
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
|
||||
|
||||
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/MBucari?locale.x=en_us)
|
||||
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
|
||||
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
|
||||
|
||||
|
||||
|
||||
# Table of Contents
|
||||
|
||||
- [Audible audiobook manager](#audible-audiobook-manager)
|
||||
|
||||
@@ -86,8 +86,12 @@ delfiles=( 'libmp3lame.arm64.so' 'libmp3lame.x64.so' 'libmp3lame.x64.dll' 'libmp
|
||||
if [[ "$ARCH" == "arm64" ]]
|
||||
then
|
||||
delfiles+=('libmp3lame.x64.dylib' 'ffmpegaac.x64.dylib')
|
||||
mv $BUNDLE_MACOS/ffmpegaac.arm64.dylib $BUNDLE_MACOS/ffmpegaac.dylib
|
||||
mv $BUNDLE_MACOS/libmp3lame.arm64.dylib $BUNDLE_MACOS/libmp3lame.dylib
|
||||
else
|
||||
delfiles+=('libmp3lame.arm64.dylib' 'ffmpegaac.arm64.dylib')
|
||||
mv $BUNDLE_MACOS/ffmpegaac.x64.dylib $BUNDLE_MACOS/ffmpegaac.dylib
|
||||
mv $BUNDLE_MACOS/libmp3lame.x64.dylib $BUNDLE_MACOS/libmp3lame.dylib
|
||||
fi
|
||||
|
||||
|
||||
@@ -111,4 +115,4 @@ mv $APP_FILE ./bundle/$APP_FILE
|
||||
|
||||
rm -r $BUNDLE
|
||||
|
||||
echo "Done!"
|
||||
echo "Done!"
|
||||
|
||||
@@ -2,10 +2,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Version>11.3.2.1</Version>
|
||||
<Version>11.5.2.1</Version>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Octokit" Version="9.1.0" />
|
||||
<PackageReference Include="Octokit" Version="11.0.1" />
|
||||
<!-- Do not remove unused Serilog.Sinks -->
|
||||
<!-- Only ZipFile sink is currently used. By user request (June 2024) others packages are included for experimental use. -->
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.ZipFile" Version="3.1.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using ApplicationServices;
|
||||
using ApplicationServices;
|
||||
using AudibleUtilities;
|
||||
using Dinah.Core;
|
||||
using Dinah.Core.IO;
|
||||
using Dinah.Core.Logging;
|
||||
using LibationFileManager;
|
||||
using System.Runtime.InteropServices;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace AppScaffolding
|
||||
{
|
||||
public enum ReleaseIdentifier
|
||||
public enum ReleaseIdentifier
|
||||
{
|
||||
None,
|
||||
WindowsClassic = OS.Windows | Variety.Classic | Architecture.X64,
|
||||
@@ -87,7 +88,8 @@ namespace AppScaffolding
|
||||
//
|
||||
|
||||
Migrations.migrate_to_v6_6_9(config);
|
||||
}
|
||||
Migrations.migrate_to_v11_5_0(config);
|
||||
}
|
||||
|
||||
/// <summary>Initialize logging. Wire-up events. Run after migration</summary>
|
||||
public static void RunPostMigrationScaffolding(Variety variety, Configuration config)
|
||||
@@ -124,6 +126,9 @@ namespace AppScaffolding
|
||||
{ "MinimumLevel", "Information" },
|
||||
{ "WriteTo", new JArray
|
||||
{
|
||||
// ABOUT SINKS
|
||||
// Only ZipFile sink is currently used. By user request (June 2024) others packages are included for experimental use.
|
||||
|
||||
// new JObject { {"Name", "Console" } }, // this has caused more problems than it's solved
|
||||
new JObject
|
||||
{
|
||||
@@ -227,12 +232,20 @@ namespace AppScaffolding
|
||||
|
||||
// begin logging session with a form feed
|
||||
Log.Logger.Information("\r\n\f");
|
||||
Log.Logger.Information("Begin. {@DebugInfo}", new
|
||||
|
||||
static int fileCount(FileManager.LongPath longPath)
|
||||
{
|
||||
try { return FileManager.FileUtility.SaferEnumerateFiles(longPath).Count(); }
|
||||
catch { return -1; }
|
||||
}
|
||||
|
||||
Log.Logger.Information("Begin. {@DebugInfo}", new
|
||||
{
|
||||
AppName = EntryAssembly.GetName().Name,
|
||||
Version = BuildVersion.ToString(),
|
||||
ReleaseIdentifier,
|
||||
Configuration.OS,
|
||||
Environment.OSVersion,
|
||||
InteropFactory.InteropFunctionsType,
|
||||
Mode = mode,
|
||||
LogLevel_Verbose_Enabled = Log.Logger.IsVerboseEnabled(),
|
||||
@@ -242,6 +255,7 @@ namespace AppScaffolding
|
||||
LogLevel_Error_Enabled = Log.Logger.IsErrorEnabled(),
|
||||
LogLevel_Fatal_Enabled = Log.Logger.IsFatalEnabled(),
|
||||
|
||||
config.AutoScan,
|
||||
config.BetaOptIn,
|
||||
config.UseCoverAsFolderIcon,
|
||||
config.LibationFiles,
|
||||
@@ -250,10 +264,12 @@ namespace AppScaffolding
|
||||
config.InProgress,
|
||||
|
||||
AudibleFileStorage.DownloadsInProgressDirectory,
|
||||
DownloadsInProgressFiles = FileManager.FileUtility.SaferEnumerateFiles(AudibleFileStorage.DownloadsInProgressDirectory).Count(),
|
||||
DownloadsInProgressFiles = fileCount(AudibleFileStorage.DownloadsInProgressDirectory),
|
||||
|
||||
AudibleFileStorage.DecryptInProgressDirectory,
|
||||
DecryptInProgressFiles = FileManager.FileUtility.SaferEnumerateFiles(AudibleFileStorage.DecryptInProgressDirectory).Count(),
|
||||
DecryptInProgressFiles = fileCount(AudibleFileStorage.DecryptInProgressDirectory),
|
||||
|
||||
disableIPv6 = AppContext.TryGetSwitch("System.Net.DisableIPv6", out bool disableIPv6Value),
|
||||
});
|
||||
|
||||
if (InteropFactory.InteropFunctionsType is null)
|
||||
@@ -390,5 +406,55 @@ namespace AppScaffolding
|
||||
UNSAFE_MigrationHelper.Settings_AddUniqueToArray("Serilog.Enrich", "WithExceptionDetails");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FilterState_6_6_9
|
||||
{
|
||||
public bool UseDefault { get; set; }
|
||||
public List<string> Filters { get; set; } = new();
|
||||
}
|
||||
|
||||
public static void migrate_to_v11_5_0(Configuration config)
|
||||
{
|
||||
// Read file, but convert old format to new (with Name field) as necessary.
|
||||
if (!File.Exists(QuickFilters.JsonFile))
|
||||
{
|
||||
QuickFilters.InMemoryState = new();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (JsonConvert.DeserializeObject<QuickFilters.FilterState>(File.ReadAllText(QuickFilters.JsonFile))
|
||||
is QuickFilters.FilterState inMemState)
|
||||
{
|
||||
QuickFilters.InMemoryState = inMemState;
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Eat
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (JsonConvert.DeserializeObject<FilterState_6_6_9>(File.ReadAllText(QuickFilters.JsonFile))
|
||||
is FilterState_6_6_9 inMemState)
|
||||
{
|
||||
// Copy old structure to new.
|
||||
QuickFilters.InMemoryState = new();
|
||||
QuickFilters.InMemoryState.UseDefault = inMemState.UseDefault;
|
||||
foreach (var oldFilter in inMemState.Filters)
|
||||
QuickFilters.InMemoryState.Filters.Add(new QuickFilters.NamedFilter(oldFilter, null));
|
||||
|
||||
return;
|
||||
}
|
||||
Debug.Assert(false, "Should not get here, QuickFilters.json deserialization issue");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Eat
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CsvHelper" Version="30.0.1" />
|
||||
<PackageReference Include="NPOI" Version="2.6.2" />
|
||||
<PackageReference Include="CsvHelper" Version="33.0.1" />
|
||||
<PackageReference Include="NPOI" Version="2.7.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -126,7 +126,8 @@ namespace ApplicationServices
|
||||
| LibraryOptions.ResponseGroupOptions.Contributors | LibraryOptions.ResponseGroupOptions.ProvidedReview
|
||||
| LibraryOptions.ResponseGroupOptions.ProductPlans | LibraryOptions.ResponseGroupOptions.Series
|
||||
| LibraryOptions.ResponseGroupOptions.CategoryLadders | LibraryOptions.ResponseGroupOptions.ProductExtendedAttrs
|
||||
| LibraryOptions.ResponseGroupOptions.PdfUrl | LibraryOptions.ResponseGroupOptions.OriginAsin,
|
||||
| LibraryOptions.ResponseGroupOptions.PdfUrl | LibraryOptions.ResponseGroupOptions.OriginAsin
|
||||
| LibraryOptions.ResponseGroupOptions.IsFinished,
|
||||
ImageSizes = LibraryOptions.ImageSizeOptions._500 | LibraryOptions.ImageSizeOptions._1215
|
||||
};
|
||||
var importItems = await scanAccountsAsync(apiExtendedfunc, accounts, libraryOptions);
|
||||
|
||||
@@ -115,6 +115,9 @@ namespace ApplicationServices
|
||||
|
||||
[Name("LastDownloadedVersion")]
|
||||
public string LastDownloadedVersion { get; set; }
|
||||
|
||||
[Name("IsFinished")]
|
||||
public bool IsFinished { get; set; }
|
||||
}
|
||||
public static class LibToDtos
|
||||
{
|
||||
@@ -153,8 +156,8 @@ namespace ApplicationServices
|
||||
Language = a.Book.Language,
|
||||
LastDownloaded = a.Book.UserDefinedItem.LastDownloaded,
|
||||
LastDownloadedVersion = a.Book.UserDefinedItem.LastDownloadedVersion?.ToString() ?? "",
|
||||
|
||||
}).ToList();
|
||||
IsFinished = a.Book.UserDefinedItem.IsFinished
|
||||
}).ToList();
|
||||
}
|
||||
public static class LibraryExporter
|
||||
{
|
||||
@@ -229,6 +232,7 @@ namespace ApplicationServices
|
||||
nameof(ExportDto.Language),
|
||||
nameof(ExportDto.LastDownloaded),
|
||||
nameof(ExportDto.LastDownloadedVersion),
|
||||
nameof(ExportDto.IsFinished)
|
||||
};
|
||||
var col = 0;
|
||||
foreach (var c in columns)
|
||||
@@ -306,6 +310,7 @@ namespace ApplicationServices
|
||||
}
|
||||
|
||||
row.CreateCell(++col).SetCellValue(dto.LastDownloadedVersion);
|
||||
row.CreateCell(++col).SetCellValue(dto.IsFinished);
|
||||
|
||||
rowIndex++;
|
||||
}
|
||||
|
||||
@@ -90,8 +90,10 @@ namespace AudibleUtilities
|
||||
var distinct = items.GetSeriesDistinct();
|
||||
if (distinct.Any(s => s.SeriesId is null))
|
||||
exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Series)} with null {nameof(Series.SeriesId)}", nameof(items)));
|
||||
if (distinct.Any(s => s.SeriesName is null))
|
||||
exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Series)} with null {nameof(Series.SeriesName)}", nameof(items)));
|
||||
|
||||
//// unfortunately, a user has a series with no name
|
||||
//if (distinct.Any(s => s.SeriesName is null))
|
||||
// exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Series)} with null {nameof(Series.SeriesName)}", nameof(items)));
|
||||
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AudibleApi" Version="9.0.0.1" />
|
||||
<PackageReference Include="AudibleApi" Version="9.2.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dinah.Core" Version="8.0.0.1" />
|
||||
<PackageReference Include="Dinah.EntityFrameworkCore" Version="8.0.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.1">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.1">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -191,7 +191,7 @@ namespace DataLayer
|
||||
ArgumentValidator.EnsureNotNull(ladders, nameof(ladders));
|
||||
|
||||
//Replace all existing category ladders.
|
||||
//Some books make have duplocate ladders
|
||||
//Some books make have duplicate ladders
|
||||
CategoriesLink.Clear();
|
||||
CategoriesLink.UnionWith(ladders.Distinct().Select(l => new BookCategory(this, l)));
|
||||
}
|
||||
|
||||
@@ -195,7 +195,23 @@ namespace DataLayer
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region IsFinished
|
||||
private bool _isFinished;
|
||||
public bool IsFinished
|
||||
{
|
||||
get => _isFinished;
|
||||
set
|
||||
{
|
||||
if (value != _isFinished)
|
||||
{
|
||||
_isFinished = value;
|
||||
OnItemChanged(nameof(IsFinished));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public override string ToString() => $"{Book} {Rating} {Tags}";
|
||||
}
|
||||
|
||||
468
Source/DataLayer/Migrations/20240911114741_MyComment.Designer.cs
generated
Normal file
468
Source/DataLayer/Migrations/20240911114741_MyComment.Designer.cs
generated
Normal file
@@ -0,0 +1,468 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using DataLayer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DataLayer.Migrations
|
||||
{
|
||||
[DbContext(typeof(LibationContext))]
|
||||
[Migration("20240911114741_MyComment")]
|
||||
partial class MyComment
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.5");
|
||||
|
||||
modelBuilder.Entity("CategoryCategoryLadder", b =>
|
||||
{
|
||||
b.Property<int>("_categoriesCategoryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("_categoryLaddersCategoryLadderId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("_categoriesCategoryId", "_categoryLaddersCategoryLadderId");
|
||||
|
||||
b.HasIndex("_categoryLaddersCategoryLadderId");
|
||||
|
||||
b.ToTable("CategoryCategoryLadder");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Book", b =>
|
||||
{
|
||||
b.Property<int>("BookId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleProductId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("ContentType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DatePublished")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsAbridged")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("LengthInMinutes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Locale")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PictureId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PictureLarge")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Subtitle")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("_audioFormat")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("BookId");
|
||||
|
||||
b.HasIndex("AudibleProductId");
|
||||
|
||||
b.ToTable("Books");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.BookCategory", b =>
|
||||
{
|
||||
b.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("CategoryLadderId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("BookId", "CategoryLadderId");
|
||||
|
||||
b.HasIndex("BookId");
|
||||
|
||||
b.HasIndex("CategoryLadderId");
|
||||
|
||||
b.ToTable("BookCategory");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.BookContributor", b =>
|
||||
{
|
||||
b.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ContributorId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Role")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<byte>("Order")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("BookId", "ContributorId", "Role");
|
||||
|
||||
b.HasIndex("BookId");
|
||||
|
||||
b.HasIndex("ContributorId");
|
||||
|
||||
b.ToTable("BookContributor");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Category", b =>
|
||||
{
|
||||
b.Property<int>("CategoryId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleCategoryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("CategoryId");
|
||||
|
||||
b.HasIndex("AudibleCategoryId");
|
||||
|
||||
b.ToTable("Categories");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.CategoryLadder", b =>
|
||||
{
|
||||
b.Property<int>("CategoryLadderId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("CategoryLadderId");
|
||||
|
||||
b.ToTable("CategoryLadders");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Contributor", b =>
|
||||
{
|
||||
b.Property<int>("ContributorId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleContributorId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("ContributorId");
|
||||
|
||||
b.HasIndex("Name");
|
||||
|
||||
b.ToTable("Contributors");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
ContributorId = -1,
|
||||
Name = ""
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.LibraryBook", b =>
|
||||
{
|
||||
b.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("AbsentFromLastScan")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Account")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("BookId");
|
||||
|
||||
b.ToTable("LibraryBooks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Series", b =>
|
||||
{
|
||||
b.Property<int>("SeriesId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleSeriesId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("SeriesId");
|
||||
|
||||
b.HasIndex("AudibleSeriesId");
|
||||
|
||||
b.ToTable("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.SeriesBook", b =>
|
||||
{
|
||||
b.Property<int>("SeriesId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Order")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("SeriesId", "BookId");
|
||||
|
||||
b.HasIndex("BookId");
|
||||
|
||||
b.HasIndex("SeriesId");
|
||||
|
||||
b.ToTable("SeriesBook");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CategoryCategoryLadder", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Category", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("_categoriesCategoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("DataLayer.CategoryLadder", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("_categoryLaddersCategoryLadderId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Book", b =>
|
||||
{
|
||||
b.OwnsOne("DataLayer.Rating", "Rating", b1 =>
|
||||
{
|
||||
b1.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<float>("OverallRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b1.Property<float>("PerformanceRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b1.Property<float>("StoryRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b1.HasKey("BookId");
|
||||
|
||||
b1.ToTable("Books");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("BookId");
|
||||
});
|
||||
|
||||
b.OwnsMany("DataLayer.Supplement", "Supplements", b1 =>
|
||||
{
|
||||
b1.Property<int>("SupplementId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<string>("Url")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.HasKey("SupplementId");
|
||||
|
||||
b1.HasIndex("BookId");
|
||||
|
||||
b1.ToTable("Supplement");
|
||||
|
||||
b1.WithOwner("Book")
|
||||
.HasForeignKey("BookId");
|
||||
|
||||
b1.Navigation("Book");
|
||||
});
|
||||
|
||||
b.OwnsOne("DataLayer.UserDefinedItem", "UserDefinedItem", b1 =>
|
||||
{
|
||||
b1.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<int>("BookStatus")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<bool>("IsFinished")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<DateTime?>("LastDownloaded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.Property<string>("LastDownloadedVersion")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.Property<int?>("PdfStatus")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<string>("Tags")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.HasKey("BookId");
|
||||
|
||||
b1.ToTable("UserDefinedItem", (string)null);
|
||||
|
||||
b1.WithOwner("Book")
|
||||
.HasForeignKey("BookId");
|
||||
|
||||
b1.OwnsOne("DataLayer.Rating", "Rating", b2 =>
|
||||
{
|
||||
b2.Property<int>("UserDefinedItemBookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b2.Property<float>("OverallRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b2.Property<float>("PerformanceRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b2.Property<float>("StoryRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b2.HasKey("UserDefinedItemBookId");
|
||||
|
||||
b2.ToTable("UserDefinedItem");
|
||||
|
||||
b2.WithOwner()
|
||||
.HasForeignKey("UserDefinedItemBookId");
|
||||
});
|
||||
|
||||
b1.Navigation("Book");
|
||||
|
||||
b1.Navigation("Rating");
|
||||
});
|
||||
|
||||
b.Navigation("Rating");
|
||||
|
||||
b.Navigation("Supplements");
|
||||
|
||||
b.Navigation("UserDefinedItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.BookCategory", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Book", "Book")
|
||||
.WithMany("CategoriesLink")
|
||||
.HasForeignKey("BookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("DataLayer.CategoryLadder", "CategoryLadder")
|
||||
.WithMany("BooksLink")
|
||||
.HasForeignKey("CategoryLadderId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Book");
|
||||
|
||||
b.Navigation("CategoryLadder");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.BookContributor", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Book", "Book")
|
||||
.WithMany("ContributorsLink")
|
||||
.HasForeignKey("BookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("DataLayer.Contributor", "Contributor")
|
||||
.WithMany("BooksLink")
|
||||
.HasForeignKey("ContributorId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Book");
|
||||
|
||||
b.Navigation("Contributor");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.LibraryBook", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Book", "Book")
|
||||
.WithOne()
|
||||
.HasForeignKey("DataLayer.LibraryBook", "BookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Book");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.SeriesBook", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Book", "Book")
|
||||
.WithMany("SeriesLink")
|
||||
.HasForeignKey("BookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("DataLayer.Series", "Series")
|
||||
.WithMany("BooksLink")
|
||||
.HasForeignKey("SeriesId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Book");
|
||||
|
||||
b.Navigation("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Book", b =>
|
||||
{
|
||||
b.Navigation("CategoriesLink");
|
||||
|
||||
b.Navigation("ContributorsLink");
|
||||
|
||||
b.Navigation("SeriesLink");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.CategoryLadder", b =>
|
||||
{
|
||||
b.Navigation("BooksLink");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Contributor", b =>
|
||||
{
|
||||
b.Navigation("BooksLink");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Series", b =>
|
||||
{
|
||||
b.Navigation("BooksLink");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Source/DataLayer/Migrations/20240911114741_MyComment.cs
Normal file
29
Source/DataLayer/Migrations/20240911114741_MyComment.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DataLayer.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class MyComment : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsFinished",
|
||||
table: "UserDefinedItem",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsFinished",
|
||||
table: "UserDefinedItem");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace DataLayer.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.5");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.5");
|
||||
|
||||
modelBuilder.Entity("CategoryCategoryLadder", b =>
|
||||
{
|
||||
@@ -312,6 +312,9 @@ namespace DataLayer.Migrations
|
||||
b1.Property<int>("BookStatus")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<bool>("IsFinished")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<DateTime?>("LastDownloaded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
||||
@@ -164,6 +164,9 @@ namespace DtoImporterService
|
||||
if (item.PictureLarge is not null)
|
||||
book.PictureLarge = item.PictureLarge;
|
||||
|
||||
if (item.IsFinished is not null)
|
||||
book.UserDefinedItem.IsFinished = item.IsFinished.Value;
|
||||
|
||||
// 2023-02-01
|
||||
// updateBook must update language on books which were imported before the migration which added language.
|
||||
// Can eventually delete this
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace FileManager
|
||||
lock (fsCacheLocker)
|
||||
{
|
||||
fsCache.Clear();
|
||||
fsCache.AddRange(FileUtility.SaferEnumerateFiles(RootDirectory, SearchPattern, SearchOption));
|
||||
fsCache.AddRange(SafestEnumerateFiles(RootDirectory));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace FileManager
|
||||
Stop();
|
||||
|
||||
lock (fsCacheLocker)
|
||||
fsCache.AddRange(FileUtility.SaferEnumerateFiles(RootDirectory, SearchPattern, SearchOption));
|
||||
fsCache.AddRange(SafestEnumerateFiles(RootDirectory));
|
||||
|
||||
directoryChangesEvents = new BlockingCollection<FileSystemEventArgs>();
|
||||
fileSystemWatcher = new FileSystemWatcher(RootDirectory)
|
||||
@@ -152,11 +152,23 @@ namespace FileManager
|
||||
if (Path.GetFileName(path).Contains("LibationContext.db") || !File.Exists(path) && !Directory.Exists(path))
|
||||
return;
|
||||
if (File.GetAttributes(path).HasFlag(FileAttributes.Directory))
|
||||
AddUniqueFiles(FileUtility.SaferEnumerateFiles(path, SearchPattern, SearchOption));
|
||||
AddUniqueFiles(SafestEnumerateFiles(path));
|
||||
else
|
||||
AddUniqueFile(path);
|
||||
}
|
||||
|
||||
private IEnumerable<LongPath> SafestEnumerateFiles(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return FileUtility.SaferEnumerateFiles(path, SearchPattern, SearchOption);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private void AddUniqueFiles(IEnumerable<LongPath> newFiles)
|
||||
{
|
||||
foreach (var file in newFiles)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dinah.Core" Version="8.0.0.1" />
|
||||
<PackageReference Include="Polly" Version="8.2.1" />
|
||||
<PackageReference Include="Polly" Version="8.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
|
||||
@@ -21,9 +21,13 @@
|
||||
<ApplicationIcon>hangover.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<RuntimeHostConfigurationOption Include="System.Net.DisableIPv6" Value="true" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<OutputPath>..\bin\Avalonia\Debug</OutputPath>
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<RuntimeHostConfigurationOption Include="System.Net.DisableIPv6" Value="true" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -77,6 +77,10 @@
|
||||
<Style Selector="Button">
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<Style Selector="ScrollBar">
|
||||
<!-- It's called AutoHide, but this is really the mouseover shrink/expand. -->
|
||||
<Setter Property="AllowAutoHide" Value="false"/>
|
||||
</Style>
|
||||
</Application.Styles>
|
||||
|
||||
<NativeMenu.Menu>
|
||||
|
||||
@@ -90,14 +90,10 @@ namespace LibationAvalonia
|
||||
|
||||
if (setupDialog.Config.LibationSettingsAreValid)
|
||||
{
|
||||
var theme
|
||||
= setupDialog.SelectedTheme.Content is nameof(ThemeVariant.Dark)
|
||||
? nameof(ThemeVariant.Dark)
|
||||
: nameof(ThemeVariant.Light);
|
||||
|
||||
string theme = setupDialog.SelectedTheme.Content as string;
|
||||
|
||||
setupDialog.Config.SetString(theme, nameof(ThemeVariant));
|
||||
|
||||
|
||||
await RunMigrationsAsync(setupDialog.Config);
|
||||
LibraryTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
|
||||
AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists();
|
||||
@@ -208,9 +204,15 @@ namespace LibationAvalonia
|
||||
|
||||
private static void ShowMainWindow(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
Current.RequestedThemeVariant = Configuration.Instance.GetString(propertyName: nameof(ThemeVariant)) is "Dark" ? ThemeVariant.Dark : ThemeVariant.Light;
|
||||
Current.RequestedThemeVariant = Configuration.Instance.GetString(propertyName: nameof(ThemeVariant)) switch
|
||||
{
|
||||
nameof(ThemeVariant.Dark) => ThemeVariant.Dark,
|
||||
nameof(ThemeVariant.Light) => ThemeVariant.Light,
|
||||
// "System"
|
||||
_ => ThemeVariant.Default
|
||||
};
|
||||
|
||||
//Reload colors for current theme
|
||||
//Reload colors for current theme
|
||||
LoadStyles();
|
||||
var mainWindow = new MainWindow();
|
||||
desktop.MainWindow = MainWindow = mainWindow;
|
||||
|
||||
@@ -107,7 +107,7 @@ namespace LibationAvalonia.Controls
|
||||
|
||||
if (known is Configuration.KnownDirectories.None)
|
||||
{
|
||||
directoryState.CustomDir = noSubDir;
|
||||
directoryState.CustomDir = directory;
|
||||
directoryState.CustomChecked = true;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
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:dialogs="clr-namespace:LibationAvalonia.Dialogs"
|
||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350"
|
||||
Width="800" Height="450"
|
||||
x:Class="LibationAvalonia.Dialogs.EditQuickFilters"
|
||||
Title="Audible Accounts"
|
||||
Icon="/Assets/libation.ico">
|
||||
Icon="/Assets/libation.ico"
|
||||
x:DataType="dialogs:EditQuickFilters">
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
|
||||
<Grid.Styles>
|
||||
@@ -43,7 +45,14 @@
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
IsReadOnly="False"
|
||||
Binding="{Binding Name, Mode=TwoWay}"
|
||||
Header="Name"/>
|
||||
|
||||
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
IsReadOnly="False"
|
||||
|
||||
@@ -12,8 +12,18 @@ namespace LibationAvalonia.Dialogs
|
||||
public ObservableCollection<Filter> Filters { get; } = new();
|
||||
|
||||
public class Filter : ViewModels.ViewModelBase
|
||||
{
|
||||
private string _filterString;
|
||||
{
|
||||
private string _name;
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set
|
||||
{
|
||||
this.RaiseAndSetIfChanged(ref _name, value);
|
||||
}
|
||||
}
|
||||
|
||||
private string _filterString;
|
||||
public string FilterString
|
||||
{
|
||||
get => _filterString;
|
||||
@@ -25,6 +35,9 @@ namespace LibationAvalonia.Dialogs
|
||||
}
|
||||
}
|
||||
public bool IsDefault { get; private set; } = true;
|
||||
|
||||
public QuickFilters.NamedFilter AsNamedFilter() => new(FilterString, Name);
|
||||
|
||||
}
|
||||
public EditQuickFilters()
|
||||
{
|
||||
@@ -40,7 +53,7 @@ namespace LibationAvalonia.Dialogs
|
||||
|
||||
ControlToFocusOnShow = this.FindControl<Button>(nameof(saveBtn));
|
||||
|
||||
var allFilters = QuickFilters.Filters.Select(f => new Filter { FilterString = f }).ToList();
|
||||
var allFilters = QuickFilters.Filters.Select(f => new Filter { FilterString = f.Filter, Name = f.Name }).ToList();
|
||||
allFilters.Add(new Filter());
|
||||
|
||||
foreach (var f in allFilters)
|
||||
@@ -61,7 +74,7 @@ namespace LibationAvalonia.Dialogs
|
||||
|
||||
protected override void SaveAndClose()
|
||||
{
|
||||
QuickFilters.ReplaceAll(Filters.Where(f => !f.IsDefault).Select(f => f.FilterString));
|
||||
QuickFilters.ReplaceAll(Filters.Where(f => !f.IsDefault).Select(x => x.AsNamedFilter()));
|
||||
base.SaveAndClose();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
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:dialogs="clr-namespace:LibationAvalonia.Dialogs"
|
||||
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="350"
|
||||
x:Class="LibationAvalonia.Dialogs.SetupDialog"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Width="500" Height="350"
|
||||
Icon="/Assets/libation.ico"
|
||||
Title="Welcome to Libation">
|
||||
Title="Welcome to Libation"
|
||||
x:DataType="dialogs:SetupDialog">
|
||||
|
||||
<Grid
|
||||
Margin="10"
|
||||
@@ -58,6 +60,7 @@
|
||||
SelectedIndex="0"
|
||||
SelectedItem="{Binding SelectedTheme, Mode=OneWayToSource}">
|
||||
<ComboBox.Items>
|
||||
<ComboBoxItem Content="System" />
|
||||
<ComboBoxItem Content="Light" />
|
||||
<ComboBoxItem Content="Dark" />
|
||||
</ComboBox.Items>
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<RuntimeHostConfigurationOption Include="System.Net.DisableIPv6" Value="true" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
|
||||
@@ -13,12 +13,12 @@ namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
partial class MainVM
|
||||
{
|
||||
private string lastGoodFilter = "";
|
||||
private string _filterString;
|
||||
private QuickFilters.NamedFilter lastGoodFilter = new(string.Empty, null);
|
||||
private QuickFilters.NamedFilter _selectedNamedFilter = new(string.Empty, null);
|
||||
private bool _firstFilterIsDefault = true;
|
||||
|
||||
/// <summary> Library filterting query </summary>
|
||||
public string FilterString { get => _filterString; set => this.RaiseAndSetIfChanged(ref _filterString, value); }
|
||||
public QuickFilters.NamedFilter SelectedNamedFilter { get => _selectedNamedFilter; set => this.RaiseAndSetIfChanged(ref _selectedNamedFilter, value); }
|
||||
public AvaloniaList<Control> QuickFilterMenuItems { get; } = new();
|
||||
/// <summary> Indicates if the first quick filter is the default filter </summary>
|
||||
public bool FirstFilterIsDefault { get => _firstFilterIsDefault; set => QuickFilters.UseDefault = this.RaiseAndSetIfChanged(ref _firstFilterIsDefault, value); }
|
||||
@@ -50,19 +50,19 @@ namespace LibationAvalonia.ViewModels
|
||||
QuickFilterMenuItems.Add(new Separator());
|
||||
}
|
||||
|
||||
public void AddQuickFilterBtn() => QuickFilters.Add(FilterString);
|
||||
public async Task FilterBtn() => await PerformFilter(FilterString);
|
||||
public void AddQuickFilterBtn() => QuickFilters.Add(SelectedNamedFilter);
|
||||
public async Task FilterBtn() => await PerformFilter(SelectedNamedFilter);
|
||||
public async Task FilterHelpBtn() => await new LibationAvalonia.Dialogs.SearchSyntaxDialog().ShowDialog(MainWindow);
|
||||
public void ToggleFirstFilterIsDefault() => FirstFilterIsDefault = !FirstFilterIsDefault;
|
||||
public async Task EditQuickFiltersAsync() => await new LibationAvalonia.Dialogs.EditQuickFilters().ShowDialog(MainWindow);
|
||||
public async Task PerformFilter(string filterString)
|
||||
public async Task PerformFilter(QuickFilters.NamedFilter namedFilter)
|
||||
{
|
||||
FilterString = filterString;
|
||||
SelectedNamedFilter = namedFilter;
|
||||
|
||||
try
|
||||
{
|
||||
await ProductsDisplay.Filter(filterString);
|
||||
lastGoodFilter = filterString;
|
||||
await ProductsDisplay.Filter(namedFilter.Filter);
|
||||
lastGoodFilter = namedFilter;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -99,8 +99,8 @@ namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
var command = ReactiveCommand.Create(async () => await PerformFilter(filter));
|
||||
|
||||
var menuItem = new MenuItem { Header = $"{++index}: {filter}", Command = command };
|
||||
var nativeMenuItem = new NativeMenuItem { Header = $"{index}: {filter}", Command = command };
|
||||
var menuItem = new MenuItem { Header = $"{++index}: {(string.IsNullOrWhiteSpace(filter.Name) ? filter.Filter : filter.Name)}", Command = command };
|
||||
var nativeMenuItem = new NativeMenuItem { Header = $"{index}: {(string.IsNullOrWhiteSpace(filter.Name) ? filter.Filter : filter.Name)}", Command = command };
|
||||
|
||||
if (Configuration.IsMacOs && index <= 10)
|
||||
{
|
||||
|
||||
@@ -31,10 +31,10 @@ namespace LibationAvalonia.ViewModels.Settings
|
||||
LoggingLevel = config.LogLevel;
|
||||
GridScaleFactor = scaleFactorToLinearRange(config.GridScaleFactor);
|
||||
GridFontScaleFactor = scaleFactorToLinearRange(config.GridFontScaleFactor);
|
||||
ThemeVariant = initialThemeVariant
|
||||
= Configuration.Instance.GetString(propertyName: nameof(ThemeVariant)) is nameof(Avalonia.Styling.ThemeVariant.Dark)
|
||||
? nameof(Avalonia.Styling.ThemeVariant.Dark)
|
||||
: nameof(Avalonia.Styling.ThemeVariant.Light);
|
||||
|
||||
ThemeVariant = initialThemeVariant = Configuration.Instance.GetString(propertyName: nameof(ThemeVariant));
|
||||
if (string.IsNullOrWhiteSpace(initialThemeVariant))
|
||||
ThemeVariant = initialThemeVariant = "System";
|
||||
}
|
||||
|
||||
public void SaveSettings(Configuration config)
|
||||
@@ -83,7 +83,7 @@ namespace LibationAvalonia.ViewModels.Settings
|
||||
public string GridScaleFactorText { get; } = Configuration.GetDescription(nameof(Configuration.GridScaleFactor));
|
||||
public string GridFontScaleFactorText { get; } = Configuration.GetDescription(nameof(Configuration.GridFontScaleFactor));
|
||||
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[] Themes { get; } = { "System", nameof(Avalonia.Styling.ThemeVariant.Light), nameof(Avalonia.Styling.ThemeVariant.Dark) };
|
||||
|
||||
public string BooksDirectory { get; set; }
|
||||
public bool SavePodcastsToParentFolder { get; set; }
|
||||
|
||||
@@ -191,7 +191,7 @@
|
||||
<Button IsVisible="{CompiledBinding RemoveButtonsVisible}" Command="{CompiledBinding DoneRemovingBtn}" Content="Done Removing Books"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBox Grid.Column="1" Margin="10,0,0,0" Name="filterSearchTb" IsVisible="{CompiledBinding !RemoveButtonsVisible}" Text="{CompiledBinding FilterString, Mode=TwoWay}" KeyDown="filterSearchTb_KeyPress" />
|
||||
<TextBox Grid.Column="1" Margin="10,0,0,0" Name="filterSearchTb" IsVisible="{CompiledBinding !RemoveButtonsVisible}" Text="{CompiledBinding SelectedNamedFilter.Filter, Mode=TwoWay}" KeyDown="filterSearchTb_KeyPress" />
|
||||
|
||||
<StackPanel Grid.Column="2" Height="30" Orientation="Horizontal">
|
||||
<Button Name="filterBtn" Command="{CompiledBinding FilterBtn}" VerticalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Content="Filter"/>
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace LibationAvalonia.Views
|
||||
{
|
||||
if (e.Key == Key.Return)
|
||||
{
|
||||
await ViewModel.PerformFilter(ViewModel.FilterString);
|
||||
await ViewModel.PerformFilter(ViewModel.SelectedNamedFilter);
|
||||
|
||||
// silence the 'ding'
|
||||
e.Handled = true;
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
<IsPublishable>True</IsPublishable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<RuntimeHostConfigurationOption Include="System.Net.DisableIPv6" Value="true" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -25,9 +25,18 @@ namespace LibationFileManager
|
||||
static AudibleFileStorage()
|
||||
{
|
||||
//Clean up any partially-decrypted files from previous Libation instances.
|
||||
//Do no clean DownloadsInProgressDirectory because those files are resumable
|
||||
foreach (var tempFile in Directory.EnumerateFiles(DecryptInProgressDirectory))
|
||||
FileUtility.SaferDelete(tempFile);
|
||||
//Do not clean DownloadsInProgressDirectory. Those files are resumable.
|
||||
try
|
||||
{
|
||||
foreach (var tempFile in FileUtility.SaferEnumerateFiles(DecryptInProgressDirectory))
|
||||
FileUtility.SaferDelete(tempFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// since this is a static constructor which may be called very early, do not assume Serilog is initialized
|
||||
try { Serilog.Log.Error(ex, "Error cleaning up partially-decrypted files"); }
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -321,7 +321,7 @@ namespace LibationFileManager
|
||||
set => setTemplate<Templates.ChapterFileTemplate>(value);
|
||||
}
|
||||
|
||||
[Description("How to format the file's Tile stored in metadata")]
|
||||
[Description("How to format the file's Title stored in metadata")]
|
||||
public string ChapterTitleTemplate
|
||||
{
|
||||
get => getTemplate<Templates.ChapterTitleTemplate>();
|
||||
|
||||
@@ -95,9 +95,9 @@ namespace LibationFileManager
|
||||
var assembly = CurrentDomain_AssemblyResolve_internal(asmName, here: here);
|
||||
lowEffortCache[key] = assembly;
|
||||
|
||||
//Let the runtime handle any dll not found exceptions.
|
||||
// Let the runtime handle any dll not found exceptions
|
||||
if (assembly is null)
|
||||
Serilog.Log.Logger.Error($"Unable to load module {args.Name}");
|
||||
Serilog.Log.Logger.Warning($"Unable to load module {args.Name}");
|
||||
|
||||
return assembly;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using Dinah.Core.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dinah.Core.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
#nullable enable
|
||||
namespace LibationFileManager
|
||||
@@ -14,24 +14,21 @@ namespace LibationFileManager
|
||||
|
||||
public static event EventHandler? UseDefaultChanged;
|
||||
|
||||
internal class FilterState
|
||||
public class FilterState
|
||||
{
|
||||
public bool UseDefault { get; set; }
|
||||
public List<string> Filters { get; set; } = new List<string>();
|
||||
public List<NamedFilter> Filters { get; set; } = new();
|
||||
}
|
||||
|
||||
public static string JsonFile => Path.Combine(Configuration.Instance.LibationFiles, "QuickFilters.json");
|
||||
|
||||
|
||||
// load json into memory. if file doesn't exist, nothing to do. save() will create if needed
|
||||
static FilterState inMemoryState { get; }
|
||||
= File.Exists(JsonFile) && JsonConvert.DeserializeObject<FilterState>(File.ReadAllText(JsonFile)) is FilterState inMemState
|
||||
? inMemState
|
||||
: new FilterState();
|
||||
// load json into memory. if file doesn't exist, nothing to do. save() will create if needed
|
||||
public static FilterState InMemoryState { get; set; } = null!;
|
||||
|
||||
public static bool UseDefault
|
||||
{
|
||||
get => inMemoryState.UseDefault;
|
||||
get => InMemoryState.UseDefault;
|
||||
set
|
||||
{
|
||||
if (UseDefault == value)
|
||||
@@ -39,7 +36,7 @@ namespace LibationFileManager
|
||||
|
||||
lock (locker)
|
||||
{
|
||||
inMemoryState.UseDefault = value;
|
||||
InMemoryState.UseDefault = value;
|
||||
save(false);
|
||||
}
|
||||
|
||||
@@ -47,68 +44,81 @@ namespace LibationFileManager
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<string> Filters => inMemoryState.Filters.AsReadOnly();
|
||||
|
||||
public static void Add(string filter)
|
||||
// Note that records overload equality automagically, so should be able to
|
||||
// compare these the same way as comparing simple strings.
|
||||
public record NamedFilter(string Filter, string Name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filter))
|
||||
public string Filter { get; set; } = Filter;
|
||||
public string? Name { get; set; } = Name;
|
||||
}
|
||||
|
||||
public static IEnumerable<NamedFilter> Filters => InMemoryState.Filters.AsReadOnly();
|
||||
|
||||
public static void Add(NamedFilter namedFilter)
|
||||
{
|
||||
if (namedFilter == null)
|
||||
throw new ArgumentNullException(nameof(namedFilter));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(namedFilter.Filter))
|
||||
return;
|
||||
filter = filter.Trim();
|
||||
namedFilter.Filter = namedFilter.Filter?.Trim() ?? string.Empty;
|
||||
namedFilter.Name = namedFilter.Name?.Trim() ?? null;
|
||||
|
||||
lock (locker)
|
||||
{
|
||||
// check for duplicate
|
||||
if (inMemoryState.Filters.ContainsInsensative(filter))
|
||||
// check for duplicates
|
||||
if (InMemoryState.Filters.Select(x => x.Filter).ContainsInsensative(namedFilter.Filter))
|
||||
return;
|
||||
|
||||
inMemoryState.Filters.Add(filter);
|
||||
InMemoryState.Filters.Add(namedFilter);
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Remove(string filter)
|
||||
public static void Remove(NamedFilter filter)
|
||||
{
|
||||
lock (locker)
|
||||
{
|
||||
inMemoryState.Filters.Remove(filter);
|
||||
InMemoryState.Filters.Remove(filter);
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Edit(string oldFilter, string newFilter)
|
||||
public static void Edit(NamedFilter oldFilter, NamedFilter newFilter)
|
||||
{
|
||||
lock (locker)
|
||||
{
|
||||
var index = inMemoryState.Filters.IndexOf(oldFilter);
|
||||
var index = InMemoryState.Filters.IndexOf(oldFilter);
|
||||
if (index < 0)
|
||||
return;
|
||||
|
||||
inMemoryState.Filters = inMemoryState.Filters.Select(f => f == oldFilter ? newFilter : f).ToList();
|
||||
InMemoryState.Filters = InMemoryState.Filters.Select(f => f == oldFilter ? newFilter : f).ToList();
|
||||
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReplaceAll(IEnumerable<string> filters)
|
||||
public static void ReplaceAll(IEnumerable<NamedFilter> filters)
|
||||
{
|
||||
filters = filters
|
||||
.Where(f => !string.IsNullOrWhiteSpace(f))
|
||||
.Distinct()
|
||||
.Select(f => f.Trim());
|
||||
.Where(f => !string.IsNullOrWhiteSpace(f.Filter))
|
||||
.Distinct();
|
||||
foreach (var filter in filters)
|
||||
filter.Filter = filter.Filter.Trim();
|
||||
lock (locker)
|
||||
{
|
||||
inMemoryState.Filters = new List<string>(filters);
|
||||
InMemoryState.Filters = new List<NamedFilter>(filters);
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
private static object locker { get; } = new object();
|
||||
private static object locker { get; } = new();
|
||||
|
||||
// ONLY call this within lock()
|
||||
private static void save(bool invokeUpdatedEvent = true)
|
||||
{
|
||||
// create json if not exists
|
||||
void resave() => File.WriteAllText(JsonFile, JsonConvert.SerializeObject(inMemoryState, Formatting.Indented));
|
||||
void resave() => File.WriteAllText(JsonFile, JsonConvert.SerializeObject(InMemoryState, Formatting.Indented));
|
||||
try { resave(); }
|
||||
catch (IOException)
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace LibationFileManager
|
||||
try
|
||||
{
|
||||
//Currently only works for Windows and macOS
|
||||
if (!Configuration.Instance.UseCoverAsFolderIcon || Configuration.IsLinux)
|
||||
if (!Configuration.Instance.UseCoverAsFolderIcon)
|
||||
return;
|
||||
|
||||
// get path of cover art in Images dir. Download first if not exists
|
||||
|
||||
@@ -54,6 +54,8 @@ namespace LibationSearchEngine
|
||||
{ FieldType.Bool, lb => (lb.Book.UserDefinedItem.BookStatus == LiberatedStatus.Error).ToString(), "LiberatedError" },
|
||||
{ FieldType.Bool, lb => lb.Book.IsEpisodeChild().ToString(), "Podcast", "Podcasts", "IsPodcast", "Episode", "Episodes", "IsEpisode" },
|
||||
{ FieldType.Bool, lb => lb.AbsentFromLastScan.ToString(), "AbsentFromLastScan", "Absent" },
|
||||
{ FieldType.Bool, lb => (!string.IsNullOrWhiteSpace(lb.Book.SeriesNames())).ToString(), "IsInSeries", "InSeries" },
|
||||
{ FieldType.Bool, lb => lb.Book.UserDefinedItem.IsFinished.ToString(), nameof(UserDefinedItem.IsFinished), "Finished", "IsFinished" },
|
||||
// all numbers are padded to 8 char.s
|
||||
// This will allow a single method to auto-pad numbers. The method will match these as well as date: yyyymmdd
|
||||
{ FieldType.Number, lb => lb.Book.LengthInMinutes.ToLuceneString(), nameof(Book.LengthInMinutes), "Length", "Minutes" },
|
||||
|
||||
@@ -133,7 +133,7 @@ namespace LibationUiBase.GridView
|
||||
private string GetTooltip()
|
||||
{
|
||||
if (IsSeries)
|
||||
return Expanded ? "Click to Collpase" : "Click to Expand";
|
||||
return Expanded ? "Click to Collapse" : "Click to Expand";
|
||||
|
||||
if (IsUnavailable)
|
||||
return "This book cannot be downloaded\nbecause it wasn't found during\nthe most recent library scan";
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.2" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
46
Source/LibationWinForms/AccessibleDataGridViewButtonCell.cs
Normal file
46
Source/LibationWinForms/AccessibleDataGridViewButtonCell.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
public class AccessibleDataGridViewButtonCell : DataGridViewButtonCell
|
||||
{
|
||||
private string accessibilityDescription;
|
||||
|
||||
protected string AccessibilityName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get or set description for accessibility. eg: screen readers. Also sets the ToolTipText
|
||||
/// </summary>
|
||||
protected string AccessibilityDescription
|
||||
{
|
||||
get => accessibilityDescription;
|
||||
set
|
||||
{
|
||||
accessibilityDescription = value;
|
||||
ToolTipText = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override AccessibleObject CreateAccessibilityInstance() => new ButtonCellAccessibilityObject(this, name: AccessibilityName, description: AccessibilityDescription);
|
||||
|
||||
public AccessibleDataGridViewButtonCell(string accessibilityName) : base()
|
||||
{
|
||||
AccessibilityName = accessibilityName;
|
||||
}
|
||||
|
||||
protected class ButtonCellAccessibilityObject : DataGridViewButtonCellAccessibleObject
|
||||
{
|
||||
private string _name;
|
||||
public override string Name => _name;
|
||||
|
||||
private string _description;
|
||||
public override string Description => _description;
|
||||
|
||||
public ButtonCellAccessibilityObject(DataGridViewCell owner, string name, string description) : base(owner)
|
||||
{
|
||||
_name = name;
|
||||
_description = description;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
Source/LibationWinForms/AccessibleDataGridViewTextBoxCell.cs
Normal file
46
Source/LibationWinForms/AccessibleDataGridViewTextBoxCell.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
internal class AccessibleDataGridViewTextBoxCell : DataGridViewTextBoxCell
|
||||
{
|
||||
private string accessibilityDescription;
|
||||
|
||||
protected string AccessibilityName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get or set description for accessibility. eg: screen readers. Also sets the ToolTipText
|
||||
/// </summary>
|
||||
protected string AccessibilityDescription
|
||||
{
|
||||
get => accessibilityDescription;
|
||||
set
|
||||
{
|
||||
accessibilityDescription = value;
|
||||
ToolTipText = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override AccessibleObject CreateAccessibilityInstance() => new TextBoxCellAccessibilityObject(this, name: AccessibilityName, description: AccessibilityDescription);
|
||||
|
||||
public AccessibleDataGridViewTextBoxCell(string accessibilityName) : base()
|
||||
{
|
||||
AccessibilityName = accessibilityName;
|
||||
}
|
||||
|
||||
protected class TextBoxCellAccessibilityObject : DataGridViewTextBoxCellAccessibleObject
|
||||
{
|
||||
private string _name;
|
||||
public override string Name => _name;
|
||||
|
||||
private string _description;
|
||||
public override string Description => _description;
|
||||
|
||||
public TextBoxCellAccessibilityObject(DataGridViewCell owner, string name, string description) : base(owner)
|
||||
{
|
||||
_name = name;
|
||||
_description = description;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,13 +51,13 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
private void AddAccountToGrid(Account account)
|
||||
{
|
||||
int row = dataGridView1.Rows.Add(
|
||||
"X",
|
||||
"Export",
|
||||
account.LibraryScan,
|
||||
account.AccountId,
|
||||
account.Locale.Name,
|
||||
account.AccountName);
|
||||
var row = dataGridView1.Rows.Add(
|
||||
"X",
|
||||
"Export",
|
||||
account.LibraryScan,
|
||||
account.AccountId,
|
||||
account.Locale.Name,
|
||||
account.AccountName);
|
||||
|
||||
dataGridView1[COL_Export, row].ToolTipText = "Export account authorization to audible-cli";
|
||||
}
|
||||
|
||||
@@ -28,116 +28,127 @@
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.cancelBtn = new System.Windows.Forms.Button();
|
||||
this.saveBtn = new System.Windows.Forms.Button();
|
||||
this.dataGridView1 = new System.Windows.Forms.DataGridView();
|
||||
this.Original = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.Delete = new DisableButtonColumn();
|
||||
this.Filter = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.MoveUp = new DisableButtonColumn();
|
||||
this.MoveDown = new DisableButtonColumn();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit();
|
||||
this.SuspendLayout();
|
||||
cancelBtn = new System.Windows.Forms.Button();
|
||||
saveBtn = new System.Windows.Forms.Button();
|
||||
dataGridView1 = new System.Windows.Forms.DataGridView();
|
||||
Original = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
Delete = new DisableButtonColumn();
|
||||
FilterName = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
Filter = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
MoveUp = new DisableButtonColumn();
|
||||
MoveDown = new DisableButtonColumn();
|
||||
((System.ComponentModel.ISupportInitialize)dataGridView1).BeginInit();
|
||||
SuspendLayout();
|
||||
//
|
||||
// cancelBtn
|
||||
//
|
||||
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.cancelBtn.Location = new System.Drawing.Point(713, 415);
|
||||
this.cancelBtn.Name = "cancelBtn";
|
||||
this.cancelBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.cancelBtn.TabIndex = 2;
|
||||
this.cancelBtn.Text = "Cancel";
|
||||
this.cancelBtn.UseVisualStyleBackColor = true;
|
||||
this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click);
|
||||
cancelBtn.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
|
||||
cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
cancelBtn.Location = new System.Drawing.Point(1248, 726);
|
||||
cancelBtn.Margin = new System.Windows.Forms.Padding(5);
|
||||
cancelBtn.Name = "cancelBtn";
|
||||
cancelBtn.Size = new System.Drawing.Size(131, 40);
|
||||
cancelBtn.TabIndex = 2;
|
||||
cancelBtn.Text = "Cancel";
|
||||
cancelBtn.UseVisualStyleBackColor = true;
|
||||
cancelBtn.Click += cancelBtn_Click;
|
||||
//
|
||||
// saveBtn
|
||||
//
|
||||
this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.saveBtn.Location = new System.Drawing.Point(612, 415);
|
||||
this.saveBtn.Name = "saveBtn";
|
||||
this.saveBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.saveBtn.TabIndex = 1;
|
||||
this.saveBtn.Text = "Save";
|
||||
this.saveBtn.UseVisualStyleBackColor = true;
|
||||
this.saveBtn.Click += new System.EventHandler(this.saveBtn_Click);
|
||||
saveBtn.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
|
||||
saveBtn.Location = new System.Drawing.Point(1071, 726);
|
||||
saveBtn.Margin = new System.Windows.Forms.Padding(5);
|
||||
saveBtn.Name = "saveBtn";
|
||||
saveBtn.Size = new System.Drawing.Size(131, 40);
|
||||
saveBtn.TabIndex = 1;
|
||||
saveBtn.Text = "Save";
|
||||
saveBtn.UseVisualStyleBackColor = true;
|
||||
saveBtn.Click += saveBtn_Click;
|
||||
//
|
||||
// dataGridView1
|
||||
//
|
||||
this.dataGridView1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.dataGridView1.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.AllCells;
|
||||
this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
||||
this.Original,
|
||||
this.Delete,
|
||||
this.Filter,
|
||||
this.MoveUp,
|
||||
this.MoveDown});
|
||||
this.dataGridView1.Location = new System.Drawing.Point(12, 12);
|
||||
this.dataGridView1.MultiSelect = false;
|
||||
this.dataGridView1.Name = "dataGridView1";
|
||||
this.dataGridView1.Size = new System.Drawing.Size(776, 397);
|
||||
this.dataGridView1.TabIndex = 0;
|
||||
this.dataGridView1.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.DataGridView1_CellContentClick);
|
||||
this.dataGridView1.DefaultValuesNeeded += new System.Windows.Forms.DataGridViewRowEventHandler(this.dataGridView1_DefaultValuesNeeded);
|
||||
dataGridView1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
dataGridView1.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.AllCells;
|
||||
dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { Original, Delete, FilterName, Filter, MoveUp, MoveDown });
|
||||
dataGridView1.Location = new System.Drawing.Point(21, 21);
|
||||
dataGridView1.Margin = new System.Windows.Forms.Padding(5);
|
||||
dataGridView1.MultiSelect = false;
|
||||
dataGridView1.Name = "dataGridView1";
|
||||
dataGridView1.RowHeadersWidth = 72;
|
||||
dataGridView1.Size = new System.Drawing.Size(1358, 695);
|
||||
dataGridView1.TabIndex = 0;
|
||||
dataGridView1.CellContentClick += DataGridView1_CellContentClick;
|
||||
dataGridView1.DefaultValuesNeeded += dataGridView1_DefaultValuesNeeded;
|
||||
//
|
||||
// Original
|
||||
//
|
||||
this.Original.HeaderText = "Original";
|
||||
this.Original.Name = "Original";
|
||||
this.Original.ReadOnly = true;
|
||||
this.Original.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
|
||||
this.Original.Visible = false;
|
||||
this.Original.Width = 48;
|
||||
Original.HeaderText = "Original";
|
||||
Original.MinimumWidth = 9;
|
||||
Original.Name = "Original";
|
||||
Original.ReadOnly = true;
|
||||
Original.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
|
||||
Original.Visible = false;
|
||||
Original.Width = 150;
|
||||
//
|
||||
// Delete
|
||||
//
|
||||
this.Delete.HeaderText = "Delete";
|
||||
this.Delete.Name = "Delete";
|
||||
this.Delete.ReadOnly = true;
|
||||
this.Delete.Text = "x";
|
||||
this.Delete.Width = 44;
|
||||
Delete.HeaderText = "Delete";
|
||||
Delete.MinimumWidth = 9;
|
||||
Delete.Name = "Delete";
|
||||
Delete.ReadOnly = true;
|
||||
Delete.Text = "x";
|
||||
Delete.Width = 127;
|
||||
//
|
||||
// FilterName
|
||||
//
|
||||
FilterName.HeaderText = "Name";
|
||||
FilterName.MinimumWidth = 300;
|
||||
FilterName.Name = "FilterName";
|
||||
FilterName.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
|
||||
FilterName.Width = 300;
|
||||
//
|
||||
// Filter
|
||||
//
|
||||
this.Filter.HeaderText = "Filter";
|
||||
this.Filter.Name = "Filter";
|
||||
this.Filter.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
|
||||
this.Filter.Width = 35;
|
||||
Filter.HeaderText = "Filter";
|
||||
Filter.MinimumWidth = 400;
|
||||
Filter.Name = "Filter";
|
||||
Filter.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
|
||||
Filter.Width = 400;
|
||||
//
|
||||
// MoveUp
|
||||
//
|
||||
this.MoveUp.HeaderText = "Move Up";
|
||||
this.MoveUp.Name = "MoveUp";
|
||||
this.MoveUp.ReadOnly = true;
|
||||
this.MoveUp.Text = "^";
|
||||
this.MoveUp.Width = 57;
|
||||
MoveUp.HeaderText = "Move Up";
|
||||
MoveUp.MinimumWidth = 9;
|
||||
MoveUp.Name = "MoveUp";
|
||||
MoveUp.ReadOnly = true;
|
||||
MoveUp.Text = "^";
|
||||
MoveUp.Width = 169;
|
||||
//
|
||||
// MoveDown
|
||||
//
|
||||
this.MoveDown.HeaderText = "Move Down";
|
||||
this.MoveDown.Name = "MoveDown";
|
||||
this.MoveDown.ReadOnly = true;
|
||||
this.MoveDown.Text = "v";
|
||||
this.MoveDown.Width = 71;
|
||||
MoveDown.HeaderText = "Move Down";
|
||||
MoveDown.MinimumWidth = 9;
|
||||
MoveDown.Name = "MoveDown";
|
||||
MoveDown.ReadOnly = true;
|
||||
MoveDown.Text = "v";
|
||||
MoveDown.Width = 215;
|
||||
//
|
||||
// EditQuickFilters
|
||||
//
|
||||
this.AcceptButton = this.saveBtn;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
|
||||
this.CancelButton = this.cancelBtn;
|
||||
this.ClientSize = new System.Drawing.Size(800, 450);
|
||||
this.Controls.Add(this.dataGridView1);
|
||||
this.Controls.Add(this.cancelBtn);
|
||||
this.Controls.Add(this.saveBtn);
|
||||
this.Name = "EditQuickFilters";
|
||||
this.Text = "Edit Quick Filters";
|
||||
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit();
|
||||
this.ResumeLayout(false);
|
||||
|
||||
AcceptButton = saveBtn;
|
||||
AutoScaleDimensions = new System.Drawing.SizeF(168F, 168F);
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
|
||||
CancelButton = cancelBtn;
|
||||
ClientSize = new System.Drawing.Size(1400, 788);
|
||||
Controls.Add(dataGridView1);
|
||||
Controls.Add(cancelBtn);
|
||||
Controls.Add(saveBtn);
|
||||
Margin = new System.Windows.Forms.Padding(5);
|
||||
Name = "EditQuickFilters";
|
||||
Text = "Edit Quick Filters";
|
||||
((System.ComponentModel.ISupportInitialize)dataGridView1).EndInit();
|
||||
ResumeLayout(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -146,6 +157,7 @@
|
||||
private System.Windows.Forms.DataGridView dataGridView1;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn Original;
|
||||
private DisableButtonColumn Delete;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn FilterName;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn Filter;
|
||||
private DisableButtonColumn MoveUp;
|
||||
private DisableButtonColumn MoveDown;
|
||||
|
||||
@@ -6,14 +6,6 @@ using LibationFileManager;
|
||||
|
||||
namespace LibationWinForms.Dialogs
|
||||
{
|
||||
public class DisableButtonColumn : DataGridViewButtonColumn
|
||||
{
|
||||
public DisableButtonColumn()
|
||||
{
|
||||
CellTemplate = new EditQuickFilters.DisableButtonCell();
|
||||
}
|
||||
}
|
||||
|
||||
public partial class EditQuickFilters : Form
|
||||
{
|
||||
private const string BLACK_UP_POINTING_TRIANGLE = "\u25B2";
|
||||
@@ -21,26 +13,43 @@ namespace LibationWinForms.Dialogs
|
||||
private const string COL_Original = nameof(Original);
|
||||
private const string COL_Delete = nameof(Delete);
|
||||
private const string COL_Filter = nameof(Filter);
|
||||
private const string COL_MoveUp = nameof(MoveUp);
|
||||
private const string COL_FilterName = nameof(FilterName);
|
||||
private const string COL_MoveUp = nameof(MoveUp);
|
||||
private const string COL_MoveDown = nameof(MoveDown);
|
||||
|
||||
internal class DisableButtonCell : DataGridViewButtonCell
|
||||
{
|
||||
protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
|
||||
internal class DisableButtonCell : AccessibleDataGridViewButtonCell
|
||||
{
|
||||
private int LastRowIndex => DataGridView.Rows[^1].IsNewRow ? DataGridView.Rows[^1].Index - 1 : DataGridView.Rows[^1].Index;
|
||||
|
||||
public DisableButtonCell() : base("Edit Filter button") { }
|
||||
|
||||
protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
|
||||
{
|
||||
if ((OwningColumn.Name == COL_MoveUp && rowIndex == 0)
|
||||
|| (OwningColumn.Name == COL_MoveDown && rowIndex == LastRowIndex)
|
||||
|| OwningRow.IsNewRow)
|
||||
{
|
||||
var isMoveUp = OwningColumn.Name == COL_MoveUp;
|
||||
var isMoveDown = OwningColumn.Name == COL_MoveDown;
|
||||
var isDelete = OwningColumn.Name == COL_Delete;
|
||||
var isNewRow = OwningRow.IsNewRow;
|
||||
|
||||
if (isNewRow
|
||||
|| (isMoveUp && rowIndex == 0)
|
||||
|| (isMoveDown && rowIndex == LastRowIndex))
|
||||
{
|
||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts ^ (DataGridViewPaintParts.ContentBackground | DataGridViewPaintParts.ContentForeground | DataGridViewPaintParts.SelectionBackground));
|
||||
|
||||
ButtonRenderer.DrawButton(graphics, cellBounds, value as string, cellStyle.Font, false, System.Windows.Forms.VisualStyles.PushButtonState.Disabled);
|
||||
}
|
||||
}
|
||||
else
|
||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts);
|
||||
}
|
||||
{
|
||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts);
|
||||
|
||||
int LastRowIndex => DataGridView.Rows[^1].IsNewRow ? DataGridView.Rows[^1].Index - 1 : DataGridView.Rows[^1].Index;
|
||||
if (isMoveUp)
|
||||
AccessibilityDescription = "Move up";
|
||||
else if (isMoveDown)
|
||||
AccessibilityDescription = "Move down";
|
||||
else if (isDelete)
|
||||
AccessibilityDescription = "Delete";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public EditQuickFilters()
|
||||
@@ -60,10 +69,11 @@ namespace LibationWinForms.Dialogs
|
||||
return;
|
||||
|
||||
foreach (var filter in filters)
|
||||
dataGridView1.Rows.Add(filter, "X", filter, BLACK_UP_POINTING_TRIANGLE, BLACK_DOWN_POINTING_TRIANGLE);
|
||||
}
|
||||
dataGridView1.Rows.Add(filter.Filter, "X", filter.Name, filter.Filter, BLACK_UP_POINTING_TRIANGLE, BLACK_DOWN_POINTING_TRIANGLE);
|
||||
//dataGridView1.Rows.Add(filter, "X", filter, BLACK_UP_POINTING_TRIANGLE, BLACK_DOWN_POINTING_TRIANGLE);
|
||||
}
|
||||
|
||||
private void dataGridView1_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e)
|
||||
private void dataGridView1_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e)
|
||||
{
|
||||
e.Row.Cells[COL_Delete].Value = "X";
|
||||
e.Row.Cells[COL_MoveUp].Value = BLACK_UP_POINTING_TRIANGLE;
|
||||
@@ -74,7 +84,9 @@ namespace LibationWinForms.Dialogs
|
||||
{
|
||||
var list = dataGridView1.Rows
|
||||
.OfType<DataGridViewRow>()
|
||||
.Select(r => r.Cells[COL_Filter].Value?.ToString())
|
||||
.Select(r => new QuickFilters.NamedFilter(
|
||||
r.Cells[COL_Filter].Value?.ToString(),
|
||||
r.Cells[COL_FilterName].Value?.ToString()))
|
||||
.ToList();
|
||||
QuickFilters.ReplaceAll(list);
|
||||
|
||||
@@ -121,4 +133,12 @@ namespace LibationWinForms.Dialogs
|
||||
}
|
||||
}
|
||||
}
|
||||
public class DisableButtonColumn : DataGridViewButtonColumn
|
||||
{
|
||||
public DisableButtonColumn()
|
||||
{
|
||||
CellTemplate = new EditQuickFilters.DisableButtonCell();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
@@ -61,16 +120,10 @@
|
||||
<metadata name="Original.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="Delete.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<metadata name="FilterName.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="Filter.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="MoveUp.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="MoveDown.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
</root>
|
||||
@@ -34,9 +34,9 @@ namespace LibationWinForms
|
||||
var quickFilterMenuItem = new ToolStripMenuItem
|
||||
{
|
||||
Tag = quickFilterTag,
|
||||
Text = $"&{++index}: {filter}"
|
||||
Text = $"&{++index}: {(string.IsNullOrWhiteSpace(filter.Name) ? filter.Filter : filter.Name)}"
|
||||
};
|
||||
quickFilterMenuItem.Click += (_, __) => performFilter(filter);
|
||||
quickFilterMenuItem.Click += (_, __) => performFilter(filter.Filter);
|
||||
quickFiltersToolStripMenuItem.DropDownItems.Add(quickFilterMenuItem);
|
||||
}
|
||||
}
|
||||
@@ -47,14 +47,17 @@ namespace LibationWinForms
|
||||
private void firstFilterIsDefaultToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
=> QuickFilters.UseDefault = !firstFilterIsDefaultToolStripMenuItem.Checked;
|
||||
|
||||
private void addQuickFilterBtn_Click(object sender, EventArgs e) => QuickFilters.Add(this.filterSearchTb.Text);
|
||||
private void addQuickFilterBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
QuickFilters.Add(new QuickFilters.NamedFilter(this.filterSearchTb.Text, null));
|
||||
}
|
||||
|
||||
private void editQuickFiltersToolStripMenuItem_Click(object sender, EventArgs e) => new EditQuickFilters().ShowDialog();
|
||||
private void editQuickFiltersToolStripMenuItem_Click(object sender, EventArgs e) => new EditQuickFilters().ShowDialog();
|
||||
|
||||
private void productsDisplay_InitialLoaded(object sender, EventArgs e)
|
||||
{
|
||||
if (QuickFilters.UseDefault)
|
||||
performFilter(QuickFilters.Filters.FirstOrDefault());
|
||||
performFilter(QuickFilters.Filters.FirstOrDefault().Filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms.GridView
|
||||
{
|
||||
public class DataGridViewImageButtonCell : DataGridViewButtonCell
|
||||
{
|
||||
protected void DrawButtonImage(Graphics graphics, Image image, Rectangle cellBounds)
|
||||
public class DataGridViewImageButtonCell : AccessibleDataGridViewButtonCell
|
||||
{
|
||||
public DataGridViewImageButtonCell(string accessibilityName) : base(accessibilityName) { }
|
||||
|
||||
protected void DrawButtonImage(Graphics graphics, Image image, Rectangle cellBounds)
|
||||
{
|
||||
var scaleFactor = OwningColumn is IDataGridScaleColumn scCol ? scCol.ScaleFactor : 1f;
|
||||
|
||||
|
||||
@@ -20,20 +20,33 @@ namespace LibationWinForms.GridView
|
||||
}
|
||||
|
||||
internal class EditTagsDataGridViewImageButtonCell : DataGridViewImageButtonCell
|
||||
{
|
||||
private static Image ButtonImage { get; } = Properties.Resources.edit_25x25;
|
||||
{
|
||||
public EditTagsDataGridViewImageButtonCell() : base("Edit Tags button") { }
|
||||
|
||||
private static Image ButtonImage { get; } = Properties.Resources.edit_25x25;
|
||||
|
||||
protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
|
||||
{
|
||||
if (rowIndex >= 0 && DataGridView.GetBoundItem<IGridEntry>(rowIndex) is ISeriesEntry)
|
||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, DataGridViewPaintParts.Background | DataGridViewPaintParts.Border);
|
||||
else if (value is string tagStr && tagStr.Length == 0)
|
||||
// series
|
||||
if (rowIndex >= 0 && DataGridView.GetBoundItem<IGridEntry>(rowIndex) is ISeriesEntry)
|
||||
{
|
||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, DataGridViewPaintParts.Background | DataGridViewPaintParts.Border);
|
||||
}
|
||||
// tag: empty
|
||||
else if (value is string tagStr && tagStr.Length == 0)
|
||||
{
|
||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts);
|
||||
|
||||
DrawButtonImage(graphics, ButtonImage, cellBounds);
|
||||
}
|
||||
AccessibilityDescription = "Click to edit tags";
|
||||
}
|
||||
// tag: not empty
|
||||
else
|
||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts);
|
||||
}
|
||||
{
|
||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts);
|
||||
|
||||
AccessibilityDescription = (string)value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using LibationUiBase.GridView;
|
||||
using System;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using LibationUiBase.GridView;
|
||||
|
||||
namespace LibationWinForms.GridView
|
||||
{
|
||||
@@ -21,15 +21,19 @@ namespace LibationWinForms.GridView
|
||||
}
|
||||
}
|
||||
|
||||
internal class LastDownloadedGridViewCell : DataGridViewTextBoxCell
|
||||
{
|
||||
internal class LastDownloadedGridViewCell : AccessibleDataGridViewTextBoxCell
|
||||
{
|
||||
private LastDownloadStatus LastDownload => (LastDownloadStatus)Value;
|
||||
protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
|
||||
|
||||
public LastDownloadedGridViewCell() : base("Last Downloaded") { }
|
||||
|
||||
protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
|
||||
{
|
||||
if (value is LastDownloadStatus lastDl)
|
||||
ToolTipText = lastDl.ToolTipText;
|
||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts);
|
||||
}
|
||||
|
||||
if (value is LastDownloadStatus lastDl)
|
||||
AccessibilityDescription = lastDl.ToolTipText;
|
||||
}
|
||||
|
||||
protected override void OnDoubleClick(DataGridViewCellEventArgs e)
|
||||
{
|
||||
|
||||
@@ -16,7 +16,9 @@ namespace LibationWinForms.GridView
|
||||
|
||||
internal class LiberateDataGridViewImageButtonCell : DataGridViewImageButtonCell
|
||||
{
|
||||
private static readonly Brush DISABLED_GRAY = new SolidBrush(Color.FromArgb(0x60, Color.LightGray));
|
||||
public LiberateDataGridViewImageButtonCell() : base("Liberate button") { }
|
||||
|
||||
private static readonly Brush DISABLED_GRAY = new SolidBrush(Color.FromArgb(0x60, Color.LightGray));
|
||||
private static readonly Color HiddenForeColor = Color.LightGray;
|
||||
protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
|
||||
{
|
||||
@@ -31,7 +33,7 @@ namespace LibationWinForms.GridView
|
||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts);
|
||||
|
||||
DrawButtonImage(graphics, (Image)status.ButtonImage, cellBounds);
|
||||
ToolTipText = status.ToolTip;
|
||||
AccessibilityDescription = status.ToolTip;
|
||||
|
||||
if (status.IsUnavailable || status.Opacity < 1)
|
||||
graphics.FillRectangle(DISABLED_GRAY, cellBounds);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using DataLayer;
|
||||
using System;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using DataLayer;
|
||||
|
||||
namespace LibationWinForms.GridView
|
||||
{
|
||||
@@ -24,14 +24,17 @@ namespace LibationWinForms.GridView
|
||||
}
|
||||
}
|
||||
|
||||
internal class MyRatingGridViewCell : DataGridViewTextBoxCell
|
||||
{
|
||||
internal class MyRatingGridViewCell : AccessibleDataGridViewTextBoxCell
|
||||
{
|
||||
private static Rating DefaultRating => new Rating(0, 0, 0);
|
||||
public override object DefaultNewRowValue => DefaultRating;
|
||||
public override Type EditType => typeof(MyRatingCellEditor);
|
||||
public override Type ValueType => typeof(Rating);
|
||||
|
||||
public MyRatingGridViewCell() { ToolTipText = ReadOnly ? "" : "Click to change ratings"; }
|
||||
public MyRatingGridViewCell() : base("My Rating")
|
||||
{
|
||||
AccessibilityDescription = ReadOnly ? "" : "Click to change ratings";
|
||||
}
|
||||
|
||||
public override void InitializeEditingControl(int rowIndex, object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
|
||||
{
|
||||
@@ -46,14 +49,14 @@ namespace LibationWinForms.GridView
|
||||
{
|
||||
if (value is Rating rating)
|
||||
{
|
||||
ToolTipText = ReadOnly ? "" : "Click to change ratings";
|
||||
|
||||
var starString = rating.ToStarString();
|
||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, starString, starString, errorText, cellStyle, advancedBorderStyle, paintParts);
|
||||
}
|
||||
else
|
||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, string.Empty, string.Empty, errorText, cellStyle, advancedBorderStyle, paintParts);
|
||||
}
|
||||
|
||||
AccessibilityDescription = ReadOnly ? "" : "Click to change ratings";
|
||||
}
|
||||
else
|
||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, string.Empty, string.Empty, errorText, cellStyle, advancedBorderStyle, paintParts);
|
||||
}
|
||||
|
||||
protected override object GetFormattedValue(object value, int rowIndex, ref DataGridViewCellStyle cellStyle, TypeConverter valueTypeConverter, TypeConverter formattedValueTypeConverter, DataGridViewDataErrorContexts context)
|
||||
=> value is Rating rating ? rating.ToStarString() : value?.ToString();
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
<!-- Version is now in AppScaffolding.csproj -->
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<RuntimeHostConfigurationOption Include="System.Net.DisableIPv6" Value="true" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -13,37 +13,40 @@ namespace LibationWinForms.SeriesView
|
||||
CellTemplate.Style.WrapMode = DataGridViewTriState.True;
|
||||
}
|
||||
}
|
||||
internal class DownloadButtonColumnCell : DataGridViewButtonCell
|
||||
{
|
||||
protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
|
||||
{
|
||||
if (value is SeriesButton sentry)
|
||||
{
|
||||
string cellValue = sentry.DisplayText;
|
||||
if (!sentry.Enabled)
|
||||
{
|
||||
//Draw disabled button
|
||||
Rectangle buttonArea = cellBounds;
|
||||
Rectangle buttonAdjustment = BorderWidths(advancedBorderStyle);
|
||||
buttonArea.X += buttonAdjustment.X;
|
||||
buttonArea.Y += buttonAdjustment.Y;
|
||||
buttonArea.Height -= buttonAdjustment.Height;
|
||||
buttonArea.Width -= buttonAdjustment.Width;
|
||||
ButtonRenderer.DrawButton(graphics, buttonArea, cellValue, cellStyle.Font, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.WordBreak, focused: false, PushButtonState.Disabled);
|
||||
internal class DownloadButtonColumnCell : AccessibleDataGridViewButtonCell
|
||||
{
|
||||
public DownloadButtonColumnCell() : base("Download Series button") { }
|
||||
|
||||
}
|
||||
else if (sentry.HasButtonAction)
|
||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, cellValue, cellValue, errorText, cellStyle, advancedBorderStyle, paintParts);
|
||||
else
|
||||
{
|
||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, DataGridViewPaintParts.Background | DataGridViewPaintParts.Border);
|
||||
TextRenderer.DrawText(graphics, cellValue, cellStyle.Font, cellBounds, cellStyle.ForeColor);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, DataGridViewPaintParts.Background | DataGridViewPaintParts.Border);
|
||||
}
|
||||
}
|
||||
protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
|
||||
{
|
||||
if (value is not SeriesButton seriesEntry)
|
||||
{
|
||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, DataGridViewPaintParts.Background | DataGridViewPaintParts.Border);
|
||||
return;
|
||||
}
|
||||
|
||||
string cellValue = seriesEntry.DisplayText;
|
||||
AccessibilityDescription = cellValue;
|
||||
|
||||
if (!seriesEntry.Enabled)
|
||||
{
|
||||
//Draw disabled button
|
||||
Rectangle buttonArea = cellBounds;
|
||||
Rectangle buttonAdjustment = BorderWidths(advancedBorderStyle);
|
||||
buttonArea.X += buttonAdjustment.X;
|
||||
buttonArea.Y += buttonAdjustment.Y;
|
||||
buttonArea.Height -= buttonAdjustment.Height;
|
||||
buttonArea.Width -= buttonAdjustment.Width;
|
||||
|
||||
ButtonRenderer.DrawButton(graphics, buttonArea, cellValue, cellStyle.Font, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.WordBreak, focused: false, PushButtonState.Disabled);
|
||||
}
|
||||
else if (seriesEntry.HasButtonAction)
|
||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, cellValue, cellValue, errorText, cellStyle, advancedBorderStyle, paintParts);
|
||||
else
|
||||
{
|
||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, DataGridViewPaintParts.Background | DataGridViewPaintParts.Border);
|
||||
TextRenderer.DrawText(graphics, cellValue, cellStyle.Font, cellBounds, cellStyle.ForeColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2210.55" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2478.35" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.6.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.6.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.6.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.6.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.6.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.6.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.6.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.6.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.6.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.6.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
Reference in New Issue
Block a user