Skip to content

Commit c6c977a

Browse files
Merge pull request #31 from datvm/feature-custompath
Added support for custom JS module path, issue #29
2 parents 1cdd267 + c74f470 commit c6c977a

18 files changed

Lines changed: 503 additions & 335 deletions

samples/KristofferStrube.Blazor.FileSystemAccess.WasmExample/Pages/IndexedDB.razor

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ In this sample we store the references to files that we have previously opened i
4040
var length = await JSRuntime.InvokeAsync<int>("getAttribute", entries, "length");
4141
StoredFileHandles = (await Task.WhenAll(Enumerable
4242
.Range(0, length)
43-
.Select(async i => (false, await FileSystemFileHandleInProcess.CreateAsync(JSRuntime, await entries.InvokeAsync<IJSInProcessObjectReference>("at", i))))
43+
.Select(async i => (false,
44+
await FileSystemFileHandleInProcess.CreateAsync(
45+
JSRuntime,
46+
await entries.InvokeAsync<IJSInProcessObjectReference>("at", i))))
4447
)).ToList();
4548
}
4649

samples/KristofferStrube.Blazor.FileSystemAccess.WasmExample/Program.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@
1010
builder.RootComponents.Add<HeadOutlet>("head::after");
1111

1212
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
13-
builder.Services.AddFileSystemAccessServiceInProcess();
13+
14+
// Configure with custom script path
15+
builder.Services.AddFileSystemAccessServiceInProcess(options =>
16+
{
17+
// The file at this path in this example is manually copied to wwwroot folder
18+
// options.BasePath = "content/";
19+
// options.ScriptPath = $"custom-path/{FileSystemAccessOptions.DefaultNamespace}.js";
20+
});
21+
1422
builder.Services.AddURLServiceInProcess();
1523

1624
// Adding and configuring IndexedDB used for the IndexedDB sample.
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
using KristofferStrube.Blazor.FileSystemAccess.Extensions;
2+
using Microsoft.JSInterop;
3+
4+
namespace KristofferStrube.Blazor.FileSystemAccess;
5+
public abstract class BaseFileSystemAccessService<TFsFileHandle, TFsDirectoryHandle, TObjReference> : IFileSystemAccessService<TFsFileHandle, TFsDirectoryHandle, TObjReference>
6+
where TFsFileHandle : FileSystemFileHandle
7+
where TFsDirectoryHandle : FileSystemDirectoryHandle
8+
where TObjReference : IJSObjectReference
9+
{
10+
protected readonly Lazy<Task<IJSObjectReference>> helperTask;
11+
protected readonly IJSRuntime jSRuntime;
12+
13+
public BaseFileSystemAccessService(IJSRuntime jSRuntime)
14+
{
15+
helperTask = new(() => jSRuntime.GetHelperAsync(FileSystemAccessOptions.DefaultInstance));
16+
this.jSRuntime = jSRuntime;
17+
}
18+
19+
#region ShowOpenFilePickerAsync
20+
21+
/// <summary>
22+
/// <see href="https://wicg.github.io/file-system-access/#api-showopenfilepicker">showOpenFilePicker() browser specs</see>
23+
/// </summary>
24+
/// <param name="openFilePickerOptions"></param>
25+
/// <returns></returns>
26+
public async Task<TFsFileHandle[]> ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInWellKnownDirectory? openFilePickerOptions)
27+
=> await this.InternalShowOpenFilePickerAsync(openFilePickerOptions?.Serializable());
28+
29+
/// <summary>
30+
/// <see href="https://wicg.github.io/file-system-access/#api-showopenfilepicker">showOpenFilePicker() browser specs</see>
31+
/// </summary>
32+
/// <param name="openFilePickerOptions"></param>
33+
/// <returns></returns>
34+
public async Task<TFsFileHandle[]> ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInWellKnownDirectory? openFilePickerOptions, FileSystemAccessOptions fsaOptions)
35+
=> await InternalShowOpenFilePickerAsync(openFilePickerOptions?.Serializable(), fsaOptions);
36+
37+
/// <summary>
38+
/// <see href="https://wicg.github.io/file-system-access/#api-showopenfilepicker">showOpenFilePicker() browser specs</see>
39+
/// </summary>
40+
/// <param name="openFilePickerOptions"></param>
41+
/// <returns></returns>
42+
public async Task<TFsFileHandle[]> ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInFileSystemHandle? openFilePickerOptions)
43+
=> await this.InternalShowOpenFilePickerAsync(openFilePickerOptions?.Serializable());
44+
45+
/// <summary>
46+
/// <see href="https://wicg.github.io/file-system-access/#api-showopenfilepicker">showOpenFilePicker() browser specs</see>
47+
/// </summary>
48+
/// <param name="openFilePickerOptions"></param>
49+
/// <returns></returns>
50+
public async Task<TFsFileHandle[]> ShowOpenFilePickerAsync(OpenFilePickerOptionsStartInFileSystemHandle? openFilePickerOptions, FileSystemAccessOptions fsaOptions)
51+
=> await this.InternalShowOpenFilePickerAsync(openFilePickerOptions?.Serializable(), fsaOptions);
52+
53+
/// <summary>
54+
/// <see href="https://wicg.github.io/file-system-access/#api-showopenfilepicker">showOpenFilePicker() browser specs</see>
55+
/// </summary>
56+
/// <returns></returns>
57+
public async Task<TFsFileHandle[]> ShowOpenFilePickerAsync()
58+
=> await InternalShowOpenFilePickerAsync(null);
59+
60+
/// <summary>
61+
/// <see href="https://wicg.github.io/file-system-access/#api-showopenfilepicker">showOpenFilePicker() browser specs</see>
62+
/// </summary>
63+
/// <returns></returns>
64+
public async Task<TFsFileHandle[]> ShowOpenFilePickerAsync(FileSystemAccessOptions fsaOptions)
65+
=> await InternalShowOpenFilePickerAsync(null, fsaOptions);
66+
67+
protected async Task<TFsFileHandle[]> InternalShowOpenFilePickerAsync(object? options)
68+
=> await InternalShowOpenFilePickerAsync(options, FileSystemAccessOptions.DefaultInstance);
69+
protected async Task<TFsFileHandle[]> InternalShowOpenFilePickerAsync(object? options, FileSystemAccessOptions fsaOptions)
70+
{
71+
var helper = await helperTask.Value;
72+
var jSFileHandles = await jSRuntime.InvokeAsync<TObjReference>("window.showOpenFilePicker", options);
73+
var length = await helper.InvokeAsync<int>("size", jSFileHandles);
74+
75+
return await Task.WhenAll(
76+
Enumerable
77+
.Range(0, length)
78+
.Select(async i =>
79+
await this.CreateFileHandleAsync(
80+
jSRuntime,
81+
await jSFileHandles.InvokeAsync<TObjReference>("at", i),
82+
fsaOptions)
83+
)
84+
.ToArray()
85+
);
86+
}
87+
88+
#endregion
89+
90+
#region ShowSaveFilePickerAsync
91+
92+
/// <summary>
93+
/// <see href="https://wicg.github.io/file-system-access/#api-showsavefilepicker">showSaveFilePicker() browser specs</see>
94+
/// </summary>
95+
/// <param name="saveFilePickerOptions"></param>
96+
/// <returns></returns>
97+
public async Task<TFsFileHandle> ShowSaveFilePickerAsync(SaveFilePickerOptionsStartInWellKnownDirectory? saveFilePickerOptions)
98+
=> await InternalShowSaveFilePickerAsync(saveFilePickerOptions?.Serializable());
99+
100+
/// <summary>
101+
/// <see href="https://wicg.github.io/file-system-access/#api-showsavefilepicker">showSaveFilePicker() browser specs</see>
102+
/// </summary>
103+
/// <param name="saveFilePickerOptions"></param>
104+
/// <returns></returns>
105+
public async Task<TFsFileHandle> ShowSaveFilePickerAsync(SaveFilePickerOptionsStartInWellKnownDirectory? saveFilePickerOptions, FileSystemAccessOptions fsaOptions)
106+
=> await InternalShowSaveFilePickerAsync(saveFilePickerOptions?.Serializable(), fsaOptions);
107+
108+
/// <summary>
109+
/// <see href="https://wicg.github.io/file-system-access/#api-showsavefilepicker">showSaveFilePicker() browser specs</see>
110+
/// </summary>
111+
/// <param name="saveFilePickerOptions"></param>
112+
/// <returns></returns>
113+
public async Task<TFsFileHandle> ShowSaveFilePickerAsync(SaveFilePickerOptionsStartInFileSystemHandle? saveFilePickerOptions)
114+
=> await InternalShowSaveFilePickerAsync(saveFilePickerOptions?.Serializable());
115+
116+
/// <summary>
117+
/// <see href="https://wicg.github.io/file-system-access/#api-showsavefilepicker">showSaveFilePicker() browser specs</see>
118+
/// </summary>
119+
/// <param name="saveFilePickerOptions"></param>
120+
/// <returns></returns>
121+
public async Task<TFsFileHandle> ShowSaveFilePickerAsync(SaveFilePickerOptionsStartInFileSystemHandle? saveFilePickerOptions, FileSystemAccessOptions fsaOptions)
122+
=> await InternalShowSaveFilePickerAsync(saveFilePickerOptions?.Serializable(), fsaOptions);
123+
124+
/// <summary>
125+
/// <see href="https://wicg.github.io/file-system-access/#api-showsavefilepicker">showSaveFilePicker() browser specs</see>
126+
/// </summary>
127+
/// <returns></returns>
128+
public async Task<TFsFileHandle> ShowSaveFilePickerAsync()
129+
=> await InternalShowSaveFilePickerAsync(null);
130+
131+
/// <summary>
132+
/// <see href="https://wicg.github.io/file-system-access/#api-showsavefilepicker">showSaveFilePicker() browser specs</see>
133+
/// </summary>
134+
/// <returns></returns>
135+
public async Task<TFsFileHandle> ShowSaveFilePickerAsync(FileSystemAccessOptions options)
136+
=> await InternalShowSaveFilePickerAsync(null, options);
137+
138+
protected async Task<TFsFileHandle> InternalShowSaveFilePickerAsync(object? options)
139+
=> await this.InternalShowSaveFilePickerAsync(options, FileSystemAccessOptions.DefaultInstance);
140+
141+
protected async Task<TFsFileHandle> InternalShowSaveFilePickerAsync(object? options, FileSystemAccessOptions fsaOptions)
142+
{
143+
var jSFileHandle = await jSRuntime.InvokeAsync<TObjReference>("window.showSaveFilePicker", options);
144+
return await this.CreateFileHandleAsync(jSRuntime, jSFileHandle, fsaOptions);
145+
}
146+
147+
#endregion
148+
149+
#region ShowDirectoryPickerAsync
150+
151+
/// <summary>
152+
/// <see href="https://wicg.github.io/file-system-access/#api-showdirectorypicker">showDirectoryPicker() browser specs</see>
153+
/// </summary>
154+
/// <param name="directoryPickerOptions"></param>
155+
/// <returns></returns>
156+
public async Task<TFsDirectoryHandle> ShowDirectoryPickerAsync(DirectoryPickerOptionsStartInWellKnownDirectory? directoryPickerOptions)
157+
=> await InternalShowDirectoryPickerAsync(directoryPickerOptions?.Serializable());
158+
159+
/// <summary>
160+
/// <see href="https://wicg.github.io/file-system-access/#api-showdirectorypicker">showDirectoryPicker() browser specs</see>
161+
/// </summary>
162+
/// <param name="directoryPickerOptions"></param>
163+
/// <returns></returns>
164+
public async Task<TFsDirectoryHandle> ShowDirectoryPickerAsync(DirectoryPickerOptionsStartInWellKnownDirectory? directoryPickerOptions, FileSystemAccessOptions fasOptions)
165+
=> await InternalShowDirectoryPickerAsync(directoryPickerOptions?.Serializable(), fasOptions);
166+
167+
/// <summary>
168+
/// <see href="https://wicg.github.io/file-system-access/#api-showdirectorypicker">showDirectoryPicker() browser specs</see>
169+
/// </summary>
170+
/// <param name="directoryPickerOptions"></param>
171+
/// <returns></returns>
172+
public async Task<TFsDirectoryHandle> ShowDirectoryPickerAsync(DirectoryPickerOptionsStartInFileSystemHandle? directoryPickerOptions)
173+
=> await InternalShowDirectoryPickerAsync(directoryPickerOptions?.Serializable());
174+
175+
/// <summary>
176+
/// <see href="https://wicg.github.io/file-system-access/#api-showdirectorypicker">showDirectoryPicker() browser specs</see>
177+
/// </summary>
178+
/// <param name="directoryPickerOptions"></param>
179+
/// <returns></returns>
180+
public async Task<TFsDirectoryHandle> ShowDirectoryPickerAsync(DirectoryPickerOptionsStartInFileSystemHandle? directoryPickerOptions, FileSystemAccessOptions fasOptions)
181+
=> await InternalShowDirectoryPickerAsync(directoryPickerOptions?.Serializable(), fasOptions);
182+
183+
/// <summary>
184+
/// <see href="https://wicg.github.io/file-system-access/#api-showdirectorypicker">showDirectoryPicker() browser specs</see>
185+
/// </summary>
186+
/// <param name="directoryPickerOptions"></param>
187+
/// <returns></returns>
188+
public async Task<TFsDirectoryHandle> ShowDirectoryPickerAsync()
189+
=> await InternalShowDirectoryPickerAsync(null);
190+
191+
/// <summary>
192+
/// <see href="https://wicg.github.io/file-system-access/#api-showdirectorypicker">showDirectoryPicker() browser specs</see>
193+
/// </summary>
194+
/// <param name="directoryPickerOptions"></param>
195+
/// <returns></returns>
196+
public async Task<TFsDirectoryHandle> ShowDirectoryPickerAsync(FileSystemAccessOptions fasOptions)
197+
=> await InternalShowDirectoryPickerAsync(null, fasOptions);
198+
199+
protected async Task<TFsDirectoryHandle> InternalShowDirectoryPickerAsync(object? options)
200+
=> await InternalShowDirectoryPickerAsync(options, FileSystemAccessOptions.DefaultInstance);
201+
202+
protected async Task<TFsDirectoryHandle> InternalShowDirectoryPickerAsync(object? options, FileSystemAccessOptions fasOptions)
203+
{
204+
var jSFileHandle = await jSRuntime.InvokeAsync<TObjReference>("window.showDirectoryPicker", options);
205+
return await this.CreateDirectoryHandleAsync(jSRuntime, jSFileHandle, fasOptions);
206+
}
207+
208+
#endregion
209+
210+
#region GetOriginPrivateDirectoryAsync
211+
212+
/// <summary>
213+
/// <see href="https://wicg.github.io/file-system-access/#dom-storagemanager-getdirectory">getDirectory() for StorageManager browser specs</see>
214+
/// </summary>
215+
/// <returns></returns>
216+
public async Task<TFsDirectoryHandle> GetOriginPrivateDirectoryAsync()
217+
=> await this.GetOriginPrivateDirectoryAsync(FileSystemAccessOptions.DefaultInstance);
218+
219+
/// <summary>
220+
/// <see href="https://wicg.github.io/file-system-access/#dom-storagemanager-getdirectory">getDirectory() for StorageManager browser specs</see>
221+
/// </summary>
222+
/// <returns></returns>
223+
public async Task<TFsDirectoryHandle> GetOriginPrivateDirectoryAsync(FileSystemAccessOptions fasOptions)
224+
{
225+
var jSFileHandle = await jSRuntime.InvokeAsync<TObjReference>("navigator.storage.getDirectory");
226+
return await CreateDirectoryHandleAsync(jSRuntime, jSFileHandle, fasOptions);
227+
}
228+
229+
#endregion
230+
231+
#region Common Handling Methods
232+
233+
protected abstract Task<TFsFileHandle> CreateFileHandleAsync(IJSRuntime jSRuntime, TObjReference jSReference, FileSystemAccessOptions options);
234+
protected abstract Task<TFsDirectoryHandle> CreateDirectoryHandleAsync(IJSRuntime jSRuntime, TObjReference jSReference, FileSystemAccessOptions options);
235+
236+
#endregion
237+
238+
/// <summary>
239+
/// Meta method for the wrapper that checks if the API is available in the current browser.
240+
/// </summary>
241+
/// <returns></returns>
242+
public async Task<bool> IsSupportedAsync()
243+
{
244+
return
245+
await jSRuntime.InvokeAsync<bool>("window.hasOwnProperty", "showOpenFilePicker") &
246+
await jSRuntime.InvokeAsync<bool>("window.hasOwnProperty", "showSaveFilePicker") &
247+
await jSRuntime.InvokeAsync<bool>("window.hasOwnProperty", "showDirectoryPicker");
248+
}
249+
250+
public async ValueTask DisposeAsync()
251+
{
252+
if (helperTask.IsValueCreated)
253+
{
254+
IJSObjectReference module = await helperTask.Value;
255+
await module.DisposeAsync();
256+
}
257+
GC.SuppressFinalize(this);
258+
}
259+
}

src/KristofferStrube.Blazor.FileSystemAccess/BaseJSWrapper.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,17 @@ public abstract class BaseJSWrapper : IAsyncDisposable
88
public readonly IJSObjectReference JSReference;
99
protected readonly Lazy<Task<IJSObjectReference>> helperTask;
1010
protected readonly IJSRuntime jSRuntime;
11+
protected readonly FileSystemAccessOptions options;
1112

1213
/// <summary>
1314
/// Constructs a wrapper instance for an equivalent JS instance.
1415
/// </summary>
1516
/// <param name="jSRuntime">An <see cref="IJSRuntime"/> instance.</param>
1617
/// <param name="jSReference">A JS reference to an existing JS instance that should be wrapped.</param>
17-
internal BaseJSWrapper(IJSRuntime jSRuntime, IJSObjectReference jSReference)
18+
internal BaseJSWrapper(IJSRuntime jSRuntime, IJSObjectReference jSReference, FileSystemAccessOptions options)
1819
{
19-
helperTask = new(jSRuntime.GetHelperAsync);
20+
this.options = options;
21+
helperTask = new(async () => await jSRuntime.GetHelperAsync(options));
2022
JSReference = jSReference;
2123
this.jSRuntime = jSRuntime;
2224
}

src/KristofferStrube.Blazor.FileSystemAccess/Extensions/IJSRuntimeExtensions.cs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@ namespace KristofferStrube.Blazor.FileSystemAccess.Extensions;
44

55
internal static class IJSRuntimeExtensions
66
{
7-
internal static async Task<IJSObjectReference> GetHelperAsync(this IJSRuntime jSRuntime)
8-
{
9-
return await jSRuntime.InvokeAsync<IJSObjectReference>(
10-
"import", "./_content/KristofferStrube.Blazor.FileSystemAccess/KristofferStrube.Blazor.FileSystemAccess.js");
11-
}
7+
internal static async Task<IJSObjectReference> GetHelperAsync(this IJSRuntime jSRuntime, FileSystemAccessOptions options)
8+
=> await GetHelperAsync<IJSObjectReference>(jSRuntime, options);
129

13-
internal static async Task<IJSInProcessObjectReference> GetInProcessHelperAsync(this IJSRuntime jSRuntime)
14-
{
15-
return await jSRuntime.InvokeAsync<IJSInProcessObjectReference>(
16-
"import", "./_content/KristofferStrube.Blazor.FileSystemAccess/KristofferStrube.Blazor.FileSystemAccess.js");
17-
}
10+
internal static async Task<IJSInProcessObjectReference> GetInProcessHelperAsync(this IJSRuntime jSRuntime, FileSystemAccessOptions options) =>
11+
await GetHelperAsync<IJSInProcessObjectReference>(jSRuntime, options);
12+
13+
static async Task<T> GetHelperAsync<T>(IJSRuntime jSRuntime, FileSystemAccessOptions options) =>
14+
await jSRuntime.InvokeAsync<T>("import", GetScriptPath(options));
15+
16+
static string GetScriptPath(FileSystemAccessOptions options) => options.FullScriptPath;
1817
}
Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,47 @@
11
using Microsoft.Extensions.DependencyInjection;
22
using Microsoft.JSInterop;
3+
using System.Diagnostics;
34

45
namespace KristofferStrube.Blazor.FileSystemAccess;
56

67
public static class IServiceCollectionExtensions
78
{
89
public static IServiceCollection AddFileSystemAccessService(this IServiceCollection serviceCollection)
910
{
11+
return AddFileSystemAccessService(serviceCollection, null);
12+
}
13+
14+
public static IServiceCollection AddFileSystemAccessService(this IServiceCollection serviceCollection, Action<FileSystemAccessOptions>? configure)
15+
{
16+
ConfigureFsaOptions(serviceCollection, configure);
17+
1018
return serviceCollection.AddScoped<IFileSystemAccessService, FileSystemAccessService>();
1119
}
20+
1221
public static IServiceCollection AddFileSystemAccessServiceInProcess(this IServiceCollection serviceCollection)
1322
{
23+
return AddFileSystemAccessServiceInProcess(serviceCollection, null);
24+
}
25+
26+
public static IServiceCollection AddFileSystemAccessServiceInProcess(this IServiceCollection serviceCollection, Action<FileSystemAccessOptions>? configure)
27+
{
28+
ConfigureFsaOptions(serviceCollection, configure);
29+
1430
return serviceCollection
15-
.AddScoped<IFileSystemAccessServiceInProcess>(sp => new FileSystemAccessServiceInProcess((IJSInProcessRuntime)sp.GetRequiredService<IJSRuntime>()))
16-
.AddScoped<IFileSystemAccessService>(sp => sp.GetRequiredService<IFileSystemAccessServiceInProcess>());
31+
.AddScoped<IFileSystemAccessServiceInProcess, FileSystemAccessServiceInProcess>()
32+
.AddScoped(sp =>
33+
{
34+
var service = sp.GetRequiredService<IFileSystemAccessServiceInProcess>();
35+
return (IFileSystemAccessService)service;
36+
});
1737
}
38+
39+
static void ConfigureFsaOptions(IServiceCollection services, Action<FileSystemAccessOptions>? configure)
40+
{
41+
if (configure is null) { return; }
42+
43+
services.Configure(configure);
44+
configure(FileSystemAccessOptions.DefaultInstance);
45+
}
46+
1847
}

0 commit comments

Comments
 (0)