diff --git a/samples/KristofferStrube.Blazor.FileSystemAccess.ServerExample/Pages/OpenFile.razor b/samples/KristofferStrube.Blazor.FileSystemAccess.ServerExample/Pages/OpenFile.razor index 43b114b..0aa5733 100644 --- a/samples/KristofferStrube.Blazor.FileSystemAccess.ServerExample/Pages/OpenFile.razor +++ b/samples/KristofferStrube.Blazor.FileSystemAccess.ServerExample/Pages/OpenFile.razor @@ -1,10 +1,27 @@ @page "/OpenFile" +@using KristofferStrube.Blazor.WebIDL.Exceptions; @inject IFileSystemAccessService FileSystemAccessService File System Access - Read File + +@if (errorMessage is not null) +{ +
@errorMessage
+} @if (fileHandle is null) { +

+ The browser can remember a set of previously used folders given an id. If a prompt has never been opened with this id then it falls back to using the well-known directory and the folder that is used this time is remembered for the next time the id is used. The id must only contain alphanumeric symbols or "_" or "-" and cannot be longer than 32 characters. +
+ You can specify an id below here or leave it blank. You can try to give it some illegal characters or make it too long to see that we can handle errors of this type. +
+ We can likewise detect if the user aborts the prompt which you can also test here by canceling or closing the prompt. +

+ + +
+
} else if (readPermissionState is PermissionState.Denied) @@ -18,16 +35,17 @@ else if (fileText is null) else if (writePermissionState is PermissionState.Denied or PermissionState.Prompt) { - + } else { - + } - @code { private string? fileText; + private string? errorMessage; + private string? id; private FileSystemFileHandle? fileHandle; private string fileHandleName = ""; private PermissionState readPermissionState; @@ -37,18 +55,31 @@ else { try { - var options = new OpenFilePickerOptionsStartInWellKnownDirectory() { Multiple = false, StartIn = WellKnownDirectory.Downloads }; + var options = new OpenFilePickerOptionsStartInWellKnownDirectory() { Multiple = false, StartIn = WellKnownDirectory.Downloads, Id = id }; var fileHandles = await FileSystemAccessService.ShowOpenFilePickerAsync(options); fileHandle = fileHandles.Single(); } - catch (JSException ex) + catch (AbortErrorException) + { + errorMessage = $"The user aborted the prompt."; + } + catch (DOMException ex) + { + errorMessage = $"A user interaction error of type {ex.Name} occurred: \"{ex.Message}\""; + } + catch (TypeErrorException ex) + { + errorMessage = $"We parsed an invalid argument to the function: \"{ex.Message}\""; + } + catch (Exception ex) { - Console.WriteLine(ex); + errorMessage = $"Some other unexpected exception of type {ex.GetType().Name} occurred: \"{ex.Message}\""; } finally { if (fileHandle != null) { + errorMessage = null; fileHandleName = await fileHandle.GetNameAsync(); readPermissionState = await fileHandle.QueryPermissionAsync(new() { Mode = FileSystemPermissionMode.Read }); } diff --git a/samples/KristofferStrube.Blazor.FileSystemAccess.ServerExample/Program.cs b/samples/KristofferStrube.Blazor.FileSystemAccess.ServerExample/Program.cs index 7c4ec3b..1920f0d 100644 --- a/samples/KristofferStrube.Blazor.FileSystemAccess.ServerExample/Program.cs +++ b/samples/KristofferStrube.Blazor.FileSystemAccess.ServerExample/Program.cs @@ -1,6 +1,7 @@ using KristofferStrube.Blazor.FileAPI; using KristofferStrube.Blazor.FileSystem; using KristofferStrube.Blazor.FileSystemAccess; +using KristofferStrube.Blazor.WebIDL; using TG.Blazor.IndexedDB; WebApplicationBuilder builder = WebApplication.CreateBuilder(args); diff --git a/samples/KristofferStrube.Blazor.FileSystemAccess.WasmExample/Pages/OpenFile.razor b/samples/KristofferStrube.Blazor.FileSystemAccess.WasmExample/Pages/OpenFile.razor index a58b053..0a76d2c 100644 --- a/samples/KristofferStrube.Blazor.FileSystemAccess.WasmExample/Pages/OpenFile.razor +++ b/samples/KristofferStrube.Blazor.FileSystemAccess.WasmExample/Pages/OpenFile.razor @@ -1,10 +1,26 @@ @page "/OpenFile" +@using KristofferStrube.Blazor.WebIDL.Exceptions; @inject IFileSystemAccessServiceInProcess FileSystemAccessService File System Access - Read File + +@if (errorMessage is not null) +{ +
@errorMessage
+} @if (fileHandle is null) { +

+ The browser can remember a set of previously used folders given an id. If a prompt has never been opened with this id then it falls back to using the well-known directory and the folder that is used this time is remembered for the next time the id is used. The id must only contain alphanumeric symbols or "_" or "-" and cannot be longer than 32 characters. +
+ You can specify an id below here or leave it blank. You can try to give it some illegal characters or make it too long to see that we can handle errors of this type. +
+ We can likewise detect if the user aborts the prompt which you can also test here by canceling or closing the prompt. +

+ +
+
} else if (readPermissionState is PermissionState.Denied) @@ -25,9 +41,10 @@ else } - @code { private string? fileText; + private string? errorMessage; + private string? id; private FileSystemFileHandleInProcess? fileHandle; private PermissionState readPermissionState; private PermissionState writePermissionState; @@ -36,18 +53,31 @@ else { try { - var options = new OpenFilePickerOptionsStartInWellKnownDirectory() { Multiple = false, StartIn = WellKnownDirectory.Downloads }; + var options = new OpenFilePickerOptionsStartInWellKnownDirectory() { Multiple = false, StartIn = WellKnownDirectory.Downloads, Id = id }; var fileHandles = await FileSystemAccessService.ShowOpenFilePickerAsync(options); fileHandle = fileHandles.Single(); } - catch (JSException ex) + catch (AbortErrorException) + { + errorMessage = $"The user aborted the prompt."; + } + catch (DOMException ex) + { + errorMessage = $"A user interaction error of type {ex.Name} occurred: \"{ex.Message}\""; + } + catch (TypeErrorException ex) + { + errorMessage = $"We parsed an invalid argument to the function: \"{ex.Message}\""; + } + catch (Exception ex) { - Console.WriteLine(ex); + errorMessage = $"Some other unexpected exception of type {ex.GetType().Name} occurred: \"{ex.Message}\""; } finally { if (fileHandle != null) { + errorMessage = null; readPermissionState = await fileHandle.QueryPermissionAsync(new() { Mode = FileSystemPermissionMode.Read }); } } diff --git a/samples/KristofferStrube.Blazor.FileSystemAccess.WasmExample/Program.cs b/samples/KristofferStrube.Blazor.FileSystemAccess.WasmExample/Program.cs index 59167f0..6561241 100644 --- a/samples/KristofferStrube.Blazor.FileSystemAccess.WasmExample/Program.cs +++ b/samples/KristofferStrube.Blazor.FileSystemAccess.WasmExample/Program.cs @@ -2,6 +2,7 @@ using KristofferStrube.Blazor.FileSystem; using KristofferStrube.Blazor.FileSystemAccess; using KristofferStrube.Blazor.FileSystemAccess.WasmExample; +using KristofferStrube.Blazor.WebIDL; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using TG.Blazor.IndexedDB; @@ -35,4 +36,8 @@ }); }); -await builder.Build().RunAsync(); +var app = builder.Build(); + +await app.Services.SetupErrorHandlingJSInterop(); + +await app.RunAsync(); diff --git a/src/KristofferStrube.Blazor.FileSystemAccess/BaseFileSystemAccessService.cs b/src/KristofferStrube.Blazor.FileSystemAccess/BaseFileSystemAccessService.cs index 7b0dd89..2100d32 100644 --- a/src/KristofferStrube.Blazor.FileSystemAccess/BaseFileSystemAccessService.cs +++ b/src/KristofferStrube.Blazor.FileSystemAccess/BaseFileSystemAccessService.cs @@ -1,5 +1,6 @@ using KristofferStrube.Blazor.FileSystem; using KristofferStrube.Blazor.FileSystemAccess.Extensions; +using KristofferStrube.Blazor.WebIDL; using Microsoft.JSInterop; namespace KristofferStrube.Blazor.FileSystemAccess; @@ -10,70 +11,56 @@ public abstract class BaseFileSystemAccessService> helperTask; protected readonly IJSRuntime jSRuntime; + protected readonly IErrorHandlingJSRuntime? errorHandlingJSRuntime; public BaseFileSystemAccessService(IJSRuntime jSRuntime) { helperTask = new(() => jSRuntime.GetHelperAsync(FileSystemAccessOptions.DefaultInstance)); + + if (ErrorHandlingJSInterop.ErrorHandlingJSInteropHasBeenSetup) + { + errorHandlingJSRuntime = new ErrorHandlingJSInProcessRuntime(); + } + else if (jSRuntime is not IJSInProcessRuntime) + { + errorHandlingJSRuntime = new ErrorHandlingJSRuntime(jSRuntime); + } this.jSRuntime = jSRuntime; } #region ShowOpenFilePickerAsync - /// - /// showOpenFilePicker() browser specs - /// - /// - /// + /// public async Task ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInWellKnownDirectory? openFilePickerOptions) { - return await this.InternalShowOpenFilePickerAsync(openFilePickerOptions?.Serializable()); + return await InternalShowOpenFilePickerAsync(openFilePickerOptions?.Serializable()); } - /// - /// showOpenFilePicker() browser specs - /// - /// - /// - /// + /// public async Task ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInWellKnownDirectory? openFilePickerOptions, FileSystemOptions fsOptions) { return await InternalShowOpenFilePickerAsync(openFilePickerOptions?.Serializable(), fsOptions); } - /// - /// showOpenFilePicker() browser specs - /// - /// - /// + /// public async Task ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInFileSystemHandle? openFilePickerOptions) { return await this.InternalShowOpenFilePickerAsync(openFilePickerOptions?.Serializable()); } - /// - /// showOpenFilePicker() browser specs - /// - /// - /// - /// + /// public async Task ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInFileSystemHandle? openFilePickerOptions, FileSystemOptions fsOptions) { return await this.InternalShowOpenFilePickerAsync(openFilePickerOptions?.Serializable(), fsOptions); } - /// - /// showOpenFilePicker() browser specs - /// - /// + /// public async Task ShowOpenFilePickerAsync() { return await InternalShowOpenFilePickerAsync(null); } - /// - /// showOpenFilePicker() browser specs - /// - /// + /// public async Task ShowOpenFilePickerAsync(FileSystemOptions fsOptions) { return await InternalShowOpenFilePickerAsync(null, fsOptions); @@ -87,14 +74,14 @@ protected async Task InternalShowOpenFilePickerAsync(object? op protected async Task InternalShowOpenFilePickerAsync(object? options, FileSystemOptions fsOptions) { IJSObjectReference helper = await helperTask.Value; - TObjReference jSFileHandles = await jSRuntime.InvokeAsync("window.showOpenFilePicker", options); + IJSObjectReference jSFileHandles = await (errorHandlingJSRuntime ?? jSRuntime).InvokeAsync("window.showOpenFilePicker", options); int length = await helper.InvokeAsync("size", jSFileHandles); return await Task.WhenAll( Enumerable .Range(0, length) .Select(async i => - await this.CreateFileHandleAsync( + await CreateFileHandleAsync( jSRuntime, await jSFileHandles.InvokeAsync("at", i), fsOptions) diff --git a/src/KristofferStrube.Blazor.FileSystemAccess/Extensions/IServiceCollectionExtensions.cs b/src/KristofferStrube.Blazor.FileSystemAccess/Extensions/IServiceCollectionExtensions.cs index a8394fb..9ce1a40 100644 --- a/src/KristofferStrube.Blazor.FileSystemAccess/Extensions/IServiceCollectionExtensions.cs +++ b/src/KristofferStrube.Blazor.FileSystemAccess/Extensions/IServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.DependencyInjection; +using KristofferStrube.Blazor.WebIDL; +using Microsoft.Extensions.DependencyInjection; namespace KristofferStrube.Blazor.FileSystemAccess; @@ -13,7 +14,9 @@ public static IServiceCollection AddFileSystemAccessService(this IServiceCollect { ConfigureFsaOptions(serviceCollection, configure); - return serviceCollection.AddScoped(); + return serviceCollection + .AddScoped() + .AddErrorHandlingJSRuntime(); } public static IServiceCollection AddFileSystemAccessServiceInProcess(this IServiceCollection serviceCollection) @@ -31,7 +34,8 @@ public static IServiceCollection AddFileSystemAccessServiceInProcess(this IServi { IFileSystemAccessServiceInProcess service = sp.GetRequiredService(); return (IFileSystemAccessService)service; - }); + }) + .AddErrorHandlingJSRuntime(); } private static void ConfigureFsaOptions(IServiceCollection services, Action? configure) diff --git a/src/KristofferStrube.Blazor.FileSystemAccess/FileSystemAccessService.InProcess.cs b/src/KristofferStrube.Blazor.FileSystemAccess/FileSystemAccessService.InProcess.cs index 6935734..5514f6d 100644 --- a/src/KristofferStrube.Blazor.FileSystemAccess/FileSystemAccessService.InProcess.cs +++ b/src/KristofferStrube.Blazor.FileSystemAccess/FileSystemAccessService.InProcess.cs @@ -1,4 +1,5 @@ using KristofferStrube.Blazor.FileSystem; +using KristofferStrube.Blazor.WebIDL; using Microsoft.JSInterop; namespace KristofferStrube.Blazor.FileSystemAccess; diff --git a/src/KristofferStrube.Blazor.FileSystemAccess/FileSystemAccessService.cs b/src/KristofferStrube.Blazor.FileSystemAccess/FileSystemAccessService.cs index 024d248..c63df44 100644 --- a/src/KristofferStrube.Blazor.FileSystemAccess/FileSystemAccessService.cs +++ b/src/KristofferStrube.Blazor.FileSystemAccess/FileSystemAccessService.cs @@ -1,4 +1,5 @@ using KristofferStrube.Blazor.FileSystem; +using KristofferStrube.Blazor.WebIDL; using Microsoft.JSInterop; namespace KristofferStrube.Blazor.FileSystemAccess; diff --git a/src/KristofferStrube.Blazor.FileSystemAccess/IFileSystemAccessService.InProcess.cs b/src/KristofferStrube.Blazor.FileSystemAccess/IFileSystemAccessService.InProcess.cs index 4a3e18e..917cbbe 100644 --- a/src/KristofferStrube.Blazor.FileSystemAccess/IFileSystemAccessService.InProcess.cs +++ b/src/KristofferStrube.Blazor.FileSystemAccess/IFileSystemAccessService.InProcess.cs @@ -13,8 +13,22 @@ public interface IFileSystemAccessServiceInProcess : new Task ShowDirectoryPickerAsync(); new Task ShowDirectoryPickerAsync(DirectoryPickerOptionsStartInFileSystemHandle? directoryPickerOptions); new Task ShowDirectoryPickerAsync(DirectoryPickerOptionsStartInWellKnownDirectory? directoryPickerOptions); + + /// + /// + /// A new . new Task ShowOpenFilePickerAsync(); + + /// + /// + /// + /// A new array of . new Task ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInFileSystemHandle? openFilePickerOptions); + + /// + /// + /// + /// A new array of . new Task ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInWellKnownDirectory? openFilePickerOptions); new Task ShowSaveFilePickerAsync(); new Task ShowSaveFilePickerAsync(SaveFilePickerOptionsStartInFileSystemHandle? saveFilePickerOptions); diff --git a/src/KristofferStrube.Blazor.FileSystemAccess/IFileSystemAccessService.cs b/src/KristofferStrube.Blazor.FileSystemAccess/IFileSystemAccessService.cs index 4eec869..f98ba93 100644 --- a/src/KristofferStrube.Blazor.FileSystemAccess/IFileSystemAccessService.cs +++ b/src/KristofferStrube.Blazor.FileSystemAccess/IFileSystemAccessService.cs @@ -1,4 +1,5 @@ using KristofferStrube.Blazor.FileSystem; +using KristofferStrube.Blazor.WebIDL.Exceptions; using Microsoft.JSInterop; namespace KristofferStrube.Blazor.FileSystemAccess @@ -11,15 +12,91 @@ public interface IFileSystemAccessService IsSupportedAsync(); + /// + /// Shows a directory picker that lets the user select a single directory, returning a handle for the selected directory if the user grants read permission. + /// + /// + /// + /// A new Task ShowDirectoryPickerAsync(); + + /// + /// Shows a directory picker that lets the user select a single directory, returning a handle for the selected directory if the user grants read permission. If the argument specified that it should have write access as well this needs to be confirmed by the user as well. + /// + /// A directory picker options for selecting to start in the position of an existing . + /// + /// + /// + /// A new Task ShowDirectoryPickerAsync(DirectoryPickerOptionsStartInFileSystemHandle? directoryPickerOptions); + + /// + /// Shows a directory picker that lets the user select a single directory, returning a handle for the selected directory if the user grants read permission. If the argument specified that it should have write access as well this needs to be confirmed by the user as well. + /// + /// A directory picker options for selecting to start in one of the cross-browser well-known directories. + /// + /// + /// + /// A new Task ShowDirectoryPickerAsync(DirectoryPickerOptionsStartInWellKnownDirectory? directoryPickerOptions); - Task ShowDirectoryPickerAsync(FileSystemOptions fasOptions); + + /// + /// Shows a directory picker that lets the user select a single directory, returning a handle for the selected directory if the user grants read permission. + /// + /// An option that can be used to define the path of the helper script used in this library. + /// + /// + /// A new + Task ShowDirectoryPickerAsync(FileSystemOptions fsOptions); + + /// + /// Shows a directory picker that lets the user select a single directory, returning a handle for the selected directory if the user grants read permission. If the argument specified that it should have write access as well this needs to be confirmed by the user as well. + /// + /// A directory picker options for selecting to start in the position of an existing . + /// An option that can be used to define the path of the helper script used in this library. + /// + /// + /// + /// A new Task ShowDirectoryPickerAsync(DirectoryPickerOptionsStartInFileSystemHandle? directoryPickerOptions, FileSystemOptions fsOptions); + + /// + /// Shows a directory picker that lets the user select a single directory, returning a handle for the selected directory if the user grants read permission. If the argument specified that it should have write access as well this needs to be confirmed by the user as well. + /// + /// A directory picker options for selecting to start in one of the cross-browser well-known directories. + /// An option that can be used to define the path of the helper script used in this library. + /// + /// + /// + /// A new Task ShowDirectoryPickerAsync(DirectoryPickerOptionsStartInWellKnownDirectory? directoryPickerOptions, FileSystemOptions fsOptions); + /// + /// Shows a file picker that lets a user select a single existing file, returning a handle for the selected file. + /// + /// + /// + /// A new Task ShowOpenFilePickerAsync(); + + /// + /// Shows a file picker that lets a user select a single existing file, if the parsed argument had multiple set to , returning a handle for the selected file. If the argument specified that the user could pick multiple files then there might be more than a single handle returned. + /// + /// A file picker options for selecting to start in the position of an existing . + /// + /// + /// + /// A new Task ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInFileSystemHandle? openFilePickerOptions); + + /// + /// Shows a file picker that lets a user select a single existing file, if the parsed argument had multiple set to , returning a handle for the selected file. If the argument specified that the user could pick multiple files then there might be more than a single handle returned. + /// + /// A file picker options for selecting to start in one of the cross-browser well-known directories. + /// + /// + /// + /// A new Task ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInWellKnownDirectory? openFilePickerOptions); Task ShowOpenFilePickerAsync(FileSystemOptions fasOptions); Task ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInFileSystemHandle? openFilePickerOptions, FileSystemOptions fsOptions); diff --git a/src/KristofferStrube.Blazor.FileSystemAccess/KristofferStrube.Blazor.FileSystemAccess.csproj b/src/KristofferStrube.Blazor.FileSystemAccess/KristofferStrube.Blazor.FileSystemAccess.csproj index 68e2802..be51f7c 100644 --- a/src/KristofferStrube.Blazor.FileSystemAccess/KristofferStrube.Blazor.FileSystemAccess.csproj +++ b/src/KristofferStrube.Blazor.FileSystemAccess/KristofferStrube.Blazor.FileSystemAccess.csproj @@ -35,6 +35,7 @@ +