Add infobars

This commit is contained in:
Aaron Junker 2024-03-17 23:54:25 +01:00
parent d7683aa8c7
commit dfb0b1810c
6 changed files with 286 additions and 29 deletions

View File

@ -2,8 +2,11 @@
// 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;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Reflection.Metadata;
using System.Text;
using System.Xml;
using Microsoft.CodeAnalysis;
@ -14,19 +17,20 @@ namespace Settings.SourceGenerators
public class SettingsSourceGenerator : ISourceGenerator
{
private GeneratorExecutionContext _context;
private string[] _modules = new[] { "Hosts" };
private string[] _modules = new[] { "Hosts", "FileLocksmith" };
private StringBuilder _source;
private string _projectPath;
private bool _failed = false;
public void Execute(GeneratorExecutionContext context)
{
// Remove following comment for debugging
/*#if DEBUG
#if DEBUG
if (!Debugger.IsAttached)
{
Debugger.Launch();
}
#endif*/
#endif
_context = context;
@ -48,6 +52,11 @@ namespace Settings.Ui.VNext
");
GeneratePopulateNavigationItemsFunction();
if (_failed)
{
return;
}
GenerateSettingsPages();
_source.Append(@" }
@ -65,18 +74,32 @@ namespace Settings.Ui.VNext
{
XmlDocument doc = new XmlDocument();
doc.Load($"{_projectPath}/ConfigFiles/{item}.xml");
string moduleName = doc.SelectSingleNode("ModuleSettings").Attributes["Name"].Value;
string iconUri = doc.SelectSingleNode("ModuleSettings").Attributes["Icon"].Value;
doc.Schemas.Add("http://schemas.microsoft.com/PowerToys/FileActionsMenu/ModuleDefinition", $"{_projectPath}/ConfigFiles/ModuleDefinition.xsd");
doc.Validate((_, e) =>
{
_context.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor("PT0001", "XML Validation error", $"XML Validation error in {item}.xml: " + e.Message, "XML validation Error", DiagnosticSeverity.Error, true, "Error: " + e.Message), null));
_failed = true;
});
if (_failed)
{
return;
}
string moduleName = doc.GetNode("ModuleSettings").Attributes["Name"].Value;
string iconUri = doc.GetNode("ModuleSettings").Attributes["Icon"].Value;
// Todo: Add NavHelper.NavigateTo property
_source.Append(
$@"
NavigationViewItem navigationViewItem = new();
var loader = ResourceLoaderInstance.ResourceLoader;
navigationViewItem.Content = loader.GetString(""Shell_{moduleName}/Content"");
navigationViewItem.Icon = new BitmapIcon() {{ UriSource = new Uri(""{iconUri}""), ShowAsMonochrome = false }};
navigationView.MenuItems.Add(navigationViewItem);
NavHelper.SetNavigateTo(navigationViewItem, typeof({moduleName}Page));
{{
NavigationViewItem navigationViewItem = new();
var loader = ResourceLoaderInstance.ResourceLoader;
navigationViewItem.Content = loader.GetString(""Shell_{moduleName}/Content"");
navigationViewItem.Icon = new BitmapIcon() {{ UriSource = new Uri(""{iconUri}""), ShowAsMonochrome = false }};
navigationView.MenuItems.Add(navigationViewItem);
NavHelper.SetNavigateTo(navigationViewItem, typeof({moduleName}Page));
}}
");
}
}
@ -90,14 +113,14 @@ namespace Settings.Ui.VNext
XmlDocument doc = new XmlDocument();
doc.Load($"{_projectPath}/ConfigFiles/{item}.xml");
string moduleName = doc.SelectSingleNode("ModuleSettings").Attributes["Name"].Value;
string headerImage = doc.SelectSingleNode("ModuleSettings/Header").Attributes["Image"].Value;
string headerPrimaryLinkName = doc.SelectSingleNode("ModuleSettings/Header/PrimaryLink").Attributes["Name"].Value;
string headerPrimaryLinkUri = doc.SelectSingleNode("ModuleSettings/Header/PrimaryLink").Attributes["Uri"].Value;
string moduleName = doc.GetNode("ModuleSettings").Attributes["Name"].Value;
string headerImage = doc.GetNode("ModuleSettings/Header").Attributes["Image"].Value;
string headerPrimaryLinkName = doc.GetNode("ModuleSettings/Header/PrimaryLink").Attributes["Name"].Value;
string headerPrimaryLinkUri = doc.GetNode("ModuleSettings/Header/PrimaryLink").Attributes["Uri"].Value;
// Generate secondary links
StringBuilder secondaryLinksSource = new StringBuilder();
XmlNodeList secondaryLinks = doc.SelectNodes("ModuleSettings/Footer/SecondaryLink");
XmlNodeList secondaryLinks = doc.GetNodes("ModuleSettings/Footer/SecondaryLink");
foreach (XmlNode link in secondaryLinks)
{
@ -114,8 +137,11 @@ namespace Settings.Ui.VNext
");
}
doc.SelectSingleNode("ModuleSettings/Header").RemoveAll();
doc.SelectSingleNode("ModuleSettings/Footer").RemoveAll();
doc.GetNode("ModuleSettings/Header").RemoveAll();
doc.GetNode("ModuleSettings/Footer").RemoveAll();
StringBuilder content = new StringBuilder();
GenerateSettingsPageElements(doc.GetNode("ModuleSettings").ChildNodes, content);
settingsPage.Append($@"// <auto-generated />
using System;
@ -123,6 +149,8 @@ using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
using Settings.Ui.VNext.Helpers;
using Settings.Ui.VNext.Controls;
using System.Collections.Generic;
namespace Settings.Ui.VNext
{{
@ -148,7 +176,15 @@ namespace Settings.Ui.VNext
SecondaryLinks = new System.Collections.ObjectModel.ObservableCollection<Settings.Ui.VNext.Controls.PageLink>
{{
{secondaryLinksSource}
}}
}},
ModuleContent =
new StackPanel
{{
Children =
{{
{content}
}}
}},
}};
}}
}}
@ -184,6 +220,60 @@ namespace Settings.Ui.VNext
#pragma warning restore RS1035 // Do not use APIs banned for analyzers
}
public void GenerateSettingsPageElements(XmlNodeList elements, StringBuilder source)
{
foreach (XmlNode element in elements)
{
switch (element.Name)
{
case "Group":
GenerateGroup(element, source);
break;
case "InfoBar":
GenerateInfoBar(element, source);
break;
}
}
}
public void GenerateGroup(XmlNode element, StringBuilder source)
{
string name = element.Attributes["Name"].Value;
StringBuilder content = new StringBuilder();
GenerateSettingsPageElements(element.ChildNodes, content);
source.Append(
$@"
new SettingsGroup
{{
Header = loader.GetString(""{name}/Header""),
ItemsSource = new System.Collections.ObjectModel.ObservableCollection<UIElement>
{{
{content}
}}
}},
");
}
public void GenerateInfoBar(XmlNode element, StringBuilder source)
{
string severity = element.Attributes["Severity"].Value;
string message = element.Attributes["Text"].Value;
source.Append(
$@"
new InfoBar
{{
Severity = InfoBarSeverity.{severity},
Message = loader.GetString(""{message}/Title""),
IsOpen = true,
IsClosable = false,
IsTabStop = true,
}},
");
}
public void Initialize(GeneratorInitializationContext context)
{
}

View File

@ -0,0 +1,40 @@
// 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.Xml;
internal static class SettingsSourceGeneratorHelpers
{
public static XmlNode GetNode(this XmlDocument doc, string path)
{
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(doc.NameTable);
namespaceManager.AddNamespace("ns", "http://schemas.microsoft.com/PowerToys/FileActionsMenu/ModuleDefinition");
return doc.SelectSingleNode("ns:" + path.Replace("/", "/ns:"), namespaceManager);
}
public static XmlNode GetNode(this XmlNode node, string path)
{
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(node.OwnerDocument.NameTable);
namespaceManager.AddNamespace("ns", "http://schemas.microsoft.com/PowerToys/FileActionsMenu/ModuleDefinition");
return node.SelectSingleNode("ns:" + path.Replace("/", "/ns:"), namespaceManager);
}
public static XmlNodeList GetNodes(this XmlDocument doc, string path)
{
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(doc.NameTable);
namespaceManager.AddNamespace("ns", "http://schemas.microsoft.com/PowerToys/FileActionsMenu/ModuleDefinition");
return doc.SelectNodes("ns:" + path.Replace("/", "/ns:"), namespaceManager);
}
public static XmlNodeList GetNodes(this XmlNode node, string path)
{
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(node.OwnerDocument.NameTable);
namespaceManager.AddNamespace("ns", "http://schemas.microsoft.com/PowerToys/FileActionsMenu/ModuleDefinition");
return node.SelectNodes("ns:" + path.Replace("/", "/ns:"), namespaceManager);
}
}

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<ModuleSettings Name="FileLocksmith" Icon="ms-appx:///Assets/Settings/Icons/FileLocksmith.png" Gpo="true" xmlns="http://schemas.microsoft.com/PowerToys/FileActionsMenu/ModuleDefinition">
<Header Image="ms-appx:///Assets/Settings/Modules/FileLocksmith.png">
<PrimaryLink Name="LearnMore_FileLocksmith" Uri="https://aka.ms/PowerToysOverview_FileLocksmith"/>
</Header>
<Footer>
</Footer>
<ActivationToggle></ActivationToggle>
<Group Name="FileLocksmith_ShellIntegration">
<ComboBox Name="FileLocksmith_Toggle_ContextMenu" SettingPropertyName="EnablesOnContextExtendedMenu">
<Item Name="FileLocksmith_Toggle_StandardContextMenu" Value="False"/>
<Item Name="FileLocksmith_Toggle_ExtendedContextMenu" Value="True"/>
</ComboBox>
<InfoBar Text="ExtendedContextMenuInfo" Severity="Informational"></InfoBar>
</Group>
</ModuleSettings>

View File

@ -1,23 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns="http://schemas.microsoft.com/PowerToys/FileActionsMenu/ModuleDefinition" targetNamespace="http://schemas.microsoft.com/PowerToys/FileActionsMenu/ModuleDefinition" attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="InfoBarSeverity">
<xs:restriction base="xs:string">
<xs:enumeration value="Informational" />
<xs:enumeration value="Success" />
<xs:enumeration value="Warning" />
<xs:enumeration value="Error" />
</xs:restriction>
</xs:simpleType>
<xs:group name="elements">
<xs:choice>
<xs:element name="InfoBar">
<xs:complexType>
<xs:attribute name="Text" type="xs:string" use="required"></xs:attribute>
<xs:attribute name="Severity" type="InfoBarSeverity" use="required"></xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="ComboBox">
<xs:annotation>
<xs:documentation>
An element that lets the user select a value from a list of options. One option has to be selected by default.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="Item">
<xs:complexType>
<xs:attribute name="Name" type="xs:string" use="required" />
<xs:attribute name="Value" type="xs:string" use="required" />
<xs:attribute name="Name" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
The name of the option.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="Value" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
The value of the option. Needs to be an enum value or true or false.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="Default" type="xs:boolean" use="optional">
<xs:annotation>
<xs:documentation>
If true, this option will be selected by default. Only one option can be selected by default.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="Name" type="xs:string" use="required" />
<xs:attribute name="SettingPropertyName" type="xs:string" use="required" />
<xs:attribute name="Icon" type="xs:string" use="optional" />
<xs:attribute name="Name" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
The name of the ComboBox.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="SettingPropertyName" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
The name of the property in the settings object that will be updated when the user selects an option.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="Icon" type="xs:anyURI" use="optional">
<xs:annotation>
<xs:documentation>
The uri to the icon that will be displayed next to the ComboBox.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="Link">
<xs:annotation>
<xs:documentation>
An element that represents a link displayed as a settings card.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="Name" type="xs:string" use="required" />
<xs:attribute name="ActionIcon" type="xs:string" use="required" />
@ -25,6 +86,11 @@
</xs:complexType>
</xs:element>
<xs:element maxOccurs="unbounded" name="Toggle">
<xs:annotation>
<xs:documentation>
An element that represents a toggle displayed as a settings card.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="Name" type="xs:string" use="required" />
<xs:attribute name="Icon" type="xs:string" use="required" />
@ -34,12 +100,21 @@
<xs:element maxOccurs="unbounded" name="ActivationToggle">
</xs:element>
</xs:choice>
</xs:group>
<xs:element name="ModuleSettings">
<xs:annotation>
<xs:documentation>
Represents the settings for a module.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="Header" minOccurs="1" maxOccurs="1">
<xs:annotation>
<xs:documentation>
Contains the primary link to the documentation for the module.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="PrimaryLink" maxOccurs ="1">
@ -53,6 +128,11 @@
</xs:complexType>
</xs:element>
<xs:element name="Footer" minOccurs="1" maxOccurs="1">
<xs:annotation>
<xs:documentation>
Contains the secondary links to the documentation for the module displayed at the bottom of the settings page.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="SecondaryLink" minOccurs="0" maxOccurs="unbounded">
@ -67,18 +147,47 @@
<xs:choice maxOccurs="unbounded">
<xs:group ref="elements" maxOccurs ="unbounded" />
<xs:element maxOccurs="unbounded" name="Group">
<xs:annotation>
<xs:documentation>
Represents a group of settings with a header.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:group ref="elements" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="Name" type="xs:string" use="required" />
<xs:attribute name="Name" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
The name of the group displayed as the header.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:sequence>
<xs:attribute name="Name" type="xs:string" use="required" />
<xs:attribute name="Icon" type="xs:anyURI" use="required" />
<xs:attribute name="Gpo" type="xs:boolean" use="required" />
<xs:attribute name="Name" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
The name of the module.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="Icon" type="xs:anyURI" use="required">
<xs:annotation>
<xs:documentation>
The uri to the icon that will be displayed in the header.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="Gpo" type="xs:boolean" use="required">
<xs:annotation>
<xs:documentation>
If true, GPO related properties will be generated for this module.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

View File

@ -22,6 +22,7 @@
<ProjectPriFileName>Settings.Ui.VNext.pri</ProjectPriFileName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>

View File

@ -8,6 +8,7 @@ using ManagedCommon;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation.Peers;
using Microsoft.UI.Xaml.Controls;
using Settings.Ui.VNext.Controls;
using Settings.Ui.VNext.Helpers;
using Settings.Ui.VNext.Services;
using Settings.Ui.VNext.ViewModels;