The dialog for file attachments will only open once.

This commit is contained in:
hart_s3 2026-04-14 14:35:17 +02:00
parent 09258c7548
commit 3d03d36f0c
8 changed files with 146 additions and 78 deletions

View File

@ -21,6 +21,7 @@
<MudTooltip Text="@T("Number of attachments")" Placement="Placement.Bottom">
<MudBadge Content="@this.Content.FileAttachments.Count" Color="Color.Primary" Overlap="true" BadgeClass="sources-card-header">
<MudIconButton Icon="@Icons.Material.Filled.AttachFile"
Disabled="@this.isOpeningAttachmentsDialog"
OnClick="@this.OpenAttachmentsDialog"/>
</MudBadge>
</MudTooltip>
@ -149,4 +150,4 @@
}
}
</MudCardContent>
</MudCard>
</MudCard>

View File

@ -102,6 +102,7 @@ public partial class ContentBlockComponent : MSGComponentBase, IAsyncDisposable
private ElementReference mathContentContainer;
private string lastMathRenderSignature = string.Empty;
private bool hasActiveMathContainer;
private bool isOpeningAttachmentsDialog;
private bool isDisposed;
#region Overrides of ComponentBase
@ -600,8 +601,20 @@ public partial class ContentBlockComponent : MSGComponentBase, IAsyncDisposable
private async Task OpenAttachmentsDialog()
{
var result = await ReviewAttachmentsDialog.OpenDialogAsync(this.DialogService, this.Content.FileAttachments.ToHashSet());
this.Content.FileAttachments = result.ToList();
if (this.isOpeningAttachmentsDialog)
return;
this.isOpeningAttachmentsDialog = true;
try
{
var result = await ReviewAttachmentsDialog.OpenDialogAsync(this.DialogService, this.Content.FileAttachments.ToHashSet());
this.Content.FileAttachments = result.ToList();
}
finally
{
this.isOpeningAttachmentsDialog = false;
}
}
public async ValueTask DisposeAsync()
@ -613,4 +626,4 @@ public partial class ContentBlockComponent : MSGComponentBase, IAsyncDisposable
await this.DisposeMathContainerIfNeededAsync();
this.Dispose();
}
}
}

View File

@ -6,11 +6,11 @@
@if (this.isDraggingOver)
{
<MudBadge
Content="@this.DocumentPaths.Count"
Color="Color.Primary"
Overlap="true"
Class="cursor-pointer"
OnClick="@this.OpenAttachmentsDialog">
Content="@this.DocumentPaths.Count"
Color="Color.Primary"
Overlap="true"
Class="cursor-pointer"
OnClick="@this.OpenAttachmentsDialog">
<MudLink OnClick="@this.AddFilesManually" Style="text-decoration: none;">
<MudTextField T="string"
Text="@DROP_FILES_HERE_TEXT"
@ -19,6 +19,7 @@
Typo="Typo.body2"
Variant="Variant.Outlined"
ReadOnly="true"
Disabled="@(this.isOpeningFileDialog || this.isOpeningAttachmentsDialog)"
/>
</MudLink>
</MudBadge>
@ -35,6 +36,7 @@
<MudIconButton
Icon="@Icons.Material.Filled.AttachFile"
Color="Color.Default"
Disabled="@(this.isOpeningFileDialog || this.isOpeningAttachmentsDialog)"
OnClick="@this.AddFilesManually"/>
</MudBadge>
</MudTooltip>
@ -45,6 +47,7 @@
<MudIconButton
Icon="@Icons.Material.Filled.AttachFile"
Color="Color.Default"
Disabled="@(this.isOpeningFileDialog || this.isOpeningAttachmentsDialog)"
OnClick="@this.AddFilesManually"/>
</MudTooltip>
}
@ -60,6 +63,7 @@ else
Variant="Variant.Filled"
StartIcon="@Icons.Material.Filled.Add"
Color="Color.Primary"
Disabled="@(this.isOpeningFileDialog || this.isOpeningAttachmentsDialog)"
OnClick="@(() => this.AddFilesManually())"
Style="vertical-align: top; margin-top: -2px;"
Size="Size.Small">

View File

@ -78,6 +78,8 @@ public partial class AttachDocuments : MSGComponentBase
private uint numDropAreasAboveThis;
private bool isComponentHovered;
private bool isDraggingOver;
private bool isOpeningFileDialog;
private bool isOpeningAttachmentsDialog;
#region Overrides of MSGComponentBase
@ -202,40 +204,64 @@ public partial class AttachDocuments : MSGComponentBase
private async Task AddFilesManually()
{
if (this.isOpeningFileDialog)
return;
this.isOpeningFileDialog = true;
try
{
// Ensure that Pandoc is installed and ready:
var pandocState = await this.PandocAvailabilityService.EnsureAvailabilityAsync(
showSuccessMessage: false,
showDialog: true);
var pandocState = await this.PandocAvailabilityService.EnsureAvailabilityAsync(
showSuccessMessage: false,
showDialog: true);
// If Pandoc is not available (user cancelled installation), abort file selection:
if (!pandocState.IsAvailable)
{
this.Logger.LogWarning("The user cancelled the Pandoc installation or Pandoc is not available. Aborting file selection.");
return;
if (!pandocState.IsAvailable)
{
this.Logger.LogWarning("The user cancelled the Pandoc installation or Pandoc is not available. Aborting file selection.");
return;
}
var selectFiles = await this.RustService.SelectFiles(T("Select files to attach"));
if (selectFiles.UserCancelled)
return;
foreach (var selectedFilePath in selectFiles.SelectedFilePaths)
{
if (!File.Exists(selectedFilePath))
continue;
if (!await FileExtensionValidation.IsExtensionValidWithNotifyAsync(FileExtensionValidation.UseCase.ATTACHING_CONTENT, selectedFilePath, this.ValidateMediaFileTypes, this.Provider))
continue;
this.DocumentPaths.Add(FileAttachment.FromPath(selectedFilePath));
}
await this.DocumentPathsChanged.InvokeAsync(this.DocumentPaths);
await this.OnChange(this.DocumentPaths);
}
var selectFiles = await this.RustService.SelectFiles(T("Select files to attach"));
if (selectFiles.UserCancelled)
return;
foreach (var selectedFilePath in selectFiles.SelectedFilePaths)
finally
{
if (!File.Exists(selectedFilePath))
continue;
if (!await FileExtensionValidation.IsExtensionValidWithNotifyAsync(FileExtensionValidation.UseCase.ATTACHING_CONTENT, selectedFilePath, this.ValidateMediaFileTypes, this.Provider))
continue;
this.DocumentPaths.Add(FileAttachment.FromPath(selectedFilePath));
this.isOpeningFileDialog = false;
}
await this.DocumentPathsChanged.InvokeAsync(this.DocumentPaths);
await this.OnChange(this.DocumentPaths);
}
private async Task OpenAttachmentsDialog()
{
this.DocumentPaths = await ReviewAttachmentsDialog.OpenDialogAsync(this.DialogService, this.DocumentPaths);
if (this.isOpeningAttachmentsDialog)
return;
this.isOpeningAttachmentsDialog = true;
try
{
this.DocumentPaths = await ReviewAttachmentsDialog.OpenDialogAsync(this.DialogService, this.DocumentPaths);
}
finally
{
this.isOpeningAttachmentsDialog = false;
}
}
private async Task ClearAllFiles()

View File

@ -1,5 +1,5 @@
@inherits MSGComponentBase
<MudButton StartIcon="@Icons.Material.Filled.Description" OnClick="@(async () => await this.SelectFile())" Variant="Variant.Filled" Class="mb-3" Disabled="@this.Disabled">
<MudButton StartIcon="@Icons.Material.Filled.Description" OnClick="@(async () => await this.SelectFile())" Variant="Variant.Filled" Class="mb-3" Disabled="@(this.Disabled || this.isSelectingFile)">
@if (string.IsNullOrWhiteSpace(this.Text))
{
@T("Use file content as input")
@ -8,4 +8,4 @@
{
@this.Text
}
</MudButton>
</MudButton>

View File

@ -30,53 +30,64 @@ public partial class ReadFileContent : MSGComponentBase
[Inject]
private PandocAvailabilityService PandocAvailabilityService { get; init; } = null!;
private bool isSelectingFile;
private async Task SelectFile()
{
if (this.Disabled)
if (this.Disabled || this.isSelectingFile)
return;
// Ensure that Pandoc is installed and ready:
var pandocState = await this.PandocAvailabilityService.EnsureAvailabilityAsync(
showSuccessMessage: false,
showDialog: true);
// Check if Pandoc is available after the check / installation:
if (!pandocState.IsAvailable)
{
this.Logger.LogWarning("The user cancelled the Pandoc installation or Pandoc is not available. Aborting file selection.");
return;
}
var selectedFile = await this.RustService.SelectFile(T("Select file to read its content"));
if (selectedFile.UserCancelled)
{
this.Logger.LogInformation("User cancelled the file selection");
return;
}
if(!File.Exists(selectedFile.SelectedFilePath))
{
this.Logger.LogWarning("Selected file does not exist: '{FilePath}'", selectedFile.SelectedFilePath);
return;
}
if (!await FileExtensionValidation.IsExtensionValidWithNotifyAsync(FileExtensionValidation.UseCase.DIRECTLY_LOADING_CONTENT, selectedFile.SelectedFilePath))
{
this.Logger.LogWarning("User attempted to load unsupported file: {FilePath}", selectedFile.SelectedFilePath);
return;
}
this.isSelectingFile = true;
try
{
var fileContent = await UserFile.LoadFileData(selectedFile.SelectedFilePath, this.RustService, this.DialogService);
await this.FileContentChanged.InvokeAsync(fileContent);
this.Logger.LogInformation("Successfully loaded file content: {FilePath}", selectedFile.SelectedFilePath);
// Ensure that Pandoc is installed and ready:
var pandocState = await this.PandocAvailabilityService.EnsureAvailabilityAsync(
showSuccessMessage: false,
showDialog: true);
// Check if Pandoc is available after the check / installation:
if (!pandocState.IsAvailable)
{
this.Logger.LogWarning("The user cancelled the Pandoc installation or Pandoc is not available. Aborting file selection.");
return;
}
var selectedFile = await this.RustService.SelectFile(T("Select file to read its content"));
if (selectedFile.UserCancelled)
{
this.Logger.LogInformation("User cancelled the file selection");
return;
}
if(!File.Exists(selectedFile.SelectedFilePath))
{
this.Logger.LogWarning("Selected file does not exist: '{FilePath}'", selectedFile.SelectedFilePath);
return;
}
if (!await FileExtensionValidation.IsExtensionValidWithNotifyAsync(FileExtensionValidation.UseCase.DIRECTLY_LOADING_CONTENT, selectedFile.SelectedFilePath))
{
this.Logger.LogWarning("User attempted to load unsupported file: {FilePath}", selectedFile.SelectedFilePath);
return;
}
try
{
var fileContent = await UserFile.LoadFileData(selectedFile.SelectedFilePath, this.RustService, this.DialogService);
await this.FileContentChanged.InvokeAsync(fileContent);
this.Logger.LogInformation("Successfully loaded file content: {FilePath}", selectedFile.SelectedFilePath);
}
catch (Exception ex)
{
this.Logger.LogError(ex, "Failed to load file content: {FilePath}", selectedFile.SelectedFilePath);
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Error, T("Failed to load file content")));
}
}
catch (Exception ex)
finally
{
this.Logger.LogError(ex, "Failed to load file content: {FilePath}", selectedFile.SelectedFilePath);
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Error, T("Failed to load file content")));
this.isSelectingFile = false;
}
}
}
}

View File

@ -13,7 +13,7 @@
Variant="Variant.Outlined"
/>
<MudButton StartIcon="@Icons.Material.Filled.FolderOpen" Variant="Variant.Outlined" Color="Color.Primary" Disabled="this.Disabled" OnClick="@this.OpenFileDialog">
<MudButton StartIcon="@Icons.Material.Filled.FolderOpen" Variant="Variant.Outlined" Color="Color.Primary" Disabled="@(this.Disabled || this.isOpeningFileDialog)" OnClick="@this.OpenFileDialog">
@T("Choose File")
</MudButton>
</MudStack>

View File

@ -35,6 +35,7 @@ public partial class SelectFile : MSGComponentBase
protected ILogger<SelectFile> Logger { get; init; } = null!;
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
private bool isOpeningFileDialog;
#region Overrides of ComponentBase
@ -55,10 +56,22 @@ public partial class SelectFile : MSGComponentBase
private async Task OpenFileDialog()
{
var response = await this.RustService.SelectFile(this.FileDialogTitle, this.Filter, string.IsNullOrWhiteSpace(this.File) ? null : this.File);
this.Logger.LogInformation($"The user selected the file '{response.SelectedFilePath}'.");
if (this.Disabled || this.isOpeningFileDialog)
return;
if (!response.UserCancelled)
this.InternalFileChanged(response.SelectedFilePath);
this.isOpeningFileDialog = true;
try
{
var response = await this.RustService.SelectFile(this.FileDialogTitle, this.Filter, string.IsNullOrWhiteSpace(this.File) ? null : this.File);
this.Logger.LogInformation($"The user selected the file '{response.SelectedFilePath}'.");
if (!response.UserCancelled)
this.InternalFileChanged(response.SelectedFilePath);
}
finally
{
this.isOpeningFileDialog = false;
}
}
}