This commit is contained in:
Dan Clark
2024-11-24 16:35:00 +00:00
parent 911ed375b4
commit d893335195
18 changed files with 282 additions and 200 deletions
+8
View File
@@ -0,0 +1,8 @@
namespace Toolkit.Foundation;
public record Activate
{
public static ActivateEventArgs<TSender> As<TSender>(TSender sender) => new(sender);
public static ActivateEventArgs<TSender> As<TSender>() where TSender : new() => new(new TSender());
}
+16
View File
@@ -0,0 +1,16 @@
namespace Toolkit.Foundation;
public record ActivateEventArgs<TSender>
{
public TSender? Sender { get; }
public ActivateEventArgs(TSender sender)
{
Sender = sender;
}
public ActivateEventArgs()
{
}
}
@@ -3,106 +3,5 @@ using CommunityToolkit.Mvvm.Messaging;
namespace Toolkit.Foundation; namespace Toolkit.Foundation;
public partial class ConfigurationValueViewModel<TConfiguration, TValue>(IServiceProvider provider,
IServiceFactory factory,
IMessenger messenger,
IDisposer disposer,
TConfiguration configuration,
IWritableConfiguration<TConfiguration> writer,
Func<TConfiguration, TValue?> read,
Action<TValue?, TConfiguration> write) :
Observable<TValue>(provider, factory, messenger, disposer),
IHandler<ChangedEventArgs<TConfiguration>>
where TConfiguration : class
{
public void Handle(ChangedEventArgs<TConfiguration> args)
{
if (args.Sender is TConfiguration configuration)
{
Value = read(configuration);
}
}
protected override void Activated() => Value = read(configuration);
protected override void Changed(TValue? value)
{
if (IsActive)
{
writer.Write(args => write(value, args));
}
}
}
public partial class ConfigurationValueViewModel<TConfiguration, TValue, TItem> :
ObservableCollection<TValue, TItem>,
IHandler<ChangedEventArgs<TConfiguration>>
where TConfiguration : class
where TItem : notnull,
IDisposable
{
private readonly TConfiguration configuration;
private readonly Func<TConfiguration, TValue?> read;
private readonly Action<TValue?, TConfiguration> write;
private readonly IWritableConfiguration<TConfiguration> writer;
public ConfigurationValueViewModel(IServiceProvider provider,
IServiceFactory factory,
IMessenger messenger,
IDisposer disposer,
TConfiguration configuration,
IWritableConfiguration<TConfiguration> writer,
Func<TConfiguration, TValue?> read,
Action<TValue?, TConfiguration> write,
TValue? value = default) : base(provider, factory, messenger, disposer, value)
{
this.configuration = configuration;
this.writer = writer;
this.read = read;
this.write = write;
Value = value;
}
public ConfigurationValueViewModel(IServiceProvider provider,
IServiceFactory factory,
IMessenger messenger,
IDisposer disposer,
IEnumerable<TItem> items,
TConfiguration configuration,
IWritableConfiguration<TConfiguration> writer,
Func<TConfiguration, TValue?> read,
Action<TValue?, TConfiguration> write,
TValue? value = default) : base(provider, factory, messenger, disposer, items, value)
{
this.configuration = configuration;
this.writer = writer;
this.read = read;
this.write = write;
Value = value;
}
public void Handle(ChangedEventArgs<TConfiguration> args)
{
if (args.Sender is TConfiguration configuration)
{
Value = read(configuration);
}
}
protected override void Activated()
{
Value = read(configuration);
}
protected override void OnChanged(TValue? value)
{
if (IsActive)
{
writer.Write(args => write(value, args));
}
base.OnChanged(value);
}
}
@@ -3,12 +3,12 @@
public class ContentTemplateDescriptor(object key, public class ContentTemplateDescriptor(object key,
Type viewModelType, Type viewModelType,
Type viewType, Type viewType,
params object[]? parameters) : params object?[]? parameters) :
IContentTemplateDescriptor IContentTemplateDescriptor
{ {
public object Key => key; public object Key => key;
public object[]? Parameters => parameters; public object?[]? Parameters => parameters;
public Type ContentType => viewModelType; public Type ContentType => viewModelType;
+8
View File
@@ -0,0 +1,8 @@
namespace Toolkit.Foundation;
public record Deactivate
{
public static DeactivateEventArgs<TSender> As<TSender>(TSender sender) => new(sender);
public static DeactivateEventArgs<TSender> As<TSender>() where TSender : new() => new(new TSender());
}
+16
View File
@@ -0,0 +1,16 @@
namespace Toolkit.Foundation;
public record DeactivateEventArgs<TSender>
{
public TSender? Sender { get; }
public DeactivateEventArgs(TSender sender)
{
Sender = sender;
}
public DeactivateEventArgs()
{
}
}
+1 -1
View File
@@ -13,4 +13,4 @@ public record DeactivatedEventArgs<TSender>
{ {
} }
} }
+4 -1
View File
@@ -17,7 +17,10 @@ public interface IValidation :
void Clear(); void Clear();
Task<bool> Validate<TProperty>(Expression<Func<TProperty>> property, Task<bool> Validate<TProperty>(Expression<Func<TProperty>> property,
ValidationRule[] rules); ValidationRule[] rules);
Task<bool> Validate(string name,
ValidationRule[] rules);
Task<bool> Validate(); Task<bool> Validate();
+1 -1
View File
@@ -127,4 +127,4 @@ public partial class Observable<TKey, TValue> :
} }
partial void OnValueChanged(TValue? value) => ValueChanged(); partial void OnValueChanged(TValue? value) => ValueChanged();
} }
+1 -1
View File
@@ -619,7 +619,7 @@ public partial class ObservableCollection<TValue, TViewModel> :
partial void OnValueChanged(TValue? value) => OnChanged(value); partial void OnValueChanged(TValue? value) => OnChanged(value);
} }
public partial class ObservableCollection<TViewModel, TKey, TValue> : public partial class ObservableCollection<TKey, TValue, TViewModel> :
ObservableCollection<TViewModel> ObservableCollection<TViewModel>
where TViewModel : IDisposable where TViewModel : IDisposable
{ {
@@ -0,0 +1,34 @@
using CommunityToolkit.Mvvm.Messaging;
namespace Toolkit.Foundation;
public partial class ObservableConfiguration<TConfiguration, TValue>(IServiceProvider provider,
IServiceFactory factory,
IMessenger messenger,
IDisposer disposer,
TConfiguration configuration,
IWritableConfiguration<TConfiguration> writer,
Func<TConfiguration, TValue?> read,
Action<TValue?, TConfiguration> write) :
Observable<TValue>(provider, factory, messenger, disposer),
IHandler<ChangedEventArgs<TConfiguration>>
where TConfiguration : class
{
public void Handle(ChangedEventArgs<TConfiguration> args)
{
if (args.Sender is TConfiguration configuration)
{
Value = read(configuration);
}
}
protected override void Activated() => Value = read(configuration);
protected override void Changed(TValue? value)
{
if (IsActive)
{
writer.Write(args => write(value, args));
}
}
}
@@ -0,0 +1,76 @@
using CommunityToolkit.Mvvm.Messaging;
namespace Toolkit.Foundation;
public partial class ObservableConfigurationCollection<TConfiguration, TValue, TViewModel> :
ObservableCollection<TValue, TViewModel>,
IHandler<ChangedEventArgs<TConfiguration>>
where TConfiguration : class
where TViewModel : notnull,
IDisposable
{
private readonly TConfiguration configuration;
private readonly Func<TConfiguration, TValue?> read;
private readonly Action<TValue?, TConfiguration> write;
private readonly IWritableConfiguration<TConfiguration> writer;
public ObservableConfigurationCollection(IServiceProvider provider,
IServiceFactory factory,
IMessenger messenger,
IDisposer disposer,
TConfiguration configuration,
IWritableConfiguration<TConfiguration> writer,
Func<TConfiguration, TValue?> read,
Action<TValue?, TConfiguration> write,
TValue? value = default) : base(provider, factory, messenger, disposer, value)
{
this.configuration = configuration;
this.writer = writer;
this.read = read;
this.write = write;
Value = value;
}
public ObservableConfigurationCollection(IServiceProvider provider,
IServiceFactory factory,
IMessenger messenger,
IDisposer disposer,
IEnumerable<TViewModel> items,
TConfiguration configuration,
IWritableConfiguration<TConfiguration> writer,
Func<TConfiguration, TValue?> read,
Action<TValue?, TConfiguration> write,
TValue? value = default) : base(provider, factory, messenger, disposer, items, value)
{
this.configuration = configuration;
this.writer = writer;
this.read = read;
this.write = write;
Value = value;
}
public void Handle(ChangedEventArgs<TConfiguration> args)
{
if (args.Sender is TConfiguration configuration)
{
Value = read(configuration);
}
}
protected override void Activated()
{
Value = read(configuration);
}
protected override void OnChanged(TValue? value)
{
if (IsActive)
{
writer.Write(args => write(value, args));
}
base.OnChanged(value);
}
}
+16 -10
View File
@@ -8,26 +8,32 @@ public record Result<TValue> :
protected internal Result(TValue? value, bool isSuccess, Error error) protected internal Result(TValue? value, bool isSuccess, Error error)
: base(isSuccess, error) => this.value = value; : base(isSuccess, error) => this.value = value;
public TValue? Value => IsSuccess ? value! : default; public TValue? Value =>
IsSuccess ? value! : default;
public static implicit operator Result<TValue>(TValue? value) => Create(value); public static implicit operator Result<TValue>(TValue? value) =>
Create(value);
} }
public record Result(bool IsSuccess,
public record Result(bool IsSuccess, Error Error) Error Error)
{ {
public bool IsFailure => !IsSuccess; public bool IsFailure => !IsSuccess;
public static Result Success() => new(true, Error.None); public static Result Success() => new(true, Error.None);
public static Result<TValue> Success<TValue>(TValue value) => new(value, true, Error.None); public static Result<TValue> Success<TValue>(TValue value) =>
new(value, true, Error.None);
public static Result Failure(Error error) => new(false, error); public static Result Failure(Error error) =>
new(false, error);
public static Result<TValue> Failure<TValue>(Error error) => new(default, false, error); public static Result<TValue> Failure<TValue>(Error error) =>
new(default, false, error);
public static Result Create(bool condition) => condition ? Success() : Failure(Error.ConditionNotMet); public static Result Create(bool condition) =>
condition ? Success() : Failure(Error.ConditionNotMet);
public static Result<TValue> Create<TValue>(TValue? value) => value is not null ? Success(value) : Failure<TValue>(Error.Null);
public static Result<TValue> Create<TValue>(TValue? value) =>
value is not null ? Success(value) : Failure<TValue>(Error.Null);
} }
+35 -72
View File
@@ -3,32 +3,24 @@ using System.Linq.Expressions;
namespace Toolkit.Foundation; namespace Toolkit.Foundation;
public class Validation(IValidatorCollection validators) : public class Validation(IValidatorCollection validators) : IValidation
IValidation
{ {
private readonly ValidationErrorCollection errors = []; private readonly ValidationErrorCollection errors = [];
public event PropertyChangedEventHandler? PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;
public IReadOnlyIndexDictionary<string, string> Errors => public IReadOnlyIndexDictionary<string, string> Errors =>
new ReadOnlyIndexDictionary<string, string>(errors); new ReadOnlyIndexDictionary<string, string>(errors);
public bool HasErrors => public bool HasErrors => Errors.Count > 0;
Errors.Count > 0;
internal IValidatorCollection Validators { get; } = validators; internal IValidatorCollection Validators { get; } = validators;
public void Add<TProperty>(Expression<Func<TProperty>> property, public void Add<TProperty>(Expression<Func<TProperty>> property, ValidationRule[] rules, ValidationTrigger trigger = ValidationTrigger.Deferred)
ValidationRule[] rules,
ValidationTrigger trigger = ValidationTrigger.Deferred)
{ {
string? name = GetPropertyName(property); string name = GetPropertyName(property);
Validators.Add(name, new Validator(name, rules)); Validators.Add(name, new Validator(name, rules));
if (trigger == ValidationTrigger.Immediate) _ = Validate(name);
if (trigger is ValidationTrigger.Immediate)
{
_ = Validate(name);
}
} }
public void Clear() public void Clear()
@@ -37,93 +29,64 @@ public class Validation(IValidatorCollection validators) :
OnPropertyChanged(nameof(Errors), null, null); OnPropertyChanged(nameof(Errors), null, null);
} }
public virtual void OnPropertyChanged(string propertyName, public Task<bool> Validate<TProperty>(Expression<Func<TProperty>> property, ValidationRule[] rules) =>
object? before, object? after) ValidateInternal(new Validator(GetPropertyName(property), rules), GetPropertyName(property));
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public async Task<bool> Validate<TProperty>(Expression<Func<TProperty>> property, public Task<bool> Validate(string name, ValidationRule[] rules) =>
ValidationRule[] rules) ValidateInternal(new Validator(name, rules), name);
{
string? name = GetPropertyName(property);
Validator validator = new(name, rules);
Clear(name); public Task<bool> Validate(string name) =>
Validators.TryGet(name, out var validator) && validator != null
(bool isValid, string? message) = await validator.TryValidate(); ? ValidateInternal(validator, name)
: Task.FromResult(true);
if (!isValid)
{
errors[name] = message ?? "";
}
OnPropertyChanged(nameof(Errors), null, null);
OnPropertyChanged(nameof(HasErrors), null, null);
return !HasErrors;
}
public async Task<bool> Validate(string name)
{
Clear(name);
if (Validators.TryGet(name, out Validator? validator))
{
if (validator is not null)
{
(bool isValid, string? message) = await validator.TryValidate();
if (!isValid)
{
errors[name] = message ?? "";
}
}
}
OnPropertyChanged(nameof(Errors), null, null);
OnPropertyChanged(nameof(HasErrors), null, null);
return !HasErrors;
}
public async Task<bool> Validate() public async Task<bool> Validate()
{ {
Clear(); Clear();
foreach (Validator? validator in Validators) foreach (var validator in Validators)
{ {
if (validator.PropertyName is string name) if (validator.PropertyName is string name)
{ {
(bool isValid, string? message) = await validator.TryValidate(); (bool isValid, string? message) = await validator.TryValidate();
if (!isValid) if (!isValid) errors[name] = message ?? string.Empty;
{
errors[name] = message ?? "";
}
} }
} }
OnPropertyChanged(nameof(Errors), null, null); OnPropertyChanged(nameof(Errors), null, null);
OnPropertyChanged(nameof(HasErrors), null, null); OnPropertyChanged(nameof(HasErrors), null, null);
return !HasErrors; return !HasErrors;
} }
private async Task<bool> ValidateInternal(Validator validator, string name)
{
Clear(name);
(bool isValid, string? message) = await validator.TryValidate();
if (!isValid) errors[name] = message ?? string.Empty;
OnPropertyChanged(nameof(Errors), null, null);
OnPropertyChanged(nameof(HasErrors), null, null);
return isValid;
}
private void Clear(string name) private void Clear(string name)
{ {
if (Errors.ContainsKey(name)) if (errors is not null && errors.ContainsKey(name))
{ {
errors.Remove(name); errors.Remove(name);
OnPropertyChanged(nameof(Errors), null, null); OnPropertyChanged(nameof(Errors), null, null);
} }
} }
private string GetPropertyName<T>(Expression<Func<T>> expression) private static string GetPropertyName<T>(Expression<Func<T>> expression) =>
{ expression.Body switch
return expression.Body switch
{ {
MemberExpression memberExpression => memberExpression.Member.Name, MemberExpression memberExpression => memberExpression.Member.Name,
UnaryExpression unaryExpression when unaryExpression.Operand is MemberExpression operand => operand.Member.Name, UnaryExpression unaryExpression when unaryExpression.Operand is MemberExpression operand => operand.Member.Name,
_ => string.Empty _ => string.Empty
}; };
}
} public virtual void OnPropertyChanged(string propertyName, object? before, object? after) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
@@ -0,0 +1,10 @@
using System;
namespace Toolkit.UI.WinUI;
public class IsStringNotNullOrEmptyConverter :
ValueConverter<string, bool>
{
protected override bool ConvertTo(string value, Type? targetType, object? parameter, string? language) =>
!string.IsNullOrEmpty(value);
}
+4 -11
View File
@@ -4,24 +4,17 @@ using System.Globalization;
namespace Toolkit.UI.WinUI; namespace Toolkit.UI.WinUI;
public class StringFormatConverter : public class StringFormatConverter :
ValueConverter<object, object> ValueConverter<object?, object?>
{ {
public string? StringFormat { get; set; } public string? StringFormat { get; set; }
protected override object? ConvertTo(object value, protected override object? ConvertTo(object? value,
Type? targetType, Type? targetType,
object? parameter, object? parameter,
string? language) string? language)
{ {
if (value is null) if (value is null) return null!;
{ if (StringFormat is not { Length: > 0}) return $"{value}"!;
return null!;
}
if (string.IsNullOrEmpty(StringFormat))
{
return value.ToString()!;
}
try try
{ {
@@ -0,0 +1,27 @@
using System;
using Toolkit.Foundation;
namespace Toolkit.UI.WinUI;
public class ValidationBooleanConverter :
ValueConverter<IReadOnlyIndexDictionary<string, string>, bool>
{
public string? Property { get; set; }
public bool TrueValue { get; set; } = true;
public bool FalseValue { get; set; } = false;
protected override bool ConvertTo(IReadOnlyIndexDictionary<string, string> value,
Type? targetType,
object? parameter,
string? language)
{
if (Property is { Length: > 0 } && value.ContainsKey(Property))
{
return TrueValue;
}
return FalseValue;
}
}
@@ -0,0 +1,23 @@
using System;
using Toolkit.Foundation;
namespace Toolkit.UI.WinUI;
public class ValidationMessageConverter :
ValueConverter<IReadOnlyIndexDictionary<string, string>, string?>
{
public string? Property { get; set; }
protected override string? ConvertTo(IReadOnlyIndexDictionary<string, string> value,
Type? targetType,
object? parameter,
string? language)
{
if (Property is { Length: > 0 } && value.TryGetValue(Property, out string? message))
{
return message;
}
return default;
}
}