|
|
@@ -1,8 +1,11 @@
|
|
|
using System.Collections;
|
|
|
+using System.Collections.ObjectModel;
|
|
|
using System.ComponentModel;
|
|
|
using System.Linq.Expressions;
|
|
|
using System.Runtime.CompilerServices;
|
|
|
+using Avalonia.Controls.Selection;
|
|
|
using Avalonia.Threading;
|
|
|
+using CommunityToolkit.Mvvm.ComponentModel;
|
|
|
using InABox.Clients;
|
|
|
using InABox.Configuration;
|
|
|
using InABox.Core;
|
|
|
@@ -23,7 +26,8 @@ namespace InABox.Avalonia
|
|
|
}
|
|
|
|
|
|
public delegate void CoreRepositoryItemCreatedEvent<TShell>(object sender, CoreRepositoryItemCreatedArgs<TShell> args);
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
public abstract class CoreRepository
|
|
|
{
|
|
|
public static bool IsCached(string? filename) =>
|
|
|
@@ -36,6 +40,12 @@ namespace InABox.Avalonia
|
|
|
public static string CacheFolder()
|
|
|
{
|
|
|
var result = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
|
|
+ if (OperatingSystem.IsWindows())
|
|
|
+ {
|
|
|
+ var assembly = Path.GetFileNameWithoutExtension(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName);
|
|
|
+ result = Path.Combine(result, assembly);
|
|
|
+ }
|
|
|
+
|
|
|
if (CacheID != Guid.Empty)
|
|
|
result = Path.Combine(result,CacheID.ToString());
|
|
|
if (!Directory.Exists(result))
|
|
|
@@ -44,14 +54,22 @@ namespace InABox.Avalonia
|
|
|
}
|
|
|
|
|
|
public static Guid CacheID { get; set; }
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
- public abstract string DefaultFileName();
|
|
|
+ public partial class CoreRepositoryFilter : ObservableObject
|
|
|
+ {
|
|
|
+ [ObservableProperty]
|
|
|
+ private string? _name;
|
|
|
+
|
|
|
+ [ObservableProperty]
|
|
|
+ private string? _filter;
|
|
|
+
|
|
|
+ [ObservableProperty]
|
|
|
+ private bool _selected;
|
|
|
|
|
|
}
|
|
|
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
public abstract class CoreRepository<TParent, TItem, TEntity> : CoreRepository, ICoreRepository, IEnumerable<TItem>
|
|
|
where TParent : CoreRepository<TParent, TItem, TEntity>
|
|
|
where TEntity : Entity, IRemotable, IPersistent, new()
|
|
|
@@ -65,11 +83,27 @@ namespace InABox.Avalonia
|
|
|
|
|
|
public IModelHost Host { get; set; }
|
|
|
|
|
|
- public DateTime LastUpdated { get; protected set; }
|
|
|
+ private DateTime _lastUpdated = DateTime.MinValue;
|
|
|
+ public DateTime LastUpdated
|
|
|
+ {
|
|
|
+ get => _lastUpdated;
|
|
|
+ protected set
|
|
|
+ {
|
|
|
+ _lastUpdated = value;
|
|
|
+ OnPropertyChanged();
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
public Func<string>? FileName { get; }
|
|
|
|
|
|
- public override string DefaultFileName() => typeof(TEntity).Name + ".db";
|
|
|
+
|
|
|
+ private string DataFileName() => FileName != null
|
|
|
+ ? $"{FileName.Invoke()}.data"
|
|
|
+ : string.Empty;
|
|
|
+
|
|
|
+ private string FilterFileName() => !string.IsNullOrWhiteSpace(FilterTag) && FileName != null
|
|
|
+ ? $"{FileName.Invoke()}.filter"
|
|
|
+ : string.Empty;
|
|
|
|
|
|
protected CoreRepository(IModelHost host, Func<Filter<TEntity>> filter, Func<string>? filename = null)
|
|
|
{
|
|
|
@@ -87,6 +121,7 @@ namespace InABox.Avalonia
|
|
|
Host = host;
|
|
|
Filter = filter;
|
|
|
FileName = filename;
|
|
|
+
|
|
|
}
|
|
|
|
|
|
protected virtual void ItemsChanged(IEnumerable<TItem> items)
|
|
|
@@ -143,26 +178,25 @@ namespace InABox.Avalonia
|
|
|
public bool HasImages() => Images.Any();
|
|
|
|
|
|
#endregion
|
|
|
-
|
|
|
|
|
|
protected virtual string FilterTag => typeof(TEntity).EntityName().Split('.').Last();
|
|
|
|
|
|
- public CoreFilterDefinitions AvailableFilters()
|
|
|
- {
|
|
|
- return string.IsNullOrWhiteSpace(FilterTag)
|
|
|
- ? new CoreFilterDefinitions()
|
|
|
- : new GlobalConfiguration<CoreFilterDefinitions>(FilterTag).Load();
|
|
|
- }
|
|
|
-
|
|
|
- protected Filter<TEntity> SelectedFilter;
|
|
|
+ public CoreObservableCollection<CoreRepositoryFilter> AvailableFilters { get; } = new();
|
|
|
+ IEnumerable ICoreRepository.AvailableFilters => AvailableFilters;
|
|
|
|
|
|
- public void SelectFilter(String name)
|
|
|
+ protected Filter<TEntity>? SelectedFilter =>
|
|
|
+ Serialization.Deserialize<Filter<TEntity>>(AvailableFilters.FirstOrDefault(x => x.Selected)?.Filter);
|
|
|
+
|
|
|
+ public bool FiltersVisible => AvailableFilters.Any();
|
|
|
+
|
|
|
+ public void SelectFilter(String? name)
|
|
|
{
|
|
|
- var definition = AvailableFilters().FirstOrDefault(x => String.Equals(x.Name, name));
|
|
|
- SelectedFilter = definition?.AsFilter<TEntity>();
|
|
|
+ var definition = AvailableFilters.FirstOrDefault(x => String.Equals(x.Name, name));
|
|
|
+ foreach (var availableFilter in AvailableFilters)
|
|
|
+ availableFilter.Selected = definition == availableFilter;
|
|
|
+ OnPropertyChanged(nameof(AvailableFilters));
|
|
|
}
|
|
|
-
|
|
|
-
|
|
|
+
|
|
|
protected Filter<TEntity> EffectiveFilter()
|
|
|
{
|
|
|
var filters = new Filters<TEntity>();
|
|
|
@@ -189,6 +223,8 @@ namespace InABox.Avalonia
|
|
|
Images.Clear();
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+
|
|
|
public bool Loaded { get; protected set; }
|
|
|
|
|
|
private void DoRefresh(bool force)
|
|
|
@@ -197,8 +233,8 @@ namespace InABox.Avalonia
|
|
|
Items.Clear();
|
|
|
SelectedItems.Clear();
|
|
|
|
|
|
- var filename = FileName?.Invoke();
|
|
|
- if (!Loaded && CoreRepository.IsCached(filename))
|
|
|
+ var dataFileName = DataFileName();
|
|
|
+ if (!force && !Loaded && CoreRepository.IsCached(dataFileName))
|
|
|
{
|
|
|
DoBeforeLoad();
|
|
|
if (LoadFromStorage())
|
|
|
@@ -216,15 +252,16 @@ namespace InABox.Avalonia
|
|
|
SelectedItems.AddRange(Items.Where(x=>curselected.Contains(x)));
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
- SelectedItems.AddRange(Items.Where(x=>curselected.Contains(x)));
|
|
|
-
|
|
|
+
|
|
|
+ SelectedItems.AddRange(Items.Where(x => curselected.Contains(x)));
|
|
|
+
|
|
|
+
|
|
|
}
|
|
|
|
|
|
private void AfterRefresh()
|
|
|
{
|
|
|
Loaded = true;
|
|
|
- Search();
|
|
|
+ Dispatcher.UIThread.Invoke(Search);
|
|
|
NotifyChanged();
|
|
|
}
|
|
|
|
|
|
@@ -252,7 +289,7 @@ namespace InABox.Avalonia
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- public Task RefreshAsync(bool force)
|
|
|
+ public Task<ICoreRepository> RefreshAsync(bool force)
|
|
|
{
|
|
|
return Task.Run(() => Refresh(force));
|
|
|
}
|
|
|
@@ -272,6 +309,8 @@ namespace InABox.Avalonia
|
|
|
private CoreTable _table = new CoreTable();
|
|
|
|
|
|
public CoreObservableCollection<TItem> Items { get; private set; }
|
|
|
+
|
|
|
+ public int ItemCount => Items.Count;
|
|
|
|
|
|
IEnumerable ICoreRepository.Items => Items;
|
|
|
|
|
|
@@ -373,6 +412,7 @@ namespace InABox.Avalonia
|
|
|
|
|
|
Items.ReplaceRange(items);
|
|
|
OnPropertyChanged(nameof(Items));
|
|
|
+ OnPropertyChanged(nameof(ItemCount));
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
@@ -416,7 +456,6 @@ namespace InABox.Avalonia
|
|
|
|
|
|
protected virtual void AfterLoad(MultiQuery query)
|
|
|
{
|
|
|
-
|
|
|
}
|
|
|
|
|
|
protected void DoLoad()
|
|
|
@@ -424,13 +463,29 @@ namespace InABox.Avalonia
|
|
|
|
|
|
try
|
|
|
{
|
|
|
+ var selected = AvailableFilters.FirstOrDefault(x => x.Selected)?.Name;
|
|
|
+
|
|
|
+ if (!string.IsNullOrWhiteSpace(FilterTag))
|
|
|
+ {
|
|
|
+ var filters = new GlobalConfiguration<CoreFilterDefinitions>(FilterTag).Load()
|
|
|
+ .Where(x => x.Visibility == CoreFilterDefinitionVisibility.DesktopAndMobile)
|
|
|
+ .Select(x => new CoreRepositoryFilter() { Name = x.Name, Filter = x.Filter, Selected = string.Equals(x.Name,selected) })
|
|
|
+ .ToList();
|
|
|
+ if (filters.Any())
|
|
|
+ filters.Insert(0, new CoreRepositoryFilter() { Name="All", Filter = "", Selected = !filters.Any(x => string.Equals(x.Name, selected)) });
|
|
|
+ AvailableFilters.ReplaceRange(filters);
|
|
|
+ Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(FiltersVisible)));
|
|
|
+ }
|
|
|
+
|
|
|
DoBeforeLoad();
|
|
|
BeforeLoad(_query);
|
|
|
+
|
|
|
Task.Run(() =>
|
|
|
{
|
|
|
_query.Query();
|
|
|
DoAfterLoad();
|
|
|
}).Wait();
|
|
|
+
|
|
|
Search();
|
|
|
AfterLoad(_query);
|
|
|
LastUpdated = DateTime.Now;
|
|
|
@@ -515,18 +570,32 @@ namespace InABox.Avalonia
|
|
|
|
|
|
protected bool LoadFromStorage()
|
|
|
{
|
|
|
- var filename = FileName?.Invoke();
|
|
|
- if (String.IsNullOrWhiteSpace(filename))
|
|
|
+
|
|
|
+ var filterFileName = FilterFileName();
|
|
|
+ if (!string.IsNullOrWhiteSpace(filterFileName))
|
|
|
+ {
|
|
|
+ filterFileName = CacheFileName(filterFileName);
|
|
|
+ if (File.Exists(filterFileName))
|
|
|
+ {
|
|
|
+ var json = File.ReadAllText(filterFileName);
|
|
|
+ var filters = Serialization.Deserialize<ObservableCollection<CoreRepositoryFilter>>(json) ?? new ObservableCollection<CoreRepositoryFilter>();
|
|
|
+ AvailableFilters.ReplaceRange(filters);
|
|
|
+ Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(FiltersVisible)));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var dataFileName = DataFileName();
|
|
|
+ if (String.IsNullOrWhiteSpace(dataFileName))
|
|
|
{
|
|
|
InitializeTables();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- var file = CacheFileName(filename);
|
|
|
- if (File.Exists(file))
|
|
|
+ dataFileName = CacheFileName(dataFileName);
|
|
|
+ if (File.Exists(dataFileName))
|
|
|
{
|
|
|
- LastUpdated = File.GetLastWriteTime(file);
|
|
|
- using (var stream = new FileStream(file, FileMode.Open))
|
|
|
+ LastUpdated = File.GetLastWriteTime(dataFileName);
|
|
|
+ using (var stream = new FileStream(dataFileName, FileMode.Open))
|
|
|
{
|
|
|
QueryStorage storage = Serialization.ReadBinary<QueryStorage>(stream,
|
|
|
BinarySerializationSettings.Latest);
|
|
|
@@ -545,7 +614,7 @@ namespace InABox.Avalonia
|
|
|
}
|
|
|
else
|
|
|
InitializeTables();
|
|
|
-
|
|
|
+
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
@@ -562,8 +631,17 @@ namespace InABox.Avalonia
|
|
|
|
|
|
protected void SaveToStorage()
|
|
|
{
|
|
|
- var filename = FileName?.Invoke();
|
|
|
- if (String.IsNullOrWhiteSpace(filename))
|
|
|
+
|
|
|
+ var filterFileName = FilterFileName();
|
|
|
+ if (!string.IsNullOrWhiteSpace(filterFileName))
|
|
|
+ {
|
|
|
+ filterFileName = CacheFileName(filterFileName);
|
|
|
+ var json = Serialization.Serialize(AvailableFilters);
|
|
|
+ File.WriteAllText(filterFileName,json);
|
|
|
+ }
|
|
|
+
|
|
|
+ var dataFileName = DataFileName();
|
|
|
+ if (String.IsNullOrWhiteSpace(dataFileName))
|
|
|
return;
|
|
|
|
|
|
QueryStorage storage = new QueryStorage();
|
|
|
@@ -573,7 +651,7 @@ namespace InABox.Avalonia
|
|
|
var data = storage.WriteBinary(BinarySerializationSettings.Latest);
|
|
|
try
|
|
|
{
|
|
|
- var file = CacheFileName(filename);
|
|
|
+ var file = CacheFileName(dataFileName);
|
|
|
File.WriteAllBytes(file,data);
|
|
|
}
|
|
|
catch (Exception e)
|