Files
PowerToys/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/NewExtensionForm.cs
Niels Laute c6cee94456 [CmdPal] UX tweaks (#40381)
Based on guidance from the design team, this PR introduces a bunch of
small UX tweaks:

- Standardizing body text on 14px (e.g. for the Adaptive Cards related
code).
- Left align all content in the details pane
- Brush tweaks to the hotkey / tags for better visibility


![image](https://github.com/user-attachments/assets/4d9bf699-29bb-42e0-af96-b9b72c34f259)


![image](https://github.com/user-attachments/assets/905a268b-2e29-408c-a301-10a98b5885f1)


![image](https://github.com/user-attachments/assets/b9050693-f4bb-4d74-8701-fb30b46698e0)


![image](https://github.com/user-attachments/assets/5f6f93a0-1d6e-4476-bad5-dc7a9e179e92)

Closes #38858
2025-07-07 10:31:56 -05:00

190 lines
6.9 KiB
C#

// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.IO.Compression;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.UI.ViewModels.BuiltinCommands;
internal sealed partial class NewExtensionForm : NewExtensionFormBase
{
private static readonly string _creatingText = "Creating new extension...";
private readonly StatusMessage _creatingMessage = new()
{
Message = _creatingText,
Progress = new ProgressState() { IsIndeterminate = true },
};
public NewExtensionForm()
{
TemplateJson = $$"""
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.6",
"body": [
{
"type": "TextBlock",
"text": {{FormatJsonString(Properties.Resources.builtin_create_extension_page_title)}},
"size": "large"
},
{
"type": "Input.Text",
"label": {{FormatJsonString(Properties.Resources.builtin_create_extension_name_label)}},
"isRequired": true,
"errorMessage": {{FormatJsonString(Properties.Resources.builtin_create_extension_name_required)}},
"id": "ExtensionName",
"placeholder": "ExtensionName",
"regex": "^[a-zA-Z_][a-zA-Z0-9_]*$"
},
{
"type": "TextBlock",
"text": {{FormatJsonString(Properties.Resources.builtin_create_extension_name_description)}},
"wrap": true,
"size": "small",
"isSubtle": true,
"spacing": "none"
},
{
"type": "Input.Text",
"label": {{FormatJsonString(Properties.Resources.builtin_create_extension_display_name_label)}},
"isRequired": true,
"errorMessage": {{FormatJsonString(Properties.Resources.builtin_create_extension_display_name_required)}},
"id": "DisplayName",
"placeholder": "My new extension",
"spacing": "medium"
},
{
"type": "TextBlock",
"text": {{FormatJsonString(Properties.Resources.builtin_create_extension_display_name_description)}},
"wrap": true,
"size": "small",
"isSubtle": true,
"spacing": "none"
},
{
"type": "Input.Text",
"label": {{FormatJsonString(Properties.Resources.builtin_create_extension_directory_label)}},
"isRequired": true,
"errorMessage": {{FormatJsonString(Properties.Resources.builtin_create_extension_directory_required)}},
"id": "OutputPath",
"placeholder": "C:\\users\\me\\dev",
"spacing": "medium"
},
{
"type": "TextBlock",
"text": {{FormatJsonString(Properties.Resources.builtin_create_extension_directory_description)}},
"wrap": true,
"size": "small",
"isSubtle": true,
"spacing": "none"
}
],
"actions": [
{
"type": "Action.Submit",
"title": {{FormatJsonString(Properties.Resources.builtin_create_extension_submit)}},
"associatedInputs": "auto"
}
]
}
""";
}
public override CommandResult SubmitForm(string payload)
{
var formInput = JsonNode.Parse(payload)?.AsObject();
if (formInput == null)
{
return CommandResult.KeepOpen();
}
var extensionName = formInput["ExtensionName"]?.AsValue()?.ToString() ?? string.Empty;
var displayName = formInput["DisplayName"]?.AsValue()?.ToString() ?? string.Empty;
var outputPath = formInput["OutputPath"]?.AsValue()?.ToString() ?? string.Empty;
_creatingMessage.State = MessageState.Info;
_creatingMessage.Message = _creatingText;
_creatingMessage.Progress = new ProgressState() { IsIndeterminate = true };
BuiltinsExtensionHost.Instance.ShowStatus(_creatingMessage, StatusContext.Extension);
try
{
CreateExtension(extensionName, displayName, outputPath);
BuiltinsExtensionHost.Instance.HideStatus(_creatingMessage);
RaiseFormSubmit(new CreatedExtensionForm(extensionName, displayName, outputPath));
}
catch (Exception e)
{
BuiltinsExtensionHost.Instance.HideStatus(_creatingMessage);
_creatingMessage.State = MessageState.Error;
_creatingMessage.Message = $"Error: {e.Message}";
}
return CommandResult.KeepOpen();
}
private void CreateExtension(string extensionName, string newDisplayName, string outputPath)
{
var newGuid = Guid.NewGuid().ToString();
// Unzip `template.zip` to a temp dir:
var tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
// Does the output path exist?
if (!Directory.Exists(outputPath))
{
Directory.CreateDirectory(outputPath);
}
var assetsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory.ToString(), "Microsoft.CmdPal.UI.ViewModels\\Assets\\template.zip");
ZipFile.ExtractToDirectory(assetsPath, tempDir);
var files = Directory.GetFiles(tempDir, "*", SearchOption.AllDirectories);
foreach (var file in files)
{
var text = File.ReadAllText(file);
// Replace all the instances of `FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF` with a new random guid:
text = text.Replace("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF", newGuid);
// Then replace all the `TemplateCmdPalExtension` with `extensionName`
text = text.Replace("TemplateCmdPalExtension", extensionName);
// Then replace all the `TemplateDisplayName` with `newDisplayName`
text = text.Replace("TemplateDisplayName", newDisplayName);
// We're going to write the file to the same relative location in the output path
var relativePath = Path.GetRelativePath(tempDir, file);
var newFileName = Path.Combine(outputPath, relativePath);
// if the file name had `TemplateCmdPalExtension` in it, replace it with `extensionName`
newFileName = newFileName.Replace("TemplateCmdPalExtension", extensionName);
// Make sure the directory exists
Directory.CreateDirectory(Path.GetDirectoryName(newFileName)!);
File.WriteAllText(newFileName, text);
// Delete the old file
File.Delete(file);
}
// Delete the temp dir
Directory.Delete(tempDir, true);
}
private string FormatJsonString(string str) =>
// Escape the string for JSON
JsonSerializer.Serialize(str, JsonSerializationContext.Default.String);
}