Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Blazor.WebIDL Exceptions to the library. #43

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
@page "/OpenFile"
@using KristofferStrube.Blazor.WebIDL.Exceptions;
@inject IFileSystemAccessService FileSystemAccessService

<PageTitle>File System Access - Read File</PageTitle>


@if (errorMessage is not null)
{
<div class="alert alert-danger">@errorMessage</div>
}
@if (fileHandle is null)
{
<p>
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.
<br />
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.
<br />
We can likewise detect if the user aborts the prompt which you can also test here by canceling or closing the prompt.
</p>
<label for="id">Id: </label>
<input @bind="id" type="text" id="id" />
<br />
<br />
<button @onclick="OpenFilePicker" class="btn btn-primary">Open File Picker for Single File</button>
}
else if (readPermissionState is PermissionState.Denied)
Expand All @@ -18,16 +35,17 @@ else if (fileText is null)
else if (writePermissionState is PermissionState.Denied or PermissionState.Prompt)
{
<button @onclick="RequestWriteAccess" class="btn btn-primary">Request Write Access for @fileHandleName</button>
<textarea style="width:100%;height:calc(100vh - 144px);" value=@fileText @oninput=TextAreaChanged disabled="disabled"></textarea>
<textarea style="width:100%;height:calc(100% - 44px);" value=@fileText @oninput=TextAreaChanged disabled="disabled"></textarea>
}
else
{
<textarea style="width:100%;height:calc(100vh - 106px);" value=@fileText @oninput=TextAreaChanged></textarea>
<textarea style="width:100%;height:calc(100% - 6px);" value=@fileText @oninput=TextAreaChanged></textarea>
}


@code {
private string? fileText;
private string? errorMessage;
private string? id;
private FileSystemFileHandle? fileHandle;
private string fileHandleName = "";
private PermissionState readPermissionState;
Expand All @@ -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 });
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
@page "/OpenFile"
@using KristofferStrube.Blazor.WebIDL.Exceptions;
@inject IFileSystemAccessServiceInProcess FileSystemAccessService

<PageTitle>File System Access - Read File</PageTitle>


@if (errorMessage is not null)
{
<div class="alert alert-danger">@errorMessage</div>
}
@if (fileHandle is null)
{
<p>
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.
<br />
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.
<br />
We can likewise detect if the user aborts the prompt which you can also test here by canceling or closing the prompt.
</p>
<label for="id">Id: </label> <input @bind="id" type="text" id="id" />
<br />
<br />
<button @onclick="OpenFilePicker" class="btn btn-primary">Open File Picker for Single File</button>
}
else if (readPermissionState is PermissionState.Denied)
Expand All @@ -25,9 +41,10 @@ else
<textarea style="width:100%;height:calc(100% - 6px);" value=@fileText @oninput=TextAreaChanged></textarea>
}


@code {
private string? fileText;
private string? errorMessage;
private string? id;
private FileSystemFileHandleInProcess? fileHandle;
private PermissionState readPermissionState;
private PermissionState writePermissionState;
Expand All @@ -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 });
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -35,4 +36,8 @@
});
});

await builder.Build().RunAsync();
var app = builder.Build();

await app.Services.SetupErrorHandlingJSInterop();

await app.RunAsync();
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using KristofferStrube.Blazor.FileSystem;
using KristofferStrube.Blazor.FileSystemAccess.Extensions;
using KristofferStrube.Blazor.WebIDL;
using Microsoft.JSInterop;

namespace KristofferStrube.Blazor.FileSystemAccess;
Expand All @@ -10,70 +11,56 @@ public abstract class BaseFileSystemAccessService<TFsFileHandle, TFsDirectoryHan
{
protected readonly Lazy<Task<IJSObjectReference>> 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

/// <summary>
/// <see href="https://wicg.github.io/file-system-access/#api-showopenfilepicker">showOpenFilePicker() browser specs</see>
/// </summary>
/// <param name="openFilePickerOptions"></param>
/// <returns></returns>
/// <inheritdoc/>
public async Task<TFsFileHandle[]> ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInWellKnownDirectory? openFilePickerOptions)
{
return await this.InternalShowOpenFilePickerAsync(openFilePickerOptions?.Serializable());
return await InternalShowOpenFilePickerAsync(openFilePickerOptions?.Serializable());
}

/// <summary>
/// <see href="https://wicg.github.io/file-system-access/#api-showopenfilepicker">showOpenFilePicker() browser specs</see>
/// </summary>
/// <param name="openFilePickerOptions"></param>
/// <param name="fsOptions"></param>
/// <returns></returns>
/// <inheritdoc/>
public async Task<TFsFileHandle[]> ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInWellKnownDirectory? openFilePickerOptions, FileSystemOptions fsOptions)
{
return await InternalShowOpenFilePickerAsync(openFilePickerOptions?.Serializable(), fsOptions);
}

/// <summary>
/// <see href="https://wicg.github.io/file-system-access/#api-showopenfilepicker">showOpenFilePicker() browser specs</see>
/// </summary>
/// <param name="openFilePickerOptions"></param>
/// <returns></returns>
/// <inheritdoc/>
public async Task<TFsFileHandle[]> ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInFileSystemHandle? openFilePickerOptions)
{
return await this.InternalShowOpenFilePickerAsync(openFilePickerOptions?.Serializable());
}

/// <summary>
/// <see href="https://wicg.github.io/file-system-access/#api-showopenfilepicker">showOpenFilePicker() browser specs</see>
/// </summary>
/// <param name="openFilePickerOptions"></param>
/// <param name="fsOptions"></param>
/// <returns></returns>
/// <inheritdoc/>
public async Task<TFsFileHandle[]> ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInFileSystemHandle? openFilePickerOptions, FileSystemOptions fsOptions)
{
return await this.InternalShowOpenFilePickerAsync(openFilePickerOptions?.Serializable(), fsOptions);
}

/// <summary>
/// <see href="https://wicg.github.io/file-system-access/#api-showopenfilepicker">showOpenFilePicker() browser specs</see>
/// </summary>
/// <returns></returns>
/// <inheritdoc/>
public async Task<TFsFileHandle[]> ShowOpenFilePickerAsync()
{
return await InternalShowOpenFilePickerAsync(null);
}

/// <summary>
/// <see href="https://wicg.github.io/file-system-access/#api-showopenfilepicker">showOpenFilePicker() browser specs</see>
/// </summary>
/// <returns></returns>
/// <inheritdoc/>
public async Task<TFsFileHandle[]> ShowOpenFilePickerAsync(FileSystemOptions fsOptions)
{
return await InternalShowOpenFilePickerAsync(null, fsOptions);
Expand All @@ -87,14 +74,14 @@ protected async Task<TFsFileHandle[]> InternalShowOpenFilePickerAsync(object? op
protected async Task<TFsFileHandle[]> InternalShowOpenFilePickerAsync(object? options, FileSystemOptions fsOptions)
{
IJSObjectReference helper = await helperTask.Value;
TObjReference jSFileHandles = await jSRuntime.InvokeAsync<TObjReference>("window.showOpenFilePicker", options);
IJSObjectReference jSFileHandles = await (errorHandlingJSRuntime ?? jSRuntime).InvokeAsync<IJSObjectReference>("window.showOpenFilePicker", options);
int length = await helper.InvokeAsync<int>("size", jSFileHandles);

return await Task.WhenAll(
Enumerable
.Range(0, length)
.Select(async i =>
await this.CreateFileHandleAsync(
await CreateFileHandleAsync(
jSRuntime,
await jSFileHandles.InvokeAsync<TObjReference>("at", i),
fsOptions)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using KristofferStrube.Blazor.WebIDL;
using Microsoft.Extensions.DependencyInjection;

namespace KristofferStrube.Blazor.FileSystemAccess;

Expand All @@ -13,7 +14,9 @@ public static IServiceCollection AddFileSystemAccessService(this IServiceCollect
{
ConfigureFsaOptions(serviceCollection, configure);

return serviceCollection.AddScoped<IFileSystemAccessService, FileSystemAccessService>();
return serviceCollection
.AddScoped<IFileSystemAccessService, FileSystemAccessService>()
.AddErrorHandlingJSRuntime();
}

public static IServiceCollection AddFileSystemAccessServiceInProcess(this IServiceCollection serviceCollection)
Expand All @@ -31,7 +34,8 @@ public static IServiceCollection AddFileSystemAccessServiceInProcess(this IServi
{
IFileSystemAccessServiceInProcess service = sp.GetRequiredService<IFileSystemAccessServiceInProcess>();
return (IFileSystemAccessService)service;
});
})
.AddErrorHandlingJSRuntime();
}

private static void ConfigureFsaOptions(IServiceCollection services, Action<FileSystemAccessOptions>? configure)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using KristofferStrube.Blazor.FileSystem;
using KristofferStrube.Blazor.WebIDL;
using Microsoft.JSInterop;

namespace KristofferStrube.Blazor.FileSystemAccess;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using KristofferStrube.Blazor.FileSystem;
using KristofferStrube.Blazor.WebIDL;
using Microsoft.JSInterop;

namespace KristofferStrube.Blazor.FileSystemAccess;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,22 @@ public interface IFileSystemAccessServiceInProcess :
new Task<FileSystemDirectoryHandleInProcess> ShowDirectoryPickerAsync();
new Task<FileSystemDirectoryHandleInProcess> ShowDirectoryPickerAsync(DirectoryPickerOptionsStartInFileSystemHandle? directoryPickerOptions);
new Task<FileSystemDirectoryHandleInProcess> ShowDirectoryPickerAsync(DirectoryPickerOptionsStartInWellKnownDirectory? directoryPickerOptions);

/// <inheritdoc cref="IFileSystemAccessService{TFsFileHandle, TFsDirectoryHandle, TObjReference}.ShowOpenFilePickerAsync()" path="/summary"/>
/// <inheritdoc cref="IFileSystemAccessService{TFsFileHandle, TFsDirectoryHandle, TObjReference}.ShowOpenFilePickerAsync()" path="/exception"/>
/// <returns>A new <see cref="FileSystemDirectoryHandleInProcess"/>.</returns>
new Task<FileSystemFileHandleInProcess[]> ShowOpenFilePickerAsync();

/// <inheritdoc cref="IFileSystemAccessService{TFsFileHandle, TFsDirectoryHandle, TObjReference}.ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInFileSystemHandle?)" path="/summary"/>
/// <inheritdoc cref="IFileSystemAccessService{TFsFileHandle, TFsDirectoryHandle, TObjReference}.ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInWellKnownDirectory?)" path="/param"/>
/// <inheritdoc cref="IFileSystemAccessService{TFsFileHandle, TFsDirectoryHandle, TObjReference}.ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInFileSystemHandle?)" path="/exception"/>
/// <returns>A new array of <see cref="FileSystemFileHandleInProcess"/>.</returns>
new Task<FileSystemFileHandleInProcess[]> ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInFileSystemHandle? openFilePickerOptions);

/// <inheritdoc cref="IFileSystemAccessService{TFsFileHandle, TFsDirectoryHandle, TObjReference}.ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInWellKnownDirectory?)" path="/summary"/>
/// <inheritdoc cref="IFileSystemAccessService{TFsFileHandle, TFsDirectoryHandle, TObjReference}.ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInWellKnownDirectory?)" path="/param"/>
/// <inheritdoc cref="IFileSystemAccessService{TFsFileHandle, TFsDirectoryHandle, TObjReference}.ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInWellKnownDirectory?)" path="/exception"/>
/// <returns>A new array of <see cref="FileSystemFileHandleInProcess"/>.</returns>
new Task<FileSystemFileHandleInProcess[]> ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInWellKnownDirectory? openFilePickerOptions);
new Task<FileSystemFileHandleInProcess> ShowSaveFilePickerAsync();
new Task<FileSystemFileHandleInProcess> ShowSaveFilePickerAsync(SaveFilePickerOptionsStartInFileSystemHandle? saveFilePickerOptions);
Expand Down
Loading