Files
AI-Studio/app/MindWork AI Studio/Components/ConfigurationText.razor.cs
T

210 lines
6.3 KiB
C#
Raw Normal View History

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
2024-09-15 21:14:56 +02:00
using Timer = System.Timers.Timer;
2024-08-21 08:30:01 +02:00
namespace AIStudio.Components;
public partial class ConfigurationText : ConfigurationBaseCore
{
/// <summary>
/// The text used for the textfield.
/// </summary>
[Parameter]
public Func<string> Text { get; set; } = () => string.Empty;
/// <summary>
2024-09-15 21:14:56 +02:00
/// An action which is called when the text was changed.
/// </summary>
[Parameter]
public Action<string> TextUpdate { get; set; } = _ => { };
/// <summary>
/// The icon to display next to the textfield, when it has a single line.
/// </summary>
[Parameter]
public string Icon { get; set; } = Icons.Material.Filled.Info;
/// <summary>
/// Removes the default bottom margin when the field is part of a compact group.
/// </summary>
[Parameter]
public bool Compact { get; set; }
/// <summary>
/// Wraps and grows with its text, including while the field is not focused, up to the lines of a
/// field for file patterns.
/// </summary>
/// <remarks>
/// Such a field shows the draft rather than the stored value. Otherwise, a reset which leaves the
/// stored value as it was would not reach the field, e.g., restoring the default while the field
/// shows a draft which the validation rejected.
/// </remarks>
[Parameter]
public bool ExpandToContent { get; set; }
/// <summary>
/// The color of the icon to use.
/// </summary>
[Parameter]
public Color IconColor { get; set; } = Color.Default;
2024-09-15 21:14:56 +02:00
/// <summary>
/// How many lines should the textfield have?
/// </summary>
[Parameter]
public int NumLines { get; set; } = 1;
/// <summary>
/// What is the maximum number of lines?
/// </summary>
[Parameter]
public int MaxLines { get; set; } = 12;
2026-08-11 20:14:21 +02:00
/// <summary>
/// When configured, displays a button which restores this value.
/// </summary>
[Parameter]
public Func<string>? ResetValue { get; set; }
/// <summary>
/// The text displayed on the optional reset button.
/// </summary>
[Parameter]
public string ResetButtonText { get; set; } = string.Empty;
/// <summary>
/// Uses an accessible reset icon inside the field.
/// </summary>
[Parameter]
public bool ResetButtonIconOnly { get; set; }
2026-08-11 20:14:21 +02:00
/// <summary>
/// Validates the configured text before it is stored.
/// </summary>
[Parameter]
public Func<string, string?>? Validation { get; set; }
2024-09-15 21:14:56 +02:00
private string internalText = string.Empty;
private string? lastSourceText;
private readonly Timer timer = new(TimeSpan.FromMilliseconds(500))
2024-09-15 21:14:56 +02:00
{
AutoReset = false
};
protected string EditingText => this.internalText;
#region Overrides of ConfigurationBase
/// <inheritdoc />
protected override bool Stretch => true;
protected override Variant Variant => Variant.Outlined;
protected override string MarginClass => this.Compact ? "mb-0" : base.MarginClass;
protected override string Label => this.OptionDescription;
2024-09-15 21:14:56 +02:00
protected override async Task OnInitializedAsync()
{
this.timer.Elapsed += (_, _) => this.InvokeAsync(async () => await this.OptionChanged(this.internalText)).Observe($"{nameof(ConfigurationText)}: applying the changed text");
2024-09-15 21:14:56 +02:00
await base.OnInitializedAsync();
}
protected override async Task OnParametersSetAsync()
{
var sourceText = this.Text();
// Re-rendering the parent must not replace a draft which is waiting for its debounce.
// A genuinely changed setting, e.g. from a configuration plugin, still replaces it.
if (this.IsDisabled || !string.Equals(sourceText, this.lastSourceText, StringComparison.Ordinal))
{
this.internalText = sourceText;
this.lastSourceText = sourceText;
}
2024-09-15 21:14:56 +02:00
await base.OnParametersSetAsync();
}
#endregion
private bool AutoGrow => this.ExpandToContent || this.NumLines > 1;
2026-10-31 10:48:20 +01:00
private InputSizing Sizing => this.AutoGrow ? InputSizing.Auto : InputSizing.Fixed;
private int GetMaxLines => this.ExpandToContent ? FilePatternSelection.MAX_FIELD_LINES : this.AutoGrow ? this.MaxLines : 1;
private string InputClass => this.ExpandToContent ? "pattern-text-field" : string.Empty;
2024-09-15 21:14:56 +02:00
//
// MudBlazor centers the icon vertically, so in a field of several lines it floats in the middle
// of the text. Such a field goes without:
//
private Adornment IconAdornment => this.NumLines == 1 ? Adornment.Start : Adornment.None;
2026-10-31 10:48:20 +01:00
protected void InternalUpdate(string? text)
2024-09-15 21:14:56 +02:00
{
if (this.IsDisabled)
return;
2024-09-15 21:14:56 +02:00
this.timer.Stop();
2026-10-31 10:48:20 +01:00
this.internalText = text ?? string.Empty;
2024-09-15 21:14:56 +02:00
this.timer.Start();
}
2026-08-11 20:14:21 +02:00
private EventCallback<MouseEventArgs> ResetAdornmentClick => this.ResetButtonIconOnly
? EventCallback.Factory.Create<MouseEventArgs>(this, this.ResetTextAsync)
: default;
protected async Task ResetTextAsync()
2026-08-11 20:14:21 +02:00
{
if (this.ResetValue is null || this.IsDisabled)
return;
await this.ApplyTextAsync(this.ResetValue());
}
/// <summary>
/// Applies an explicit value immediately, including a reset of part of a compound setting.
/// </summary>
protected async Task ApplyTextAsync(string text)
{
if (this.IsDisabled)
return;
2026-08-11 20:14:21 +02:00
this.timer.Stop();
this.internalText = text;
2026-08-11 20:14:21 +02:00
await this.OptionChanged(this.internalText);
}
protected async Task OptionChanged(string updatedText)
{
if (this.IsDisabled || this.Validation?.Invoke(updatedText) is not null)
2026-08-11 20:14:21 +02:00
return;
this.TextUpdate(updatedText);
// A save may finish after another keystroke. Remember the value it applied before
// awaiting storage, so its configuration-change notification cannot erase that keystroke.
this.lastSourceText = this.Text();
await this.SettingsManager.StoreSettings();
await this.InformAboutChange();
}
2025-08-15 21:16:51 +02:00
#region Overrides of MSGComponentBase
protected override void DisposeResources()
{
try
{
this.timer.Stop();
this.timer.Dispose();
}
catch
{
// ignore
}
base.DisposeResources();
}
#endregion
}