Skip to content
Merged
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
3 changes: 2 additions & 1 deletion docs/docs/guides/core-window.md
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,8 @@ string?[] folders = window.ShowOpenFolder("Select Folder", multiSelect: false);
string? path = window.ShowSaveFile(
title: "Save As",
defaultPath: null,
filters: [("Text Files", ["txt"])]
filters: [("Text Files", ["txt"])],
defaultFileName: "document.txt"
);
```

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/migration/photino-backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ This backlog will be used to track any remaining issues or features that need to
| 📝 | Interoperability between C # and JavaScript | [Photino.NET#120](https://github.com/tryphotino/photino.NET/issues/120) | [InfiniFrame#294](https://github.com/InfiniLore/InfiniFrame/issues/219) |
| ✅ | Window.external is deprecated by browsers | [Photino.NET#124](https://github.com/tryphotino/photino.NET/issues/124) | |
| 📝 | Intercept navigation events | [Photino.NET#139](https://github.com/tryphotino/photino.NET/issues/139) | [InfiniFrame#294](https://github.com/InfiniLore/InfiniFrame/issues/294) |
| 📝 | ShowSaveFile How to set the default file name | [Photino.NET#140](https://github.com/tryphotino/photino.NET/issues/140) | [InfiniFrame#295](https://github.com/InfiniLore/InfiniFrame/issues/295) |
| | ShowSaveFile How to set the default file name | [Photino.NET#140](https://github.com/tryphotino/photino.NET/issues/140) | [InfiniFrame#295](https://github.com/InfiniLore/InfiniFrame/issues/295) |
| 📝 | Running as administrator disables other instances | [Photino.NET#162](https://github.com/tryphotino/photino.NET/issues/162) | [InfiniFrame#296](https://github.com/InfiniLore/InfiniFrame/issues/296) |
| ❌ | Mica/Fluent design window for windows | [Photino.NET#167](https://github.com/tryphotino/photino.NET/issues/167) | |
| 📝 | Retrieve current Url of PhotinoWindow | [Photino.NET#197](https://github.com/tryphotino/photino.NET/issues/197) | [InfiniFrame#297](https://github.com/InfiniLore/InfiniFrame/issues/297) |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ import type {FilePickerFilter} from "./WindowFeatureTypes";
export interface FilePickerDialogsInfiniFrameWindowFeature {
showOpenFileAsync(title?: string, defaultPath?: string | null, multiSelect?: boolean, filters?: FilePickerFilter[] | null): Promise<(string | null)[]>;
showOpenFolderAsync(title?: string, defaultPath?: string | null, multiSelect?: boolean): Promise<(string | null)[]>;
showSaveFileAsync(title?: string, defaultPath?: string | null, filters?: FilePickerFilter[] | null): Promise<string | null>;
showSaveFileAsync(title?: string, defaultPath?: string | null, filters?: FilePickerFilter[] | null, defaultFileName?: string | null): Promise<string | null>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class FilePickerDialogsInfiniFrameWindowFeature extends InfiniFrameWindow
return this.get<(string | null)[]>("showOpenFolder", {title, defaultPath, multiSelect});
}

showSaveFileAsync(title = "Save file", defaultPath: string | null = null, filters: FilePickerFilter[] | null = null) {
return this.get<string | null>("showSaveFile", {title, defaultPath, filters});
showSaveFileAsync(title = "Save file", defaultPath: string | null = null, filters: FilePickerFilter[] | null = null, defaultFileName: string | null = null) {
return this.get<string | null>("showSaveFile", {title, defaultPath, filters, defaultFileName});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ const contracts: FeatureContract[] = [
gets: [
{method: "showOpenFileAsync", command: "showOpenFile", parameters: ["Open", "/tmp", true, [{name: "Text", extensions: ["txt"]}]], args: {title: "Open", defaultPath: "/tmp", multiSelect: true, filters: [{name: "Text", extensions: ["txt"]}]}, result: ["/tmp/a.txt"]},
{method: "showOpenFolderAsync", command: "showOpenFolder", parameters: ["Folder", "/tmp", true], args: {title: "Folder", defaultPath: "/tmp", multiSelect: true}, result: ["/tmp"]},
{method: "showSaveFileAsync", command: "showSaveFile", parameters: ["Save", "/tmp/a.txt", null], args: {title: "Save", defaultPath: "/tmp/a.txt", filters: null}, result: "/tmp/a.txt"}
{method: "showSaveFileAsync", command: "showSaveFile", parameters: ["Save", "/tmp/a.txt", null, null], args: {title: "Save", defaultPath: "/tmp/a.txt", filters: null, defaultFileName: null}, result: "/tmp/a.txt"},
{method: "showSaveFileAsync", command: "showSaveFile", parameters: ["Save", "/tmp/a.txt", null, "document.txt"], args: {title: "Save", defaultPath: "/tmp/a.txt", filters: null, defaultFileName: "document.txt"}, result: "/tmp/a.txt"}
]
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,18 @@ public interface IFilePickerDialogsInfiniFrameWindowFeature {
/// <param name="title">The dialog title.</param>
/// <param name="defaultPath">The default directory path.</param>
/// <param name="filters">File extension filters.</param>
/// <param name="defaultFileName">The default file name to pre-populate in the save dialog.</param>
/// <returns>The selected file path, or <c>null</c> if cancelled.</returns>
string? ShowSaveFile(string title = "Save file", string? defaultPath = null, (string Name, string[] Extensions)[]? filters = null);
string? ShowSaveFile(string title = "Save file", string? defaultPath = null, (string Name, string[] Extensions)[]? filters = null, string? defaultFileName = null);

/// <summary>
/// Shows a save file dialog asynchronously and returns the selected file path.
/// </summary>
/// <param name="title">The dialog title.</param>
/// <param name="defaultPath">The default directory path.</param>
/// <param name="filters">File extension filters.</param>
/// <param name="defaultFileName">The default file name to pre-populate in the save dialog.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>A task that resolves to the selected file path, or <c>null</c> if cancelled.</returns>
Task<string?> ShowSaveFileAsync(string title = "Choose file", string? defaultPath = null, (string Name, string[] Extensions)[]? filters = null, CancellationToken ct = default);
Task<string?> ShowSaveFileAsync(string title = "Choose file", string? defaultPath = null, (string Name, string[] Extensions)[]? filters = null, string? defaultFileName = null, CancellationToken ct = default);
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,10 @@ public static class IFilePickerDialogsInfiniFrameWindowFeatureExtensions {
/// <param name="title">The dialog title.</param>
/// <param name="defaultPath">The default directory path.</param>
/// <param name="filters">File extension filters.</param>
/// <param name="defaultFileName">The default file name to pre-populate in the save dialog.</param>
/// <returns>The selected file path, or <c>null</c> if cancelled.</returns>
public static string? ShowSaveFile(this IInfiniFrameWindow window, string title = "Save file", string? defaultPath = null, (string Name, string[] Extensions)[]? filters = null)
=> window.Features.FilePickerDialogs.ShowSaveFile(title, defaultPath, filters);
public static string? ShowSaveFile(this IInfiniFrameWindow window, string title = "Save file", string? defaultPath = null, (string Name, string[] Extensions)[]? filters = null, string? defaultFileName = null)
=> window.Features.FilePickerDialogs.ShowSaveFile(title, defaultPath, filters, defaultFileName);

/// <summary>
/// Shows a save file dialog asynchronously and returns the selected file path.
Expand All @@ -72,8 +73,9 @@ public static class IFilePickerDialogsInfiniFrameWindowFeatureExtensions {
/// <param name="title">The dialog title.</param>
/// <param name="defaultPath">The default directory path.</param>
/// <param name="filters">File extension filters.</param>
/// <param name="defaultFileName">The default file name to pre-populate in the save dialog.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>A task that resolves to the selected file path, or <c>null</c> if cancelled.</returns>
public static Task<string?> ShowSaveFileAsync(this IInfiniFrameWindow window, string title = "Choose file", string? defaultPath = null, (string Name, string[] Extensions)[]? filters = null, CancellationToken ct = default)
=> window.Features.FilePickerDialogs.ShowSaveFileAsync(title, defaultPath, filters, ct);
public static Task<string?> ShowSaveFileAsync(this IInfiniFrameWindow window, string title = "Choose file", string? defaultPath = null, (string Name, string[] Extensions)[]? filters = null, string? defaultFileName = null, CancellationToken ct = default)
=> window.Features.FilePickerDialogs.ShowSaveFileAsync(title, defaultPath, filters, defaultFileName, ct);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,18 @@ ILogger<FilePickerDialogsInfiniFrameWindowFeature> logger

/// <inheritdoc cref="IFilePickerDialogsInfiniFrameWindowFeature.ShowOpenFileAsync" />
public Task<string?[]> ShowOpenFileAsync(string title = DefaultFilePickerTitle, string? defaultPath = null, bool multiSelect = false, (string Name, string[] Extensions)[]? filters = null, CancellationToken ct = default)
=> ShowDialogAsync(InfiniFileDialogKind.OpenFile, title, defaultPath, multiSelect, filters, ct);
=> ShowDialogAsync(InfiniFileDialogKind.OpenFile, title, defaultPath, multiSelect, filters, null, ct);

/// <inheritdoc cref="IFilePickerDialogsInfiniFrameWindowFeature.ShowOpenFolder" />
public string?[] ShowOpenFolder(string title = DefaultFolderPickerTitle, string? defaultPath = null, bool multiSelect = false)
=> ShowOpenDialog(true, title, defaultPath, multiSelect, null);

/// <inheritdoc cref="IFilePickerDialogsInfiniFrameWindowFeature.ShowOpenFolderAsync" />
public Task<string?[]> ShowOpenFolderAsync(string title = DefaultFolderPickerTitle, string? defaultPath = null, bool multiSelect = false, CancellationToken ct = default)
=> ShowDialogAsync(InfiniFileDialogKind.OpenFolder, title, defaultPath, multiSelect, null, ct);
=> ShowDialogAsync(InfiniFileDialogKind.OpenFolder, title, defaultPath, multiSelect, null, null, ct);

/// <inheritdoc cref="IFilePickerDialogsInfiniFrameWindowFeature.ShowSaveFile" />
public string? ShowSaveFile(string title = DefaultSaveFilePickerTitle, string? defaultPath = null, (string Name, string[] Extensions)[]? filters = null) {
public string? ShowSaveFile(string title = DefaultSaveFilePickerTitle, string? defaultPath = null, (string Name, string[] Extensions)[]? filters = null, string? defaultFileName = null) {
if (window.IsClosedOrClosing()) return null;

defaultPath ??= Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
Expand All @@ -54,15 +54,15 @@ ILogger<FilePickerDialogsInfiniFrameWindowFeature> logger
defaultPath,
nativeFilters,
filters.Length,
null// TODO actually assign this
defaultFileName
);

return result;
}

/// <inheritdoc cref="IFilePickerDialogsInfiniFrameWindowFeature.ShowSaveFileAsync" />
public Task<string?> ShowSaveFileAsync(string title = DefaultSaveFilePickerTitle, string? defaultPath = null, (string Name, string[] Extensions)[]? filters = null, CancellationToken ct = default)
=> ShowSaveDialogAsync(title, defaultPath, filters, ct);
public Task<string?> ShowSaveFileAsync(string title = DefaultSaveFilePickerTitle, string? defaultPath = null, (string Name, string[] Extensions)[]? filters = null, string? defaultFileName = null, CancellationToken ct = default)
=> ShowSaveDialogAsync(title, defaultPath, filters, defaultFileName, ct);

private string?[] ShowOpenDialog(bool foldersOnly, string title, string? defaultPath, bool multiSelect, (string Name, string[] Extensions)[]? filters) {
if (window.IsClosedOrClosing()) return [];
Expand Down Expand Up @@ -107,6 +107,7 @@ ILogger<FilePickerDialogsInfiniFrameWindowFeature> logger
string? defaultPath,
bool multiSelect,
(string Name, string[] Extensions)[]? filters,
string? defaultFileName,
CancellationToken ct
) {
ct.ThrowIfCancellationRequested();
Expand All @@ -115,7 +116,7 @@ CancellationToken ct
filters ??= [];
var operation = new InfiniFileDialogOperation(
window, logger, kind, title, defaultPath, multiSelect,
GetNativeFilters(filters, kind == InfiniFileDialogKind.OpenFolder), ct
GetNativeFilters(filters, kind == InfiniFileDialogKind.OpenFolder), defaultFileName, ct
);
_ = operation.StartAsync();
return await operation.Task.WaitAsync(ct).ConfigureAwait(false);
Expand All @@ -125,10 +126,11 @@ CancellationToken ct
string title,
string? defaultPath,
(string Name, string[] Extensions)[]? filters,
string? defaultFileName,
CancellationToken ct
) {
string?[] values = await ShowDialogAsync(
InfiniFileDialogKind.SaveFile, title, defaultPath, false, filters, ct
InfiniFileDialogKind.SaveFile, title, defaultPath, false, filters, defaultFileName, ct
).ConfigureAwait(false);
return values.FirstOrDefault();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ protected override IFilePickerDialogsInfiniFrameWindowFeature SelectFeature(IInf
string? defaultPath = Arg<string?>(args, "defaultPath", null);
WindowFeatureFilePickerFilter[]? filterDtos = Arg<WindowFeatureFilePickerFilter[]?>(args, "filters", null);
(string Name, string[] Extensions)[]? filters = filterDtos?.Select(filter => (filter.Name, filter.Extensions)).ToArray();
string? defaultFileName = Arg<string?>(args, "defaultFileName", null);

return command switch {
"showOpenFile" => feature.ShowOpenFile(Arg(args, "title", "Choose file"), defaultPath, Arg(args, "multiSelect", false), filters),
"showOpenFolder" => feature.ShowOpenFolder(Arg(args, "title", "Select folder"), defaultPath, Arg(args, "multiSelect", false)),
"showSaveFile" => feature.ShowSaveFile(Arg(args, "title", "Save file"), defaultPath, filters),
"showSaveFile" => feature.ShowSaveFile(Arg(args, "title", "Save file"), defaultPath, filters, defaultFileName),
_ => throw Unsupported(command)
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ internal sealed class InfiniFileDialogOperation {
private readonly string _defaultPath;
private readonly bool _multiSelect;
private readonly string[] _filters;
private readonly string? _defaultFileName;
private readonly CancellationToken _cancellationToken;
private readonly string? _diagnosticKey;
private readonly TaskCompletionSource<string?[]> _completion =
Expand All @@ -48,6 +49,7 @@ public InfiniFileDialogOperation(
string defaultPath,
bool multiSelect,
string[] filters,
string? defaultFileName,
CancellationToken cancellationToken
) {
_window = window;
Expand All @@ -57,6 +59,7 @@ CancellationToken cancellationToken
_defaultPath = defaultPath;
_multiSelect = multiSelect;
_filters = filters;
_defaultFileName = defaultFileName;
_cancellationToken = cancellationToken;
_diagnosticKey = (window as InfiniFrameWindow)?.BeginDiagnosticOperation(kind.ToString(), Id);
}
Expand All @@ -79,7 +82,7 @@ public async Task StartAsync() {
_lease.Handle, Id, _title, _defaultPath, _multiSelect, CompletionCallback, context),
InfiniFileDialogKind.SaveFile => InfiniFrameNative.BeginShowSaveFile(
_lease.Handle, Id, _title, _defaultPath,
_filters, _filters.Length, string.Empty, CompletionCallback, context),
_filters, _filters.Length, _defaultFileName ?? string.Empty, CompletionCallback, context),
_ => InfiniFrameNativeInteropStatus.InvalidArgument
};
if (status != InfiniFrameNativeInteropStatus.Success)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
using InfiniFrame;

namespace InfiniTests.InfiniFrame.Window.Features.FilePickerDialogs;
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
public class FilePickerDialogsDefaultFileNameTests {
[Test]
[NotInParallelInfiniTests]
public async Task ShowSaveFile_WithDefaultFileName_ShouldAcceptParameter(CancellationToken ct) {
// Arrange
using var windowUtility = InfiniFrameTestWindow.Create(ct);
IInfiniFrameWindow window = windowUtility.Window;
await EnsureWindowClosed(window, ct);

// Act
string? result = window.Features.FilePickerDialogs.ShowSaveFile(defaultFileName: "document.txt");

// Assert
await Assert.That(result).IsNull();
}

[Test]
[NotInParallelInfiniTests]
public async Task ShowSaveFile_Extension_WithDefaultFileName_ShouldAcceptParameter(CancellationToken ct) {
// Arrange
using var windowUtility = InfiniFrameTestWindow.Create(ct);
IInfiniFrameWindow window = windowUtility.Window;
await EnsureWindowClosed(window, ct);

// Act
string? result = window.ShowSaveFile(defaultFileName: "document.txt");

// Assert
await Assert.That(result).IsNull();
}

private static async Task EnsureWindowClosed(IInfiniFrameWindow window, CancellationToken ct) {
window.Close();
DateTime timeoutAt = DateTime.UtcNow.AddSeconds(5);
while (!window.IsClosedOrClosing() && DateTime.UtcNow < timeoutAt) {
await Task.Delay(50, ct);
}
}
}
Loading