Compare commits

...

5 Commits

Author SHA1 Message Date
Matija Koželj
be914e4b2e [WIP] Refactor converters and dependant components. 2022-12-07 17:12:28 +01:00
Matija Koželj
6f91dacb0c [WIP] Refactor components to match converter refactor 2022-12-06 19:18:32 +01:00
Matija Koželj
f3ae953772 Remove reference to unused project 2022-12-06 19:18:01 +01:00
Matija Koželj
2daf5b6c1e [WIP] Refactor default converters 2022-12-06 19:17:23 +01:00
Matija Koželj
82594cff34 Remove connected specific files 2022-12-06 19:16:52 +01:00
45 changed files with 4948 additions and 4610 deletions

226
.editorconfig Normal file
View File

@ -0,0 +1,226 @@
# Remove the line below if you want to inherit .editorconfig settings from higher directories
root = true
# C# files
[*.cs]
#### Core EditorConfig Options ####
# Indentation and spacing
indent_size = 3
indent_style = tab
tab_width = 3
# New line preferences
end_of_line = crlf
insert_final_newline = false
#### .NET Coding Conventions ####
# Organize usings
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = false
file_header_template = unset
# this. and Me. preferences
dotnet_style_qualification_for_event = false:suggestion
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members
# Expression-level preferences
dotnet_style_coalesce_expression = true
dotnet_style_collection_initializer = true
dotnet_style_explicit_tuple_names = true
dotnet_style_namespace_match_folder = true
dotnet_style_null_propagation = true
dotnet_style_object_initializer = true
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true:suggestion
dotnet_style_prefer_compound_assignment = true
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
dotnet_style_prefer_inferred_anonymous_type_member_names = true
dotnet_style_prefer_inferred_tuple_names = false
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
dotnet_style_prefer_simplified_boolean_expressions = true
dotnet_style_prefer_simplified_interpolation = true
# Field preferences
dotnet_style_readonly_field = true
# Parameter preferences
dotnet_code_quality_unused_parameters = all
# Suppression preferences
dotnet_remove_unnecessary_suppression_exclusions = 0
# New line preferences
dotnet_style_allow_multiple_blank_lines_experimental = false:suggestion
dotnet_style_allow_statement_immediately_after_block_experimental = false:suggestion
#### C# Coding Conventions ####
# var preferences
csharp_style_var_elsewhere = true:warning
csharp_style_var_for_built_in_types = true:warning
csharp_style_var_when_type_is_apparent = true:warning
# Expression-bodied members
csharp_style_expression_bodied_accessors = when_on_single_line:suggestion
csharp_style_expression_bodied_constructors = when_on_single_line:suggestion
csharp_style_expression_bodied_indexers = when_on_single_line:suggestion
csharp_style_expression_bodied_lambdas = when_on_single_line:suggestion
csharp_style_expression_bodied_local_functions = when_on_single_line:suggestion
csharp_style_expression_bodied_methods = when_on_single_line:suggestion
csharp_style_expression_bodied_operators = when_on_single_line:suggestion
csharp_style_expression_bodied_properties = when_on_single_line:suggestion
# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true
csharp_style_pattern_matching_over_is_with_cast_check = true
csharp_style_prefer_extended_property_pattern = true
csharp_style_prefer_not_pattern = true
csharp_style_prefer_pattern_matching = true:suggestion
csharp_style_prefer_switch_expression = true
# Null-checking preferences
csharp_style_conditional_delegate_call = true
# Modifier preferences
csharp_prefer_static_local_function = true
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
csharp_style_prefer_readonly_struct = true
# Code-block preferences
csharp_prefer_braces = when_multiline:suggestion
csharp_prefer_simple_using_statement = true
csharp_style_namespace_declarations = file_scoped:warning
csharp_style_prefer_method_group_conversion = true:suggestion
csharp_style_prefer_top_level_statements = false:suggestion
# Expression-level preferences
csharp_prefer_simple_default_expression = true
csharp_style_deconstructed_variable_declaration = true
csharp_style_implicit_object_creation_when_type_is_apparent = true
csharp_style_inlined_variable_declaration = true
csharp_style_prefer_index_operator = true
csharp_style_prefer_local_over_anonymous_function = true
csharp_style_prefer_null_check_over_type_check = true
csharp_style_prefer_range_operator = true
csharp_style_prefer_tuple_swap = true
csharp_style_prefer_utf8_string_literals = true
csharp_style_throw_expression = true
csharp_style_unused_value_assignment_preference = discard_variable
csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:suggestion
# New line preferences
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:suggestion
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:suggestion
csharp_style_allow_embedded_statements_on_same_line_experimental = false:suggestion
#### C# Formatting Rules ####
# New line preferences
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = no_change
csharp_indent_switch_labels = true
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Wrapping preferences
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case

View File

@ -5,14 +5,14 @@
<CascadingValue Name="SubscribeToParentForm" Value="false" IsFixed="true"> <CascadingValue Name="SubscribeToParentForm" Value="false" IsFixed="true">
<div class="@AutocompleteClassList"> <div class="@AutocompleteClassList">
<InputControl Label="@Label" Variant="@Variant" HelperText="@HelperText" HelperTextOnFocus="@HelperTextOnFocus" FullWidth="@FullWidth" Margin="@Margin" Class="@ClassList()" Style="@Style" <InputControl Label="@Label" Variant="@Variant" HelperText="@HelperText" HelperTextOnFocus="@HelperTextOnFocus" FullWidth="@FullWidth" Margin="@Margin" Class="@ClassList()" Style="@Style"
Error="@Error" ErrorText="@ErrorText" Disabled="@Disabled" @onclick="@ToggleMenu" Required="@Required" ForId="@FieldId"> Error="@HasError" ErrorText="@ErrorText" Disabled="@Disabled" @onclick="@ToggleMenu" Required="@Required" ForId="@FieldId">
<InputContent> <InputContent>
<Input @ref="_elementReference" @key="_elementKey" InputType="InputType.Text" <Input @ref="_elementReference" @key="_elementKey" InputType="InputType.Text"
Class="select-input" Margin="@Margin" Class="select-input" Margin="@Margin"
Variant="@Variant" Variant="@Variant"
TextUpdateSuppression="@TextUpdateSuppression" TextUpdateSuppression="@TextUpdateSuppression"
Value="@Text" DisableUnderLine="@DisableUnderLine" Value="@Text" DisableUnderLine="@DisableUnderLine"
Disabled="@Disabled" ReadOnly="@ReadOnly" Error="@Error" Disabled="@Disabled" ReadOnly="@ReadOnly" Error="@HasError"
OnAdornmentClick="@OnAdornmentClick" AdornmentIcon="@CurrentIcon" Adornment="@Adornment" AdornmentColor="@AdornmentColor" IconSize="@IconSize" AdornmentText="@AdornmentText" OnAdornmentClick="@OnAdornmentClick" AdornmentIcon="@CurrentIcon" Adornment="@Adornment" AdornmentColor="@AdornmentColor" IconSize="@IconSize" AdornmentText="@AdornmentText"
Clearable="@Clearable" OnClearButtonClick="@OnClearButtonClick" Clearable="@Clearable" OnClearButtonClick="@OnClearButtonClick"
@attributes="UserAttributes" @attributes="UserAttributes"

File diff suppressed because it is too large Load Diff

View File

@ -44,17 +44,17 @@ public class BooleanInput<T> : FormComponent<T, bool?>
/// </summary> /// </summary>
[Parameter] public EventCallback<T> CheckedChanged { get; set; } [Parameter] public EventCallback<T> CheckedChanged { get; set; }
protected bool? BoolValue => Converter.Set(Checked); protected bool? BoolValue => Converter.Convert(Checked);
protected virtual Task OnChange(ChangeEventArgs args) protected virtual Task OnChange(ChangeEventArgs args)
{ {
Touched = true; Modified = true;
return SetBoolValueAsync((bool?)args.Value); return SetBoolValueAsync((bool?)args.Value);
} }
protected Task SetBoolValueAsync(bool? value) protected Task SetBoolValueAsync(bool? value)
{ {
return SetCheckedAsync(Converter.Get(value)); return SetCheckedAsync(Converter.ConvertBack(value));
} }
protected async Task SetCheckedAsync(T value) protected async Task SetCheckedAsync(T value)
@ -74,7 +74,7 @@ public class BooleanInput<T> : FormComponent<T, bool?>
{ {
var changed = base.SetConverter(value); var changed = base.SetConverter(value);
if (changed) if (changed)
SetBoolValueAsync(Converter.Set(Checked)).AndForget(); SetBoolValueAsync(Converter.Convert(Checked)).AndForget();
return changed; return changed;
} }

View File

@ -139,7 +139,7 @@ public partial class CheckBox<T> : BooleanInput<T>
protected override Task OnChange(ChangeEventArgs args) protected override Task OnChange(ChangeEventArgs args)
{ {
Touched = true; Modified = true;
// Apply only when TriState parameter is set to true and T is bool? // Apply only when TriState parameter is set to true and T is bool?
if (TriState && typeof(T) == typeof(bool?)) if (TriState && typeof(T) == typeof(bool?))

File diff suppressed because it is too large Load Diff

View File

@ -30,7 +30,7 @@ public class DatePicker : DatePickerBase
{ {
if (_value != date) if (_value != date)
{ {
Touched = true; Modified = true;
if (date is not null && IsDateDisabledFunc(date.Value.Date)) if (date is not null && IsDateDisabledFunc(date.Value.Date))
{ {
@ -40,10 +40,8 @@ public class DatePicker : DatePickerBase
_value = date; _value = date;
if (updateValue) if (updateValue)
{ await SetTextAsync(Converter.Convert(_value), false);
Converter.GetError = false;
await SetTextAsync(Converter.Set(_value), false);
}
await DateChanged.InvokeAsync(_value); await DateChanged.InvokeAsync(_value);
BeginValidate(); BeginValidate();
FieldChanged(_value); FieldChanged(_value);
@ -52,15 +50,15 @@ public class DatePicker : DatePickerBase
protected override Task DateFormatChanged(string newFormat) protected override Task DateFormatChanged(string newFormat)
{ {
Touched = true; Modified = true;
return SetTextAsync(Converter.Set(_value), false); return SetTextAsync(Converter.Convert(_value), false);
} }
protected override Task StringValueChanged(string value) protected override Task StringValueChanged(string value)
{ {
Touched = true; Modified = true;
// Update the date property (without updating back the Value property) // Update the date property (without updating back the Value property)
return SetDateAsync(Converter.Get(value), false); return SetDateAsync(Converter.ConvertBack(value), false);
} }
protected override string GetDayClasses(int month, DateTime day) protected override string GetDayClasses(int month, DateTime day)

View File

@ -230,7 +230,7 @@ public abstract partial class DatePickerBase : Picker<DateTime?>
base.OnPickerOpened(); base.OnPickerOpened();
if (Editable == true && Text != null) if (Editable == true && Text != null)
{ {
DateTime? a = Converter.Get(Text); DateTime? a = Converter.ConvertBack(Text);
if (a.HasValue) if (a.HasValue)
{ {
a = new DateTime(a.Value.Year, a.Value.Month, 1); a = new DateTime(a.Value.Year, a.Value.Month, 1);

View File

@ -1,4 +1,7 @@
namespace Connected.Components; using Connected.Extensions;
using Connected.Utilities.BindingConverters;
namespace Connected.Components;
public class DateRange : Range<DateTime?>, IEquatable<DateRange> public class DateRange : Range<DateTime?>, IEquatable<DateRange>
{ {
@ -15,7 +18,7 @@ public class DateRange : Range<DateTime?>, IEquatable<DateRange>
if (Start == null || End == null) if (Start == null || End == null)
return string.Empty; return string.Empty;
return RangeConverter<DateTime>.Join(converter.Set(Start.Value), converter.Set(End.Value)); return RangeConverter<DateTime>.Join(converter.Convert(Start.Value), converter.Convert(End.Value));
} }
public string ToIsoDateString() public string ToIsoDateString()
@ -40,12 +43,14 @@ public class DateRange : Range<DateTime?>, IEquatable<DateRange>
{ {
date = null; date = null;
var endDate = converter.Get(end); var endDate = converter.ConvertBack(end);
if (converter.GetError)
if (endDate is null)
return false; return false;
var startDate = converter.Get(start); var startDate = converter.ConvertBack(start);
if (converter.GetError)
if (startDate is null)
return false; return false;
date = new DateRange(startDate, endDate); date = new DateRange(startDate, endDate);

View File

@ -6,13 +6,13 @@
@code{ @code{
protected override RenderFragment InputContent=> protected override RenderFragment InputContent=>
@<InputControl Label="@Label" Variant="@Variant" HelperText="@HelperText" Error="@Error" @<InputControl Label="@Label" Variant="@Variant" HelperText="@HelperText" Error="@HasError"
ErrorText="@ErrorText" Disabled="@Disabled" Margin="@Margin" Required="@Required" ErrorText="@ErrorText" Disabled="@Disabled" Margin="@Margin" Required="@Required"
@onclick="() => { if (!Editable) ToggleState(); }" ForId="@FieldId"> @onclick="() => { if (!Editable) ToggleState(); }" ForId="@FieldId">
<InputContent> <InputContent>
<RangeInput @ref="_rangeInput" @attributes="UserAttributes" InputType="@InputType.Text" Class="@PickerInputClass" Style="@Style" Variant="@Variant" ReadOnly="@(!Editable)" <RangeInput @ref="_rangeInput" @attributes="UserAttributes" InputType="@InputType.Text" Class="@PickerInputClass" Style="@Style" Variant="@Variant" ReadOnly="@(!Editable)"
@bind-Value="@RangeText" Disabled="@Disabled" Adornment="@Adornment" AdornmentIcon="@AdornmentIcon" AdornmentColor="@AdornmentColor" IconSize="@IconSize" OnAdornmentClick="ToggleState" @bind-Value="@RangeText" Disabled="@Disabled" Adornment="@Adornment" AdornmentIcon="@AdornmentIcon" AdornmentColor="@AdornmentColor" IconSize="@IconSize" OnAdornmentClick="ToggleState"
Required="@Required" RequiredError="@RequiredError" Error="@Error" ErrorText="@ErrorText" Margin="@Margin" AdornmentAriaLabel="@AdornmentAriaLabel"/> Required="@Required" RequiredError="@RequiredError" Error="@HasError" ErrorText="@ErrorText" Margin="@Margin" AdornmentAriaLabel="@AdornmentAriaLabel"/>
</InputContent> </InputContent>
</InputControl>; </InputControl>;

View File

@ -57,7 +57,6 @@ public partial class DateRangePicker : DatePickerBase
if (updateValue) if (updateValue)
{ {
Converter.GetError = false;
if (_dateRange == null) if (_dateRange == null)
{ {
_rangeText = null; _rangeText = null;
@ -66,8 +65,8 @@ public partial class DateRangePicker : DatePickerBase
else else
{ {
_rangeText = new Range<string>( _rangeText = new Range<string>(
Converter.Set(_dateRange.Start), Converter.Convert(_dateRange.Start),
Converter.Set(_dateRange.End)); Converter.Convert(_dateRange.End));
await SetTextAsync(_dateRange.ToString(Converter), false); await SetTextAsync(_dateRange.ToString(Converter), false);
} }
} }
@ -85,7 +84,7 @@ public partial class DateRangePicker : DatePickerBase
if (_rangeText?.Equals(value) ?? value == null) if (_rangeText?.Equals(value) ?? value == null)
return; return;
Touched = true; Modified = true;
_rangeText = value; _rangeText = value;
SetDateRangeAsync(ParseDateRangeValue(value?.Start, value?.End), false).AndForget(); SetDateRangeAsync(ParseDateRangeValue(value?.Start, value?.End), false).AndForget();
} }
@ -135,13 +134,13 @@ public partial class DateRangePicker : DatePickerBase
protected override Task DateFormatChanged(string newFormat) protected override Task DateFormatChanged(string newFormat)
{ {
Touched = true; Modified = true;
return SetTextAsync(_dateRange?.ToString(Converter), false); return SetTextAsync(_dateRange?.ToString(Converter), false);
} }
protected override Task StringValueChanged(string value) protected override Task StringValueChanged(string value)
{ {
Touched = true; Modified = true;
// Update the daterange property (without updating back the Value property) // Update the daterange property (without updating back the Value property)
return SetDateRangeAsync(ParseDateRangeValue(value), false); return SetDateRangeAsync(ParseDateRangeValue(value), false);
} }

View File

@ -109,14 +109,14 @@ public partial class FileUpload<T> : FormComponent<T, string>
await FilesChanged.InvokeAsync(_value); await FilesChanged.InvokeAsync(_value);
BeginValidate(); BeginValidate();
FieldChanged(_value); FieldChanged(_value);
if (!Error || !SuppressOnChangeWhenInvalid) //only trigger FilesChanged if validation passes or SuppressOnChangeWhenInvalid is false if (!HasError || !SuppressOnChangeWhenInvalid) //only trigger FilesChanged if validation passes or SuppressOnChangeWhenInvalid is false
await OnFilesChanged.InvokeAsync(args); await OnFilesChanged.InvokeAsync(args);
} }
protected override void OnInitialized() protected override void OnInitialized()
{ {
if (!(typeof(T) == typeof(IReadOnlyList<IBrowserFile>) || typeof(T) == typeof(IBrowserFile))) //if (!(typeof(T) == typeof(IReadOnlyList<IBrowserFile>) || typeof(T) == typeof(IBrowserFile)))
Logger.LogWarning("T must be of type {type1} or {type2}", typeof(IReadOnlyList<IBrowserFile>), typeof(IBrowserFile)); // Logger.LogWarning("T must be of type {type1} or {type2}", typeof(IReadOnlyList<IBrowserFile>), typeof(IBrowserFile));
base.OnInitialized(); base.OnInitialized();
} }

View File

@ -206,11 +206,11 @@ public partial class Form : UIComponent, IDisposable, IForm
// - none have an error // - none have an error
// - all required fields have been touched (and thus validated) // - all required fields have been touched (and thus validated)
var no_errors = _formControls.All(x => x.HasErrors == false); var no_errors = _formControls.All(x => x.HasErrors == false);
var required_all_touched = _formControls.Where(x => x.Required).All(x => x.Touched); var required_all_touched = _formControls.Where(x => x.Required).All(x => x.Modified);
var valid = no_errors && required_all_touched; var valid = no_errors && required_all_touched;
var old_touched = _touched; var old_touched = _touched;
_touched = _formControls.Any(x => x.Touched); _touched = _formControls.Any(x => x.Modified);
try try
{ {
_shouldRender = false; _shouldRender = false;

File diff suppressed because it is too large Load Diff

View File

@ -3,9 +3,9 @@
public interface IFormComponent public interface IFormComponent
{ {
public bool Required { get; set; } public bool Required { get; set; }
public bool Error { get; set; } public bool HasError { get; set; }
public bool HasErrors { get; } public bool HasErrors { get; }
public bool Touched { get; } public bool Modified { get; }
public object Validation { get; set; } public object Validation { get; set; }
public bool IsForNull { get; } public bool IsForNull { get; }
public List<string> ValidationErrors { get; set; } public List<string> ValidationErrors { get; set; }

View File

@ -1,5 +1,6 @@
using System.Timers; using System.Timers;
using Connected.Annotations; using Connected.Annotations;
using Connected.Utilities.BindingConverters;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace Connected.Components; namespace Connected.Components;

View File

@ -41,7 +41,7 @@
@onkeyup:preventDefault="@KeyUpPreventDefault" @onkeyup:preventDefault="@KeyUpPreventDefault"
@onmousewheel="@OnMouseWheel" @onmousewheel="@OnMouseWheel"
@onwheel="@OnMouseWheel" @onwheel="@OnMouseWheel"
aria-invalid="@Error.ToString().ToLower()" aria-invalid="@HasError.ToString().ToLower()"
aria-describedby="@ErrorId" aria-describedby="@ErrorId"
> >
@Text @Text
@ -73,7 +73,7 @@
@onkeyup:preventDefault="@KeyUpPreventDefault" @onkeyup:preventDefault="@KeyUpPreventDefault"
@onmousewheel="@OnMouseWheel" @onmousewheel="@OnMouseWheel"
@onwheel="@OnMouseWheel" @onwheel="@OnMouseWheel"
aria-invalid="@Error.ToString().ToLower()" aria-invalid="@HasError.ToString().ToLower()"
aria-describedby="@ErrorId" aria-describedby="@ErrorId"
/> />
@*Note: double mouse wheel handlers needed for Firefox because it doesn't know onmousewheel*@ @*Note: double mouse wheel handlers needed for Firefox because it doesn't know onmousewheel*@

View File

@ -215,7 +215,7 @@ public abstract class InputBase<T> : FormComponent<T, string>
{ {
Text = text; Text = text;
if (!string.IsNullOrWhiteSpace(Text)) if (!string.IsNullOrWhiteSpace(Text))
Touched = true; Modified = true;
if (updateValue) if (updateValue)
await UpdateValuePropertyAsync(false); await UpdateValuePropertyAsync(false);
await TextChanged.InvokeAsync(Text); await TextChanged.InvokeAsync(Text);
@ -227,7 +227,7 @@ public abstract class InputBase<T> : FormComponent<T, string>
/// </summary> /// </summary>
protected virtual Task UpdateTextPropertyAsync(bool updateValue) protected virtual Task UpdateTextPropertyAsync(bool updateValue)
{ {
return SetTextAsync(Converter.Set(Value), updateValue); return SetTextAsync(Converter.Convert(Value), updateValue);
} }
/// <summary> /// <summary>
@ -266,7 +266,7 @@ public abstract class InputBase<T> : FormComponent<T, string>
if (!OnlyValidateIfDirty || _isDirty) if (!OnlyValidateIfDirty || _isDirty)
{ {
Touched = true; Modified = true;
BeginValidateAfter(OnBlur.InvokeAsync(obj)); BeginValidateAfter(OnBlur.InvokeAsync(obj));
} }
} }
@ -361,7 +361,7 @@ public abstract class InputBase<T> : FormComponent<T, string>
/// </summary> /// </summary>
protected virtual Task UpdateValuePropertyAsync(bool updateText) protected virtual Task UpdateValuePropertyAsync(bool updateText)
{ {
return SetValueAsync(Converter.Get(Text), updateText); return SetValueAsync(Converter.ConvertBack(Text), updateText);
} }
protected override bool SetConverter(Converter<T, string> value) protected override bool SetConverter(Converter<T, string> value)
@ -389,7 +389,7 @@ public abstract class InputBase<T> : FormComponent<T, string>
[Category(CategoryTypes.FormComponent.Behavior)] [Category(CategoryTypes.FormComponent.Behavior)]
public string Format public string Format
{ {
get => ((Converter<T>)Converter).Format; get => ((ToStringConverter<T>)Converter).Format;
set => SetFormat(value); set => SetFormat(value);
} }
@ -398,7 +398,7 @@ public abstract class InputBase<T> : FormComponent<T, string>
var changed = Format != value; var changed = Format != value;
if (changed) if (changed)
{ {
((Converter<T>)Converter).Format = value; ((ToStringConverter<T>)Converter).Format = value;
UpdateTextPropertyAsync(false).AndForget(); // refresh only Text property from current Value UpdateTextPropertyAsync(false).AndForget(); // refresh only Text property from current Value
} }
return changed; return changed;

View File

@ -1,4 +1,5 @@
using Connected.Extensions; using Connected.Extensions;
using Connected.Utilities.BindingConverters;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace Connected.Components; namespace Connected.Components;
@ -10,7 +11,7 @@ public partial class RangeInput<T> : InputBase<Range<T>>
public RangeInput() public RangeInput()
{ {
Value = new Range<T>(); Value = new Range<T>();
Converter = new RangeConverter<T>(); SetConverter(new RangeConverter<T>());
} }
protected string Classname => InputCssHelper.GetClassname(this, protected string Classname => InputCssHelper.GetClassname(this,

View File

@ -238,7 +238,7 @@ public partial class Mask : InputBase<string>, IDisposable
await base.SetTextAsync(text, updateValue: false); await base.SetTextAsync(text, updateValue: false);
if (Clearable) if (Clearable)
UpdateClearable(Text); UpdateClearable(Text);
var v = Converter.Get(cleanText); var v = Converter.ConvertBack(cleanText);
Value = v; Value = v;
await ValueChanged.InvokeAsync(v); await ValueChanged.InvokeAsync(v);
SetCaretPosition(caret, selection); SetCaretPosition(caret, selection);
@ -262,7 +262,7 @@ public partial class Mask : InputBase<string>, IDisposable
// allow this only via changes from the outside // allow this only via changes from the outside
if (_updating) if (_updating)
return; return;
var text = Converter.Set(Value); var text = Converter.Convert(Value);
var cleanText = MaskKind.GetCleanText(); var cleanText = MaskKind.GetCleanText();
if (cleanText == text || string.IsNullOrEmpty(cleanText) && string.IsNullOrEmpty(text)) if (cleanText == text || string.IsNullOrEmpty(cleanText) && string.IsNullOrEmpty(text))
return; return;

View File

@ -39,7 +39,7 @@
AdornmentIcon="@AdornmentIcon" AdornmentIcon="@AdornmentIcon"
AdornmentColor="@AdornmentColor" AdornmentColor="@AdornmentColor"
IconSize="@IconSize" IconSize="@IconSize"
Error="@Error" Error="@HasError"
Immediate="@(Immediate)" Immediate="@(Immediate)"
Margin="@Margin" Margin="@Margin"
MaxLength="@MaxLength" MaxLength="@MaxLength"

View File

@ -8,6 +8,7 @@ using System.Globalization;
using Connected.Annotations; using Connected.Annotations;
using Connected.Services; using Connected.Services;
using Connected.Utilities; using Connected.Utilities;
using Connected.Utilities.BindingConverters;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web;
@ -208,7 +209,7 @@ public partial class NumericField<T> : DebouncedInput<T>
return (T)(object)Convert.ToInt64(FromInt64(Value) + FromInt64(Step) * factor); return (T)(object)Convert.ToInt64(FromInt64(Value) + FromInt64(Step) * factor);
if (typeof(T) == typeof(ulong) || typeof(T) == typeof(ulong?)) if (typeof(T) == typeof(ulong) || typeof(T) == typeof(ulong?))
return (T)(object)Convert.ToUInt64(FromUInt64(Value) + FromUInt64(Step) * factor); return (T)(object)Convert.ToUInt64(FromUInt64(Value) + FromUInt64(Step) * factor);
return Num.To<T>(Num.From(Value) + Num.From(Step) * factor); return Number.To<T>(Number.From(Value) + Number.From(Step) * factor);
} }
/// <summary> /// <summary>

View File

@ -31,7 +31,7 @@
Margin="@Margin" Margin="@Margin"
Required="@Required" Required="@Required"
RequiredError="@RequiredError" RequiredError="@RequiredError"
Error="@Error" Error="@HasError"
ErrorText="@ErrorText" ErrorText="@ErrorText"
Clearable="@(ReadOnly ? false : Clearable)" Clearable="@(ReadOnly ? false : Clearable)"
OnClearButtonClick="@(() => Clear())" OnClearButtonClick="@(() => Clear())"

View File

@ -2,6 +2,7 @@
using Connected.Annotations; using Connected.Annotations;
using Connected.Extensions; using Connected.Extensions;
using Connected.Utilities; using Connected.Utilities;
using Connected.Utilities.BindingConverters;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace Connected.Components; namespace Connected.Components;

View File

@ -81,7 +81,7 @@ public partial class RadioGroup<T> : FormComponent<T, T>, IRadioGroup
internal Task SetSelectedRadioAsync(Radio<T> radio) internal Task SetSelectedRadioAsync(Radio<T> radio)
{ {
Touched = true; Modified = true;
return SetSelectedRadioAsync(radio, true); return SetSelectedRadioAsync(radio, true);
} }

View File

@ -5,14 +5,14 @@
<CascadingValue Name="SubscribeToParentForm" Value="false" IsFixed="true"> <CascadingValue Name="SubscribeToParentForm" Value="false" IsFixed="true">
<div class="mud-select" id="@_elementId"> <div class="mud-select" id="@_elementId">
<InputControl Label="@Label" Variant="@Variant" HelperText="@HelperText" HelperTextOnFocus="@HelperTextOnFocus" FullWidth="@FullWidth" Margin="@Margin" Class="@Classname" Style="@Style" <InputControl Label="@Label" Variant="@Variant" HelperText="@HelperText" HelperTextOnFocus="@HelperTextOnFocus" FullWidth="@FullWidth" Margin="@Margin" Class="@Classname" Style="@Style"
Error="@Error" ErrorText="@ErrorText" ErrorId="@ErrorId" Disabled="@Disabled" @onclick="@ToggleMenu" Required="@Required" ForId="@FieldId"> Error="@HasError" ErrorText="@ErrorText" ErrorId="@ErrorId" Disabled="@Disabled" @onclick="@ToggleMenu" Required="@Required" ForId="@FieldId">
<InputContent> <InputContent>
<Input @ref="_elementReference" InputType="@(CanRenderValue || (Strict && !IsValueInList) ? InputType.Hidden : InputType.Text)" <Input @ref="_elementReference" InputType="@(CanRenderValue || (Strict && !IsValueInList) ? InputType.Hidden : InputType.Text)"
Class="mud-select-input" Margin="@Margin" Placeholder="@Placeholder" Class="mud-select-input" Margin="@Margin" Placeholder="@Placeholder"
Variant="@Variant" Variant="@Variant"
TextUpdateSuppression="false" TextUpdateSuppression="false"
Value="@(Strict && !IsValueInList ? null : Text)" DisableUnderLine="@DisableUnderLine" Value="@(Strict && !IsValueInList ? null : Text)" DisableUnderLine="@DisableUnderLine"
Disabled="@Disabled" ReadOnly="true" Error="@Error" ErrorId="@ErrorId" Disabled="@Disabled" ReadOnly="true" Error="@HasError" ErrorId="@ErrorId"
OnAdornmentClick="@OnAdornmentClick" AdornmentIcon="@_currentIcon" Adornment="@Adornment" OnAdornmentClick="@OnAdornmentClick" AdornmentIcon="@_currentIcon" Adornment="@Adornment"
AdornmentColor="@AdornmentColor" IconSize="@IconSize" AdornmentText="@AdornmentText" AdornmentColor="@AdornmentColor" IconSize="@IconSize" AdornmentText="@AdornmentText"
Clearable="@Clearable" OnClearButtonClick="(async (e) => await SelectClearButtonClickHandlerAsync(e))" Clearable="@Clearable" OnClearButtonClick="(async (e) => await SelectClearButtonClickHandlerAsync(e))"

File diff suppressed because it is too large Load Diff

View File

@ -129,7 +129,7 @@ public partial class SelectItem<T> : SelectItemBase, IDisposable
var converter = Select?.Converter; var converter = Select?.Converter;
if (converter == null) if (converter == null)
return $"{Value}"; return $"{Value}";
return converter.Set(Value); return converter.Convert(Value);
} }
} }

View File

@ -28,8 +28,8 @@ public partial class Slider<T> : UIComponent
[Category(CategoryTypes.Slider.Validation)] [Category(CategoryTypes.Slider.Validation)]
public T Min public T Min
{ {
get => Converter.Get(_min); get => Converter.ConvertBack(_min);
set => _min = Converter.Set(value); set => _min = Converter.Convert(value);
} }
/// <summary> /// <summary>
@ -40,8 +40,8 @@ public partial class Slider<T> : UIComponent
[Category(CategoryTypes.Slider.Validation)] [Category(CategoryTypes.Slider.Validation)]
public T Max public T Max
{ {
get => Converter.Get(_max); get => Converter.ConvertBack(_max);
set => _max = Converter.Set(value); set => _max = Converter.Convert(value);
} }
/// <summary> /// <summary>
@ -52,8 +52,8 @@ public partial class Slider<T> : UIComponent
[Category(CategoryTypes.Slider.Validation)] [Category(CategoryTypes.Slider.Validation)]
public T Step public T Step
{ {
get => Converter.Get(_step); get => Converter.ConvertBack(_step);
set => _step = Converter.Set(value); set => _step = Converter.Convert(value);
} }
/// <summary> /// <summary>
@ -73,7 +73,7 @@ public partial class Slider<T> : UIComponent
[Parameter] [Parameter]
[Category(CategoryTypes.Slider.Behavior)] [Category(CategoryTypes.Slider.Behavior)]
public Converter<T> Converter { get; set; } = new DefaultConverter<T>() { Culture = CultureInfo.InvariantCulture }; public ToStringConverter<T> Converter { get; set; } = new DefaultConverter<T>() { Culture = CultureInfo.InvariantCulture };
[Parameter] public EventCallback<T> ValueChanged { get; set; } [Parameter] public EventCallback<T> ValueChanged { get; set; }
@ -81,10 +81,10 @@ public partial class Slider<T> : UIComponent
[Category(CategoryTypes.Slider.Data)] [Category(CategoryTypes.Slider.Data)]
public T Value public T Value
{ {
get => Converter.Get(_value); get => Converter.ConvertBack(_value);
set set
{ {
var d = Converter.Set(value); var d = Converter.Convert(value);
if (_value == d) if (_value == d)
return; return;
_value = d; _value = d;

View File

@ -9,7 +9,7 @@
HelperTextOnFocus="@HelperTextOnFocus" HelperTextOnFocus="@HelperTextOnFocus"
CounterText="@GetCounterText()" CounterText="@GetCounterText()"
FullWidth="@FullWidth" FullWidth="@FullWidth"
Class="@Classname" Class="@ClassList"
Error="@HasErrors" Error="@HasErrors"
ErrorText="@GetErrorText()" ErrorText="@GetErrorText()"
ErrorId="@ErrorId" ErrorId="@ErrorId"
@ -43,7 +43,7 @@
AdornmentAriaLabel="@AdornmentAriaLabel" AdornmentAriaLabel="@AdornmentAriaLabel"
IconSize="@IconSize" IconSize="@IconSize"
OnAdornmentClick="@OnAdornmentClick" OnAdornmentClick="@OnAdornmentClick"
Error="@Error" Error="@HasError"
ErrorId="@ErrorId" ErrorId="@ErrorId"
Immediate="@Immediate" Immediate="@Immediate"
Margin="@Margin" Margin="@Margin"
@ -83,7 +83,7 @@
AdornmentColor="@AdornmentColor" AdornmentColor="@AdornmentColor"
IconSize="@IconSize" IconSize="@IconSize"
OnAdornmentClick="@OnAdornmentClick" OnAdornmentClick="@OnAdornmentClick"
Error="@Error" Error="@HasError"
Immediate="@Immediate" Immediate="@Immediate"
Margin="@Margin" OnBlur="@OnBlurred" Margin="@Margin" OnBlur="@OnBlurred"
Clearable="@Clearable" Clearable="@Clearable"

View File

@ -7,13 +7,9 @@ namespace Connected.Components;
public partial class TextField<T> : DebouncedInput<T> public partial class TextField<T> : DebouncedInput<T>
{ {
protected string Classname => private Mask? _maskReference;
new CssBuilder("mud-input-input-control")
.AddClass(Class)
.Build();
public Input<string> InputReference { get; private set; } public Input<string> InputReference { get; private set; }
private Mask _maskReference;
/// <summary> /// <summary>
/// Type of the input element. It should be a valid HTML5 input type. /// Type of the input element. It should be a valid HTML5 input type.
@ -38,6 +34,11 @@ public partial class TextField<T> : DebouncedInput<T>
/// </summary> /// </summary>
[Parameter] public EventCallback<MouseEventArgs> OnClearButtonClick { get; set; } [Parameter] public EventCallback<MouseEventArgs> OnClearButtonClick { get; set; }
protected string ClassList =>
new CssBuilder("mud-input-input-control")
.AddClass(Class)
.Build();
public override ValueTask FocusAsync() public override ValueTask FocusAsync()
{ {
if (_mask == null) if (_mask == null)
@ -130,10 +131,10 @@ public partial class TextField<T> : DebouncedInput<T>
{ {
if (_mask != null) if (_mask != null)
{ {
var textValue = Converter.Set(value); var textValue = Converter.Convert(value);
_mask.SetText(textValue); _mask.SetText(textValue);
textValue = Mask.GetCleanText(); textValue = Mask.GetCleanText();
value = Converter.Get(textValue); value = Converter.ConvertBack(textValue);
} }
return base.SetValueAsync(value, updateText); return base.SetValueAsync(value, updateText);
} }

View File

@ -14,11 +14,13 @@ public partial class TimePicker : Picker<TimeSpan?>
private const string format24Hours = "HH:mm"; private const string format24Hours = "HH:mm";
private const string format12Hours = "hh:mm tt"; private const string format12Hours = "hh:mm tt";
public TimePicker() : base(new DefaultConverter<TimeSpan?>())
public TimePicker() : base()
{ {
Converter.GetFunc = OnGet; _timeFormat = format24Hours;
Converter.SetFunc = OnSet;
((DefaultConverter<TimeSpan?>)Converter).Format = format24Hours; SetConverter(new LambdaConverter<TimeSpan?, string>((e) => OnSet(e), (e) => OnGet(e)));
AdornmentIcon = Icons.Material.Filled.AccessTime; AdornmentIcon = Icons.Material.Filled.AccessTime;
AdornmentAriaLabel = "Open Time Picker"; AdornmentAriaLabel = "Open Time Picker";
} }
@ -30,7 +32,7 @@ public partial class TimePicker : Picker<TimeSpan?>
var time = DateTime.Today.Add(timespan.Value); var time = DateTime.Today.Add(timespan.Value);
return time.ToString(((DefaultConverter<TimeSpan?>)Converter).Format, Culture); return time.ToString(_timeFormat, Culture);
} }
private TimeSpan? OnGet(string value) private TimeSpan? OnGet(string value)
@ -38,7 +40,7 @@ public partial class TimePicker : Picker<TimeSpan?>
if (string.IsNullOrEmpty(value)) if (string.IsNullOrEmpty(value))
return null; return null;
if (DateTime.TryParseExact(value, ((DefaultConverter<TimeSpan?>)Converter).Format, Culture, DateTimeStyles.None, out var time)) if (DateTime.TryParseExact(value, _timeFormat, Culture, DateTimeStyles.None, out var time))
{ {
return time.TimeOfDay; return time.TimeOfDay;
} }
@ -105,13 +107,10 @@ public partial class TimePicker : Picker<TimeSpan?>
_amPm = value; _amPm = value;
if (Converter is DefaultConverter<TimeSpan?> defaultConverter && string.IsNullOrWhiteSpace(_timeFormat)) _timeFormat = AmPm ? format12Hours : format24Hours;
{
defaultConverter.Format = AmPm ? format12Hours : format24Hours;
}
Touched = true; Modified = true;
SetTextAsync(Converter.Set(_value), false).AndForget(); SetTextAsync(Converter.Convert(_value), false).AndForget();
} }
} }
@ -129,11 +128,9 @@ public partial class TimePicker : Picker<TimeSpan?>
return; return;
_timeFormat = value; _timeFormat = value;
if (Converter is DefaultConverter<TimeSpan?> defaultConverter)
defaultConverter.Format = _timeFormat; Modified = true;
SetTextAsync(Converter.Convert(_value), false).AndForget();
Touched = true;
SetTextAsync(Converter.Set(_value), false).AndForget();
} }
} }
@ -155,7 +152,7 @@ public partial class TimePicker : Picker<TimeSpan?>
TimeIntermediate = time; TimeIntermediate = time;
_value = time; _value = time;
if (updateValue) if (updateValue)
await SetTextAsync(Converter.Set(_value), false); await SetTextAsync(Converter.Convert(_value), false);
UpdateTimeSetFromTime(); UpdateTimeSetFromTime();
await TimeChanged.InvokeAsync(_value); await TimeChanged.InvokeAsync(_value);
BeginValidate(); BeginValidate();
@ -170,9 +167,9 @@ public partial class TimePicker : Picker<TimeSpan?>
protected override Task StringValueChanged(string value) protected override Task StringValueChanged(string value)
{ {
Touched = true; Modified = true;
// Update the time property (without updating back the Value property) // Update the time property (without updating back the Value property)
return SetTimeAsync(Converter.Get(value), false); return SetTimeAsync(Converter.ConvertBack(value), false);
} }
//The last line cannot be tested //The last line cannot be tested

View File

@ -12,7 +12,7 @@ public partial class TreeViewItem<T> : UIComponent
private string _text; private string _text;
private bool _disabled; private bool _disabled;
private bool _isChecked, _isSelected, _isServerLoaded; private bool _isChecked, _isSelected, _isServerLoaded;
private Converter<T> _converter = new DefaultConverter<T>(); private ToStringConverter<T> _converter = new DefaultConverter<T>();
private readonly List<TreeViewItem<T>> _childItems = new(); private readonly List<TreeViewItem<T>> _childItems = new();
protected string Classname => protected string Classname =>
@ -68,7 +68,7 @@ public partial class TreeViewItem<T> : UIComponent
[Category(CategoryTypes.TreeView.Behavior)] [Category(CategoryTypes.TreeView.Behavior)]
public string Text public string Text
{ {
get => string.IsNullOrEmpty(_text) ? _converter.Set(Value) : _text; get => string.IsNullOrEmpty(_text) ? _converter.Convert(Value) : _text;
set => _text = value; set => _text = value;
} }

View File

@ -28,10 +28,6 @@
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="7.0.0-rc.2.22476.2" /> <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="7.0.0-rc.2.22476.2" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Connected.Client\Connected.Client.csproj" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Colors\" /> <Folder Include="Colors\" />
</ItemGroup> </ItemGroup>

View File

@ -1,38 +1,48 @@
using System.ComponentModel; using System.ComponentModel;
namespace Connected namespace Connected;
public enum InputType
{ {
public enum InputType [Description("text")]
{ Text,
[Description("text")]
Text, [Description("password")]
[Description("password")] Password,
Password,
[Description("email")] [Description("email")]
Email, Email,
[Description("hidden")]
Hidden, [Description("hidden")]
[Description("number")] Hidden,
Number,
[Description("search")] [Description("number")]
Search, Number,
[Description("tel")]
Telephone, [Description("search")]
[Description("url")] Search,
Url,
[Description("color")] [Description("tel")]
Color, Telephone,
[Description("date")]
Date, [Description("url")]
[Description("datetime-local")] Url,
DateTimeLocal,
[Description("month")] [Description("color")]
Month, Color,
[Description("time")]
Time, [Description("date")]
[Description("week")] Date,
Week
[Description("datetime-local")]
DateTimeLocal,
}
[Description("month")]
Month,
[Description("time")]
Time,
[Description("week")]
Week
} }

View File

@ -1,63 +0,0 @@
using Connected.Middleware;
using Connected.Startup;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
namespace Connected;
public static class Instance
{
public static WebAssemblyHost Host { get; private set; } = default!;
private static IServiceProvider ServiceProvider { get; set; } = default!;
internal static T? GetService<T>()
{
return ServiceProvider.GetService<T>();
}
public static async Task Start<TApp>(List<IStartup> startups, Dictionary<string, string> args) where TApp : IComponent
{
var builder = WebAssemblyHostBuilder.CreateDefault(UnpackArguments(args));
builder.RootComponents.Add<TApp>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddHttpClient();
builder.Services.AddSingleton(typeof(IComponentMiddlewareService), typeof(ComponentMiddlewareService));
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
foreach (var startup in startups)
await startup.ConfigureServices(builder.Services);
Host = builder.Build();
foreach (var startup in startups)
await startup.Configure(Host);
await Host.RunAsync();
}
private static string[] UnpackArguments(Dictionary<string, string> args)
{
var result = new string[args.Count];
for (var i = 0; i < args.Count; i++)
{
var arg = args.ElementAt(i);
result[i] = string.IsNullOrWhiteSpace(arg.Value) ? arg.Key : $"{arg.Key}={arg.Value}";
}
return result;
}
public static void UseMiddlewareComponent<TMiddleware, TComponent>(this WebAssemblyHost app)
{
if (app.Services.GetService<IComponentMiddlewareService>() is not IComponentMiddlewareService service)
throw new NullReferenceException(nameof(IComponentMiddlewareService));
service.Add<TMiddleware, TComponent>();
}
}

View File

@ -1,6 +0,0 @@
namespace Connected;
public static class UIRoutes
{
public const string Management = "management";
}

View File

@ -11,11 +11,19 @@ namespace Connected
public BoolConverter() public BoolConverter()
{ {
SetFunc = OnSet;
GetFunc = OnGet;
} }
private T OnGet(bool? value) protected override bool? ConvertValue(T? value)
{
return OnSet(value);
}
protected override T? ConvertValueBack(bool? value)
{
return OnGet(value);
}
private T OnGet(bool? value)
{ {
try try
{ {
@ -31,12 +39,12 @@ namespace Connected
return (T)(object)(value == true ? 1 : (value == false ? (int?)0 : null)); return (T)(object)(value == true ? 1 : (value == false ? (int?)0 : null));
else else
{ {
UpdateGetError($"Conversion to type {typeof(T)} not implemented"); TriggerError($"Conversion to type {typeof(T)} not implemented");
} }
} }
catch (Exception e) catch (Exception e)
{ {
UpdateGetError("Conversion error: " + e.Message); TriggerError("Conversion error: " + e.Message);
return default(T); return default(T);
} }
return default(T); return default(T);
@ -69,13 +77,13 @@ namespace Connected
} }
else else
{ {
UpdateSetError("Unable to convert to bool? from type " + typeof(T).Name); TriggerError("Unable to convert to bool? from type " + typeof(T).Name);
return null; return null;
} }
} }
catch (FormatException e) catch (FormatException e)
{ {
UpdateSetError("Conversion error: " + e.Message); TriggerError("Conversion error: " + e.Message);
return null; return null;
} }
} }

View File

@ -1,88 +1,111 @@
using System; using System.Globalization;
using System.Globalization;
namespace Connected namespace Connected;
public class Converter<TSourceType, TDestinationType>
{ {
public class Converter<T, U> public event EventHandler<string?>? ErrorOccured;
{
public Func<T, U> SetFunc { get; set; }
public Func<U, T> GetFunc { get; set; }
/// <summary> /// <summary>
/// The culture info being used for decimal points, date and time format, etc. /// The culture info being used for decimal points, date and time format, etc.
/// </summary> /// </summary>
public CultureInfo Culture { get; set; } = Converters.DefaultCulture; public CultureInfo Culture { get; set; } = Converters.DefaultCulture;
public Action<string> OnError { get; set; } public TDestinationType? Convert(TSourceType value)
public bool SetError { get; set; } {
public bool GetError { get; set; } try
public string SetErrorMessage { get; set; } {
public string GetErrorMessage { get; set; } return ConvertValue(value);
}
catch (Exception e)
{
TriggerError($"Conversion from {typeof(TSourceType).Name} to {typeof(TDestinationType).Name} failed: {e.Message}", e);
}
public U Set(T value) return default;
{ }
SetError = false;
SetErrorMessage = null;
if (SetFunc == null)
return default(U);
try
{
return SetFunc(value);
}
catch (Exception e)
{
SetError = true;
SetErrorMessage = $"Conversion from {typeof(T).Name} to {typeof(U).Name} failed: {e.Message}";
}
return default(U);
}
public T Get(U value) protected virtual TDestinationType? ConvertValue(TSourceType? value)
{ {
GetError = false; return default;
GetErrorMessage = null; }
if (GetFunc == null)
return default(T);
try
{
return GetFunc(value);
}
catch (Exception e)
{
GetError = true;
GetErrorMessage = $"Conversion from {typeof(U).Name} to {typeof(T).Name} failed: {e.Message}";
}
return default(T);
}
protected void UpdateSetError(string msg) public TSourceType? ConvertBack(TDestinationType value)
{ {
SetError = true; try
SetErrorMessage = msg; {
OnError?.Invoke(msg); return ConvertValueBack(value);
} }
catch (Exception e)
{
TriggerError($"Conversion from {typeof(TDestinationType).Name} to {typeof(TSourceType).Name} failed: {e.Message}", e);
}
protected void UpdateGetError(string msg) return default;
{ }
GetError = true;
GetErrorMessage = msg;
OnError?.Invoke(msg);
}
}
/// <summary> protected virtual TSourceType? ConvertValueBack(TDestinationType? value)
/// Converter from T to string {
/// return default;
/// Set converts to string }
/// Get converts from string
/// </summary>
public class Converter<T> : Converter<T, string>
{
/// <summary> protected void TriggerError(string? msg, Exception? e = null)
/// Custom Format to be applied on bidirectional way. {
/// </summary> ErrorOccured?.Invoke(this, msg);
public string Format { get; set; } = null;
} OnErrorOccured(msg, e);
}
protected virtual void OnErrorOccured(string? msg, Exception? e)
{
}
}
/// <summary>
/// Converts values using the supplied lambda functions.
/// </summary>
/// <typeparam name="TSourceType">The source type</typeparam>
/// <typeparam name="TDestinationType">The destination type</typeparam>
public class LambdaConverter<TSourceType, TDestinationType> : Converter<TSourceType, TDestinationType>
{
private readonly Func<TSourceType, TDestinationType?>? _convertFunction;
private readonly Func<TDestinationType, TSourceType?>? _convertBackFunction;
public LambdaConverter(Func<TSourceType, TDestinationType?>? convertFunction = null, Func<TDestinationType, TSourceType?>? convertBackFunction = null)
{
_convertFunction = convertFunction;
_convertBackFunction = convertBackFunction;
}
protected override TDestinationType? ConvertValue(TSourceType? value)
{
if (_convertFunction is null)
return base.ConvertValue(value);
return _convertFunction.Invoke(value);
}
protected override TSourceType? ConvertValueBack(TDestinationType? value)
{
if (_convertFunction is null)
return base.ConvertValueBack(value);
return _convertBackFunction.Invoke(value);
}
}
/// <summary>
/// Converter from T to string
///
/// Set converts to string
/// Get converts from string
/// </summary>
public class ToStringConverter<T> : Converter<T, string>
{
/// <summary>
/// Custom Format to be applied on bidirectional way.
/// </summary>
public string? Format { get; set; } = null;
} }

View File

@ -1,55 +1,8 @@
using System.Globalization; using System.Globalization;
namespace Connected namespace Connected;
public static class Converters
{ {
public static class Converters public static CultureInfo DefaultCulture { get; set; } = CultureInfo.CurrentUICulture;
{
public static CultureInfo DefaultCulture = CultureInfo.CurrentUICulture;
#region --> Date converters
public static Converter<DateTime> IsoDate
=> new()
{ SetFunc = SetIsoDate, GetFunc = GetIsoDate };
private static DateTime GetIsoDate(string value)
{
if (DateTime.TryParse(value, out var dateTime))
return dateTime;
return DateTime.MinValue;
}
private static string SetIsoDate(DateTime value)
{
return value.ToIsoDateString();
}
public static Converter<DateTime?> NullableIsoDate
=> new()
{ SetFunc = SetNullableIsoDate, GetFunc = GetNullableIsoDate };
private static DateTime? GetNullableIsoDate(string value)
{
if (DateTime.TryParse(value, out var dateTime))
return dateTime;
return null;
}
private static string SetNullableIsoDate(DateTime? value)
{
return value.ToIsoDateString();
}
public static DateConverter DateFormat(string format)
{
format ??= "yyyy-MM-dd";
return new DateConverter(format);
}
public static DateConverter DateFormat(string format, CultureInfo culture)
{
return new DateConverter(format) { Culture = culture };
}
#endregion
}
} }

View File

@ -1,90 +1,29 @@
using System; namespace Connected;
namespace Connected /// <summary>
/// A ready made DateTime? to string binding converter with configurable date format and culture
/// </summary>
public class NullableDateConverter : ToStringConverter<DateTime?>
{ {
public string DateFormat { get; set; }
/// <summary> public NullableDateConverter(string format = "yyyy-MM-dd") => DateFormat = format;
/// A ready made DateTime? to string binding converter with configurable date format and culture
/// </summary>
public class NullableDateConverter : Converter<DateTime?>
{
public string DateFormat { get; set; }
public NullableDateConverter(string format = "yyyy-MM-dd") protected override string? ConvertValue(DateTime? value) => value?.ToString(DateFormat, Culture);
{
DateFormat = format;
SetFunc = OnSet;
GetFunc = OnGet;
}
private DateTime? OnGet(string arg)
{
try
{
return DateTime.ParseExact(arg, DateFormat, Culture);
}
catch (FormatException e)
{
UpdateGetError(e.Message);
return null;
}
}
private string OnSet(DateTime? arg)
{
if (arg == null)
return null;
try
{
return arg.Value.ToString(DateFormat, Culture);
}
catch (FormatException e)
{
UpdateSetError(e.Message);
return null;
}
}
}
/// <summary>
/// A ready made DateTime to string binding converter with configurable date format and culture
/// </summary>
public class DateConverter : Converter<DateTime>
{
public string DateFormat { get; set; } = "yyyy-MM-dd";
public DateConverter(string format)
{
DateFormat = format;
SetFunc = OnSet;
GetFunc = OnGet;
}
private DateTime OnGet(string arg)
{
try
{
return DateTime.ParseExact(arg, DateFormat, Culture);
}
catch (FormatException e)
{
UpdateGetError(e.Message);
return default;
}
}
private string OnSet(DateTime arg)
{
try
{
return arg.ToString(DateFormat, Culture);
}
catch (FormatException e)
{
UpdateSetError(e.Message);
return null;
}
}
}
protected override DateTime? ConvertValueBack(string? value) => DateTime.ParseExact(value, DateFormat, Culture);
} }
/// <summary>
/// A ready made DateTime to string binding converter with configurable date format and culture
/// </summary>
public class DateConverter : ToStringConverter<DateTime>
{
public string DateFormat { get; set; }
public DateConverter(string format = "yyyy-MM-dd") => DateFormat = format;
protected override string? ConvertValue(DateTime value) => value.ToString(DateFormat, Culture);
protected override DateTime ConvertValueBack(string? value) => DateTime.ParseExact(value, DateFormat, Culture);
}

View File

@ -1,315 +1,464 @@
using System; using System.Globalization;
using System.Globalization;
namespace Connected namespace Connected;
/// <summary>
/// A universal T to string binding converter
/// </summary>
public class DefaultConverter<T> : ToStringConverter<T>
{ {
protected override string? ConvertValue(T? value) => ConvertToString(value);
/// <summary> protected override T? ConvertValueBack(string? value) => ConvertFromString(value);
/// A universal T to string binding converter
/// </summary>
public class DefaultConverter<T> : Converter<T>
{
public DefaultConverter() public string DefaultTimeSpanFormat { get; set; } = "c";
{
SetFunc = ConvertToString;
GetFunc = ConvertFromString;
}
public string DefaultTimeSpanFormat { get; set; } = "c"; protected virtual T? ConvertFromString(string? value)
{
try
{
/*
* This is important, or otherwise all the TryParse down there might fail.
*/
if (string.IsNullOrEmpty(value))
{
return default;
}
/*
* String
*/
else if (typeof(T) == typeof(string))
{
return (T)(object)value;
}
/*
* Char
*/
else if (typeof(T) == typeof(char) || typeof(T) == typeof(char?))
{
return (T)(object)value[0];
}
/*
* Bool
*/
else if (typeof(T) == typeof(bool) || typeof(T) == typeof(bool?))
{
var lowerValue = value.ToLowerInvariant();
protected virtual T ConvertFromString(string value) if (lowerValue is "true" or "on")
{ return (T)(object)true;
try
{
// string
if (typeof(T) == typeof(string))
return (T)(object)value;
// this is important, or otherwise all the TryParse down there might fail. if (lowerValue is "false" or "off")
if (string.IsNullOrEmpty(value)) return (T)(object)false;
return default(T);
// char
else if (typeof(T) == typeof(char) || typeof(T) == typeof(char?))
{
return (T)(object)value[0];
}
// bool
else if (typeof(T) == typeof(bool) || typeof(T) == typeof(bool?))
{
var lowerValue = value.ToLowerInvariant();
if (lowerValue is "true" or "on")
return (T)(object)true;
if (lowerValue is "false" or "off")
return (T)(object)false;
UpdateGetError("Not a valid boolean");
}
// sbyte
else if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(sbyte?))
{
if (sbyte.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue;
UpdateGetError("Not a valid number");
}
// byte
else if (typeof(T) == typeof(byte) || typeof(T) == typeof(byte?))
{
if (byte.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue;
UpdateGetError("Not a valid number");
}
// short
else if (typeof(T) == typeof(short) || typeof(T) == typeof(short?))
{
if (short.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue;
UpdateGetError("Not a valid number");
}
// ushort
else if (typeof(T) == typeof(ushort) || typeof(T) == typeof(ushort?))
{
if (ushort.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue;
UpdateGetError("Not a valid number");
}
// int
else if (typeof(T) == typeof(int) || typeof(T) == typeof(int?))
{
if (int.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue;
UpdateGetError("Not a valid number");
}
// uint
else if (typeof(T) == typeof(uint) || typeof(T) == typeof(uint?))
{
if (uint.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue;
UpdateGetError("Not a valid number");
}
// long
else if (typeof(T) == typeof(long) || typeof(T) == typeof(long?))
{
if (long.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue;
UpdateGetError("Not a valid number");
}
// ulong
else if (typeof(T) == typeof(ulong) || typeof(T) == typeof(ulong?))
{
if (ulong.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue;
UpdateGetError("Not a valid number");
}
// float
else if (typeof(T) == typeof(float) || typeof(T) == typeof(float?))
{
if (float.TryParse(value, NumberStyles.Any, Culture, out var parsedValue))
return (T)(object)parsedValue;
UpdateGetError("Not a valid number");
}
// double
else if (typeof(T) == typeof(double) || typeof(T) == typeof(double?))
{
if (double.TryParse(value, NumberStyles.Any, Culture, out var parsedValue))
return (T)(object)parsedValue;
UpdateGetError("Not a valid number");
}
// decimal
else if (typeof(T) == typeof(decimal) || typeof(T) == typeof(decimal?))
{
if (decimal.TryParse(value, NumberStyles.Any, Culture, out var parsedValue))
return (T)(object)parsedValue;
UpdateGetError("Not a valid number");
}
// guid
else if (typeof(T) == typeof(Guid) || typeof(T) == typeof(Guid?))
{
if (Guid.TryParse(value, out var parsedValue))
return (T)(object)parsedValue;
UpdateGetError("Not a valid GUID");
}
// enum
else if (IsNullableEnum(typeof(T)))
{
var enum_type = Nullable.GetUnderlyingType(typeof(T));
if (Enum.TryParse(enum_type, value, out var parsedValue))
return (T)parsedValue;
UpdateGetError("Not a value of " + enum_type.Name);
}
else if (typeof(T).IsEnum)
{
if (Enum.TryParse(typeof(T), value, out var parsedValue))
return (T)parsedValue;
UpdateGetError("Not a value of " + typeof(T).Name);
}
// datetime
else if (typeof(T) == typeof(DateTime) || typeof(T) == typeof(DateTime?))
{
try
{
return (T)(object)DateTime.ParseExact(value, Format ?? Culture.DateTimeFormat.ShortDatePattern, Culture);
}
catch (FormatException)
{
UpdateGetError("Not a valid date time");
}
}
// timespan
else if (typeof(T) == typeof(TimeSpan) || typeof(T) == typeof(TimeSpan?))
{
try
{
return (T)(object)TimeSpan.ParseExact(value, Format ?? DefaultTimeSpanFormat, Culture);
}
catch (FormatException)
{
UpdateGetError("Not a valid time span");
}
}
else
{
UpdateGetError($"Conversion to type {typeof(T)} not implemented");
}
}
catch (Exception e)
{
UpdateGetError("Conversion error: " + e.Message);
}
return default(T); TriggerError("Not a valid boolean");
} }
/*
* Sbyte
*/
else if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(sbyte?))
{
if (sbyte.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue;
protected virtual string ConvertToString(T arg) TriggerError("Not a valid number");
{ }
if (arg == null) /*
return null; // <-- this catches all nullable values which are null. no nullchecks necessary below! * Byte
try */
{ else if (typeof(T) == typeof(byte) || typeof(T) == typeof(byte?))
// string {
if (typeof(T) == typeof(string)) if (byte.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (string)(object)arg; return (T)(object)parsedValue;
// char
if (typeof(T) == typeof(char))
return ((char)(object)arg).ToString(Culture);
if (typeof(T) == typeof(char?))
return ((char?)(object)arg).Value.ToString(Culture);
// bool
if (typeof(T) == typeof(bool))
return ((bool)(object)arg).ToString(CultureInfo.InvariantCulture);
if (typeof(T) == typeof(bool?))
return ((bool?)(object)arg).Value.ToString(CultureInfo.InvariantCulture);
// sbyte
if (typeof(T) == typeof(sbyte))
return ((sbyte)(object)arg).ToString(Format, Culture);
if (typeof(T) == typeof(sbyte?))
return ((sbyte?)(object)arg).Value.ToString(Format, Culture);
// byte
if (typeof(T) == typeof(byte))
return ((byte)(object)arg).ToString(Format, Culture);
if (typeof(T) == typeof(byte?))
return ((byte?)(object)arg).Value.ToString(Format, Culture);
// short
if (typeof(T) == typeof(short))
return ((short)(object)arg).ToString(Format, Culture);
if (typeof(T) == typeof(short?))
return ((short?)(object)arg).Value.ToString(Format, Culture);
// ushort
if (typeof(T) == typeof(ushort))
return ((ushort)(object)arg).ToString(Format, Culture);
if (typeof(T) == typeof(ushort?))
return ((ushort?)(object)arg).Value.ToString(Format, Culture);
// int
else if (typeof(T) == typeof(int))
return ((int)(object)arg).ToString(Format, Culture);
else if (typeof(T) == typeof(int?))
return ((int?)(object)arg).Value.ToString(Format, Culture);
// uint
else if (typeof(T) == typeof(uint))
return ((uint)(object)arg).ToString(Format, Culture);
else if (typeof(T) == typeof(uint?))
return ((uint?)(object)arg).Value.ToString(Format, Culture);
// long
else if (typeof(T) == typeof(long))
return ((long)(object)arg).ToString(Format, Culture);
else if (typeof(T) == typeof(long?))
return ((long?)(object)arg).Value.ToString(Format, Culture);
// ulong
else if (typeof(T) == typeof(ulong))
return ((ulong)(object)arg).ToString(Format, Culture);
else if (typeof(T) == typeof(ulong?))
return ((ulong?)(object)arg).Value.ToString(Format, Culture);
// float
else if (typeof(T) == typeof(float))
return ((float)(object)arg).ToString(Format, Culture);
else if (typeof(T) == typeof(float?))
return ((float?)(object)arg).Value.ToString(Format, Culture);
// double
else if (typeof(T) == typeof(double))
return ((double)(object)arg).ToString(Format, Culture);
else if (typeof(T) == typeof(double?))
return ((double?)(object)arg).Value.ToString(Format, Culture);
// decimal
else if (typeof(T) == typeof(decimal))
return ((decimal)(object)arg).ToString(Format, Culture);
else if (typeof(T) == typeof(decimal?))
return ((decimal?)(object)arg).Value.ToString(Format, Culture);
// guid
else if (typeof(T) == typeof(Guid))
{
var value = (Guid)(object)arg;
return value.ToString();
}
else if (typeof(T) == typeof(Guid?))
{
var value = (Guid?)(object)arg;
return value.Value.ToString();
}
// enum
else if (IsNullableEnum(typeof(T)))
{
var value = (Enum)(object)arg;
return value.ToString();
}
else if (typeof(T).IsEnum)
{
var value = (Enum)(object)arg;
return value.ToString();
}
// datetime
else if (typeof(T) == typeof(DateTime))
{
var value = (DateTime)(object)arg;
return value.ToString(Format ?? Culture.DateTimeFormat.ShortDatePattern, Culture);
}
else if (typeof(T) == typeof(DateTime?))
{
var value = (DateTime?)(object)arg;
return value.Value.ToString(Format ?? Culture.DateTimeFormat.ShortDatePattern, Culture);
}
// timespan
else if (typeof(T) == typeof(TimeSpan))
{
var value = (TimeSpan)(object)arg;
return value.ToString(Format ?? DefaultTimeSpanFormat, Culture);
}
else if (typeof(T) == typeof(TimeSpan?))
{
var value = (TimeSpan?)(object)arg;
return value.Value.ToString(Format ?? DefaultTimeSpanFormat, Culture);
}
return arg.ToString();
}
catch (FormatException e)
{
UpdateSetError("Conversion error: " + e.Message);
return null;
}
}
public static bool IsNullableEnum(Type t) TriggerError("Not a valid number");
{ }
var u = Nullable.GetUnderlyingType(t); /*
return (u != null) && u.IsEnum; * Short
} */
} else if (typeof(T) == typeof(short) || typeof(T) == typeof(short?))
{
if (short.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue;
TriggerError("Not a valid number");
}
/*
* Ushort
*/
else if (typeof(T) == typeof(ushort) || typeof(T) == typeof(ushort?))
{
if (ushort.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue;
TriggerError("Not a valid number");
}
/*
* Int
*/
else if (typeof(T) == typeof(int) || typeof(T) == typeof(int?))
{
if (int.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue;
TriggerError("Not a valid number");
}
/*
* Uint
*/
else if (typeof(T) == typeof(uint) || typeof(T) == typeof(uint?))
{
if (uint.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue;
TriggerError("Not a valid number");
}
/*
* Long
*/
else if (typeof(T) == typeof(long) || typeof(T) == typeof(long?))
{
if (long.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue;
TriggerError("Not a valid number");
}
/*
* Ulong
*/
else if (typeof(T) == typeof(ulong) || typeof(T) == typeof(ulong?))
{
if (ulong.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue;
TriggerError("Not a valid number");
}
/*
* Float
*/
else if (typeof(T) == typeof(float) || typeof(T) == typeof(float?))
{
if (float.TryParse(value, NumberStyles.Any, Culture, out var parsedValue))
return (T)(object)parsedValue;
TriggerError("Not a valid number");
}
/*
* Double
*/
else if (typeof(T) == typeof(double) || typeof(T) == typeof(double?))
{
if (double.TryParse(value, NumberStyles.Any, Culture, out var parsedValue))
return (T)(object)parsedValue;
TriggerError("Not a valid number");
}
/*
* Decimal
*/
else if (typeof(T) == typeof(decimal) || typeof(T) == typeof(decimal?))
{
if (decimal.TryParse(value, NumberStyles.Any, Culture, out var parsedValue))
return (T)(object)parsedValue;
TriggerError("Not a valid number");
}
/*
* Guid
*/
else if (typeof(T) == typeof(Guid) || typeof(T) == typeof(Guid?))
{
if (Guid.TryParse(value, out var parsedValue))
return (T)(object)parsedValue;
TriggerError("Not a valid GUID");
}
/*
* Enum
*/
else if (IsNullableEnum(typeof(T)))
{
var enum_type = Nullable.GetUnderlyingType(typeof(T));
if (Enum.TryParse(enum_type, value, out var parsedValue))
return (T)parsedValue;
TriggerError("Not a value of " + enum_type.Name);
}
else if (typeof(T).IsEnum)
{
if (Enum.TryParse(typeof(T), value, out var parsedValue))
return (T)parsedValue;
TriggerError("Not a value of " + typeof(T).Name);
}
/*
* DateTime
*/
else if (typeof(T) == typeof(DateTime) || typeof(T) == typeof(DateTime?))
{
try
{
return (T)(object)DateTime.ParseExact(value, Format ?? Culture.DateTimeFormat.ShortDatePattern, Culture);
}
catch (FormatException)
{
TriggerError("Not a valid date time");
}
}
/*
* Timespan
*/
else if (typeof(T) == typeof(TimeSpan) || typeof(T) == typeof(TimeSpan?))
{
try
{
return (T)(object)TimeSpan.ParseExact(value, Format ?? DefaultTimeSpanFormat, Culture);
}
catch (FormatException)
{
TriggerError("Not a valid time span");
}
}
else
{
TriggerError($"Conversion to type {typeof(T)} not implemented");
}
}
catch (Exception e)
{
TriggerError("Conversion error: " + e.Message);
}
return default;
}
protected virtual string? ConvertToString(T? arg)
{
/*
* This catches all null values. No additional null checks necessary.
*/
if (arg is null)
return null;
try
{
/*
* String
*/
if (typeof(T) == typeof(string))
{
return (string)(object)arg;
}
/*
* Char
*/
else if (typeof(T) == typeof(char))
{
return ((char)(object)arg).ToString(Culture);
}
else if (typeof(T) == typeof(char?))
{
return ((char?)(object)arg).Value.ToString(Culture);
}
/*
* Bool
*/
else if (typeof(T) == typeof(bool))
{
return ((bool)(object)arg).ToString(CultureInfo.InvariantCulture);
}
else if (typeof(T) == typeof(bool?))
{
return ((bool?)(object)arg).Value.ToString(CultureInfo.InvariantCulture);
}
/*
* Sbyte
*/
else if (typeof(T) == typeof(sbyte))
{
return ((sbyte)(object)arg).ToString(Format, Culture);
}
else if (typeof(T) == typeof(sbyte?))
{
return ((sbyte?)(object)arg).Value.ToString(Format, Culture);
}
/*
* Byte
*/
else if (typeof(T) == typeof(byte))
{
return ((byte)(object)arg).ToString(Format, Culture);
}
else if (typeof(T) == typeof(byte?))
{
return ((byte?)(object)arg).Value.ToString(Format, Culture);
}
/*
* Short
*/
else if (typeof(T) == typeof(short))
{
return ((short)(object)arg).ToString(Format, Culture);
}
else if (typeof(T) == typeof(short?))
{
return ((short?)(object)arg).Value.ToString(Format, Culture);
}
/*
* Ushort
*/
else if (typeof(T) == typeof(ushort))
{
return ((ushort)(object)arg).ToString(Format, Culture);
}
else if (typeof(T) == typeof(ushort?))
{
return ((ushort?)(object)arg).Value.ToString(Format, Culture);
}
/*
* Int
*/
else if (typeof(T) == typeof(int))
{
return ((int)(object)arg).ToString(Format, Culture);
}
else if (typeof(T) == typeof(int?))
{
return ((int?)(object)arg).Value.ToString(Format, Culture);
}
/*
* Uint
*/
else if (typeof(T) == typeof(uint))
{
return ((uint)(object)arg).ToString(Format, Culture);
}
else if (typeof(T) == typeof(uint?))
{
return ((uint?)(object)arg).Value.ToString(Format, Culture);
}
/*
* Long
*/
else if (typeof(T) == typeof(long))
{
return ((long)(object)arg).ToString(Format, Culture);
}
else if (typeof(T) == typeof(long?))
{
return ((long?)(object)arg).Value.ToString(Format, Culture);
}
/*
* Ulong
*/
else if (typeof(T) == typeof(ulong))
{
return ((ulong)(object)arg).ToString(Format, Culture);
}
else if (typeof(T) == typeof(ulong?))
{
return ((ulong?)(object)arg).Value.ToString(Format, Culture);
}
/*
* Float
*/
else if (typeof(T) == typeof(float))
{
return ((float)(object)arg).ToString(Format, Culture);
}
else if (typeof(T) == typeof(float?))
{
return ((float?)(object)arg).Value.ToString(Format, Culture);
}
/*
* Double
*/
else if (typeof(T) == typeof(double))
{
return ((double)(object)arg).ToString(Format, Culture);
}
else if (typeof(T) == typeof(double?))
{
return ((double?)(object)arg).Value.ToString(Format, Culture);
}
/*
* Decimal
*/
else if (typeof(T) == typeof(decimal))
{
return ((decimal)(object)arg).ToString(Format, Culture);
}
else if (typeof(T) == typeof(decimal?))
{
return ((decimal?)(object)arg).Value.ToString(Format, Culture);
}
/*
* Guid
*/
else if (typeof(T) == typeof(Guid))
{
var value = (Guid)(object)arg;
return value.ToString();
}
else if (typeof(T) == typeof(Guid?))
{
var value = (Guid?)(object)arg;
return value.Value.ToString();
}
/*
* Enum
*/
else if (IsNullableEnum(typeof(T)))
{
var value = (Enum)(object)arg;
return value.ToString();
}
else if (typeof(T).IsEnum)
{
var value = (Enum)(object)arg;
return value.ToString();
}
/*
* DateTime
*/
else if (typeof(T) == typeof(DateTime))
{
var value = (DateTime)(object)arg;
return value.ToString(Format ?? Culture.DateTimeFormat.ShortDatePattern, Culture);
}
else if (typeof(T) == typeof(DateTime?))
{
var value = (DateTime?)(object)arg;
return value.Value.ToString(Format ?? Culture.DateTimeFormat.ShortDatePattern, Culture);
}
/*
* Timespan
*/
else if (typeof(T) == typeof(TimeSpan))
{
var value = (TimeSpan)(object)arg;
return value.ToString(Format ?? DefaultTimeSpanFormat, Culture);
}
else if (typeof(T) == typeof(TimeSpan?))
{
var value = (TimeSpan?)(object)arg;
return value.Value.ToString(Format ?? DefaultTimeSpanFormat, Culture);
}
return arg.ToString();
}
catch (FormatException e)
{
TriggerError("Conversion error: " + e.Message);
return null;
}
}
public static bool IsNullableEnum(Type t)
{
var u = Nullable.GetUnderlyingType(t);
return (u is not null) && u.IsEnum;
}
} }

View File

@ -0,0 +1,17 @@
using Connected.Extensions;
namespace Connected;
public class IsoDateConverter : Converter<DateTime, string>
{
protected override string ConvertValue(DateTime value) => value.ToIsoDateString();
protected override DateTime ConvertValueBack(string? value) => DateTime.TryParse(value, out var dateTime) ? dateTime : DateTime.MinValue;
}
public class NullableIsoDateConverter : Converter<DateTime?, string?>
{
protected override string? ConvertValue(DateTime? value) => value?.ToIsoDateString();
protected override DateTime? ConvertValueBack(string? value) => DateTime.TryParse(value, out var dateTime) ? dateTime : null;
}

View File

@ -1,285 +1,390 @@
using System; using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
namespace Connected namespace Connected;
/// <summary>
/// A universal T to double binding converter
///
/// Note: currently not in use. Should we ever use it, remove
/// the [ExcludeFromCodeCoverage] attribute
/// </summary>
[ExcludeFromCodeCoverage]
public class NumericConverter<T> : Converter<T, double>
{ {
/// <summary> protected override double ConvertValue(T? value)
/// A universal T to double binding converter {
/// if (value is null)
/// Note: currently not in use. Should we ever use it, remove {
/// the [ExcludeFromCodeCoverage] attribute return double.NaN;
/// </summary> }
[ExcludeFromCodeCoverage] /*
public class NumericConverter<T> : Converter<T, double> * Double
{ */
else if (typeof(T) == typeof(double))
{
return (double)(object)value;
}
else if (typeof(T) == typeof(double?))
{
return ((double?)(object)value).Value;
}
/*
* String
*/
else if (typeof(T) == typeof(string))
{
return double.Parse((string)(object)value, NumberStyles.Any, Culture);
}
/*
* SByte
*/
else if (typeof(T) == typeof(sbyte))
{
return System.Convert.ToDouble((sbyte)(object)value);
}
else if (typeof(T) == typeof(sbyte?))
{
return System.Convert.ToDouble(((sbyte?)(object)value).Value);
}
/*
* Byte
*/
else if (typeof(T) == typeof(byte))
{
return System.Convert.ToDouble((byte)(object)value);
}
else if (typeof(T) == typeof(byte?))
{
return System.Convert.ToDouble(((byte?)(object)value).Value);
}
/*
* Short
*/
else if (typeof(T) == typeof(short))
{
return System.Convert.ToDouble((short)(object)value);
}
else if (typeof(T) == typeof(short?))
{
return System.Convert.ToDouble(((short?)(object)value).Value);
}
/*
* Ushort
*/
else if (typeof(T) == typeof(ushort))
{
return System.Convert.ToDouble((ushort)(object)value);
}
else if (typeof(T) == typeof(ushort?))
{
return System.Convert.ToDouble(((ushort?)(object)value).Value);
}
/*
* Int
*/
else if (typeof(T) == typeof(int))
{
return System.Convert.ToDouble((int)(object)value);
}
else if (typeof(T) == typeof(int?))
{
return System.Convert.ToDouble(((int?)(object)value).Value);
}
/*
* Uint
*/
else if (typeof(T) == typeof(uint))
{
return System.Convert.ToDouble((uint)(object)value);
}
else if (typeof(T) == typeof(uint?))
{
return System.Convert.ToDouble(((uint?)(object)value).Value);
}
/*
* Long
*/
else if (typeof(T) == typeof(long))
{
return System.Convert.ToDouble((long)(object)value);
}
else if (typeof(T) == typeof(long?))
{
return System.Convert.ToDouble(((long?)(object)value).Value);
}
/*
* Ulong
*/
else if (typeof(T) == typeof(ulong))
{
return System.Convert.ToDouble((ulong)(object)value);
}
else if (typeof(T) == typeof(ulong?))
{
return System.Convert.ToDouble(((ulong?)(object)value).Value);
}
/*
* Float
*/
else if (typeof(T) == typeof(float))
{
return System.Convert.ToDouble((float)(object)value);
}
else if (typeof(T) == typeof(float?))
{
return System.Convert.ToDouble(((float?)(object)value).Value);
}
/*
* Deimal
*/
else if (typeof(T) == typeof(decimal))
{
return System.Convert.ToDouble((decimal)(object)value);
}
else if (typeof(T) == typeof(decimal?))
{
return System.Convert.ToDouble(((decimal?)(object)value).Value);
}
else
{
TriggerError("Unable to convert to double from type " + typeof(T).Name);
return double.NaN;
}
}
public NumericConverter() protected override T? ConvertValueBack(double value)
{ {
SetFunc = OnSet; /*
GetFunc = OnGet; * Double
} */
if (typeof(T) == typeof(double) || typeof(T) == typeof(double?))
{
return (T)(object)value;
}
/*
* String
*/
else if (typeof(T) == typeof(string))
{
return (T)(object)value.ToString(Culture);
}
/*
* Sbyte
*/
else if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(sbyte?))
{
return (T)(object)System.Convert.ToSByte(value);
}
/*
* Byte
*/
else if (typeof(T) == typeof(byte) || typeof(T) == typeof(byte?))
{
return (T)(object)System.Convert.ToByte(value);
}
/*
* Short
*/
else if (typeof(T) == typeof(short) || typeof(T) == typeof(short?))
{
return (T)(object)System.Convert.ToInt16(value);
}
/*
* Ushort
*/
else if (typeof(T) == typeof(ushort) || typeof(T) == typeof(ushort?))
{
return (T)(object)System.Convert.ToUInt16(value);
}
/*
* Int
*/
else if (typeof(T) == typeof(int) || typeof(T) == typeof(int?))
{
return (T)(object)System.Convert.ToInt32(value);
}
/*
* Uint
*/
else if (typeof(T) == typeof(uint) || typeof(T) == typeof(uint?))
{
return (T)(object)System.Convert.ToUInt32(value);
}
/*
* Long
*/
else if (typeof(T) == typeof(long) || typeof(T) == typeof(long?))
{
return (T)(object)System.Convert.ToInt64(value);
}
/*
* Ulong
*/
else if (typeof(T) == typeof(ulong) || typeof(T) == typeof(ulong?))
{
return (T)(object)System.Convert.ToUInt64(value);
}
/*
* Float
*/
else if (typeof(T) == typeof(float) || typeof(T) == typeof(float?))
{
return (T)(object)System.Convert.ToSingle(value);
}
/*
* Decimal
*/
else if (typeof(T) == typeof(decimal) || typeof(T) == typeof(decimal?))
{
return (T)(object)System.Convert.ToDecimal(value);
}
else
{
TriggerError($"Conversion to type {typeof(T)} not implemented");
return default;
}
}
private T OnGet(double value) #region Floating Point comparison
{
try
{
// double
if (typeof(T) == typeof(double) || typeof(T) == typeof(double?))
return (T)(object)value;
// string
else if (typeof(T) == typeof(string))
return (T)(object)value.ToString(Culture);
// sbyte
else if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(sbyte?))
return (T)(object)Convert.ToSByte(value);
// byte
else if (typeof(T) == typeof(byte) || typeof(T) == typeof(byte?))
return (T)(object)Convert.ToByte(value);
// short
else if (typeof(T) == typeof(short) || typeof(T) == typeof(short?))
return (T)(object)Convert.ToInt16(value);
// ushort
else if (typeof(T) == typeof(ushort) || typeof(T) == typeof(ushort?))
return (T)(object)Convert.ToUInt16(value);
// int
else if (typeof(T) == typeof(int) || typeof(T) == typeof(int?))
return (T)(object)Convert.ToInt32(value);
// uint
else if (typeof(T) == typeof(uint) || typeof(T) == typeof(uint?))
return (T)(object)Convert.ToUInt32(value);
// long
else if (typeof(T) == typeof(long) || typeof(T) == typeof(long?))
return (T)(object)Convert.ToInt64(value);
// ulong
else if (typeof(T) == typeof(ulong) || typeof(T) == typeof(ulong?))
return (T)(object)Convert.ToUInt64(value);
// float
else if (typeof(T) == typeof(float) || typeof(T) == typeof(float?))
return (T)(object)Convert.ToSingle(value);
// decimal
else if (typeof(T) == typeof(decimal) || typeof(T) == typeof(decimal?))
return (T)(object)Convert.ToDecimal(value);
else
{
UpdateGetError($"Conversion to type {typeof(T)} not implemented");
}
}
catch (Exception e)
{
UpdateGetError("Conversion error: " + e.Message);
return default(T);
}
return default(T);
}
private double OnSet(T arg) private const double MinNormal = 2.2250738585072014E-308d;
{
if (arg == null)
return double.NaN; // <-- this catches all nullable values which are null. no nullchecks necessary below!
try
{
// double
if (typeof(T) == typeof(double))
return (double)(object)arg;
else if (typeof(T) == typeof(double?))
return ((double?)(object)arg).Value;
// string
if (typeof(T) == typeof(string))
return double.Parse((string)(object)arg, NumberStyles.Any, Culture);
// sbyte
if (typeof(T) == typeof(sbyte))
return Convert.ToDouble((sbyte)(object)arg);
if (typeof(T) == typeof(sbyte?))
return Convert.ToDouble(((sbyte?)(object)arg).Value);
// byte
if (typeof(T) == typeof(byte))
return Convert.ToDouble((byte)(object)arg);
if (typeof(T) == typeof(byte?))
return Convert.ToDouble(((byte?)(object)arg).Value);
// short
if (typeof(T) == typeof(short))
return Convert.ToDouble((short)(object)arg);
if (typeof(T) == typeof(short?))
return Convert.ToDouble(((short?)(object)arg).Value);
// ushort
if (typeof(T) == typeof(ushort))
return Convert.ToDouble((ushort)(object)arg);
if (typeof(T) == typeof(ushort?))
return Convert.ToDouble(((ushort?)(object)arg).Value);
// int
else if (typeof(T) == typeof(int))
return Convert.ToDouble((int)(object)arg);
else if (typeof(T) == typeof(int?))
return Convert.ToDouble(((int?)(object)arg).Value);
// uint
else if (typeof(T) == typeof(uint))
return Convert.ToDouble((uint)(object)arg);
else if (typeof(T) == typeof(uint?))
return Convert.ToDouble(((uint?)(object)arg).Value);
// long
else if (typeof(T) == typeof(long))
return Convert.ToDouble((long)(object)arg);
else if (typeof(T) == typeof(long?))
return Convert.ToDouble(((long?)(object)arg).Value);
// ulong
else if (typeof(T) == typeof(ulong))
return Convert.ToDouble((ulong)(object)arg);
else if (typeof(T) == typeof(ulong?))
return Convert.ToDouble(((ulong?)(object)arg).Value);
// float
else if (typeof(T) == typeof(float))
return Convert.ToDouble((float)(object)arg);
else if (typeof(T) == typeof(float?))
return Convert.ToDouble(((float?)(object)arg).Value);
// decimal
else if (typeof(T) == typeof(decimal))
return Convert.ToDouble((decimal)(object)arg);
else if (typeof(T) == typeof(decimal?))
return Convert.ToDouble(((decimal?)(object)arg).Value);
else
{
UpdateSetError("Unable to convert to double from type " + typeof(T).Name);
return double.NaN;
}
}
catch (FormatException e)
{
UpdateSetError("Conversion error: " + e.Message);
return double.NaN;
}
}
public static bool AreEqual(double a, double b, double epsilon = MinNormal)
{
/*
* Copyright (c) Michael Borgwardt
*/
var absA = Math.Abs(a);
var absB = Math.Abs(b);
var diff = Math.Abs(a - b);
/*
* Shortcut, handles infinities
*/
if (a.Equals(b))
{
return true;
}
else if (a == 0 || b == 0 || absA + absB < MinNormal)
{
/*
* a or b is zero or both are extremely close to it
* relative error is less meaningful here
*/
return diff < epsilon * MinNormal;
}
else
{
/*
* Use relative error
*/
return diff / (absA + absB) < epsilon;
}
}
#region --> Floating Point comparison #endregion
}
const double MinNormal = 2.2250738585072014E-308d;
[ExcludeFromCodeCoverage]
public static bool AreEqual(double a, double b, double epsilon = MinNormal) internal static class Number
{ {
// Copyright (c) Michael Borgwardt public static T? To<T>(double d)
var absA = Math.Abs(a); {
var absB = Math.Abs(b); if (typeof(T) == typeof(sbyte) && d >= sbyte.MinValue && sbyte.MaxValue >= d)
var diff = Math.Abs(a - b); return (T)(object)Convert.ToSByte(d);
if (typeof(T) == typeof(byte) && d >= byte.MinValue && byte.MaxValue >= d)
if (a.Equals(b)) return (T)(object)Convert.ToByte(d);
{ // shortcut, handles infinities if (typeof(T) == typeof(short) && d >= short.MinValue && short.MaxValue >= d)
return true; return (T)(object)Convert.ToInt16(d);
} if (typeof(T) == typeof(ushort) && d >= ushort.MinValue && ushort.MaxValue >= d)
else if (a == 0 || b == 0 || absA + absB < MinNormal) return (T)(object)Convert.ToUInt16(d);
{ if (typeof(T) == typeof(int) && d >= int.MinValue && int.MaxValue >= d)
// a or b is zero or both are extremely close to it return (T)(object)Convert.ToInt32(d);
// relative error is less meaningful here if (typeof(T) == typeof(uint) && d >= uint.MinValue && uint.MaxValue >= d)
return diff < (epsilon * MinNormal); return (T)(object)Convert.ToUInt32(d);
} if (typeof(T) == typeof(long) && d >= long.MinValue && long.MaxValue >= d)
else return (T)(object)Convert.ToInt64(d);
{ // use relative error if (typeof(T) == typeof(ulong) && d >= ulong.MinValue && ulong.MaxValue >= d)
return diff / (absA + absB) < epsilon; return (T)(object)Convert.ToUInt64(d);
} if (typeof(T) == typeof(float) && d >= float.MinValue && float.MaxValue >= d)
} return (T)(object)Convert.ToSingle(d);
if (typeof(T) == typeof(double) && d >= double.MinValue && double.MaxValue >= d)
#endregion return (T)(object)Convert.ToDouble(d);
} if (typeof(T) == typeof(decimal) && (decimal)d >= decimal.MinValue && decimal.MaxValue >= (decimal)d)
return (T)(object)Convert.ToDecimal(d);
[ExcludeFromCodeCoverage] if (typeof(T) == typeof(sbyte?) && d >= sbyte.MinValue && sbyte.MaxValue >= d)
internal static class Num return (T)(object)Convert.ToSByte(d);
{ if (typeof(T) == typeof(byte?) && d >= byte.MinValue && byte.MaxValue >= d)
public static T To<T>(double d) return (T)(object)Convert.ToByte(d);
{ if (typeof(T) == typeof(short?) && d >= short.MinValue && short.MaxValue >= d)
if (typeof(T) == typeof(sbyte) && d >= sbyte.MinValue && sbyte.MaxValue >= d) return (T)(object)Convert.ToInt16(d);
return (T)(object)Convert.ToSByte(d); if (typeof(T) == typeof(ushort?) && d >= ushort.MinValue && ushort.MaxValue >= d)
if (typeof(T) == typeof(byte) && d >= byte.MinValue && byte.MaxValue >= d) return (T)(object)Convert.ToUInt16(d);
return (T)(object)Convert.ToByte(d); if (typeof(T) == typeof(int?) && d >= int.MinValue && int.MaxValue >= d)
if (typeof(T) == typeof(short) && d >= short.MinValue && short.MaxValue >= d) return (T)(object)Convert.ToInt32(d);
return (T)(object)Convert.ToInt16(d); if (typeof(T) == typeof(uint?) && d >= uint.MinValue && uint.MaxValue >= d)
if (typeof(T) == typeof(ushort) && d >= ushort.MinValue && ushort.MaxValue >= d) return (T)(object)Convert.ToUInt32(d);
return (T)(object)Convert.ToUInt16(d); if (typeof(T) == typeof(long?) && d >= long.MinValue && long.MaxValue >= d)
if (typeof(T) == typeof(int) && d >= int.MinValue && int.MaxValue >= d) return (T)(object)Convert.ToInt64(d);
return (T)(object)Convert.ToInt32(d); if (typeof(T) == typeof(ulong?) && d >= ulong.MinValue && ulong.MaxValue >= d)
if (typeof(T) == typeof(uint) && d >= uint.MinValue && uint.MaxValue >= d) return (T)(object)Convert.ToUInt64(d);
return (T)(object)Convert.ToUInt32(d); if (typeof(T) == typeof(float?) && d >= float.MinValue && float.MaxValue >= d)
if (typeof(T) == typeof(long) && d >= long.MinValue && long.MaxValue >= d) return (T)(object)Convert.ToSingle(d);
return (T)(object)Convert.ToInt64(d); if (typeof(T) == typeof(double?) && d >= double.MinValue && double.MaxValue >= d)
if (typeof(T) == typeof(ulong) && d >= ulong.MinValue && ulong.MaxValue >= d) return (T)(object)Convert.ToDouble(d);
return (T)(object)Convert.ToUInt64(d); if (typeof(T) == typeof(decimal?) && (decimal)d >= decimal.MinValue && decimal.MaxValue >= (decimal)d)
if (typeof(T) == typeof(float) && d >= float.MinValue && float.MaxValue >= d) return (T)(object)Convert.ToDecimal(d);
return (T)(object)Convert.ToSingle(d);
if (typeof(T) == typeof(double) && d >= double.MinValue && double.MaxValue >= d) return default;
return (T)(object)Convert.ToDouble(d); }
if (typeof(T) == typeof(decimal) && (decimal)d >= decimal.MinValue && decimal.MaxValue >= (decimal)d) public static double From<T>(T v)
return (T)(object)Convert.ToDecimal(d); {
if (typeof(T) == typeof(sbyte?) && d >= sbyte.MinValue && sbyte.MaxValue >= d) if (typeof(T) == typeof(sbyte))
return (T)(object)Convert.ToSByte(d); return Convert.ToDouble((sbyte)(object)v);
if (typeof(T) == typeof(byte?) && d >= byte.MinValue && byte.MaxValue >= d) if (typeof(T) == typeof(byte))
return (T)(object)Convert.ToByte(d); return Convert.ToDouble((byte)(object)v);
if (typeof(T) == typeof(short?) && d >= short.MinValue && short.MaxValue >= d) if (typeof(T) == typeof(short))
return (T)(object)Convert.ToInt16(d); return Convert.ToDouble((short)(object)v);
if (typeof(T) == typeof(ushort?) && d >= ushort.MinValue && ushort.MaxValue >= d) if (typeof(T) == typeof(ushort))
return (T)(object)Convert.ToUInt16(d); return Convert.ToDouble((ushort)(object)v);
if (typeof(T) == typeof(int?) && d >= int.MinValue && int.MaxValue >= d) if (typeof(T) == typeof(int))
return (T)(object)Convert.ToInt32(d); return Convert.ToDouble((int)(object)v);
if (typeof(T) == typeof(uint?) && d >= uint.MinValue && uint.MaxValue >= d) if (typeof(T) == typeof(uint))
return (T)(object)Convert.ToUInt32(d); return Convert.ToDouble((uint)(object)v);
if (typeof(T) == typeof(long?) && d >= long.MinValue && long.MaxValue >= d) if (typeof(T) == typeof(long))
return (T)(object)Convert.ToInt64(d); return Convert.ToDouble((long)(object)v);
if (typeof(T) == typeof(ulong?) && d >= ulong.MinValue && ulong.MaxValue >= d) if (typeof(T) == typeof(ulong))
return (T)(object)Convert.ToUInt64(d); return Convert.ToDouble((ulong)(object)v);
if (typeof(T) == typeof(float?) && d >= float.MinValue && float.MaxValue >= d) if (typeof(T) == typeof(float))
return (T)(object)Convert.ToSingle(d); return Convert.ToDouble((float)(object)v);
if (typeof(T) == typeof(double?) && d >= double.MinValue && double.MaxValue >= d) if (typeof(T) == typeof(double))
return (T)(object)Convert.ToDouble(d); return Convert.ToDouble((double)(object)v);
if (typeof(T) == typeof(decimal?) && (decimal)d >= decimal.MinValue && decimal.MaxValue >= (decimal)d) if (typeof(T) == typeof(decimal))
return (T)(object)Convert.ToDecimal(d); return Convert.ToDouble((decimal)(object)v);
return default; if (typeof(T) == typeof(sbyte?))
} return Convert.ToDouble((sbyte?)(object)v);
public static double From<T>(T v) if (typeof(T) == typeof(byte?))
{ return Convert.ToDouble((byte?)(object)v);
if (typeof(T) == typeof(sbyte)) if (typeof(T) == typeof(short?))
return Convert.ToDouble((sbyte)(object)v); return Convert.ToDouble((short?)(object)v);
if (typeof(T) == typeof(byte)) if (typeof(T) == typeof(ushort?))
return Convert.ToDouble((byte)(object)v); return Convert.ToDouble((ushort?)(object)v);
if (typeof(T) == typeof(short)) if (typeof(T) == typeof(int?))
return Convert.ToDouble((short)(object)v); return Convert.ToDouble((int?)(object)v);
if (typeof(T) == typeof(ushort)) if (typeof(T) == typeof(uint?))
return Convert.ToDouble((ushort)(object)v); return Convert.ToDouble((uint?)(object)v);
if (typeof(T) == typeof(int)) if (typeof(T) == typeof(long?))
return Convert.ToDouble((int)(object)v); return Convert.ToDouble((long?)(object)v);
if (typeof(T) == typeof(uint)) if (typeof(T) == typeof(ulong?))
return Convert.ToDouble((uint)(object)v); return Convert.ToDouble((ulong?)(object)v);
if (typeof(T) == typeof(long)) if (typeof(T) == typeof(float?))
return Convert.ToDouble((long)(object)v); return Convert.ToDouble((float?)(object)v);
if (typeof(T) == typeof(ulong)) if (typeof(T) == typeof(double?))
return Convert.ToDouble((ulong)(object)v); return Convert.ToDouble((double?)(object)v);
if (typeof(T) == typeof(float)) if (typeof(T) == typeof(decimal?))
return Convert.ToDouble((float)(object)v); return Convert.ToDouble((decimal?)(object)v);
if (typeof(T) == typeof(double))
return Convert.ToDouble((double)(object)v); return default;
if (typeof(T) == typeof(decimal)) }
return Convert.ToDouble((decimal)(object)v);
if (typeof(T) == typeof(sbyte?))
return Convert.ToDouble((sbyte?)(object)v);
if (typeof(T) == typeof(byte?))
return Convert.ToDouble((byte?)(object)v);
if (typeof(T) == typeof(short?))
return Convert.ToDouble((short?)(object)v);
if (typeof(T) == typeof(ushort?))
return Convert.ToDouble((ushort?)(object)v);
if (typeof(T) == typeof(int?))
return Convert.ToDouble((int?)(object)v);
if (typeof(T) == typeof(uint?))
return Convert.ToDouble((uint?)(object)v);
if (typeof(T) == typeof(long?))
return Convert.ToDouble((long?)(object)v);
if (typeof(T) == typeof(ulong?))
return Convert.ToDouble((ulong?)(object)v);
if (typeof(T) == typeof(float?))
return Convert.ToDouble((float?)(object)v);
if (typeof(T) == typeof(double?))
return Convert.ToDouble((double?)(object)v);
if (typeof(T) == typeof(decimal?))
return Convert.ToDouble((decimal?)(object)v);
return default;
}
}
} }

View File

@ -1,61 +1,41 @@
namespace Connected using Connected.Components;
namespace Connected.Utilities.BindingConverters;
public class RangeConverter<T> : ToStringConverter<Range<T>>
{ {
public class RangeConverter<T> : Converter<Components.Range<T>> private readonly DefaultConverter<T> _converter;
{
readonly DefaultConverter<T> _converter;
public RangeConverter() public RangeConverter() => _converter = new DefaultConverter<T>();
{
_converter = new DefaultConverter<T>();
SetFunc = OnSet; protected override string? ConvertValue(Range<T>? value) => value is not null
GetFunc = OnGet; ? Join(_converter.Convert(value.Start), _converter.Convert(value.End))
} : string.Empty;
private Components.Range<T> OnGet(string value) protected override Range<T>? ConvertValueBack(string? value)
{ {
if (!Split(value, out var valueStart, out var valueEnd)) return !Split(value, out var valueStart, out var valueEnd)
return null; ? null
: new Range<T>(_converter.ConvertBack(valueStart), _converter.ConvertBack(valueEnd));
}
return new Components.Range<T>(_converter.Get(valueStart), _converter.Get(valueEnd)); public static string Join(string? valueStart, string? valueEnd) => string.IsNullOrEmpty(valueStart) && string.IsNullOrEmpty(valueEnd) ? string.Empty : $"[{valueStart};{valueEnd}]";
}
private string OnSet(Components.Range<T> arg) public static bool Split(string? value, out string? valueStart, out string? valueEnd)
{ {
if (arg == null) valueStart = valueEnd = string.Empty;
return string.Empty;
return Join(_converter.Set(arg.Start), _converter.Set(arg.End)); if (string.IsNullOrEmpty(value) || value[0] != '[' || value[^1] != ']')
} return false;
public static string Join(string valueStart, string valueEnd) var idx = value.IndexOf(';');
{
if (string.IsNullOrEmpty(valueStart) && string.IsNullOrEmpty(valueEnd))
return string.Empty;
return $"[{valueStart};{valueEnd}]"; if (idx < 1)
} return false;
public static bool Split(string value, out string valueStart, out string valueEnd) valueStart = value[1..idx];
{ valueEnd = value[(idx + 1)..^1];
valueStart = valueEnd = string.Empty;
if (string.IsNullOrEmpty(value) || value[0] != '[' || value[^1] != ']') return true;
{ }
return false;
}
var idx = value.IndexOf(';');
if (idx < 1)
{
return false;
}
valueStart = value[1..idx];
valueEnd = value[(idx + 1)..^1];
return true;
}
}
} }