XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEToolBox

【WPF】XFE工具箱

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/XFEToolBox

管理后台与编辑器 UI 优化,新增字节转换器

本次提交包含多项改进: - 管理后台 DataGrid 视觉样式升级,支持丰富列模板、状态徽章、图标和空数据提示,提升可读性与一致性。 - 优化 CheckBox、ListBox、DataGrid、滚动条等控件样式,调整尺寸、圆角、配色和交互反馈,改善整体 UI 体验。 - Code Studio 编辑器主题和语义高亮增强,支持更细致的 C# 语义高亮、悬浮提示、签名帮助和补全文档。 - 资源管理器支持 XAML/代码文件互跳,自动显示相关按钮,优化多文件项目导航。 - 新建工具项目自动生成默认图标文件,manifest.json 默认引用新路径,客户端和服务端均支持。 - 工具包与软件下载目录文档全面更新,详细说明多渠道下载、manifest 约束、接口字段和安全约定。 - 新增 CONTRIBUTING.md,规范贡献流程、分支管理、代码约定和安全报告方式。 - 其它细节优化:修复默认图标路径、优化弹窗文档、调整 WebView2 控件类型、完善 XAML 资源引用等。 - 新增 ByteSizeConverter.cs,实现字节数值到带单位字符串的转换器,支持多种整型输入类型,ConvertBack 不支持反向转换。

5382f2f
XFE工作室室长 <mail@xfegzs.com>
提交于

代码差异

19 个文件 +1298 -193
Added CONTRIBUTING.md +99 -0
@@ -0,0 +1,99 @@
1 # 为 XFEToolBox 贡献
2
3 感谢你愿意改进 XFEToolBox。本文说明本地开发、测试、提交和文档维护的约定,适用于客户端、服务端、共享核心与工具包规范。
4
5 ## 开始之前
6
7 - 使用 Windows 与 .NET 10 SDK;涉及 Code Studio 时还需要 Microsoft Edge WebView2 Runtime。
8 - 先搜索已有 Issue 和 Pull Request,避免重复工作。较大的功能、公共契约变更或界面重构建议先发 Issue 说明目标和方案。
9 - `dev` 是日常开发分支。请从最新的 `dev` 创建短生命周期分支,并将 Pull Request 合并目标设为 `dev`。
10 - 不要提交密码、管理密钥、登录令牌、AutoConfig 生成的本地配置、服务端 `Data`、日志、构建产物或个人 IDE 设置。
11
12 ## 本地开发
13
14 ```powershell
15 git clone https://github.com/XFEstudio/XFEToolBox.git
16 Set-Location .\XFEToolBox
17 git switch dev
18 git pull --ff-only
19 git switch -c feature/short-topic
20 dotnet restore .\XFEToolBox.sln
21 dotnet build .\XFEToolBox.sln
22 ```
23
24 常用启动命令:
25
26 ```powershell
27 # WPF 客户端
28 dotnet run --project .\XFEToolBox\XFEToolBox.Client.csproj
29
30 # 服务端
31 dotnet run --project .\XFEToolBox.Server\XFEToolBox.Server.csproj
32 ```
33
34 服务端首次运行会创建管理员 `admin`,默认密码为 `ChangeMe_123!`。该账号仅用于本地初始化,首次登录后应立即修改密码。若要让客户端连接本地服务,需要同步调整 `ClientSession.ApiAddress`;请勿把个人环境地址意外提交到 Pull Request。
35
36 ## 代码约定
37
38 - 遵循现有 C# 风格:启用 Nullable 与 Implicit Usings,优先使用文件范围命名空间、清晰的类型名和早返回。
39 - 异步 I/O 使用 `async`/`await` 并以 `Async` 结尾;可传播的调用应继续传递 `CancellationToken`。
40 - 公共契约放在 `XFEToolBox.Core`,客户端专用的非 UI 能力放在 `XFEToolBox.Client.Core`,服务端存储与校验放在 `XFEToolBox.Server.Core`。
41 - WPF 页面保持 View、ViewModel 和业务服务边界清晰。新增样式前先复用现有 ResourceDictionary、控件和主题资源。
42 - 用户可见文字应明确、可操作;同一功能内保持中英文术语一致。
43 - 文件、ZIP、下载地址和用户输入必须在信任边界处校验。不要降低路径穿越、符号链接、文件数量、解压大小、压缩率或 SHA-256 校验等安全限制。
44 - 不要静默吞掉会影响数据、登录、下载或发布结果的异常;应记录或向用户显示有意义的信息。
45 - 保持修改聚焦,避免把无关格式化、资源重排或生成文件混入同一提交。
46
47 ## 工具包与服务端变更
48
49 修改 `.xfetool` 清单、目录契约或接口时,需要同时检查以下位置:
50
51 - `XFEToolBox.Core` 中的共享模型与客户端契约;
52 - `XFEToolBox.Server.Core` 中的校验和存储;
53 - `XFEToolBox.Server` 中的公开及管理接口;
54 - WPF 客户端中的 Code Studio、下载、缓存和运行流程;
55 - `docs/tool-packages.md` 或 `docs/software-catalog.md` 中的示例与限制。
56
57 破坏兼容性的工具包变更应提升 `packageFormatVersion`,并明确旧版本的迁移或拒绝策略。新增管理员能力时,应继续校验登录态和管理员角色;遗留的 API Key 接口不得暴露到不受信网络。
58
59 ## 测试与验证
60
61 提交前至少构建整个解决方案,并运行与修改范围相关的测试:
62
63 ```powershell
64 dotnet build .\XFEToolBox.sln --configuration Release
65 dotnet run --project .\XFEToolBox.Test\XFEToolBox.Client.Test.csproj --configuration Release -- --benchmarks --quick
66 dotnet run --project .\XFEToolBox.Server.Test\XFEToolBox.Server.Test.csproj --configuration Release
67 dotnet run --project .\XFEToolBox.Client.Wpf.Test\XFEToolBox.Client.Wpf.Test.csproj --configuration Release -- --benchmarks --quick
68 ```
69
70 客户端与 WPF 验证当前使用兼容期内的 `SMTest` 基准标记;缺少 `--benchmarks` 时运行器会发现 0 项。新增代码应使用当前测试框架的 `Test`/`TestCase` 或 `Benchmark`,不要继续增加 `SMTest`。
71
72 涉及 WPF 的改动还应手动检查窗口缩放、DPI、滚动、键盘操作、空数据、错误状态和主题资源;涉及服务端的改动应检查未登录、普通用户、管理员、非法输入和大小边界。
73
74 若某项测试受环境限制无法运行,请在 Pull Request 中写明未运行的命令、原因和已完成的替代验证。
75
76 ## 提交和 Pull Request
77
78 提交信息使用简短、具体的中文或英文,说明实际结果,例如 `修复工具包路径校验`。一个提交尽量只表达一个逻辑变更。
79
80 Pull Request 应包含:
81
82 - 变更目的和用户可见效果;
83 - 关键实现与兼容性、安全性说明;
84 - 实际运行过的构建、测试和手动验证;
85 - UI 变更前后的截图或录屏;
86 - 关联的 Issue,以及仍待处理的限制。
87
88 提交前检查:
89
90 - [ ] 修改范围聚焦,未覆盖他人的无关改动。
91 - [ ] 未包含密钥、令牌、本地配置、运行数据或生成产物。
92 - [ ] Debug/Release 构建和相关测试通过,或已说明限制。
93 - [ ] 新行为有测试覆盖,边界和失败路径已验证。
94 - [ ] 公共接口、配置、工具包格式或用户流程变化已同步更新文档。
95 - [ ] UI 变更已进行人工视觉检查。
96
97 ## 报告安全问题
98
99 发现认证绕过、任意文件访问、危险工具包执行或敏感信息泄露时,请不要在公开 Issue 中披露利用细节。应通过仓库维护者提供的私密联系方式报告,并附上影响范围、复现条件和建议修复方向。
Modified README.md +89 -1
@@ -1,3 +1,91 @@
1 1 # XFEToolBox
2 2
3 ## �����ز���дMD�ĵ��ˣ���͵����~
3 ![XFEToolBox Logo](XFEToolBox/Resources/Icon/XFEToolBoxLogo.png)
4
5 XFEToolBox 是一个基于 .NET 10 与 WPF 的 Windows 桌面工具箱。项目由桌面客户端、工具与软件下载服务端、共享契约、测试程序和安装器组成,当前程序集版本为 `0.2.0`。
6
7 > 项目仍在持续开发中,界面、服务接口和工具包规范可能继续调整。请勿在生产环境中使用默认管理员密码或未经审核的源码工具包。
8
9 ## 主要功能
10
11 - WPF 桌面客户端:提供主页、控制台、工具库、软件下载、设置和个人中心。
12 - XFEToolBox Code Studio:使用 Monaco Editor 与 WebView2 创建和编辑 WPF 源码工具,可预览、独立编译运行、导出或发布 `.xfetool` 工具包。
13 - 工具分发:服务端校验、保存和发布源码工具包;客户端在下载后校验 SHA-256,并在独立窗口中编译运行。
14 - 软件下载目录:支持搜索、分类、多下载渠道、浏览器跳转、客户端下载和服务端文件托管。
15 - 账号与管理:支持注册、登录、个人资料以及管理员侧的用户、工具包和软件目录管理。
16
17 ## 环境要求
18
19 - Windows 10 1809(Build 17763)或更高版本,用于运行 WPF 客户端。
20 - [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)。
21 - Microsoft Edge WebView2 Runtime,用于 Code Studio 编辑器和 Markdown 预览。
22 - 可选:支持 .NET 10 与 WPF 的 Visual Studio。
23
24 服务端和不依赖 WPF 的核心项目以 `net10.0` 为目标框架;完整解决方案建议在 Windows 上构建。
25
26 ## 快速开始
27
28 克隆仓库并还原、构建解决方案:
29
30 ```powershell
31 git clone https://github.com/XFEstudio/XFEToolBox.git
32 Set-Location .\XFEToolBox
33 dotnet restore .\XFEToolBox.sln
34 dotnet build .\XFEToolBox.sln
35 ```
36
37 启动桌面客户端:
38
39 ```powershell
40 dotnet run --project .\XFEToolBox\XFEToolBox.Client.csproj
41 ```
42
43 启动服务端:
44
45 ```powershell
46 dotnet run --project .\XFEToolBox.Server\XFEToolBox.Server.csproj
47 ```
48
49 服务端默认监听 `http://localhost:3000/api`。首次启动会生成 AutoConfig XML 配置及初始管理员 `admin`;默认密码为 `ChangeMe_123!`,登录后应立即修改。工具包和软件文件的默认数据目录位于服务端输出目录下的 `Data`,这些运行数据不应提交到仓库。
50
51 客户端当前通过 `XFEToolBox/Utilities/Server/ClientSession.cs` 中的 `ApiAddress` 连接已部署服务。联调本地服务时,请先将该地址切换为本地 API 地址。
52
53 ## 测试
54
55 仓库中的验证项目是可直接运行的控制台程序。客户端与 WPF 项目仍使用兼容期内的 `SMTest` 基准标记,因此需要显式传入 `--benchmarks`:
56
57 ```powershell
58 dotnet run --project .\XFEToolBox.Test\XFEToolBox.Client.Test.csproj --configuration Release -- --benchmarks --quick
59 dotnet run --project .\XFEToolBox.Server.Test\XFEToolBox.Server.Test.csproj
60 dotnet run --project .\XFEToolBox.Client.Wpf.Test\XFEToolBox.Client.Wpf.Test.csproj --configuration Release -- --benchmarks --quick
61 ```
62
63 最后一项会初始化 WPF 和图形环境,应在 Windows 桌面会话中运行。`SMTest` 已被测试框架标记为弃用,后续应迁移到 `Benchmark`。
64
65 ## 项目结构
66
67 | 路径 | 说明 |
68 | --- | --- |
69 | `XFEToolBox/` | WPF 桌面客户端与 Code Studio |
70 | `XFEToolBox.Client.Core/` | 客户端可复用的非 WPF 基础能力 |
71 | `XFEToolBox.Core/` | 客户端与服务端共享的模型、契约和目录客户端 |
72 | `XFEToolBox.Server/` | 服务端入口、用户体系、目录与管理接口 |
73 | `XFEToolBox.Server.Core/` | 工具包校验、语义化版本与文件仓库实现 |
74 | `XFEToolBoxInstaller/` | WPF 安装器项目 |
75 | `XFEToolBox.Test/` | 客户端核心测试 |
76 | `XFEToolBox.Server.Test/` | 服务端核心测试 |
77 | `XFEToolBox.Client.Wpf.Test/` | WPF 渲染与性能测试 |
78 | `docs/` | 功能与扩展规范 |
79
80 ## 文档
81
82 - [工具源码包、Code Studio 与服务端接口](docs/tool-packages.md)
83 - [软件下载目录与多渠道配置](docs/software-catalog.md)
84 - [通用弹窗使用说明](docs/popup-window.md)
85 - [贡献指南](CONTRIBUTING.md)
86
87 ## 贡献与许可
88
89 欢迎提交问题和改进。开始编码前请阅读 [CONTRIBUTING.md](CONTRIBUTING.md),并确保构建及相关测试通过。
90
91 本项目采用 [Apache License 2.0](LICENSE.txt) 许可。
Modified XFEToolBox/Resources/Editor/editor.html +429 -39
@@ -26,8 +26,10 @@
26 26 display: flex; align-items: flex-end; gap: 4px; overflow-x: auto; overflow-y: hidden;
27 27 padding: 5px 7px 0; background: var(--surface-subtle); border-bottom: 1px solid var(--border);
28 28 }
29 #tabs::-webkit-scrollbar { height: 3px; }
30 #tabs::-webkit-scrollbar-thumb { background: #c8c7e9; border-radius: 3px; }
29 #tabs::-webkit-scrollbar { height: 8px; }
30 #tabs::-webkit-scrollbar-track { background: #ececfa; border-radius: 5px; }
31 #tabs::-webkit-scrollbar-thumb { background: #aaa9e9; border: 2px solid #ececfa; border-radius: 5px; }
32 #tabs::-webkit-scrollbar-thumb:hover { background: #9292df; }
31 33 .tab {
32 34 min-width: 126px; max-width: 220px; height: 36px; display: flex; align-items: center;
33 35 padding: 0 10px; gap: 7px; border: 1px solid transparent; border-bottom: 0;
@@ -76,6 +78,9 @@
76 78 --border: #34344a; --text: #deddec; --muted: #85859b;
77 79 }
78 80 body.dark #tabs { background: #1b1b2b; }
81 body.dark #tabs::-webkit-scrollbar-track { background: #2d2d45; }
82 body.dark #tabs::-webkit-scrollbar-thumb { background: #7777c9; border-color: #2d2d45; }
83 body.dark #tabs::-webkit-scrollbar-thumb:hover { background: #9898e7; }
79 84 body.dark .tab:hover { background: #29293e; color: #d3d3e4; }
80 85 body.dark .tab.drag-over { background: #353550; }
81 86 body.dark .tab.active { background: #202033; color: white; }
@@ -114,70 +119,184 @@
114 119 require.config({ paths: { vs: './monaco/vs' } });
115 120 require(['vs/editor/editor.main'], function () {
116 121 monaco.editor.defineTheme('xfe-light', {
117 base: 'vs', inherit: true,
122 base: 'vs', inherit: true, semanticHighlighting: true,
118 123 rules: [
119 { token: 'keyword', foreground: '7777C9', fontStyle: 'bold' },
120 { token: 'string', foreground: '3F8A68' },
121 { token: 'number', foreground: 'C46D38' },
122 { token: 'comment', foreground: '9898A8', fontStyle: 'italic' },
123 { token: 'type', foreground: '5C75B8' },
124 { token: 'tag', foreground: '715DB5' },
125 { token: 'attribute.name', foreground: 'A05E78' }
124 { token: 'keyword', foreground: '7553B5', fontStyle: 'bold' },
125 { token: 'keyword.control', foreground: '8A3FA0', fontStyle: 'bold' },
126 { token: 'keyword.json', foreground: '9A496D', fontStyle: 'bold' },
127 { token: 'namespace', foreground: '7061B4' },
128 { token: 'namespace.cpp', foreground: 'A45178', fontStyle: 'bold' },
129 { token: 'type', foreground: '2F6FA3' },
130 { token: 'class', foreground: '2F6FA3', fontStyle: 'bold' },
131 { token: 'interface', foreground: '087B83', fontStyle: 'bold' },
132 { token: 'struct', foreground: 'A75C36', fontStyle: 'bold' },
133 { token: 'enum', foreground: '99671F', fontStyle: 'bold' },
134 { token: 'attribute', foreground: '9D4D78' },
135 { token: 'method', foreground: '7250A5' },
136 { token: 'property', foreground: 'A14970' },
137 { token: 'field', foreground: '956025' },
138 { token: 'event', foreground: 'B04C69' },
139 { token: 'parameter', foreground: '855E42' },
140 { token: 'variable', foreground: '4D6578' },
141 { token: 'constant', foreground: 'B04D61', fontStyle: 'bold' },
142 { token: 'operator', foreground: '6B6682' },
143 { token: 'delimiter', foreground: '77728B' },
144 { token: 'number', foreground: 'B45B32' },
145 { token: 'number.hex', foreground: 'A35467' },
146 { token: 'string', foreground: '2E7D5B' },
147 { token: 'string.key.json', foreground: '2F6FA3' },
148 { token: 'string.value.json', foreground: '2E7D5B' },
149 { token: 'string.escape', foreground: 'B45B32', fontStyle: 'bold' },
150 { token: 'string.escape.invalid', foreground: 'D13F57', fontStyle: 'bold underline' },
151 { token: 'comment', foreground: '7F8998', fontStyle: 'italic' },
152 { token: 'tag', foreground: '6652B5', fontStyle: 'bold' },
153 { token: 'attribute.name', foreground: 'A04D70' },
154 { token: 'attribute.value', foreground: '2E7D5B' },
155 { token: 'metatag', foreground: '8C5B2F' },
156 { token: 'strong', foreground: 'A14970', fontStyle: 'bold' },
157 { token: 'emphasis', foreground: '2F6FA3', fontStyle: 'italic' },
158 { token: 'link', foreground: '6262C2', fontStyle: 'underline' },
159 { token: 'variable.source', foreground: '985B2F' }
126 160 ],
127 161 colors: {
128 162 'editor.background': '#FBFBFE', 'editor.foreground': '#444459',
129 163 'editorLineNumber.foreground': '#B0AFC0', 'editorLineNumber.activeForeground': '#6969B8',
130 164 'editor.selectionBackground': '#9898E74A', 'editor.inactiveSelectionBackground': '#B9B9E72E',
165 'editor.selectionHighlightBackground': '#9898E724', 'editor.wordHighlightBackground': '#70A7D92A',
166 'editor.wordHighlightStrongBackground': '#B17BB632', 'editor.findMatchBackground': '#F0C66A75',
167 'editor.findMatchHighlightBackground': '#F0C66A36', 'editor.findRangeHighlightBackground': '#9898E71D',
131 168 'editorCursor.foreground': '#7777C9', 'editorIndentGuide.background1': '#E8E7F0',
132 169 'editorIndentGuide.activeBackground1': '#C7C6DF', 'editor.lineHighlightBackground': '#F3F2FA',
133 170 'editorWhitespace.foreground': '#D5D4E2', 'editorWidget.background': '#FFFFFF',
134 'editorWidget.border': '#D9D8E7', 'editorSuggestWidget.selectedBackground': '#EDECF9',
171 'editorWidget.border': '#D9D8E7', 'editorWidget.resizeBorder': '#9898E7',
135 172 'editorSuggestWidget.background': '#FFFFFF', 'editorSuggestWidget.foreground': '#343449',
136 173 'editorSuggestWidget.selectedForeground': '#29293D', 'editorSuggestWidget.highlightForeground': '#5555B8',
137 174 'editorSuggestWidget.focusHighlightForeground': '#4545A7', 'editorSuggestWidget.selectedBackground': '#DCDCF6',
138 175 'editorSuggestWidget.selectedIconForeground': '#4A4AA5', 'editorSuggestWidgetStatus.foreground': '#66667B',
139 176 'editorSuggestWidget.border': '#BEBED8', 'editorHoverWidget.background': '#FFFFFF',
140 177 'editorHoverWidget.foreground': '#343449', 'editorHoverWidget.border': '#BEBED8',
141 'minimap.background': '#F8F8FC', 'scrollbarSlider.background': '#B7B6D84D',
142 'scrollbarSlider.hoverBackground': '#9D9CCD66', 'scrollbarSlider.activeBackground': '#8585BC73'
178 'editorHoverWidget.highlightForeground': '#5959B5', 'editorHoverWidget.statusBarBackground': '#F4F3FB',
179 'editorCodeLens.foreground': '#85849B', 'editorLink.activeForeground': '#5858BC',
180 'editorInlayHint.foreground': '#77758D', 'editorInlayHint.background': '#EEEFFA',
181 'editorError.foreground': '#D9435D', 'editorWarning.foreground': '#C77A1E',
182 'editorInfo.foreground': '#3C7FBA', 'editorHint.foreground': '#766AC0',
183 'editorOverviewRuler.errorForeground': '#D9435DB8', 'editorOverviewRuler.warningForeground': '#C77A1EB8',
184 'editorOverviewRuler.infoForeground': '#3C7FBAB8', 'editorLightBulb.foreground': '#B47A19',
185 'editorBracketHighlight.foreground1': '#7857C4', 'editorBracketHighlight.foreground2': '#3279B5',
186 'editorBracketHighlight.foreground3': '#25866C', 'editorBracketHighlight.foreground4': '#C06A34',
187 'editorBracketHighlight.foreground5': '#B04D78', 'editorBracketHighlight.foreground6': '#3D8E98',
188 'editorBracketHighlight.unexpectedBracket.foreground': '#D9435D',
189 'editorBracketPairGuide.background1': '#7857C438', 'editorBracketPairGuide.background2': '#3279B538',
190 'editorBracketPairGuide.background3': '#25866C38', 'editorBracketPairGuide.activeBackground1': '#7857C4A8',
191 'editorBracketPairGuide.activeBackground2': '#3279B5A8', 'editorBracketPairGuide.activeBackground3': '#25866CA8',
192 'editorGutter.foldingControlForeground': '#7777B8', 'editorStickyScroll.background': '#F7F6FC',
193 'editorStickyScroll.border': '#E2E1ED', 'editorStickyScrollHover.background': '#EEEFFA',
194 'minimap.background': '#F8F8FC', 'scrollbarSlider.background': '#9898E77A',
195 'scrollbarSlider.hoverBackground': '#8989D9B3', 'scrollbarSlider.activeBackground': '#7777C9E6'
143 196 }
144 197 });
145 198 monaco.editor.defineTheme('xfe-dark', {
146 base: 'vs-dark', inherit: true,
199 base: 'vs-dark', inherit: true, semanticHighlighting: true,
147 200 rules: [
148 { token: 'keyword', foreground: 'C9A7FF' },
201 { token: 'keyword', foreground: 'C69CF4', fontStyle: 'bold' },
202 { token: 'keyword.control', foreground: 'F0A7E1', fontStyle: 'bold' },
203 { token: 'keyword.json', foreground: 'F5A6CD', fontStyle: 'bold' },
204 { token: 'namespace', foreground: 'AFA2E8' },
205 { token: 'namespace.cpp', foreground: 'F5A6CD', fontStyle: 'bold' },
206 { token: 'type', foreground: '7DCFFF' },
207 { token: 'class', foreground: '7DCFFF', fontStyle: 'bold' },
208 { token: 'interface', foreground: '5AD6C8', fontStyle: 'bold' },
209 { token: 'struct', foreground: 'F5A97F', fontStyle: 'bold' },
210 { token: 'enum', foreground: 'F0C57A', fontStyle: 'bold' },
211 { token: 'attribute', foreground: 'F5A6CD' },
212 { token: 'method', foreground: 'D2B0F3' },
213 { token: 'property', foreground: 'FFB3D2' },
214 { token: 'field', foreground: 'E8C07D' },
215 { token: 'event', foreground: 'FF8EAC' },
216 { token: 'parameter', foreground: 'F0C6A8' },
217 { token: 'variable', foreground: 'C6D0F5' },
218 { token: 'constant', foreground: 'FF8EA1', fontStyle: 'bold' },
219 { token: 'operator', foreground: 'B7B0D8' },
220 { token: 'delimiter', foreground: '9390AA' },
149 221 { token: 'string', foreground: 'A6E3A1' },
150 222 { token: 'number', foreground: 'FAB387' },
151 { token: 'comment', foreground: '6C7086', fontStyle: 'italic' }
223 { token: 'number.hex', foreground: 'F5A6CD' },
224 { token: 'string.key.json', foreground: '8AADF4' },
225 { token: 'string.value.json', foreground: 'A6E3A1' },
226 { token: 'string.escape', foreground: 'F9C36A', fontStyle: 'bold' },
227 { token: 'string.escape.invalid', foreground: 'FF6F91', fontStyle: 'bold underline' },
228 { token: 'comment', foreground: '8389A1', fontStyle: 'italic' },
229 { token: 'tag', foreground: 'CBA6F7', fontStyle: 'bold' },
230 { token: 'attribute.name', foreground: 'F5C2E7' },
231 { token: 'attribute.value', foreground: 'A6E3A1' },
232 { token: 'metatag', foreground: 'F0C57A' },
233 { token: 'strong', foreground: 'FFB3D2', fontStyle: 'bold' },
234 { token: 'emphasis', foreground: '7DCFFF', fontStyle: 'italic' },
235 { token: 'link', foreground: 'B4BEFE', fontStyle: 'underline' },
236 { token: 'variable.source', foreground: 'F5A97F' }
152 237 ],
153 238 colors: {
154 239 'editor.background': '#202033', 'editor.foreground': '#d9d9e8',
155 240 'editorLineNumber.foreground': '#58586b', 'editorLineNumber.activeForeground': '#cdd6f4',
156 241 'editor.selectionBackground': '#55559A70', 'editor.inactiveSelectionBackground': '#45456A55',
242 'editor.selectionHighlightBackground': '#7373BA35', 'editor.wordHighlightBackground': '#5A9BCD32',
243 'editor.wordHighlightStrongBackground': '#BE7DB43D', 'editor.findMatchBackground': '#D9A84D76',
244 'editor.findMatchHighlightBackground': '#D9A84D3E', 'editor.findRangeHighlightBackground': '#7777C929',
157 245 'editorCursor.foreground': '#b4befe', 'editorIndentGuide.background1': '#343447',
158 'editor.lineHighlightBackground': '#26263A', 'minimap.background': '#1E1E30',
246 'editorIndentGuide.activeBackground1': '#666684', 'editor.lineHighlightBackground': '#26263A',
247 'editorWhitespace.foreground': '#414159', 'editorWidget.background': '#29293E',
248 'editorWidget.border': '#555574', 'editorWidget.resizeBorder': '#9898E7',
159 249 'editorSuggestWidget.background': '#29293E', 'editorSuggestWidget.foreground': '#ECECF6',
160 250 'editorSuggestWidget.selectedForeground': '#FFFFFF', 'editorSuggestWidget.highlightForeground': '#C7C7FF',
161 251 'editorSuggestWidget.focusHighlightForeground': '#D9D9FF', 'editorSuggestWidget.selectedBackground': '#444466',
162 252 'editorSuggestWidget.selectedIconForeground': '#D7D7FF', 'editorSuggestWidgetStatus.foreground': '#B8B8CA',
163 253 'editorSuggestWidget.border': '#555574', 'editorHoverWidget.background': '#29293E',
164 'editorHoverWidget.foreground': '#ECECF6', 'editorHoverWidget.border': '#555574'
254 'editorHoverWidget.foreground': '#ECECF6', 'editorHoverWidget.border': '#555574',
255 'editorHoverWidget.highlightForeground': '#C8C8FF', 'editorHoverWidget.statusBarBackground': '#242438',
256 'editorCodeLens.foreground': '#9998B0', 'editorLink.activeForeground': '#C7C7FF',
257 'editorInlayHint.foreground': '#BBB9CC', 'editorInlayHint.background': '#313149',
258 'editorError.foreground': '#FF6F91', 'editorWarning.foreground': '#F0B35B',
259 'editorInfo.foreground': '#69B9F5', 'editorHint.foreground': '#B4A7F5',
260 'editorOverviewRuler.errorForeground': '#FF6F91C8', 'editorOverviewRuler.warningForeground': '#F0B35BC8',
261 'editorOverviewRuler.infoForeground': '#69B9F5C8', 'editorLightBulb.foreground': '#F0C57A',
262 'editorBracketHighlight.foreground1': '#C69CF4', 'editorBracketHighlight.foreground2': '#7DCFFF',
263 'editorBracketHighlight.foreground3': '#5AD6C8', 'editorBracketHighlight.foreground4': '#F5A97F',
264 'editorBracketHighlight.foreground5': '#F5A6CD', 'editorBracketHighlight.foreground6': '#F0C57A',
265 'editorBracketHighlight.unexpectedBracket.foreground': '#FF6F91',
266 'editorBracketPairGuide.background1': '#C69CF44A', 'editorBracketPairGuide.background2': '#7DCFFF4A',
267 'editorBracketPairGuide.background3': '#5AD6C84A', 'editorBracketPairGuide.activeBackground1': '#C69CF4C0',
268 'editorBracketPairGuide.activeBackground2': '#7DCFFFC0', 'editorBracketPairGuide.activeBackground3': '#5AD6C8C0',
269 'editorGutter.foldingControlForeground': '#AFA2E8', 'editorStickyScroll.background': '#222236',
270 'editorStickyScroll.border': '#3D3D55', 'editorStickyScrollHover.background': '#303049',
271 'minimap.background': '#1E1E30',
272 'scrollbarSlider.background': '#9898E77A', 'scrollbarSlider.hoverBackground': '#AAAAEEB3',
273 'scrollbarSlider.activeBackground': '#BDBDF5E6'
165 274 }
166 275 });
276 registerSemanticHighlighting();
167 277 editor = monaco.editor.create(document.getElementById('editor'), {
168 278 theme: 'xfe-light', automaticLayout: true, fontFamily: "'Cascadia Code', 'JetBrains Mono', Consolas, monospace",
169 fontSize: 13.5, lineHeight: 22, fontLigatures: true,
279 fontSize: 13.5, lineHeight: 22, fontLigatures: true, fontWeight: '450',
170 280 minimap: { enabled: true, renderCharacters: false, maxColumn: 100 }, smoothScrolling: true,
171 281 cursorSmoothCaretAnimation: 'on', bracketPairColorization: { enabled: true },
172 cursorBlinking: 'smooth', guides: { bracketPairs: true, indentation: true }, padding: { top: 14, bottom: 12 },
282 cursorBlinking: 'smooth', guides: { bracketPairs: true, bracketPairsHorizontal: 'active', highlightActiveBracketPair: true, indentation: true, highlightActiveIndentation: true }, padding: { top: 14, bottom: 12 },
173 283 quickSuggestions: { other: true, comments: false, strings: false },
174 suggestOnTriggerCharacters: true, wordBasedSuggestions: 'matchingDocuments',
284 suggestOnTriggerCharacters: true, wordBasedSuggestions: 'matchingDocuments', acceptSuggestionOnEnter: 'smart',
285 tabCompletion: 'on', snippetSuggestions: 'top', suggestSelection: 'recentlyUsedByPrefix',
286 suggest: { showIcons: true, showStatusBar: true, preview: true, previewMode: 'subwordSmart', showInlineDetails: true, localityBonus: true, shareSuggestSelections: true },
287 parameterHints: { enabled: true, cycle: true }, hover: { enabled: true, delay: 280, sticky: true },
288 inlineSuggest: { enabled: true, showToolbar: 'onHover' }, inlayHints: { enabled: 'on' },
289 'semanticHighlighting.enabled': true,
175 290 formatOnPaste: true, formatOnType: true, tabSize: 4, insertSpaces: true,
176 renderLineHighlight: 'all', renderWhitespace: 'selection', scrollBeyondLastLine: false,
177 stickyScroll: { enabled: true }, scrollbar: { verticalScrollbarSize: 10, horizontalScrollbarSize: 10 }
291 autoClosingBrackets: 'always', autoClosingQuotes: 'always', autoSurround: 'languageDefined',
292 matchBrackets: 'always', selectionHighlight: true, occurrencesHighlight: 'singleFile',
293 renderLineHighlight: 'all', renderWhitespace: 'selection', renderControlCharacters: true, scrollBeyondLastLine: false,
294 folding: true, foldingHighlight: true, showFoldingControls: 'mouseover', links: true, colorDecorators: true,
295 stickyScroll: { enabled: true, maxLineCount: 4 }, scrollbar: { verticalScrollbarSize: 12, horizontalScrollbarSize: 12 }
178 296 });
179 297
180 298 registerCompletions();
299 registerLanguageHints();
181 300 registerFormatters();
182 301 editor.onDidChangeCursorPosition(updateStatus);
183 302 editor.onDidChangeModelContent(() => {
@@ -358,23 +477,161 @@
358 477 const text = model.getValue();
359 478 if (model.getLanguageId() === 'csharp') {
360 479 const stack = [];
361 for (let i = 0; i < text.length; i++) {
362 if (text[i] === '{') stack.push(i);
363 if (text[i] === '}' && stack.length === 0) {
364 const p = model.getPositionAt(i); markers.push(marker('多余的右花括号', p.lineNumber, p.column));
365 } else if (text[i] === '}') stack.pop();
480 const pairs = { '{': '}', '[': ']', '(': ')' };
481 const closing = new Set(Object.values(pairs));
482 const lines = model.getLinesContent();
483 const lexicalLines = monaco.editor.tokenize(text, 'csharp');
484 for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
485 const line = lines[lineIndex];
486 const tokens = lexicalLines[lineIndex] || [];
487 for (let columnIndex = 0; columnIndex < line.length; columnIndex++) {
488 if (/comment|string/.test(lexicalTypeAt(tokens, columnIndex))) continue;
489 const character = line[columnIndex];
490 if (pairs[character]) {
491 stack.push({ character, line: lineIndex + 1, column: columnIndex + 1 });
492 continue;
493 }
494 if (!closing.has(character)) continue;
495 const opening = stack[stack.length - 1];
496 if (!opening) {
497 markers.push(marker(`没有与“${character}”匹配的左括号`, lineIndex + 1, columnIndex + 1));
498 } else if (pairs[opening.character] !== character) {
499 markers.push(marker(`此处应使用“${pairs[opening.character]}”来匹配第 ${opening.line} 行的“${opening.character}”`, lineIndex + 1, columnIndex + 1));
500 stack.pop();
501 } else {
502 stack.pop();
503 }
504 }
366 505 }
367 stack.forEach(i => { const p = model.getPositionAt(i); markers.push(marker('缺少匹配的右花括号', p.lineNumber, p.column)); });
506 stack.forEach(opening => markers.push(marker(`缺少与“${opening.character}”匹配的“${pairs[opening.character]}”`, opening.line, opening.column)));
368 507 } else if (model.getLanguageId() === 'xml' && text.trim()) {
369 508 const parsed = new DOMParser().parseFromString(text, 'application/xml');
370 509 const error = parsed.querySelector('parsererror');
371 if (error) markers.push(marker(error.textContent.split('\n')[0], 1, 1));
510 if (error) {
511 const message = error.textContent.split('\n')[0];
512 const line = Number(/(?:line|行)\s*[::]?\s*(\d+)/i.exec(error.textContent)?.[1] || 1);
513 const column = Number(/(?:column|列)\s*[::]?\s*(\d+)/i.exec(error.textContent)?.[1] || 1);
514 markers.push(marker(`XAML / XML 结构错误:${message}`, line, column));
515 }
516 } else if (model.getLanguageId() === 'json' && text.trim()) {
517 try {
518 JSON.parse(text);
519 } catch (error) {
520 const offset = Number(/position\s+(\d+)/i.exec(error.message)?.[1] || 0);
521 const position = model.getPositionAt(Math.min(offset, text.length));
522 markers.push(marker(`JSON 语法错误:${error.message}`, position.lineNumber, position.column));
523 }
372 524 }
373 525 monaco.editor.setModelMarkers(model, 'xfe-basic-diagnostics', markers);
374 526 document.getElementById('diagnostics').textContent = markers.length ? `⚠ ${markers.length} 个问题` : '✓ 0 个问题';
375 527 }
376 528
377 function marker(message, line, column) { return { severity: monaco.MarkerSeverity.Error, message, startLineNumber: line, startColumn: column, endLineNumber: line, endColumn: column + 1 }; }
529 function marker(message, line, column) {
530 return { severity: monaco.MarkerSeverity.Error, source: 'XFE 编辑器', message, startLineNumber: line, startColumn: column, endLineNumber: line, endColumn: column + 1 };
531 }
532
533 function registerSemanticHighlighting() {
534 const legend = {
535 tokenTypes: ['namespace', 'type', 'class', 'interface', 'struct', 'enum', 'attribute', 'method', 'property', 'field', 'event', 'parameter', 'variable', 'constant'],
536 tokenModifiers: ['declaration', 'readonly', 'static', 'async']
537 };
538 const typeIndexes = new Map(legend.tokenTypes.map((type, index) => [type, index]));
539 monaco.languages.registerDocumentSemanticTokensProvider('csharp', {
540 getLegend: () => legend,
541 provideDocumentSemanticTokens(model, _lastResultId, cancellationToken) {
542 const values = [];
543 const lines = model.getLinesContent();
544 const lexicalLines = monaco.editor.tokenize(model.getValue(), 'csharp');
545 for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
546 if (cancellationToken.isCancellationRequested) return null;
547 const line = lines[lineIndex];
548 const lexicalTokens = lexicalLines[lineIndex] || [];
549 const identifierPattern = /@?[A-Za-z_]\w*/g;
550 let match;
551 while ((match = identifierPattern.exec(line)) !== null) {
552 const lexicalType = lexicalTypeAt(lexicalTokens, match.index);
553 if (/comment|string|number|keyword|directive/.test(lexicalType)) continue;
554 const rawWord = match[0];
555 const word = rawWord.startsWith('@') ? rawWord.slice(1) : rawWord;
556 const before = line.slice(0, match.index);
557 const after = line.slice(match.index + rawWord.length);
558 const classification = classifyCSharpIdentifier(word, before, after);
559 if (!classification || !typeIndexes.has(classification.type)) continue;
560 let modifierMask = 0;
561 classification.modifiers.forEach(modifier => {
562 const modifierIndex = legend.tokenModifiers.indexOf(modifier);
563 if (modifierIndex >= 0) modifierMask |= (1 << modifierIndex);
564 });
565 values.push({ line: lineIndex, start: match.index, length: rawWord.length, type: typeIndexes.get(classification.type), modifiers: modifierMask });
566 }
567 }
568 values.sort((left, right) => left.line - right.line || left.start - right.start);
569 const data = [];
570 let previousLine = 0;
571 let previousStart = 0;
572 values.forEach(value => {
573 const deltaLine = value.line - previousLine;
574 const deltaStart = deltaLine === 0 ? value.start - previousStart : value.start;
575 data.push(deltaLine, deltaStart, value.length, value.type, value.modifiers);
576 previousLine = value.line;
577 previousStart = value.start;
578 });
579 return { data: new Uint32Array(data) };
580 },
581 releaseDocumentSemanticTokens() { }
582 });
583 }
584
585 function lexicalTypeAt(tokens, offset) {
586 let type = '';
587 for (const token of tokens) {
588 if (token.offset > offset) break;
589 type = token.type || '';
590 }
591 return type;
592 }
593
594 function classifyCSharpIdentifier(word, before, after) {
595 const modifiers = [];
596 const addDeclarationModifiers = () => {
597 modifiers.push('declaration');
598 if (/\breadonly\b/.test(before)) modifiers.push('readonly');
599 if (/\bstatic\b/.test(before)) modifiers.push('static');
600 if (/\basync\b/.test(before)) modifiers.push('async');
601 };
602 if (/\b(?:namespace|using)\s+(?:global::)?(?:[A-Za-z_]\w*\.)*$/.test(before)) return { type: 'namespace', modifiers };
603 if (/\b(?:class|record)\s+$/.test(before)) { addDeclarationModifiers(); return { type: 'class', modifiers }; }
604 if (/\binterface\s+$/.test(before)) { addDeclarationModifiers(); return { type: 'interface', modifiers }; }
605 if (/\bstruct\s+$/.test(before)) { addDeclarationModifiers(); return { type: 'struct', modifiers }; }
606 if (/\benum\s+$/.test(before)) { addDeclarationModifiers(); return { type: 'enum', modifiers }; }
607
608 const lastAttributeOpen = before.lastIndexOf('[');
609 const lastAttributeClose = before.lastIndexOf(']');
610 if (lastAttributeOpen > lastAttributeClose && /^[A-Z]/.test(word)) return { type: 'attribute', modifiers };
611
612 const declarationPrefix = /\b(?:var|bool|byte|sbyte|short|ushort|int|uint|long|ulong|float|double|decimal|char|string|object|dynamic|[A-Z][A-Za-z0-9_.]*(?:<[^;=(){}]+>)?(?:\[\])?)[?]?\s+$/;
613 const isDeclaration = declarationPrefix.test(before);
614 if (/\bevent\s+[A-Za-z_][\w.<>,?\[\]]*\s+$/.test(before)) { addDeclarationModifiers(); return { type: 'event', modifiers }; }
615 if (/\bconst\s+[A-Za-z_][\w.<>,?\[\]]*\s+$/.test(before)) { addDeclarationModifiers(); modifiers.push('readonly'); return { type: 'constant', modifiers }; }
616 if (isDeclaration && /^\s*(?:\{|=>)/.test(after)) { addDeclarationModifiers(); return { type: 'property', modifiers }; }
617
618 if (/^\s*(?:<[^>\r\n]+>)?\s*\(/.test(after)) {
619 if (isDeclaration) addDeclarationModifiers();
620 return { type: 'method', modifiers };
621 }
622 if (/\.\s*$/.test(before)) return { type: 'property', modifiers };
623 if (isDeclaration) {
624 addDeclarationModifiers();
625 const lastOpenParenthesis = before.lastIndexOf('(');
626 const lastCloseParenthesis = before.lastIndexOf(')');
627 if (lastOpenParenthesis > lastCloseParenthesis) return { type: 'parameter', modifiers };
628 return { type: word.startsWith('_') ? 'field' : 'variable', modifiers };
629 }
630 if (/^[A-Z][A-Z0-9_]+$/.test(word)) return { type: 'constant', modifiers: ['readonly'] };
631 if (word.startsWith('_')) return { type: 'field', modifiers };
632 if (/^[A-Z]/.test(word)) return { type: 'type', modifiers };
633 return { type: 'variable', modifiers };
634 }
378 635
379 636 function registerCompletions() {
380 637 const K = monaco.languages.CompletionItemKind;
@@ -386,13 +643,18 @@
386 643 'int', 'long', 'double', 'decimal', 'bool', 'object', 'void', 'async', 'await', 'return',
387 644 'new', 'this', 'base', 'null', 'true', 'false', 'get', 'set', 'init', 'required', 'readonly',
388 645 'virtual', 'override', 'event', 'delegate', 'if', 'else', 'switch', 'case', 'for', 'foreach',
389 'while', 'try', 'catch', 'finally', 'throw'].map(keyword => word(keyword, K.Keyword)),
646 'while', 'try', 'catch', 'finally', 'throw'].map(keyword => word(keyword, K.Keyword, 'C# 关键字', csharpKeywordDocumentation(keyword))),
390 647 snippet('wpf-page', 'WPF UserControl 模板', 'public partial class ${1:ToolView} : UserControl\n{\n public ${1:ToolView}()\n {\n InitializeComponent();\n }\n}', K.Snippet),
391 648 snippet('observable-property', 'MVVM 可观察属性', '[ObservableProperty]\nprivate ${1:string} ${2:value} = ${3:string.Empty};', K.Snippet),
392 649 snippet('relay-command', 'MVVM 命令', '[RelayCommand]\nprivate void ${1:Execute}()\n{\n ${0}\n}', K.Snippet),
393 word('UserControl', K.Class), word('DependencyProperty', K.Class), word('ObservableObject', K.Class),
394 word('RelayCommand', K.Class), word('ICommand', K.Interface), word('InitializeComponent', K.Method),
395 word('DataContext', K.Property), word('Dispatcher', K.Property), word('async', K.Keyword), word('await', K.Keyword)
650 word('UserControl', K.Class, 'WPF 控件基类', '用于创建可复用 WPF 用户控件。'),
651 word('DependencyProperty', K.Class, 'WPF 依赖属性', '支持绑定、样式、动画和属性值继承。'),
652 word('ObservableObject', K.Class, 'MVVM 可观察对象', 'CommunityToolkit.Mvvm 提供的属性通知基类。'),
653 word('RelayCommand', K.Class, 'MVVM 命令', '将方法包装为可供界面绑定的命令。'),
654 word('ICommand', K.Interface, '命令接口', 'WPF 命令绑定使用的标准接口。'),
655 word('InitializeComponent', K.Method, '加载 XAML', '加载并连接当前页面或控件对应的 XAML。'),
656 word('DataContext', K.Property, '绑定数据源', '当前元素及其子元素默认使用的数据绑定源。'),
657 word('Dispatcher', K.Property, 'UI 调度器', '将操作调度到拥有当前对象的 UI 线程。')
396 658 ] })
397 659 });
398 660 monaco.languages.registerCompletionItemProvider('xml', {
@@ -403,14 +665,142 @@
403 665 snippet('TextBlock', '文本控件', '<TextBlock Text="${1:文本}" />', K.Snippet),
404 666 snippet('Button', '按钮控件', '<Button Content="${1:按钮}" Command="{Binding ${2:Command}}" />', K.Snippet),
405 667 snippet('Binding', '数据绑定', '{Binding ${1:Property}, Mode=${2:OneWay}}', K.Snippet),
406 word('Grid.Row', K.Property), word('Grid.Column', K.Property), word('HorizontalAlignment', K.Property),
407 word('VerticalAlignment', K.Property), word('Margin', K.Property), word('Padding', K.Property)
668 word('Grid.Row', K.Property, 'Grid 附加属性', '指定元素所在的网格行。'),
669 word('Grid.Column', K.Property, 'Grid 附加属性', '指定元素所在的网格列。'),
670 word('HorizontalAlignment', K.Property, '布局属性', '设置元素的水平对齐方式。'),
671 word('VerticalAlignment', K.Property, '布局属性', '设置元素的垂直对齐方式。'),
672 word('Margin', K.Property, '布局属性', '设置元素边界外侧的留白。'),
673 word('Padding', K.Property, '布局属性', '设置控件边界与内容之间的留白。')
408 674 ] })
409 675 });
410 676 }
411 677
412 function word(label, kind) { return { label, kind, insertText: label }; }
413 function snippet(label, detail, insertText, kind) { return { label, detail, kind, insertText, insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet }; }
678 function csharpKeywordDocumentation(keyword) {
679 return ({
680 async: '声明异步方法或匿名函数,通常与 await 配合使用。',
681 await: '异步等待任务完成,而不阻塞当前线程。',
682 record: '声明具有值相等性的数据类型。',
683 required: '要求对象初始化时为成员赋值。',
684 init: '限制属性只能在初始化阶段设置。',
685 partial: '把同一个类型或成员的实现拆分到多个源文件。',
686 using: '导入命名空间,或声明可自动释放的资源。',
687 namespace: '声明用于组织类型的命名空间。',
688 var: '由编译器推断局部变量的静态类型。',
689 override: '重写基类中可重写的成员。',
690 readonly: '限制字段只能在声明处或构造函数中赋值。',
691 sealed: '阻止类型被继承,或阻止成员继续被重写。'
692 })[keyword] || `插入 C# 关键字 \`${keyword}\`。`;
693 }
694
695 function registerLanguageHints() {
696 const csharpHints = {
697 async: '将方法标记为异步方法。通常与 `await` 配合使用,返回 `Task`、`Task<T>` 或 `ValueTask`。',
698 await: '异步等待任务完成,同时不会阻塞当前线程。只能用于异步方法或异步匿名函数中。',
699 var: '让编译器根据右侧表达式推断局部变量的静态类型,并不代表动态类型。',
700 record: '声明以值相等性为核心的数据类型,适合不可变数据模型和消息对象。',
701 required: '要求对象初始化时必须为该字段或属性赋值。',
702 init: '仅允许在对象初始化期间设置属性,之后保持只读语义。',
703 partial: '允许把同一个类型、方法或属性的定义拆分到多个源文件中。',
704 using: '导入命名空间,或声明会在作用域结束时自动释放的对象。',
705 namespace: '为类型组织逻辑作用域,避免名称冲突。',
706 UserControl: 'WPF 可复用用户控件基类,通常与对应的 XAML 文件共同组成界面。',
707 DependencyProperty: 'WPF 依赖属性,支持绑定、样式、动画、继承及默认值元数据。',
708 ObservableObject: 'CommunityToolkit.Mvvm 提供的可观察对象基类,可简化属性变更通知。',
709 RelayCommand: '把方法包装为可绑定命令,适合 MVVM 中的按钮和交互行为。',
710 DataContext: 'WPF 绑定默认使用的数据源对象,并可沿可视树或逻辑树继承。',
711 Dispatcher: '用于把操作调度回拥有当前 UI 对象的线程。',
712 InitializeComponent: '加载并连接编译后的 XAML 组件,应在页面或控件构造函数中调用。'
713 };
714 const xamlHints = {
715 Binding: '创建 WPF 数据绑定。可设置 `Path`、`Mode`、`Converter` 与 `UpdateSourceTrigger`。',
716 DataContext: '设置当前元素及其子元素默认使用的绑定数据源。',
717 'Grid.Row': '指定元素所在的 Grid 行,索引从 0 开始。',
718 'Grid.Column': '指定元素所在的 Grid 列,索引从 0 开始。',
719 HorizontalAlignment: '控制元素在可用水平空间中的对齐方式。',
720 VerticalAlignment: '控制元素在可用垂直空间中的对齐方式。',
721 Margin: '设置元素边界外侧的留白,顺序为左、上、右、下。',
722 Padding: '设置控件边界与其内容之间的内侧留白。'
723 };
724 registerHoverHints('csharp', csharpHints, 'C#');
725 registerHoverHints('xml', xamlHints, 'XAML');
726
727 const signatures = {
728 SetValue: { label: 'void SetValue(DependencyProperty dp, object value)', documentation: '为当前依赖对象设置依赖属性值。', parameters: ['要设置的依赖属性标识符。', '新的属性值。'] },
729 GetValue: { label: 'object GetValue(DependencyProperty dp)', documentation: '读取当前依赖对象上的依赖属性值。', parameters: ['要读取的依赖属性标识符。'] },
730 OnPropertyChanged: { label: 'void OnPropertyChanged(string? propertyName = null)', documentation: '通知绑定系统指定属性已经发生变化。', parameters: ['发生变化的属性名。'] },
731 WriteLine: { label: 'void Console.WriteLine(string? value)', documentation: '将文本及换行符写入标准输出。', parameters: ['要写出的文本或对象。'] },
732 Delay: { label: 'Task Task.Delay(int millisecondsDelay)', documentation: '创建一个在指定时间后完成的异步任务。', parameters: ['延迟的毫秒数。'] },
733 IsNullOrWhiteSpace: { label: 'bool string.IsNullOrWhiteSpace(string? value)', documentation: '判断字符串是否为 null、空字符串或仅包含空白字符。', parameters: ['要检查的字符串。'] },
734 Show: { label: 'MessageBoxResult MessageBox.Show(string messageBoxText, string caption)', documentation: '显示消息框。', parameters: ['消息正文。', '窗口标题。'] }
735 };
736 monaco.languages.registerSignatureHelpProvider('csharp', {
737 signatureHelpTriggerCharacters: ['(', ','],
738 signatureHelpRetriggerCharacters: [','],
739 provideSignatureHelp(model, position) {
740 const linePrefix = model.getLineContent(position.lineNumber).slice(0, position.column - 1);
741 const call = /([A-Za-z_]\w*)\s*\(([^()]*)$/.exec(linePrefix);
742 if (!call || !signatures[call[1]]) return null;
743 const definition = signatures[call[1]];
744 const activeParameter = Math.min((call[2].match(/,/g) || []).length, definition.parameters.length - 1);
745 return {
746 value: {
747 signatures: [{
748 label: definition.label,
749 documentation: definition.documentation,
750 parameters: definition.parameters.map((documentation, index) => ({ label: signatureParameterLabel(definition.label, index), documentation }))
751 }],
752 activeSignature: 0,
753 activeParameter: Math.max(0, activeParameter)
754 },
755 dispose() { }
756 };
757 }
758 });
759 }
760
761 function registerHoverHints(language, hints, label) {
762 monaco.languages.registerHoverProvider(language, {
763 provideHover(model, position) {
764 const range = model.getWordAtPosition(position);
765 if (!range) return null;
766 let name = range.word;
767 if (language === 'xml') {
768 const line = model.getLineContent(position.lineNumber);
769 const offset = position.column - 1;
770 const attributePattern = /[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)?/g;
771 let candidate;
772 while ((candidate = attributePattern.exec(line)) !== null) {
773 if (offset >= candidate.index && offset <= candidate.index + candidate[0].length && hints[candidate[0]]) {
774 name = candidate[0];
775 break;
776 }
777 }
778 }
779 const help = hints[name];
780 if (!help) return null;
781 return {
782 range: new monaco.Range(position.lineNumber, range.startColumn, position.lineNumber, range.endColumn),
783 contents: [{ value: `**${name}** · ${label}` }, { value: help }]
784 };
785 }
786 });
787 }
788
789 function signatureParameterLabel(signature, index) {
790 const open = signature.indexOf('(');
791 const close = signature.lastIndexOf(')');
792 if (open < 0 || close <= open) return '';
793 return (signature.slice(open + 1, close).split(',')[index] || '').trim();
794 }
795
796 function word(label, kind, detail = '代码建议', documentation = '') {
797 const item = { label, kind, insertText: label, detail };
798 if (documentation) item.documentation = documentation;
799 return item;
800 }
801 function snippet(label, detail, insertText, kind) {
802 return { label, detail, documentation: detail, kind, insertText, sortText: `0_${label}`, insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet };
803 }
414 804
415 805 function registerFormatters() {
416 806 monaco.languages.registerDocumentFormattingEditProvider('xml', { provideDocumentFormattingEdits(model) { return [{ range: model.getFullModelRange(), text: formatXml(model.getValue()) }]; } });
Added XFEToolBox/Resources/Image/default_tool_icon.png +0 -0
二进制文件已变更,无法进行逐行预览。
Modified XFEToolBox/Resources/Style/AdminManagementStyle.xaml +3 -0
@@ -88,8 +88,11 @@
88 88 </Style>
89 89
90 90 <Style x:Key="AdminCheckBox" TargetType="CheckBox" BasedOn="{StaticResource ToolBoxCheckBoxStyle}"/>
91 <Style x:Key="AdminGridCheckBox" TargetType="CheckBox" BasedOn="{StaticResource ToolBoxGridCheckBoxStyle}"/>
91 92
92 93 <Style x:Key="AdminDataGridColumnHeader" TargetType="DataGridColumnHeader" BasedOn="{StaticResource ToolBoxDataGridColumnHeaderStyle}"/>
94 <Style x:Key="AdminDataGridCenteredHeader" TargetType="DataGridColumnHeader" BasedOn="{StaticResource ToolBoxDataGridCenteredHeaderStyle}"/>
95 <Style x:Key="AdminDataGridRightHeader" TargetType="DataGridColumnHeader" BasedOn="{StaticResource ToolBoxDataGridRightHeaderStyle}"/>
93 96 <Style x:Key="AdminDataGridCell" TargetType="DataGridCell" BasedOn="{StaticResource ToolBoxDataGridCellStyle}"/>
94 97 <Style x:Key="AdminDataGridRow" TargetType="DataGridRow" BasedOn="{StaticResource ToolBoxDataGridRowStyle}"/>
95 98 <Style x:Key="AdminDataGrid" TargetType="DataGrid" BasedOn="{StaticResource ToolBoxDataGridStyle}">
Modified XFEToolBox/Resources/Style/StandardControlsStyle.xaml +65 -27
@@ -165,24 +165,32 @@
165 165
166 166 <!-- 选择控件。 -->
167 167 <Style x:Key="ToolBoxCheckBoxStyle" TargetType="CheckBox">
168 <Setter Property="Foreground" Value="{DynamicResource ToolTextPrimaryBrush}"/><Setter Property="FontSize" Value="10.5"/>
168 <Setter Property="MinHeight" Value="22"/><Setter Property="Foreground" Value="{DynamicResource ToolTextPrimaryBrush}"/><Setter Property="FontSize" Value="11"/>
169 169 <Setter Property="Cursor" Value="Hand"/><Setter Property="FocusVisualStyle" Value="{x:Null}"/><Setter Property="VerticalContentAlignment" Value="Center"/>
170 170 <Setter Property="Template"><Setter.Value><ControlTemplate TargetType="CheckBox">
171 <Grid><Grid.ColumnDefinitions><ColumnDefinition Width="20"/><ColumnDefinition Width="*"/></Grid.ColumnDefinitions>
172 <Border x:Name="Box" Width="16" Height="16" Background="{DynamicResource ToolControlBackgroundBrush}"
173 BorderBrush="{DynamicResource ToolControlBorderBrush}" BorderThickness="1" CornerRadius="5" VerticalAlignment="Center"/>
174 <Path x:Name="CheckMark" Width="9" Height="7" Margin="3.5,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center"
171 <Grid><Grid.ColumnDefinitions><ColumnDefinition Width="22"/><ColumnDefinition Width="*"/></Grid.ColumnDefinitions>
172 <Border x:Name="Box" Width="18" Height="18" Background="{DynamicResource ToolControlBackgroundBrush}"
173 BorderBrush="{DynamicResource ToolControlBorderBrush}" BorderThickness="1" CornerRadius="6" VerticalAlignment="Center"/>
174 <Path x:Name="CheckMark" Width="10" Height="8" Margin="4,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center"
175 175 Data="M 1,3.5 L 3.5,6 L 8,1" Stretch="Fill" Stroke="White" StrokeThickness="1.8"
176 176 StrokeStartLineCap="Round" StrokeEndLineCap="Round" Visibility="Collapsed"/>
177 <ContentPresenter Grid.Column="1" Margin="4,0,0,0" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
177 <Border x:Name="IndeterminateMark" Width="8" Height="2" Margin="5,0,0,0" HorizontalAlignment="Left"
178 VerticalAlignment="Center" Background="White" CornerRadius="1" Visibility="Collapsed"/>
179 <ContentPresenter Grid.Column="1" Margin="5,0,0,0" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
178 180 </Grid>
179 181 <ControlTemplate.Triggers>
180 <Trigger Property="IsMouseOver" Value="True"><Setter TargetName="Box" Property="BorderBrush" Value="{DynamicResource MainColor}"/></Trigger>
182 <Trigger Property="IsMouseOver" Value="True"><Setter TargetName="Box" Property="Background" Value="{DynamicResource ToolAccentSoftBrush}"/><Setter TargetName="Box" Property="BorderBrush" Value="{DynamicResource MainColor}"/></Trigger>
181 183 <Trigger Property="IsChecked" Value="True"><Setter TargetName="Box" Property="Background" Value="{DynamicResource MainColor}"/><Setter TargetName="Box" Property="BorderBrush" Value="{DynamicResource MainColor}"/><Setter TargetName="CheckMark" Property="Visibility" Value="Visible"/></Trigger>
184 <Trigger Property="IsChecked" Value="{x:Null}"><Setter TargetName="Box" Property="Background" Value="{DynamicResource MainColor}"/><Setter TargetName="Box" Property="BorderBrush" Value="{DynamicResource MainColor}"/><Setter TargetName="IndeterminateMark" Property="Visibility" Value="Visible"/></Trigger>
185 <Trigger Property="IsKeyboardFocused" Value="True"><Setter TargetName="Box" Property="BorderThickness" Value="2"/><Setter TargetName="Box" Property="BorderBrush" Value="{DynamicResource MainColor}"/></Trigger>
182 186 <Trigger Property="IsEnabled" Value="False"><Setter Property="Opacity" Value="0.5"/><Setter Property="Cursor" Value="Arrow"/></Trigger>
183 187 </ControlTemplate.Triggers>
184 188 </ControlTemplate></Setter.Value></Setter>
185 189 </Style>
190 <Style x:Key="ToolBoxGridCheckBoxStyle" TargetType="CheckBox" BasedOn="{StaticResource ToolBoxCheckBoxStyle}">
191 <Setter Property="Width" Value="22"/><Setter Property="Height" Value="22"/><Setter Property="MinHeight" Value="22"/>
192 <Setter Property="HorizontalAlignment" Value="Center"/><Setter Property="VerticalAlignment" Value="Center"/>
193 </Style>
186 194 <Style TargetType="CheckBox" BasedOn="{StaticResource ToolBoxCheckBoxStyle}"/>
187 195
188 196 <Style x:Key="ToolBoxRadioButtonStyle" TargetType="RadioButton">
@@ -270,16 +278,21 @@
270 278 <!-- 列表:默认轻量行,卡片列表作为可复用变体。资源管理器和控制台保留专用模板。 -->
271 279 <Style x:Key="ToolBoxListBoxStyle" TargetType="ListBox">
272 280 <Setter Property="Padding" Value="5"/><Setter Property="Background" Value="{DynamicResource ToolSurfaceBrush}"/>
281 <Setter Property="Foreground" Value="{DynamicResource ToolTextPrimaryBrush}"/><Setter Property="FontSize" Value="11"/>
273 282 <Setter Property="BorderBrush" Value="{DynamicResource ToolControlBorderBrush}"/><Setter Property="BorderThickness" Value="1"/>
274 283 <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
275 <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/><Setter Property="FocusVisualStyle" Value="{x:Null}"/>
284 <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/><Setter Property="ScrollViewer.CanContentScroll" Value="True"/>
285 <Setter Property="VirtualizingPanel.IsVirtualizing" Value="True"/><Setter Property="VirtualizingPanel.VirtualizationMode" Value="Recycling"/>
286 <Setter Property="VirtualizingPanel.ScrollUnit" Value="Pixel"/><Setter Property="HorizontalContentAlignment" Value="Stretch"/>
287 <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
276 288 <Setter Property="controls:ControlAssist.CornerRadius" Value="12"/>
277 289 </Style>
278 290 <Style TargetType="ListBox" BasedOn="{StaticResource ToolBoxListBoxStyle}"/>
279 291
280 292 <Style x:Key="ToolBoxListBoxItemStyle" TargetType="ListBoxItem">
281 293 <Setter Property="HorizontalContentAlignment" Value="Stretch"/><Setter Property="VerticalContentAlignment" Value="Center"/>
282 <Setter Property="Padding" Value="12,9"/><Setter Property="Margin" Value="1,1,1,5"/>
294 <Setter Property="MinHeight" Value="42"/><Setter Property="Padding" Value="13,8"/><Setter Property="Margin" Value="1,1,1,2"/>
295 <Setter Property="Foreground" Value="{DynamicResource ToolTextPrimaryBrush}"/><Setter Property="FontSize" Value="11"/>
283 296 <Setter Property="Background" Value="Transparent"/><Setter Property="BorderBrush" Value="Transparent"/>
284 297 <Setter Property="BorderThickness" Value="1"/><Setter Property="FocusVisualStyle" Value="{x:Null}"/><Setter Property="Cursor" Value="Hand"/>
285 298 <Setter Property="Template"><Setter.Value><ControlTemplate TargetType="ListBoxItem">
@@ -288,14 +301,15 @@
288 301 <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
289 302 </Border>
290 303 <ControlTemplate.Triggers>
291 <Trigger Property="IsMouseOver" Value="True"><Setter TargetName="Surface" Property="Background" Value="{DynamicResource ToolControlHoverBrush}"/><Setter TargetName="Surface" Property="BorderBrush" Value="{DynamicResource ToolDividerBrush}"/></Trigger>
292 <Trigger Property="IsSelected" Value="True"><Setter TargetName="Surface" Property="Background" Value="{DynamicResource ToolAccentSelectedBrush}"/><Setter TargetName="Surface" Property="BorderBrush" Value="{DynamicResource MainColor}"/></Trigger>
304 <Trigger Property="IsMouseOver" Value="True"><Setter TargetName="Surface" Property="Background" Value="{DynamicResource ToolControlHoverBrush}"/><Setter TargetName="Surface" Property="BorderBrush" Value="{DynamicResource ToolControlHoverBorderBrush}"/></Trigger>
305 <Trigger Property="IsSelected" Value="True"><Setter TargetName="Surface" Property="Background" Value="{DynamicResource ToolAccentSelectedBrush}"/><Setter TargetName="Surface" Property="BorderBrush" Value="{DynamicResource MainColor}"/><Setter Property="Foreground" Value="#5555A8"/></Trigger>
306 <Trigger Property="IsKeyboardFocusWithin" Value="True"><Setter TargetName="Surface" Property="BorderBrush" Value="{DynamicResource MainColor}"/></Trigger>
293 307 <Trigger Property="IsEnabled" Value="False"><Setter Property="Opacity" Value="0.5"/><Setter Property="Cursor" Value="Arrow"/></Trigger>
294 308 </ControlTemplate.Triggers>
295 309 </ControlTemplate></Setter.Value></Setter>
296 310 </Style>
297 311 <Style x:Key="ToolBoxCardListBoxItemStyle" TargetType="ListBoxItem" BasedOn="{StaticResource ToolBoxListBoxItemStyle}">
298 <Setter Property="Padding" Value="13,10"/><Setter Property="Margin" Value="0,0,0,7"/>
312 <Setter Property="MinHeight" Value="52"/><Setter Property="Padding" Value="14,10"/><Setter Property="Margin" Value="1,0,1,7"/>
299 313 <Setter Property="Background" Value="{DynamicResource ToolControlBackgroundBrush}"/>
300 314 <Setter Property="BorderBrush" Value="{DynamicResource ToolDividerBrush}"/>
301 315 </Style>
@@ -303,23 +317,29 @@
303 317
304 318 <!-- 数据表格。管理页仅保留语义别名,不再复制模板。 -->
305 319 <Style x:Key="ToolBoxDataGridColumnHeaderStyle" TargetType="DataGridColumnHeader">
306 <Setter Property="Height" Value="40"/><Setter Property="Padding" Value="14,0"/>
320 <Setter Property="Height" Value="44"/><Setter Property="Padding" Value="16,0"/>
307 321 <Setter Property="Background" Value="{DynamicResource ToolAccentSoftBrush}"/><Setter Property="Foreground" Value="{DynamicResource ToolTextSecondaryBrush}"/>
308 <Setter Property="FontSize" Value="10"/><Setter Property="FontWeight" Value="SemiBold"/>
322 <Setter Property="FontSize" Value="10.5"/><Setter Property="FontWeight" Value="SemiBold"/>
309 323 <Setter Property="BorderBrush" Value="{DynamicResource ToolControlBorderBrush}"/><Setter Property="BorderThickness" Value="0,0,0,1"/>
310 324 <Setter Property="HorizontalContentAlignment" Value="Left"/><Setter Property="VerticalContentAlignment" Value="Center"/>
311 325 </Style>
326 <Style x:Key="ToolBoxDataGridCenteredHeaderStyle" TargetType="DataGridColumnHeader" BasedOn="{StaticResource ToolBoxDataGridColumnHeaderStyle}">
327 <Setter Property="HorizontalContentAlignment" Value="Center"/><Setter Property="Padding" Value="8,0"/>
328 </Style>
329 <Style x:Key="ToolBoxDataGridRightHeaderStyle" TargetType="DataGridColumnHeader" BasedOn="{StaticResource ToolBoxDataGridColumnHeaderStyle}">
330 <Setter Property="HorizontalContentAlignment" Value="Right"/><Setter Property="Padding" Value="8,0,16,0"/>
331 </Style>
312 332 <Style x:Key="ToolBoxDataGridCellStyle" TargetType="DataGridCell">
313 <Setter Property="Padding" Value="14,0"/><Setter Property="Foreground" Value="{DynamicResource ToolTextPrimaryBrush}"/>
333 <Setter Property="Padding" Value="16,0"/><Setter Property="Foreground" Value="{DynamicResource ToolTextPrimaryBrush}"/>
314 334 <Setter Property="FontSize" Value="10.5"/><Setter Property="BorderBrush" Value="{DynamicResource ToolDividerBrush}"/>
315 <Setter Property="BorderThickness" Value="0,0,0,1"/><Setter Property="VerticalContentAlignment" Value="Center"/><Setter Property="FocusVisualStyle" Value="{x:Null}"/>
335 <Setter Property="BorderThickness" Value="0,0,0,1"/><Setter Property="HorizontalContentAlignment" Value="Stretch"/><Setter Property="VerticalContentAlignment" Value="Center"/><Setter Property="FocusVisualStyle" Value="{x:Null}"/>
316 336 <Style.Triggers><Trigger Property="IsSelected" Value="True"><Setter Property="Background" Value="Transparent"/><Setter Property="Foreground" Value="{DynamicResource ToolTextPrimaryBrush}"/></Trigger></Style.Triggers>
317 337 </Style>
318 338 <Style x:Key="ToolBoxDataGridRowStyle" TargetType="DataGridRow">
319 <Setter Property="Height" Value="41"/><Setter Property="Background" Value="Transparent"/><Setter Property="BorderThickness" Value="0"/>
339 <Setter Property="MinHeight" Value="46"/><Setter Property="Background" Value="Transparent"/><Setter Property="BorderThickness" Value="0"/>
320 340 <Setter Property="SnapsToDevicePixels" Value="True"/>
321 341 <Style.Triggers>
322 <Trigger Property="AlternationIndex" Value="1"><Setter Property="Background" Value="#F8F8FC"/></Trigger>
342 <Trigger Property="AlternationIndex" Value="1"><Setter Property="Background" Value="#FAFAFD"/></Trigger>
323 343 <Trigger Property="IsMouseOver" Value="True"><Setter Property="Background" Value="{DynamicResource ToolControlHoverBrush}"/></Trigger>
324 344 <Trigger Property="IsSelected" Value="True"><Setter Property="Background" Value="{DynamicResource ToolAccentSelectedBrush}"/></Trigger>
325 345 </Style.Triggers>
@@ -331,47 +351,65 @@
331 351 <Setter Property="AutoGenerateColumns" Value="False"/><Setter Property="CanUserAddRows" Value="False"/>
332 352 <Setter Property="CanUserDeleteRows" Value="False"/><Setter Property="CanUserResizeRows" Value="False"/>
333 353 <Setter Property="SelectionMode" Value="Single"/><Setter Property="SelectionUnit" Value="FullRow"/><Setter Property="AlternationCount" Value="2"/>
354 <Setter Property="CanUserReorderColumns" Value="False"/><Setter Property="CanUserSortColumns" Value="True"/>
355 <Setter Property="EnableRowVirtualization" Value="True"/><Setter Property="EnableColumnVirtualization" Value="True"/>
334 356 <Setter Property="ColumnHeaderStyle" Value="{StaticResource ToolBoxDataGridColumnHeaderStyle}"/>
335 357 <Setter Property="CellStyle" Value="{StaticResource ToolBoxDataGridCellStyle}"/>
336 358 <Setter Property="RowStyle" Value="{StaticResource ToolBoxDataGridRowStyle}"/>
337 359 <Setter Property="RowHeaderWidth" Value="0"/><Setter Property="HorizontalGridLinesBrush" Value="Transparent"/>
338 360 <Setter Property="VerticalGridLinesBrush" Value="Transparent"/><Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
361 <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
339 362 <Setter Property="controls:ControlAssist.CornerRadius" Value="12"/>
340 363 </Style>
364 <Style x:Key="ToolBoxDataGridPrimaryTextStyle" TargetType="TextBlock">
365 <Setter Property="Foreground" Value="{DynamicResource ToolTextPrimaryBrush}"/><Setter Property="FontSize" Value="11"/><Setter Property="FontWeight" Value="SemiBold"/>
366 <Setter Property="VerticalAlignment" Value="Center"/><Setter Property="TextTrimming" Value="CharacterEllipsis"/>
367 </Style>
368 <Style x:Key="ToolBoxDataGridSecondaryTextStyle" TargetType="TextBlock">
369 <Setter Property="Foreground" Value="{DynamicResource ToolTextSecondaryBrush}"/><Setter Property="FontSize" Value="10"/>
370 <Setter Property="VerticalAlignment" Value="Center"/><Setter Property="TextTrimming" Value="CharacterEllipsis"/>
371 </Style>
372 <Style x:Key="ToolBoxDataGridCenteredTextStyle" TargetType="TextBlock" BasedOn="{StaticResource ToolBoxDataGridSecondaryTextStyle}">
373 <Setter Property="HorizontalAlignment" Value="Center"/><Setter Property="TextAlignment" Value="Center"/>
374 </Style>
375 <Style x:Key="ToolBoxDataGridNumericTextStyle" TargetType="TextBlock" BasedOn="{StaticResource ToolBoxDataGridSecondaryTextStyle}">
376 <Setter Property="HorizontalAlignment" Value="Right"/><Setter Property="TextAlignment" Value="Right"/><Setter Property="FontFamily" Value="Cascadia Mono, Consolas"/>
377 </Style>
341 378 <Style TargetType="DataGrid" BasedOn="{StaticResource ToolBoxDataGridStyle}"/>
342 379
343 380 <!-- 横向和纵向共用的统一滚动条。 -->
344 381 <Style x:Key="ToolBoxScrollBarThumbStyle" TargetType="Thumb">
345 <Setter Property="MinHeight" Value="34"/><Setter Property="MinWidth" Value="34"/>
346 382 <Setter Property="Template"><Setter.Value><ControlTemplate TargetType="Thumb">
347 <Border x:Name="Surface" Margin="1" Background="{DynamicResource MainColor}" CornerRadius="4" Opacity="0.5"/>
383 <Border x:Name="Surface" Margin="2" Background="{DynamicResource MainColor}" CornerRadius="5" Opacity="0.48"/>
348 384 <ControlTemplate.Triggers>
349 <Trigger Property="IsMouseOver" Value="True"><Setter TargetName="Surface" Property="Opacity" Value="0.72"/></Trigger>
350 <Trigger Property="IsDragging" Value="True"><Setter TargetName="Surface" Property="Opacity" Value="0.92"/></Trigger>
385 <Trigger Property="IsMouseOver" Value="True"><Setter TargetName="Surface" Property="Opacity" Value="0.7"/></Trigger>
386 <Trigger Property="IsDragging" Value="True"><Setter TargetName="Surface" Property="Opacity" Value="0.9"/></Trigger>
351 387 </ControlTemplate.Triggers>
352 388 </ControlTemplate></Setter.Value></Setter>
353 389 </Style>
354 390 <Style x:Key="ToolBoxScrollBarStyle" TargetType="ScrollBar">
355 <Setter Property="Width" Value="9"/><Setter Property="MinWidth" Value="9"/><Setter Property="Height" Value="Auto"/><Setter Property="MinHeight" Value="0"/>
356 <Setter Property="Background" Value="Transparent"/><Setter Property="Margin" Value="1,2"/><Setter Property="IsTabStop" Value="False"/><Setter Property="FocusVisualStyle" Value="{x:Null}"/>
391 <Setter Property="Width" Value="12"/><Setter Property="MinWidth" Value="12"/><Setter Property="Height" Value="Auto"/><Setter Property="MinHeight" Value="0"/>
392 <Setter Property="Background" Value="#ECECFA"/><Setter Property="Margin" Value="1,3,1,3"/><Setter Property="IsTabStop" Value="False"/><Setter Property="FocusVisualStyle" Value="{x:Null}"/>
357 393 <Setter Property="Template"><Setter.Value><ControlTemplate TargetType="ScrollBar">
358 394 <Grid>
395 <Border x:Name="TrackSurface" Background="{TemplateBinding Background}" CornerRadius="6"/>
359 396 <Track x:Name="VerticalTrack" Orientation="Vertical" IsDirectionReversed="True" Focusable="False">
360 397 <Track.DecreaseRepeatButton><RepeatButton Command="{x:Static ScrollBar.PageUpCommand}" Focusable="False" Opacity="0"/></Track.DecreaseRepeatButton>
361 <Track.Thumb><Thumb Style="{StaticResource ToolBoxScrollBarThumbStyle}"/></Track.Thumb>
398 <Track.Thumb><Thumb MinHeight="34" Style="{StaticResource ToolBoxScrollBarThumbStyle}"/></Track.Thumb>
362 399 <Track.IncreaseRepeatButton><RepeatButton Command="{x:Static ScrollBar.PageDownCommand}" Focusable="False" Opacity="0"/></Track.IncreaseRepeatButton>
363 400 </Track>
364 401 <Track x:Name="HorizontalTrack" Orientation="Horizontal" IsDirectionReversed="False" Focusable="False" Visibility="Collapsed">
365 402 <Track.DecreaseRepeatButton><RepeatButton Command="{x:Static ScrollBar.PageLeftCommand}" Focusable="False" Opacity="0"/></Track.DecreaseRepeatButton>
366 <Track.Thumb><Thumb Style="{StaticResource ToolBoxScrollBarThumbStyle}"/></Track.Thumb>
403 <Track.Thumb><Thumb MinWidth="34" Style="{StaticResource ToolBoxScrollBarThumbStyle}"/></Track.Thumb>
367 404 <Track.IncreaseRepeatButton><RepeatButton Command="{x:Static ScrollBar.PageRightCommand}" Focusable="False" Opacity="0"/></Track.IncreaseRepeatButton>
368 405 </Track>
369 406 </Grid>
370 407 <ControlTemplate.Triggers>
371 408 <Trigger Property="Orientation" Value="Horizontal">
372 409 <Setter TargetName="VerticalTrack" Property="Visibility" Value="Collapsed"/><Setter TargetName="HorizontalTrack" Property="Visibility" Value="Visible"/>
373 <Setter Property="Width" Value="Auto"/><Setter Property="MinWidth" Value="0"/><Setter Property="Height" Value="9"/><Setter Property="MinHeight" Value="9"/><Setter Property="Margin" Value="2,1"/>
410 <Setter Property="Width" Value="Auto"/><Setter Property="MinWidth" Value="0"/><Setter Property="Height" Value="12"/><Setter Property="MinHeight" Value="12"/><Setter Property="Margin" Value="3,1,3,1"/>
374 411 </Trigger>
412 <Trigger Property="IsMouseOver" Value="True"><Setter TargetName="TrackSurface" Property="Background" Value="#E7E7F7"/></Trigger>
375 413 <Trigger Property="IsEnabled" Value="False"><Setter Property="Opacity" Value="0"/></Trigger>
376 414 </ControlTemplate.Triggers>
377 415 </ControlTemplate></Setter.Value></Setter>
Added XFEToolBox/Utilities/ByteSizeConverter.cs +36 -0
@@ -0,0 +1,36 @@
1 using System.Globalization;
2 using System.Windows.Data;
3
4 namespace XFEToolBox.Client.Utilities;
5
6 public sealed class ByteSizeConverter : IValueConverter
7 {
8 public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
9 {
10 if (!TryGetBytes(value, out var bytes)) return "--";
11 return bytes switch
12 {
13 >= 1024L * 1024 * 1024 => $"{bytes / (1024d * 1024 * 1024):F2} GB",
14 >= 1024L * 1024 => $"{bytes / (1024d * 1024):F2} MB",
15 >= 1024L => $"{bytes / 1024d:F2} KB",
16 _ => $"{bytes} B"
17 };
18 }
19
20 public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
21 throw new NotSupportedException();
22
23 private static bool TryGetBytes(object? value, out long bytes)
24 {
25 switch (value)
26 {
27 case byte byteValue: bytes = byteValue; return true;
28 case short shortValue: bytes = shortValue; return true;
29 case int intValue: bytes = intValue; return true;
30 case long longValue: bytes = longValue; return true;
31 case uint uintValue: bytes = uintValue; return true;
32 case ulong ulongValue when ulongValue <= long.MaxValue: bytes = (long)ulongValue; return true;
33 default: bytes = 0; return false;
34 }
35 }
36 }
Modified XFEToolBox/Utilities/ToolProjectRunService.cs +7 -2
@@ -154,7 +154,11 @@ internal static class ToolProjectRunService
154 154 var root = EscapeXml(Path.GetFullPath(workspaceRoot).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
155 155 var coreAssembly = EscapeXml(typeof(ToolPackageManifest).Assembly.Location);
156 156 var clientCoreAssembly = EscapeXml(typeof(AppPath).Assembly.Location);
157 var clientAssembly = EscapeXml(typeof(ToolProjectRunService).Assembly.Location);
157 var clientAssemblyPath = typeof(ToolProjectRunService).Assembly.Location;
158 var clientAssembly = EscapeXml(clientAssemblyPath);
159 var xfeExtensionAssembly = EscapeXml(Path.Combine(
160 Path.GetDirectoryName(clientAssemblyPath)!,
161 "XFEExtension.NetCore.dll"));
158 162 return $$"""
159 163 <Project Sdk="Microsoft.NET.Sdk">
160 164 <PropertyGroup>
@@ -181,6 +185,7 @@ internal static class ToolProjectRunService
181 185 <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
182 186 <Reference Include="XFEToolBox.Core"><HintPath>{{coreAssembly}}</HintPath><Private>true</Private></Reference>
183 187 <Reference Include="XFEToolBox.Client.Core"><HintPath>{{clientCoreAssembly}}</HintPath><Private>true</Private></Reference>
188 <Reference Include="XFEExtension.NetCore"><HintPath>{{xfeExtensionAssembly}}</HintPath><Private>true</Private></Reference>
184 189 <Reference Include="{{EscapeXml(hostAssemblyName)}}"><HintPath>{{clientAssembly}}</HintPath><Private>true</Private></Reference>
185 190 </ItemGroup>
186 191 </Project>
@@ -202,7 +207,7 @@ internal static class ToolProjectRunService
202 207 var mainStyleResourceUri = JsonSerializer.Serialize(
203 208 $"pack://application:,,,/{hostAssemblyName};component/Resources/Style/MainStyle.xaml");
204 209 var defaultIconResourceUri = JsonSerializer.Serialize(
205 $"pack://application:,,,/{hostAssemblyName};component/Resources/Image/wrench_tool.png");
210 $"pack://application:,,,/{hostAssemblyName};component/Resources/Image/default_tool_icon.png");
206 211 var width = JsonSerializer.Serialize(window.Width);
207 212 var height = JsonSerializer.Serialize(window.Height);
208 213 var minWidth = JsonSerializer.Serialize(window.MinWidth);
Modified XFEToolBox/Utilities/ToolProjectWorkspaceService.cs +29 -0
@@ -4,6 +4,7 @@ using System.Security;
4 4 using System.Text;
5 5 using System.Text.Json;
6 6 using System.Text.Json.Serialization;
7 using System.Windows;
7 8 using XFEToolBox.Core.Model;
8 9 using XFEToolBox.Core.Tools;
9 10
@@ -11,6 +12,8 @@ namespace XFEToolBox.Client.Utilities;
11 12
12 13 internal static class ToolProjectWorkspaceService
13 14 {
15 private const string DefaultToolIconRelativePath = "Assets/icon.png";
16 private const string DefaultToolIconResourcePath = "Resources/Image/default_tool_icon.png";
14 17 private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web) { WriteIndented = true };
15 18
16 19 public static string DefaultProjectsRoot => Path.Combine(AppPath.AppLocalData, "EditorWorkspaces");
@@ -32,9 +35,11 @@ internal static class ToolProjectWorkspaceService
32 35 var viewsDirectory = Path.Combine(root, "Code", "Views");
33 36 var modelsDirectory = Path.Combine(root, "Code", "Models");
34 37 var viewModelsDirectory = Path.Combine(root, "Code", "ViewModels");
38 var assetsDirectory = Path.Combine(root, "Assets");
35 39 Directory.CreateDirectory(viewsDirectory);
36 40 Directory.CreateDirectory(modelsDirectory);
37 41 Directory.CreateDirectory(viewModelsDirectory);
42 Directory.CreateDirectory(assetsDirectory);
38 43
39 44 var safeId = new string(projectName.ToLowerInvariant()
40 45 .Select(character => char.IsLetterOrDigit(character) ? character : '-')
@@ -58,6 +63,7 @@ internal static class ToolProjectWorkspaceService
58 63 "version": "1.0.0",
59 64 "description": "请在这里填写工具说明。",
60 65 "author": "XFEstudio",
66 "icon": "{{DefaultToolIconRelativePath}}",
61 67 "category": "开发工具",
62 68 "tags": [ "WPF" ],
63 69 "entry": {
@@ -138,6 +144,8 @@ internal static class ToolProjectWorkspaceService
138 144 await File.WriteAllTextAsync(output, content, new UTF8Encoding(false));
139 145 }
140 146
147 await WriteDefaultToolIconAsync(Path.Combine(root, DefaultToolIconRelativePath));
148
141 149 await RememberProjectAsync(root);
142 150 return root;
143 151 }
@@ -232,6 +240,27 @@ internal static class ToolProjectWorkspaceService
232 240 await File.WriteAllTextAsync(HistoryPath, JsonSerializer.Serialize(items, JsonOptions), new UTF8Encoding(false));
233 241 }
234 242
243 private static async Task WriteDefaultToolIconAsync(string outputPath)
244 {
245 var assemblyName = typeof(ToolProjectWorkspaceService).Assembly.GetName().Name
246 ?? throw new InvalidOperationException("无法确定客户端程序集名称。");
247 var resourceUri = new Uri(
248 $"pack://application:,,,/{assemblyName};component/{DefaultToolIconResourcePath}",
249 UriKind.Absolute);
250 var resource = Application.GetResourceStream(resourceUri)
251 ?? throw new InvalidOperationException("无法读取内置的默认工具图标。");
252
253 await using var source = resource.Stream;
254 await using var destination = new FileStream(
255 outputPath,
256 FileMode.CreateNew,
257 FileAccess.Write,
258 FileShare.None,
259 81920,
260 FileOptions.Asynchronous);
261 await source.CopyToAsync(destination);
262 }
263
235 264 private static string GetLayoutPath(string projectPath)
236 265 {
237 266 var normalized = Path.GetFullPath(projectPath).ToUpperInvariant();
Modified XFEToolBox/Views/Pages/Admin/SoftwareManagementPage.xaml +2 -11
@@ -3,15 +3,6 @@
3 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 4 xmlns:controls="clr-namespace:XFEToolBox.Client.Views.Controls"
5 5 Background="Transparent" Title="" Loaded="Page_Loaded">
6 <Page.Resources>
7 <Style x:Key="ChannelCheckBox" TargetType="CheckBox" BasedOn="{StaticResource AdminCheckBox}">
8 <Setter Property="Width" Value="20"/>
9 <Setter Property="Height" Value="20"/>
10 <Setter Property="HorizontalAlignment" Value="Center"/>
11 <Setter Property="VerticalAlignment" Value="Center"/>
12 <Setter Property="ToolTip" Value="启用此下载渠道"/>
13 </Style>
14 </Page.Resources>
15 6 <Grid Margin="14,9,14,12">
16 7 <Grid.RowDefinitions>
17 8 <RowDefinition Height="50"/>
@@ -216,11 +207,11 @@
216 207 <DataGridComboBoxColumn x:Name="ModeColumn" Header="方式"
217 208 SelectedItemBinding="{Binding Mode, UpdateSourceTrigger=PropertyChanged}" Width="82"/>
218 209 <DataGridTextColumn Header="地址 / 文件名" Binding="{Binding DisplayAddress, UpdateSourceTrigger=PropertyChanged}" Width="1.4*"/>
219 <DataGridTemplateColumn Header="启用" Width="58">
210 <DataGridTemplateColumn Header="启用" Width="64" HeaderStyle="{StaticResource AdminDataGridCenteredHeader}">
220 211 <DataGridTemplateColumn.CellTemplate>
221 212 <DataTemplate>
222 213 <CheckBox IsChecked="{Binding Enabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
223 Style="{StaticResource ChannelCheckBox}"/>
214 Style="{StaticResource AdminGridCheckBox}" ToolTip="启用此下载渠道"/>
224 215 </DataTemplate>
225 216 </DataGridTemplateColumn.CellTemplate>
226 217 </DataGridTemplateColumn>
Modified XFEToolBox/Views/Pages/Admin/ToolManagementPage.xaml +117 -14
@@ -2,7 +2,14 @@
2 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 4 xmlns:controls="clr-namespace:XFEToolBox.Client.Views.Controls"
5 xmlns:utilities="clr-namespace:XFEToolBox.Client.Utilities"
5 6 Background="Transparent" Title="" Loaded="Page_Loaded">
7 <Page.Resources>
8 <utilities:ByteSizeConverter x:Key="ByteSizeConverter"/>
9 <Style x:Key="ToolVersionRow" TargetType="DataGridRow" BasedOn="{StaticResource AdminDataGridRow}">
10 <Setter Property="MinHeight" Value="56"/>
11 </Style>
12 </Page.Resources>
6 13 <Grid Margin="14,9,14,12">
7 14 <Grid.RowDefinitions>
8 15 <RowDefinition Height="50"/>
@@ -57,18 +64,100 @@
57 64 </StackPanel>
58 65 </Border>
59 66 </Grid>
60 <controls:RoundedClipBorder Grid.Row="1" Background="White" BorderBrush="#E5E5F0" BorderThickness="1"
61 CornerRadius="12">
62 <DataGrid x:Name="ToolGrid" Style="{StaticResource AdminDataGrid}" IsReadOnly="True">
63 <DataGrid.Columns>
64 <DataGridTextColumn Header="工具" Binding="{Binding Manifest.Name}" Width="1.15*"/>
65 <DataGridTextColumn Header="标识" Binding="{Binding Manifest.Id}" Width="*"/>
66 <DataGridTextColumn Header="版本" Binding="{Binding Manifest.Version}" Width="90"/>
67 <DataGridCheckBoxColumn Header="已发布" Binding="{Binding Package.Published}" Width="80"/>
68 <DataGridTextColumn Header="大小(字节)" Binding="{Binding Package.PackageSize}" Width="105"/>
69 </DataGrid.Columns>
70 </DataGrid>
71 </controls:RoundedClipBorder>
67 <Grid Grid.Row="1">
68 <controls:RoundedClipBorder Background="White" BorderBrush="#E5E5F0" BorderThickness="1"
69 CornerRadius="12">
70 <DataGrid x:Name="ToolGrid" Style="{StaticResource AdminDataGrid}" RowStyle="{StaticResource ToolVersionRow}"
71 IsReadOnly="True" SelectionMode="Single" SelectionUnit="FullRow">
72 <DataGrid.Columns>
73 <DataGridTemplateColumn Header="工具" SortMemberPath="Manifest.Name" Width="1.25*">
74 <DataGridTemplateColumn.CellTemplate>
75 <DataTemplate>
76 <Grid ToolTip="{Binding Manifest.Description}">
77 <Grid.ColumnDefinitions><ColumnDefinition Width="34"/><ColumnDefinition Width="*"/></Grid.ColumnDefinitions>
78 <Border Width="28" Height="28" CornerRadius="9" Background="#EEEEFC" VerticalAlignment="Center">
79 <Path Width="13" Height="13" Stretch="Uniform" Stroke="#7777C9" StrokeThickness="1.5"
80 StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
81 Data="M 2,5 L 7,2 L 12,5 L 7,8 Z M 2,5 L 2,11 L 7,14 L 12,11 L 12,5 M 7,8 L 7,14"/>
82 </Border>
83 <StackPanel Grid.Column="1" VerticalAlignment="Center">
84 <TextBlock Text="{Binding Manifest.Name}" Style="{StaticResource ToolBoxDataGridPrimaryTextStyle}"/>
85 <TextBlock Text="{Binding Manifest.Author, StringFormat=发布者:{0}}" Style="{StaticResource ToolBoxDataGridSecondaryTextStyle}" Margin="0,3,0,0"/>
86 </StackPanel>
87 </Grid>
88 </DataTemplate>
89 </DataGridTemplateColumn.CellTemplate>
90 </DataGridTemplateColumn>
91 <DataGridTemplateColumn Header="工具标识" SortMemberPath="Manifest.Id" Width="1.05*">
92 <DataGridTemplateColumn.CellTemplate>
93 <DataTemplate>
94 <TextBlock Text="{Binding Manifest.Id}" Style="{StaticResource ToolBoxDataGridSecondaryTextStyle}"
95 FontFamily="Cascadia Mono, Consolas" ToolTip="{Binding Manifest.Id}"/>
96 </DataTemplate>
97 </DataGridTemplateColumn.CellTemplate>
98 </DataGridTemplateColumn>
99 <DataGridTemplateColumn Header="版本" SortMemberPath="Manifest.Version" Width="92"
100 HeaderStyle="{StaticResource AdminDataGridCenteredHeader}">
101 <DataGridTemplateColumn.CellTemplate>
102 <DataTemplate>
103 <Border Padding="9,4" Background="#F0F0FB" BorderBrush="#DDDDF1" BorderThickness="1"
104 CornerRadius="9" HorizontalAlignment="Center" VerticalAlignment="Center">
105 <TextBlock Text="{Binding Manifest.Version}" Foreground="#6666A8" FontSize="9.5" FontWeight="SemiBold"/>
106 </Border>
107 </DataTemplate>
108 </DataGridTemplateColumn.CellTemplate>
109 </DataGridTemplateColumn>
110 <DataGridTemplateColumn Header="发布状态" SortMemberPath="Package.Published" Width="112"
111 HeaderStyle="{StaticResource AdminDataGridCenteredHeader}">
112 <DataGridTemplateColumn.CellTemplate>
113 <DataTemplate>
114 <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
115 <CheckBox IsChecked="{Binding Package.Published, Mode=OneWay}" Style="{StaticResource AdminGridCheckBox}"
116 IsHitTestVisible="False" Focusable="False"/>
117 <TextBlock x:Name="PublicationText" Text="未发布" Margin="5,0,0,0" Foreground="#9999AA"
118 FontSize="9.5" VerticalAlignment="Center"/>
119 </StackPanel>
120 <DataTemplate.Triggers>
121 <DataTrigger Binding="{Binding Package.Published}" Value="True">
122 <Setter TargetName="PublicationText" Property="Text" Value="已发布"/>
123 <Setter TargetName="PublicationText" Property="Foreground" Value="#4B966D"/>
124 </DataTrigger>
125 </DataTemplate.Triggers>
126 </DataTemplate>
127 </DataGridTemplateColumn.CellTemplate>
128 </DataGridTemplateColumn>
129 <DataGridTemplateColumn Header="包大小" SortMemberPath="Package.PackageSize" Width="112"
130 HeaderStyle="{StaticResource AdminDataGridRightHeader}">
131 <DataGridTemplateColumn.CellTemplate>
132 <DataTemplate>
133 <TextBlock Text="{Binding Package.PackageSize, Converter={StaticResource ByteSizeConverter}}"
134 Style="{StaticResource ToolBoxDataGridNumericTextStyle}"/>
135 </DataTemplate>
136 </DataGridTemplateColumn.CellTemplate>
137 </DataGridTemplateColumn>
138 </DataGrid.Columns>
139 </DataGrid>
140 </controls:RoundedClipBorder>
141 <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" IsHitTestVisible="False">
142 <StackPanel.Style>
143 <Style TargetType="StackPanel">
144 <Setter Property="Visibility" Value="Collapsed"/>
145 <Style.Triggers>
146 <DataTrigger Binding="{Binding HasItems, ElementName=ToolGrid}" Value="False">
147 <Setter Property="Visibility" Value="Visible"/>
148 </DataTrigger>
149 </Style.Triggers>
150 </Style>
151 </StackPanel.Style>
152 <Border Width="48" Height="48" CornerRadius="15" Background="#F0EFFC" HorizontalAlignment="Center">
153 <TextBlock Text="◇" Foreground="#8585D3" FontSize="21" HorizontalAlignment="Center" VerticalAlignment="Center"/>
154 </Border>
155 <TextBlock Text="还没有工具版本" Foreground="#55556B" FontSize="11.5" FontWeight="SemiBold"
156 HorizontalAlignment="Center" Margin="0,10,0,0"/>
157 <TextBlock Text="可以从项目中直接发布,或上传 .xfetool 工具包" Foreground="#9999AA" FontSize="9"
158 HorizontalAlignment="Center" Margin="0,4,0,0"/>
159 </StackPanel>
160 </Grid>
72 161 </Grid>
73 162 </Border>
74 163
@@ -88,8 +177,22 @@
88 177 <TextBlock x:Name="StatusText" Foreground="#ECFFFFFF" FontSize="10.5"
89 178 VerticalAlignment="Center" TextWrapping="Wrap"/>
90 179 </StackPanel>
91 <Button Grid.Column="1" Content="切换选中版本的发布状态" controls:ButtonAssist.IsPrimary="True"
92 HorizontalAlignment="Right" MinWidth="178" Click="TogglePublicationButton_Click"/>
180 <Button Grid.Column="1" controls:ButtonAssist.IsPrimary="True"
181 HorizontalAlignment="Right" MinWidth="150" Click="TogglePublicationButton_Click">
182 <Button.Style>
183 <Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
184 <Setter Property="Content" Value="发布选中版本"/>
185 <Style.Triggers>
186 <DataTrigger Binding="{Binding SelectedItem, ElementName=ToolGrid}" Value="{x:Null}">
187 <Setter Property="IsEnabled" Value="False"/>
188 </DataTrigger>
189 <DataTrigger Binding="{Binding SelectedItem.Package.Published, ElementName=ToolGrid}" Value="True">
190 <Setter Property="Content" Value="下架选中版本"/>
191 </DataTrigger>
192 </Style.Triggers>
193 </Style>
194 </Button.Style>
195 </Button>
93 196 </Grid>
94 197 </Border>
95 198 </Grid>
Modified XFEToolBox/Views/Pages/Admin/UserManagementPage.xaml +75 -10
Modified XFEToolBox/Views/Pages/ToolBoxPage.xaml.cs +1 -1
@@ -195,7 +195,7 @@ public partial class ToolBoxPage : Page
195 195 {
196 196 var assemblyName = typeof(ToolBoxPage).Assembly.GetName().Name;
197 197 var image = new BitmapImage(new Uri(
198 $"pack://application:,,,/{assemblyName};component/Resources/Image/wrench_tool.png",
198 $"pack://application:,,,/{assemblyName};component/Resources/Image/default_tool_icon.png",
199 199 UriKind.Absolute));
200 200 image.Freeze();
201 201 return image;
Modified XFEToolBox/Views/Windows/ToolCodeEditorWindow.xaml +7 -3
@@ -433,7 +433,7 @@
433 433 <Style TargetType="Border">
434 434 <Setter Property="Visibility" Value="Collapsed"/>
435 435 <Style.Triggers>
436 <DataTrigger Binding="{Binding IsFolder}" Value="True">
436 <DataTrigger Binding="{Binding CanExpand}" Value="True">
437 437 <Setter Property="Visibility" Value="Visible"/>
438 438 </DataTrigger>
439 439 </Style.Triggers>
@@ -543,6 +543,9 @@
543 543 <Button x:Name="ClosePreviewDocumentTabButton" Content="×" Width="28" Height="28" MinWidth="28" Padding="0"
544 544 Margin="4,0,0,0" FontSize="13" ToolTip="关闭预览选项卡" Click="ClosePreviewDocumentTabButton_Click"/>
545 545 </StackPanel>
546 <Button x:Name="XamlRelatedFileButton" Content="转到代码" Height="30" MinWidth="0"
547 Margin="0,0,7,0" Padding="10,0" FontSize="9.5" Visibility="Collapsed"
548 Click="XamlRelatedFileButton_Click"/>
546 549 <Button x:Name="ManifestViewToggleButton" Content="编辑 JSON 源码" Height="30" MinWidth="0"
547 550 Margin="0,0,7,0" Padding="10,0" FontSize="9.5" Visibility="Collapsed" Click="ManifestViewToggleButton_Click"/>
548 551 <Button Content="在资源管理器中显示" Height="30" MinWidth="0" Margin="0"
@@ -556,7 +559,8 @@
556 559 <ColumnDefinition x:Name="PreviewSplitterColumn" Width="0"/>
557 560 <ColumnDefinition x:Name="PreviewColumn" Width="0" MinWidth="0"/>
558 561 </Grid.ColumnDefinitions>
559 <wv2:WebView2CompositionControl x:Name="EditorWebView" DefaultBackgroundColor="#FBFBFE"/>
562 <!-- Monaco uses the HWND-backed control so text is rendered at the monitor's native pixel density. -->
563 <wv2:WebView2 x:Name="EditorWebView" DefaultBackgroundColor="#FBFBFE"/>
560 564 <Border x:Name="EditorLoading" Background="#FBFBFE">
561 565 <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
562 566 <Border Width="58" Height="58" CornerRadius="18" Background="#EFEEFC" HorizontalAlignment="Center">
@@ -768,7 +772,7 @@
768 772 <ContentControl x:Name="PreviewContent" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"/>
769 773 </Border>
770 774 </ScrollViewer>
771 <wv2:WebView2CompositionControl x:Name="MarkdownPreviewWebView" DefaultBackgroundColor="#FFFFFF" Visibility="Collapsed"/>
775 <wv2:WebView2 x:Name="MarkdownPreviewWebView" DefaultBackgroundColor="#FFFFFF" Visibility="Collapsed"/>
772 776 <StackPanel x:Name="PreviewErrorPanel" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="24" Visibility="Collapsed">
773 777 <Border Width="48" Height="48" CornerRadius="15" Background="#FFF0F2" HorizontalAlignment="Center">
774 778 <TextBlock Text="!" Foreground="#C85E69" FontSize="20" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
Modified XFEToolBox/Views/Windows/ToolCodeEditorWindow.xaml.cs +123 -22
Modified XFEToolBox/XFEToolBox.Client.csproj +3 -1
Modified docs/popup-window.md +46 -3
Modified docs/software-catalog.md +79 -20
Modified docs/tool-packages.md +88 -39