Skip to content

feat(homepage): 支持自定义主页局部更新#2829

Open
Remygred wants to merge 4 commits into
PCL-Community:devfrom
Remygred:feat/custom-home-live-patch
Open

feat(homepage): 支持自定义主页局部更新#2829
Remygred wants to merge 4 commits into
PCL-Community:devfrom
Remygred:feat/custom-home-live-patch

Conversation

@Remygred
Copy link
Copy Markdown

@Remygred Remygred commented May 15, 2026

概述

本 PR 为本地自定义主页增加一个轻量的 live patch 机制,让外部辅助程序可以在不刷新整个主页的情况下更新指定控件内容。

主要变化:

  • 在本地 Custom.xaml 主页启用时监听 PCL/CustomLive.json
  • 写入 CustomLive.supported.json,用于让外部程序判断当前启动器是否支持局部更新。
  • 支持通过控件 Tag 匹配目标元素。
  • 支持更新常见属性:TextTitleInfoToolTipVisibilityIsEnabledOpacity
  • 支持通过 childrenXaml 替换目标 Panel 的子元素。
  • 在主页内容未变化时,也会尝试应用 live patch,避免必须整页刷新。

适用场景

该机制适合需要动态更新自定义主页局部内容的场景,例如:

  • 只刷新某个提示文本。
  • 更新某张卡片的状态信息。
  • 替换某个面板内的局部 XAML 内容。

相比直接触发整页刷新,这可以减少主页跳动、折叠状态丢失、滚动位置变化等问题。

Review 反馈处理

根据反馈已补充以下调整:

  • TrySetElementProperty 中数值解析改为使用 CultureInfo.InvariantCultureTryParse,枚举解析改为 Enum.TryParse,避免外部工具传入异常格式时受区域设置影响或直接抛出。
  • FileSystemWatcher 的变更防抖改为 UI 线程上的 DispatcherTimer,避免每轮文件变更额外创建后台线程。
  • DisposeHomepageLiveWatcherDeleteHomepageLiveSupportMarker 不再静默吞掉异常,会将释放 watcher / 删除支持标记失败写入 Developer 日志,方便诊断 live patch 问题。

附带修复

  • 修复自定义命令执行确认中的“继续且今后不再要求确认”不生效问题:HintCustomCommand 实际为 bool 配置,现在会按 bool 写入并兼容旧字符串值。

验证

已执行:

..\.dotnet-sdk-10\dotnet.exe build "Plain Craft Launcher 2\Plain Craft Launcher 2.csproj" -c Release -p:Platform=x64 -p:DefineConstants=TRACE --no-restore

结果:构建通过,0 个错误。项目现有 warning 仍存在。

Summary by Sourcery

为自定义主页添加实时补丁机制,在无需重新加载整个页面的情况下更新特定控件。

New Features:

  • 监视自定义主页实时补丁文件,当该文件发生变化时,根据 JSON 定义对控件应用更新。
  • 暴露一个支持标记文件,以便外部工具可以检测并使用自定义主页实时补丁功能。

Enhancements:

  • 允许根据实时补丁定义更新带标签的主页元素的通用属性以及面板子元素的 XAML,即使底层主页内容本身没有发生变化也可以更新。
Original summary in English

Summary by Sourcery

Add a live patch mechanism for the custom homepage that updates specific controls without reloading the entire page.

New Features:

  • Watch a custom homepage live patch file and apply JSON-defined updates to controls when the file changes.
  • Expose a support marker file so external tools can detect and use the custom homepage live patch capability.

Enhancements:

  • Allow updating tagged homepage elements’ common properties and panel children XAML based on live patch definitions, including when the underlying homepage content has not changed.

@pcl-ce-automation pcl-ce-automation Bot added 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查 size: L PR 大小评估:大型 labels May 15, 2026
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 15, 2026

审阅者指南

为本地自定义主页新增一个轻量级实时补丁(live patch)机制,用于监听一个 JSON 文件的增量更新,并将其应用到带有特定 Tag 的 UI 元素上,而无需整页刷新。

自定义主页实时补丁应用的时序图

sequenceDiagram
    actor ExternalTool
    participant OSFileSystem
    participant PageLaunchRight
    participant HomepageLiveWatcher
    participant BackgroundThread
    participant UIThread

    PageLaunchRight->>PageLaunchRight: EnsureHomepageLiveWatcher
    PageLaunchRight->>OSFileSystem: WriteHomepageLiveSupportMarker
    PageLaunchRight->>HomepageLiveWatcher: FileSystemWatcher(..., CustomLive.json)
    HomepageLiveWatcher-->>HomepageLiveWatcher: EnableRaisingEvents = true
    PageLaunchRight->>PageLaunchRight: QueueHomepageLivePatchApply

    ExternalTool->>OSFileSystem: write CustomLive.json
    OSFileSystem-->>HomepageLiveWatcher: Changed/Created/Renamed
    HomepageLiveWatcher->>PageLaunchRight: QueueHomepageLivePatchApply

    PageLaunchRight->>BackgroundThread: ModBase.RunInNewThread
    BackgroundThread-->>BackgroundThread: Thread.Sleep(120)
    BackgroundThread->>UIThread: ModBase.RunInUi(ApplyHomepageLivePatchesFromFile)

    UIThread->>PageLaunchRight: ApplyHomepageLivePatchesFromFile
    PageLaunchRight->>OSFileSystem: ReadHomepageLivePatchFile
    PageLaunchRight->>PageLaunchRight: EnumerateHomepageLivePatches

    loop patches
        PageLaunchRight->>PageLaunchRight: ApplyHomepageLivePatch
        PageLaunchRight->>PageLaunchRight: FindElementsByTag
        PageLaunchRight->>PageLaunchRight: ApplyHomepageLivePatchToElement
        alt childrenXaml present
            PageLaunchRight->>PageLaunchRight: ReplacePanelChildren
        end
    end
Loading

文件级变更

变更 详情 文件
为自定义主页引入实时补丁 watcher 及其生命周期管理,包括支持标记文件的管理。
  • 当启用了自定义主页类型时,在页面初始化时注册主页实时 watcher,并在页面卸载时释放。
  • 在 PCL 目录上创建一个指向 CustomLive.jsonFileSystemWatcher,在应用补丁前对变更事件进行防抖处理。
  • 在启动时写入带有进程元数据的 CustomLive.supported.json 标记文件,在释放时仅当其属于当前进程时才删除。
Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.cs
CustomLive.json 中的实时补丁应用到已加载的主页 UI 上,而不是强制整页重新加载。
  • 在加载内容时,如果内容哈希未变化,则跳过重新加载 XAML,但仍尝试应用实时补丁。
  • 在将自定义 XAML 成功加载到 PanCustom 后,立即从 JSON 文件中应用补丁。
  • 通过重试安全地读取补丁文件,以应对并发写入,并对所有补丁应用逻辑加上错误日志保护。
Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.cs
定义 JSON 补丁格式,并实现将补丁通过 Tag 属性映射到 WPF 元素的逻辑。
  • 从多种 JSON 结构中枚举补丁对象(包含 patches 数组的对象、直接的补丁数组或基于属性的简写形式),并进行归一化。
  • 通过匹配 FrameworkElement.Tag 与补丁中的 target/tag/name 字段来解析目标元素,并从 PanCustom 起遍历可视树。
  • 对每个匹配的元素,将简单 JSON 字段和 properties 字典映射到元素的 CLR 属性上,通过反射进行基础类型转换并记录错误日志。
Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.cs
支持更新常见属性,并通过 XAML 片段替换 Panel 子元素。
  • 支持更新 TextTitleInfoToolTipVisibilityIsEnabledOpacity 等常见属性,以及通过 properties 子对象更新任意属性,且 JSON 键名大小写不敏感。
  • 实现 ReplacePanelChildren:解析 childrenXaml 片段,剥离多余的 xmlns 声明,将其实例化为临时 StackPanel,然后将其子元素替换到目标 Panel 中。
  • 在元素查找和属性写入时采用防御性处理,包括对基础类型和枚举的类型转换,以及在遍历树时容忍非可视子元素。
Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.cs

可能关联的问题

  • #在自定义主页刷新时,默认收起的卡片会显示由展开变为收起的动画:PR 新增主页局部更新机制以减少整页刷新,从而避免折叠卡片刷新时异常动画

提示与命令

与 Sourcery 交互

  • 触发一次新审查: 在 Pull Request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 从审查评论生成 GitHub issue: 在审查评论下回复,请求 Sourcery 从该评论创建一个 issue。你也可以直接回复 @sourcery-ai issue 来从该评论创建 issue。
  • 生成 Pull Request 标题: 在 Pull Request 标题中任意位置写入 @sourcery-ai,即可随时生成标题。你也可以在 Pull Request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 Pull Request 摘要: 在 Pull Request 正文任意位置写入 @sourcery-ai summary,即可在指定位置随时生成 PR 摘要。你也可以评论 @sourcery-ai summary 来(重新)生成摘要。
  • 生成审阅者指南: 在 Pull Request 中评论 @sourcery-ai guide,即可随时(重新)生成审阅者指南。
  • 一次性解决所有 Sourcery 评论: 在 Pull Request 中评论 @sourcery-ai resolve,即可解决所有 Sourcery 评论。适用于你已经处理完所有评论且不希望再看到它们的情况。
  • 一次性忽略所有 Sourcery 审查: 在 Pull Request 中评论 @sourcery-ai dismiss,即可忽略所有现有的 Sourcery 审查。特别适用于你想从头开始一次新的审查——别忘了再评论 @sourcery-ai review 来触发新的审查!

自定义你的体验

访问你的 控制面板 以:

  • 启用或禁用审查功能,例如 Sourcery 生成的 Pull Request 摘要、审阅者指南等。
  • 更改审查语言。
  • 添加、移除或编辑自定义审查说明。
  • 调整其他审查设置。

获取帮助

Original review guide in English

Reviewer's Guide

Adds a lightweight live patch mechanism for the local custom homepage that watches a JSON file for incremental updates and applies them to tagged UI elements without reloading the entire page.

Sequence diagram for custom homepage live patch application

sequenceDiagram
    actor ExternalTool
    participant OSFileSystem
    participant PageLaunchRight
    participant HomepageLiveWatcher
    participant BackgroundThread
    participant UIThread

    PageLaunchRight->>PageLaunchRight: EnsureHomepageLiveWatcher
    PageLaunchRight->>OSFileSystem: WriteHomepageLiveSupportMarker
    PageLaunchRight->>HomepageLiveWatcher: FileSystemWatcher(..., CustomLive.json)
    HomepageLiveWatcher-->>HomepageLiveWatcher: EnableRaisingEvents = true
    PageLaunchRight->>PageLaunchRight: QueueHomepageLivePatchApply

    ExternalTool->>OSFileSystem: write CustomLive.json
    OSFileSystem-->>HomepageLiveWatcher: Changed/Created/Renamed
    HomepageLiveWatcher->>PageLaunchRight: QueueHomepageLivePatchApply

    PageLaunchRight->>BackgroundThread: ModBase.RunInNewThread
    BackgroundThread-->>BackgroundThread: Thread.Sleep(120)
    BackgroundThread->>UIThread: ModBase.RunInUi(ApplyHomepageLivePatchesFromFile)

    UIThread->>PageLaunchRight: ApplyHomepageLivePatchesFromFile
    PageLaunchRight->>OSFileSystem: ReadHomepageLivePatchFile
    PageLaunchRight->>PageLaunchRight: EnumerateHomepageLivePatches

    loop patches
        PageLaunchRight->>PageLaunchRight: ApplyHomepageLivePatch
        PageLaunchRight->>PageLaunchRight: FindElementsByTag
        PageLaunchRight->>PageLaunchRight: ApplyHomepageLivePatchToElement
        alt childrenXaml present
            PageLaunchRight->>PageLaunchRight: ReplacePanelChildren
        end
    end
Loading

File-Level Changes

Change Details Files
Introduce a live patch watcher and lifecycle for the custom homepage, including support marker file management.
  • Register a homepage live watcher on page initialization when the custom homepage type is enabled and dispose it on page unload.
  • Create a FileSystemWatcher on the PCL directory targeting CustomLive.json and debounce change events before applying patches.
  • Write a CustomLive.supported.json marker with process metadata on startup and delete it on dispose only if it belongs to the current process.
Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.cs
Apply live patches from CustomLive.json to the already-loaded homepage UI instead of forcing a full reload.
  • When loading content, if the content hash has not changed, skip reloading XAML but still attempt to apply live patches.
  • After successfully loading the custom XAML into PanCustom, immediately apply patches from the JSON file.
  • Safely read the patch file with retries to handle concurrent writes and guard all patch application logic with error logging.
Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.cs
Define a JSON patch format and implement logic to map patches onto WPF elements via the Tag property.
  • Enumerate patch objects from various JSON shapes (object with patches array, direct patch array, or property-based shorthand) and normalize them.
  • Resolve target elements by matching FrameworkElement.Tag against the patch target/tag/name field and traverse the visual tree from PanCustom.
  • For each matched element, map simple JSON fields and a properties bag onto element CLR properties via reflection with basic type conversion and error logging.
Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.cs
Support updating common properties and replacing panel children via XAML snippets.
  • Support updating Text, Title, Info, ToolTip, Visibility, IsEnabled, Opacity and arbitrary properties through a properties sub-object, with case-insensitive JSON keys.
  • Implement ReplacePanelChildren to parse a childrenXaml snippet, strip redundant xmlns declarations, instantiate it as a temporary StackPanel, and swap its children into the target Panel.
  • Handle element lookup and property writes defensively, including type conversion for primitives and enums and tolerance for non-visual children when walking the tree.
Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.cs

Possibly linked issues

  • #在自定义主页刷新时,默认收起的卡片会显示由展开变为收起的动画: PR新增主页局部更新机制以减少整页刷新,从而避免折叠卡片刷新时异常动画

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我在这里给出了一些整体性的反馈:

  • TrySetElementProperty 中,解析数值和枚举类型时建议使用 CultureInfo.InvariantCulture(以及对应的 TryParse 变体),以避免在外部工具提供了意外格式时出现依赖区域性的失败或直接抛出异常。
  • 当前 FileSystemWatcher 的回调是在后台线程里通过 Thread.Sleep 来做防抖;改为在 UI 线程上使用 DispatcherTimer,或使用一个统一的防抖机制,会简化逻辑,并避免每一波变更都额外创建线程。
  • DisposeHomepageLiveWatcherDeleteHomepageLiveSupportMarker 中,所有异常都被吞掉了;至少对意料之外的释放/标记失败进行日志记录,会让诊断实时补丁机制的问题更容易,同时不会影响用户可见的行为。
给 AI 代理的提示(Prompt)
Please address the comments from this code review:

## Overall Comments
- In `TrySetElementProperty`, consider using `CultureInfo.InvariantCulture` (and `TryParse` variants) when parsing numeric and enum values to avoid culture-dependent failures or hard exceptions when external tools provide unexpected formats.
- The `FileSystemWatcher` callbacks currently debounce via `Thread.Sleep` in a background thread; using a `DispatcherTimer` or a central debounce mechanism on the UI thread would simplify the logic and avoid extra threads per burst of changes.
- In `DisposeHomepageLiveWatcher` and `DeleteHomepageLiveSupportMarker`, all exceptions are swallowed; logging at least unexpected disposal/marker failures would make diagnosing issues with the live patch mechanism easier without affecting user-facing behavior.

Sourcery 对开源项目是免费的——如果你觉得这些 Review 有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点选 👍 或 👎,我会根据你的反馈改进后续的 Review。
Original comment in English

Hey - I've left some high level feedback:

  • In TrySetElementProperty, consider using CultureInfo.InvariantCulture (and TryParse variants) when parsing numeric and enum values to avoid culture-dependent failures or hard exceptions when external tools provide unexpected formats.
  • The FileSystemWatcher callbacks currently debounce via Thread.Sleep in a background thread; using a DispatcherTimer or a central debounce mechanism on the UI thread would simplify the logic and avoid extra threads per burst of changes.
  • In DisposeHomepageLiveWatcher and DeleteHomepageLiveSupportMarker, all exceptions are swallowed; logging at least unexpected disposal/marker failures would make diagnosing issues with the live patch mechanism easier without affecting user-facing behavior.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `TrySetElementProperty`, consider using `CultureInfo.InvariantCulture` (and `TryParse` variants) when parsing numeric and enum values to avoid culture-dependent failures or hard exceptions when external tools provide unexpected formats.
- The `FileSystemWatcher` callbacks currently debounce via `Thread.Sleep` in a background thread; using a `DispatcherTimer` or a central debounce mechanism on the UI thread would simplify the logic and avoid extra threads per burst of changes.
- In `DisposeHomepageLiveWatcher` and `DeleteHomepageLiveSupportMarker`, all exceptions are swallowed; logging at least unexpected disposal/marker failures would make diagnosing issues with the live patch mechanism easier without affecting user-facing behavior.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Copy Markdown
Contributor

@Chiloven945 Chiloven945 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

方法命名规范请遵守 Wiki/技术规范

@Remygred Remygred requested a review from Chiloven945 May 15, 2026 09:39
@Chiloven945 Chiloven945 requested a review from a team May 15, 2026 11:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: L PR 大小评估:大型 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants