|
@@ -1,6 +1,7 @@
|
|
|
using System.Collections;
|
|
|
using System.Collections.ObjectModel;
|
|
|
using System.ComponentModel;
|
|
|
+using System.Diagnostics.CodeAnalysis;
|
|
|
using System.Linq.Expressions;
|
|
|
using System.Runtime.CompilerServices;
|
|
|
using Avalonia.Controls.Selection;
|
|
@@ -11,6 +12,7 @@ using InABox.Configuration;
|
|
|
using InABox.Core;
|
|
|
|
|
|
using JetBrains.Annotations;
|
|
|
+using NotNullAttribute = JetBrains.Annotations.NotNullAttribute;
|
|
|
|
|
|
namespace InABox.Avalonia
|
|
|
{
|
|
@@ -34,7 +36,7 @@ namespace InABox.Avalonia
|
|
|
!String.IsNullOrWhiteSpace(filename)
|
|
|
&& File.Exists(CacheFileName(filename));
|
|
|
|
|
|
- public static string CacheFileName(string? filename) =>
|
|
|
+ public static string CacheFileName(string filename) =>
|
|
|
Path.Combine(CacheFolder(), filename);
|
|
|
|
|
|
public static string CacheFolder()
|
|
@@ -79,7 +81,7 @@ namespace InABox.Avalonia
|
|
|
|
|
|
public Func<Filter<TEntity>> Filter { get; set; }
|
|
|
|
|
|
- protected virtual Filter<TEntity> BaseFilter() => null;
|
|
|
+ protected virtual Filter<TEntity>? BaseFilter() => null;
|
|
|
|
|
|
public IModelHost Host { get; set; }
|
|
|
|
|
@@ -109,13 +111,10 @@ namespace InABox.Avalonia
|
|
|
{
|
|
|
AllItems = new CoreObservableCollection<TItem>();
|
|
|
AllItems.CollectionChanged += (sender, args) => ItemsChanged(AllItems);
|
|
|
- EnableSynchronization(AllItems);
|
|
|
+ // EnableSynchronization(AllItems);
|
|
|
|
|
|
Items = new CoreObservableCollection<TItem>();
|
|
|
- EnableSynchronization(Items);
|
|
|
-
|
|
|
- SelectedItems = new CoreObservableCollection<TItem>();
|
|
|
- EnableSynchronization(SelectedItems);
|
|
|
+ // EnableSynchronization(Items);
|
|
|
|
|
|
Reset();
|
|
|
Host = host;
|
|
@@ -128,29 +127,29 @@ namespace InABox.Avalonia
|
|
|
{
|
|
|
}
|
|
|
|
|
|
- private void EnableSynchronization(IEnumerable items)
|
|
|
- {
|
|
|
- // BindingBase.EnableCollectionSynchronization(items, null,
|
|
|
- // (collection, context, method, access) =>
|
|
|
- // {
|
|
|
- // lock (collection)
|
|
|
- // {
|
|
|
- // method?.Invoke();
|
|
|
- // }
|
|
|
- // }
|
|
|
- // );
|
|
|
- }
|
|
|
+ // private void EnableSynchronization(IEnumerable items)
|
|
|
+ // {
|
|
|
+ // // BindingBase.EnableCollectionSynchronization(items, null,
|
|
|
+ // // (collection, context, method, access) =>
|
|
|
+ // // {
|
|
|
+ // // lock (collection)
|
|
|
+ // // {
|
|
|
+ // // method?.Invoke();
|
|
|
+ // // }
|
|
|
+ // // }
|
|
|
+ // // );
|
|
|
+ // }
|
|
|
|
|
|
#region INotifyPropertyChanged
|
|
|
|
|
|
- public event PropertyChangedEventHandler PropertyChanged;
|
|
|
+ public event PropertyChangedEventHandler? PropertyChanged;
|
|
|
|
|
|
protected void DoPropertyChanged(object sender, PropertyChangedEventArgs args)
|
|
|
{
|
|
|
PropertyChanged?.Invoke(sender, args);
|
|
|
}
|
|
|
|
|
|
- protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
|
|
|
+ protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
|
|
|
{
|
|
|
if (EqualityComparer<T>.Default.Equals(field, value))
|
|
|
return false;
|
|
@@ -159,7 +158,7 @@ namespace InABox.Avalonia
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
|
|
+ protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
|
|
|
=> DoPropertyChanged(this, new PropertyChangedEventArgs(propertyName));
|
|
|
|
|
|
#endregion
|
|
@@ -173,7 +172,7 @@ namespace InABox.Avalonia
|
|
|
return Images.GetValueOrDefault(id);
|
|
|
}
|
|
|
|
|
|
- public byte[] GetImage(Guid id) => Images.GetValueOrDefault(id);
|
|
|
+ public byte[]? GetImage(Guid id) => Images.GetValueOrDefault(id);
|
|
|
|
|
|
public bool HasImages() => Images.Any();
|
|
|
|
|
@@ -197,7 +196,7 @@ namespace InABox.Avalonia
|
|
|
OnPropertyChanged(nameof(AvailableFilters));
|
|
|
}
|
|
|
|
|
|
- protected Filter<TEntity> EffectiveFilter()
|
|
|
+ protected Filter<TEntity>? EffectiveFilter()
|
|
|
{
|
|
|
var filters = new Filters<TEntity>();
|
|
|
filters.Add(BaseFilter());
|
|
@@ -219,7 +218,6 @@ namespace InABox.Avalonia
|
|
|
Loaded = false;
|
|
|
AllItems.Clear();
|
|
|
Items.Clear();
|
|
|
- SelectedItems.Clear();
|
|
|
Images.Clear();
|
|
|
}
|
|
|
|
|
@@ -229,9 +227,8 @@ namespace InABox.Avalonia
|
|
|
|
|
|
private void DoRefresh(bool force)
|
|
|
{
|
|
|
- var curselected = SelectedItems.ToArray();
|
|
|
+ var selectedIDs = SelectedItems.Select(x => x.ID).ToHashSet();
|
|
|
Items.Clear();
|
|
|
- SelectedItems.Clear();
|
|
|
|
|
|
var dataFileName = DataFileName();
|
|
|
if (!force && !Loaded && CoreRepository.IsCached(dataFileName))
|
|
@@ -240,7 +237,10 @@ namespace InABox.Avalonia
|
|
|
if (LoadFromStorage())
|
|
|
{
|
|
|
DoAfterLoad();
|
|
|
- SelectedItems.AddRange(Items.Where(x=>curselected.Contains(x)));
|
|
|
+ foreach(var item in Items)
|
|
|
+ {
|
|
|
+ item.IsSelected = selectedIDs.Contains(item.ID);
|
|
|
+ }
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
@@ -249,13 +249,16 @@ namespace InABox.Avalonia
|
|
|
{
|
|
|
DoLoad();
|
|
|
SaveToStorage();
|
|
|
- SelectedItems.AddRange(Items.Where(x=>curselected.Contains(x)));
|
|
|
+ foreach(var item in Items)
|
|
|
+ {
|
|
|
+ item.IsSelected = selectedIDs.Contains(item.ID);
|
|
|
+ }
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
- SelectedItems.AddRange(Items.Where(x => curselected.Contains(x)));
|
|
|
-
|
|
|
-
|
|
|
+ foreach(var item in Items)
|
|
|
+ {
|
|
|
+ item.IsSelected = selectedIDs.Contains(item.ID);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private void AfterRefresh()
|
|
@@ -299,10 +302,10 @@ namespace InABox.Avalonia
|
|
|
Initialize();
|
|
|
}
|
|
|
|
|
|
- public event CoreRepositoryChangedEvent Changed;
|
|
|
+ public event CoreRepositoryChangedEvent? Changed;
|
|
|
protected void NotifyChanged() => Changed?.Invoke(this, new CoreRepositoryChangedEventArgs());
|
|
|
|
|
|
- public virtual SortOrder<TEntity> Sort => LookupFactory.DefineSort<TEntity>();
|
|
|
+ public virtual SortOrder<TEntity>? Sort => LookupFactory.DefineSort<TEntity>();
|
|
|
|
|
|
protected CoreObservableCollection<TItem> AllItems { get; private set; }
|
|
|
|
|
@@ -313,40 +316,44 @@ namespace InABox.Avalonia
|
|
|
public int ItemCount => Items.Count;
|
|
|
|
|
|
IEnumerable ICoreRepository.Items => Items;
|
|
|
-
|
|
|
+
|
|
|
#region Item Selection
|
|
|
-
|
|
|
- public CoreObservableCollection<TItem> SelectedItems { get; private set; }
|
|
|
-
|
|
|
+
|
|
|
IEnumerable ICoreRepository.SelectedItems => SelectedItems;
|
|
|
-
|
|
|
- public bool IsSelected(TItem item) => (item != null) && SelectedItems.Contains(item);
|
|
|
+
|
|
|
+ IEnumerable<TItem> SelectedItems => Items.Where(x => x.IsSelected);
|
|
|
+
|
|
|
+ public bool IsSelected(TItem? item) => item is not null && item.IsSelected;
|
|
|
|
|
|
public void SetSelectedItems(IEnumerable<TItem> items)
|
|
|
{
|
|
|
- SelectedItems.ReplaceRange(items);
|
|
|
+ var selectedItems = items.Select(x => x.ID).ToHashSet();
|
|
|
+ foreach(var item in Items)
|
|
|
+ {
|
|
|
+ item.IsSelected = selectedItems.Contains(item.ID);
|
|
|
+ }
|
|
|
Search();
|
|
|
}
|
|
|
|
|
|
- public void SelectItem([CanBeNull] TItem item)
|
|
|
+ public void SelectItem([CanBeNull] TItem? item)
|
|
|
{
|
|
|
- if ((item != null) && !SelectedItems.Contains(item))
|
|
|
+ if ((item != null) && !item.IsSelected)
|
|
|
{
|
|
|
- SelectedItems.Add(item);
|
|
|
+ item.IsSelected = true;
|
|
|
Search();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- public void UnselectItem([CanBeNull] TItem item)
|
|
|
+ public void UnselectItem([CanBeNull] TItem? item)
|
|
|
{
|
|
|
- if ((item != null) && SelectedItems.Contains(item))
|
|
|
+ if ((item != null) && item.IsSelected)
|
|
|
{
|
|
|
- SelectedItems.Remove(item);
|
|
|
+ item.IsSelected = false;
|
|
|
Search();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- public void ToggleSelection(TItem item)
|
|
|
+ public void ToggleSelection(TItem? item)
|
|
|
{
|
|
|
if (IsSelected(item))
|
|
|
UnselectItem(item);
|
|
@@ -356,13 +363,19 @@ namespace InABox.Avalonia
|
|
|
|
|
|
public void SelectNone()
|
|
|
{
|
|
|
- SelectedItems.Clear();
|
|
|
+ foreach(var item in Items)
|
|
|
+ {
|
|
|
+ item.IsSelected = false;
|
|
|
+ }
|
|
|
Search();
|
|
|
}
|
|
|
|
|
|
public void SelectAll()
|
|
|
{
|
|
|
- SelectedItems.ReplaceRange(Items);
|
|
|
+ foreach(var item in Items)
|
|
|
+ {
|
|
|
+ item.IsSelected = true;
|
|
|
+ }
|
|
|
Search();
|
|
|
}
|
|
|
|
|
@@ -376,9 +389,9 @@ namespace InABox.Avalonia
|
|
|
|
|
|
#region Searching
|
|
|
|
|
|
- public Func<TItem, bool> SearchPredicate { get; set; }
|
|
|
+ public Func<TItem, bool>? SearchPredicate { get; set; }
|
|
|
|
|
|
- public Func<List<TItem>,List<TItem>> SortPredicate { get; set; }
|
|
|
+ public Func<List<TItem>,List<TItem>>? SortPredicate { get; set; }
|
|
|
|
|
|
public ICoreRepository Search(Func<TItem, bool> searchpredicate, Func<List<TItem>,List<TItem>> sortpredicate)
|
|
|
{
|
|
@@ -397,7 +410,6 @@ namespace InABox.Avalonia
|
|
|
|
|
|
public ICoreRepository Search()
|
|
|
{
|
|
|
- var curselected = SelectedItems.ToArray();
|
|
|
var items = AllItems == null
|
|
|
? new List<TItem>()
|
|
|
: SearchPredicate != null
|
|
@@ -407,9 +419,6 @@ namespace InABox.Avalonia
|
|
|
if (SortPredicate != null)
|
|
|
items = SortPredicate(items);
|
|
|
|
|
|
- SelectedItems.ReplaceRange(items.Where(x=>curselected.Contains(x)));
|
|
|
- OnPropertyChanged(nameof(SelectedItems));
|
|
|
-
|
|
|
Items.ReplaceRange(items);
|
|
|
OnPropertyChanged(nameof(Items));
|
|
|
OnPropertyChanged(nameof(ItemCount));
|
|
@@ -422,7 +431,7 @@ namespace InABox.Avalonia
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
- protected virtual Expression<Func<TEntity, object>> ImageColumn => null;
|
|
|
+ protected virtual Expression<Func<TEntity, object?>>? ImageColumn => null;
|
|
|
|
|
|
#region Loading
|
|
|
|
|
@@ -437,7 +446,7 @@ namespace InABox.Avalonia
|
|
|
Sort
|
|
|
);
|
|
|
|
|
|
- if (ImageColumn != null)
|
|
|
+ if (ImageColumn is not null)
|
|
|
{
|
|
|
_query.Add(
|
|
|
new Filter<Document>(x => x.ID).InQuery(EffectiveFilter(), ImageColumn),
|
|
@@ -545,6 +554,11 @@ namespace InABox.Avalonia
|
|
|
public void Set([NotNull] String key, CoreTable table) => _data[key] = table;
|
|
|
|
|
|
public bool Contains([NotNull] String key) => _data.ContainsKey(key);
|
|
|
+
|
|
|
+ public bool TryGet(string key, [NotNullWhen(true)] out CoreTable? table)
|
|
|
+ {
|
|
|
+ return _data.TryGetValue(key, out table);
|
|
|
+ }
|
|
|
|
|
|
public void SerializeBinary(CoreBinaryWriter writer)
|
|
|
{
|
|
@@ -603,10 +617,13 @@ namespace InABox.Avalonia
|
|
|
var defs = _query.Definitions();
|
|
|
foreach (var key in defs.Keys)
|
|
|
{
|
|
|
- var table = storage.Contains(key.ToString())
|
|
|
- ? storage.Get(key.ToString())
|
|
|
- : InitializeTable(defs[key]);
|
|
|
- if (CheckColumns(table, _query.Definitions()[key].Columns))
|
|
|
+ var keyStr = key.ToString() ?? "";
|
|
|
+ if(!storage.TryGet(keyStr, out var table))
|
|
|
+ {
|
|
|
+ table = InitializeTable(defs[key]);
|
|
|
+ }
|
|
|
+ var queryDef = _query.Definitions()[key];
|
|
|
+ if (CheckColumns(table, queryDef.Type, queryDef.Columns))
|
|
|
_query.Set(key, table);
|
|
|
else
|
|
|
return false;
|
|
@@ -619,8 +636,9 @@ namespace InABox.Avalonia
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- private bool CheckColumns(CoreTable table, IColumns required)
|
|
|
+ private bool CheckColumns(CoreTable table, Type T, IColumns? required)
|
|
|
{
|
|
|
+ required = CoreUtils.GetColumns(T, required);
|
|
|
foreach (var column in required.ColumnNames())
|
|
|
{
|
|
|
if (!table.Columns.Any(x => String.Equals(x.ColumnName, column)))
|
|
@@ -648,7 +666,7 @@ namespace InABox.Avalonia
|
|
|
QueryStorage storage = new QueryStorage();
|
|
|
var results = _query.Results();
|
|
|
foreach (var key in results.Keys)
|
|
|
- storage.Set(key.ToString(),results[key]);
|
|
|
+ storage.Set(key.ToString() ?? "", results[key]);
|
|
|
var data = storage.WriteBinary(BinarySerializationSettings.Latest);
|
|
|
try
|
|
|
{
|
|
@@ -666,7 +684,7 @@ namespace InABox.Avalonia
|
|
|
|
|
|
#region CRUD Operations
|
|
|
|
|
|
- public event CoreRepositoryItemCreatedEvent<TItem> ItemAdded;
|
|
|
+ public event CoreRepositoryItemCreatedEvent<TItem>? ItemAdded;
|
|
|
|
|
|
private T CreateItem<T>(CoreRow row)
|
|
|
where T : Shell<TParent,TEntity>, new()
|
|
@@ -693,7 +711,7 @@ namespace InABox.Avalonia
|
|
|
{
|
|
|
_table.Rows.Add(item.Row);
|
|
|
AllItems.Add(item);
|
|
|
- Search(null);
|
|
|
+ Search();
|
|
|
NotifyChanged();
|
|
|
}
|
|
|
|
|
@@ -708,7 +726,7 @@ namespace InABox.Avalonia
|
|
|
{
|
|
|
_table.Rows.Remove(item.Row);
|
|
|
AllItems.Remove(item);
|
|
|
- Search(null);
|
|
|
+ Search();
|
|
|
NotifyChanged();
|
|
|
}
|
|
|
|
|
@@ -716,6 +734,10 @@ namespace InABox.Avalonia
|
|
|
{
|
|
|
new Client<TEntity>().Save(Items.Select(x=>x.Entity).Where(x=>x.IsChanged()),auditMessage);
|
|
|
}
|
|
|
+ public virtual Task SaveAsync(string auditMessage)
|
|
|
+ {
|
|
|
+ return Task.Run(() => Save(auditMessage));
|
|
|
+ }
|
|
|
|
|
|
object ICoreRepository.CreateItem() => this.CreateItem();
|
|
|
|