Improved the dialogs for local data sources and the Batch Processing assistant (#1096)

Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
Paul KoudelkaandThorsten Sommer authored and GitHub committed 2026-10-27 19:57:04 +01:00
1 parent 149395c1a0
commit f7e6ceb915
45 files changed
+2514 -361

No files matched your search

@@ -0,0 +1,3 @@
@inherits ConfigurationText
<FileExclusionEditor Text="@this.EditingText" TextChanged="@this.InternalUpdate" Label="@this.OptionDescription" Disabled="@this.IsDisabled" ShowHelp="@this.ShowHelp" ShowReset="@(this.ResetValue is not null)" ResetValue="@(this.ResetValue?.Invoke() ?? FileExclusions.DEFAULT_PATTERNS)" ResetButtonText="@this.ResetButtonText" ResetRequested="@this.ApplyTextAsync"/>
@@ -0,0 +1,17 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
/// <summary>
/// Reuses ConfigurationText's persistence, validation, reset, and organization locks with the
/// shared exclusion editor. The editor supplies its own outlined fields and heading.
/// </summary>
public partial class ConfigurationFileExclusions : ConfigurationText
{
[Parameter]
public bool ShowHelp { get; set; } = true;
protected override Variant Variant => Variant.Text;
protected override string Label => string.Empty;
}
@@ -4,7 +4,7 @@
{
<MudTextField
T="string"
Text="@this.Text()"
Text="@(this.ExpandToContent ? this.EditingText : this.Text())"
TextChanged="@this.InternalUpdate"
Disabled="@this.IsDisabled"
Adornment="@this.IconAdornment"
@@ -14,6 +14,7 @@
Lines="@this.NumLines"
AutoGrow="@this.AutoGrow"
MaxLines="@this.GetMaxLines"
Class="@this.InputClass"
Immediate="@true"
Validation="@this.Validation"
Underline="false"
@@ -25,20 +26,26 @@ else
<div>
<MudTextField
T="string"
Text="@this.Text()"
Text="@(this.ExpandToContent ? this.EditingText : this.Text())"
TextChanged="@this.InternalUpdate"
Disabled="@this.IsDisabled"
Adornment="@this.IconAdornment"
AdornmentIcon="@this.Icon"
Adornment="@(this.ResetButtonIconOnly ? Adornment.End : this.IconAdornment)"
AdornmentIcon="@(this.ResetButtonIconOnly ? Icons.Material.Filled.Restore : this.Icon)"
OnAdornmentClick="@this.ResetAdornmentClick"
AdornmentAriaLabel="@(this.ResetButtonIconOnly ? this.ResetButtonText : null)"
AdornmentColor="@this.IconColor"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
Lines="@this.NumLines"
AutoGrow="@this.AutoGrow"
MaxLines="@this.GetMaxLines"
Class="@this.InputClass"
Immediate="@true"
Validation="@this.Validation"
Underline="false"
/>
<RestoreDefaultButton Text="@this.ResetButtonText" Disabled="@this.IsDisabled" OnClick="@this.ResetTextAsync"/>
@if (!this.ResetButtonIconOnly)
{
<RestoreDefaultButton Text="@this.ResetButtonText" Disabled="@this.IsDisabled" OnClick="@this.ResetTextAsync"/>
}
</div>
}
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Timer = System.Timers.Timer;
@@ -24,6 +25,24 @@ public partial class ConfigurationText : ConfigurationBaseCore
[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>
@@ -54,6 +73,12 @@ public partial class ConfigurationText : ConfigurationBaseCore
[Parameter]
public string ResetButtonText { get; set; } = string.Empty;
/// <summary>
/// Uses an accessible reset icon inside the field.
/// </summary>
[Parameter]
public bool ResetButtonIconOnly { get; set; }
/// <summary>
/// Validates the configured text before it is stored.
/// </summary>
@@ -61,10 +86,14 @@ public partial class ConfigurationText : ConfigurationBaseCore
public Func<string, string?>? Validation { get; set; }
private string internalText = string.Empty;
private string? lastSourceText;
private readonly Timer timer = new(TimeSpan.FromMilliseconds(500))
{
AutoReset = false
};
protected string EditingText => this.internalText;
#region Overrides of ConfigurationBase
@@ -72,6 +101,8 @@ public partial class ConfigurationText : ConfigurationBaseCore
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;
@@ -83,15 +114,24 @@ public partial class ConfigurationText : ConfigurationBaseCore
protected override async Task OnParametersSetAsync()
{
this.internalText = this.Text();
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;
}
await base.OnParametersSetAsync();
}
#endregion
private bool AutoGrow => this.NumLines > 1;
private bool AutoGrow => this.ExpandToContent || this.NumLines > 1;
private int GetMaxLines => this.AutoGrow ? this.MaxLines : 1;
private int GetMaxLines => this.ExpandToContent ? FilePatternSelection.MAX_FIELD_LINES : this.AutoGrow ? this.MaxLines : 1;
private string InputClass => this.ExpandToContent ? "pattern-text-field" : string.Empty;
//
// MudBlazor centers the icon vertically, so in a field of several lines it floats in the middle
@@ -99,29 +139,50 @@ public partial class ConfigurationText : ConfigurationBaseCore
//
private Adornment IconAdornment => this.NumLines == 1 ? Adornment.Start : Adornment.None;
private void InternalUpdate(string text)
protected void InternalUpdate(string text)
{
if (this.IsDisabled)
return;
this.timer.Stop();
this.internalText = text;
this.timer.Start();
}
private async Task ResetTextAsync()
private EventCallback<MouseEventArgs> ResetAdornmentClick => this.ResetButtonIconOnly
? EventCallback.Factory.Create<MouseEventArgs>(this, this.ResetTextAsync)
: default;
protected async Task ResetTextAsync()
{
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;
this.timer.Stop();
this.internalText = this.ResetValue();
this.internalText = text;
await this.OptionChanged(this.internalText);
}
private async Task OptionChanged(string updatedText)
protected async Task OptionChanged(string updatedText)
{
if (this.Validation?.Invoke(updatedText) is not null)
if (this.IsDisabled || this.Validation?.Invoke(updatedText) is not null)
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();
}
@@ -0,0 +1,25 @@
@inherits MSGComponentBase
<div class="file-exclusion-editor mb-3">
<MudStack Row="@true" AlignItems="AlignItems.Center" Spacing="1" Class="mb-1">
<MudText Typo="Typo.subtitle2" Class="flex-grow-1">@this.EditorLabel</MudText>
@if (this.ShowHelp)
{
<MudTooltip Text="@T("Exclusion help and examples")" RootStyle="display: inline-flex;">
<MudIconButton Icon="@Icons.Material.Outlined.HelpOutline" Color="@(this.showHelp ? Color.Info : Color.Default)" OnClick="@this.ToggleHelp" aria-label="@T("Exclusion help and examples")" aria-expanded="@(this.showHelp ? "true" : "false")"/>
</MudTooltip>
}
</MudStack>
<MudStack Spacing="1">
<MudTextField T="string" InputId="@this.folderInputId" Text="@this.state.FolderText" TextChanged="@this.ChangeFoldersAsync" Immediate="@true" Disabled="@this.Disabled" Label="@T("Folders")" Lines="1" AutoGrow="@true" MaxLines="@FilePatternSelection.MAX_FIELD_LINES" Class="pattern-text-field" Variant="Variant.Outlined" Margin="Margin.Dense" Validation="@FileExclusions.ValidateFolders" UserAttributes="@INPUT_ATTRIBUTES" Placeholder="node_modules/;bin/;cmake-build-*/" Adornment="@(this.ShowReset ? Adornment.End : Adornment.None)" AdornmentIcon="@Icons.Material.Filled.Restore" OnAdornmentClick="@(() => this.RestoreDefaultsAsync(true))" AdornmentAriaLabel="@this.FolderRestoreLabel"/>
<MudTextField T="string" InputId="@this.fileInputId" Text="@this.state.FileText" TextChanged="@this.ChangeFilesAsync" Immediate="@true" Disabled="@this.Disabled" Label="@T("Files")" Lines="1" AutoGrow="@true" MaxLines="@FilePatternSelection.MAX_FIELD_LINES" Class="pattern-text-field" Variant="Variant.Outlined" Margin="Margin.Dense" Validation="@FileExclusions.ValidateFiles" UserAttributes="@INPUT_ATTRIBUTES" Placeholder="*.min.js;*.webm;draft-*" Adornment="@(this.ShowReset ? Adornment.End : Adornment.None)" AdornmentIcon="@Icons.Material.Filled.Restore" OnAdornmentClick="@(() => this.RestoreDefaultsAsync(false))" AdornmentAriaLabel="@this.FileRestoreLabel"/>
</MudStack>
<MudText Typo="Typo.caption" Class="mud-text-secondary mt-1">@T("These files and folders are left out, even when a file pattern selects them. Separate entries with a semicolon or a line break. End an entry with a slash to exclude every folder of that name, e.g., node_modules/.")</MudText>
<MudCollapse Expanded="@(this.ShowHelp && this.showHelp)">
<div class="mt-2">
<FilePatternHelp ShowInclusionExamples="@false" ShowExclusionExamples="@true"/>
</div>
</MudCollapse>
</div>
@@ -0,0 +1,115 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
/// <summary>
/// A compact, two-group editor for the exclusion string shared by folder-based features.
/// </summary>
public partial class FileExclusionEditor : MSGComponentBase
{
private static readonly Dictionary<string, object?> INPUT_ATTRIBUTES = new()
{
["spellcheck"] = false,
["autocorrect"] = "off",
["autocapitalize"] = "off",
};
[Parameter]
public string? Text { get; set; } = string.Empty;
[Parameter]
public EventCallback<string> TextChanged { get; set; }
[Parameter]
public string Label { get; set; } = string.Empty;
[Parameter]
public bool Disabled { get; set; }
[Parameter]
public bool ShowReset { get; set; } = true;
[Parameter]
public bool ShowHelp { get; set; } = true;
[Parameter]
public string ResetValue { get; set; } = FileExclusions.DEFAULT_PATTERNS;
[Parameter]
public string ResetButtonText { get; set; } = string.Empty;
/// <summary>
/// Immediately persists the combined value after restoring just one of its groups.
/// Ordinary edits continue through TextChanged.
/// </summary>
[Parameter]
public EventCallback<string> ResetRequested { get; set; }
private readonly FileExclusionEditorState state = new();
private readonly string folderInputId = $"exclusion-folders-{Guid.NewGuid():N}";
private readonly string fileInputId = $"exclusion-files-{Guid.NewGuid():N}";
private bool showHelp;
/// <summary>
/// False while a field holds an invalid draft. The editor keeps such a draft to itself, so the
/// bound text is still the last valid one and cannot tell.
/// </summary>
public bool IsValid => this.state.IsValid;
private string EditorLabel => string.IsNullOrWhiteSpace(this.Label) ? T("Excluded files and folders") : this.Label;
private string RestoreLabel => string.IsNullOrWhiteSpace(this.ResetButtonText) ? T("Restore default") : this.ResetButtonText;
private string FolderRestoreLabel => $"{this.RestoreLabel}: {T("Folders")}";
private string FileRestoreLabel => $"{this.RestoreLabel}: {T("Files")}";
protected override void OnParametersSet()
{
this.state.Receive(this.Text, discardDraft: this.Disabled);
base.OnParametersSet();
}
protected Task ChangeFoldersAsync(string text) => this.ChangePatternsAsync(text, folders: true);
protected Task ChangeFilesAsync(string text) => this.ChangePatternsAsync(text, folders: false);
private async Task ChangePatternsAsync(string text, bool folders)
{
if (this.Disabled)
return;
var previousText = this.state.Text;
var wasValid = this.state.IsValid;
if (folders)
this.state.EditFolders(text);
else
this.state.EditFiles(text);
// Moving a rule to the correct field can keep the combined text identical. It must still
// publish the now-valid draft, because the previous, invalid arrangement was not sent.
if (this.state.IsValid && (!wasValid || !string.Equals(previousText, this.state.Text, StringComparison.Ordinal)))
await this.TextChanged.InvokeAsync(this.state.Text);
}
protected async Task RestoreDefaultsAsync(bool folders)
{
if (this.Disabled || !this.ShowReset)
return;
if (folders)
this.state.RestoreFolders(this.ResetValue);
else
this.state.RestoreFiles(this.ResetValue);
if (!this.state.IsValid)
return;
if (this.ResetRequested.HasDelegate)
await this.ResetRequested.InvokeAsync(this.state.Text);
else
await this.TextChanged.InvokeAsync(this.state.Text);
}
private void ToggleHelp() => this.showHelp = !this.showHelp;
}
@@ -0,0 +1,97 @@
namespace AIStudio.Components;
/// <summary>
/// Presents the existing exclusion string as two editable groups without losing the other group
/// or letting delayed parameter echoes overwrite newer edits.
/// </summary>
internal sealed class FileExclusionEditorState
{
private string? receivedText;
private readonly HashSet<string> pendingValues = new(StringComparer.Ordinal);
public string Text { get; private set; } = string.Empty;
public string FolderText { get; private set; } = string.Empty;
public string FileText { get; private set; } = string.Empty;
public bool IsValid => FileExclusions.IsValidFolders(this.FolderText) && FileExclusions.IsValidFiles(this.FileText);
/// <summary>
/// Takes over the text of the parent, which renders again after every published edit and passes
/// that edit back, possibly only after the user typed on. Such an echo must not replace the newer
/// draft; any other text does, e.g., a change by a configuration plugin.
/// </summary>
/// <param name="text">The text the parent passes in.</param>
/// <param name="discardDraft">Whether to take over the text in any case, e.g., while the editor is disabled.</param>
/// <returns>Whether the text replaced the draft.</returns>
public bool Receive(string? text, bool discardDraft = false)
{
text ??= string.Empty;
if (discardDraft)
{
var changed = !string.Equals(text, this.Text, StringComparison.Ordinal);
this.receivedText = text;
this.Reset(text);
return changed;
}
// The parent passes the same text with every render; only a different one counts:
if (string.Equals(text, this.receivedText, StringComparison.Ordinal))
return false;
this.receivedText = text;
// The parent caught up with the draft. Its renders arrive in order, so no older echo follows:
if (string.Equals(text, this.Text, StringComparison.Ordinal))
{
this.pendingValues.Clear();
return false;
}
// The echo of an earlier edit, which a later one replaced already:
if (this.pendingValues.Remove(text))
return false;
this.Reset(text);
return true;
}
public void Reset(string text)
{
this.pendingValues.Clear();
this.Text = text;
this.Regroup();
}
public void EditFolders(string? text)
{
this.FolderText = text ?? string.Empty;
this.UpdateText();
}
public void EditFiles(string? text)
{
this.FileText = text ?? string.Empty;
this.UpdateText();
}
public void RestoreFolders(string defaults) => this.EditFolders(FileExclusions.Group(defaults).Folders);
public void RestoreFiles(string defaults) => this.EditFiles(FileExclusions.Group(defaults).Files);
private void Regroup()
{
var groups = FileExclusions.Group(this.Text);
this.FolderText = groups.Folders;
this.FileText = groups.Files;
}
private void UpdateText()
{
this.Text = string.Join(FileExclusions.ENTRY_SEPARATOR, FileExclusions.Parse(this.FolderText)
.Concat(FileExclusions.Parse(this.FileText))
.Distinct(StringComparer.OrdinalIgnoreCase));
this.pendingValues.Add(this.Text);
}
}
@@ -0,0 +1,23 @@
@inherits MSGComponentBase
<MudPaper Outlined="@true" Class="pa-3" Elevation="0">
<MudText Typo="Typo.subtitle2">@T("Pattern examples")</MudText>
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-2">@T("Separate patterns with semicolons. Exclusions take priority over included files.")</MudText>
<MudSimpleTable Dense="@true" Elevation="0" aria-label="@T("Pattern examples")">
<tbody>
<tr><td><code>*.pdf;*.docx</code></td><td>@T("PDF and Word files")</td></tr>
@if (this.ShowInclusionExamples)
{
<tr><td><code>:documents:</code></td><td>@T("Include all documents and readable text files")</td></tr>
<tr><td><code>*</code></td><td>@T("Include all files, even audio and video")</td></tr>
<tr><td><code>:text:</code></td><td>@T("Include readable text files, even those without an extension")</td></tr>
}
<tr><td><code>draft-*</code></td><td>@T("File names starting with draft-")</td></tr>
@if (this.ShowExclusionExamples)
{
<tr><td><code>*build/</code></td><td>@T("Exclude folders ending in build, such as build or app-build")</td></tr>
}
</tbody>
</MudSimpleTable>
<MudText Typo="Typo.caption" Class="mud-text-secondary mt-2">@T("Patterns match file names regardless of letter case. Folder paths are not supported.")</MudText>
</MudPaper>
@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
/// <summary>
/// Examples of file patterns, shared by the fields for files to include and for exclusions, so both
/// explain the patterns in the same words.
/// </summary>
public partial class FilePatternHelp : MSGComponentBase
{
/// <summary>
/// Shows the selectors and wildcards which only the files to include accept, such as :text:.
/// </summary>
[Parameter]
public bool ShowInclusionExamples { get; set; } = true;
/// <summary>
/// Shows an entry which excludes folders.
/// </summary>
[Parameter]
public bool ShowExclusionExamples { get; set; }
}
@@ -0,0 +1,27 @@
@inherits MSGComponentBase
<MudStack Spacing="1" Class="mb-3">
<div class="pattern-row">
<div class="pattern-row-main">
@this.AllowedContent
</div>
<MudTooltip Text="@T("Pattern help and examples")" RootStyle="display: inline-flex;">
<MudIconButton Icon="@Icons.Material.Outlined.HelpOutline" Color="@(this.showHelp ? Color.Info : Color.Default)" OnClick="@this.ToggleHelp" aria-label="@T("Pattern help and examples")" aria-expanded="@(this.showHelp ? "true" : "false")"/>
</MudTooltip>
</div>
@if (this.HasExclusions)
{
<div class="pattern-row">
<div class="pattern-row-main mt-3">
@this.ExcludedContent
</div>
</div>
}
<MudCollapse Expanded="@this.showHelp">
<div class="pattern-row">
<div class="pattern-row-main">
<FilePatternHelp ShowExclusionExamples="@this.HasExclusions"/>
</div>
</div>
</MudCollapse>
</MudStack>
@@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
/// <summary>
/// A file-pattern field with shared, collapsible help and optional, always-visible exclusions.
/// Callers supply their existing fields, preserving validation and managed-configuration locks.
/// </summary>
public partial class FilePatternSelection : MSGComponentBase
{
/// <summary>
/// How many lines a field for file patterns or exclusions grows to. A longer list scrolls.
/// </summary>
public const int MAX_FIELD_LINES = 6;
[Parameter, EditorRequired]
public RenderFragment AllowedContent { get; set; } = null!;
/// <summary>
/// When supplied, shows the existing exclusion field below the inclusion field.
/// Both groups stay visible and use the same help control.
/// </summary>
[Parameter]
public RenderFragment? ExcludedContent { get; set; }
private bool HasExclusions => this.ExcludedContent is not null;
private bool showHelp;
private void ToggleHelp() => this.showHelp = !this.showHelp;
}
@@ -1,10 +1,13 @@
@inherits MSGComponentBase
<MudStack Row="false" Class="mb-3 pa-3 border-dashed border rounded-lg">
<MudJustifiedText Typo="Typo.body1">
@this.DetermineIntroText()
</MudJustifiedText>
<MudButton Color="@this.DetermineButtonColor()" Variant="Variant.Filled" OnClick="() => this.ShowPandocDialogAsync()">
@this.DetermineButtonText()
</MudButton>
</MudStack>
@if (ShouldShowInstallationPrompt(this.pandocInstallation))
{
<MudStack Row="false" Class="mb-3 pa-3 border-dashed border rounded-lg">
<MudJustifiedText Typo="Typo.body2">
@this.PromptText
</MudJustifiedText>
<MudButton Color="@this.ButtonColor" Variant="Variant.Filled" OnClick="() => this.ShowPandocDialogAsync()">
@this.ButtonText
</MudButton>
</MudStack>
}
@@ -7,6 +7,11 @@ using DialogOptions = AIStudio.Dialogs.DialogOptions;
namespace AIStudio.Components;
/// <summary>
/// Asks to install or update Pandoc where a feature needs it for some files only, such as the HTML files
/// of a local data source. It stays hidden while the check runs and once Pandoc is ready, so it does not
/// distract from the rest of the dialog.
/// </summary>
public partial class ManagePandocDependency : MSGComponentBase
{
[Parameter]
@@ -18,7 +23,10 @@ public partial class ManagePandocDependency : MSGComponentBase
[Inject]
private RustService RustService { get; init; } = null!;
private PandocInstallation pandocInstallation;
// Null means the silent availability check has not completed yet.
private PandocInstallation? pandocInstallation;
internal static bool ShouldShowInstallationPrompt(PandocInstallation? installation) => installation is { IsAvailable: false };
#region Overrides of MSGComponentBase
@@ -30,60 +38,20 @@ public partial class ManagePandocDependency : MSGComponentBase
#endregion
private Color DetermineButtonColor()
{
if (this.pandocInstallation == default)
return Color.Default;
switch (this.pandocInstallation)
{
case { CheckWasSuccessful: true, IsAvailable: true }:
return Color.Default;
case { CheckWasSuccessful: true, IsAvailable: false }:
return Color.Warning;
case { CheckWasSuccessful: false }:
return Color.Error;
}
}
private string DetermineButtonText()
{
if(this.pandocInstallation == default)
return T("Please wait while we check the availability of Pandoc.");
switch (this.pandocInstallation)
{
case { CheckWasSuccessful: true, IsAvailable: true }:
return T("Check your Pandoc installation");
case { CheckWasSuccessful: true, IsAvailable: false }:
return T("Update Pandoc");
case { CheckWasSuccessful: false }:
return T("Install Pandoc");
}
}
//
// The prompt shows up only for an installation which is not available, so it is either outdated,
// when the check found one, or missing:
//
private bool IsOutdated => this.pandocInstallation is { CheckWasSuccessful: true };
private Color ButtonColor => this.IsOutdated ? Color.Warning : Color.Error;
private string ButtonText => this.IsOutdated ? T("Update Pandoc") : T("Install Pandoc");
private string PromptText => this.IsOutdated
? $"{this.IntroText} {T("Your Pandoc installation is outdated. Please update it to the latest version to ensure compatibility with all features.")}"
: $"{this.IntroText} {T("Pandoc is not installed or not available. Please install it to use the features that require Pandoc.")}";
private string DetermineIntroText()
{
if (this.pandocInstallation == default)
return $"{this.IntroText} {T("Please wait while we check the availability of Pandoc.")}";
switch (this.pandocInstallation)
{
case { CheckWasSuccessful: true, IsAvailable: true }:
return $"{this.IntroText} {T("Your Pandoc installation meets the requirements.")}";
case { CheckWasSuccessful: true, IsAvailable: false }:
return $"{this.IntroText} {T("Your Pandoc installation is outdated. Please update it to the latest version to ensure compatibility with all features.")}";
case { CheckWasSuccessful: false }:
return $"{this.IntroText} {T("Pandoc is not installed or not available. Please install it to use the features that require Pandoc.")}";
}
}
private async Task ShowPandocDialogAsync()
{
var dialogReference = await this.DialogService.ShowAsync<PandocDialog>(T("Pandoc Installation"), DialogOptions.FULLSCREEN);
@@ -7,9 +7,11 @@
Text="@this.Value"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Lines="3"
Lines="@this.Lines"
MaxLines="@this.MaxLines"
AutoGrow="@true"
Adornment="@(string.IsNullOrEmpty(this.Icon) ? Adornment.None : Adornment.Start)"
AdornmentIcon="@this.Icon"
Style="@this.GetColor()"
UserAttributes="@USER_INPUT_ATTRIBUTES" />
@@ -9,6 +9,9 @@ public partial class TextInfoLines : MSGComponentBase
[Parameter]
public string Label { get; set; } = string.Empty;
[Parameter]
public string Icon { get; set; } = string.Empty;
[Parameter]
public string Value { get; set; } = string.Empty;
@@ -18,6 +21,9 @@ public partial class TextInfoLines : MSGComponentBase
[Parameter]
public int MaxLines { get; set; } = 30;
[Parameter]
public int Lines { get; set; } = 3;
[Parameter]
public bool ShowingCopyButton { get; set; } = true;