Improve Linux clipboard support and file handling (#872)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-07-19 19:56:30 +02:00
1 parent f666760415
commit 8fa0c4db01
16 files changed
+343 -166

No files matched your search

@@ -102,13 +102,14 @@ public partial class AttachDocuments : MSGComponentBase
private uint numDropAreasAboveThis;
private bool isComponentHovered;
private bool isDraggingOver;
private bool isFileDialogOpen;
private MediaImportOwner EffectiveImportOwner => this.OwnerChat is not null
? MediaImportOwner.ForChat(this.OwnerChat.ChatId)
: this.ImportOwner ?? this.fallbackMediaImportOwner;
private MediaImportTarget EffectiveMediaImportTarget => new(this.EffectiveImportOwner, string.IsNullOrWhiteSpace(this.Name) ? "attachments" : this.Name);
private bool IsUnavailable => this.Disabled || this.MediaTranscriptionService.IsBusy(this.EffectiveImportOwner);
private bool IsUnavailable => this.Disabled || this.isFileDialogOpen || this.MediaTranscriptionService.IsBusy(this.EffectiveImportOwner);
#region Overrides of MSGComponentBase
@@ -310,13 +311,21 @@ public partial class AttachDocuments : MSGComponentBase
if (this.IsUnavailable)
return;
var selectFiles = await this.RustService.SelectFiles(T("Select files to attach"));
if (selectFiles.UserCancelled)
return;
this.isFileDialogOpen = true;
try
{
var selectFiles = await this.RustService.SelectFiles(T("Select files to attach"));
if (selectFiles.UserCancelled)
return;
await this.AddFileBatchAsync(selectFiles.SelectedFilePaths);
await this.DocumentPathsChanged.InvokeAsync(this.DocumentPaths);
await this.OnChange(this.DocumentPaths);
await this.AddFileBatchAsync(selectFiles.SelectedFilePaths);
await this.DocumentPathsChanged.InvokeAsync(this.DocumentPaths);
await this.OnChange(this.DocumentPaths);
}
finally
{
this.isFileDialogOpen = false;
}
}
private async Task OpenAttachmentsDialog()
@@ -397,7 +406,17 @@ public partial class AttachDocuments : MSGComponentBase
private async Task AddFileBatchAsync(IEnumerable<string> paths)
{
var existingPaths = paths.Where(File.Exists).ToList();
var pathList = paths.ToList();
var inaccessiblePaths = pathList.Where(path => !File.Exists(path)).ToList();
if (inaccessiblePaths.Count > 0)
{
this.Logger.LogWarning("Could not access {Count} dropped or selected file(s): {Paths}", inaccessiblePaths.Count, string.Join(", ", inaccessiblePaths));
await this.MessageBus.SendWarning(new(
Icons.Material.Filled.Warning,
this.T("Some files could not be accessed. Please select them with the file chooser instead.")));
}
var existingPaths = pathList.Except(inaccessiblePaths).ToList();
var mediaPaths = existingPaths.Where(IsTranscribableMedia).ToList();
var regularPaths = existingPaths.Except(mediaPaths).ToList();
@@ -19,7 +19,7 @@
Variant="Variant.Outlined"
Color="Color.Primary"
Size="Size.Small"
Disabled="@this.IsDisabled"
Disabled="@(this.IsDisabled || this.isFileDialogOpen)"
Class="mb-1"
OnClick="@this.OpenFileDialog">
@T("Choose File")
@@ -49,6 +49,7 @@ public partial class ConfigurationFile : ConfigurationBaseCore
private RustService RustService { get; init; } = null!;
private string internalText = string.Empty;
private bool isFileDialogOpen;
private readonly Timer timer = new(TimeSpan.FromMilliseconds(500))
{
AutoReset = false
@@ -90,13 +91,24 @@ public partial class ConfigurationFile : ConfigurationBaseCore
private async Task OpenFileDialog()
{
var response = await this.RustService.SelectFile(this.FileDialogTitle, this.Filter, string.IsNullOrWhiteSpace(this.internalText) ? null : this.internalText);
if (response.UserCancelled)
if (this.isFileDialogOpen)
return;
this.timer.Stop();
this.internalText = response.SelectedFilePath;
await this.OptionChanged(response.SelectedFilePath);
this.isFileDialogOpen = true;
try
{
var response = await this.RustService.SelectFile(this.FileDialogTitle, this.Filter, string.IsNullOrWhiteSpace(this.internalText) ? null : this.internalText);
if (response.UserCancelled)
return;
this.timer.Stop();
this.internalText = response.SelectedFilePath;
await this.OptionChanged(response.SelectedFilePath);
}
finally
{
this.isFileDialogOpen = false;
}
}
private async Task OptionChanged(string updatedText)
@@ -74,9 +74,10 @@ public partial class ReadFileContent : MSGComponentBase
private string dragClass = DEFAULT_DRAG_CLASS;
private uint numDropAreasAboveThis;
private bool isComponentHovered;
private bool isFileDialogOpen;
private bool IsCurrentTargetBusy => this.MediaTranscriptionService.GetSnapshot(this.EffectiveImportOwner) is { IsBusy: true } snapshot
&& snapshot.Target == this.EffectiveMediaImportTarget;
private bool IsUnavailable => this.Disabled || this.MediaTranscriptionService.IsBusy(this.EffectiveImportOwner);
private bool IsUnavailable => this.Disabled || this.isFileDialogOpen || this.MediaTranscriptionService.IsBusy(this.EffectiveImportOwner);
#region Overrides of MSGComponentBase
@@ -217,14 +218,22 @@ public partial class ReadFileContent : MSGComponentBase
if (this.IsUnavailable)
return;
var selectedFile = await this.RustService.SelectFile(T("Select file to read its content"));
if (selectedFile.UserCancelled)
this.isFileDialogOpen = true;
try
{
this.Logger.LogInformation("User cancelled the 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;
}
await this.LoadFileIfValid(selectedFile.SelectedFilePath);
await this.LoadFileIfValid(selectedFile.SelectedFilePath);
}
finally
{
this.isFileDialogOpen = false;
}
}
private async Task<bool> EnsurePandocAvailability()
@@ -246,6 +255,15 @@ public partial class ReadFileContent : MSGComponentBase
private async Task LoadFirstValidFile(List<string> paths)
{
var inaccessiblePaths = paths.Where(path => !File.Exists(path)).ToList();
if (inaccessiblePaths.Count > 0)
{
this.Logger.LogWarning("Could not access {Count} dropped file(s): {Paths}", inaccessiblePaths.Count, string.Join(", ", inaccessiblePaths));
await this.MessageBus.SendWarning(new(
Icons.Material.Filled.Warning,
this.T("Some dropped files could not be accessed. Please select them with the file chooser instead.")));
}
foreach (var path in paths)
{
if (await this.LoadFileIfValid(path))
@@ -13,7 +13,7 @@
Variant="Variant.Outlined"
/>
<MudButton StartIcon="@Icons.Material.Filled.FolderOpen" Variant="Variant.Outlined" Color="Color.Primary" Disabled="this.Disabled" OnClick="@this.OpenDirectoryDialog">
<MudButton StartIcon="@Icons.Material.Filled.FolderOpen" Variant="Variant.Outlined" Color="Color.Primary" Disabled="@(this.Disabled || this.isDirectoryDialogOpen)" OnClick="@this.OpenDirectoryDialog">
@T("Choose Directory")
</MudButton>
</MudStack>
@@ -31,6 +31,7 @@ public partial class SelectDirectory : MSGComponentBase
protected ILogger<SelectDirectory> Logger { get; init; } = null!;
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
private bool isDirectoryDialogOpen;
#region Overrides of ComponentBase
@@ -51,10 +52,21 @@ public partial class SelectDirectory : MSGComponentBase
private async Task OpenDirectoryDialog()
{
var response = await this.RustService.SelectDirectory(this.DirectoryDialogTitle, string.IsNullOrWhiteSpace(this.Directory) ? null : this.Directory);
this.Logger.LogInformation($"The user selected the directory '{response.SelectedDirectory}'.");
if (this.isDirectoryDialogOpen)
return;
if (!response.UserCancelled)
this.InternalDirectoryChanged(response.SelectedDirectory);
this.isDirectoryDialogOpen = true;
try
{
var response = await this.RustService.SelectDirectory(this.DirectoryDialogTitle, string.IsNullOrWhiteSpace(this.Directory) ? null : this.Directory);
this.Logger.LogInformation("The user selected the directory '{SelectedDirectory}'.", response.SelectedDirectory);
if (!response.UserCancelled)
this.InternalDirectoryChanged(response.SelectedDirectory);
}
finally
{
this.isDirectoryDialogOpen = false;
}
}
}
@@ -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.isFileDialogOpen)" OnClick="@this.OpenFileDialog">
@T("Choose File")
</MudButton>
</MudStack>
@@ -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 isFileDialogOpen;
#region Overrides of ComponentBase
@@ -55,10 +56,21 @@ 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.isFileDialogOpen)
return;
if (!response.UserCancelled)
this.InternalFileChanged(response.SelectedFilePath);
this.isFileDialogOpen = 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 '{SelectedFilePath}'.", response.SelectedFilePath);
if (!response.UserCancelled)
this.InternalFileChanged(response.SelectedFilePath);
}
finally
{
this.isFileDialogOpen = false;
}
}
}