Skip to content

Commit

Permalink
feat: Support for Native FileSavePicker
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinZikmund committed Mar 7, 2021
1 parent a59bd2d commit d9e32fd
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 9 deletions.
2 changes: 2 additions & 0 deletions src/Uno.UI/LinkerDefinition.Wasm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@
<type fullname="Uno.Storage.Internal.NativeStorageItemInfo" />
<type fullname="Uno.ApplicationModel.Contacts.Internal.WasmContact" />
<type fullname="Uno.ApplicationModel.Contacts.Internal.WasmContactAddress" />
<type fullname="Uno.Storage.Pickers.Internal.NativeFilePickerAcceptType" />
<type fullname="Uno.Storage.Pickers.Internal.NativeFilePickerAcceptTypeItem" />
</assembly>

<assembly fullname="Uno.Foundation">
Expand Down
43 changes: 43 additions & 0 deletions src/Uno.UI/ts/Windows/Storage/Pickers/FileSavePicker.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,49 @@
namespace Windows.Storage.Pickers {

export class FileSavePicker {
public static isNativeSupported(): boolean {
return typeof showSaveFilePicker === "function";
}

public static async pickSaveFileAsync(showAllEntry: boolean, fileTypesJson: any): Promise<string> {

if (!FileSavePicker.isNativeSupported()) {
return null;
}

const options: SaveFilePickerOptions = {
excludeAcceptAllOption: !showAllEntry,
types: [],
};

const acceptTypes = <Uno.Storage.Pickers.NativeFilePickerAcceptType[]>JSON.parse(fileTypesJson);

for (var acceptType of acceptTypes) {
const pickerAcceptType: FilePickerAcceptType = {
accept: {},
description: acceptType.description,
};

for (var acceptTypeItem of acceptType.accept) {
pickerAcceptType.accept[acceptTypeItem.mimeType] = acceptTypeItem.extensions
}

options.types.push(pickerAcceptType);
}

try {
const selectedFile = await showSaveFilePicker(options);
const info = Uno.Storage.NativeStorageItem.getInfos(selectedFile)[0];
const json = JSON.stringify(info);
return json;
}
catch (e) {
console.log("User did not make a selection or it file selected was" +
"deemed too sensitive or dangerous to be exposed to the website - " + e);
return null;
}
}

public static SaveAs(fileName: string, dataPtr: any, size: number): void {

const buffer = new Uint8Array(size);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Uno.Storage.Pickers {
export class NativeFilePickerAcceptType {
description: string;
accept: NativeFilePickerAcceptTypeItem[];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Uno.Storage.Pickers {
export class NativeFilePickerAcceptTypeItem {
mimeType: string;
extensions: string[];
}
}
10 changes: 10 additions & 0 deletions src/Uno.UWP/Helpers/Serialization/JsonHelper.wasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,15 @@ public static bool TryDeserialize<T>(string json, out T value)
return false;
}
}

public static string Serialize<T>(T value)
{
using var stream = new MemoryStream();
var serializer = new DataContractJsonSerializer(typeof(T));
serializer.WriteObject(stream, value);
stream.Position = 0;
using StreamReader reader = new StreamReader(stream);
return reader.ReadToEnd();
}
}
}
2 changes: 0 additions & 2 deletions src/Uno.UWP/Storage/Pickers/FileOpenPicker.wasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ namespace Windows.Storage.Pickers
public partial class FileOpenPicker
{
private const string JsType = "Windows.Storage.Pickers.FileOpenPicker";
private const string FileSeparator = "\\\\";
private const char FileInfoSeparator = '\\';

private async Task<StorageFile?> PickSingleFileTaskAsync(CancellationToken token)
{
Expand Down
71 changes: 64 additions & 7 deletions src/Uno.UWP/Storage/Pickers/FileSavePicker.wasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,83 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Uno.Extensions;
using Uno;
using Uno.Foundation;
using Windows.Foundation;
using Windows.Storage.Provider;
using Uno.Helpers.Serialization;
using Uno.Storage.Internal;
using Uno.Storage.Pickers.Internal;

namespace Windows.Storage.Pickers
{
public partial class FileSavePicker
{
private const string JsType = "Windows.Storage.Pickers.FileSavePicker";

private async Task<StorageFile?> PickSaveFileTaskAsync(CancellationToken token)
{
if (!FileTypeChoices.Any())
if (WinRTFeatureConfiguration.Storage.Pickers.AllowWasmNativePickers && IsNativePickerSupported())
{
throw new InvalidOperationException();
return await NativePickerPickSaveFileAsync(token);
}

// Fallback to download-based picker.
return await DownloadPickerPickSaveFileAsync(token);
}

private bool IsNativePickerSupported()
{
var isSupportedString = WebAssemblyRuntime.InvokeJS($"{JsType}.isNativeSupported()");
return bool.TryParse(isSupportedString, out var isSupported) && isSupported;
}

private async Task<StorageFile?> NativePickerPickSaveFileAsync(CancellationToken token)
{
var showAllEntryParameter = "true";
var fileTypeMapParameter = JsonHelper.Serialize(BuildFileTypesMap());

var promise = $"{JsType}.pickSaveFileAsync({showAllEntryParameter},'{WebAssemblyRuntime.EscapeJs(fileTypeMapParameter)}')";
var nativeStorageItemInfo = await WebAssemblyRuntime.InvokeAsync(promise);
if (nativeStorageItemInfo is null)
{
return null;
}

var info = JsonHelper.Deserialize<NativeStorageItemInfo>(nativeStorageItemInfo);
return StorageFile.GetFromNativeInfo(info);
}

private NativeFilePickerAcceptType[] BuildFileTypesMap()
{
var acceptTypes = new List<NativeFilePickerAcceptType>();

var mimeTypeMap = new Dictionary<string, List<string>>();
foreach (var choice in FileTypeChoices)
{
var acceptType = new NativeFilePickerAcceptType();
acceptType.Description = choice.Key;

var acceptItems = new List<NativeFilePickerAcceptTypeItem>();
foreach (var extension in choice.Value)
{
var acceptItem = new NativeFilePickerAcceptTypeItem()
{
MimeType = MimeTypeService.GetFromExtension(extension),
Extensions = new[] { extension }
};
acceptItems.Add(acceptItem);
}

acceptType.Accept = acceptItems.ToArray();
acceptTypes.Add(acceptType);
}

return acceptTypes.ToArray();
}

private async Task<StorageFile?> DownloadPickerPickSaveFileAsync(CancellationToken token)
{
if (SuggestedSaveFile == null)
{
var temporaryFolder = ApplicationData.Current.LocalCacheFolder;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Runtime.Serialization;

namespace Uno.Storage.Pickers.Internal
{
[DataContract]
internal class NativeFilePickerAcceptType
{
[DataMember(Name = "description")]
public string Description { get; set; }

[DataMember(Name = "accept")]
public NativeFilePickerAcceptTypeItem[] Accept { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Runtime.Serialization;

namespace Uno.Storage.Pickers.Internal
{
[DataContract]
internal class NativeFilePickerAcceptTypeItem
{
[DataMember(Name = "mimeType")]
public string MimeType { get; set; }

[DataMember(Name = "extensions")]
public string[] Extensions { get; set; }
}
}

0 comments on commit d9e32fd

Please sign in to comment.