Merge pull request #405 from Mbucari/master
Upgraded to Avalonia 11-Preview4
This commit is contained in:
commit
451af7bea9
@ -12,10 +12,10 @@ namespace FileManager
|
|||||||
public const int FIXED_COUNT = 6;
|
public const int FIXED_COUNT = 6;
|
||||||
|
|
||||||
internal const char QUOTE_MARK = '"';
|
internal const char QUOTE_MARK = '"';
|
||||||
[JsonIgnore] public bool Mandatory { get; internal set; }
|
[JsonIgnore] public bool Mandatory { get; set; }
|
||||||
[JsonProperty] public char CharacterToReplace { get; private set; }
|
[JsonProperty] public char CharacterToReplace { get; private set; }
|
||||||
[JsonProperty] public string ReplacementString { get; set; }
|
[JsonProperty] public string ReplacementString { get; private set; }
|
||||||
[JsonProperty] public string Description { get; private set; }
|
[JsonProperty] public string Description { get; set; }
|
||||||
public override string ToString() => $"{CharacterToReplace} → {ReplacementString} ({Description})";
|
public override string ToString() => $"{CharacterToReplace} → {ReplacementString} ({Description})";
|
||||||
|
|
||||||
public Replacement(char charToReplace, string replacementString, string description)
|
public Replacement(char charToReplace, string replacementString, string description)
|
||||||
@ -169,9 +169,9 @@ namespace FileManager
|
|||||||
|
|
||||||
|
|
||||||
public static bool ContainsInvalidPathChar(string path)
|
public static bool ContainsInvalidPathChar(string path)
|
||||||
=> path.Any(c => invalidChars.Contains(c));
|
=> path.Any(c => invalidChars?.Contains(c) == true);
|
||||||
public static bool ContainsInvalidFilenameChar(string path)
|
public static bool ContainsInvalidFilenameChar(string path)
|
||||||
=> path.Any(c => invalidChars.Concat(new char[] { '\\', '/' }).Contains(c));
|
=> path.Any(c => invalidChars?.Concat(new char[] { '\\', '/' })?.Contains(c) == true);
|
||||||
|
|
||||||
public string ReplaceInvalidFilenameChars(string fileName)
|
public string ReplaceInvalidFilenameChars(string fileName)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -63,11 +63,13 @@
|
|||||||
<TrimmableAssembly Include="Avalonia.Themes.Default" />
|
<TrimmableAssembly Include="Avalonia.Themes.Default" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia" Version="0.10.18" />
|
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
|
<PackageReference Include="Avalonia" Version="11.0.0-preview4" />
|
||||||
|
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-preview4" />
|
||||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.18" />
|
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-preview4" />
|
||||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.18" />
|
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview4" />
|
||||||
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-preview4" />
|
||||||
<PackageReference Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
<PackageReference Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -8,9 +8,9 @@
|
|||||||
</Application.DataTemplates>
|
</Application.DataTemplates>
|
||||||
|
|
||||||
<Application.Styles>
|
<Application.Styles>
|
||||||
<FluentTheme Mode="Light"/>
|
<FluentTheme Mode="Light"/>
|
||||||
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
|
<StyleInclude Source="avares://Avalonia.Themes.Fluent/FluentLight.xaml"/>
|
||||||
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
|
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml"/>
|
||||||
<StyleInclude Source="/Assets/DataGridTheme.xaml"/>
|
<StyleInclude Source="/Assets/DataGridTheme.xaml"/>
|
||||||
<StyleInclude Source="/Assets/LibationStyles.xaml"/>
|
<StyleInclude Source="/Assets/LibationStyles.xaml"/>
|
||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
|
|||||||
@ -42,9 +42,6 @@ namespace LibationAvalonia
|
|||||||
{
|
{
|
||||||
LoadStyles();
|
LoadStyles();
|
||||||
|
|
||||||
var SEGOEUI = new Typeface(new FontFamily(new Uri("avares://Libation/Assets/WINGDING.TTF"), "SEGOEUI_Local"));
|
|
||||||
var gtf = FontManager.Current.GetOrAddGlyphTypeface(SEGOEUI);
|
|
||||||
|
|
||||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
if (SetupRequired)
|
if (SetupRequired)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -15,15 +15,15 @@ namespace LibationAvalonia.Controls
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
protected override void OnPointerEnter(PointerEventArgs e)
|
protected override void OnPointerEntered(PointerEventArgs e)
|
||||||
{
|
{
|
||||||
this.Cursor = HandCursor;
|
this.Cursor = HandCursor;
|
||||||
base.OnPointerEnter(e);
|
base.OnPointerEntered(e);
|
||||||
}
|
}
|
||||||
protected override void OnPointerLeave(PointerEventArgs e)
|
protected override void OnPointerExited(PointerEventArgs e)
|
||||||
{
|
{
|
||||||
this.Cursor = Cursor.Default;
|
this.Cursor = Cursor.Default;
|
||||||
base.OnPointerLeave(e);
|
base.OnPointerExited(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
|
|||||||
@ -10,6 +10,7 @@ using LibationAvalonia.ViewModels;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace LibationAvalonia.Dialogs
|
namespace LibationAvalonia.Dialogs
|
||||||
{
|
{
|
||||||
@ -54,7 +55,7 @@ namespace LibationAvalonia.Dialogs
|
|||||||
base.SaveAndClose();
|
base.SaveAndClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GoToAudible_Tapped(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
public void GoToAudible_Tapped(object sender, Avalonia.Input.TappedEventArgs e)
|
||||||
{
|
{
|
||||||
var locale = AudibleApi.Localization.Get(_libraryBook.Book.Locale);
|
var locale = AudibleApi.Localization.Get(_libraryBook.Book.Locale);
|
||||||
var link = $"https://www.audible.{locale.TopDomain}/pd/{_libraryBook.Book.AudibleProductId}";
|
var link = $"https://www.audible.{locale.TopDomain}/pd/{_libraryBook.Book.AudibleProductId}";
|
||||||
|
|||||||
@ -2,60 +2,71 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
|
||||||
|
MinWidth="500" MinHeight="450"
|
||||||
x:Class="LibationAvalonia.Dialogs.EditReplacementChars"
|
x:Class="LibationAvalonia.Dialogs.EditReplacementChars"
|
||||||
Title="EditReplacementChars">
|
Title="Illegal Character Replacement"
|
||||||
|
Icon="/Assets/libation.ico">
|
||||||
|
|
||||||
<DataGrid
|
<Grid
|
||||||
GridLinesVisibility="All"
|
RowDefinitions="*,Auto"
|
||||||
AutoGenerateColumns="False"
|
ColumnDefinitions="*,Auto">
|
||||||
Items="{Binding replacements}">
|
|
||||||
|
<DataGrid
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
GridLinesVisibility="All"
|
||||||
|
Margin="5"
|
||||||
|
Name="replacementGrid"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
IsReadOnly="False"
|
||||||
|
BeginningEdit="ReplacementGrid_BeginningEdit"
|
||||||
|
CellEditEnding="ReplacementGrid_CellEditEnding"
|
||||||
|
KeyDown="ReplacementGrid_KeyDown"
|
||||||
|
Items="{Binding replacements}">
|
||||||
|
|
||||||
|
<DataGrid.Columns>
|
||||||
|
|
||||||
|
<DataGridTextColumn
|
||||||
|
IsReadOnly="False"
|
||||||
|
Binding="{Binding CharacterToReplace, Mode=TwoWay}"
|
||||||
|
Header="Char to
Replace"/>
|
||||||
|
|
||||||
|
<DataGridTextColumn
|
||||||
|
IsReadOnly="False"
|
||||||
|
Binding="{Binding ReplacementText, Mode=TwoWay}"
|
||||||
|
Header="Replacement
Text"/>
|
||||||
|
|
||||||
|
<DataGridTextColumn Width="*"
|
||||||
|
IsReadOnly="False"
|
||||||
|
Binding="{Binding Description, Mode=TwoWay}"
|
||||||
|
Header="Description"/>
|
||||||
|
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
|
||||||
|
<StackPanel
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
Margin="5"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
|
||||||
|
<Button Margin="0,0,10,0" Click="Defaults_Click" Content="Defaults" />
|
||||||
|
<Button Margin="0,0,10,0" Click="LoFiDefaults_Click" Content="LoFi Defaults" />
|
||||||
|
<Button Click="Barebones_Click" Content="Barebones" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="5"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
|
||||||
|
<Button Margin="0,0,10,0" Click="Cancel_Click" Content="Cancel" />
|
||||||
|
<Button Padding="20,5,20,6" Click="Save_Click" Content="Save" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<DataGrid.Columns>
|
</Grid>
|
||||||
|
|
||||||
<DataGridTemplateColumn Width="Auto" Header="Char to
Replace">
|
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<TextPresenter
|
|
||||||
Height="18"
|
|
||||||
Margin="10,0,10,0"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
FontFamily="SEGOEUI_Local"
|
|
||||||
Text="{Binding Replacement.CharacterToReplace}" />
|
|
||||||
</DataTemplate>
|
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
|
||||||
</DataGridTemplateColumn>
|
|
||||||
|
|
||||||
<DataGridTemplateColumn IsReadOnly="False" Width="Auto" Header="Replacement Text">
|
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<Grid RowDefinitions="*" ColumnDefinitions="*">
|
|
||||||
|
|
||||||
<TextBox
|
|
||||||
Grid.Column="0"
|
|
||||||
Grid.Row="0"
|
|
||||||
VerticalAlignment="Stretch"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
FontSize="14"
|
|
||||||
FontFamily="SEGOEUI_Local"
|
|
||||||
Foreground="{StaticResource SystemControlTransparentBrush}"
|
|
||||||
SelectionBrush="{StaticResource SystemControlTransparentBrush}"
|
|
||||||
BorderBrush="{StaticResource SystemControlTransparentBrush}"
|
|
||||||
Text="{Binding ReplacementText, Mode=TwoWay}" />
|
|
||||||
<TextBlock
|
|
||||||
Grid.Column="0"
|
|
||||||
Grid.Row="0"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
VerticalAlignment="Stretch"
|
|
||||||
FontSize="14"
|
|
||||||
FontFamily="SEGOEUI_Local"
|
|
||||||
Text="{Binding ReplacementText}" />
|
|
||||||
</Grid>
|
|
||||||
</DataTemplate>
|
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
|
||||||
</DataGridTemplateColumn>
|
|
||||||
|
|
||||||
</DataGrid.Columns>
|
|
||||||
</DataGrid>
|
|
||||||
|
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@ -1,54 +1,179 @@
|
|||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using FileManager;
|
using FileManager;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Avalonia.Collections;
|
||||||
|
using Avalonia.Data;
|
||||||
|
|
||||||
namespace LibationAvalonia.Dialogs
|
namespace LibationAvalonia.Dialogs
|
||||||
{
|
{
|
||||||
public partial class EditReplacementChars : DialogWindow
|
public partial class EditReplacementChars : DialogWindow
|
||||||
{
|
{
|
||||||
Configuration config = Configuration.Instance;
|
Configuration config;
|
||||||
public ObservableCollection<ReplacementsExt> replacements { get; }
|
|
||||||
|
private readonly List<ReplacementsExt> SOURCE = new();
|
||||||
|
public DataGridCollectionView replacements { get; }
|
||||||
public EditReplacementChars()
|
public EditReplacementChars()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
if (Design.IsDesignMode)
|
replacements = new(SOURCE);
|
||||||
AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists();
|
|
||||||
|
if (Design.IsDesignMode)
|
||||||
|
{
|
||||||
|
LoadTable(ReplacementCharacters.Default.Replacements);
|
||||||
|
}
|
||||||
|
|
||||||
replacements = new(config.ReplacementCharacters.Replacements.Select(r => new ReplacementsExt { Replacement = r }));
|
|
||||||
DataContext = this;
|
DataContext = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EditReplacementChars(Configuration config) : this()
|
||||||
|
{
|
||||||
|
this.config = config;
|
||||||
|
LoadTable(config.ReplacementCharacters.Replacements);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Defaults_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
|
=> LoadTable(ReplacementCharacters.Default.Replacements);
|
||||||
|
public void LoFiDefaults_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
|
=> LoadTable(ReplacementCharacters.LoFiDefault.Replacements);
|
||||||
|
public void Barebones_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
|
=> LoadTable(ReplacementCharacters.Barebones.Replacements);
|
||||||
|
public void Save_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
|
=> SaveAndClose();
|
||||||
|
public void Cancel_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
|
=> Close();
|
||||||
|
|
||||||
|
protected override void SaveAndClose()
|
||||||
|
{
|
||||||
|
var replacements = SOURCE
|
||||||
|
.Where(r=> !r.IsDefault)
|
||||||
|
.Select(r => new Replacement(r.Character, r.ReplacementText, r.Description) { Mandatory = r.Mandatory })
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (config is not null)
|
||||||
|
config.ReplacementCharacters = new ReplacementCharacters { Replacements = replacements };
|
||||||
|
base.SaveAndClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadTable(IReadOnlyList<Replacement> replacements)
|
||||||
|
{
|
||||||
|
SOURCE.Clear();
|
||||||
|
SOURCE.AddRange(replacements.Select(r => new ReplacementsExt(r)));
|
||||||
|
SOURCE.Add(new ReplacementsExt());
|
||||||
|
this.replacements.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReplacementGrid_KeyDown(object sender, Avalonia.Input.KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key == Avalonia.Input.Key.Delete
|
||||||
|
&& ((DataGrid)sender).SelectedItem is ReplacementsExt repl
|
||||||
|
&& !repl.Mandatory
|
||||||
|
&& !repl.IsDefault)
|
||||||
|
{
|
||||||
|
replacements.Remove(repl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReplacementGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
|
||||||
|
{
|
||||||
|
var replacement = e.Row.DataContext as ReplacementsExt;
|
||||||
|
var colBinding = columnBindingPath(e.Column);
|
||||||
|
|
||||||
|
//Prevent duplicate CharacterToReplace
|
||||||
|
if (e.EditingElement is TextBox tbox
|
||||||
|
&& colBinding == nameof(replacement.CharacterToReplace)
|
||||||
|
&& SOURCE.Any(r => r != replacement && r.CharacterToReplace == tbox.Text))
|
||||||
|
{
|
||||||
|
tbox.Text = replacement.CharacterToReplace;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add new blank row
|
||||||
|
void Replacement_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!SOURCE.Any(r => r.IsDefault))
|
||||||
|
{
|
||||||
|
var rewRepl = new ReplacementsExt();
|
||||||
|
SOURCE.Add(rewRepl);
|
||||||
|
}
|
||||||
|
replacement.PropertyChanged -= Replacement_PropertyChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
replacement.PropertyChanged += Replacement_PropertyChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReplacementGrid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
|
||||||
|
{
|
||||||
|
var replacement = e.Row.DataContext as ReplacementsExt;
|
||||||
|
|
||||||
|
//Disallow editing of Mandatory CharacterToReplace and Descriptions
|
||||||
|
if (replacement.Mandatory
|
||||||
|
&& columnBindingPath(e.Column) != nameof(replacement.ReplacementText))
|
||||||
|
e.Cancel = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string columnBindingPath(DataGridColumn column)
|
||||||
|
=> ((Binding)((DataGridBoundColumn)column).Binding).Path;
|
||||||
|
|
||||||
public class ReplacementsExt : ViewModels.ViewModelBase
|
public class ReplacementsExt : ViewModels.ViewModelBase
|
||||||
{
|
{
|
||||||
public Replacement Replacement { get; init; }
|
public ReplacementsExt()
|
||||||
|
{
|
||||||
|
_replacementText = string.Empty;
|
||||||
|
_description = string.Empty;
|
||||||
|
_characterToReplace = string.Empty;
|
||||||
|
IsDefault = true;
|
||||||
|
}
|
||||||
|
public ReplacementsExt(Replacement replacement)
|
||||||
|
{
|
||||||
|
_characterToReplace = replacement.CharacterToReplace == default ? "" : replacement.CharacterToReplace.ToString();
|
||||||
|
_replacementText = replacement.ReplacementString;
|
||||||
|
_description = replacement.Description;
|
||||||
|
Mandatory = replacement.Mandatory;
|
||||||
|
}
|
||||||
|
private string _replacementText;
|
||||||
|
private string _description;
|
||||||
|
private string _characterToReplace;
|
||||||
|
public bool Mandatory { get; }
|
||||||
public string ReplacementText
|
public string ReplacementText
|
||||||
{
|
{
|
||||||
get => Replacement.ReplacementString;
|
get => _replacementText;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
Replacement.ReplacementString = value;
|
if (ReplacementCharacters.ContainsInvalidPathChar(value))
|
||||||
this.RaisePropertyChanged(nameof(ReplacementText));
|
this.RaisePropertyChanged(nameof(ReplacementText));
|
||||||
|
else
|
||||||
|
this.RaiseAndSetIfChanged(ref _replacementText, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string Description { get => _description; set => this.RaiseAndSetIfChanged(ref _description, value); }
|
||||||
|
|
||||||
|
public string CharacterToReplace
|
||||||
|
{
|
||||||
|
get => _characterToReplace;
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value?.Length != 1 || !ReplacementCharacters.ContainsInvalidPathChar(value))
|
||||||
|
this.RaisePropertyChanged(nameof(CharacterToReplace));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IsDefault = false;
|
||||||
|
this.RaiseAndSetIfChanged(ref _characterToReplace, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public char Character => string.IsNullOrEmpty(_characterToReplace) ? default : _characterToReplace[0];
|
||||||
|
public bool IsDefault { get; private set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void LoadTable(IReadOnlyList<Replacement> replacements)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ namespace LibationAvalonia.Dialogs.Login
|
|||||||
DataContext = this;
|
DataContext = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void ExternalLoginLink_Tapped(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
public async void ExternalLoginLink_Tapped(object sender, Avalonia.Input.TappedEventArgs e)
|
||||||
{
|
{
|
||||||
LoginMethod = LoginMethod.External;
|
LoginMethod = LoginMethod.External;
|
||||||
await SaveAndCloseAsync();
|
await SaveAndCloseAsync();
|
||||||
|
|||||||
@ -28,7 +28,7 @@ namespace LibationAvalonia.Dialogs
|
|||||||
DataContext = this;
|
DataContext = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void GoToGithub_Tapped(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
private async void GoToGithub_Tapped(object sender, Avalonia.Input.TappedEventArgs e)
|
||||||
{
|
{
|
||||||
var url = "https://github.com/rmcrackan/Libation/issues";
|
var url = "https://github.com/rmcrackan/Libation/issues";
|
||||||
try
|
try
|
||||||
@ -41,7 +41,7 @@ namespace LibationAvalonia.Dialogs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void GoToLogs_Tapped(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
private async void GoToLogs_Tapped(object sender, Avalonia.Input.TappedEventArgs e)
|
||||||
{
|
{
|
||||||
LongPath dir = "";
|
LongPath dir = "";
|
||||||
try
|
try
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
mc:Ignorable="d" d:DesignWidth="265" d:DesignHeight="110"
|
mc:Ignorable="d" d:DesignWidth="265" d:DesignHeight="110"
|
||||||
MinWidth="265" MinHeight="110"
|
MinWidth="265" MinHeight="110"
|
||||||
x:Class="LibationAvalonia.Dialogs.MessageBoxWindow"
|
x:Class="LibationAvalonia.Dialogs.MessageBoxWindow"
|
||||||
Title="{Binding Caption}" HasSystemDecorations="True" ShowInTaskbar="True"
|
Title="{Binding Caption}" ShowInTaskbar="True"
|
||||||
Icon="/Assets/1x1.png">
|
Icon="/Assets/1x1.png">
|
||||||
<Grid ColumnDefinitions="*" RowDefinitions="*,Auto">
|
<Grid ColumnDefinitions="*" RowDefinitions="*,Auto">
|
||||||
|
|
||||||
@ -34,13 +34,13 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</DockPanel.Styles>
|
</DockPanel.Styles>
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="5" DockPanel.Dock="Bottom">
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="5" DockPanel.Dock="Bottom">
|
||||||
<Button Grid.Column="0" MinWidth="75" MinHeight="25" Name="Button1" Click="Button1_Click" Margin="5">
|
<Button Grid.Column="0" MinWidth="75" MinHeight="28" Name="Button1" Click="Button1_Click" Margin="5">
|
||||||
<TextBlock VerticalAlignment="Center" Text="{Binding Button1Text}"/>
|
<TextBlock VerticalAlignment="Center" Text="{Binding Button1Text}"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button Grid.Column="1" IsVisible="{Binding HasButton2}" MinWidth="75" MinHeight="25" Name="Button2" Click="Button2_Click" Margin="5">
|
<Button Grid.Column="1" IsVisible="{Binding HasButton2}" MinWidth="75" MinHeight="28" Name="Button2" Click="Button2_Click" Margin="5">
|
||||||
<TextBlock VerticalAlignment="Center" Text="{Binding Button2Text}"/>
|
<TextBlock VerticalAlignment="Center" Text="{Binding Button2Text}"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button Grid.Column="2" IsVisible="{Binding HasButton3}" MinWidth="75" MinHeight="25" Name="Button3" Click="Button3_Click" Content="Cancel" Margin="5">
|
<Button Grid.Column="2" IsVisible="{Binding HasButton3}" MinWidth="75" MinHeight="28" Name="Button3" Click="Button3_Click" Content="Cancel" Margin="5">
|
||||||
<TextBlock VerticalAlignment="Center" Text="{Binding Button3Text}"/>
|
<TextBlock VerticalAlignment="Center" Text="{Binding Button3Text}"/>
|
||||||
</Button>
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@ -37,10 +37,13 @@ Find books that you haven't rated:
|
|||||||
" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchBoolFields());
|
" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchBoolFields());
|
||||||
|
|
||||||
IdFields = @"
|
IdFields = @"
|
||||||
Alice's Adventures in Wonderland (ID: B015D78L0U)
|
Alice's Adventures in
|
||||||
|
Wonderland (ID: B015D78L0U)
|
||||||
|
|
||||||
id:B015D78L0U
|
id:B015D78L0U
|
||||||
|
|
||||||
All of these are synonyms for the ID field
|
All of these are synonyms
|
||||||
|
for the ID field
|
||||||
|
|
||||||
|
|
||||||
" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchIdFields());
|
" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchIdFields());
|
||||||
|
|||||||
@ -23,12 +23,12 @@
|
|||||||
<TabControl Grid.Column="0">
|
<TabControl Grid.Column="0">
|
||||||
<TabControl.Styles>
|
<TabControl.Styles>
|
||||||
<Style Selector="ItemsPresenter#PART_ItemsPresenter">
|
<Style Selector="ItemsPresenter#PART_ItemsPresenter">
|
||||||
<Setter Property="Height" Value="18"/>
|
<Setter Property="Height" Value="28"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="TabItem">
|
<Style Selector="TabItem">
|
||||||
<Setter Property="MinHeight" Value="30"/>
|
<Setter Property="MinHeight" Value="40"/>
|
||||||
<Setter Property="Height" Value="30"/>
|
<Setter Property="Height" Value="40"/>
|
||||||
<Setter Property="Padding" Value="8,2,8,0"/>
|
<Setter Property="Padding" Value="8,2,8,10"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="TabItem#Header TextBlock">
|
<Style Selector="TabItem#Header TextBlock">
|
||||||
<Setter Property="MinHeight" Value="5"/>
|
<Setter Property="MinHeight" Value="5"/>
|
||||||
@ -344,7 +344,6 @@
|
|||||||
<Button
|
<Button
|
||||||
Grid.Row="6"
|
Grid.Row="6"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
IsEnabled="False"
|
|
||||||
Content="{Binding DownloadDecryptSettings.EditCharReplacementText}"
|
Content="{Binding DownloadDecryptSettings.EditCharReplacementText}"
|
||||||
Height="30"
|
Height="30"
|
||||||
Padding="30,3,30,3"
|
Padding="30,3,30,3"
|
||||||
|
|||||||
@ -69,13 +69,10 @@ namespace LibationAvalonia.Dialogs
|
|||||||
settingsDisp.DownloadDecryptSettings.ChapterFileTemplate = newTemplate;
|
settingsDisp.DownloadDecryptSettings.ChapterFileTemplate = newTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EditCharReplacementButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
public async void EditCharReplacementButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
/*
|
var form = new EditReplacementChars(config);
|
||||||
var form = new LibationAvalonia.Dialogs.EditReplacementChars(config);
|
await form.ShowDialog<DialogResult>(this);
|
||||||
form.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
|
||||||
form.ShowDialog();
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void EditChapterTitleTemplateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
public async void EditChapterTitleTemplateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
|
|||||||
@ -2,17 +2,17 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="330"
|
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="350"
|
||||||
MinWidth="500" MinHeight="330"
|
MinWidth="500" MinHeight="350"
|
||||||
MaxWidth="500" MaxHeight="330"
|
MaxWidth="500" MaxHeight="350"
|
||||||
x:Class="LibationAvalonia.Dialogs.SetupDialog"
|
x:Class="LibationAvalonia.Dialogs.SetupDialog"
|
||||||
WindowStartupLocation="CenterScreen"
|
WindowStartupLocation="CenterScreen"
|
||||||
Icon="/Assets/libation.ico"
|
Icon="/Assets/libation.ico"
|
||||||
Title="Welcome to Libation">
|
Title="Welcome to Libation">
|
||||||
|
|
||||||
<Grid Margin="10" RowDefinitions="*,Auto,Auto">
|
<Grid Margin="10" ColumnDefinitions="*" RowDefinitions="*,Auto,Auto">
|
||||||
|
|
||||||
<TextBlock Grid.Row="0" Text="This appears to be your first time using Libation or a previous setup was incomplete.
|
<TextBlock Grid.Row="0" TextWrapping="Wrap" Text="This appears to be your first time using Libation or a previous setup was incomplete.
|
||||||


|


|
||||||

Please fill in a few settings. You can also change these settings later.
|

Please fill in a few settings. You can also change these settings later.
|
||||||


|


|
||||||
@ -22,11 +22,10 @@
|
|||||||

Download your entire library from the "Liberate" tab or
|

Download your entire library from the "Liberate" tab or
|
||||||

liberate your books one at a time by clicking the stoplight." />
|

liberate your books one at a time by clicking the stoplight." />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Margin="0,10,0,10"
|
Width="480"
|
||||||
Padding="0,10,0,10"
|
Margin="0,0,0,10"
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Click="NewUser_Click">
|
Click="NewUser_Click">
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
@ -35,12 +34,11 @@
|
|||||||
|
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Padding="0,10,0,10"
|
Width="480"
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Click="ReturningUser_Click">
|
Click="ReturningUser_Click">
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
TextAlignment="Center"
|
TextAlignment="Center"
|
||||||
Text="RETURNING USER

I have previously installed Libation. This is an upgrade or re-install."/>
|
Text="RETURNING USER

I have previously installed Libation. This is an upgrade or re-install."/>
|
||||||
|
|||||||
@ -40,8 +40,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AvaloniaResource Include="Assets\**" />
|
<AvaloniaResource Include="Assets\**" />
|
||||||
<AvaloniaResource Remove="Assets\SEGOEUI.TTF" />
|
<None Remove=".gitignore" />
|
||||||
<None Remove=".gitignore" />
|
|
||||||
<None Remove="Assets\Asterisk.png" />
|
<None Remove="Assets\Asterisk.png" />
|
||||||
<None Remove="Assets\cancel.png" />
|
<None Remove="Assets\cancel.png" />
|
||||||
<None Remove="Assets\completed.png" />
|
<None Remove="Assets\completed.png" />
|
||||||
@ -80,9 +79,7 @@
|
|||||||
<None Remove="Assets\plus.png" />
|
<None Remove="Assets\plus.png" />
|
||||||
<None Remove="Assets\Question.png" />
|
<None Remove="Assets\Question.png" />
|
||||||
<None Remove="Assets\queued.png" />
|
<None Remove="Assets\queued.png" />
|
||||||
<None Remove="Assets\SEGOEUI.TTF" />
|
|
||||||
<None Remove="Assets\up.png" />
|
<None Remove="Assets\up.png" />
|
||||||
<None Remove="Assets\WINGDING.TTF" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -92,21 +89,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Update="Dialogs\LiberatedStatusBatchAutoDialog.axaml.cs">
|
|
||||||
<DependentUpon>LiberatedStatusBatchAutoDialog.axaml</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="Dialogs\LiberatedStatusBatchManualDialog.axaml.cs">
|
|
||||||
<DependentUpon>LiberatedStatusBatchManualDialog.axaml</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="Views\ProcessBookControl.axaml.cs">
|
|
||||||
<DependentUpon>ProcessBookControl.axaml</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="Views\ProcessQueueControl.axaml.cs">
|
|
||||||
<DependentUpon>ProcessQueueControl.axaml</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="Views\ProductsDisplay.axaml.cs">
|
|
||||||
<DependentUpon>ProductsDisplay.axaml</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="Properties\Resources.Designer.cs">
|
<Compile Update="Properties\Resources.Designer.cs">
|
||||||
<DesignTime>True</DesignTime>
|
<DesignTime>True</DesignTime>
|
||||||
<AutoGen>True</AutoGen>
|
<AutoGen>True</AutoGen>
|
||||||
@ -124,21 +106,20 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<UpToDateCheckInput Remove="Controls\GroupBox.axaml" />
|
<UpToDateCheckInput Remove="Controls\GroupBox.axaml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Avalonia" Version="11.0.0-preview4" />
|
||||||
|
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.0-preview4" />
|
||||||
|
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-preview4" />
|
||||||
|
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.0-preview4 " />
|
||||||
|
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview4" />
|
||||||
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-preview4" />
|
||||||
|
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.0.0-preview4" />
|
||||||
|
<PackageReference Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<EmbeddedResource Include="Assets\SEGOEUI.TTF" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia" Version="0.10.18" />
|
|
||||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.18" />
|
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
|
|
||||||
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.18" />
|
|
||||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.18" />
|
|
||||||
<PackageReference Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Update="glass-with-glow_256.svg">
|
<None Update="glass-with-glow_256.svg">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
|||||||
@ -1,177 +0,0 @@
|
|||||||
using ApplicationServices;
|
|
||||||
using LibationSearchEngine;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Collections.Specialized;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace LibationAvalonia.ViewModels
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Allows filtering of the underlying ObservableCollection<GridEntry>
|
|
||||||
*
|
|
||||||
* When filtering is applied, the filtered-out items are removed
|
|
||||||
* from the base list and added to the private FilterRemoved list.
|
|
||||||
* When filtering is removed, items in the FilterRemoved list are
|
|
||||||
* added back to the base list.
|
|
||||||
*
|
|
||||||
* Items are added and removed to/from the ObservableCollection's
|
|
||||||
* internal list instead of the ObservableCollection itself to
|
|
||||||
* avoid ObservableCollection firing CollectionChanged for every
|
|
||||||
* item. Editing the list this way improve's display performance,
|
|
||||||
* but requires ResetCollection() to be called after all changes
|
|
||||||
* have been made.
|
|
||||||
*/
|
|
||||||
public class GridEntryCollection : ObservableCollection<GridEntry>
|
|
||||||
{
|
|
||||||
public GridEntryCollection(IEnumerable<GridEntry> enumeration)
|
|
||||||
: base(new List<GridEntry>(enumeration)) { }
|
|
||||||
public GridEntryCollection(List<GridEntry> list)
|
|
||||||
: base(list) { }
|
|
||||||
|
|
||||||
public List<GridEntry> InternalList => Items as List<GridEntry>;
|
|
||||||
/// <returns>All items in the list, including those filtered out.</returns>
|
|
||||||
public List<GridEntry> AllItems() => Items.Concat(FilterRemoved).ToList();
|
|
||||||
|
|
||||||
/// <summary>When true, itms will not be checked filtered by search criteria on item changed<summary>
|
|
||||||
public bool SuspendFilteringOnUpdate { get; set; }
|
|
||||||
public string Filter { get => FilterString; set => ApplyFilter(value); }
|
|
||||||
|
|
||||||
/// <summary> Items that were removed from the base list due to filtering </summary>
|
|
||||||
private readonly List<GridEntry> FilterRemoved = new();
|
|
||||||
private string FilterString;
|
|
||||||
private SearchResultSet SearchResults;
|
|
||||||
|
|
||||||
#region Items Management
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes all items from the collection, both visible and hidden, adds new items to the visible collection.
|
|
||||||
/// </summary>
|
|
||||||
public void ReplaceList(IEnumerable<GridEntry> newItems)
|
|
||||||
{
|
|
||||||
Items.Clear();
|
|
||||||
FilterRemoved.Clear();
|
|
||||||
((List<GridEntry>)Items).AddRange(newItems);
|
|
||||||
ResetCollection();
|
|
||||||
}
|
|
||||||
public void ResetCollection()
|
|
||||||
=> OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Filtering
|
|
||||||
|
|
||||||
|
|
||||||
private void ApplyFilter(string filterString)
|
|
||||||
{
|
|
||||||
if (filterString != FilterString)
|
|
||||||
RemoveFilter();
|
|
||||||
|
|
||||||
FilterString = filterString;
|
|
||||||
SearchResults = SearchEngineCommands.Search(filterString);
|
|
||||||
|
|
||||||
var booksFilteredIn = Items.BookEntries().Join(SearchResults.Docs, lbe => lbe.AudibleProductId, d => d.ProductId, (lbe, d) => (GridEntry)lbe);
|
|
||||||
|
|
||||||
//Find all series containing children that match the search criteria
|
|
||||||
var seriesFilteredIn = Items.SeriesEntries().Where(s => s.Children.Join(SearchResults.Docs, lbe => lbe.AudibleProductId, d => d.ProductId, (lbe, d) => lbe).Any());
|
|
||||||
|
|
||||||
var filteredOut = Items.Except(booksFilteredIn.Concat(seriesFilteredIn)).ToList();
|
|
||||||
|
|
||||||
foreach (var item in filteredOut)
|
|
||||||
{
|
|
||||||
FilterRemoved.Add(item);
|
|
||||||
Items.Remove(item);
|
|
||||||
}
|
|
||||||
ResetCollection();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveFilter()
|
|
||||||
{
|
|
||||||
if (FilterString is null) return;
|
|
||||||
|
|
||||||
int visibleCount = Items.Count;
|
|
||||||
|
|
||||||
foreach (var item in FilterRemoved.ToList())
|
|
||||||
{
|
|
||||||
if (item is SeriesEntry || item is LibraryBookEntry lbe && (lbe.Parent is null || lbe.Parent.Liberate.Expanded))
|
|
||||||
{
|
|
||||||
|
|
||||||
FilterRemoved.Remove(item);
|
|
||||||
Items.Insert(visibleCount++, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FilterString = null;
|
|
||||||
SearchResults = null;
|
|
||||||
ResetCollection();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Expand/Collapse
|
|
||||||
|
|
||||||
public void CollapseAll()
|
|
||||||
{
|
|
||||||
foreach (var series in Items.SeriesEntries().ToList())
|
|
||||||
CollapseItem(series);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ExpandAll()
|
|
||||||
{
|
|
||||||
foreach (var series in Items.SeriesEntries().ToList())
|
|
||||||
ExpandItem(series);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CollapseItem(SeriesEntry sEntry)
|
|
||||||
{
|
|
||||||
foreach (var episode in Items.BookEntries().Where(b => b.Parent == sEntry).OrderByDescending(lbe => lbe.SeriesIndex).ToList())
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bypass ObservationCollection's InsertItem method so that CollectionChanged isn't
|
|
||||||
* fired. When adding or removing many items at once, Avalonia's CollectionChanged
|
|
||||||
* event handler causes serious performance problems. And unfotrunately, Avalonia
|
|
||||||
* doesn't respect the NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList? changedItems)
|
|
||||||
* overload that would fire only once for all changed items.
|
|
||||||
*
|
|
||||||
* Doing this requires resetting the list so the view knows it needs to rebuild its display.
|
|
||||||
*/
|
|
||||||
|
|
||||||
FilterRemoved.Add(episode);
|
|
||||||
Items.Remove(episode);
|
|
||||||
}
|
|
||||||
|
|
||||||
sEntry.Liberate.Expanded = false;
|
|
||||||
ResetCollection();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ExpandItem(SeriesEntry sEntry)
|
|
||||||
{
|
|
||||||
var sindex = Items.IndexOf(sEntry);
|
|
||||||
|
|
||||||
foreach (var episode in FilterRemoved.BookEntries().Where(b => b.Parent == sEntry).OrderByDescending(lbe => lbe.SeriesIndex).ToList())
|
|
||||||
{
|
|
||||||
if (SearchResults is null || SearchResults.Docs.Any(d => d.ProductId == episode.AudibleProductId))
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bypass ObservationCollection's InsertItem method so that CollectionChanged isn't
|
|
||||||
* fired. When adding or removing many items at once, Avalonia's CollectionChanged
|
|
||||||
* event handler causes serious performance problems. And unfotrunately, Avalonia
|
|
||||||
* doesn't respect the NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList? changedItems)
|
|
||||||
* overload that would fire only once for all changed items.
|
|
||||||
*
|
|
||||||
* Doing this requires resetting the list so the view knows it needs to rebuild its display.
|
|
||||||
*/
|
|
||||||
|
|
||||||
FilterRemoved.Remove(episode);
|
|
||||||
Items.Insert(++sindex, episode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sEntry.Liberate.Expanded = true;
|
|
||||||
ResetCollection();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +1,3 @@
|
|||||||
using Avalonia.Controls;
|
|
||||||
using DataLayer;
|
using DataLayer;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -6,13 +5,11 @@ using System.ComponentModel;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using System.Reflection;
|
|
||||||
using System.Collections;
|
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using ApplicationServices;
|
using ApplicationServices;
|
||||||
using AudibleUtilities;
|
using AudibleUtilities;
|
||||||
using LibationAvalonia.Views;
|
|
||||||
using LibationAvalonia.Dialogs.Login;
|
using LibationAvalonia.Dialogs.Login;
|
||||||
|
using Avalonia.Collections;
|
||||||
|
|
||||||
namespace LibationAvalonia.ViewModels
|
namespace LibationAvalonia.ViewModels
|
||||||
{
|
{
|
||||||
@ -21,83 +18,38 @@ namespace LibationAvalonia.ViewModels
|
|||||||
/// <summary>Number of visible rows has changed</summary>
|
/// <summary>Number of visible rows has changed</summary>
|
||||||
public event EventHandler<int> VisibleCountChanged;
|
public event EventHandler<int> VisibleCountChanged;
|
||||||
public event EventHandler<int> RemovableCountChanged;
|
public event EventHandler<int> RemovableCountChanged;
|
||||||
public event EventHandler InitialLoaded;
|
|
||||||
|
|
||||||
private DataGridColumn _currentSortColumn;
|
/// <summary>Backing list of all grid entries</summary>
|
||||||
private DataGrid productsDataGrid;
|
private readonly List<GridEntry> SOURCE = new();
|
||||||
|
/// <summary>Grid entries included in the filter set. If null, all grid entries are shown</summary>
|
||||||
|
private List<GridEntry> FilteredInGridEntries;
|
||||||
|
public string FilterString { get; private set; }
|
||||||
|
public DataGridCollectionView GridEntries { get; }
|
||||||
|
|
||||||
private GridEntryCollection _gridEntries;
|
|
||||||
private bool _removeColumnVisivle;
|
private bool _removeColumnVisivle;
|
||||||
public GridEntryCollection GridEntries { get => _gridEntries; private set => this.RaiseAndSetIfChanged(ref _gridEntries, value); }
|
|
||||||
public bool RemoveColumnVisivle { get => _removeColumnVisivle; private set => this.RaiseAndSetIfChanged(ref _removeColumnVisivle, value); }
|
public bool RemoveColumnVisivle { get => _removeColumnVisivle; private set => this.RaiseAndSetIfChanged(ref _removeColumnVisivle, value); }
|
||||||
|
|
||||||
public List<LibraryBook> GetVisibleBookEntries()
|
public List<LibraryBook> GetVisibleBookEntries()
|
||||||
=> GridEntries.InternalList
|
=> GridEntries
|
||||||
.BookEntries()
|
.OfType<LibraryBookEntry>()
|
||||||
.Select(lbe => lbe.LibraryBook)
|
.Select(lbe => lbe.LibraryBook)
|
||||||
.ToList();
|
.ToList();
|
||||||
public IEnumerable<LibraryBookEntry> GetAllBookEntries()
|
|
||||||
=> GridEntries
|
private IEnumerable<LibraryBookEntry> GetAllBookEntries()
|
||||||
.AllItems()
|
=> SOURCE
|
||||||
.BookEntries();
|
.BookEntries();
|
||||||
public ProductsDisplayViewModel() { }
|
|
||||||
public ProductsDisplayViewModel(List<GridEntry> items)
|
public ProductsDisplayViewModel()
|
||||||
{
|
{
|
||||||
GridEntries = new GridEntryCollection(items);
|
GridEntries = new(SOURCE);
|
||||||
|
GridEntries.Filter = CollectionFilter;
|
||||||
|
|
||||||
|
GridEntries.CollectionChanged += (s, e)
|
||||||
|
=> VisibleCountChanged?.Invoke(this, GridEntries.OfType<LibraryBookEntry>().Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Display Functions
|
#region Display Functions
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Call once on load so we can modify access a private member with reflection
|
|
||||||
/// </summary>
|
|
||||||
public void RegisterCollectionChanged(ProductsDisplay productsDisplay = null)
|
|
||||||
{
|
|
||||||
productsDataGrid ??= productsDisplay?.productsGrid;
|
|
||||||
|
|
||||||
if (GridEntries is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
//Avalonia displays items in the DataConncetion from an internal copy of
|
|
||||||
//the bound list, not the actual bound list. So we need to reflect to get
|
|
||||||
//the current display order and set each GridEntry.ListIndex correctly.
|
|
||||||
var DataConnection_PI = typeof(DataGrid).GetProperty("DataConnection", BindingFlags.NonPublic | BindingFlags.Instance);
|
|
||||||
var DataSource_PI = DataConnection_PI.PropertyType.GetProperty("DataSource", BindingFlags.Public | BindingFlags.Instance);
|
|
||||||
|
|
||||||
GridEntries.CollectionChanged += (s, e) =>
|
|
||||||
{
|
|
||||||
if (s != GridEntries) return;
|
|
||||||
|
|
||||||
var displayListGE = ((IEnumerable)DataSource_PI.GetValue(DataConnection_PI.GetValue(productsDataGrid))).Cast<GridEntry>();
|
|
||||||
int index = 0;
|
|
||||||
foreach (var di in displayListGE)
|
|
||||||
{
|
|
||||||
di.ListIndex = index++;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Only call once per lifetime
|
|
||||||
/// </summary>
|
|
||||||
public void InitialDisplay(List<LibraryBook> dbBooks)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
GridEntries = new GridEntryCollection(CreateGridEntries(dbBooks));
|
|
||||||
GridEntries.CollapseAll();
|
|
||||||
|
|
||||||
InitialLoaded?.Invoke(this, EventArgs.Empty);
|
|
||||||
VisibleCountChanged?.Invoke(this, GridEntries.BookEntries().Count());
|
|
||||||
|
|
||||||
RegisterCollectionChanged();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Serilog.Log.Error(ex, "Error displaying library in {0}", nameof(ProductsDisplayViewModel));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Call when there's been a change to the library
|
/// Call when there's been a change to the library
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -105,29 +57,25 @@ namespace LibationAvalonia.ViewModels
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//List is already displayed. Replace all items with new ones, refilter, and re-sort
|
var existingSeriesEntries = SOURCE.SeriesEntries().ToList();
|
||||||
string existingFilter = GridEntries?.Filter;
|
|
||||||
var newEntries = CreateGridEntries(dbBooks);
|
|
||||||
|
|
||||||
var existingSeriesEntries = GridEntries.AllItems().SeriesEntries().ToList();
|
SOURCE.Clear();
|
||||||
|
SOURCE.AddRange(CreateGridEntries(dbBooks));
|
||||||
|
|
||||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
//If replacing the list, preserve user's existing collapse/expand
|
||||||
|
//state. When resetting a list, default state is cosed.
|
||||||
|
foreach (var series in existingSeriesEntries)
|
||||||
{
|
{
|
||||||
GridEntries.ReplaceList(newEntries);
|
var sEntry = SOURCE.FirstOrDefault(ge => ge.AudibleProductId == series.AudibleProductId);
|
||||||
|
if (sEntry is SeriesEntry se)
|
||||||
|
se.Liberate.Expanded = series.Liberate.Expanded;
|
||||||
|
}
|
||||||
|
|
||||||
//We're replacing the list, so preserve usere's existing collapse/expand
|
//Run query on new list
|
||||||
//state. When resetting a list, default state is open.
|
FilteredInGridEntries = QueryResults(SOURCE, FilterString);
|
||||||
foreach (var series in existingSeriesEntries)
|
|
||||||
{
|
await Dispatcher.UIThread.InvokeAsync(GridEntries.Refresh);
|
||||||
var sEntry = GridEntries.InternalList.FirstOrDefault(ge => ge.AudibleProductId == series.AudibleProductId);
|
|
||||||
if (sEntry is SeriesEntry se && !series.Liberate.Expanded)
|
|
||||||
GridEntries.CollapseItem(se);
|
|
||||||
}
|
|
||||||
|
|
||||||
GridEntries.Filter = existingFilter;
|
|
||||||
ReSort();
|
|
||||||
VisibleCountChanged?.Invoke(this, GridEntries.BookEntries().Count());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -135,7 +83,7 @@ namespace LibationAvalonia.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<GridEntry> CreateGridEntries(IEnumerable<LibraryBook> dbBooks)
|
private static List<GridEntry> CreateGridEntries(IEnumerable<LibraryBook> dbBooks)
|
||||||
{
|
{
|
||||||
var geList = dbBooks
|
var geList = dbBooks
|
||||||
.Where(lb => lb.Book.IsProduct())
|
.Where(lb => lb.Book.IsProduct())
|
||||||
@ -145,9 +93,7 @@ namespace LibationAvalonia.ViewModels
|
|||||||
|
|
||||||
var episodes = dbBooks.Where(lb => lb.Book.IsEpisodeChild());
|
var episodes = dbBooks.Where(lb => lb.Book.IsEpisodeChild());
|
||||||
|
|
||||||
var seriesBooks = dbBooks.Where(lb => lb.Book.IsEpisodeParent()).ToList();
|
foreach (var parent in dbBooks.Where(lb => lb.Book.IsEpisodeParent()))
|
||||||
|
|
||||||
foreach (var parent in seriesBooks)
|
|
||||||
{
|
{
|
||||||
var seriesEpisodes = episodes.FindChildren(parent);
|
var seriesEpisodes = episodes.FindChildren(parent);
|
||||||
|
|
||||||
@ -158,72 +104,67 @@ namespace LibationAvalonia.ViewModels
|
|||||||
geList.Add(seriesEntry);
|
geList.Add(seriesEntry);
|
||||||
geList.AddRange(seriesEntry.Children);
|
geList.AddRange(seriesEntry.Children);
|
||||||
}
|
}
|
||||||
return geList.OrderByDescending(e => e.DateAdded);
|
|
||||||
|
var bookList = geList.OrderByDescending(e => e.DateAdded).ToList();
|
||||||
|
|
||||||
|
//ListIndex is used by RowComparer to make column sort stable
|
||||||
|
int index = 0;
|
||||||
|
foreach (GridEntry di in bookList)
|
||||||
|
di.ListIndex = index++;
|
||||||
|
|
||||||
|
return bookList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ToggleSeriesExpanded(SeriesEntry seriesEntry)
|
public void ToggleSeriesExpanded(SeriesEntry seriesEntry)
|
||||||
{
|
{
|
||||||
if (seriesEntry.Liberate.Expanded)
|
seriesEntry.Liberate.Expanded = !seriesEntry.Liberate.Expanded;
|
||||||
GridEntries.CollapseItem(seriesEntry);
|
GridEntries.Refresh();
|
||||||
else
|
|
||||||
GridEntries.ExpandItem(seriesEntry);
|
|
||||||
|
|
||||||
VisibleCountChanged?.Invoke(this, GridEntries.BookEntries().Count());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Filtering
|
#region Filtering
|
||||||
|
|
||||||
public async Task Filter(string searchString)
|
public async Task Filter(string searchString)
|
||||||
{
|
{
|
||||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
if (searchString == FilterString)
|
||||||
{
|
return;
|
||||||
int visibleCount = GridEntries.Count;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(searchString))
|
FilterString = searchString;
|
||||||
GridEntries.RemoveFilter();
|
|
||||||
else
|
|
||||||
GridEntries.Filter = searchString;
|
|
||||||
|
|
||||||
if (visibleCount != GridEntries.Count)
|
if (SOURCE.Count == 0)
|
||||||
VisibleCountChanged?.Invoke(this, GridEntries.BookEntries().Count());
|
return;
|
||||||
|
|
||||||
//Re-sort after filtering
|
FilteredInGridEntries = QueryResults(SOURCE, searchString);
|
||||||
ReSort();
|
|
||||||
});
|
await Dispatcher.UIThread.InvokeAsync(GridEntries.Refresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
private bool CollectionFilter(object item)
|
||||||
|
|
||||||
#region Sorting
|
|
||||||
|
|
||||||
public void Sort(DataGridColumn sortColumn)
|
|
||||||
{
|
{
|
||||||
//Force the comparer to get the current sort order. We can't
|
if (item is LibraryBookEntry lbe
|
||||||
//retrieve it from inside this event handler because Avalonia
|
&& lbe.IsEpisode
|
||||||
//doesn't set the property until after this event.
|
&& lbe.Parent?.Liberate?.Expanded != true)
|
||||||
var comparer = sortColumn.CustomSortComparer as RowComparer;
|
return false;
|
||||||
comparer.SortDirection = null;
|
|
||||||
|
|
||||||
_currentSortColumn = sortColumn;
|
if (FilteredInGridEntries is null) return true;
|
||||||
|
|
||||||
|
return FilteredInGridEntries.Contains(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Must be invoked on UI thread
|
private static List<GridEntry> QueryResults(List<GridEntry> entries, string searchString)
|
||||||
private void ReSort()
|
|
||||||
{
|
{
|
||||||
if (_currentSortColumn is null)
|
if (string.IsNullOrEmpty(searchString)) return null;
|
||||||
{
|
|
||||||
//Sort ascending and reverse. That's how the comparer is designed to work to be compatible with Avalonia.
|
var SearchResults = SearchEngineCommands.Search(searchString);
|
||||||
var defaultComparer = new RowComparer(ListSortDirection.Descending, nameof(GridEntry.DateAdded));
|
|
||||||
GridEntries.InternalList.Sort(defaultComparer);
|
var booksFilteredIn = entries.BookEntries().Join(SearchResults.Docs, lbe => lbe.AudibleProductId, d => d.ProductId, (lbe, d) => (GridEntry)lbe);
|
||||||
GridEntries.InternalList.Reverse();
|
|
||||||
GridEntries.ResetCollection();
|
//Find all series containing children that match the search criteria
|
||||||
}
|
var seriesFilteredIn = entries.SeriesEntries().Where(s => s.Children.Join(SearchResults.Docs, lbe => lbe.AudibleProductId, d => d.ProductId, (lbe, d) => lbe).Any());
|
||||||
else
|
|
||||||
{
|
return booksFilteredIn.Concat(seriesFilteredIn).ToList();
|
||||||
_currentSortColumn.Sort(((RowComparer)_currentSortColumn.CustomSortComparer).SortDirection ?? ListSortDirection.Ascending);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -231,8 +172,8 @@ namespace LibationAvalonia.ViewModels
|
|||||||
|
|
||||||
public void DoneRemovingBooks()
|
public void DoneRemovingBooks()
|
||||||
{
|
{
|
||||||
foreach (var item in GridEntries.AllItems())
|
foreach (var item in SOURCE)
|
||||||
item.PropertyChanged -= Item_PropertyChanged;
|
item.PropertyChanged -= GridEntry_PropertyChanged;
|
||||||
RemoveColumnVisivle = false;
|
RemoveColumnVisivle = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,49 +188,47 @@ namespace LibationAvalonia.ViewModels
|
|||||||
var result = await MessageBox.ShowConfirmationDialog(
|
var result = await MessageBox.ShowConfirmationDialog(
|
||||||
null,
|
null,
|
||||||
libraryBooks,
|
libraryBooks,
|
||||||
// do not use `$` string interpolation. See impl.
|
// do not use `$` string interpolation. See impl.
|
||||||
"Are you sure you want to remove {0} from Libation's library?",
|
"Are you sure you want to remove {0} from Libation's library?",
|
||||||
"Remove books from Libation?");
|
"Remove books from Libation?");
|
||||||
|
|
||||||
if (result != DialogResult.Yes)
|
if (result != DialogResult.Yes)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var book in selectedBooks)
|
foreach (var book in selectedBooks)
|
||||||
book.PropertyChanged -= Item_PropertyChanged;
|
book.PropertyChanged -= GridEntry_PropertyChanged;
|
||||||
|
|
||||||
var idsToRemove = libraryBooks.Select(lb => lb.Book.AudibleProductId).ToList();
|
var idsToRemove = libraryBooks.Select(lb => lb.Book.AudibleProductId).ToList();
|
||||||
|
|
||||||
|
void BindingList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Action != System.Collections.Specialized.NotifyCollectionChangedAction.Reset)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//After DisplayBooks() re-creates the list,
|
||||||
|
//re-subscribe to all items' PropertyChanged events.
|
||||||
|
|
||||||
|
foreach (var b in GetAllBookEntries())
|
||||||
|
b.PropertyChanged += GridEntry_PropertyChanged;
|
||||||
|
|
||||||
|
GridEntries.CollectionChanged -= BindingList_CollectionChanged;
|
||||||
|
}
|
||||||
|
|
||||||
GridEntries.CollectionChanged += BindingList_CollectionChanged;
|
GridEntries.CollectionChanged += BindingList_CollectionChanged;
|
||||||
|
|
||||||
//The RemoveBooksAsync will fire LibrarySizeChanged, which calls ProductsDisplay2.Display(),
|
//The RemoveBooksAsync will fire LibrarySizeChanged, which calls ProductsDisplay2.Display(),
|
||||||
//so there's no need to remove books from the grid display here.
|
//so there's no need to remove books from the grid display here.
|
||||||
var removeLibraryBooks = await LibraryCommands.RemoveBooksAsync(idsToRemove);
|
var removeLibraryBooks = await LibraryCommands.RemoveBooksAsync(idsToRemove);
|
||||||
|
|
||||||
foreach (var b in GetAllBookEntries())
|
|
||||||
b.Remove = false;
|
|
||||||
|
|
||||||
RemovableCountChanged?.Invoke(this, 0);
|
RemovableCountChanged?.Invoke(this, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BindingList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.Action != System.Collections.Specialized.NotifyCollectionChangedAction.Reset)
|
|
||||||
return;
|
|
||||||
|
|
||||||
//After ProductsDisplay2.Display() re-creates the list,
|
|
||||||
//re-subscribe to all items' PropertyChanged events.
|
|
||||||
|
|
||||||
foreach (var b in GetAllBookEntries())
|
|
||||||
b.PropertyChanged += Item_PropertyChanged;
|
|
||||||
|
|
||||||
GridEntries.CollectionChanged -= BindingList_CollectionChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ScanAndRemoveBooksAsync(params Account[] accounts)
|
public async Task ScanAndRemoveBooksAsync(params Account[] accounts)
|
||||||
{
|
{
|
||||||
foreach (var item in GridEntries.AllItems())
|
foreach (var item in SOURCE)
|
||||||
{
|
{
|
||||||
item.Remove = false;
|
item.Remove = false;
|
||||||
item.PropertyChanged += Item_PropertyChanged;
|
item.PropertyChanged += GridEntry_PropertyChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
RemoveColumnVisivle = true;
|
RemoveColumnVisivle = true;
|
||||||
@ -302,9 +241,6 @@ namespace LibationAvalonia.ViewModels
|
|||||||
|
|
||||||
var allBooks = GetAllBookEntries();
|
var allBooks = GetAllBookEntries();
|
||||||
|
|
||||||
foreach (var b in allBooks)
|
|
||||||
b.Remove = false;
|
|
||||||
|
|
||||||
var lib = allBooks
|
var lib = allBooks
|
||||||
.Select(lbe => lbe.LibraryBook)
|
.Select(lbe => lbe.LibraryBook)
|
||||||
.Where(lb => !lb.Book.HasLiberated());
|
.Where(lb => !lb.Book.HasLiberated());
|
||||||
@ -326,7 +262,7 @@ namespace LibationAvalonia.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
private void GridEntry_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.PropertyName == nameof(GridEntry.Remove) && sender is LibraryBookEntry lbEntry)
|
if (e.PropertyName == nameof(GridEntry.Remove) && sender is LibraryBookEntry lbEntry)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -14,12 +14,6 @@ namespace LibationAvalonia.ViewModels
|
|||||||
public static IEnumerable<SeriesEntry> SeriesEntries(this IEnumerable<GridEntry> gridEntries)
|
public static IEnumerable<SeriesEntry> SeriesEntries(this IEnumerable<GridEntry> gridEntries)
|
||||||
=> gridEntries.OfType<SeriesEntry>();
|
=> gridEntries.OfType<SeriesEntry>();
|
||||||
|
|
||||||
public static T? FindByAsin<T>(this IEnumerable<T> gridEntries, string audibleProductID) where T : GridEntry
|
|
||||||
=> gridEntries.FirstOrDefault(i => i.AudibleProductId == audibleProductID);
|
|
||||||
|
|
||||||
public static IEnumerable<SeriesEntry> EmptySeries(this IEnumerable<GridEntry> gridEntries)
|
|
||||||
=> gridEntries.SeriesEntries().Where(i => i.Children.Count == 0);
|
|
||||||
|
|
||||||
public static SeriesEntry? FindSeriesParent(this IEnumerable<GridEntry> gridEntries, LibraryBook seriesEpisode)
|
public static SeriesEntry? FindSeriesParent(this IEnumerable<GridEntry> gridEntries, LibraryBook seriesEpisode)
|
||||||
{
|
{
|
||||||
if (seriesEpisode.Book.SeriesLink is null) return null;
|
if (seriesEpisode.Book.SeriesLink is null) return null;
|
||||||
|
|||||||
@ -13,25 +13,19 @@ namespace LibationAvalonia.ViewModels
|
|||||||
/// sorted by series index, ascending. Stable sorting is achieved by comparing the GridEntry.ListIndex
|
/// sorted by series index, ascending. Stable sorting is achieved by comparing the GridEntry.ListIndex
|
||||||
/// properties when 2 items compare equal.
|
/// properties when 2 items compare equal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class RowComparer : IComparer, IComparer<GridEntry>
|
internal class RowComparer : IComparer, IComparer<GridEntry>, IComparer<object>
|
||||||
{
|
{
|
||||||
private static readonly PropertyInfo HeaderCellPi = typeof(DataGridColumn).GetProperty("HeaderCell", BindingFlags.NonPublic | BindingFlags.Instance);
|
private static readonly PropertyInfo HeaderCellPi = typeof(DataGridColumn).GetProperty("HeaderCell", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
private static readonly PropertyInfo CurrentSortingStatePi = typeof(DataGridColumnHeader).GetProperty("CurrentSortingState", BindingFlags.NonPublic | BindingFlags.Instance);
|
private static readonly PropertyInfo CurrentSortingStatePi = typeof(DataGridColumnHeader).GetProperty("CurrentSortingState", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
|
||||||
public DataGridColumn Column { get; init; }
|
public DataGridColumn Column { get; init; }
|
||||||
public string PropertyName { get; private set; }
|
public string PropertyName { get; private set; }
|
||||||
public ListSortDirection? SortDirection { get; set; }
|
|
||||||
|
|
||||||
public RowComparer(DataGridColumn column)
|
public RowComparer(DataGridColumn column)
|
||||||
{
|
{
|
||||||
Column = column;
|
Column = column;
|
||||||
PropertyName = Column.SortMemberPath;
|
PropertyName = Column.SortMemberPath;
|
||||||
}
|
}
|
||||||
public RowComparer(ListSortDirection direction, string propertyName)
|
|
||||||
{
|
|
||||||
SortDirection = direction;
|
|
||||||
PropertyName = propertyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Compare(object x, object y)
|
public int Compare(object x, object y)
|
||||||
{
|
{
|
||||||
@ -42,7 +36,7 @@ namespace LibationAvalonia.ViewModels
|
|||||||
var geA = (GridEntry)x;
|
var geA = (GridEntry)x;
|
||||||
var geB = (GridEntry)y;
|
var geB = (GridEntry)y;
|
||||||
|
|
||||||
SortDirection ??= GetSortOrder();
|
var sortDirection = GetSortOrder();
|
||||||
|
|
||||||
SeriesEntry parentA = null;
|
SeriesEntry parentA = null;
|
||||||
SeriesEntry parentB = null;
|
SeriesEntry parentB = null;
|
||||||
@ -54,16 +48,16 @@ namespace LibationAvalonia.ViewModels
|
|||||||
|
|
||||||
//both a and b are top-level grid entries
|
//both a and b are top-level grid entries
|
||||||
if (parentA is null && parentB is null)
|
if (parentA is null && parentB is null)
|
||||||
return InternalCompare(geA, geB);
|
return InternalCompare(geA, geB, sortDirection);
|
||||||
|
|
||||||
//a is top-level, b is a child
|
//a is top-level, b is a child
|
||||||
if (parentA is null && parentB is not null)
|
if (parentA is null && parentB is not null)
|
||||||
{
|
{
|
||||||
// b is a child of a, parent is always first
|
// b is a child of a, parent is always first
|
||||||
if (parentB == geA)
|
if (parentB == geA)
|
||||||
return SortDirection is ListSortDirection.Ascending ? -1 : 1;
|
return sortDirection is ListSortDirection.Ascending ? -1 : 1;
|
||||||
else
|
else
|
||||||
return InternalCompare(geA, parentB);
|
return InternalCompare(geA, parentB, sortDirection);
|
||||||
}
|
}
|
||||||
|
|
||||||
//a is a child, b is a top-level
|
//a is a child, b is a top-level
|
||||||
@ -71,24 +65,24 @@ namespace LibationAvalonia.ViewModels
|
|||||||
{
|
{
|
||||||
// a is a child of b, parent is always first
|
// a is a child of b, parent is always first
|
||||||
if (parentA == geB)
|
if (parentA == geB)
|
||||||
return SortDirection is ListSortDirection.Ascending ? 1 : -1;
|
return sortDirection is ListSortDirection.Ascending ? 1 : -1;
|
||||||
else
|
else
|
||||||
return InternalCompare(parentA, geB);
|
return InternalCompare(parentA, geB, sortDirection);
|
||||||
}
|
}
|
||||||
|
|
||||||
//both are children of the same series, always present in order of series index, ascending
|
//both are children of the same series, always present in order of series index, ascending
|
||||||
if (parentA == parentB)
|
if (parentA == parentB)
|
||||||
return geA.SeriesIndex.CompareTo(geB.SeriesIndex) * (SortDirection is ListSortDirection.Ascending ? 1 : -1);
|
return geA.SeriesIndex.CompareTo(geB.SeriesIndex) * (sortDirection is ListSortDirection.Ascending ? 1 : -1);
|
||||||
|
|
||||||
//a and b are children of different series.
|
//a and b are children of different series.
|
||||||
return InternalCompare(parentA, parentB);
|
return InternalCompare(parentA, parentB, sortDirection);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Avalonia doesn't expose the column's CurrentSortingState, so we must get it through reflection
|
//Avalonia doesn't expose the column's CurrentSortingState, so we must get it through reflection
|
||||||
private ListSortDirection? GetSortOrder()
|
private ListSortDirection? GetSortOrder()
|
||||||
=> CurrentSortingStatePi.GetValue(HeaderCellPi.GetValue(Column)) as ListSortDirection?;
|
=> CurrentSortingStatePi.GetValue(HeaderCellPi.GetValue(Column)) as ListSortDirection?;
|
||||||
|
|
||||||
private int InternalCompare(GridEntry x, GridEntry y)
|
private int InternalCompare(GridEntry x, GridEntry y, ListSortDirection? sortDirection)
|
||||||
{
|
{
|
||||||
var val1 = x.GetMemberValue(PropertyName);
|
var val1 = x.GetMemberValue(PropertyName);
|
||||||
var val2 = y.GetMemberValue(PropertyName);
|
var val2 = y.GetMemberValue(PropertyName);
|
||||||
@ -98,7 +92,7 @@ namespace LibationAvalonia.ViewModels
|
|||||||
//If items compare equal, compare them by their positions in the the list.
|
//If items compare equal, compare them by their positions in the the list.
|
||||||
//This is how you achieve a stable sort.
|
//This is how you achieve a stable sort.
|
||||||
if (compareResult == 0)
|
if (compareResult == 0)
|
||||||
return x.ListIndex.CompareTo(y.ListIndex);
|
return x.ListIndex.CompareTo(y.ListIndex) * (sortDirection is ListSortDirection.Ascending ? 1 : -1);
|
||||||
else
|
else
|
||||||
return compareResult;
|
return compareResult;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,7 +55,7 @@ namespace LibationAvalonia.ViewModels
|
|||||||
|
|
||||||
public SeriesEntry(LibraryBook parent, IEnumerable<LibraryBook> children)
|
public SeriesEntry(LibraryBook parent, IEnumerable<LibraryBook> children)
|
||||||
{
|
{
|
||||||
Liberate = new LiberateButtonStatus(IsSeries) { Expanded = true };
|
Liberate = new LiberateButtonStatus(IsSeries);
|
||||||
SeriesIndex = -1;
|
SeriesIndex = -1;
|
||||||
LibraryBook = parent;
|
LibraryBook = parent;
|
||||||
|
|
||||||
|
|||||||
@ -60,11 +60,5 @@ namespace LibationAvalonia.Views
|
|||||||
|
|
||||||
public async void editQuickFiltersToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
public async void editQuickFiltersToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
=> await new Dialogs.EditQuickFilters().ShowDialog(this);
|
=> await new Dialogs.EditQuickFilters().ShowDialog(this);
|
||||||
|
|
||||||
public async void ProductsDisplay_Initialized(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (QuickFilters.UseDefault)
|
|
||||||
await performFilter(QuickFilters.Filters.FirstOrDefault());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
Name="Form1"
|
Name="Form1"
|
||||||
Icon="/Assets/libation.ico">
|
Icon="/Assets/libation.ico">
|
||||||
|
|
||||||
<Border BorderBrush="{DynamicResource DataGridGridLinesBrush}" BorderThickness="2" Padding="15">
|
<Border BorderBrush="{DynamicResource DataGridGridLinesBrush}" BorderThickness="2" Padding="10,0,10,10">
|
||||||
<Grid RowDefinitions="Auto,Auto,*,Auto">
|
<Grid RowDefinitions="Auto,Auto,*,Auto">
|
||||||
<Grid Grid.Row="0" ColumnDefinitions="1*,Auto">
|
<Grid Grid.Row="0" ColumnDefinitions="1*,Auto">
|
||||||
|
|
||||||
@ -143,23 +143,30 @@
|
|||||||
<Style Selector="TextBox">
|
<Style Selector="TextBox">
|
||||||
<Setter Property="MinHeight" Value="10" />
|
<Setter Property="MinHeight" Value="10" />
|
||||||
</Style>
|
</Style>
|
||||||
|
<Style Selector="Button">
|
||||||
|
<Setter Property="Padding" Value="15,0,15,0" />
|
||||||
|
<Setter Property="Margin" Value="10,0,0,0" />
|
||||||
|
<Setter Property="Height" Value="30" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
</Grid.Styles>
|
</Grid.Styles>
|
||||||
|
|
||||||
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||||
<Button Click="filterHelpBtn_Click" Height="30" Width="30" Content="?"/>
|
<Button Margin="0" Click="filterHelpBtn_Click" Content="?"/>
|
||||||
<Button Click="addQuickFilterBtn_Click" Height="30" Width="150" Margin="10,0,10,0" Content="Add To Quick Filters"/>
|
<Button Click="addQuickFilterBtn_Click" Content="Add To Quick Filters"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||||
<Button IsVisible="{Binding RemoveButtonsVisible}" IsEnabled="{Binding RemoveBooksButtonEnabled}" Click="removeBooksBtn_Click" Height="30" Width="220" Content="{Binding RemoveBooksButtonText}"/>
|
<Button IsVisible="{Binding RemoveButtonsVisible}" IsEnabled="{Binding RemoveBooksButtonEnabled}" Click="removeBooksBtn_Click" Content="{Binding RemoveBooksButtonText}"/>
|
||||||
<Button IsVisible="{Binding RemoveButtonsVisible}" Click="doneRemovingBtn_Click" Height="30" Width="160" Margin="10,0,0,0" Content="Done Removing Books"/>
|
<Button IsVisible="{Binding RemoveButtonsVisible}" Click="doneRemovingBtn_Click" Content="Done Removing Books"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<TextBox Grid.Column="1" IsVisible="{Binding !RemoveButtonsVisible}" Text="{Binding FilterString, Mode=TwoWay}" KeyDown="filterSearchTb_KeyPress" />
|
<TextBox Grid.Column="1" Margin="10,0,0,0" IsVisible="{Binding !RemoveButtonsVisible}" Text="{Binding FilterString, Mode=TwoWay}" KeyDown="filterSearchTb_KeyPress" />
|
||||||
|
|
||||||
<StackPanel Grid.Column="2" Height="30" Orientation="Horizontal">
|
<StackPanel Grid.Column="2" Height="30" Orientation="Horizontal">
|
||||||
<Button Click="filterBtn_Click" Height="30" Width="80" Margin="10,0,10,0" Content="Filter"/>
|
<Button Click="filterBtn_Click" Height="30" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Content="Filter"/>
|
||||||
<Button Click="ToggleQueueHideBtn_Click" Height="30" Width="30" Content="{Binding QueueHideButtonText}"/>
|
<Button Padding="5,0,5,0" Click="ToggleQueueHideBtn_Click" Content="{Binding QueueHideButtonText}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -174,7 +181,6 @@
|
|||||||
<!-- Product Display Grid -->
|
<!-- Product Display Grid -->
|
||||||
<views:ProductsDisplay
|
<views:ProductsDisplay
|
||||||
Name="productsDisplay"
|
Name="productsDisplay"
|
||||||
Initialized="ProductsDisplay_Initialized1"
|
|
||||||
DataContext="{Binding ProductsDisplay}"
|
DataContext="{Binding ProductsDisplay}"
|
||||||
LiberateClicked="ProductsDisplay_LiberateClicked"/>
|
LiberateClicked="ProductsDisplay_LiberateClicked"/>
|
||||||
</SplitView>
|
</SplitView>
|
||||||
|
|||||||
@ -10,6 +10,8 @@ using DataLayer;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using AppScaffolding;
|
using AppScaffolding;
|
||||||
|
using System.Linq;
|
||||||
|
using LibationAvalonia.Dialogs;
|
||||||
|
|
||||||
namespace LibationAvalonia.Views
|
namespace LibationAvalonia.Views
|
||||||
{
|
{
|
||||||
@ -46,7 +48,6 @@ namespace LibationAvalonia.Views
|
|||||||
// misc which belongs in winforms app but doesn't have a UI element
|
// misc which belongs in winforms app but doesn't have a UI element
|
||||||
Configure_NonUI();
|
Configure_NonUI();
|
||||||
|
|
||||||
_viewModel.ProductsDisplay.InitialLoaded += ProductsDisplay_Initialized;
|
|
||||||
_viewModel.ProductsDisplay.RemovableCountChanged += ProductsDisplay_RemovableCountChanged;
|
_viewModel.ProductsDisplay.RemovableCountChanged += ProductsDisplay_RemovableCountChanged;
|
||||||
_viewModel.ProductsDisplay.VisibleCountChanged += ProductsDisplay_VisibleCountChanged;
|
_viewModel.ProductsDisplay.VisibleCountChanged += ProductsDisplay_VisibleCountChanged;
|
||||||
|
|
||||||
@ -172,15 +173,12 @@ namespace LibationAvalonia.Views
|
|||||||
Environment.Exit(0);
|
Environment.Exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ProductsDisplay_Initialized1(object sender, EventArgs e)
|
private async void MainWindow_LibraryLoaded(object sender, List<LibraryBook> dbBooks)
|
||||||
{
|
{
|
||||||
if (sender is ProductsDisplay products)
|
if (QuickFilters.UseDefault)
|
||||||
_viewModel.ProductsDisplay.RegisterCollectionChanged(products);
|
await performFilter(QuickFilters.Filters.FirstOrDefault());
|
||||||
}
|
|
||||||
|
|
||||||
private void MainWindow_LibraryLoaded(object sender, List<LibraryBook> dbBooks)
|
await _viewModel.ProductsDisplay.DisplayBooks(dbBooks);
|
||||||
{
|
|
||||||
_viewModel.ProductsDisplay.InitialDisplay(dbBooks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
@ -196,10 +194,5 @@ namespace LibationAvalonia.Views
|
|||||||
quickFiltersToolStripMenuItem = this.FindControl<MenuItem>(nameof(quickFiltersToolStripMenuItem));
|
quickFiltersToolStripMenuItem = this.FindControl<MenuItem>(nameof(quickFiltersToolStripMenuItem));
|
||||||
productsDisplay = this.FindControl<ProductsDisplay>(nameof(productsDisplay));
|
productsDisplay = this.FindControl<ProductsDisplay>(nameof(productsDisplay));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDataContextChanged(EventArgs e)
|
|
||||||
{
|
|
||||||
base.OnDataContextChanged(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,40 +10,53 @@
|
|||||||
<Grid>
|
<Grid>
|
||||||
|
|
||||||
<DataGrid
|
<DataGrid
|
||||||
CopyingRowClipboardContent="DataGrid_CopyToClipboard"
|
|
||||||
Name="productsGrid"
|
Name="productsGrid"
|
||||||
|
ClipboardCopyMode="IncludeHeader"
|
||||||
GridLinesVisibility="All"
|
GridLinesVisibility="All"
|
||||||
AutoGenerateColumns="False"
|
AutoGenerateColumns="False"
|
||||||
Items="{Binding GridEntries}"
|
Items="{Binding GridEntries}"
|
||||||
Sorting="ProductsGrid_Sorting"
|
CanUserSortColumns="True"
|
||||||
CanUserSortColumns="True"
|
|
||||||
CanUserReorderColumns="True">
|
CanUserReorderColumns="True">
|
||||||
|
|
||||||
<DataGrid.Columns>
|
<DataGrid.Columns>
|
||||||
|
|
||||||
<controls:DataGridCheckBoxColumnExt
|
<DataGridTemplateColumn
|
||||||
PropertyChanged="RemoveColumn_PropertyChanged"
|
|
||||||
IsVisible="{Binding RemoveColumnVisivle}"
|
|
||||||
Header="Remove"
|
|
||||||
IsThreeState="True"
|
|
||||||
IsReadOnly="False"
|
|
||||||
CanUserSort="True"
|
CanUserSort="True"
|
||||||
Binding="{Binding Remove, Mode=TwoWay}"
|
IsVisible="{Binding RemoveColumnVisivle}"
|
||||||
Width="70" SortMemberPath="Remove" />
|
PropertyChanged="RemoveColumn_PropertyChanged"
|
||||||
|
Header="Remove"
|
||||||
<DataGridTemplateColumn CanUserSort="True" Width="75" Header="Liberate" SortMemberPath="Liberate">
|
IsReadOnly="False"
|
||||||
|
SortMemberPath="Remove"
|
||||||
|
Width="75">
|
||||||
|
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<StackPanel Orientation="Horizontal">
|
<CheckBox
|
||||||
<Button Width="75" Height="80" Click="LiberateButton_Click" ToolTip.Tip="{Binding Liberate.ToolTip}">
|
HorizontalAlignment="Center"
|
||||||
<Image Stretch="None" Height="80" Source="{Binding Liberate.Image}" />
|
IsThreeState="True"
|
||||||
</Button>
|
IsChecked="{Binding Remove, Mode=TwoWay}" />
|
||||||
</StackPanel>
|
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
<DataGridTemplateColumn CanUserSort="False" Width="80" Header="Cover" SortMemberPath="Cover">
|
<DataGridTemplateColumn CanUserSort="True" Width="75" Header="Liberate" SortMemberPath="Liberate" ClipboardContentBinding="{Binding Liberate.ToolTip}">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Button Padding="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Click="LiberateButton_Click" ToolTip.Tip="{Binding Liberate.ToolTip}">
|
||||||
|
<Image Stretch="None" Source="{Binding Liberate.Image}" />
|
||||||
|
<Button.ContextMenu>
|
||||||
|
<ContextMenu IsVisible="{Binding !Liberate.IsSeries}">
|
||||||
|
<MenuItem Header="Item 1" Click="ContextMenuItem1_Click" />
|
||||||
|
<MenuItem Header="Item 2" Click="ContextMenuItem2_Click" />
|
||||||
|
<MenuItem Header="Item 3" Click="ContextMenuItem3_Click" />
|
||||||
|
</ContextMenu>
|
||||||
|
</Button.ContextMenu>
|
||||||
|
</Button>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
|
<DataGridTemplateColumn CanUserSort="False" Width="80" Header="Cover" SortMemberPath="Cover" ClipboardContentBinding="{Binding LibraryBook.Book.PictureLarge}">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Image Tapped="Cover_Click" Height="80" Source="{Binding Cover}" ToolTip.Tip="Click to see full size" />
|
<Image Tapped="Cover_Click" Height="80" Source="{Binding Cover}" ToolTip.Tip="Click to see full size" />
|
||||||
@ -51,7 +64,7 @@
|
|||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
<DataGridTemplateColumn MinWidth="150" Width="2*" Header="Title" CanUserSort="True" SortMemberPath="Title">
|
<DataGridTemplateColumn MinWidth="150" Width="2*" Header="Title" CanUserSort="True" SortMemberPath="Title" ClipboardContentBinding="{Binding Title}">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Panel Background="{Binding BackgroundBrush}">
|
<Panel Background="{Binding BackgroundBrush}">
|
||||||
@ -63,7 +76,7 @@
|
|||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
<DataGridTemplateColumn MinWidth="80" Width="1*" Header="Authors" CanUserSort="True" SortMemberPath="Authors">
|
<DataGridTemplateColumn MinWidth="80" Width="1*" Header="Authors" CanUserSort="True" SortMemberPath="Authors" ClipboardContentBinding="{Binding Authors}">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Panel Background="{Binding BackgroundBrush}">
|
<Panel Background="{Binding BackgroundBrush}">
|
||||||
@ -75,7 +88,7 @@
|
|||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
<DataGridTemplateColumn MinWidth="80" Width="1*" Header="Narrators" CanUserSort="True" SortMemberPath="Narrators">
|
<DataGridTemplateColumn MinWidth="80" Width="1*" Header="Narrators" CanUserSort="True" SortMemberPath="Narrators" ClipboardContentBinding="{Binding Narrators}">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Panel Background="{Binding BackgroundBrush}">
|
<Panel Background="{Binding BackgroundBrush}">
|
||||||
@ -87,7 +100,7 @@
|
|||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
<DataGridTemplateColumn Width="90" Header="Length" CanUserSort="True" SortMemberPath="Length">
|
<DataGridTemplateColumn Width="90" Header="Length" CanUserSort="True" SortMemberPath="Length" ClipboardContentBinding="{Binding Length}">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Panel Background="{Binding BackgroundBrush}">
|
<Panel Background="{Binding BackgroundBrush}">
|
||||||
@ -99,7 +112,7 @@
|
|||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
<DataGridTemplateColumn MinWidth="80" Width="1*" Header="Series" CanUserSort="True" SortMemberPath="Series">
|
<DataGridTemplateColumn MinWidth="80" Width="1*" Header="Series" CanUserSort="True" SortMemberPath="Series" ClipboardContentBinding="{Binding Series}">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Panel Background="{Binding BackgroundBrush}">
|
<Panel Background="{Binding BackgroundBrush}">
|
||||||
@ -111,7 +124,7 @@
|
|||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
<DataGridTemplateColumn MinWidth="100" Width="1*" Header="Description" CanUserSort="True" SortMemberPath="Description">
|
<DataGridTemplateColumn MinWidth="100" Width="1*" Header="Description" CanUserSort="True" SortMemberPath="Description" ClipboardContentBinding="{Binding LongDescription}">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Panel Background="{Binding BackgroundBrush}">
|
<Panel Background="{Binding BackgroundBrush}">
|
||||||
@ -123,7 +136,7 @@
|
|||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
<DataGridTemplateColumn Width="100" Header="Category" CanUserSort="True" SortMemberPath="Category">
|
<DataGridTemplateColumn Width="100" Header="Category" CanUserSort="True" SortMemberPath="Category" ClipboardContentBinding="{Binding Category}">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Panel Background="{Binding BackgroundBrush}">
|
<Panel Background="{Binding BackgroundBrush}">
|
||||||
@ -135,7 +148,7 @@
|
|||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
<DataGridTemplateColumn Width="120" Header="Product
Rating" CanUserSort="True" SortMemberPath="ProductRating">
|
<DataGridTemplateColumn Width="120" Header="Product
Rating" CanUserSort="True" SortMemberPath="ProductRating" ClipboardContentBinding="{Binding ProductRating}">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Panel Background="{Binding BackgroundBrush}">
|
<Panel Background="{Binding BackgroundBrush}">
|
||||||
@ -147,7 +160,7 @@
|
|||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
<DataGridTemplateColumn Width="90" Header="Purchase
Date" CanUserSort="True" SortMemberPath="PurchaseDate">
|
<DataGridTemplateColumn Width="90" Header="Purchase
Date" CanUserSort="True" SortMemberPath="PurchaseDate" ClipboardContentBinding="{Binding PurchaseDate}">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Panel Background="{Binding BackgroundBrush}">
|
<Panel Background="{Binding BackgroundBrush}">
|
||||||
@ -159,7 +172,7 @@
|
|||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
<DataGridTemplateColumn Width="120" Header="My Rating" CanUserSort="True" SortMemberPath="MyRating">
|
<DataGridTemplateColumn Width="120" Header="My Rating" CanUserSort="True" SortMemberPath="MyRating" ClipboardContentBinding="{Binding MyRating}">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Panel Background="{Binding BackgroundBrush}">
|
<Panel Background="{Binding BackgroundBrush}">
|
||||||
@ -171,7 +184,7 @@
|
|||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
<DataGridTemplateColumn Width="135" Header="Misc" CanUserSort="True" SortMemberPath="Misc">
|
<DataGridTemplateColumn Width="135" Header="Misc" CanUserSort="True" SortMemberPath="Misc" ClipboardContentBinding="{Binding Misc}">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Panel Background="{Binding BackgroundBrush}">
|
<Panel Background="{Binding BackgroundBrush}">
|
||||||
@ -183,7 +196,7 @@
|
|||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
<DataGridTemplateColumn CanUserSort="True" Width="100" Header="Tags" SortMemberPath="BookTags">
|
<DataGridTemplateColumn CanUserSort="True" Width="100" Header="Tags" SortMemberPath="BookTags" ClipboardContentBinding="{Binding BookTags.Tags}">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
|
|||||||
@ -10,6 +10,7 @@ using LibationAvalonia.Dialogs;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
|
||||||
namespace LibationAvalonia.Views
|
namespace LibationAvalonia.Views
|
||||||
{
|
{
|
||||||
@ -27,32 +28,33 @@ namespace LibationAvalonia.Views
|
|||||||
if (Design.IsDesignMode)
|
if (Design.IsDesignMode)
|
||||||
{
|
{
|
||||||
using var context = DbContexts.GetContext();
|
using var context = DbContexts.GetContext();
|
||||||
List<GridEntry> sampleEntries = new()
|
List<LibraryBook> sampleEntries = new()
|
||||||
{
|
{
|
||||||
new LibraryBookEntry(context.GetLibraryBook_Flat_NoTracking("B017V4IM1G")),
|
//context.GetLibraryBook_Flat_NoTracking("B00DCD0OXU"),
|
||||||
new LibraryBookEntry(context.GetLibraryBook_Flat_NoTracking("B017V4IWVG")),
|
context.GetLibraryBook_Flat_NoTracking("B017V4IM1G"),
|
||||||
new LibraryBookEntry(context.GetLibraryBook_Flat_NoTracking("B017V4JA2Q")),
|
context.GetLibraryBook_Flat_NoTracking("B017V4IWVG"),
|
||||||
new LibraryBookEntry(context.GetLibraryBook_Flat_NoTracking("B017V4NUPO")),
|
context.GetLibraryBook_Flat_NoTracking("B017V4JA2Q"),
|
||||||
new LibraryBookEntry(context.GetLibraryBook_Flat_NoTracking("B017V4NMX4")),
|
context.GetLibraryBook_Flat_NoTracking("B017V4NUPO"),
|
||||||
new LibraryBookEntry(context.GetLibraryBook_Flat_NoTracking("B017V4NOZ0")),
|
context.GetLibraryBook_Flat_NoTracking("B017V4NMX4"),
|
||||||
new LibraryBookEntry(context.GetLibraryBook_Flat_NoTracking("B017WJ5ZK6")),
|
context.GetLibraryBook_Flat_NoTracking("B017V4NOZ0"),
|
||||||
|
context.GetLibraryBook_Flat_NoTracking("B017WJ5ZK6")
|
||||||
};
|
};
|
||||||
DataContext = new ProductsDisplayViewModel(sampleEntries);
|
|
||||||
|
var pdvm = new ProductsDisplayViewModel();
|
||||||
|
pdvm.DisplayBooks(sampleEntries);
|
||||||
|
DataContext = pdvm;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Configure_ColumnCustomization();
|
Configure_ColumnCustomization();
|
||||||
|
|
||||||
foreach (var column in productsGrid.Columns)
|
foreach (var column in productsGrid.Columns)
|
||||||
{
|
{
|
||||||
column.CustomSortComparer = new RowComparer(column);
|
column.CustomSortComparer = new RowComparer(column);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProductsGrid_Sorting(object sender, DataGridColumnEventArgs e)
|
|
||||||
{
|
|
||||||
_viewModel.Sort(e.Column);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveColumn_PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
|
private void RemoveColumn_PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is DataGridColumn col && e.Property.Name == nameof(DataGridColumn.IsVisible))
|
if (sender is DataGridColumn col && e.Property.Name == nameof(DataGridColumn.IsVisible))
|
||||||
@ -62,11 +64,6 @@ namespace LibationAvalonia.Views
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DataGrid_CopyToClipboard(object sender, DataGridRowClipboardEventArgs e)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
@ -188,6 +185,22 @@ namespace LibationAvalonia.Views
|
|||||||
|
|
||||||
#region Button Click Handlers
|
#region Button Click Handlers
|
||||||
|
|
||||||
|
public void ContextMenuItem1_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
var lbe = getBoundEntry(args.Source);
|
||||||
|
}
|
||||||
|
public void ContextMenuItem2_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
var lbe = getBoundEntry(args.Source);
|
||||||
|
}
|
||||||
|
public void ContextMenuItem3_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
var lbe = getBoundEntry(args.Source);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LibraryBookEntry getBoundEntry(IInteractive source)
|
||||||
|
=> (source is IStyledElement se && se.DataContext is LibraryBookEntry lbe ? lbe : null);
|
||||||
|
|
||||||
public void LiberateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
public void LiberateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||||
{
|
{
|
||||||
var button = args.Source as Button;
|
var button = args.Source as Button;
|
||||||
@ -198,7 +211,7 @@ namespace LibationAvalonia.Views
|
|||||||
|
|
||||||
//Expanding and collapsing reset the list, which will cause focus to shift
|
//Expanding and collapsing reset the list, which will cause focus to shift
|
||||||
//to the topright cell. Reset focus onto the clicked button's cell.
|
//to the topright cell. Reset focus onto the clicked button's cell.
|
||||||
((sender as Control).Parent.Parent as DataGridCell)?.Focus();
|
(sender as Button).Parent?.Focus();
|
||||||
}
|
}
|
||||||
else if (button.DataContext is LibraryBookEntry lbEntry)
|
else if (button.DataContext is LibraryBookEntry lbEntry)
|
||||||
{
|
{
|
||||||
@ -212,12 +225,11 @@ namespace LibationAvalonia.Views
|
|||||||
imageDisplayDialog.Close();
|
imageDisplayDialog.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Cover_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
public void Cover_Click(object sender, Avalonia.Input.TappedEventArgs args)
|
||||||
{
|
{
|
||||||
if (sender is not Image tblock || tblock.DataContext is not GridEntry gEntry)
|
if (sender is not Image tblock || tblock.DataContext is not GridEntry gEntry)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
||||||
if (imageDisplayDialog is null || !imageDisplayDialog.IsVisible)
|
if (imageDisplayDialog is null || !imageDisplayDialog.IsVisible)
|
||||||
{
|
{
|
||||||
imageDisplayDialog = new ImageDisplayDialog();
|
imageDisplayDialog = new ImageDisplayDialog();
|
||||||
@ -252,7 +264,7 @@ namespace LibationAvalonia.Views
|
|||||||
imageDisplayDialog.Show();
|
imageDisplayDialog.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Description_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
public void Description_Click(object sender, Avalonia.Input.TappedEventArgs args)
|
||||||
{
|
{
|
||||||
if (sender is TextBlock tblock && tblock.DataContext is GridEntry gEntry)
|
if (sender is TextBlock tblock && tblock.DataContext is GridEntry gEntry)
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user