Compare commits

..

4 Commits

Author SHA1 Message Date
Shawn Yuan (from Dev Box)
ca4b52e8c2 update 2026-01-09 12:03:30 +08:00
Shawn Yuan (from Dev Box)
be06f081d1 update 2026-01-09 11:46:10 +08:00
Shawn Yuan (from Dev Box)
7cb41d8fe3 Optimize the module list logic 2026-01-09 11:41:05 +08:00
Shawn Yuan (from Dev Box)
73dda5a08d Fix quickaccess localization issue 2026-01-09 09:38:30 +08:00
10 changed files with 82 additions and 157 deletions

View File

@@ -6,45 +6,13 @@ description: 'Generate an 80-character git commit title for the local diff'
# Generate Commit Title
## Purpose
Provide a single-line, ready-to-paste git commit title (<= 80 characters) that reflects the most important local changes since `HEAD`.
**Goal:** Provide a ready-to-paste git commit title (<= 80 characters) that captures the most important local changes since `HEAD`.
## Input to collect
- Run exactly one command to view the local diff:
```@terminal
git diff HEAD
```
## How to decide the title
1. From the diff, find the dominant area (e.g., `src/modules/*`, `doc/devdocs/**`) and the change type (bug fix, docs update, config tweak).
2. Draft an imperative, plain-ASCII title that:
- Mentions the primary component when obvious (e.g., `FancyZones:` or `Docs:`)
- Stays within 80 characters and has no trailing punctuation
## Final output
- Reply with only the commit title on a single line—no extra text.
## PR title convention (when asked)
Use Conventional Commits style:
`<type>(<scope>): <summary>`
**Allowed types**
- feat, fix, docs, refactor, perf, test, build, ci, chore
**Scope rules**
- Use a short, PowerToys-focused scope (one word preferred). Common scopes:
- Core: `runner`, `settings-ui`, `common`, `docs`, `build`, `ci`, `installer`, `gpo`, `dsc`
- Modules: `fancyzones`, `powerrename`, `awake`, `colorpicker`, `imageresizer`, `keyboardmanager`, `mouseutils`, `peek`, `hosts`, `file-locksmith`, `screen-ruler`, `text-extractor`, `cropandlock`, `paste`, `powerlauncher`
- If unclear, pick the closest module or subsystem; omit only if unavoidable
**Summary rules**
- Imperative, present tense (“add”, “update”, “remove”, “fix”)
- Keep it <= 72 characters when possible; be specific, avoid “misc changes”
**Examples**
- `feat(fancyzones): add canvas template duplication`
- `fix(mouseutils): guard crosshair toggle when dpi info missing`
- `docs(runner): document tray icon states`
- `build(installer): align wix v5 suffix flag`
- `ci(ci): cache pipeline artifacts for x64`
**Workflow:**
1. Run a single command to view the local diff since the last commit:
```@terminal
git diff HEAD
```
2. From that diff, identify the dominant area (reference key paths like `src/modules/*`, `doc/devdocs/**`, etc.), the type of change (bug fix, docs update, config tweak), and any notable impact.
3. Draft a concise, imperative commit title summarizing the dominant change. Keep it plain ASCII, <= 80 characters, and avoid trailing punctuation. Mention the primary component when obvious (for example `FancyZones:` or `Docs:`).
4. Respond with only the final commit title on a single line so it can be pasted directly into `git commit`.

View File

@@ -22,4 +22,3 @@ description: 'Generate a PowerToys-ready pull request description from the local
5. Confirm validation: list tests executed with results or state why tests were skipped in line with repo guidance.
6. Load `.github/pull_request_template.md`, mirror its section order, and populate it with the gathered facts. Include only relevant checklist entries, marking them `[x]/[ ]` and noting any intentional omissions as "N/A".
7. Present the filled template inside a fenced ```markdown code block with no extra commentary so it is ready to paste into a PR, clearly flagging any placeholders that still need user input.
8. Prepend the PR title above the filled template, applying the Conventional Commit type/scope rules from `.github/prompts/create-commit-title.prompt.md`; pick the dominant component from the diff and keep the title concise and imperative.

View File

@@ -10,8 +10,8 @@ description: 'Resolve Code scanning / check-spelling comments on the active PR'
**Guardrails:**
- Update only discussion threads authored by `github-actions` or `github-actions[bot]` that mention `Code scanning results / check-spelling`.
- Prefer improving the wording in the originally flagged file when it clarifies intent without changing meaning; if the wording is already clear/standard for the context, handle it via `.github/actions/spell-check/expect.txt` and reuse existing entries.
- Limit edits to the flagged text and `.github/actions/spell-check/expect.txt`; leave all other files and topics untouched.
- Resolve findings solely by editing `.github/actions/spell-check/expect.txt`; reuse existing entries.
- Leave all other files and topics untouched.
**Prerequisites:**
- Install GitHub CLI if it is not present: `winget install GitHub.cli`.
@@ -20,6 +20,5 @@ description: 'Resolve Code scanning / check-spelling comments on the active PR'
**Workflow:**
1. Determine the active pull request with a single `gh pr view --json number` call (default to the current branch).
2. Fetch all PR discussion data once via `gh pr view --json comments,reviews` and filter to check-spelling comments authored by `github-actions` or `github-actions[bot]` that are not minimized; when several remain, process only the most recent comment body.
3. For each flagged token, first consider tightening or rephrasing the original text to avoid the false positive while keeping the meaning intact; if the existing wording is already normal and professional for the context, proceed to allowlisting instead of changing it.
4. When allowlisting, review `.github/actions/spell-check/expect.txt` for an equivalent term (for example an existing lowercase variant); when found, reuse that normalized term rather than adding a new entry, even if the flagged token differs only by casing. Only add a new entry after confirming no equivalent already exists.
5. Add any remaining missing token to `.github/actions/spell-check/expect.txt`, keeping surrounding formatting intact.
3. For each flagged token, review `.github/actions/spell-check/expect.txt` for an equivalent term (for example an existing lowercase variant); when found, reuse that normalized term rather than adding a new entry, even if the flagged token differs only by casing. Only add a new entry after confirming no equivalent already exists.
4. Add any remaining missing token to `.github/actions/spell-check/expect.txt`, keeping surrounding formatting intact.

17
.vscode/settings.json vendored
View File

@@ -1,17 +0,0 @@
{
"github.copilot.chat.reviewSelection.instructions": [
{
"file": ".github/prompts/review-pr.prompt.md"
}
],
"github.copilot.chat.commitMessageGeneration.instructions": [
{
"file": ".github/prompts/create-commit-title.prompt.md"
}
],
"github.copilot.chat.pullRequestDescriptionGeneration.instructions": [
{
"file": ".github/prompts/create-pr-summary.prompt.md"
}
]
}

View File

@@ -50,8 +50,8 @@
</ItemGroup>
<ItemGroup>
<PRIResource Include="..\Settings.UI\Strings\en-us\Resources.resw">
<Link>Strings\en-us\Resources.resw</Link>
<PRIResource Include="..\Settings.UI\Strings\**\Resources.resw">
<Link>Strings\%(RecursiveDir)Resources.resw</Link>
</PRIResource>
</ItemGroup>

View File

@@ -10,6 +10,7 @@ using global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.QuickAccess.Helpers;
using Microsoft.PowerToys.QuickAccess.Services;
using Microsoft.PowerToys.Settings.UI.Controls;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
@@ -26,6 +27,7 @@ public sealed class AllAppsViewModel : Observable
private readonly SettingsUtils _settingsUtils;
private readonly ResourceLoader _resourceLoader;
private readonly DispatcherQueue _dispatcherQueue;
private readonly List<FlyoutMenuItem> _allFlyoutMenuItems = new();
private GeneralSettings _generalSettings;
public ObservableCollection<FlyoutMenuItem> FlyoutMenuItems { get; }
@@ -58,9 +60,28 @@ public sealed class AllAppsViewModel : Observable
_resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
FlyoutMenuItems = new ObservableCollection<FlyoutMenuItem>();
BuildFlyoutMenuItems();
RefreshFlyoutMenuItems();
}
private void BuildFlyoutMenuItems()
{
_allFlyoutMenuItems.Clear();
foreach (ModuleType moduleType in Enum.GetValues<ModuleType>())
{
if (moduleType == ModuleType.GeneralSettings)
{
continue;
}
_allFlyoutMenuItems.Add(new FlyoutMenuItem
{
Tag = moduleType,
EnabledChangedCallback = EnabledChangedOnUI,
});
}
}
private void OnSettingsChanged(GeneralSettings newSettings)
{
_dispatcherQueue.TryEnqueue(() =>
@@ -82,63 +103,37 @@ public sealed class AllAppsViewModel : Observable
private void RefreshFlyoutMenuItems()
{
var desiredItems = new List<FlyoutMenuItem>();
foreach (ModuleType moduleType in Enum.GetValues<ModuleType>())
foreach (var item in _allFlyoutMenuItems)
{
if (moduleType == ModuleType.GeneralSettings)
{
continue;
}
var moduleType = item.Tag;
var gpo = Helpers.ModuleGpoHelper.GetModuleGpoConfiguration(moduleType);
var isLocked = gpo is GpoRuleConfigured.Enabled or GpoRuleConfigured.Disabled;
var isEnabled = gpo == GpoRuleConfigured.Enabled || (!isLocked && Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetIsModuleEnabled(_generalSettings, moduleType));
var existingItem = FlyoutMenuItems.FirstOrDefault(x => x.Tag == moduleType);
item.Label = _resourceLoader.GetString(Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleLabelResourceName(moduleType));
item.IsLocked = isLocked;
item.Icon = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleTypeFluentIconName(moduleType);
if (existingItem != null)
if (item.IsEnabled != isEnabled)
{
existingItem.Label = _resourceLoader.GetString(Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleLabelResourceName(moduleType));
existingItem.IsLocked = isLocked;
existingItem.Icon = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleTypeFluentIconName(moduleType);
if (existingItem.IsEnabled != isEnabled)
{
var callback = existingItem.EnabledChangedCallback;
existingItem.EnabledChangedCallback = null;
existingItem.IsEnabled = isEnabled;
existingItem.EnabledChangedCallback = callback;
}
desiredItems.Add(existingItem);
}
else
{
desiredItems.Add(new FlyoutMenuItem
{
Label = _resourceLoader.GetString(Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleLabelResourceName(moduleType)),
IsEnabled = isEnabled,
IsLocked = isLocked,
Tag = moduleType,
Icon = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleTypeFluentIconName(moduleType),
EnabledChangedCallback = EnabledChangedOnUI,
});
item.UpdateStatus(isEnabled);
}
}
var sortedItems = DashboardSortOrder switch
{
DashboardSortOrder.ByStatus => desiredItems.OrderByDescending(x => x.IsEnabled).ThenBy(x => x.Label).ToList(),
_ => desiredItems.OrderBy(x => x.Label).ToList(),
DashboardSortOrder.ByStatus => _allFlyoutMenuItems.OrderByDescending(x => x.IsEnabled).ThenBy(x => x.Label).ToList(),
_ => _allFlyoutMenuItems.OrderBy(x => x.Label).ToList(),
};
for (int i = FlyoutMenuItems.Count - 1; i >= 0; i--)
if (FlyoutMenuItems.Count == 0)
{
if (!sortedItems.Contains(FlyoutMenuItems[i]))
foreach (var item in sortedItems)
{
FlyoutMenuItems.RemoveAt(i);
FlyoutMenuItems.Add(item);
}
return;
}
for (int i = 0; i < sortedItems.Count; i++)
@@ -146,20 +141,17 @@ public sealed class AllAppsViewModel : Observable
var item = sortedItems[i];
var oldIndex = FlyoutMenuItems.IndexOf(item);
if (oldIndex < 0)
{
FlyoutMenuItems.Insert(i, item);
}
else if (oldIndex != i)
if (oldIndex != -1 && oldIndex != i)
{
FlyoutMenuItems.Move(oldIndex, i);
}
}
}
private void EnabledChangedOnUI(FlyoutMenuItem item)
private void EnabledChangedOnUI(ModuleListItem item)
{
if (_coordinator.UpdateModuleEnabled(item.Tag, item.IsEnabled))
var flyoutItem = (FlyoutMenuItem)item;
if (_coordinator.UpdateModuleEnabled(flyoutItem.Tag, flyoutItem.IsEnabled))
{
_coordinator.NotifyUserSettingsInteraction();
}

View File

@@ -22,21 +22,6 @@ public sealed class FlyoutMenuItem : ModuleListItem
set => base.Tag = value;
}
public override bool IsEnabled
{
get => base.IsEnabled;
set
{
if (base.IsEnabled != value)
{
base.IsEnabled = value;
EnabledChangedCallback?.Invoke(this);
}
}
}
public Action<FlyoutMenuItem>? EnabledChangedCallback { get; set; }
public bool Visible
{
get => _visible;

View File

@@ -2,6 +2,7 @@
// 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; // For Action
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
@@ -17,6 +18,22 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
private bool _isLocked;
private object? _tag;
private ICommand? _clickCommand;
private bool _isUpdating;
public Action<ModuleListItem>? EnabledChangedCallback { get; set; }
public void UpdateStatus(bool isEnabled)
{
_isUpdating = true;
try
{
IsEnabled = isEnabled;
}
finally
{
_isUpdating = false;
}
}
public virtual string Label
{
@@ -79,6 +96,11 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
{
_isEnabled = value;
OnPropertyChanged();
if (!_isUpdating)
{
EnabledChangedCallback?.Invoke(this);
}
}
}
}

View File

@@ -26,21 +26,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
set => base.Tag = value;
}
public Action<DashboardListItem> EnabledChangedCallback { get; set; }
public override bool IsEnabled
{
get => base.IsEnabled;
set
{
if (base.IsEnabled != value)
{
base.IsEnabled = value;
EnabledChangedCallback?.Invoke(this);
}
}
}
public bool Visible
{
get => _visible;

View File

@@ -50,7 +50,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
// Flag to prevent circular updates when a UI toggle triggers settings changes.
private bool _isUpdatingFromUI;
private bool _isUpdatingFromSettings;
private AllHotkeyConflictsData _allHotkeyConflictsData = new AllHotkeyConflictsData();
@@ -258,7 +257,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
// Only update if there's an actual change to minimize UI notifications.
if (item.IsEnabled != newEnabledState)
{
item.IsEnabled = newEnabledState;
item.UpdateStatus(newEnabledState);
}
if (item.IsLocked != newLockedState)
@@ -275,19 +274,17 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
/// Sets the _isUpdatingFromUI flag to prevent circular updates, then updates
/// settings, re-sorts if needed, and refreshes dependent collections.
/// </summary>
private void EnabledChangedOnUI(DashboardListItem dashboardListItem)
private void EnabledChangedOnUI(ModuleListItem item)
{
if (_isUpdatingFromSettings)
{
return;
}
var dashboardListItem = (DashboardListItem)item;
var isEnabled = dashboardListItem.IsEnabled;
_isUpdatingFromUI = true;
try
{
Views.ShellPage.UpdateGeneralSettingsCallback(dashboardListItem.Tag, dashboardListItem.IsEnabled);
Views.ShellPage.UpdateGeneralSettingsCallback(dashboardListItem.Tag, isEnabled);
if (dashboardListItem.Tag == ModuleType.NewPlus && dashboardListItem.IsEnabled == true)
if (dashboardListItem.Tag == ModuleType.NewPlus && isEnabled == true)
{
var settingsUtils = SettingsUtils.Default;
var settings = NewPlusViewModel.LoadSettings(settingsUtils);
@@ -325,7 +322,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
return;
}
_isUpdatingFromSettings = true;
try
{
RefreshModuleList();
@@ -340,10 +336,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
Logger.LogError($"Updating active/disabled modules list failed: {ex.Message}");
}
finally
{
_isUpdatingFromSettings = false;
}
}
/// <summary>