diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index 5f116f95b6..587b34c9fe 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -82,7 +82,13 @@ namespace ControlCatalog.Pages return new List { FilePickerFileTypes.All, - FilePickerFileTypes.TextPlain + FilePickerFileTypes.TextPlain, + new("Binary Log") + { + Patterns = new[] { "*.binlog", "*.buildlog" }, + MimeTypes = new[] { "application/binlog", "application/buildlog" }, + AppleUniformTypeIdentifiers = new []{ "public.data" } + } }; } diff --git a/src/Avalonia.Base/Platform/Storage/FilePickerFileType.cs b/src/Avalonia.Base/Platform/Storage/FilePickerFileType.cs index f9c7f9685d..7b0446e224 100644 --- a/src/Avalonia.Base/Platform/Storage/FilePickerFileType.cs +++ b/src/Avalonia.Base/Platform/Storage/FilePickerFileType.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.IO; +using System.Linq; namespace Avalonia.Platform.Storage; @@ -21,7 +23,7 @@ public sealed class FilePickerFileType /// List of extensions in GLOB format. I.e. "*.png" or "*.*". /// /// - /// Used on Windows and Linux systems. + /// Used on Windows, Linux and Browser platforms. /// public IReadOnlyList? Patterns { get; set; } @@ -29,7 +31,7 @@ public sealed class FilePickerFileType /// List of extensions in MIME format. /// /// - /// Used on Android, Browser and Linux systems. + /// Used on Android, Linux and Browser platforms. /// public IReadOnlyList? MimeTypes { get; set; } @@ -41,4 +43,14 @@ public sealed class FilePickerFileType /// See https://developer.apple.com/documentation/uniformtypeidentifiers/system_declared_uniform_type_identifiers. /// public IReadOnlyList? AppleUniformTypeIdentifiers { get; set; } + + internal IReadOnlyList? TryGetExtensions() + { + // Converts random glob pattern to a simple extension name. + // GetExtension should be sufficient here. + // Only exception is "*.*proj" patterns that should be filtered as well. + return Patterns?.Select(Path.GetExtension) + .Where(e => !string.IsNullOrEmpty(e) && !e.Contains('*') && e.StartsWith(".")) + .ToArray()!; + } } diff --git a/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs b/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs index a4d7bcdb87..902dd03885 100644 --- a/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs +++ b/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs @@ -54,5 +54,5 @@ internal static partial class StorageHelper public static partial JSObject[] ItemsArray(JSObject item); [JSImport("StorageProvider.createAcceptType", AvaloniaModule.StorageModuleName)] - public static partial JSObject CreateAcceptType(string description, string[] mimeTypes); + public static partial JSObject CreateAcceptType(string description, string[] mimeTypes, string[]? extensions); } diff --git a/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs b/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs index 7189ae4111..08d7664719 100644 --- a/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs +++ b/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs @@ -147,7 +147,7 @@ internal class BrowserStorageProvider : IStorageProvider { var types = input? .Where(t => t.MimeTypes?.Any() == true && t != FilePickerFileTypes.All) - .Select(t => StorageHelper.CreateAcceptType(t.Name, t.MimeTypes!.ToArray())) + .Select(t => StorageHelper.CreateAcceptType(t.Name, t.MimeTypes!.ToArray(), t.TryGetExtensions()?.ToArray())) .ToArray(); if (types?.Length == 0) { diff --git a/src/Browser/Avalonia.Browser/webapp/modules/storage/storageProvider.ts b/src/Browser/Avalonia.Browser/webapp/modules/storage/storageProvider.ts index e621a1ed30..8259510ed2 100644 --- a/src/Browser/Avalonia.Browser/webapp/modules/storage/storageProvider.ts +++ b/src/Browser/Avalonia.Browser/webapp/modules/storage/storageProvider.ts @@ -62,9 +62,9 @@ export class StorageProvider { } } - public static createAcceptType(description: string, mimeTypes: string[]): FilePickerAcceptType { + public static createAcceptType(description: string, mimeTypes: string[], extensions: string[] | undefined): FilePickerAcceptType { const accept: Record = {}; - mimeTypes.forEach(a => { accept[a] = []; }); + mimeTypes.forEach(a => { accept[a] = extensions ?? []; }); return { description, accept }; } } diff --git a/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs b/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs index 376e988e74..ae2bf2a99d 100644 --- a/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs +++ b/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs @@ -43,6 +43,10 @@ internal class IOSStorageProvider : IStorageProvider { return f.AppleUniformTypeIdentifiers.Select(id => UTType.CreateFromIdentifier(id)); } + if (f.TryGetExtensions() is { } extensions && extensions.Any()) + { + return extensions.Select(id => UTType.CreateFromExtension(id.TrimStart('.'))); + } if (f.MimeTypes?.Any() == true) { return f.MimeTypes.Select(id => UTType.CreateFromMimeType(id));