返回提交历史
Added
CONTRIBUTING.md
+99
-0
Modified
README.md
+89
-1
Modified
XFEToolBox/Resources/Editor/editor.html
+429
-39
Added
XFEToolBox/Resources/Image/default_tool_icon.png
+0
-0
Modified
XFEToolBox/Resources/Style/AdminManagementStyle.xaml
+3
-0
Modified
XFEToolBox/Resources/Style/StandardControlsStyle.xaml
+65
-27
Added
XFEToolBox/Utilities/ByteSizeConverter.cs
+36
-0
Modified
XFEToolBox/Utilities/ToolProjectRunService.cs
+7
-2
Modified
XFEToolBox/Utilities/ToolProjectWorkspaceService.cs
+29
-0
Modified
XFEToolBox/Views/Pages/Admin/SoftwareManagementPage.xaml
+2
-11
Modified
XFEToolBox/Views/Pages/Admin/ToolManagementPage.xaml
+117
-14
Modified
XFEToolBox/Views/Pages/Admin/UserManagementPage.xaml
+75
-10
Modified
XFEToolBox/Views/Pages/ToolBoxPage.xaml.cs
+1
-1
Modified
XFEToolBox/Views/Windows/ToolCodeEditorWindow.xaml
+7
-3
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
XFEstudio/XFEToolBox
管理后台与编辑器 UI 优化,新增字节转换器
本次提交包含多项改进: - 管理后台 DataGrid 视觉样式升级,支持丰富列模板、状态徽章、图标和空数据提示,提升可读性与一致性。 - 优化 CheckBox、ListBox、DataGrid、滚动条等控件样式,调整尺寸、圆角、配色和交互反馈,改善整体 UI 体验。 - Code Studio 编辑器主题和语义高亮增强,支持更细致的 C# 语义高亮、悬浮提示、签名帮助和补全文档。 - 资源管理器支持 XAML/代码文件互跳,自动显示相关按钮,优化多文件项目导航。 - 新建工具项目自动生成默认图标文件,manifest.json 默认引用新路径,客户端和服务端均支持。 - 工具包与软件下载目录文档全面更新,详细说明多渠道下载、manifest 约束、接口字段和安全约定。 - 新增 CONTRIBUTING.md,规范贡献流程、分支管理、代码约定和安全报告方式。 - 其它细节优化:修复默认图标路径、优化弹窗文档、调整 WebView2 控件类型、完善 XAML 资源引用等。 - 新增 ByteSizeConverter.cs,实现字节数值到带单位字符串的转换器,支持多种整型输入类型,ConvertBack 不支持反向转换。
5382f2f
代码差异
19 个文件
+1298
-193
@@ -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 中披露利用细节。应通过仓库维护者提供的私密联系方式报告,并附上影响范围、复现条件和建议修复方向。
@@ -1,3 +1,91 @@
1
1
# XFEToolBox
2
2
3
## �����ز���дMD�ĵ��ˣ���͵����~
3

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) 许可。
@@ -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()) }]; } });
二进制文件已变更,无法进行逐行预览。
@@ -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}">
@@ -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>
@@ -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
}
@@ -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);
@@ -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();
@@ -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>
@@ -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>
@@ -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;
@@ -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"/>