using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
///
/// What the user can do with a file or a web page which a source, a passage, or an attachment
/// names: copy its path, find it in the file manager, open it, at a page where there is one.
///
///
/// Every place which names a file offers the same buttons the same way, which is why they live
/// here. Each place decides which of them it shows, e.g., a title copies and finds, while the bar
/// above a text opens.
///
/// A file which is no longer at its place gets a warning instead of the buttons which would only
/// fail; copying its path stays, since it tells where the file was. Whether it is there is asked
/// off the UI thread and once per path: on an unreachable network share, the answer takes until
/// the timeout of the share, and a list of sources renders anew with every streamed chunk.
///
/// A web page is copied and opened in the browser. Anything else, such as a mail, which only
/// AI Studio can find by its address, or a relative path, gets no buttons at all.
///
/// Every place which opens a file names it the same way, see GetOpenTooltip, also where a title
/// opens it instead of a button.
///
public partial class FileActions : MSGComponentBase
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(FileActions).Namespace, nameof(FileActions));
///
/// What a path names.
///
internal enum FileActionsKind
{
NONE,
FILE,
WEB_PAGE,
}
///
/// The path of the file, or the address of the web page.
///
[Parameter]
public string Path { get; set; } = string.Empty;
///
/// The page to open a file at, or null to open it at its beginning.
///
[Parameter]
public int? PageNumber { get; set; }
[Parameter]
public bool ShowCopy { get; set; } = true;
[Parameter]
public bool ShowFileManager { get; set; } = true;
[Parameter]
public bool ShowOpen { get; set; } = true;
///
/// Whether a file which is no longer at its place gets a warning. False where the caller says so
/// itself, or where another place on the screen warns already.
///
[Parameter]
public bool WarnIfMissing { get; set; } = true;
[Parameter]
public Placement TooltipPlacement { get; set; } = Placement.Top;
///
/// Tells the caller whether the file is at its place, once that is known, e.g., so that a title
/// which opens it stops being a link.
///
[Parameter]
public EventCallback OnExistenceChecked { get; set; }
[Inject]
private RustService RustService { get; init; } = null!;
private FileActionsKind kind;
///
/// The path whose existence was asked last, so that a render with the same path does not ask again.
///
private string? checkedPath;
///
/// Whether the file is known to be gone. Until the answer arrives, the buttons show: most files are there.
///
private bool isMissing;
///
/// The tooltip of opening a file, at its page where there is one.
///
/// The page the file opens at, or null when it opens at its beginning.
/// The tooltip.
internal static string GetOpenTooltip(int? pageNumber) => pageNumber is { } page
? string.Format(TB("Open the file at page {0}"), page)
: TB("Open the file in the program your system uses for it");
#region Overrides of ComponentBase
protected override async Task OnParametersSetAsync()
{
this.kind = DetermineKind(this.Path);
if (this.kind is FileActionsKind.FILE && !string.Equals(this.checkedPath, this.Path, StringComparison.Ordinal))
{
var path = this.Path;
this.checkedPath = path;
this.isMissing = false;
var exists = await Task.Run(() => File.Exists(path));
// Another path took over meanwhile, which asks for itself:
if (!string.Equals(this.checkedPath, path, StringComparison.Ordinal))
return;
this.isMissing = !exists;
await this.OnExistenceChecked.InvokeAsync(exists);
}
await base.OnParametersSetAsync();
}
#endregion
///
/// Tells a web page from a file on this computer by its path.
///
///
/// Only an absolute path names a file: a relative one would name another file depending on
/// where it is opened from.
///
internal static FileActionsKind DetermineKind(string path)
{
if (string.IsNullOrWhiteSpace(path))
return FileActionsKind.NONE;
if (Uri.TryCreate(path, UriKind.Absolute, out var uri) && uri.Scheme is "http" or "https")
return FileActionsKind.WEB_PAGE;
return System.IO.Path.IsPathRooted(path) && (uri is null || uri.IsFile)
? FileActionsKind.FILE
: FileActionsKind.NONE;
}
private async Task CopyPath() => await this.RustService.CopyText2Clipboard(this.Path);
private async Task ShowInFileManager() => await new SourceDocumentLocation(this.Path, null).ShowInFileManagerAsync(this.RustService);
private async Task Open() => await new SourceDocumentLocation(this.Path, this.PageNumber).OpenAsync(this.RustService);
}