From 4c09d97966140b4cce7e54572daa69b4caa09747 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 03:08:53 +0000 Subject: [PATCH 01/12] build(deps): bump Quartz from 3.13.0 to 3.13.1 Bumps [Quartz](https://github.com/quartznet/quartznet) from 3.13.0 to 3.13.1. - [Release notes](https://github.com/quartznet/quartznet/releases) - [Changelog](https://github.com/quartznet/quartznet/blob/v3.13.1/changelog.md) - [Commits](https://github.com/quartznet/quartznet/compare/v3.13.0...v3.13.1) --- updated-dependencies: - dependency-name: Quartz dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj | 2 +- TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj b/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj index 0eb3832ff..dda5c7e2f 100644 --- a/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj +++ b/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj @@ -12,7 +12,7 @@ - + diff --git a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj index dedf2ea3b..20e6cb8a8 100644 --- a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj +++ b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj @@ -60,7 +60,7 @@ - + From 298eb6d287cab2a0fb356a6924d30a7c342e3c97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Mon, 4 Nov 2024 18:44:40 +0100 Subject: [PATCH 02/12] fix(GenericInput): use decimal for numeric values --- TeslaSolarCharger/Client/Components/GenericInput.razor | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TeslaSolarCharger/Client/Components/GenericInput.razor b/TeslaSolarCharger/Client/Components/GenericInput.razor index e2a899aa6..58eacea49 100644 --- a/TeslaSolarCharger/Client/Components/GenericInput.razor +++ b/TeslaSolarCharger/Client/Components/GenericInput.razor @@ -131,7 +131,8 @@ Variant="Variant.Outlined" Adornment="@Adornment" AdornmentText="@AdornmentText" - Margin="InputMargin"/> + Margin="InputMargin" + InputMode="@(InputMode.@decimal)"/> } else if (IsNormalText()) { From a671fd395169590e52697b87652bd30b6fe38ceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Mon, 4 Nov 2024 18:56:15 +0100 Subject: [PATCH 03/12] feat(GenericInput): use input mode text --- TeslaSolarCharger/Client/Components/GenericInput.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeslaSolarCharger/Client/Components/GenericInput.razor b/TeslaSolarCharger/Client/Components/GenericInput.razor index 58eacea49..84ba92958 100644 --- a/TeslaSolarCharger/Client/Components/GenericInput.razor +++ b/TeslaSolarCharger/Client/Components/GenericInput.razor @@ -132,7 +132,7 @@ Adornment="@Adornment" AdornmentText="@AdornmentText" Margin="InputMargin" - InputMode="@(InputMode.@decimal)"/> + InputMode="@(InputMode.text)"/> } else if (IsNormalText()) { From 3cc7de062f8ba2bfd7257f9d08d084cea1ee3b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Mon, 4 Nov 2024 19:42:56 +0100 Subject: [PATCH 04/12] feat(JAvaScriptWrapper): can detect iOS Device --- .../Client/Components/GenericInput.razor | 12 +++-- .../Helper/Contracts/IJavaScriptWrapper.cs | 9 ++++ .../Client/Helper/JavaScriptWrapper.cs | 54 +++++++++++++++++++ TeslaSolarCharger/Client/Program.cs | 3 ++ TeslaSolarCharger/Client/wwwroot/index.html | 1 + .../wwwroot/js/javaScriptWrapperFunctions.js | 30 +++++++++++ 6 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 TeslaSolarCharger/Client/Helper/Contracts/IJavaScriptWrapper.cs create mode 100644 TeslaSolarCharger/Client/Helper/JavaScriptWrapper.cs create mode 100644 TeslaSolarCharger/Client/wwwroot/js/javaScriptWrapperFunctions.js diff --git a/TeslaSolarCharger/Client/Components/GenericInput.razor b/TeslaSolarCharger/Client/Components/GenericInput.razor index 84ba92958..4010caa2d 100644 --- a/TeslaSolarCharger/Client/Components/GenericInput.razor +++ b/TeslaSolarCharger/Client/Components/GenericInput.razor @@ -2,6 +2,7 @@ @using System.Reflection @using System.ComponentModel.DataAnnotations @using System.ComponentModel +@using Lysando.LabStorageV2.UiHelper.Wrapper.Contracts @using MudExtensions @using TeslaSolarCharger.Shared.Attributes @using TeslaSolarCharger.Shared.Helper.Contracts @@ -11,9 +12,12 @@ @inject IStringHelper StringHelper @* ReSharper disable once InconsistentNaming *@ @inject IJSRuntime JSRuntime +@inject IJavaScriptWrapper JavaScriptWrapper @typeparam T +
@($"iOS Device: {isIosDevice}")
+ @if (!EqualityComparer.Default.Equals(Value, default(T)) || !IsReadOnly) {
@@ -210,7 +214,7 @@ [Parameter] public bool? ShouldBeInErrorState { get; set; } - + [Parameter] public string? ErrorMessage { get; set; } @@ -231,6 +235,8 @@ } } + private bool isIosDevice; + private string _inputId = Guid.NewGuid().ToString(); [Parameter] @@ -616,9 +622,9 @@ } } - protected override void OnInitialized() + protected override async Task OnInitializedAsync() { - + isIosDevice = await JavaScriptWrapper.IsIosDevice(); } private string GetMultiSelectionText(List selectedValues) diff --git a/TeslaSolarCharger/Client/Helper/Contracts/IJavaScriptWrapper.cs b/TeslaSolarCharger/Client/Helper/Contracts/IJavaScriptWrapper.cs new file mode 100644 index 000000000..047b7632c --- /dev/null +++ b/TeslaSolarCharger/Client/Helper/Contracts/IJavaScriptWrapper.cs @@ -0,0 +1,9 @@ +namespace Lysando.LabStorageV2.UiHelper.Wrapper.Contracts; + +public interface IJavaScriptWrapper +{ + Task SetFocusToElementById(string elementId); + Task RemoveFocusFromElementById(string elementId); + Task OpenUrlInNewTab(string url); + Task IsIosDevice(); +} \ No newline at end of file diff --git a/TeslaSolarCharger/Client/Helper/JavaScriptWrapper.cs b/TeslaSolarCharger/Client/Helper/JavaScriptWrapper.cs new file mode 100644 index 000000000..2cd6a7539 --- /dev/null +++ b/TeslaSolarCharger/Client/Helper/JavaScriptWrapper.cs @@ -0,0 +1,54 @@ +using Lysando.LabStorageV2.UiHelper.Wrapper.Contracts; +using Microsoft.JSInterop; + +namespace Lysando.LabStorageV2.UiHelper.Wrapper; + +public class JavaScriptWrapper(IJSRuntime jsRuntime) : IJavaScriptWrapper +{ + /// + /// Sets the focus to an element with a specific ID + /// + /// ID to set the focus on + /// Was the ID set successfully + public async Task SetFocusToElementById(string elementId) + { + try + { + return await jsRuntime.InvokeAsync("setFocus", elementId); + } + catch (Exception) + { + return false; + } + } + + public async Task RemoveFocusFromElementById(string elementId) + { + try + { + return await jsRuntime.InvokeAsync("removeFocus", elementId); + } + catch (Exception) + { + return false; + } + } + + public async Task OpenUrlInNewTab(string url) + { + await jsRuntime.InvokeVoidAsync("openInNewTab", url); + } + + public async Task IsIosDevice() + { + try + { + var device = await jsRuntime.InvokeAsync("detectDevice"); + return device == "iOS"; + } + catch (Exception) + { + return false; + } + } +} \ No newline at end of file diff --git a/TeslaSolarCharger/Client/Program.cs b/TeslaSolarCharger/Client/Program.cs index 8e8d173d0..6c2f2bbcb 100644 --- a/TeslaSolarCharger/Client/Program.cs +++ b/TeslaSolarCharger/Client/Program.cs @@ -1,3 +1,5 @@ +using Lysando.LabStorageV2.UiHelper.Wrapper; +using Lysando.LabStorageV2.UiHelper.Wrapper.Contracts; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using MudBlazor; @@ -21,6 +23,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddSharedDependencies(); diff --git a/TeslaSolarCharger/Client/wwwroot/index.html b/TeslaSolarCharger/Client/wwwroot/index.html index edb707476..3301a3b53 100644 --- a/TeslaSolarCharger/Client/wwwroot/index.html +++ b/TeslaSolarCharger/Client/wwwroot/index.html @@ -37,6 +37,7 @@ + diff --git a/TeslaSolarCharger/Client/wwwroot/js/javaScriptWrapperFunctions.js b/TeslaSolarCharger/Client/wwwroot/js/javaScriptWrapperFunctions.js new file mode 100644 index 000000000..4843b48f8 --- /dev/null +++ b/TeslaSolarCharger/Client/wwwroot/js/javaScriptWrapperFunctions.js @@ -0,0 +1,30 @@ +function setFocus(elementId) { + const element = document.getElementById(elementId); + if (element) { + element.focus(); + return true; + } + return false; +} + +function removeFocus(elementId) { + const element = document.getElementById(elementId); + if (element) { + element.blur(); + return true; + } + return false; +} + +function openInNewTab(url) { + window.open(url, '_blank'); +} + +function detectDevice() { + var ua = navigator.userAgent || navigator.vendor || window.opera; + // iOS detection + if (/iPad|iPhone|iPod/.test(ua) && !window.MSStream) { + return "iOS"; + } + return "Other"; +} From 27ac81035e6a5bafab4863bd7aca3bf3e125b3f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Tue, 5 Nov 2024 21:05:14 +0100 Subject: [PATCH 05/12] feat(GenericInput): use Pattern for ios --- TeslaSolarCharger/Client/Components/GenericInput.razor | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TeslaSolarCharger/Client/Components/GenericInput.razor b/TeslaSolarCharger/Client/Components/GenericInput.razor index 4010caa2d..7cdd8cf85 100644 --- a/TeslaSolarCharger/Client/Components/GenericInput.razor +++ b/TeslaSolarCharger/Client/Components/GenericInput.razor @@ -136,7 +136,8 @@ Adornment="@Adornment" AdornmentText="@AdornmentText" Margin="InputMargin" - InputMode="@(InputMode.text)"/> + Pattern="-?[0-9]+" + InputMode="@(InputMode.numeric)"/> } else if (IsNormalText()) { From 8121cf4c7afc3255bc35cb9b6d9d150fda0ebdb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Tue, 5 Nov 2024 21:28:02 +0100 Subject: [PATCH 06/12] feat(GenericInput): use empty pattern --- TeslaSolarCharger/Client/Components/GenericInput.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeslaSolarCharger/Client/Components/GenericInput.razor b/TeslaSolarCharger/Client/Components/GenericInput.razor index 7cdd8cf85..9e98938e5 100644 --- a/TeslaSolarCharger/Client/Components/GenericInput.razor +++ b/TeslaSolarCharger/Client/Components/GenericInput.razor @@ -136,7 +136,7 @@ Adornment="@Adornment" AdornmentText="@AdornmentText" Margin="InputMargin" - Pattern="-?[0-9]+" + Pattern="" InputMode="@(InputMode.numeric)"/> } else if (IsNormalText()) From d1a1f991de0a123a41f42c4a16e8c6339883b7b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Wed, 6 Nov 2024 21:14:02 +0100 Subject: [PATCH 07/12] feat(GenericInput): update input --- .../Client/Components/GenericInput.razor | 426 +++++++++++++----- 1 file changed, 310 insertions(+), 116 deletions(-) diff --git a/TeslaSolarCharger/Client/Components/GenericInput.razor b/TeslaSolarCharger/Client/Components/GenericInput.razor index 9e98938e5..940bb435c 100644 --- a/TeslaSolarCharger/Client/Components/GenericInput.razor +++ b/TeslaSolarCharger/Client/Components/GenericInput.razor @@ -10,33 +10,36 @@ @inject IConstants Constants @inject IStringHelper StringHelper -@* ReSharper disable once InconsistentNaming *@ -@inject IJSRuntime JSRuntime -@inject IJavaScriptWrapper JavaScriptWrapper @typeparam T -
@($"iOS Device: {isIosDevice}")
- @if (!EqualityComparer.Default.Equals(Value, default(T)) || !IsReadOnly) {
-
+
@if (typeof(T) == typeof(DateTime?)) { - + Margin="InputMargin" + Clearable="@(Clearable && !IsReadOnly && !IsDisabled)" + @attributes="@(ShouldBeInErrorState.HasValue ? new Dictionary + { + { "Error", ShouldBeInErrorState.Value }, + { "ErrorText", ErrorMessage ?? string.Empty }, + } :new())" /> } else if (DropDownOptions != default && typeof(T) == typeof(int?)) { - + ItemCollection="@DropDownOptions.Keys.Select(k => (int?)k).ToList()" + Immediate="@ImmediateValueUpdate" + Virtualize="true" + Clearable="@(Clearable && !IsReadOnly && !IsDisabled)" + @attributes="@(ShouldBeInErrorState.HasValue ? new Dictionary + { + { "Error", ShouldBeInErrorState.Value }, + { "ErrorText", ErrorMessage ?? string.Empty }, + } :new())"> } else if (DropDownOptions != default && typeof(T) == typeof(HashSet)) { - + ItemCollection="@DropDownOptions.Keys.ToList()" + Immediate="@ImmediateValueUpdate" + Virtualize="true" + Clearable="@(Clearable && !IsReadOnly && !IsDisabled)" + @attributes="@(ShouldBeInErrorState.HasValue ? new Dictionary + { + { "Error", ShouldBeInErrorState.Value }, + { "ErrorText", ErrorMessage ?? string.Empty }, + } :new())"> + + + } + else if (LongIdDropDownOptions != default && typeof(T) == typeof(long?)) + { + + + } + else if (LongIdDropDownOptions != default && typeof(T) == typeof(HashSet)) + { + + } else if (StringIdDropDownOptions != default && typeof(T) == typeof(string)) { @* Even though compiler says ?? string.Empty is not needed in ToStringFunc, it is needed. *@ - + ToStringFunc="@(new Func(x => StringIdDropDownOptions.TryGetValue(x, out var value) ? value : string.Empty))" + ItemCollection="@StringIdDropDownOptions.Keys.ToList()" + Immediate="@ImmediateValueUpdate" + Virtualize="true" + Clearable="@(Clearable && !IsReadOnly && !IsDisabled)" + @attributes="@(ShouldBeInErrorState.HasValue ? new Dictionary + { + { "Error", ShouldBeInErrorState.Value }, + { "ErrorText", ErrorMessage ?? string.Empty }, + } :new())"> } else if (StringIdDropDownOptions != default && typeof(T) == typeof(HashSet)) { //ToDo: For label is missing @* Even though compiler says ?? string.Empty is not needed in ToStringFunc, it is needed. *@ - + MultiSelectionTextFunc="@(GetMultiSelectionText)" + ToStringFunc="@(new Func(x => StringIdDropDownOptions.TryGetValue(x, out var value) ? value : string.Empty))" + ItemCollection="@StringIdDropDownOptions.Keys.ToList()" + Immediate="@ImmediateValueUpdate" + Virtualize="true" + Clearable="@(Clearable && !IsReadOnly && !IsDisabled)" + @attributes="@(ShouldBeInErrorState.HasValue ? new Dictionary + { + { "Error", ShouldBeInErrorState.Value }, + { "ErrorText", ErrorMessage ?? string.Empty }, + } :new())"> } else if (typeof(T) == typeof(short) - || typeof(T) == typeof(short?) - || typeof(T) == typeof(ushort) - || typeof(T) == typeof(ushort?) - || typeof(T) == typeof(int) - || typeof(T) == typeof(int?) - || typeof(T) == typeof(uint) - || typeof(T) == typeof(uint?) - || typeof(T) == typeof(long) - || typeof(T) == typeof(long?) - || typeof(T) == typeof(ulong) - || typeof(T) == typeof(ulong?) - || typeof(T) == typeof(float) - || typeof(T) == typeof(float?) - || typeof(T) == typeof(double) - || typeof(T) == typeof(double?) - || typeof(T) == typeof(decimal) - || typeof(T) == typeof(decimal?)) + || typeof(T) == typeof(short?) + || typeof(T) == typeof(ushort) + || typeof(T) == typeof(ushort?) + || typeof(T) == typeof(int) + || typeof(T) == typeof(int?) + || typeof(T) == typeof(uint) + || typeof(T) == typeof(uint?) + || typeof(T) == typeof(long) + || typeof(T) == typeof(long?) + || typeof(T) == typeof(ulong) + || typeof(T) == typeof(ulong?) + || typeof(T) == typeof(float) + || typeof(T) == typeof(float?) + || typeof(T) == typeof(double) + || typeof(T) == typeof(double?) + || typeof(T) == typeof(decimal) + || typeof(T) == typeof(decimal?)) { - + Immediate="@ImmediateValueUpdate" + Clearable="@(Clearable && !IsReadOnly && !IsDisabled)" + @attributes="@(ShouldBeInErrorState.HasValue ? new Dictionary + { + { "Error", ShouldBeInErrorState.Value }, + { "ErrorText", ErrorMessage ?? string.Empty }, + } :new())" /> } else if (IsNormalText()) { if (IsPassword) { - + Margin="InputMargin" + Immediate="@ImmediateValueUpdate" + Clearable="@(Clearable && !IsReadOnly && !IsDisabled)" + @attributes="@(ShouldBeInErrorState.HasValue ? new Dictionary + { + { "Error", ShouldBeInErrorState.Value }, + { "ErrorText", ErrorMessage ?? string.Empty }, + } :new())" /> } else { - + Margin="InputMargin" + Immediate="@ImmediateValueUpdate" + Clearable="@(Clearable && !IsReadOnly && !IsDisabled)" + @attributes="@(ShouldBeInErrorState.HasValue ? new Dictionary + { + { "Error", ShouldBeInErrorState.Value }, + { "ErrorText", ErrorMessage ?? string.Empty }, + } :new())" /> } } else if (typeof(T) == typeof(bool) - || typeof(T) == typeof(bool?)) + || typeof(T) == typeof(bool?)) { - + Dense="InputMargin == Margin.Dense" + @attributes="@(ShouldBeInErrorState.HasValue ? new Dictionary + { + { "Error", ShouldBeInErrorState.Value }, + { "ErrorText", ErrorMessage ?? string.Empty }, + } :new())"> } else @@ -185,7 +307,7 @@ throw new ArgumentOutOfRangeException(); }
- @if(!string.IsNullOrEmpty(HelperText)) + @if (!string.IsNullOrEmpty(HelperText)) {
@HelperText @@ -194,7 +316,7 @@
@if (!string.IsNullOrEmpty(PostfixButtonStartIcon)) { -
+
Constants.DefaultMargin; private Margin InputMargin => Constants.InputMargin; @@ -236,14 +367,8 @@ } } - private bool isIosDevice; - - private string _inputId = Guid.NewGuid().ToString(); - - [Parameter] - public int TextAreaMinimumLines { get; set; } = 1; - private int TextAreaLines { get; set; } = 1; + private bool? _isReadOnlyParameter; private Expression>? ForDateTime { @@ -269,49 +394,9 @@ private int MultiSelectValue { get; set; } = 0; - private string MultiSelectStringValue { get; set; } = string.Empty; + private long MultiSelectLongValue { get; set; } = 0; - private async Task GetVisibleLineBreaksCount() - { - return await JSRuntime.InvokeAsync("countVisibleLineBreaks", _inputId); - } - - private async Task IsTextCutOff() - { - return await JSRuntime.InvokeAsync("isInputTextCutOff", _inputId); - } - - private async Task SetFocusToCurrentInput() - { - await JSRuntime.InvokeVoidAsync("setFocusToInput", _inputId); - } - - private async Task UpdateLineCount(bool shouldSetFocus) - { - var textFieldReplacedByTextarea = false; - if (IsNormalText() && !IsPassword) - { - if (TextAreaLines < 2) - { - if (!await IsTextCutOff()) - { - return; - } - textFieldReplacedByTextarea = true; - } - var lineCount = await GetVisibleLineBreaksCount(); - TextAreaLines = lineCount > TextAreaMinimumLines ? lineCount : TextAreaMinimumLines; - this.StateHasChanged(); - if (shouldSetFocus && textFieldReplacedByTextarea) - { - await SetFocusToCurrentInput(); - } - if (textFieldReplacedByTextarea) - { - await UpdateLineCount(false); - } - } - } + private string MultiSelectStringValue { get; set; } = string.Empty; private Expression>> ForMultiSelectValues { @@ -336,7 +421,7 @@ } throw new InvalidCastException(); } - set => throw new NotImplementedException($"{nameof(ForMultiSelectValues)} can not be set."); + set => throw new NotImplementedException($"{nameof(ForNullableString)} can not be set."); } private Expression> ForNullableInt @@ -349,12 +434,28 @@ } throw new InvalidCastException(); } - set => throw new NotImplementedException($"{nameof(ForMultiSelectValues)} can not be set."); + set => throw new NotImplementedException($"{nameof(ForNullableInt)} can not be set."); + } + + private Expression> ForNullableLong + { + get + { + if (typeof(T) == typeof(long?) && For != null) + { + return (Expression>)(object)For; + } + throw new InvalidCastException(); + } + set => throw new NotImplementedException($"{nameof(ForNullableLong)} can not be set."); } [Parameter] public Dictionary? DropDownOptions { get; set; } + [Parameter] + public Dictionary? LongIdDropDownOptions { get; set; } + [Parameter] public Dictionary? StringIdDropDownOptions { get; set; } @@ -392,17 +493,40 @@ public bool? IsRequiredParameter { get; set; } [Parameter] - public bool? IsReadOnlyParameter { get; set; } + public bool? IsReadOnlyParameter + { + get => _isReadOnlyParameter; + set + { + if (_isReadOnlyParameter != value && _componentRenderedCounter > 0) + { + _isReadOnlyParameter = value; + OnAfterRender(true); + } + else + { + _isReadOnlyParameter = value; + } + } + } [Parameter] public string? HelperText { get; set; } + [Parameter] + public bool ImmediateValueUpdate { get; set; } + + [Parameter] + public bool Clearable { get; set; } + private string? AdornmentText { get; set; } private bool IsRequired { get; set; } private bool IsDisabled { get; set; } private bool IsReadOnly { get; set; } private Adornment Adornment { get; set; } + private int _componentRenderedCounter = 0; + private IEnumerable SelectedMultiSelectValues { get @@ -419,6 +543,22 @@ set => Value = (T)value; } + private IEnumerable SelectedMultiSelectLongValues + { + get + { + if (Value is HashSet selectedValues) + { + return selectedValues; + } + else + { + throw new NotImplementedException(); + } + } + set => Value = (T)value; + } + private IEnumerable SelectedMultiSelectStringValues { get @@ -489,6 +629,33 @@ } } + private long? NullableLongValue + { + get + { + if (typeof(T) == typeof(long?) && Value != null) + { + return (long?)(object)Value; + } + if (Value == null) + { + return null; + } + throw new NotImplementedException(); + } + set + { + if (value != default) + { + Value = (T)(object)value; + } + else + { + Value = default; + } + } + } + private DateTime? DateValue { get @@ -547,17 +714,13 @@ } } - protected override async Task OnAfterRenderAsync(bool firstRender) + protected override void OnAfterRender(bool firstRender) { - if (firstRender && IsNormalText() && !IsPassword) - { - await UpdateLineCount(false); - } + _componentRenderedCounter++; } protected override void OnParametersSet() { - TextAreaLines = TextAreaMinimumLines; if (For == default) { throw new ArgumentException("Expression body is null"); @@ -595,7 +758,7 @@ var postfixAttribute = propertyInfo.GetCustomAttributes(false).SingleOrDefault(); var prefixAttribute = propertyInfo.GetCustomAttributes(false).SingleOrDefault(); - + if (postfixAttribute != default) { @@ -620,30 +783,55 @@ else { Adornment = Adornment.None; - } + } + StateHasChanged(); } - protected override async Task OnInitializedAsync() + private string GetIntMultiSelectionText(List selectedValues) { - isIosDevice = await JavaScriptWrapper.IsIosDevice(); + if (DisplayMultiSelectValues && selectedValues.Count > 0) + { + if (DropDownOptions != null) + { + try + { + return string.Join("; ", selectedValues.Select(x => DropDownOptions[Convert.ToInt32(x)])); + } + catch (Exception) + { + // ignored + } + } + } + + return GetMultiSelectionTextWithoutValues(selectedValues.Count, DropDownOptions?.Count); } - private string GetMultiSelectionText(List selectedValues) + private string GetLongMultiSelectionText(List selectedValues) { if (DisplayMultiSelectValues && selectedValues.Count > 0) { - if (DropDownOptions != null) + if (LongIdDropDownOptions != null) { try { - return string.Join("; ", selectedValues.Select(x => DropDownOptions[Convert.ToInt32(x)])); + return string.Join("; ", selectedValues.Select(x => LongIdDropDownOptions[Convert.ToInt64(x)])); } catch (Exception) { // ignored } } - else if(StringIdDropDownOptions != null) + } + + return GetMultiSelectionTextWithoutValues(selectedValues.Count, LongIdDropDownOptions?.Count); + } + + private string GetMultiSelectionText(List selectedValues) + { + if (DisplayMultiSelectValues && selectedValues.Count > 0) + { + if (StringIdDropDownOptions != null) { try { @@ -655,7 +843,13 @@ } } } - return $"{selectedValues.Count} item{(selectedValues.Count == 1 ? " has" : "s have")} been selected"; + return GetMultiSelectionTextWithoutValues(selectedValues.Count, StringIdDropDownOptions?.Count); + } + + private string GetMultiSelectionTextWithoutValues(int selectedValues, int? availableOptions) + { + var ofText = availableOptions == null ? string.Empty : $"/{availableOptions}"; + return $"{selectedValues}{ofText} item{(selectedValues == 1 ? " has" : "s have")} been selected"; } private void InvokeOnButtonClicked() From 88289bb23cba6117af22fee8292fec8165240f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Wed, 6 Nov 2024 21:35:31 +0100 Subject: [PATCH 08/12] Fix(BaseConfigurationRazor): fix id --- TeslaSolarCharger/Client/Pages/BaseConfiguration.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor b/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor index 858bb319c..d1d712833 100644 --- a/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor +++ b/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor @@ -1,4 +1,4 @@ -@page "/BaseConfiguration" +@page "/BaseConfiguration" @using System.Globalization @using TeslaSolarCharger.Client.Helper.Contracts @using TeslaSolarCharger.Shared.Dtos.BaseConfiguration @@ -241,7 +241,7 @@ else UnitText="min" HelpText=""> - +
From 83d483e8819dd6f006b1af7f01e83e796ef5fd16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Fri, 8 Nov 2024 22:45:49 +0100 Subject: [PATCH 09/12] feat(PowerBufferComponent): use optionally displayed power buffer component --- .../Components/PowerBufferComponent.razor | 55 +++++++++++++++++++ .../Components/PowerFlowComponent.razor | 23 -------- .../Client/Pages/BaseConfiguration.razor | 4 +- TeslaSolarCharger/Client/Pages/Index.razor | 1 + .../BaseConfigurationController.cs | 3 + .../Shared/Contracts/IConfigurationWrapper.cs | 1 + .../BaseConfigurationBase.cs | 2 + .../Shared/Wrappers/ConfigurationWrapper.cs | 5 ++ 8 files changed, 70 insertions(+), 24 deletions(-) create mode 100644 TeslaSolarCharger/Client/Components/PowerBufferComponent.razor diff --git a/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor b/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor new file mode 100644 index 000000000..12b957940 --- /dev/null +++ b/TeslaSolarCharger/Client/Components/PowerBufferComponent.razor @@ -0,0 +1,55 @@ +@using TeslaSolarCharger.Shared.Dtos +@using TeslaSolarCharger.Shared.Dtos.IndexRazor.PvValues + +@inject HttpClient HttpClient +@inject ISnackbar Snackbar + + +@if(_displayValue && _pvValues != default) +{ +
+ +
+} + + +@code { + private DtoPvValues? _pvValues; + private bool _displayValue; + + protected override async Task OnInitializedAsync() + { + var result = await HttpClient.GetFromJsonAsync>("api/BaseConfiguration/AllowPowerBufferChangeOnHome").ConfigureAwait(false); + if(result == default) + { + return; + } + _displayValue = result.Value; + if(_displayValue) + { + _pvValues = await HttpClient.GetFromJsonAsync("api/Index/GetPvValues").ConfigureAwait(false); + } + + } + + private async Task UpdatePowerBuffer() + { + if(_pvValues == default) + { + return; + } + var response = await HttpClient.GetAsync($"api/BaseConfiguration/UpdatePowerBuffer?powerBuffer={_pvValues.PowerBuffer ?? 0}").ConfigureAwait(false); + if (response.IsSuccessStatusCode) + { + Snackbar.Add("Power Buffer updated", Severity.Success); + } + else + { + Snackbar.Add("Failed to update Power Buffer", Severity.Error); + } + + } +} \ No newline at end of file diff --git a/TeslaSolarCharger/Client/Components/PowerFlowComponent.razor b/TeslaSolarCharger/Client/Components/PowerFlowComponent.razor index a9d77f22e..678d44645 100644 --- a/TeslaSolarCharger/Client/Components/PowerFlowComponent.razor +++ b/TeslaSolarCharger/Client/Components/PowerFlowComponent.razor @@ -5,7 +5,6 @@ @inject HttpClient HttpClient @inject IConstants Constants -@inject ISnackbar Snackbar @implements IDisposable @if (_pvValues != default) @@ -166,14 +165,6 @@
- if (_pvValues.PowerBuffer != default && _pvValues.PowerBuffer != 0) - { -
- -
- } } @@ -641,18 +632,4 @@ return homePower; } - private async Task UpdatePowerBuffer(int? newValue) - { - var response = await HttpClient.GetAsync($"api/BaseConfiguration/UpdatePowerBuffer?powerBuffer={newValue ?? 0}").ConfigureAwait(false); - if (response.IsSuccessStatusCode) - { - Snackbar.Add("Power Buffer updated", Severity.Success); - } - else - { - Snackbar.Add("Failed to update Power Buffer", Severity.Error); - } - - } - } \ No newline at end of file diff --git a/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor b/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor index d1d712833..8e19fbd9c 100644 --- a/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor +++ b/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor @@ -153,11 +153,13 @@ else + HelpText="Set values higher than 0 to always have some overage (power to grid). Set values lower than 0 to always consume some power from the grid."> + + + @if (_carBaseStates == null || _carBaseSettings == null) diff --git a/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs b/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs index 961809373..119d579ee 100644 --- a/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs +++ b/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs @@ -15,6 +15,9 @@ public class BaseConfigurationController( [HttpGet] public Task GetBaseConfiguration() => configurationWrapper.GetBaseConfigurationAsync(); + [HttpGet] + public DtoValue AllowPowerBufferChangeOnHome() => new(configurationWrapper.AllowPowerBufferChangeOnHome()); + [HttpPut] public Task UpdateBaseConfiguration([FromBody] DtoBaseConfiguration baseConfiguration) => service.UpdateBaseConfigurationAsync(baseConfiguration); diff --git a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs index eb8bd93ac..21aa9eda2 100644 --- a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs +++ b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs @@ -107,4 +107,5 @@ public interface IConfigurationWrapper TimeSpan BleUsageStopAfterError(); bool UseTeslaMateIntegration(); string FleetTelemetryApiUrl(); + bool AllowPowerBufferChangeOnHome(); } diff --git a/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationBase.cs b/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationBase.cs index 51ede9127..9871ef8aa 100644 --- a/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationBase.cs +++ b/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationBase.cs @@ -45,6 +45,8 @@ public class BaseConfigurationBase public int MinutesUntilSwitchOff { get; set; } = 5; [Required] public int PowerBuffer { get; set; } = 0; + [HelperText("If enabled the configured power buffer is displayed on the home screen including the option to directly change it.")] + public bool AllowPowerBufferChangeOnHome { get; set; } public string? CurrentPowerToGridJsonPattern { get; set; } public decimal CurrentPowerToGridCorrectionFactor { get; set; } = 1; public string? CurrentInverterPowerJsonPattern { get; set; } diff --git a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs index 059f57ab2..fd85b529b 100644 --- a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs +++ b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs @@ -212,6 +212,11 @@ public string FleetTelemetryApiUrl() return value; } + public bool AllowPowerBufferChangeOnHome() + { + return GetBaseConfiguration().AllowPowerBufferChangeOnHome; + } + public bool IsDevelopmentEnvironment() { var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); From 210ce44914ea8c0bf79fd5b11d86ad69658eaeee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Fri, 8 Nov 2024 22:47:19 +0100 Subject: [PATCH 10/12] fix(GenericInput): null ref exception --- TeslaSolarCharger/Client/Components/GenericInput.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TeslaSolarCharger/Client/Components/GenericInput.razor b/TeslaSolarCharger/Client/Components/GenericInput.razor index 940bb435c..bee8085e7 100644 --- a/TeslaSolarCharger/Client/Components/GenericInput.razor +++ b/TeslaSolarCharger/Client/Components/GenericInput.razor @@ -161,7 +161,7 @@ Variant="Variant.Outlined" Margin="InputMargin" SearchBoxAutoFocus="true" - ToStringFunc="@(new Func(x => StringIdDropDownOptions.TryGetValue(x, out var value) ? value : string.Empty))" + ToStringFunc="@(new Func(x => StringIdDropDownOptions.TryGetValue(x ?? string.Empty, out var value) ? value : string.Empty))" ItemCollection="@StringIdDropDownOptions.Keys.ToList()" Immediate="@ImmediateValueUpdate" Virtualize="true" @@ -191,7 +191,7 @@ Margin="InputMargin" SearchBoxAutoFocus="true" MultiSelectionTextFunc="@(GetMultiSelectionText)" - ToStringFunc="@(new Func(x => StringIdDropDownOptions.TryGetValue(x, out var value) ? value : string.Empty))" + ToStringFunc="@(new Func(x => StringIdDropDownOptions.TryGetValue(x ?? string.Empty, out var value) ? value : string.Empty))" ItemCollection="@StringIdDropDownOptions.Keys.ToList()" Immediate="@ImmediateValueUpdate" Virtualize="true" From 85fe6152443f91cc4eb4dc454e023abcf140677c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Fri, 8 Nov 2024 22:52:57 +0100 Subject: [PATCH 11/12] feat(BaseConfigurationRazor): clarify power buffer display on home screen --- .../Shared/Dtos/BaseConfiguration/BaseConfigurationBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationBase.cs b/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationBase.cs index 9871ef8aa..b54c76ea4 100644 --- a/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationBase.cs +++ b/TeslaSolarCharger/Shared/Dtos/BaseConfiguration/BaseConfigurationBase.cs @@ -45,7 +45,7 @@ public class BaseConfigurationBase public int MinutesUntilSwitchOff { get; set; } = 5; [Required] public int PowerBuffer { get; set; } = 0; - [HelperText("If enabled the configured power buffer is displayed on the home screen including the option to directly change it.")] + [HelperText("If enabled, the configured power buffer is displayed on the home screen, including the option to directly change it. Note: The values you set on the home screen will be overwritten on every TSC restart. To set a permanent power buffer, use the field above.")] public bool AllowPowerBufferChangeOnHome { get; set; } public string? CurrentPowerToGridJsonPattern { get; set; } public decimal CurrentPowerToGridCorrectionFactor { get; set; } = 1; From f647aa71d13faaebb18162e59ea3c252f9aaf709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Fri, 8 Nov 2024 23:06:47 +0100 Subject: [PATCH 12/12] feat(GenericInput): use InputMode Text on Numericfield on iOS --- .../Client/Components/GenericInput.razor | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/TeslaSolarCharger/Client/Components/GenericInput.razor b/TeslaSolarCharger/Client/Components/GenericInput.razor index bee8085e7..918a42ac8 100644 --- a/TeslaSolarCharger/Client/Components/GenericInput.razor +++ b/TeslaSolarCharger/Client/Components/GenericInput.razor @@ -10,6 +10,7 @@ @inject IConstants Constants @inject IStringHelper StringHelper +@inject IJavaScriptWrapper JavaScriptWrapper @typeparam T @@ -235,11 +236,7 @@ Margin="InputMargin" Immediate="@ImmediateValueUpdate" Clearable="@(Clearable && !IsReadOnly && !IsDisabled)" - @attributes="@(ShouldBeInErrorState.HasValue ? new Dictionary - { - { "Error", ShouldBeInErrorState.Value }, - { "ErrorText", ErrorMessage ?? string.Empty }, - } :new())" /> + @attributes="@(NumericFieldAttributes())" /> } else if (IsNormalText()) { @@ -369,6 +366,25 @@ private bool? _isReadOnlyParameter; + private bool _isIosDevice; + + private IDictionary NumericFieldAttributes() + { + var attributes = new Dictionary(); + + if (ShouldBeInErrorState.HasValue) + { + attributes["Error"] = ShouldBeInErrorState.Value; + attributes["ErrorText"] = ErrorMessage ?? string.Empty; + } + + if (_isIosDevice) + { + attributes["InputMode"] = InputMode.text; + } + + return attributes; + } private Expression>? ForDateTime { @@ -714,6 +730,18 @@ } } + protected override async Task OnInitializedAsync() + { + try + { + _isIosDevice = await JavaScriptWrapper.IsIosDevice(); + } + catch (Exception) + { + _isIosDevice = false; + } + } + protected override void OnAfterRender(bool firstRender) { _componentRenderedCounter++;