mirror of
https://github.com/BeyondDimension/SteamTools.git
synced 2026-10-26 12:09:32 +08:00
Compare commits
277
Commits
3.0.0-rc.1
...
3.0.0-rc.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
deee8d056f | ||
|
|
3016469c7f | ||
|
|
526fb7a78d | ||
|
|
2790ffc5f1 | ||
|
|
e22897e2ae | ||
|
|
00b8c4dfcf | ||
|
|
9a2d2c6c94 | ||
|
|
29fb43fc38 | ||
|
|
4ca3066644 | ||
|
|
890f2fab37 | ||
|
|
f561f841e3 | ||
|
|
a016631d68 | ||
|
|
367c97b480 | ||
|
|
6367bc5ed1 | ||
|
|
2b69b98f3c | ||
|
|
a6010ba5f4 | ||
|
|
0c67cda96a | ||
|
|
c23823a5af | ||
|
|
f8424a2915 | ||
|
|
152fc74869 | ||
|
|
7c96f7af46 | ||
|
|
9b5bc82873 | ||
|
|
011c08266f | ||
|
|
bd43f16dd6 | ||
|
|
8bd7085d35 | ||
|
|
1bb5bef800 | ||
|
|
da73e4cb84 | ||
|
|
d11c1afc12 | ||
|
|
f21f069a2b | ||
|
|
242866fe88 | ||
|
|
46d87bc2d6 | ||
|
|
70ce3237cd | ||
|
|
e57e7db81b | ||
|
|
5bbddf86cd | ||
|
|
541399f011 | ||
|
|
287ff69011 | ||
|
|
b00fbba75f | ||
|
|
f9f0325610 | ||
|
|
6dae018cb7 | ||
|
|
a675d8ea2f | ||
|
|
586f5d6a6c | ||
|
|
e4058219d0 | ||
|
|
9adeb39b4f | ||
|
|
2f71afc818 | ||
|
|
aa271827b3 | ||
|
|
88ed73d720 | ||
|
|
cde6e0d724 | ||
|
|
df5e359d0d | ||
|
|
14e050a942 | ||
|
|
568483c454 | ||
|
|
926fd00fc7 | ||
|
|
5c64dd1168 | ||
|
|
5dd50aeeca | ||
|
|
c2bb27c537 | ||
|
|
9cf88df056 | ||
|
|
995aaee884 | ||
|
|
99e915adbe | ||
|
|
b8801d6b82 | ||
|
|
73639376aa | ||
|
|
5852423652 | ||
|
|
18012f775e | ||
|
|
9054d4d095 | ||
|
|
e19011a785 | ||
|
|
a63dfc0846 | ||
|
|
40539d99ed | ||
|
|
9ae1584506 | ||
|
|
38f35d36e0 | ||
|
|
b3c18e2042 | ||
|
|
5ea5fe1e83 | ||
|
|
9c71e89645 | ||
|
|
8a745839fe | ||
|
|
1a237a10bc | ||
|
|
655f943f5d | ||
|
|
77f8178557 | ||
|
|
c33dfc8e70 | ||
|
|
803b626e8a | ||
|
|
f9228b93a3 | ||
|
|
60ec614d46 | ||
|
|
237190384f | ||
|
|
7dd576a6c5 | ||
|
|
3fb0057883 | ||
|
|
aeccf68061 | ||
|
|
0806f61448 | ||
|
|
b67adfd67c | ||
|
|
97fb2ed673 | ||
|
|
0beadc8733 | ||
|
|
d0bc99d5a5 | ||
|
|
0852c57ed8 | ||
|
|
84653abace | ||
|
|
2a738a36a1 | ||
|
|
b18925a1a0 | ||
|
|
29fd1f0b19 | ||
|
|
d414267c9f | ||
|
|
af8739bb59 | ||
|
|
e140902de6 | ||
|
|
4a5e3447ff | ||
|
|
a97357099d | ||
|
|
7131deb860 | ||
|
|
268b803ce7 | ||
|
|
6a9af1d95f | ||
|
|
1bf13ffb4b | ||
|
|
1b90cd4bce | ||
|
|
62efead062 | ||
|
|
2754fb5dac | ||
|
|
8fff1d1828 | ||
|
|
9805ad4114 | ||
|
|
83ae3102dd | ||
|
|
c3911fd5bf | ||
|
|
ef27310e4f | ||
|
|
9ea1c83342 | ||
|
|
bbcee67fe0 | ||
|
|
4706561d42 | ||
|
|
c9d5fe37bb | ||
|
|
6bca078614 | ||
|
|
a4481011eb | ||
|
|
81d39b5436 | ||
|
|
4bff8602d1 | ||
|
|
4e434d25fe | ||
|
|
4e2cfc23ae | ||
|
|
e61c240db7 | ||
|
|
b1a0f178e5 | ||
|
|
5d7d00c623 | ||
|
|
c9b3191f2b | ||
|
|
e8ac8a52d6 | ||
|
|
34b4dc9ef7 | ||
|
|
47e16e3fc4 | ||
|
|
bbd9623311 | ||
|
|
30d5a7d14f | ||
|
|
f8d02c64b0 | ||
|
|
80c28caed9 | ||
|
|
4d91ce0690 | ||
|
|
d92968caf4 | ||
|
|
57459be38c | ||
|
|
276f150866 | ||
|
|
209634c73e | ||
|
|
d22a83228f | ||
|
|
8d1e8b2795 | ||
|
|
ef728120e4 | ||
|
|
df158c340e | ||
|
|
bf450815ae | ||
|
|
ee1187482c | ||
|
|
755fda1cd3 | ||
|
|
72fad6c877 | ||
|
|
c6873c255a | ||
|
|
ffa89cef34 | ||
|
|
db058c79f6 | ||
|
|
5be7f5984d | ||
|
|
2dbe0df4aa | ||
|
|
beef9611f7 | ||
|
|
b340c365ea | ||
|
|
924779d29f | ||
|
|
5bd73c86e8 | ||
|
|
95052a6af1 | ||
|
|
0aedef2bb6 | ||
|
|
3038f72e49 | ||
|
|
710ee5f350 | ||
|
|
74eb79b7f5 | ||
|
|
f626cbe96c | ||
|
|
bfbb1e36d4 | ||
|
|
dc2826af1c | ||
|
|
d3dcb913a8 | ||
|
|
5a6f18109d | ||
|
|
eb25310530 | ||
|
|
db5ff210f9 | ||
|
|
b1d7faee8a | ||
|
|
0ff633ec8f | ||
|
|
d0ae475c50 | ||
|
|
47cb686ed1 | ||
|
|
053bd2097b | ||
|
|
c945161805 | ||
|
|
4c848ef373 | ||
|
|
12d49ef32a | ||
|
|
3afb735fb9 | ||
|
|
f24a828cca | ||
|
|
d56312246e | ||
|
|
dc3dac0bdd | ||
|
|
16758c9295 | ||
|
|
cda93da954 | ||
|
|
93e35508f3 | ||
|
|
efc234e3fa | ||
|
|
c31848929f | ||
|
|
0eff585092 | ||
|
|
eaeb90d853 | ||
|
|
09dc6a5e81 | ||
|
|
8a56c52940 | ||
|
|
2f14d083ee | ||
|
|
07d5c3de27 | ||
|
|
9e4c4836c0 | ||
|
|
d34a5d3347 | ||
|
|
fd119ae0e1 | ||
|
|
3d59c24dec | ||
|
|
ea315a9b44 | ||
|
|
d5592b4bc2 | ||
|
|
2df32c67d0 | ||
|
|
fdaf795202 | ||
|
|
a7572ecd50 | ||
|
|
d433d60d29 | ||
|
|
3fe8f931a2 | ||
|
|
a69d97fcdb | ||
|
|
aa63e3ab9f | ||
|
|
a56fc36380 | ||
|
|
4c5428be5f | ||
|
|
e6953e50b8 | ||
|
|
a961f68242 | ||
|
|
72f301d526 | ||
|
|
2b18e03098 | ||
|
|
9fffe8458a | ||
|
|
ea7282f290 | ||
|
|
3714710dd8 | ||
|
|
00c228abd8 | ||
|
|
54fc1e3593 | ||
|
|
d2d04a37cf | ||
|
|
0600a5ca9f | ||
|
|
01dc528e21 | ||
|
|
9014198aeb | ||
|
|
bfd6b82a95 | ||
|
|
a6a45e5324 | ||
|
|
0ed0416f69 | ||
|
|
ce6fd9a4d6 | ||
|
|
7f06198bb6 | ||
|
|
8202df3a65 | ||
|
|
5033261a64 | ||
|
|
191c957a23 | ||
|
|
9eb5def6f0 | ||
|
|
2467ce9bb4 | ||
|
|
f968f3f66a | ||
|
|
511a2fa366 | ||
|
|
5ed54bd72e | ||
|
|
91ecfa342a | ||
|
|
be1481d063 | ||
|
|
fce80b18ee | ||
|
|
211daa428a | ||
|
|
781fd9489f | ||
|
|
680586b6e9 | ||
|
|
35ee526efc | ||
|
|
2f0090d571 | ||
|
|
2f6ad43382 | ||
|
|
20056583a3 | ||
|
|
77dc0abd2a | ||
|
|
ddf6486af6 | ||
|
|
0229003e19 | ||
|
|
8486edf6b8 | ||
|
|
fe941e1271 | ||
|
|
81129afc8f | ||
|
|
566cc4c017 | ||
|
|
0925ec9104 | ||
|
|
cac4a9370a | ||
|
|
e32fade0ea | ||
|
|
f6554a3b21 | ||
|
|
d00572d97d | ||
|
|
56e3563634 | ||
|
|
9355c317ac | ||
|
|
35b5edd09a | ||
|
|
ab22ea1113 | ||
|
|
62c015ad0b | ||
|
|
195f5269be | ||
|
|
b1fa5b4a4d | ||
|
|
7778854007 | ||
|
|
68bbd8b3e3 | ||
|
|
137a2dcde9 | ||
|
|
ef239c5dc2 | ||
|
|
1fd4d4e35a | ||
|
|
a71ff13d49 | ||
|
|
56f9be173a | ||
|
|
6e3e84c9c3 | ||
|
|
a55e28600f | ||
|
|
31dd805a6d | ||
|
|
aeac99fc1c | ||
|
|
5b9bc9e00b | ||
|
|
5c3ab25271 | ||
|
|
1e8d373df5 | ||
|
|
f226aeb8a9 | ||
|
|
3f0422cfbb | ||
|
|
af89dddc4c | ||
|
|
55f2c519b2 | ||
|
|
527f5fb8a4 | ||
|
|
8139a7414d |
+23
-52
@@ -89,22 +89,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TFM", "TFM", "{3B0CFC2A-F61
|
||||
src\TFM_NETX_WITH_WINDOWS.props = src\TFM_NETX_WITH_WINDOWS.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArchiSteamFarm.Library", "ref\ArchiSteamFarm\ArchiSteamFarm.Library\ArchiSteamFarm.Library.csproj", "{6981D5CA-837C-47F8-9568-E3BD451A6C68}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibAPNG", "ref\Avalonia.Image2\LibAPNG\LibAPNG.csproj", "{47B2D651-8EE0-4C05-B451-F2DA264DD5B3}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Gif", "ref\Avalonia.Image2\Avalonia.Gif\Avalonia.Gif.csproj", "{5D6D28A7-1CD8-4C30-A0E4-1F8ACF389A29}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ArchiSteamFarm", "ArchiSteamFarm", "{A3A703CC-F22E-40CE-9064-6DF3E55DD556}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Avalonia", "Avalonia", "{E5732454-7455-4078-88A2-E727DCC64D26}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReverseProxy", "ReverseProxy", "{4FA9B783-ADAE-46F6-9AAD-ED32F1839F13}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Steam", "Steam", "{99D97BD4-3F51-48BC-8BD2-647F6D268BB0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Titanium.Web.Proxy", "ref\Titanium-Web-Proxy\src\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj", "{826716DC-65C2-456A-BB6D-5FDEC423E4E6}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameloop.Vdf", "ref\Gameloop.Vdf\Gameloop.Vdf\Gameloop.Vdf.csproj", "{FE91B979-FE45-43F3-B9C4-430F2360FDDF}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steam4NET", "ref\Steam4NET\Steam4NET\Steam4NET.csproj", "{C8792BC7-33C0-41D4-97F6-684AD4B0B6A5}"
|
||||
@@ -224,6 +216,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steam++", "src\BD.WTTS.Clie
|
||||
EndProject
|
||||
Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "BD.WTTS.Client.AppHost.Bridge.Package", "src\BD.WTTS.Client.AppHost.Bridge.Package\BD.WTTS.Client.AppHost.Bridge.Package.wapproj", "{27795C79-2994-4514-8656-BA34FCEE8901}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArchiSteamFarm", "ref\ArchiSteamFarm\ArchiSteamFarm\ArchiSteamFarm.csproj", "{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -238,26 +232,6 @@ Global
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|x64.Build.0 = Release|Any CPU
|
||||
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|x86.Build.0 = Release|Any CPU
|
||||
{47B2D651-8EE0-4C05-B451-F2DA264DD5B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{47B2D651-8EE0-4C05-B451-F2DA264DD5B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{47B2D651-8EE0-4C05-B451-F2DA264DD5B3}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
@@ -298,26 +272,6 @@ Global
|
||||
{5D6D28A7-1CD8-4C30-A0E4-1F8ACF389A29}.Release|x64.Build.0 = Release|Any CPU
|
||||
{5D6D28A7-1CD8-4C30-A0E4-1F8ACF389A29}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{5D6D28A7-1CD8-4C30-A0E4-1F8ACF389A29}.Release|x86.Build.0 = Release|Any CPU
|
||||
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|x64.Build.0 = Release|Any CPU
|
||||
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|x86.Build.0 = Release|Any CPU
|
||||
{FE91B979-FE45-43F3-B9C4-430F2360FDDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FE91B979-FE45-43F3-B9C4-430F2360FDDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FE91B979-FE45-43F3-B9C4-430F2360FDDF}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
@@ -1308,6 +1262,26 @@ Global
|
||||
{27795C79-2994-4514-8656-BA34FCEE8901}.Release|x86.ActiveCfg = Release|x86
|
||||
{27795C79-2994-4514-8656-BA34FCEE8901}.Release|x86.Build.0 = Release|x86
|
||||
{27795C79-2994-4514-8656-BA34FCEE8901}.Release|x86.Deploy.0 = Release|x86
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|x64.Build.0 = Release|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -1317,14 +1291,10 @@ Global
|
||||
{202026AE-894E-4E18-9846-B0739CDBE3E9} = {46259E8D-32A4-4065-953D-D6F835B57A28}
|
||||
{F776BA87-0B9B-4C3C-B04F-688DCC021BF1} = {202026AE-894E-4E18-9846-B0739CDBE3E9}
|
||||
{3B0CFC2A-F61B-4000-AC12-6E0AFB5C475C} = {46259E8D-32A4-4065-953D-D6F835B57A28}
|
||||
{6981D5CA-837C-47F8-9568-E3BD451A6C68} = {A3A703CC-F22E-40CE-9064-6DF3E55DD556}
|
||||
{47B2D651-8EE0-4C05-B451-F2DA264DD5B3} = {E5732454-7455-4078-88A2-E727DCC64D26}
|
||||
{5D6D28A7-1CD8-4C30-A0E4-1F8ACF389A29} = {E5732454-7455-4078-88A2-E727DCC64D26}
|
||||
{A3A703CC-F22E-40CE-9064-6DF3E55DD556} = {90E88965-DB90-4B49-94F5-6D2965491988}
|
||||
{E5732454-7455-4078-88A2-E727DCC64D26} = {90E88965-DB90-4B49-94F5-6D2965491988}
|
||||
{4FA9B783-ADAE-46F6-9AAD-ED32F1839F13} = {90E88965-DB90-4B49-94F5-6D2965491988}
|
||||
{99D97BD4-3F51-48BC-8BD2-647F6D268BB0} = {90E88965-DB90-4B49-94F5-6D2965491988}
|
||||
{826716DC-65C2-456A-BB6D-5FDEC423E4E6} = {4FA9B783-ADAE-46F6-9AAD-ED32F1839F13}
|
||||
{FE91B979-FE45-43F3-B9C4-430F2360FDDF} = {99D97BD4-3F51-48BC-8BD2-647F6D268BB0}
|
||||
{C8792BC7-33C0-41D4-97F6-684AD4B0B6A5} = {99D97BD4-3F51-48BC-8BD2-647F6D268BB0}
|
||||
{123F532B-4178-4D03-A4EE-2A8F3E01433F} = {99D97BD4-3F51-48BC-8BD2-647F6D268BB0}
|
||||
@@ -1378,6 +1348,7 @@ Global
|
||||
{35676E23-8E54-4785-A75B-98EB0650596B} = {99D97BD4-3F51-48BC-8BD2-647F6D268BB0}
|
||||
{26D8AA24-2ACE-4167-BC2B-117B9BB76AA4} = {5541B406-4A9A-4AC8-AA77-CE14E133DB80}
|
||||
{27795C79-2994-4514-8656-BA34FCEE8901} = {5541B406-4A9A-4AC8-AA77-CE14E133DB80}
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B} = {99D97BD4-3F51-48BC-8BD2-647F6D268BB0}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {567F588F-E95C-4286-8B30-DB3B2200221C}
|
||||
|
||||
@@ -40,14 +40,14 @@ else
|
||||
fi
|
||||
}
|
||||
certutil_Init() {
|
||||
certutil -d sql:$HOME/.pki/nssdb -L
|
||||
certutil -d $HOME/.pki/nssdb -L
|
||||
ret=$?
|
||||
if [ $ret -ne 0 ]; then
|
||||
echo "即将初始化 certutil sql:\$HOME/.pki/nssdb"
|
||||
mkdir -p sql:$HOME/.pki
|
||||
mkdir -p sql:$HOME/.pki/nssdb
|
||||
chmod 700 sql:$HOME/.pki/nssdb
|
||||
certutil -d sql:$HOME/.pki/nssdb -N --empty-password
|
||||
echo "即将初始化 certutil \$HOME/.pki/nssdb"
|
||||
mkdir -p $HOME/.pki
|
||||
mkdir -p $HOME/.pki/nssdb
|
||||
chmod 700 $HOME/.pki/nssdb
|
||||
certutil -d $HOME/.pki/nssdb -N --empty-password
|
||||
else
|
||||
echo "certutil nssdb 正常"
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
#添加桌面文件
|
||||
run_path=$(dirname "$0")
|
||||
base_path="$(dirname "$run_path")"
|
||||
exec_name="Steam++"
|
||||
rm -rf "$HOME/Desktop/Watt Toolkit.desktop" 2>/dev/null
|
||||
echo "#!/usr/bin/env xdg-open
|
||||
[Desktop Entry]
|
||||
Name=Watt Toolkit
|
||||
Exec=$base_path/$exec_name.sh
|
||||
Icon=$base_path/Icons/Watt-Toolkit.png
|
||||
Terminal=false
|
||||
Type=Application
|
||||
StartupNotify=false" >"$HOME/Desktop/Watt Toolkit.desktop"
|
||||
chmod +x "$HOME/Desktop/Watt Toolkit.desktop"
|
||||
exit 0
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env xdg-open
|
||||
[Desktop Entry]
|
||||
Name=°²×° Watt Toolkit
|
||||
Exec=curl https://steampp.net/pkg/latest/steamdeck/install.sh | bash -s --
|
||||
Icon=steamdeck-gaming-return
|
||||
Terminal=true
|
||||
Type=Application
|
||||
StartupNotify=false
|
||||
@@ -0,0 +1,189 @@
|
||||
#!/bin/bash
|
||||
run_path=$(dirname "$0")
|
||||
base_path="$(dirname "$run_path")"
|
||||
mkdir -p "$base_path"
|
||||
cd "$base_path" || exit 1
|
||||
appVer_path="$base_path/WattToolkit.AppVer"
|
||||
exec_name="Steam++"
|
||||
tar_name="WattToolkit.tgz"
|
||||
tar_path="$base_path/$tar_name"
|
||||
app_name="Watt Toolkit"
|
||||
PROCESS_NAMES=("$exec_name" "$app_name")
|
||||
export LC_ALL=en_US.UTF-8
|
||||
Install_certutil() {
|
||||
# 判断发行版类型
|
||||
if command -v certutil &>/dev/null; then
|
||||
echo "certutil 工具已安装。"
|
||||
else
|
||||
echo "证书导入以及验证需要使用 certutil 工具。"
|
||||
# 判断包管理器
|
||||
if command -v apt &>/dev/null; then
|
||||
# 使用 apt (Debian/Ubuntu)
|
||||
sudo apt update
|
||||
sudo apt install -y libnss3-tools
|
||||
elif command -v dnf &>/dev/null; then
|
||||
# 使用 dnf (Fedora)
|
||||
sudo dnf install -y nss-tools
|
||||
elif command -v yum &>/dev/null; then
|
||||
# 使用 yum (CentOS/Red Hat)
|
||||
sudo yum install -y nss-tools
|
||||
elif command -v pacman &>/dev/null; then
|
||||
# 使用 pacman (Arch Linux)
|
||||
# sudo pacman -S nss
|
||||
echo "请手动安装 certutil 工具。"
|
||||
exit 1
|
||||
else
|
||||
echo "请手动安装 certutil 工具。"
|
||||
exit 1
|
||||
fi
|
||||
echo "certutil 工具已安装。"
|
||||
fi
|
||||
}
|
||||
certutil_Init() {
|
||||
certutil -d $HOME/.pki/nssdb -L
|
||||
ret=$?
|
||||
if [ $ret -ne 0 ]; then
|
||||
echo "即将初始化 certutil \$HOME/.pki/nssdb"
|
||||
mkdir -p $HOME/.pki
|
||||
mkdir -p $HOME/.pki/nssdb
|
||||
chmod 700 $HOME/.pki/nssdb
|
||||
certutil -d $HOME/.pki/nssdb -N --empty-password
|
||||
else
|
||||
echo "certutil nssdb 正常"
|
||||
fi
|
||||
}
|
||||
Install_jq() {
|
||||
# Check if jq is already installed
|
||||
if command -v jq &>/dev/null; then
|
||||
echo "jq 工具已安装。"
|
||||
else
|
||||
echo "jq 用来解析版本更新。"
|
||||
# Check the package manager
|
||||
if command -v apt &>/dev/null; then
|
||||
# Using apt (Debian/Ubuntu)
|
||||
sudo apt update
|
||||
sudo apt install -y jq
|
||||
elif command -v dnf &>/dev/null; then
|
||||
# Using dnf (Fedora)
|
||||
sudo dnf install -y jq
|
||||
elif command -v yum &>/dev/null; then
|
||||
# Using yum (CentOS/Red Hat)
|
||||
sudo yum install -y jq
|
||||
elif command -v pacman &>/dev/null; then
|
||||
# Using pacman (Arch Linux)
|
||||
sudo pacman -S jq
|
||||
else
|
||||
echo "请手动安装 jq 工具。"
|
||||
exit 1
|
||||
fi
|
||||
echo "请手动安装 jq 工具。"
|
||||
fi
|
||||
}
|
||||
Install_zenity() {
|
||||
# 判断发行版类型
|
||||
if command -v zenity &>/dev/null; then
|
||||
echo "zenity 工具已安装。"
|
||||
else
|
||||
echo "安装过程需要 zenity 工具。"
|
||||
# 判断包管理器
|
||||
if command -v apt &>/dev/null; then
|
||||
# 使用 apt (Debian/Ubuntu)
|
||||
sudo apt update
|
||||
sudo apt install -y zenity
|
||||
elif command -v dnf &>/dev/null; then
|
||||
# 使用 dnf (Fedora)
|
||||
sudo dnf install -y zenity
|
||||
elif command -v yum &>/dev/null; then
|
||||
# 使用 yum (CentOS/Red Hat)
|
||||
sudo yum install -y zenity
|
||||
elif command -v pacman &>/dev/null; then
|
||||
# 使用 pacman (Arch Linux)
|
||||
# sudo pacman -S zenity
|
||||
echo "请手动安装 zenity 工具。"
|
||||
exit 1
|
||||
else
|
||||
echo "请手动安装 zenity 工具。"
|
||||
exit 1
|
||||
fi
|
||||
echo "zenity 工具已安装。"
|
||||
fi
|
||||
}
|
||||
|
||||
Show_Run() {
|
||||
local param1=$1
|
||||
# 显示提示框,询问是否运行程序
|
||||
zenity --question --text="$1" --width=400
|
||||
|
||||
# 获取上一个命令的退出码
|
||||
response=$?
|
||||
|
||||
if [ $response -eq 0 ]; then
|
||||
echo "程序已启动。"
|
||||
exit 0 &
|
||||
# 用户点击了 "运行" 按钮,启动程序
|
||||
/bin/sh -c "$base_path/$exec_name.sh"
|
||||
else
|
||||
# 用户点击了 "关闭" 按钮,退出脚本
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
Kill_Process() {
|
||||
# 尝试的次数
|
||||
Kill_MAX_RETRIES=3
|
||||
|
||||
# 循环尝试终止进程
|
||||
for process_name in "${PROCESS_NAMES[@]}"; do
|
||||
kill_retry=1
|
||||
while [ $kill_retry -le $Kill_MAX_RETRIES ]; do
|
||||
pid=$(pgrep "$process_name")
|
||||
if [ -n "$pid" ]; then
|
||||
echo "尝试 $kill_retry: 进程 $process_name 正在运行中。正在终止..."
|
||||
kill $pid
|
||||
sleep 2
|
||||
else
|
||||
break
|
||||
fi
|
||||
kill_retry=$((kill_retry + 1))
|
||||
done
|
||||
done
|
||||
|
||||
for process_name in "${PROCESS_NAMES[@]}"; do
|
||||
# 检查是否成功终止进程
|
||||
if pgrep -x "$process_name" >/dev/null; then
|
||||
echo "无法终止程序 $process_name。尝试次数已达上限。"
|
||||
exit 1
|
||||
else
|
||||
echo "程序 $process_name 已成功终止。"
|
||||
fi
|
||||
done
|
||||
|
||||
}
|
||||
Install_certutil
|
||||
Install_zenity
|
||||
Install_jq
|
||||
certutil_Init
|
||||
Kill_Process
|
||||
|
||||
dotnet_exec="$dotnet_path/dotnet/dotnet"
|
||||
if [ -x "$dotnet_exec" ]; then
|
||||
echo "文件具有执行权限。"
|
||||
else
|
||||
chmod +x "$dotnet_exec"
|
||||
fi
|
||||
chmod +x "$base_path/$exec_name.sh"
|
||||
# xdg-icon-resource install "$base_path/Icons/Watt-Toolkit.png" --size 128 Watt-Toolkit
|
||||
#添加桌面文件
|
||||
rm -rf "$HOME/Desktop/Watt Toolkit.desktop" 2>/dev/null
|
||||
echo "#!/usr/bin/env xdg-open
|
||||
[Desktop Entry]
|
||||
Name=Watt Toolkit
|
||||
Exec=$base_path/$exec_name.sh
|
||||
Icon=$base_path/Icons/Watt-Toolkit.png
|
||||
Terminal=false
|
||||
Type=Application
|
||||
StartupNotify=false" >"$HOME/Desktop/Watt Toolkit.desktop"
|
||||
chmod +x "$HOME/Desktop/Watt Toolkit.desktop"
|
||||
# update-desktop-database ~/.local/share/applications
|
||||
#运行程序
|
||||
Show_Run "下载安装完成,是否启动程序?"
|
||||
exit 0
|
||||
@@ -41,14 +41,14 @@ Install_certutil() {
|
||||
fi
|
||||
}
|
||||
certutil_Init() {
|
||||
certutil -d sql:$HOME/.pki/nssdb -L
|
||||
certutil -d $HOME/.pki/nssdb -L
|
||||
ret=$?
|
||||
if [ $ret -ne 0 ]; then
|
||||
echo "即将初始化 certutil sql:\$HOME/.pki/nssdb"
|
||||
mkdir -p sql:$HOME/.pki
|
||||
mkdir -p sql:$HOME/.pki/nssdb
|
||||
chmod 700 sql:$HOME/.pki/nssdb
|
||||
certutil -d sql:$HOME/.pki/nssdb -N --empty-password
|
||||
echo "即将初始化 certutil \$HOME/.pki/nssdb"
|
||||
mkdir -p $HOME/.pki
|
||||
mkdir -p $HOME/.pki/nssdb
|
||||
chmod 700 $HOME/.pki/nssdb
|
||||
certutil -d $HOME/.pki/nssdb -N --empty-password
|
||||
else
|
||||
echo "certutil nssdb 正常"
|
||||
fi
|
||||
+50
-11
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
base_path="$HOME/WattToolkit"
|
||||
export LC_ALL=en_US.UTF-8
|
||||
run_path=$(dirname "$0")
|
||||
base_path="$(dirname "$run_path")"
|
||||
exec_name="Steam++"
|
||||
app_name="Watt Toolkit"
|
||||
AppData="$XDG_DATA_HOME/Steam++"
|
||||
@@ -8,7 +8,37 @@ AppData_t="$HOME/.local/share/Steam++"
|
||||
Cache="$XDG_CACHE_HOME/Steam++"
|
||||
Cache_t="$HOME/.cache/Steam+"
|
||||
PROCESS_NAMES=("$exec_name" "$app_name")
|
||||
export LC_ALL=en_US.UTF-8
|
||||
#检查是否存在 Steam++.sh 避免卸载错误
|
||||
file_to_check="$base_path/Steam++.sh"
|
||||
folders_to_check=("dotnet" "modules" "native" "script" "assemblies")
|
||||
if [ -e "$file_to_check" ]; then
|
||||
echo "文件 $file_to_check 存在"
|
||||
else
|
||||
echo "文件 $file_to_check 不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for folder in "${folders_to_check[@]}"; do
|
||||
folder_path="$base_path/$folder"
|
||||
if [ -d "$folder_path" ]; then
|
||||
echo "文件夹 $folder_path 存在"
|
||||
else
|
||||
echo "文件夹 $folder_path 不存在"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo "验证该文件夹是 $app_name 安装目录,请确认是否卸载。"
|
||||
|
||||
read -p "所有文件和文件夹都存在,确认卸载吗? (输入 'y' 确认): " confirmation
|
||||
|
||||
if [ "${confirmation,,}" == "y" ]; then
|
||||
echo "开始执行卸载操作"
|
||||
# 在这里添加您的卸载操作
|
||||
else
|
||||
echo "用户取消卸载操作"
|
||||
exit 0
|
||||
fi
|
||||
Kill_Process() {
|
||||
# 尝试的次数
|
||||
Kill_MAX_RETRIES=3
|
||||
@@ -42,19 +72,28 @@ Kill_Process() {
|
||||
}
|
||||
Kill_Process
|
||||
|
||||
zenity --question --text="是否删除用户数据" --width=400
|
||||
zenity --question --text="是否删除证书?" --width=400
|
||||
# 获取上一个命令的退出码
|
||||
response=$?
|
||||
delCer=$?
|
||||
|
||||
if [ $response -eq 0 ]; then
|
||||
rm -rfq "$AppData" 2>/dev/null
|
||||
rm -rfq "$AppData_t" 2>/dev/null
|
||||
if [ $delCer -eq 0 ]; then
|
||||
certutil -D -d $HOME/.pki/nssdb -n "SteamTools"
|
||||
fi
|
||||
|
||||
zenity --question --text="是否删除用户数据?" --width=400
|
||||
# 获取上一个命令的退出码
|
||||
delUserData=$?
|
||||
|
||||
if [ $delUserData -eq 0 ]; then
|
||||
rm -rf $AppData 2>/dev/null
|
||||
rm -rf $AppData_t 2>/dev/null
|
||||
certutil -D -d $HOME/.pki/nssdb -n "SteamTools"
|
||||
else
|
||||
echo "保留用户数据方便下次安装。"
|
||||
echo "如需删除可手动删除该目录:$AppData 或者 $AppData_t"
|
||||
fi
|
||||
rm -rfq "$Cache" 2>/dev/null
|
||||
rm -rfq "$Cache_t" 2>/dev/null
|
||||
rm -rfq "$base_path" 2>/dev/null
|
||||
rm -rfq "$HOME/Desktop/Watt Toolkit.desktop" 2>/dev/null
|
||||
rm -rf $Cache 2>/dev/null
|
||||
rm -rf $Cache_t 2>/dev/null
|
||||
rm -rf $base_path 2>/dev/null
|
||||
rm -rf "$HOME/Desktop/Watt Toolkit.desktop" 2>/dev/null
|
||||
zenity --info --text="卸载完成!" --width=300
|
||||
|
||||
+34
-19
@@ -10,11 +10,12 @@ Base_Path="/Users/moxiansheng/Documents/GitHub/SteamTools"
|
||||
App_Path="$Base_Path/src/BD.WTTS.Client.Avalonia.App"
|
||||
Publish_Path="bin/Release/Publish/assemblies"
|
||||
Plugins_Release_Path="bin/Release"
|
||||
CodesignKey_Name="mossimo CodesignKey"
|
||||
CodesignKey_Name="mossimo code"
|
||||
App_Modules_Path="$App_Path/$Publish_Path/modules"
|
||||
echo "编译主程序"
|
||||
cd "$App_Path"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos -p:CreatePackage=false -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net8.0-macos -p:CreatePackage=false -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
|
||||
rm -r "$App_Modules_Path"
|
||||
mkdir -p "$App_Modules_Path"
|
||||
echo "编译插件"
|
||||
@@ -29,53 +30,67 @@ Copy_Plugins() {
|
||||
echo "编译加速插件"
|
||||
Plugins_Accelerator_Path="$Base_Path/src/BD.WTTS.Client.Plugins.Accelerator"
|
||||
cd "$Plugins_Accelerator_Path"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos
|
||||
Copy_Plugins "$Plugins_Release_Path/net7.0-macos" "Accelerator" "BD.WTTS.Client.Plugins.Accelerator.dll"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net8.0-macos
|
||||
Copy_Plugins "$Plugins_Release_Path/net8.0-macos" "Accelerator" "BD.WTTS.Client.Plugins.Accelerator.dll"
|
||||
|
||||
Plugins_Accelerator_ReverseProxy_Path="$Base_Path/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy"
|
||||
cd "$Plugins_Accelerator_ReverseProxy_Path"
|
||||
# 根据 Build_Mode 执行不同逻辑
|
||||
if [ "$Build_Mode" = "any" ]; then
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path/arm64" -f net7.0 -r osx-arm64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path/x64" -f net7.0 -r osx-x64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path/arm64" -f net8.0 -r osx-arm64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path/x64" -f net8.0 -r osx-x64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
lipo -create "$Publish_Path/arm64/Steam++.Accelerator" "$Publish_Path/x64/Steam++.Accelerator" -output "$Publish_Path/Steam++.Accelerator"
|
||||
Copy_Plugins "$Publish_Path" "Accelerator" "Steam++.Accelerator"
|
||||
elif [ "$Build_Mode" = "arm64" ]; then
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path/arm64" -f net7.0 -r osx-arm64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path/arm64" -f net8.0 -r osx-arm64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
Copy_Plugins "$Publish_Path/arm64" "Accelerator" "Steam++.Accelerator"
|
||||
elif [ "$Build_Mode" = "x64" ]; then
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path/x64" -f net7.0 -r osx-x64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path/x64" -f net8.0 -r osx-x64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
Copy_Plugins "$Publish_Path/x64" "Accelerator" "Steam++.Accelerator"
|
||||
fi
|
||||
|
||||
echo "编译令牌插件"
|
||||
Plugins_Authenticator_Path="$Base_Path/src/BD.WTTS.Client.Plugins.Authenticator"
|
||||
cd "$Plugins_Authenticator_Path"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
Copy_Plugins "$Plugins_Release_Path/net7.0-macos" "Authenticator" "BD.WTTS.Client.Plugins.Authenticator.dll"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net8.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
Copy_Plugins "$Plugins_Release_Path/net8.0-macos" "Authenticator" "BD.WTTS.Client.Plugins.Authenticator.dll"
|
||||
|
||||
echo "编译账户插件"
|
||||
Plugins_GameAccount_Path="$Base_Path/src/BD.WTTS.Client.Plugins.GameAccount"
|
||||
cd "$Plugins_GameAccount_Path"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
Copy_Plugins "$Plugins_Release_Path/net7.0-macos" "GameAccount" "BD.WTTS.Client.Plugins.GameAccount.dll"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net8.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
Copy_Plugins "$Plugins_Release_Path/net8.0-macos" "GameAccount" "BD.WTTS.Client.Plugins.GameAccount.dll"
|
||||
|
||||
echo "编译游戏库插件"
|
||||
Plugins_GameList_Path="$Base_Path/src/BD.WTTS.Client.Plugins.GameList"
|
||||
cd "$Plugins_GameList_Path"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
Copy_Plugins "$Plugins_Release_Path/net7.0-macos" "GameList" "BD.WTTS.Client.Plugins.GameList.dll"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net8.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
Copy_Plugins "$Plugins_Release_Path/net8.0-macos" "GameList" "BD.WTTS.Client.Plugins.GameList.dll"
|
||||
|
||||
echo "复制插件到程序目录"
|
||||
cd "$App_Path"
|
||||
|
||||
# 根据 Build_Mode 执行不同逻辑
|
||||
if [ "$Build_Mode" = "any" ]; then
|
||||
cp -r "$App_Modules_Path" "$Plugins_Release_Path/net7.0-macos/Steam++.app/Contents/MonoBundle"
|
||||
cp -r "$App_Modules_Path" "$Plugins_Release_Path/net8.0-macos/Steam++.app/Contents/MonoBundle"
|
||||
elif [ "$Build_Mode" = "arm64" ]; then
|
||||
cp -r "$App_Modules_Path" "$Plugins_Release_Path/net7.0-macos/osx-arm64/Steam++.app/Contents/MonoBundle"
|
||||
cp -r "$Plugins_Release_Path/net7.0-macos/Steam++.app/Contents/Resources" "$Plugins_Release_Path/net7.0-macos/Steam++.app/Contents"
|
||||
cp -r "$App_Modules_Path" "$Plugins_Release_Path/net8.0-macos/osx-arm64/Steam++.app/Contents/MonoBundle"
|
||||
cp -r "$Plugins_Release_Path/net8.0-macos/Steam++.app/Contents/Resources" "$Plugins_Release_Path/net8.0-macos/Steam++.app/Contents"
|
||||
elif [ "$Build_Mode" = "x64" ]; then
|
||||
cp -r "$App_Modules_Path" "$Plugins_Release_Path/net7.0-macos/osx-x64/Steam++.app/Contents/MonoBundle"
|
||||
cp -r "$Plugins_Release_Path/net7.0-macos/Steam++.app/Contents/Resources" "$Plugins_Release_Path/net7.0-macos/Steam++.app/Contents"
|
||||
cp -r "$App_Modules_Path" "$Plugins_Release_Path/net8.0-macos/osx-x64/Steam++.app/Contents/MonoBundle"
|
||||
cp -r "$Plugins_Release_Path/net8.0-macos/Steam++.app/Contents/Resources" "$Plugins_Release_Path/net8.0-macos/Steam++.app/Contents"
|
||||
fi
|
||||
|
||||
echo "代码签名重新手动签名"
|
||||
App_Publish_Path="$App_Path/bin/Release/net8.0-macos/Steam++.app"
|
||||
# #代码签名重新手动签名
|
||||
codesign --remove-signature "$App_Publish_Path"
|
||||
codesign --remove-signature "$App_Publish_Path/Contents/MacOS/Steam++"
|
||||
dylibs=$(find "$App_Publish_Path/Contents/MonoBundle" -type f -name "*.dylib")
|
||||
for dylib in $dylibs; do
|
||||
codesign --remove-signature "$dylib"
|
||||
codesign -f -s "$CodesignKey_Name" "$dylib"
|
||||
done
|
||||
codesign -f -s "$CodesignKey_Name" "$App_Publish_Path/Contents/MacOS/Steam++"
|
||||
codesign -f -s "$CodesignKey_Name" "$App_Publish_Path"
|
||||
sudo xattr -rd com.apple.quarantine "$App_Publish_Path"
|
||||
|
||||
+222
-1
@@ -498,7 +498,7 @@
|
||||
{
|
||||
"TypeName": "bool",
|
||||
"PropertyName": "GameInstalledFilter",
|
||||
"DefaultValue": "true",
|
||||
"DefaultValue": "false",
|
||||
"DefaultValueIsConst": true,
|
||||
"Summary": "已安装游戏筛选",
|
||||
"IsRegionOrEndregion": null,
|
||||
@@ -943,6 +943,227 @@
|
||||
},
|
||||
{
|
||||
"Sharp": "endif"
|
||||
},
|
||||
{
|
||||
"TypeName": "bool",
|
||||
"PropertyName": "UseDoh",
|
||||
"DefaultValue": "true",
|
||||
"DefaultValueIsConst": true,
|
||||
"Summary": "启用 DNS over HTTPS",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": null
|
||||
},
|
||||
{
|
||||
"TypeName": "string",
|
||||
"PropertyName": "CustomDohAddres",
|
||||
"DefaultValue": "null",
|
||||
"DefaultValueIsConst": true,
|
||||
"Summary": "自定义 DNS over HTTPS 地址",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": null
|
||||
}
|
||||
],
|
||||
"Usings": ""
|
||||
},
|
||||
"SteamIdleSettings": {
|
||||
"ClassFilePath": "..\\src\\BD.WTTS.Client.Plugins.SteamIdleCard\\Settings",
|
||||
"InterfaceFilePath": "..\\src\\BD.WTTS.Client.Plugins.SteamIdleCard\\Settings\\Abstractions",
|
||||
"Properties": [
|
||||
{
|
||||
"TypeName": "TimeSpan",
|
||||
"PropertyName": "IdleTime",
|
||||
"DefaultValue": "TimeSpan.FromMinutes(6)",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "挂卡状态更新时间",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "IdleRule",
|
||||
"PropertyName": "IdleRule",
|
||||
"DefaultValue": "IdleRule.FastMode",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "运行规则",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "IdleSequentital",
|
||||
"PropertyName": "IdleSequentital",
|
||||
"DefaultValue": "IdleSequentital.Default",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "运行顺序",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "MaxIdleCount",
|
||||
"DefaultValue": "30",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "最大并行运行游戏数量",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "double",
|
||||
"PropertyName": "MinRunTime",
|
||||
"DefaultValue": "2",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "最少游戏时间 hours",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "double",
|
||||
"PropertyName": "SwitchTime",
|
||||
"DefaultValue": "5000",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "自动切换游戏时间间隔 ms",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "double",
|
||||
"PropertyName": "RefreshBadgesTime",
|
||||
"DefaultValue": "6",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "自动刷新徽章数据时间间隔 min",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
}
|
||||
],
|
||||
"Usings": ""
|
||||
},
|
||||
"ASFSettings": {
|
||||
"ClassFilePath": "..\\src\\BD.WTTS.Client.Plugins.ArchiSteamFarmPlus\\Settings",
|
||||
"InterfaceFilePath": "..\\src\\BD.WTTS.Client.Plugins.ArchiSteamFarmPlus\\Settings\\Abstractions",
|
||||
"Properties": [
|
||||
{
|
||||
"TypeName": "string",
|
||||
"PropertyName": "ArchiSteamFarmExePath",
|
||||
"DefaultValue": "string.Empty",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "ASF 路径",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": false
|
||||
},
|
||||
{
|
||||
"TypeName": "bool",
|
||||
"PropertyName": "AutoRunArchiSteamFarm",
|
||||
"DefaultValue": "false",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "程序启动时自动运行 ASF",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "bool",
|
||||
"PropertyName": "CheckArchiSteamFarmExe",
|
||||
"DefaultValue": "false",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "检查文件安全性",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "ConsoleMaxLineDefault",
|
||||
"DefaultValue": "200",
|
||||
"DefaultValueIsConst": true,
|
||||
"Summary": "控制台默认最大行数",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "MinRangeConsoleMaxLine",
|
||||
"DefaultValue": "DefaultConsoleMaxLineDefault",
|
||||
"DefaultValueIsConst": true,
|
||||
"Summary": "控制台默认最大行数范围最小值",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "MaxRangeConsoleMaxLine",
|
||||
"DefaultValue": "5000",
|
||||
"DefaultValueIsConst": true,
|
||||
"Summary": "控制台默认最大行数范围最大值",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "ConsoleMaxLine",
|
||||
"DefaultValue": "DefaultConsoleMaxLineDefault",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "控制台最大行数",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "ConsoleFontSizeDefault",
|
||||
"DefaultValue": "14",
|
||||
"DefaultValueIsConst": true,
|
||||
"Summary": "",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "MinRangeConsoleFontSize",
|
||||
"DefaultValue": "8",
|
||||
"DefaultValueIsConst": true,
|
||||
"Summary": "",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "MaxRangeConsoleFontSize",
|
||||
"DefaultValue": "24",
|
||||
"DefaultValueIsConst": true,
|
||||
"Summary": "",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "ConsoleFontSize",
|
||||
"DefaultValue": "DefaultConsoleFontSizeDefault",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "控制台字体大小",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "IPCPortIdValue",
|
||||
"DefaultValue": "6242",
|
||||
"DefaultValueIsConst": true,
|
||||
"Summary": "",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": null
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "IPCPortId",
|
||||
"DefaultValue": "DefaultIPCPortIdValue",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "IPC 端口号,默认值为 <see cref=\"DefaultIPCPortIdValue\" />",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "bool",
|
||||
"PropertyName": "IPCPortOccupiedRandom",
|
||||
"DefaultValue": "true",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "IPC 端口号被占用时是否随机一个未使用的端口号,默认值 <see langword=\"true\" />",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
}
|
||||
],
|
||||
"Usings": ""
|
||||
|
||||
+1
-1
Submodule ref/ArchiSteamFarm updated: 26bb562481...a768ec43a5
+1
-1
Submodule ref/Avalonia.Image2 updated: 3d8eb52230...c4162f332f
+1
-1
Submodule ref/Common updated: e258b48fbf...4fd40563b7
+1
-1
Submodule ref/DirectoryPackages updated: 3a012bc7cb...6974b6215e
Submodule ref/SteamAchievementManager updated: bfcf236fbc...4c578c1cc4
+1
-1
Submodule ref/SteamClient updated: 31e6f4436f...079a2c22ef
Submodule ref/WTTS.MicroServices.ClientSDK updated: 0f04842586...29a5a63dcb
+1
-1
Submodule ref/WinAuth updated: 06269f56a1...d3f45cffe2
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 2.5 KiB |
@@ -5,11 +5,13 @@ namespace BD.WTTS;
|
||||
/// </summary>
|
||||
public static partial class AssemblyInfo
|
||||
{
|
||||
const string Version2 = "3.0";
|
||||
|
||||
/// <summary>
|
||||
/// 语义化应用程序版本
|
||||
/// https://semver.org/lang/zh-CN/
|
||||
/// </summary>
|
||||
public const string Version = "3.0.0";
|
||||
public const string Version = $"{Version2}.0";
|
||||
|
||||
/// <summary>
|
||||
/// 预览版本号,范围 1~9,只增不减
|
||||
@@ -19,9 +21,9 @@ public static partial class AssemblyInfo
|
||||
/// <summary>
|
||||
/// RC 版本号,范围 0~9,只增不减
|
||||
/// </summary>
|
||||
const string ver_for_rc = "1";
|
||||
const string ver_for_rc = "5";
|
||||
|
||||
public const string FileVersion = $"{Version}.{ver_for_rc}0{ver_for_preview}";
|
||||
public const string FileVersion = $"{Version2}.{ver_for_rc}0{ver_for_preview}.0";
|
||||
|
||||
//public const string InformationalVersion = Version;
|
||||
//public const string InformationalVersion = $"{Version}-preview.{ver_for_preview}";
|
||||
|
||||
@@ -22,6 +22,9 @@ global using Avalonia.Metadata;
|
||||
[assembly: XmlnsDefinition("https://steampp.net/ui", "BD.WTTS.Converters")]
|
||||
[assembly: XmlnsDefinition("https://steampp.net/ui", "BD.WTTS.Markup")]
|
||||
[assembly: XmlnsDefinition("https://steampp.net/ui", "BD.WTTS.Client.Resources")]
|
||||
[assembly: XmlnsDefinition("https://steampp.net/ui", "Mobius.Models")]
|
||||
[assembly: XmlnsDefinition("https://steampp.net/ui", "Mobius")]
|
||||
|
||||
[assembly: XmlnsDefinition("https://steampp.net/services", "BD.WTTS.Services")]
|
||||
[assembly: XmlnsDefinition("https://steampp.net/services", "BD.WTTS.Plugins")]
|
||||
[assembly: XmlnsDefinition("https://steampp.net/services", "BD.WTTS.Plugins.Abstractions")]
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<Identity
|
||||
Name="4651ED44255E.47979655102CE"
|
||||
Publisher="CN=A90E406B-B2D3-4A23-B061-0FA1D65C4F66"
|
||||
Version="3.0.0.0" />
|
||||
Version="3.0.207.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Watt Toolkit</DisplayName>
|
||||
@@ -62,9 +62,9 @@
|
||||
|
||||
<Extensions>
|
||||
<desktop2:Extension Category="windows.firewallRules">
|
||||
<desktop2:FirewallRules Executable="Steam++\Steam++.exe">
|
||||
<desktop2:Rule Direction="in" IPProtocol="TCP" Profile="all" />
|
||||
<desktop2:Rule Direction="in" IPProtocol="UDP" Profile="all" />
|
||||
<desktop2:FirewallRules Executable="Steam++\modules\Accelerator\Steam++.Accelerator.exe">
|
||||
<desktop2:Rule Direction="in" IPProtocol="TCP" Profile="all"/>
|
||||
<desktop2:Rule Direction="in" IPProtocol="UDP" Profile="all"/>
|
||||
</desktop2:FirewallRules>
|
||||
</desktop2:Extension>
|
||||
</Extensions>
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
<legacyCorruptedStateExceptionsPolicy enabled="true"/>
|
||||
</runtime>
|
||||
<appSettings>
|
||||
<add key="d" value="7.0.10"/>
|
||||
<add key="d" value="7.0.11"/>
|
||||
</appSettings>
|
||||
</configuration>
|
||||
@@ -10,6 +10,6 @@
|
||||
<legacyCorruptedStateExceptionsPolicy enabled="true"/>
|
||||
</runtime>
|
||||
<appSettings>
|
||||
<add key="d" value="7.0.10"/>
|
||||
<add key="d" value="8.0.0"/>
|
||||
</appSettings>
|
||||
</configuration>
|
||||
@@ -16,9 +16,9 @@ static unsafe partial class Program
|
||||
{
|
||||
static Program()
|
||||
{
|
||||
dotnet_version_major = "7";
|
||||
dotnet_version_major = "8";
|
||||
dotnet_version_minor = "0";
|
||||
dotnet_version_build = "10";
|
||||
dotnet_version_build = "0";
|
||||
dotnet_version = $"{dotnet_version_major}.{dotnet_version_minor}.{dotnet_version_build}";
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ static unsafe partial class Program
|
||||
//#else
|
||||
// public const string dotnet_version_major = "7";
|
||||
// public const string dotnet_version_minor = "0";
|
||||
// public const string dotnet_version_build = "10";
|
||||
// public const string dotnet_version_build = "11";
|
||||
// public const string dotnet_version = $"{dotnet_version_major}.{dotnet_version_minor}.{dotnet_version_build}";
|
||||
//#endif
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"><assemblyIdentity version="1.0.0.0" name="BeyondDimension.WattToolkit.AppHost"/><trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"><security><requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3"><requestedExecutionLevel level="asInvoker" uiAccess="false" /></requestedPrivileges></security></trustInfo><compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"><application><supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" /><supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" /><supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" /><supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" /><supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /></application></compatibility></assembly>
|
||||
<?xml version="1.0" encoding="utf-8"?><assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"><assemblyIdentity version="1.0.0.0" name="BeyondDimension.WattToolkit.AppHost"/><trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"><security><requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3"><requestedExecutionLevel level="requireAdministrator" uiAccess="false" /></requestedPrivileges></security></trustInfo><compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"><application><supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" /><supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" /><supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" /><supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" /><supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /></application></compatibility></assembly>
|
||||
@@ -98,14 +98,14 @@
|
||||
<ProjectReference Include="..\BD.WTTS.Client\BD.WTTS.Client.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows' AND Exists('$(NugetPackageRoot)\system.drawing.common\$(Version_System_Drawing_Common)\runtimes\win\lib\net7.0\System.Drawing.Common.dll')">
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows' AND Exists('$(NugetPackageRoot)\system.drawing.common\$(Version_System_Drawing_Common)\lib\net8.0\System.Drawing.Common.dll')">
|
||||
<!-- Windows Only -->
|
||||
<!-- 修复:存在 Microsoft.WindowsDesktop.App 依赖时不会将 System.Drawing.Common.dll 复制到输出目录-->
|
||||
<None Include="$(NugetPackageRoot)\system.drawing.common\$(Version_System_Drawing_Common)\runtimes\win\lib\net7.0\System.Drawing.Common.dll">
|
||||
<None Include="$(NugetPackageRoot)\system.drawing.common\$(Version_System_Drawing_Common)\lib\net8.0\System.Drawing.Common.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<Reference Include="System.Drawing.Common">
|
||||
<HintPath>$(NugetPackageRoot)\system.drawing.common\$(Version_System_Drawing_Common)\runtimes\win\lib\net7.0\System.Drawing.Common.dll</HintPath>
|
||||
<HintPath>$(NugetPackageRoot)\system.drawing.common\$(Version_System_Drawing_Common)\lib\net8.0\System.Drawing.Common.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
<string>NSApplication</string>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>AppIcon.icns</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>net.steampp.app</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -1 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"><assemblyIdentity version="1.0.0.0" name="BeyondDimension.WattToolkit.App"/><trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"><security><requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3"><requestedExecutionLevel level="asInvoker" uiAccess="false" /></requestedPrivileges></security></trustInfo><compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"><application><supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" /><supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" /><supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" /><supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" /><supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /></application></compatibility></assembly>
|
||||
<?xml version="1.0" encoding="utf-8"?><assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"><assemblyIdentity version="1.0.0.0" name="BeyondDimension.WattToolkit.App"/><trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"><security><requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3"><requestedExecutionLevel level="requireAdministrator" uiAccess="false" /></requestedPrivileges></security></trustInfo><compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"><application><supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" /><supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" /><supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" /><supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" /><supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /></application></compatibility></assembly>
|
||||
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"profiles": {
|
||||
"BD.WTTS.Client.Avalonia.App": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "-clt devtools -steamrun"
|
||||
"commandName": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,7 +248,7 @@ sealed partial class Program : Startup
|
||||
|
||||
sealed class Essentials_AppVerS : IApplicationVersionService
|
||||
{
|
||||
string IApplicationVersionService.ApplicationVersion => AssemblyInfo.Version;
|
||||
string IApplicationVersionService.ApplicationVersion => AssemblyInfo.FileVersion;
|
||||
|
||||
string IApplicationVersionService.AssemblyTrademark => AssemblyInfo.Trademark;
|
||||
}
|
||||
|
||||
@@ -126,6 +126,12 @@
|
||||
<ProjectReference Include="..\BD.WTTS.Client\BD.WTTS.Client.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="UI\Views\Controls\Widgets\CarouselItems.cs">
|
||||
<DependentUpon>%(Filename)</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\TFM_NETX_WITH_DESKTOP.props" />
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -18,6 +18,8 @@ public sealed class EnumLocalizationNameConverter : IValueConverter
|
||||
else
|
||||
{
|
||||
text = Strings.ResourceManager.GetString(desc, Strings.Culture);
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return desc;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
|
||||
+7
-3
@@ -1,19 +1,23 @@
|
||||
namespace BD.WTTS.Converters;
|
||||
|
||||
public sealed class VisbleStringConverter : IMultiValueConverter
|
||||
public sealed class VisibleStringConverter : IMultiValueConverter
|
||||
{
|
||||
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (values.Count >= 3 && values.FirstOrDefault() is bool isVisble)
|
||||
if (values.Count >= 2 && values.FirstOrDefault() is bool isVisble)
|
||||
{
|
||||
if (isVisble)
|
||||
{
|
||||
return values[1];
|
||||
}
|
||||
else
|
||||
else if (values.Count >= 3)
|
||||
{
|
||||
return values[2];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
@@ -2,18 +2,11 @@ namespace BD.WTTS.UI;
|
||||
|
||||
public static class ControlExtensions
|
||||
{
|
||||
public static void SetViewModel<T>(this Control c, bool useCache = true) where T : ViewModelBase
|
||||
public static void SetViewModel<T>(this Control c) where T : ViewModelBase, new()
|
||||
{
|
||||
if (c.DataContext == null || c.DataContext.GetType() != typeof(T))
|
||||
{
|
||||
T cx;
|
||||
|
||||
if (useCache)
|
||||
cx = IViewModelManager.Instance.Get<T>();
|
||||
else
|
||||
cx = Activator.CreateInstance<T>();
|
||||
|
||||
c.DataContext = cx;
|
||||
c.DataContext = new T();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace BD.WTTS;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IEnumerable"/> extensions methods
|
||||
/// </summary>
|
||||
public static class IEnumerableExtensions
|
||||
{
|
||||
public static IEnumerable<IEnumerable> Batch(this IEnumerable source, int size)
|
||||
{
|
||||
object[]? bucket = null;
|
||||
var count = 0;
|
||||
|
||||
foreach (var item in source)
|
||||
{
|
||||
bucket ??= new object[size];
|
||||
|
||||
bucket[count++] = item;
|
||||
|
||||
if (count == size)
|
||||
{
|
||||
yield return bucket;
|
||||
bucket = null;
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (bucket != null && count > 0)
|
||||
yield return bucket.Take(count).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
+3
-30
@@ -5,46 +5,19 @@ namespace BD.WTTS.Services.Implementation;
|
||||
/// <inheritdoc cref="IAppUpdateService"/>
|
||||
sealed class AvaloniaApplicationUpdateServiceImpl : AppUpdateServiceBaseImpl
|
||||
{
|
||||
readonly IWindowManager windowManager;
|
||||
|
||||
public AvaloniaApplicationUpdateServiceImpl(
|
||||
IApplication application,
|
||||
INotificationService notificationService,
|
||||
IWindowManager windowManager,
|
||||
IToast toast,
|
||||
IMicroServiceClient client,
|
||||
IOptions<AppSettings> options) : base(application, notificationService, toast, client, options)
|
||||
{
|
||||
this.windowManager = windowManager;
|
||||
|
||||
}
|
||||
|
||||
protected override async Task ShowNewVersionWindowAsync()
|
||||
protected override bool HasActiveWindow()
|
||||
{
|
||||
await windowManager.ShowAsync(typeof(object), AppEndPoint.NewVersion, isParent: false);
|
||||
}
|
||||
|
||||
public override async void OnMainOpenTryShowNewVersionWindow()
|
||||
{
|
||||
if (ShowNewVersionWindowOnMainOpen)
|
||||
{
|
||||
ShowNewVersionWindowOnMainOpen = false;
|
||||
await ShowNewVersionWindowAsync();
|
||||
}
|
||||
}
|
||||
|
||||
protected override async void OnExistNewVersion()
|
||||
{
|
||||
base.OnExistNewVersion();
|
||||
|
||||
var hasActiveWindow = App.Instance.HasActiveWindow();
|
||||
if (hasActiveWindow)
|
||||
{
|
||||
await ShowNewVersionWindowAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowNewVersionWindowOnMainOpen = true;
|
||||
notification.Notify(AppResources.NewVersionUpdateNotifyText_.Format(NewVersionInfo?.Version), NotificationType.NewVersion);
|
||||
}
|
||||
return hasActiveWindow;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ using System.Collections.Immutable;
|
||||
namespace BD.WTTS.Services.Implementation;
|
||||
|
||||
/// <inheritdoc cref="IWindowManager"/>
|
||||
sealed class AvaloniaWindowManagerImpl : IWindowManagerImpl
|
||||
public sealed class AvaloniaWindowManagerImpl : IWindowManagerImpl
|
||||
{
|
||||
Type? IWindowManagerImpl.WindowType => typeof(Window);
|
||||
|
||||
@@ -116,7 +116,7 @@ sealed class AvaloniaWindowManagerImpl : IWindowManagerImpl
|
||||
|
||||
if (viewModel is IWindowViewModel window)
|
||||
{
|
||||
window.Close = () => td?.Hide(TaskDialogStandardResult.Close);
|
||||
window.Close = b => td?.Hide(b ? TaskDialogStandardResult.OK : TaskDialogStandardResult.Close);
|
||||
}
|
||||
|
||||
td.DataContext = viewModel;
|
||||
|
||||
@@ -10,7 +10,9 @@ public sealed class NavigationService : INavigationService
|
||||
{
|
||||
public static NavigationService Instance { get; } = (NavigationService)Ioc.Get<INavigationService>();
|
||||
|
||||
public Control? PreviousPage { get; set; }
|
||||
//public Type? PreviousPage { get; set; }
|
||||
|
||||
public Type? CurrnetPage => _frame?.CurrentSourcePageType;
|
||||
|
||||
public object? GetViewModelToPageContent(object viewModel, bool isCreateInstance = true)
|
||||
{
|
||||
@@ -54,7 +56,8 @@ public sealed class NavigationService : INavigationService
|
||||
|
||||
public void Navigate(Type? t, NavigationTransitionEffect effect = NavigationTransitionEffect.None, bool useCache = true)
|
||||
{
|
||||
_frame.ThrowIsNull();
|
||||
if (_frame == null)
|
||||
return;
|
||||
|
||||
var navOptions = new FrameNavigationOptions
|
||||
{
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
using Avalonia.Controls;
|
||||
using BD.SteamClient.Services;
|
||||
using BD.WTTS.Client.Resources;
|
||||
using BD.WTTS.Converters;
|
||||
using System.Collections.Specialized;
|
||||
using static BD.WTTS.Services.INotificationService;
|
||||
|
||||
namespace BD.WTTS.UI;
|
||||
|
||||
partial class App
|
||||
{
|
||||
public Dictionary<string, TrayMenuItem> TrayMenus { get; } = new Dictionary<string, TrayMenuItem>();
|
||||
|
||||
public TrayIcons TrayIcons { get; init; } = new TrayIcons();
|
||||
|
||||
readonly Dictionary<string, ICommand> trayIconMenus = new();
|
||||
@@ -32,51 +36,11 @@ partial class App
|
||||
ToolTipText = AssemblyInfo.Trademark,
|
||||
Command = ReactiveCommand.Create(RestoreMainWindow),
|
||||
//[!TrayIcon.IsVisibleProperty] = new Binding { Path = "Value", Source = GeneralSettings.IsEnableTrayIcon, Mode = BindingMode.OneWay },
|
||||
Menu = new NativeMenu
|
||||
{
|
||||
//new NativeMenuItem
|
||||
//{
|
||||
// Header = "Test",
|
||||
//},
|
||||
//new NativeMenuItemSeparator(),
|
||||
new NativeMenuItem
|
||||
{
|
||||
[!NativeMenuItem.HeaderProperty] = new MultiBinding {
|
||||
Bindings = new List<Avalonia.Data.IBinding>()
|
||||
{
|
||||
new Binding { Path = "IsRunningSteamProcess", Source = SteamConnectService.Current, Mode = BindingMode.OneWay },
|
||||
new Binding { Path = "Res.CloseSteam", Source = ResourceService.Current, Mode = BindingMode.OneWay },
|
||||
new Binding { Path = "Res.StartSteam", Source = ResourceService.Current, Mode = BindingMode.OneWay },
|
||||
},
|
||||
Converter = (VisbleStringConverter)App.Instance.FindResource("VisbleStringConverter")!,
|
||||
},
|
||||
Command = ReactiveCommand.Create(() =>
|
||||
{
|
||||
if(ISteamService.Instance.IsRunningSteamProcess)
|
||||
{
|
||||
ISteamService.Instance.TryKillSteamProcess();
|
||||
}
|
||||
else
|
||||
{
|
||||
ISteamService.Instance.StartSteamWithParameter();
|
||||
}
|
||||
})
|
||||
},
|
||||
new NativeMenuItemSeparator(),
|
||||
new NativeMenuItem
|
||||
{
|
||||
[!NativeMenuItem.HeaderProperty] = new Binding { Path = "Res.OpenMainWindow", Source = ResourceService.Current, Mode = BindingMode.OneWay },
|
||||
Command = ReactiveCommand.Create(RestoreMainWindow)
|
||||
},
|
||||
new NativeMenuItemSeparator(),
|
||||
new NativeMenuItem
|
||||
{
|
||||
[!NativeMenuItem.HeaderProperty] = new Binding { Path = "Res.Exit", Source = ResourceService.Current, Mode = BindingMode.OneWay },
|
||||
Command = ReactiveCommand.Create(() => { Shutdown(); })
|
||||
}
|
||||
}
|
||||
Menu = new NativeMenu(),
|
||||
});
|
||||
|
||||
UpdateMenuItems();
|
||||
|
||||
//IViewModelManager.Instance.InitTaskBarWindowViewModel();
|
||||
//NotifyIconHelper.Init(this,
|
||||
// notifyIconClick: (_, _) => RestoreMainWindow());
|
||||
@@ -102,4 +66,108 @@ partial class App
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateMenuItems()
|
||||
{
|
||||
MainThread2.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
var menus = TrayIcons.FirstOrDefault()?.Menu;
|
||||
|
||||
if (menus != null)
|
||||
{
|
||||
menus.Items.Clear();
|
||||
|
||||
var defaultTrayMenus = new List<NativeMenuItemBase>()
|
||||
{
|
||||
new NativeMenuItem
|
||||
{
|
||||
[!NativeMenuItem.HeaderProperty] = new MultiBinding {
|
||||
Bindings = new List<Avalonia.Data.IBinding>()
|
||||
{
|
||||
new Binding { Path = "IsRunningSteamProcess", Source = SteamConnectService.Current, Mode = BindingMode.OneWay },
|
||||
new Binding { Path = "Res.CloseSteam", Source = ResourceService.Current, Mode = BindingMode.OneWay },
|
||||
new Binding { Path = "Res.StartSteam", Source = ResourceService.Current, Mode = BindingMode.OneWay },
|
||||
},
|
||||
Converter = (VisibleStringConverter)App.Instance.FindResource(nameof(VisibleStringConverter))!,
|
||||
},
|
||||
Command = ReactiveCommand.Create(async () =>
|
||||
{
|
||||
if(ISteamService.Instance.IsRunningSteamProcess)
|
||||
{
|
||||
await ISteamService.Instance.TryKillSteamProcess();
|
||||
}
|
||||
else
|
||||
{
|
||||
ISteamService.Instance.StartSteamWithParameter();
|
||||
}
|
||||
}),
|
||||
},
|
||||
//new NativeMenuItemSeparator(),
|
||||
new NativeMenuItem
|
||||
{
|
||||
[!NativeMenuItem.HeaderProperty] = new Binding { Path = "Res.OpenMainWindow", Source = ResourceService.Current, Mode = BindingMode.OneWay },
|
||||
Command = ReactiveCommand.Create(RestoreMainWindow),
|
||||
},
|
||||
new NativeMenuItemSeparator(),
|
||||
new NativeMenuItem
|
||||
{
|
||||
[!NativeMenuItem.HeaderProperty] = new Binding { Path = "Res.Exit", Source = ResourceService.Current, Mode = BindingMode.OneWay },
|
||||
Command = ReactiveCommand.Create(() => { Shutdown(); })
|
||||
},
|
||||
};
|
||||
|
||||
if (TrayMenus.Any_Nullable())
|
||||
{
|
||||
foreach (var item in TrayMenus)
|
||||
{
|
||||
NativeMenu? subMenu = null;
|
||||
if (item.Value.Items.Any_Nullable())
|
||||
{
|
||||
subMenu = new NativeMenu();
|
||||
foreach (var sub in item.Value.Items)
|
||||
subMenu.Add(new NativeMenuItem
|
||||
{
|
||||
Header = sub.Name,
|
||||
Command = sub.Command,
|
||||
CommandParameter = sub.CommandParameter,
|
||||
});
|
||||
menus.Add(new NativeMenuItem
|
||||
{
|
||||
Header = item.Value.Name,
|
||||
Menu = subMenu,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
menus.Add(new NativeMenuItem
|
||||
{
|
||||
Header = item.Value.Name,
|
||||
Command = item.Value.Command,
|
||||
CommandParameter = item.Value.CommandParameter,
|
||||
});
|
||||
}
|
||||
}
|
||||
menus.Add(new NativeMenuItemSeparator());
|
||||
}
|
||||
|
||||
foreach (var item in defaultTrayMenus)
|
||||
{
|
||||
menus.Add(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void UpdateMenuItems(string menuKey, TrayMenuItem trayMenuItem)
|
||||
{
|
||||
if (TrayMenus != null)
|
||||
{
|
||||
if (TrayMenus.ContainsKey(menuKey))
|
||||
TrayMenus[menuKey] = trayMenuItem;
|
||||
else
|
||||
TrayMenus.Add(menuKey, trayMenuItem);
|
||||
|
||||
UpdateMenuItems();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
<spp:EnumEqualValueConverter x:Key="EnumEqualValueConverter" />
|
||||
<spp:StringEqualsConverter x:Key="StringEqualsConverter" />
|
||||
<spp:EnumLocalizationNameConverter x:Key="EnumLocalizationNameConverter" />
|
||||
<spp:VisbleStringConverter x:Key="VisbleStringConverter" />
|
||||
<spp:VisibleStringConverter x:Key="VisibleStringConverter" />
|
||||
</Application.Resources>
|
||||
<Application.Styles>
|
||||
<sty:FluentAvaloniaTheme PreferSystemTheme="False" PreferUserAccentColor="True" />
|
||||
|
||||
@@ -44,6 +44,9 @@ public sealed partial class App : Application
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
|
||||
#if WINDOWS || LINUX || MACOS
|
||||
if (GeneralSettings.MinimizeOnStartup.Value)
|
||||
Startup.Instance.IsMinimize = true;
|
||||
|
||||
if (Startup.Instance.IsSteamRun)
|
||||
{
|
||||
try
|
||||
@@ -90,8 +93,18 @@ public sealed partial class App : Application
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
MainWindow = InitializeMainWindow?.Invoke(this);
|
||||
desktop.MainWindow = MainWindow ??= new MainWindow();
|
||||
MainWindow = InitializeMainWindow?.Invoke(this) ?? new MainWindow();
|
||||
|
||||
desktop.MainWindow =
|
||||
#if !UI_DEMO
|
||||
Startup.Instance.IsMinimize && MainWindow is MainWindow ? null :
|
||||
#endif
|
||||
MainWindow;
|
||||
|
||||
if (Startup.Instance.IsMinimize && MainWindow is AppWindow appWindow)
|
||||
{
|
||||
Task2.InBackground(() => appWindow.SplashScreen?.RunTasks(CancellationToken.None));
|
||||
}
|
||||
}
|
||||
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleView)
|
||||
{
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
@@ -13,8 +13,9 @@
|
||||
<Thickness x:Key="PageMargin">10</Thickness>
|
||||
<sys:Double x:Key="SettingsSpacing">3</sys:Double>
|
||||
|
||||
<!--<sys:Char x:Key="PasswordChar">●</sys:Char>-->
|
||||
<sys:Char x:Key="PasswordChar">•</sys:Char>
|
||||
<sys:Char x:Key="PasswordChar">●</sys:Char>
|
||||
<!--<sys:Char x:Key="PasswordChar">•</sys:Char>
|
||||
<sys:Char x:Key="PasswordChar">·</sys:Char>-->
|
||||
<FontFamily x:Key="ContentControlThemeFontFamily">Null</FontFamily>
|
||||
<BoxShadows x:Key="InfoBoxShadow">4 4 32 0 #66000000</BoxShadows>
|
||||
|
||||
@@ -44,6 +45,7 @@
|
||||
|
||||
<StyleInclude Source="/UI/Styling/InfoBox.axaml" />
|
||||
<StyleInclude Source="/UI/Styling/Controls/PageBase.axaml" />
|
||||
<StyleInclude Source="/UI/Styling/Controls/CarouselItems.axaml" />
|
||||
<StyleInclude Source="/UI/Styling/Controls/OptionsDisplayItem.axaml" />
|
||||
<StyleInclude Source="/UI/Styling/Controls/RippleEffect.axaml" />
|
||||
<StyleInclude Source="/UI/Styling/Controls/Draggable.axaml" />
|
||||
|
||||
@@ -180,6 +180,7 @@
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0,5"
|
||||
VerticalAlignment="Top"
|
||||
Classes="CaptionTextBlockStyle"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- AutoCompleteBox -->
|
||||
<Style Selector="AutoCompleteBox.Search /template/ TextBox#PART_TextBox">
|
||||
<Setter Property="Watermark" Value="{ReflectionBinding Path=Res.Search, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<Setter Property="Watermark" Value="{Binding Path=Res.Search, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<Setter Property="InnerRightContent">
|
||||
<Template>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<Styles
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:generic="using:System.Collections.Generic"
|
||||
xmlns:spp="https://steampp.net/ui"
|
||||
xmlns:sys="using:System"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
x:CompileBindings="True">
|
||||
<Design.PreviewWith>
|
||||
<Border Padding="20">
|
||||
<spp:CarouselItems>
|
||||
<spp:CarouselItems.Items>
|
||||
<Border Background="Red" />
|
||||
<Border Background="Blue" />
|
||||
<Border Background="Black" />
|
||||
<Border Background="Yellow" />
|
||||
</spp:CarouselItems.Items>
|
||||
</spp:CarouselItems>
|
||||
</Border>
|
||||
</Design.PreviewWith>
|
||||
|
||||
<Style Selector="spp|CarouselItems">
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Border
|
||||
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="0"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="8">
|
||||
<Grid RowDefinitions="*,5,Auto">
|
||||
<Carousel Name="CarouselControl" Classes="AnimeSlide">
|
||||
<Carousel.ItemTemplate>
|
||||
<DataTemplate DataType="sys:Object">
|
||||
<ItemsControl ItemTemplate="{Binding $parent[spp:CarouselItems].ItemTemplate}" ItemsSource="{Binding}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<spp:FixedWrapPanel ItemsPerLine="{Binding $parent[spp:CarouselItems].ItemsPerPage}" Spacing="5" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</DataTemplate>
|
||||
</Carousel.ItemTemplate>
|
||||
</Carousel>
|
||||
<Button
|
||||
Name="Left"
|
||||
HorizontalAlignment="Left"
|
||||
Background="#80000000"
|
||||
Classes="action"
|
||||
IsVisible="False">
|
||||
<ui:SymbolIcon FontSize="18" Symbol="ChevronLeft" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="Right"
|
||||
HorizontalAlignment="Right"
|
||||
Background="#80000000"
|
||||
Classes="action"
|
||||
IsVisible="False">
|
||||
<ui:SymbolIcon FontSize="18" Symbol="ChevronRight" />
|
||||
</Button>
|
||||
|
||||
<Panel
|
||||
Grid.Row="2"
|
||||
Height="10"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom">
|
||||
<ItemsRepeater Name="Swiper" IsVisible="False">
|
||||
<ItemsRepeater.Layout>
|
||||
<StackLayout
|
||||
DisableVirtualization="True"
|
||||
Orientation="Horizontal"
|
||||
Spacing="8" />
|
||||
</ItemsRepeater.Layout>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<DataTemplate.DataType>
|
||||
<x:Type x:TypeArguments="x:Int32, x:String" TypeName="generic:KeyValuePair" />
|
||||
</DataTemplate.DataType>
|
||||
<Button
|
||||
Padding="0"
|
||||
Classes="Plain"
|
||||
Command="{Binding $parent[spp:CarouselItems].CarouselBannerIndexCommand}"
|
||||
CommandParameter="{Binding Key}">
|
||||
<Ellipse
|
||||
Name="IndexEllipse"
|
||||
Width="10"
|
||||
Height="10"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Fill="{Binding Value}"
|
||||
Opacity="0.6"
|
||||
StrokeThickness="0" />
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</Panel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
|
||||
<Style Selector="spp|CarouselItems /template/ Button.action">
|
||||
<Setter Property="Width" Value="36" />
|
||||
<Setter Property="Height" Value="64" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</Style>
|
||||
</Styles>
|
||||
@@ -75,19 +75,29 @@
|
||||
</ContentPresenter.IsVisible>-->
|
||||
</ContentPresenter>
|
||||
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Content="{TemplateBinding NoResultMessage}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
Theme="{StaticResource SubtitleTextBlockStyle}">
|
||||
<ContentPresenter.IsVisible>
|
||||
<Panel HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<spp:Image2
|
||||
Width="100"
|
||||
Height="100"
|
||||
Margin="0,0,0,120"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality"
|
||||
Source="avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/empty.png"
|
||||
Stretch="UniformToFill" />
|
||||
<Panel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="IsShowNoResultText" RelativeSource="{RelativeSource TemplatedParent}" />
|
||||
<Binding Path="!IsLoading" RelativeSource="{RelativeSource TemplatedParent}" />
|
||||
</MultiBinding>
|
||||
</ContentPresenter.IsVisible>
|
||||
</ContentPresenter>
|
||||
</Panel.IsVisible>
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Content="{TemplateBinding NoResultMessage}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
Theme="{StaticResource SubtitleTextBlockStyle}" />
|
||||
</Panel>
|
||||
</Panel>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}" />
|
||||
<Panel
|
||||
Name="FilePickerTipBorder"
|
||||
MaxWidth="{ReflectionBinding $parent.Bounds.Width}"
|
||||
MaxHeight="{ReflectionBinding $parent.Bounds.Height}">
|
||||
MaxWidth="{Binding $parent.Bounds.Width}"
|
||||
MaxHeight="{Binding $parent.Bounds.Height}">
|
||||
<Border Background="{DynamicResource ControlOnImageFillColorSecondaryBrush}" Opacity="0.8">
|
||||
<StackPanel
|
||||
Margin="10,5"
|
||||
@@ -49,9 +49,7 @@
|
||||
<ui:SymbolIcon FontSize="30" Symbol="Download" />
|
||||
<TextBlock
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Text="{ReflectionBinding Path=Res.CustomFilePicker_Tip,
|
||||
Mode=OneWay,
|
||||
Source={x:Static s:ResourceService.Current}}"
|
||||
Text="{Binding Path=Res.CustomFilePicker_Tip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
TextAlignment="Center"
|
||||
Theme="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
@@ -59,12 +59,10 @@
|
||||
<BrushTransition Property="BorderBrush" Duration="00:00:00.083" />
|
||||
</Transitions>
|
||||
</ui:FABorder.Transitions>
|
||||
<Grid
|
||||
MinHeight="{TemplateBinding MinHeight}"
|
||||
ColumnDefinitions="Auto,*,Auto,Auto"
|
||||
RowDefinitions="*,Auto">
|
||||
<Grid MinHeight="{TemplateBinding MinHeight}" ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<Viewbox
|
||||
Name="IconHost"
|
||||
Grid.RowSpan="2"
|
||||
Width="{DynamicResource SettingsExpanderItemIconSize}"
|
||||
Height="{DynamicResource SettingsExpanderItemIconSize}"
|
||||
HorizontalAlignment="Left"
|
||||
@@ -76,28 +74,31 @@
|
||||
Margin="{DynamicResource SettingsExpanderItemContentMargin}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock
|
||||
<ContentPresenter
|
||||
Name="HeaderText"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Classes="BaseTextBlockStyle"
|
||||
Text="{TemplateBinding Header}"
|
||||
Content="{TemplateBinding Header}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
|
||||
<TextBlock
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Classes="CaptionTextBlockStyle"
|
||||
Content="{TemplateBinding Description}"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
IsVisible="{TemplateBinding Description,
|
||||
Converter={StaticResource IsNullConverter},
|
||||
ConverterParameter=invert}"
|
||||
Text="{TemplateBinding Description}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<ContentPresenter
|
||||
Name="ActionButton"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="2"
|
||||
Margin="{DynamicResource SettingsExpanderItemFooterMargin}"
|
||||
HorizontalAlignment="Right"
|
||||
@@ -106,6 +107,7 @@
|
||||
|
||||
<ui:SymbolIcon
|
||||
Name="Chevron"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="3"
|
||||
Margin="24,4,0,4"
|
||||
VerticalAlignment="Center"
|
||||
|
||||
@@ -17,10 +17,7 @@
|
||||
Classes="Circle"
|
||||
Value="60" />
|
||||
<ProgressBar Width="150" IsIndeterminate="True" />
|
||||
<ProgressBar
|
||||
Width="150"
|
||||
Classes="notransitions"
|
||||
Value="50" />
|
||||
<ProgressBar Width="150" Value="50" />
|
||||
|
||||
<TextBlock Text="Material Progress" />
|
||||
<ProgressBar
|
||||
@@ -31,7 +28,6 @@
|
||||
<ProgressBar
|
||||
Width="150"
|
||||
Height="5"
|
||||
Classes="Material notransitions"
|
||||
Value="50" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
@@ -58,7 +54,7 @@
|
||||
<Setter Property="Width" Value="{DynamicResource ProgressBarMinHeight}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ProgressBar:not(.notransitions) /template/ Border#PART_Indicator">
|
||||
<Style Selector="ProgressBar[IsIndeterminate=False] /template/ Border#PART_Indicator">
|
||||
<Setter Property="Transitions">
|
||||
<Transitions>
|
||||
<DoubleTransition
|
||||
|
||||
@@ -296,8 +296,8 @@
|
||||
</Styles.Resources>
|
||||
|
||||
<Style Selector="ToggleSwitch">
|
||||
<Setter Property="OffContent" Value="{ReflectionBinding Path=Res.Off, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<Setter Property="OnContent" Value="{ReflectionBinding Path=Res.On, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<Setter Property="OffContent" Value="{Binding Path=Res.Off, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<Setter Property="OnContent" Value="{Binding Path=Res.On, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<Setter Property="MinWidth" Value="0" />
|
||||
</Style>
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ public class ReactiveAppWindow<TViewModel> : AppWindow, IViewFor<TViewModel>, IV
|
||||
|
||||
//SystemDecorations = SystemDecorations.BorderOnly;
|
||||
Background = null;
|
||||
TransparencyBackgroundFallback = Brushes.Transparent;
|
||||
TransparencyBackgroundFallback = OperatingSystem2.IsMacOS() ? Brushes.Black : Brushes.Transparent;
|
||||
TransparencyLevelHint = new WindowTransparencyLevel[] { UISettings.WindowBackgroundMaterial.Value.ToWindowTransparencyLevel() };
|
||||
|
||||
if (IsSaveWindowSize)
|
||||
|
||||
@@ -8,9 +8,7 @@
|
||||
xmlns:spp="https://steampp.net/ui"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
IsVisible="{ReflectionBinding IsShowAdvertise,
|
||||
Mode=OneWay,
|
||||
Source={x:Static s:AdvertiseService.Current}}"
|
||||
IsVisible="{Binding IsShowAdvertise, Mode=OneWay, Source={x:Static s:AdvertiseService.Current}}"
|
||||
mc:Ignorable="d">
|
||||
<Border ClipToBounds="True" CornerRadius="{DynamicResource ControlCornerRadius}">
|
||||
<Panel>
|
||||
@@ -20,15 +18,14 @@
|
||||
<Button
|
||||
Padding="0"
|
||||
Classes="Plain"
|
||||
Command="{ReflectionBinding ClickAdvertisementCommand,
|
||||
Mode=OneWay,
|
||||
Source={x:Static s:AdvertiseService.Current}}"
|
||||
Command="{Binding ClickAdvertisementCommand, Mode=OneWay, Source={x:Static s:AdvertiseService.Current}}"
|
||||
CommandParameter="{Binding}"
|
||||
Cursor="Hand"
|
||||
ToolTip.Tip="{Binding Describe}">
|
||||
<spp:Image2
|
||||
Width="{Binding $parent[UserControl].Bounds.Width}"
|
||||
Height="{Binding $parent[UserControl].Bounds.Height}"
|
||||
EnableCancelToken="False"
|
||||
Source="{Binding ImageUrl}"
|
||||
Stretch="UniformToFill" />
|
||||
</Button>
|
||||
@@ -39,17 +36,13 @@
|
||||
Margin="10"
|
||||
FontSize="10"
|
||||
Foreground="White"
|
||||
Text="{ReflectionBinding Path=Res.Advertisement,
|
||||
Mode=OneWay,
|
||||
Source={x:Static s:ResourceService.Current}}" />
|
||||
Text="{Binding Path=Res.Advertisement, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<Button
|
||||
Name="CloseAdBtn"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Background="Transparent"
|
||||
Content="{ReflectionBinding Path=Res.Advertisement_Close,
|
||||
Mode=OneWay,
|
||||
Source={x:Static s:ResourceService.Current}}"
|
||||
Content="{Binding Path=Res.Advertisement_Close, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Cursor="Hand"
|
||||
Foreground="White" />
|
||||
</Panel>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public enum AlignContent
|
||||
{
|
||||
FlexStart,
|
||||
FlexEnd,
|
||||
Center,
|
||||
Stretch,
|
||||
SpaceBetween,
|
||||
SpaceAround,
|
||||
SpaceEvenly
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
[SuppressMessage("Naming", "CA1717:Only FlagsAttribute enums should have plural names")]
|
||||
public enum AlignItems
|
||||
{
|
||||
FlexStart,
|
||||
FlexEnd,
|
||||
Center,
|
||||
Stretch
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public static class Flex
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines an attached property to control the alignment of a specific child in a flex layout.
|
||||
/// </summary>
|
||||
public static readonly AttachedProperty<AlignItems?> AlignSelfProperty =
|
||||
AvaloniaProperty.RegisterAttached<Layoutable, AlignItems?>("AlignSelf", typeof(Flex));
|
||||
|
||||
/// <summary>
|
||||
/// Defines an attached property to control the order of a specific child in a flex layout.
|
||||
/// </summary>
|
||||
public static readonly AttachedProperty<int> OrderProperty =
|
||||
AvaloniaProperty.RegisterAttached<Layoutable, int>("Order", typeof(Flex));
|
||||
|
||||
public static readonly AttachedProperty<FlexBasis> BasisProperty =
|
||||
AvaloniaProperty.RegisterAttached<Layoutable, FlexBasis>("Basis", typeof(Flex), FlexBasis.Auto);
|
||||
|
||||
public static readonly AttachedProperty<double> ShrinkProperty =
|
||||
AvaloniaProperty.RegisterAttached<Layoutable, double>("Shrink", typeof(Flex), 1.0, validate: v => v >= 0.0);
|
||||
|
||||
public static readonly AttachedProperty<double> GrowProperty =
|
||||
AvaloniaProperty.RegisterAttached<Layoutable, double>("Grow", typeof(Flex), 0.0, validate: v => v >= 0.0);
|
||||
|
||||
internal static readonly AttachedProperty<double> BaseLengthProperty =
|
||||
AvaloniaProperty.RegisterAttached<Layoutable, double>("BaseLength", typeof(Flex), 0.0);
|
||||
|
||||
internal static readonly AttachedProperty<double> CurrentLengthProperty =
|
||||
AvaloniaProperty.RegisterAttached<Layoutable, double>("CurrentLength", typeof(Flex), 0.0);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the child alignment in a flex layout
|
||||
/// </summary>
|
||||
public static AlignItems? GetAlignSelf(Layoutable layoutable)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
return layoutable.GetValue(AlignSelfProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the child alignment in a flex layout
|
||||
/// </summary>
|
||||
public static void SetAlignSelf(Layoutable layoutable, AlignItems? value)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
layoutable.SetValue(AlignSelfProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the child order in a flex layout
|
||||
/// </summary>
|
||||
public static int GetOrder(Layoutable layoutable)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
return layoutable.GetValue(OrderProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the child order in a flex layout
|
||||
/// </summary>
|
||||
public static void SetOrder(Layoutable layoutable, int value)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
layoutable.SetValue(OrderProperty, value);
|
||||
}
|
||||
|
||||
public static FlexBasis GetBasis(Layoutable layoutable)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
return layoutable.GetValue(BasisProperty);
|
||||
}
|
||||
|
||||
public static void SetBasis(Layoutable layoutable, FlexBasis value)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
layoutable.SetValue(BasisProperty, value);
|
||||
}
|
||||
|
||||
public static double GetShrink(Layoutable layoutable)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
return layoutable.GetValue(ShrinkProperty);
|
||||
}
|
||||
|
||||
public static void SetShrink(Layoutable layoutable, double value)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
layoutable.SetValue(ShrinkProperty, value);
|
||||
}
|
||||
|
||||
public static double GetGrow(Layoutable layoutable)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
return layoutable.GetValue(GrowProperty);
|
||||
}
|
||||
|
||||
public static void SetGrow(Layoutable layoutable, double value)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
layoutable.SetValue(GrowProperty, value);
|
||||
}
|
||||
|
||||
internal static double GetBaseLength(Layoutable layoutable)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
return layoutable.GetValue(BaseLengthProperty);
|
||||
}
|
||||
|
||||
internal static void SetBaseLength(Layoutable layoutable, double value)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
layoutable.SetValue(BaseLengthProperty, value);
|
||||
}
|
||||
|
||||
internal static double GetCurrentLength(Layoutable layoutable)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
return layoutable.GetValue(CurrentLengthProperty);
|
||||
}
|
||||
|
||||
internal static void SetCurrentLength(Layoutable layoutable, double value)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
layoutable.SetValue(CurrentLengthProperty, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public readonly struct FlexBasis : IEquatable<FlexBasis>
|
||||
{
|
||||
public double Value { get; }
|
||||
|
||||
public FlexBasisKind Kind { get; }
|
||||
|
||||
public FlexBasis(double value, FlexBasisKind kind)
|
||||
{
|
||||
if (value < 0 || double.IsNaN(value) || double.IsInfinity(value))
|
||||
throw new ArgumentException($"Invalid basis value: {value}", nameof(value));
|
||||
if (kind < FlexBasisKind.Auto || kind > FlexBasisKind.Relative)
|
||||
throw new ArgumentException($"Invalid basis kind: {kind}", nameof(kind));
|
||||
Value = value;
|
||||
Kind = kind;
|
||||
}
|
||||
|
||||
public FlexBasis(double value) : this(value, FlexBasisKind.Absolute) { }
|
||||
|
||||
public static FlexBasis Auto => new(0.0, FlexBasisKind.Auto);
|
||||
|
||||
public bool IsAuto => Kind == FlexBasisKind.Auto;
|
||||
|
||||
public bool IsAbsolute => Kind == FlexBasisKind.Absolute;
|
||||
|
||||
public bool IsRelative => Kind == FlexBasisKind.Relative;
|
||||
|
||||
[SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
|
||||
public bool Equals(FlexBasis other) =>
|
||||
(IsAuto && other.IsAuto) || (Value == other.Value && Kind == other.Kind);
|
||||
|
||||
public override bool Equals(object? obj) =>
|
||||
obj is FlexBasis other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
HashCode.Combine(Value, (int)Kind);
|
||||
|
||||
public static bool operator ==(FlexBasis left, FlexBasis right) =>
|
||||
left.Equals(right);
|
||||
|
||||
public static bool operator !=(FlexBasis left, FlexBasis right) =>
|
||||
!left.Equals(right);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Kind switch
|
||||
{
|
||||
FlexBasisKind.Auto => "Auto",
|
||||
FlexBasisKind.Absolute => FormattableString.Invariant($"{Value:G17}"),
|
||||
FlexBasisKind.Relative => FormattableString.Invariant($"{Value * 100:G17}%"),
|
||||
_ => throw new InvalidOperationException(),
|
||||
};
|
||||
}
|
||||
|
||||
public static FlexBasis Parse(string str)
|
||||
{
|
||||
return str.ToUpperInvariant() switch
|
||||
{
|
||||
"AUTO" => Auto,
|
||||
var s when s.EndsWith("%") => new FlexBasis(ParseDouble(s[..^1].Trim()) / 100, FlexBasisKind.Relative),
|
||||
_ => new FlexBasis(ParseDouble(str), FlexBasisKind.Absolute),
|
||||
};
|
||||
double ParseDouble(string s) => double.Parse(s, CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public enum FlexBasisKind
|
||||
{
|
||||
Auto,
|
||||
Absolute,
|
||||
Relative,
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public enum FlexDirection
|
||||
{
|
||||
Row,
|
||||
RowReverse,
|
||||
Column,
|
||||
ColumnReverse
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public sealed class FlexPanel : Panel
|
||||
{
|
||||
private static readonly Func<Layoutable, int> s_getOrder = x => x is { } y ? Flex.GetOrder(y) : 0;
|
||||
private static readonly Func<Layoutable, bool> s_isVisible = x => x.IsVisible;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Direction"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<FlexDirection> DirectionProperty =
|
||||
AvaloniaProperty.Register<FlexPanel, FlexDirection>(nameof(Direction));
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="JustifyContent"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<JustifyContent> JustifyContentProperty =
|
||||
AvaloniaProperty.Register<FlexPanel, JustifyContent>(nameof(JustifyContent));
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="AlignItems"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<AlignItems> AlignItemsProperty =
|
||||
AvaloniaProperty.Register<FlexPanel, AlignItems>(nameof(AlignItems));
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="AlignContent"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<AlignContent> AlignContentProperty =
|
||||
AvaloniaProperty.Register<FlexPanel, AlignContent>(nameof(AlignContent));
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Wrap"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<FlexWrap> WrapProperty =
|
||||
AvaloniaProperty.Register<FlexPanel, FlexWrap>(nameof(Wrap), FlexWrap.Wrap);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ColumnSpacing"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<double> ColumnSpacingProperty =
|
||||
AvaloniaProperty.Register<FlexPanel, double>(nameof(ColumnSpacing));
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="RowSpacing"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<double> RowSpacingProperty =
|
||||
AvaloniaProperty.Register<FlexPanel, double>(nameof(RowSpacing));
|
||||
|
||||
private FlexLayoutState? _state;
|
||||
|
||||
static FlexPanel()
|
||||
{
|
||||
AffectsMeasure<FlexPanel>(
|
||||
DirectionProperty,
|
||||
JustifyContentProperty,
|
||||
WrapProperty,
|
||||
ColumnSpacingProperty,
|
||||
RowSpacingProperty);
|
||||
|
||||
AffectsArrange<FlexPanel>(
|
||||
AlignItemsProperty,
|
||||
AlignContentProperty);
|
||||
|
||||
AffectsParentMeasure<FlexPanel>(
|
||||
HorizontalAlignmentProperty,
|
||||
VerticalAlignmentProperty,
|
||||
Flex.OrderProperty,
|
||||
Flex.BasisProperty,
|
||||
Flex.ShrinkProperty,
|
||||
Flex.GrowProperty);
|
||||
|
||||
AffectsParentArrange<FlexPanel>(
|
||||
Flex.AlignSelfProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flex direction
|
||||
/// </summary>
|
||||
public FlexDirection Direction
|
||||
{
|
||||
get => GetValue(DirectionProperty);
|
||||
set => SetValue(DirectionProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flex justify content mode
|
||||
/// </summary>
|
||||
public JustifyContent JustifyContent
|
||||
{
|
||||
get => GetValue(JustifyContentProperty);
|
||||
set => SetValue(JustifyContentProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flex align items mode
|
||||
/// </summary>
|
||||
public AlignItems AlignItems
|
||||
{
|
||||
get => GetValue(AlignItemsProperty);
|
||||
set => SetValue(AlignItemsProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flex align content mode
|
||||
/// </summary>
|
||||
public AlignContent AlignContent
|
||||
{
|
||||
get => GetValue(AlignContentProperty);
|
||||
set => SetValue(AlignContentProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flex wrap mode
|
||||
/// </summary>
|
||||
public FlexWrap Wrap
|
||||
{
|
||||
get => GetValue(WrapProperty);
|
||||
set => SetValue(WrapProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the column spacing
|
||||
/// </summary>
|
||||
public double ColumnSpacing
|
||||
{
|
||||
get => GetValue(ColumnSpacingProperty);
|
||||
set => SetValue(ColumnSpacingProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the row spacing
|
||||
/// </summary>
|
||||
public double RowSpacing
|
||||
{
|
||||
get => GetValue(RowSpacingProperty);
|
||||
set => SetValue(RowSpacingProperty, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
var children = (IReadOnlyList<Layoutable>)Children;
|
||||
|
||||
var isColumn = Direction is FlexDirection.Column or FlexDirection.ColumnReverse;
|
||||
|
||||
var max = Uv.FromSize(availableSize, isColumn);
|
||||
var spacing = Uv.FromSize(ColumnSpacing, RowSpacing, isColumn);
|
||||
|
||||
var (lineU, lineV, lineShrink, lineGrow, lineAutoMargins) = (0.0, 0.0, 0.0, 0.0, 0);
|
||||
var (childIndex, firstChildIndex, itemIndex, lineIndex) = (0, 0, 0, 0);
|
||||
|
||||
var lines = new List<FlexLine>();
|
||||
children = children.Where(s_isVisible).OrderBy(s_getOrder).ToArray();
|
||||
|
||||
foreach (var element in children)
|
||||
{
|
||||
var basis = Flex.GetBasis(element);
|
||||
var flexConstraint = basis.Kind switch
|
||||
{
|
||||
FlexBasisKind.Auto => max.U,
|
||||
FlexBasisKind.Absolute => basis.Value,
|
||||
FlexBasisKind.Relative => max.U * basis.Value / 100,
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
element.Measure(Uv.ToSize(max.WithU(flexConstraint), isColumn));
|
||||
|
||||
var size = Uv.FromSize(element.DesiredSize, isColumn);
|
||||
var flexLength = basis.Kind switch
|
||||
{
|
||||
FlexBasisKind.Auto => size.U,
|
||||
FlexBasisKind.Absolute or FlexBasisKind.Relative => Math.Max(size.U, flexConstraint),
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
size = size.WithU(flexLength);
|
||||
Flex.SetBaseLength(element, flexLength);
|
||||
Flex.SetCurrentLength(element, flexLength);
|
||||
|
||||
if (Wrap != FlexWrap.NoWrap && lineU + size.U + itemIndex * spacing.U > max.U)
|
||||
{
|
||||
lines.Add(new FlexLine(firstChildIndex, childIndex - 1,
|
||||
lineU, lineV, lineShrink, lineGrow, lineAutoMargins));
|
||||
(lineU, lineV, lineShrink, lineGrow, lineAutoMargins) = (0.0, 0.0, 0.0, 0.0, 0);
|
||||
firstChildIndex = childIndex;
|
||||
itemIndex = 0;
|
||||
lineIndex++;
|
||||
}
|
||||
|
||||
lineU += size.U;
|
||||
lineV = Math.Max(lineV, size.V);
|
||||
lineShrink += Flex.GetShrink(element);
|
||||
lineGrow += Flex.GetGrow(element);
|
||||
lineAutoMargins += GetItemAutoMargins(element, isColumn);
|
||||
itemIndex++;
|
||||
childIndex++;
|
||||
}
|
||||
|
||||
if (itemIndex != 0)
|
||||
{
|
||||
lines.Add(new FlexLine(firstChildIndex, firstChildIndex + itemIndex - 1,
|
||||
lineU, lineV, lineShrink, lineGrow, lineAutoMargins));
|
||||
}
|
||||
|
||||
var state = new FlexLayoutState(children, lines, Wrap);
|
||||
|
||||
var totalSpacingV = (lines.Count - 1) * spacing.V;
|
||||
var panelSizeU = lines.Count > 0 ? lines.Max(line => line.U + (line.Count - 1) * spacing.U) : 0.0;
|
||||
|
||||
// Reizing along main axis using grow and shrink factors can affect cross axis, so remeasure affected items and lines.
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var (itemsCount, totalSpacingU, totalU, freeU) = GetLineMeasureU(line, max.U, spacing.U);
|
||||
var (lineMult, autoMargins, remainingFreeU) = GetLineMultInfo(line, freeU);
|
||||
if (lineMult != 0.0 && remainingFreeU != 0.0)
|
||||
{
|
||||
foreach (var element in state.GetLineItems(line))
|
||||
{
|
||||
var baseLength = Flex.GetBaseLength(element);
|
||||
var mult = GetItemMult(element, freeU);
|
||||
if (mult != 0.0)
|
||||
{
|
||||
var length = Math.Max(0.0, baseLength + remainingFreeU * mult / lineMult);
|
||||
element.Measure(Uv.ToSize(max.WithU(length), isColumn));
|
||||
}
|
||||
}
|
||||
|
||||
line.V = state.GetLineItems(line).Max(i => Uv.FromSize(i.DesiredSize, isColumn).V);
|
||||
}
|
||||
}
|
||||
|
||||
_state = state;
|
||||
var totalLineV = lines.Sum(l => l.V);
|
||||
var panelSize = lines.Count == 0 ? default : new Uv(panelSizeU, totalLineV + totalSpacingV);
|
||||
return Uv.ToSize(panelSize, isColumn);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
var state = _state ?? throw new InvalidOperationException();
|
||||
|
||||
var isColumn = Direction is FlexDirection.Column or FlexDirection.ColumnReverse;
|
||||
var isReverse = Direction is FlexDirection.RowReverse or FlexDirection.ColumnReverse;
|
||||
|
||||
var panelSize = Uv.FromSize(finalSize, isColumn);
|
||||
var spacing = Uv.FromSize(ColumnSpacing, RowSpacing, isColumn);
|
||||
|
||||
var linesCount = state.Lines.Count;
|
||||
var totalLineV = state.Lines.Sum(s => s.V);
|
||||
var totalSpacingV = (linesCount - 1) * spacing.V;
|
||||
var totalV = totalLineV + totalSpacingV;
|
||||
var freeV = panelSize.V - totalV;
|
||||
|
||||
var alignContent = freeV >= 0.0 ? AlignContent : AlignContent switch
|
||||
{
|
||||
AlignContent.FlexStart or AlignContent.Stretch or AlignContent.SpaceBetween => AlignContent.FlexStart,
|
||||
AlignContent.Center or AlignContent.SpaceAround or AlignContent.SpaceEvenly => AlignContent.Center,
|
||||
AlignContent.FlexEnd => AlignContent.FlexEnd,
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
|
||||
var (spacingV, v) = alignContent switch
|
||||
{
|
||||
AlignContent.FlexStart => (spacing.V, 0.0),
|
||||
AlignContent.FlexEnd => (spacing.V, freeV),
|
||||
AlignContent.Center => (spacing.V, freeV / 2),
|
||||
AlignContent.Stretch => (spacing.V, 0.0),
|
||||
AlignContent.SpaceBetween => (spacing.V + freeV / (linesCount - 1), 0.0),
|
||||
AlignContent.SpaceAround => (spacing.V + freeV / linesCount, freeV / linesCount / 2),
|
||||
AlignContent.SpaceEvenly => (spacing.V + freeV / (linesCount + 1), freeV / (linesCount + 1)),
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
|
||||
var scaleV = alignContent == AlignContent.Stretch ? (panelSize.V - totalSpacingV) / totalLineV : 1.0;
|
||||
|
||||
foreach (var line in state.Lines)
|
||||
{
|
||||
var lineV = scaleV * line.V;
|
||||
var (itemsCount, totalSpacingU, totalU, freeU) = GetLineMeasureU(line, panelSize.U, spacing.U);
|
||||
var (lineMult, lineAutoMargins, remainingFreeU) = GetLineMultInfo(line, freeU);
|
||||
|
||||
var currentFreeU = remainingFreeU;
|
||||
if (lineMult != 0.0 && remainingFreeU != 0.0)
|
||||
{
|
||||
foreach (var element in state.GetLineItems(line))
|
||||
{
|
||||
var baseLength = Flex.GetBaseLength(element);
|
||||
var mult = GetItemMult(element, freeU);
|
||||
if (mult != 0.0)
|
||||
{
|
||||
var length = Math.Max(0.0, baseLength + remainingFreeU * mult / lineMult);
|
||||
Flex.SetCurrentLength(element, length);
|
||||
currentFreeU -= length - baseLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
remainingFreeU = currentFreeU;
|
||||
|
||||
if (lineAutoMargins != 0 && remainingFreeU != 0.0)
|
||||
{
|
||||
foreach (var element in state.GetLineItems(line))
|
||||
{
|
||||
var baseLength = Flex.GetCurrentLength(element);
|
||||
var autoMargins = GetItemAutoMargins(element, isColumn);
|
||||
if (autoMargins != 0)
|
||||
{
|
||||
var length = Math.Max(0.0, baseLength + remainingFreeU * autoMargins / lineAutoMargins);
|
||||
Flex.SetCurrentLength(element, length);
|
||||
currentFreeU -= length - baseLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
remainingFreeU = currentFreeU;
|
||||
|
||||
var (spacingU, u) = line.Grow > 0 ? (spacing.U, 0.0) : JustifyContent switch
|
||||
{
|
||||
JustifyContent.FlexStart => (spacing.U, 0.0),
|
||||
JustifyContent.FlexEnd => (spacing.U, remainingFreeU),
|
||||
JustifyContent.Center => (spacing.U, remainingFreeU / 2),
|
||||
JustifyContent.SpaceBetween => (spacing.U + remainingFreeU / (itemsCount - 1), 0.0),
|
||||
JustifyContent.SpaceAround => (spacing.U + remainingFreeU / itemsCount, remainingFreeU / itemsCount / 2),
|
||||
JustifyContent.SpaceEvenly => (spacing.U + remainingFreeU / (itemsCount + 1), remainingFreeU / (itemsCount + 1)),
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
|
||||
foreach (var element in state.GetLineItems(line))
|
||||
{
|
||||
var size = Uv.FromSize(element.DesiredSize, isColumn).WithU(Flex.GetCurrentLength(element));
|
||||
var align = Flex.GetAlignSelf(element) ?? AlignItems;
|
||||
|
||||
var positionV = align switch
|
||||
{
|
||||
AlignItems.FlexStart => v,
|
||||
AlignItems.FlexEnd => v + lineV - size.V,
|
||||
AlignItems.Center => v + (lineV - size.V) / 2,
|
||||
AlignItems.Stretch => v,
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
|
||||
size = size.WithV(align == AlignItems.Stretch ? lineV : size.V);
|
||||
var position = new Uv(isReverse ? panelSize.U - size.U - u : u, positionV);
|
||||
element.Arrange(new Rect(Uv.ToPoint(position, isColumn), Uv.ToSize(size, isColumn)));
|
||||
|
||||
u += size.U + spacingU;
|
||||
}
|
||||
|
||||
v += lineV + spacingV;
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
private static (int ItemsCount, double TotalSpacingU, double TotalU, double FreeU) GetLineMeasureU(
|
||||
FlexLine line, double panelSizeU, double spacingU)
|
||||
{
|
||||
var itemsCount = line.Count;
|
||||
var totalSpacingU = (itemsCount - 1) * spacingU;
|
||||
var totalU = line.U + totalSpacingU;
|
||||
var freeU = panelSizeU - totalU;
|
||||
return (itemsCount, totalSpacingU, totalU, freeU);
|
||||
}
|
||||
|
||||
private static (double LineMult, double LineAutoMargins, double RemainingFreeU) GetLineMultInfo(FlexLine line, double freeU)
|
||||
{
|
||||
var lineMult = freeU switch
|
||||
{
|
||||
< 0 => line.Shrink,
|
||||
> 0 => line.Grow,
|
||||
_ => 0.0,
|
||||
};
|
||||
// https://www.w3.org/TR/css-flexbox-1/#remaining-free-space
|
||||
// Sum of flex factors less than 1 reduces remaining free space to be distributed.
|
||||
return lineMult is > 0 and < 1
|
||||
? (lineMult, line.AutoMargins, freeU * lineMult)
|
||||
: (lineMult, line.AutoMargins, freeU);
|
||||
}
|
||||
|
||||
private static double GetItemMult(Layoutable element, double freeU)
|
||||
{
|
||||
var mult = freeU switch
|
||||
{
|
||||
< 0 => Flex.GetShrink(element),
|
||||
> 0 => Flex.GetGrow(element),
|
||||
_ => 0.0,
|
||||
};
|
||||
return mult;
|
||||
}
|
||||
|
||||
private static int GetItemAutoMargins(Layoutable element, bool isColumn)
|
||||
{
|
||||
return isColumn
|
||||
? element.VerticalAlignment switch
|
||||
{
|
||||
VerticalAlignment.Stretch => 0,
|
||||
VerticalAlignment.Top or VerticalAlignment.Bottom => 1,
|
||||
VerticalAlignment.Center => 2,
|
||||
_ => throw new InvalidOperationException()
|
||||
}
|
||||
: element.HorizontalAlignment switch
|
||||
{
|
||||
HorizontalAlignment.Stretch => 0,
|
||||
HorizontalAlignment.Left or HorizontalAlignment.Right => 1,
|
||||
HorizontalAlignment.Center => 2,
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
}
|
||||
|
||||
private readonly struct FlexLayoutState
|
||||
{
|
||||
private readonly IReadOnlyList<Layoutable> _children;
|
||||
|
||||
public IReadOnlyList<FlexLine> Lines { get; }
|
||||
|
||||
public FlexLayoutState(IReadOnlyList<Layoutable> children, List<FlexLine> lines, FlexWrap wrap)
|
||||
{
|
||||
if (wrap == FlexWrap.WrapReverse)
|
||||
{
|
||||
lines.Reverse();
|
||||
}
|
||||
_children = children;
|
||||
Lines = lines;
|
||||
}
|
||||
|
||||
public IEnumerable<Layoutable> GetLineItems(FlexLine line)
|
||||
{
|
||||
for (var i = line.First; i <= line.Last; i++)
|
||||
yield return _children[i];
|
||||
}
|
||||
}
|
||||
|
||||
private class FlexLine
|
||||
{
|
||||
public FlexLine(int first, int last, double u, double v, double shrink, double grow, int autoMargins)
|
||||
{
|
||||
First = first;
|
||||
Last = last;
|
||||
U = u;
|
||||
V = v;
|
||||
Shrink = shrink;
|
||||
Grow = grow;
|
||||
AutoMargins = autoMargins;
|
||||
}
|
||||
|
||||
/// <summary>First item index.</summary>
|
||||
public int First { get; }
|
||||
|
||||
/// <summary>Last item index.</summary>
|
||||
public int Last { get; }
|
||||
|
||||
/// <summary>Sum of main sizes of items.</summary>
|
||||
public double U { get; }
|
||||
|
||||
/// <summary>Max of cross sizes of items.</summary>
|
||||
public double V { get; set; }
|
||||
|
||||
/// <summary>Sum of shrink factors of flexible items.</summary>
|
||||
public double Shrink { get; }
|
||||
|
||||
/// <summary>Sum of grow factors of flexible items.</summary>
|
||||
public double Grow { get; }
|
||||
|
||||
/// <summary>Number of "auto margins" along main axis.</summary>
|
||||
public int AutoMargins { get; }
|
||||
|
||||
/// <summary>Number of items.</summary>
|
||||
public int Count => Last - First + 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public enum FlexWrap
|
||||
{
|
||||
NoWrap,
|
||||
Wrap,
|
||||
WrapReverse
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public enum JustifyContent
|
||||
{
|
||||
FlexStart,
|
||||
FlexEnd,
|
||||
Center,
|
||||
SpaceBetween,
|
||||
SpaceAround,
|
||||
SpaceEvenly
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
internal readonly struct Uv(double u, double v)
|
||||
{
|
||||
public double U { get; } = u;
|
||||
|
||||
public double V { get; } = v;
|
||||
|
||||
public static Uv FromSize(double width, double height, bool swap) =>
|
||||
new Uv(swap ? height : width, swap ? width : height);
|
||||
|
||||
public static Uv FromSize(Size size, bool swap) =>
|
||||
FromSize(size.Width, size.Height, swap);
|
||||
|
||||
public static Point ToPoint(Uv uv, bool swap) =>
|
||||
new Point(swap ? uv.V : uv.U, swap ? uv.U : uv.V);
|
||||
|
||||
public static Size ToSize(Uv uv, bool swap) =>
|
||||
new Size(swap ? uv.V : uv.U, swap ? uv.U : uv.V);
|
||||
|
||||
public Uv WithU(double u) =>
|
||||
new Uv(u, V);
|
||||
|
||||
public Uv WithV(double v) =>
|
||||
new Uv(U, v);
|
||||
|
||||
public override string ToString() =>
|
||||
$"U: {U}, V: {V}";
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public class OptionsDisplayItem : TemplatedControl
|
||||
{
|
||||
public static readonly StyledProperty<string> HeaderProperty =
|
||||
AvaloniaProperty.Register<OptionsDisplayItem, string>(nameof(Header));
|
||||
public static readonly StyledProperty<object?> HeaderProperty =
|
||||
AvaloniaProperty.Register<OptionsDisplayItem, object?>(nameof(Header));
|
||||
|
||||
public static readonly StyledProperty<string> DescriptionProperty =
|
||||
AvaloniaProperty.Register<OptionsDisplayItem, string>(nameof(Description));
|
||||
public static readonly StyledProperty<object?> DescriptionProperty =
|
||||
AvaloniaProperty.Register<OptionsDisplayItem, object?>(nameof(Description));
|
||||
|
||||
public static readonly StyledProperty<FAIconElement> IconProperty =
|
||||
AvaloniaProperty.Register<OptionsDisplayItem, FAIconElement>(nameof(Icon));
|
||||
public static readonly StyledProperty<object?> IconProperty =
|
||||
AvaloniaProperty.Register<OptionsDisplayItem, object?>(nameof(Icon));
|
||||
|
||||
public static readonly StyledProperty<bool> NavigatesProperty =
|
||||
AvaloniaProperty.Register<OptionsDisplayItem, bool>(nameof(Navigates));
|
||||
@@ -29,19 +31,19 @@ public class OptionsDisplayItem : TemplatedControl
|
||||
public static readonly StyledProperty<ICommand> NavigationCommandProperty =
|
||||
AvaloniaProperty.Register<OptionsDisplayItem, ICommand>(nameof(NavigationCommand));
|
||||
|
||||
public string Header
|
||||
public object? Header
|
||||
{
|
||||
get => GetValue(HeaderProperty);
|
||||
set => SetValue(HeaderProperty, value);
|
||||
}
|
||||
|
||||
public string Description
|
||||
public object? Description
|
||||
{
|
||||
get => GetValue(DescriptionProperty);
|
||||
set => SetValue(DescriptionProperty, value);
|
||||
}
|
||||
|
||||
public FAIconElement Icon
|
||||
public object? Icon
|
||||
{
|
||||
get => GetValue(IconProperty);
|
||||
set => SetValue(IconProperty, value);
|
||||
@@ -112,7 +114,7 @@ public class OptionsDisplayItem : TemplatedControl
|
||||
}
|
||||
else if (change.Property == DescriptionProperty)
|
||||
{
|
||||
PseudoClasses.Set(":desc", !string.IsNullOrEmpty(change.GetNewValue<string>()));
|
||||
PseudoClasses.Set(":desc", change.GetNewValue<object?>() != null);
|
||||
}
|
||||
else if (change.Property == IsExpandedProperty)
|
||||
PseudoClasses.Set(":expanded", change.GetNewValue<bool>());
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</UserControl.Styles>
|
||||
<Border
|
||||
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
BorderThickness="0"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="8">
|
||||
<Panel>
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace BD.WTTS.UI.Views.Controls
|
||||
/// Defines the <see cref="AutoScrollInterval"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<int> AutoScrollIntervalProperty =
|
||||
AvaloniaProperty.Register<CarouselBanner, int>(nameof(AutoScrollInterval), 8000);
|
||||
AvaloniaProperty.Register<CarouselBanner, int>(nameof(AutoScrollInterval), 6000);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Avalonia.Controls.ItemsControl.Items property.
|
||||
@@ -29,6 +29,12 @@ namespace BD.WTTS.UI.Views.Controls
|
||||
public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty =
|
||||
Carousel.ItemTemplateProperty.AddOwner<CarouselBanner>();
|
||||
|
||||
///// <summary>
|
||||
///// Defines the <see cref="GroupView"/> property.
|
||||
///// </summary>
|
||||
//public static readonly StyledProperty<bool> GroupViewProperty =
|
||||
// AvaloniaProperty.Register<CarouselBanner, bool>(nameof(GroupView), true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the items to display.
|
||||
/// </summary>
|
||||
@@ -65,23 +71,32 @@ namespace BD.WTTS.UI.Views.Controls
|
||||
set => SetValue(AutoScrollIntervalProperty, value);
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// GroupView
|
||||
///// </summary>
|
||||
//public bool GroupView
|
||||
//{
|
||||
// get => GetValue(GroupViewProperty);
|
||||
// set => SetValue(GroupViewProperty, value);
|
||||
//}
|
||||
|
||||
private Timer? _timer;
|
||||
|
||||
public CarouselBanner()
|
||||
{
|
||||
InitializeComponent();
|
||||
Left.Click += (s, e) => SwiperPrevious();
|
||||
Right.Click += (s, e) => SwiperNext();
|
||||
Left.Command = ReactiveCommand.Create(SwiperPrevious);
|
||||
Right.Command = ReactiveCommand.Create(SwiperNext);
|
||||
|
||||
//CarouselControl[!Carousel.Items] = this[!Items];
|
||||
CarouselControl[!Carousel.ItemsSourceProperty] = this[!ItemsSourceProperty];
|
||||
CarouselControl[!Carousel.ItemTemplateProperty] = this[!ItemTemplateProperty];
|
||||
|
||||
this.GetObservable(ItemsSourceProperty)
|
||||
CarouselControl.GetObservable(Carousel.ItemCountProperty)
|
||||
.Subscribe(_ => SwipersLoad());
|
||||
|
||||
CarouselControl.GetObservable(Carousel.SelectedIndexProperty)
|
||||
.Subscribe(x => SwipersLoad());
|
||||
.Subscribe(_ => SwipersLoad());
|
||||
|
||||
this.GetObservable(AutoScrollProperty)
|
||||
.Subscribe(x =>
|
||||
@@ -107,6 +122,40 @@ namespace BD.WTTS.UI.Views.Controls
|
||||
});
|
||||
}
|
||||
|
||||
//protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
//{
|
||||
// if (change.Property == ItemsSourceProperty)
|
||||
// {
|
||||
// SwipersLoad();
|
||||
// }
|
||||
// else if (change.Property == AutoScrollProperty)
|
||||
// {
|
||||
// if (change.NewValue is bool x)
|
||||
// {
|
||||
// if (x && _timer == null)
|
||||
// {
|
||||
// _timer = new Timer(_ =>
|
||||
// {
|
||||
// if (!this.IsPointerOver)
|
||||
// {
|
||||
// Dispatcher.UIThread.Post(SwiperNext, DispatcherPriority.Background);
|
||||
// }
|
||||
// }, nameof(AutoScroll), AutoScrollInterval, AutoScrollInterval);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (_timer != null)
|
||||
// {
|
||||
// _timer.Dispose();
|
||||
// _timer = null;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// base.OnPropertyChanged(change);
|
||||
//}
|
||||
|
||||
private void CarouselBannerIndexButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Button button && button.Tag is int index)
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Layout;
|
||||
using FluentAvalonia.Core;
|
||||
using System.Reactive;
|
||||
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public class CarouselItems : ItemsControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="AutoScroll"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<bool> AutoScrollProperty =
|
||||
AvaloniaProperty.Register<CarouselItems, bool>(nameof(AutoScroll), true);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="AutoScrollInterval"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<int> AutoScrollIntervalProperty =
|
||||
AvaloniaProperty.Register<CarouselItems, int>(nameof(AutoScrollInterval), 6000);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ItemsPerPage"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<int> ItemsPerPageProperty =
|
||||
AvaloniaProperty.Register<CarouselItems, int>(nameof(ItemsPerPage), 4);
|
||||
|
||||
/// <summary>
|
||||
/// AutoScroll
|
||||
/// </summary>
|
||||
public bool AutoScroll
|
||||
{
|
||||
get => GetValue(AutoScrollProperty);
|
||||
set => SetValue(AutoScrollProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自动播放间隔时间(单位毫秒)
|
||||
/// </summary>
|
||||
public int AutoScrollInterval
|
||||
{
|
||||
get => GetValue(AutoScrollIntervalProperty);
|
||||
set => SetValue(AutoScrollIntervalProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ItemsPerPage
|
||||
/// </summary>
|
||||
public int ItemsPerPage
|
||||
{
|
||||
get => GetValue(ItemsPerPageProperty);
|
||||
set => SetValue(ItemsPerPageProperty, value);
|
||||
}
|
||||
|
||||
Carousel? _carouselControl;
|
||||
Button? _leftButton;
|
||||
Button? _rightButton;
|
||||
ItemsRepeater? _swiper;
|
||||
Timer? _timer;
|
||||
|
||||
public ICommand? CarouselBannerIndexCommand { get; }
|
||||
|
||||
public CarouselItems()
|
||||
{
|
||||
CarouselBannerIndexCommand = ReactiveCommand.Create<int>(CarouselBannerIndex);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
|
||||
_carouselControl = e.NameScope.Find<Carousel>("CarouselControl");
|
||||
_leftButton = e.NameScope.Find<Button>("Left");
|
||||
_rightButton = e.NameScope.Find<Button>("Right");
|
||||
_swiper = e.NameScope.Find<ItemsRepeater>("Swiper");
|
||||
|
||||
if (_leftButton != null)
|
||||
{
|
||||
_leftButton.Command = ReactiveCommand.Create(SwiperPrevious);
|
||||
}
|
||||
if (_rightButton != null)
|
||||
{
|
||||
_rightButton.Command = ReactiveCommand.Create(SwiperNext);
|
||||
}
|
||||
|
||||
if (_carouselControl != null)
|
||||
{
|
||||
_carouselControl.GetObservable(Carousel.ItemCountProperty)
|
||||
.Subscribe(_ => SwipersLoad());
|
||||
|
||||
_carouselControl.GetObservable(Carousel.SelectedIndexProperty)
|
||||
.Subscribe(_ => SwipersLoad());
|
||||
|
||||
RefreshItemsSource();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
|
||||
if (change.Property == ItemCountProperty)
|
||||
{
|
||||
RefreshItemsSource();
|
||||
}
|
||||
else if (change.Property == ItemsPerPageProperty)
|
||||
{
|
||||
RefreshItemsSource();
|
||||
}
|
||||
else if (change.Property == AutoScrollProperty)
|
||||
{
|
||||
var x = change.GetNewValue<bool>();
|
||||
if (x && _timer == null)
|
||||
{
|
||||
_timer = new Timer(_ =>
|
||||
{
|
||||
if (!this.IsPointerOver)
|
||||
{
|
||||
Dispatcher.UIThread.Post(SwiperNext, DispatcherPriority.Background);
|
||||
}
|
||||
}, nameof(AutoScroll), AutoScrollInterval, AutoScrollInterval);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_timer != null)
|
||||
{
|
||||
_timer.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPointerEntered(PointerEventArgs e)
|
||||
{
|
||||
base.OnPointerEntered(e);
|
||||
|
||||
if (_swiper == null || _leftButton == null || _rightButton == null)
|
||||
return;
|
||||
|
||||
_leftButton.IsVisible = _rightButton.IsVisible = _swiper.IsVisible;
|
||||
}
|
||||
|
||||
protected override void OnPointerExited(PointerEventArgs e)
|
||||
{
|
||||
base.OnPointerExited(e);
|
||||
|
||||
if (_swiper == null || _leftButton == null || _rightButton == null)
|
||||
return;
|
||||
|
||||
_leftButton.IsVisible = _rightButton.IsVisible = false;
|
||||
}
|
||||
|
||||
void RefreshItemsSource()
|
||||
{
|
||||
if (_carouselControl != null)
|
||||
{
|
||||
var items = ItemsSource?.Batch(ItemsPerPage);
|
||||
_carouselControl.ItemsSource = items;
|
||||
}
|
||||
}
|
||||
|
||||
void CarouselBannerIndex(int index)
|
||||
{
|
||||
if (_carouselControl != null)
|
||||
_carouselControl.SelectedIndex = index;
|
||||
}
|
||||
|
||||
void SwipersLoad()
|
||||
{
|
||||
if (_carouselControl == null || _swiper == null || _leftButton == null || _rightButton == null)
|
||||
return;
|
||||
|
||||
if (_carouselControl.ItemCount <= 0)
|
||||
{
|
||||
_swiper.ItemsSource = null;
|
||||
return;
|
||||
}
|
||||
if (_carouselControl.ItemCount == 1)
|
||||
{
|
||||
_swiper.IsVisible = false;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
_swiper.IsVisible = true;
|
||||
var arr = new Dictionary<int, string>();
|
||||
for (var i = 0; i < _carouselControl.ItemCount; i++)
|
||||
{
|
||||
arr.Add(i, "#ADADAD");
|
||||
}
|
||||
var index = _carouselControl.SelectedIndex < 0 ? 0 : _carouselControl.SelectedIndex;
|
||||
arr[index] = "#FFFFFF";
|
||||
|
||||
_swiper.ItemsSource = arr;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 滑动到下一个
|
||||
/// </summary>
|
||||
public void SwiperNext()
|
||||
{
|
||||
if (_carouselControl == null || _carouselControl.ItemCount < 1)
|
||||
return;
|
||||
|
||||
if (_carouselControl.SelectedIndex < _carouselControl.ItemCount - 1)
|
||||
{
|
||||
_carouselControl.Next();
|
||||
}
|
||||
else
|
||||
{
|
||||
_carouselControl.SelectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 滑动到上一个
|
||||
/// </summary>
|
||||
public void SwiperPrevious()
|
||||
{
|
||||
if (_carouselControl == null || _carouselControl.ItemCount < 1)
|
||||
return;
|
||||
|
||||
if (_carouselControl.SelectedIndex > 0)
|
||||
{
|
||||
_carouselControl.Previous();
|
||||
}
|
||||
else
|
||||
{
|
||||
_carouselControl.SelectedIndex = _carouselControl.ItemCount - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,25 +2,50 @@ using static System.Math;
|
||||
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// 固定的 WrapPanel
|
||||
/// </summary>
|
||||
public class FixedWrapPanel : Panel, INavigableContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ItemsPerLine"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<int> ItemsPerLineProperty =
|
||||
AvaloniaProperty.Register<FixedWrapPanel, int>(nameof(ItemsPerLine), 3);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Spacing"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<double> SpacingProperty =
|
||||
AvaloniaProperty.Register<FixedWrapPanel, double>(nameof(Spacing), 0);
|
||||
|
||||
static FixedWrapPanel()
|
||||
{
|
||||
AffectsMeasure<FixedWrapPanel>(ItemsPerLineProperty);
|
||||
}
|
||||
|
||||
public double ItemsPerLine
|
||||
/// <summary>
|
||||
/// ItemsPerLine
|
||||
/// </summary>
|
||||
public int ItemsPerLine
|
||||
{
|
||||
get => GetValue(ItemsPerLineProperty);
|
||||
set => SetValue(ItemsPerLineProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spacing
|
||||
/// </summary>
|
||||
public double Spacing
|
||||
{
|
||||
get => GetValue(SpacingProperty);
|
||||
set => SetValue(SpacingProperty, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement? from, bool wrap)
|
||||
{
|
||||
int index = from is not null ? Children.IndexOf((Control)from) : -1;
|
||||
int index = from is Control control ? Children.IndexOf(control) : -1;
|
||||
switch (direction)
|
||||
{
|
||||
case NavigationDirection.First:
|
||||
@@ -57,13 +82,14 @@ public class FixedWrapPanel : Panel, INavigableContainer
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Size MeasureOverride(Size constraint)
|
||||
{
|
||||
double itemWidth = constraint.Width / ItemsPerLine;
|
||||
double itemWidth = (constraint.Width - (Spacing * (ItemsPerLine - 1))) / ItemsPerLine;
|
||||
MutableSize currentLineSize = default;
|
||||
MutableSize panelSize = default;
|
||||
Size lineConstraint = new(constraint.Width, constraint.Height);
|
||||
Size childConstraint = new(itemWidth, constraint.Height);
|
||||
Size childConstraint = new(itemWidth - Spacing, constraint.Height);
|
||||
|
||||
for (int i = 0, count = Children.Count; i < count; i++)
|
||||
{
|
||||
@@ -76,7 +102,7 @@ public class FixedWrapPanel : Panel, INavigableContainer
|
||||
child.Measure(childConstraint);
|
||||
Size childSize = new(itemWidth, child.DesiredSize.Height);
|
||||
|
||||
if (MathUtilities.GreaterThan(currentLineSize.Width + childSize.Width, lineConstraint.Width))
|
||||
if (MathUtilities.GreaterThan(currentLineSize.Width + childSize.Width + Spacing, lineConstraint.Width))
|
||||
{
|
||||
// Need to switch to another line
|
||||
panelSize.Width = Max(currentLineSize.Width, panelSize.Width);
|
||||
@@ -86,21 +112,25 @@ public class FixedWrapPanel : Panel, INavigableContainer
|
||||
else
|
||||
{
|
||||
// Continue to accumulate a line
|
||||
currentLineSize.Width += childSize.Width;
|
||||
currentLineSize.Width += childSize.Width + Spacing;
|
||||
currentLineSize.Height = Max(childSize.Height, currentLineSize.Height);
|
||||
}
|
||||
}
|
||||
|
||||
// The last line size, if any should be added
|
||||
panelSize.Width = Max(currentLineSize.Width, panelSize.Width);
|
||||
panelSize.Height += currentLineSize.Height;
|
||||
panelSize.Height = currentLineSize.Height;
|
||||
|
||||
if (double.IsInfinity(panelSize.Width))
|
||||
panelSize.Width = 0;
|
||||
|
||||
return panelSize.ToSize();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
double itemWidth = finalSize.Width / ItemsPerLine;
|
||||
double itemWidth = (finalSize.Width - (Spacing * (ItemsPerLine - 1))) / ItemsPerLine;
|
||||
int firstInLine = 0;
|
||||
double accumulatedHeight = 0;
|
||||
var currentLineSize = default(MutableSize);
|
||||
@@ -139,7 +169,7 @@ public class FixedWrapPanel : Panel, INavigableContainer
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
private void ArrangeLine(double y, double height, int start, int end, double width)
|
||||
void ArrangeLine(double y, double height, int start, int end, double width)
|
||||
{
|
||||
double x = 0;
|
||||
for (int i = start; i < end; i++)
|
||||
@@ -150,12 +180,20 @@ public class FixedWrapPanel : Panel, INavigableContainer
|
||||
continue;
|
||||
}
|
||||
|
||||
child.Arrange(new Rect(x, y, width, height));
|
||||
x += width;
|
||||
if (i < end - 1)
|
||||
{
|
||||
child.Arrange(new Rect(x, y, width - Spacing, height));
|
||||
x += width + Spacing;
|
||||
}
|
||||
else
|
||||
{
|
||||
child.Arrange(new Rect(x, y, width, height));
|
||||
x += width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct MutableSize
|
||||
record struct MutableSize
|
||||
{
|
||||
internal MutableSize(double width, double height)
|
||||
{
|
||||
@@ -172,7 +210,7 @@ public class FixedWrapPanel : Panel, INavigableContainer
|
||||
internal double Width;
|
||||
internal double Height;
|
||||
|
||||
internal Size ToSize()
|
||||
internal readonly Size ToSize()
|
||||
{
|
||||
return new Size(Width, Height);
|
||||
}
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
<StackPanel Spacing="{StaticResource SettingsSpacing}">
|
||||
<TextBlock
|
||||
Margin="0,0,0,5"
|
||||
Text="{ReflectionBinding Path=Res.About,
|
||||
Mode=OneWay,
|
||||
Source={x:Static s:ResourceService.Current}}"
|
||||
Text="{Binding Path=Res.About, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Theme="{StaticResource SubtitleTextBlockStyle}" />
|
||||
|
||||
<spp:OptionsDisplayItem
|
||||
@@ -140,9 +138,7 @@
|
||||
Margin="12,4,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Text="{ReflectionBinding Path=Res.Link,
|
||||
Mode=OneWay,
|
||||
Source={x:Static s:ResourceService.Current}}" />
|
||||
Text="{Binding Path=Res.Link, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ItemsRepeater Margin="0,0,6,6" ItemsSource="{Binding Hyperlinks, Mode=OneTime}">
|
||||
<ItemsRepeater.Layout>
|
||||
<UniformGridLayout MinItemWidth="166" />
|
||||
@@ -165,9 +161,7 @@
|
||||
Margin="12,4,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Text="{ReflectionBinding Path=Res.OpenSourceLibrary,
|
||||
Mode=OneWay,
|
||||
Source={x:Static s:ResourceService.Current}}" />
|
||||
Text="{Binding Path=Res.OpenSourceLibrary, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ItemsRepeater Margin="0,0,6,6" ItemsSource="{Binding OSL, Mode=OneTime}">
|
||||
<ItemsRepeater.Layout>
|
||||
<UniformGridLayout MinItemWidth="290" />
|
||||
|
||||
@@ -8,7 +8,8 @@ public partial class AboutPage : ReactiveUserControl<AboutPageViewModel>
|
||||
public AboutPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.SetViewModel<AboutPageViewModel>(false);
|
||||
DataContext = new AboutPageViewModel();
|
||||
this.SetViewModel<AboutPageViewModel>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
</spp:PageBase.PreviewImage>
|
||||
<spp:PageBase.TabContent>
|
||||
<TabStrip
|
||||
Name="TabSelect"
|
||||
Margin="4"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
@@ -110,19 +111,45 @@
|
||||
</DropDownButton>
|
||||
</StackPanel>
|
||||
</spp:PageBase.ActionContent>
|
||||
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}" CornerRadius="{DynamicResource ControlCornerRadius}">
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
<TextBox
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
Classes="readonlybox"
|
||||
Text="{Binding DebugString}"
|
||||
TextAlignment="Start"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBox
|
||||
Name="CommandTextBox"
|
||||
Grid.Row="1"
|
||||
Watermark="{Binding Path=Res.Debug_InstructionPlaceholder, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Carousel SelectedIndex="{Binding #TabSelect.SelectedIndex}">
|
||||
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}" CornerRadius="{DynamicResource ControlCornerRadius}">
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
<TextBox
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
Classes="readonlybox"
|
||||
Text="{Binding DebugString}"
|
||||
TextAlignment="Start"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBox
|
||||
Name="CommandTextBox"
|
||||
Grid.Row="1"
|
||||
Watermark="{Binding Path=Res.Debug_InstructionPlaceholder, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Panel>
|
||||
<TextBlock Text="Test Control" />
|
||||
<ItemsControl>
|
||||
<ItemsControl.Items>
|
||||
<Border Height="30" Background="Red" />
|
||||
<Border Background="Blue" />
|
||||
<Border Background="Yellow" />
|
||||
<Border Height="30" Background="Yellow" />
|
||||
<Border Background="Red" />
|
||||
<Border Background="Blue" />
|
||||
</ItemsControl.Items>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<spp:FixedWrapPanel ItemsPerLine="3" Spacing="5" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</Panel>
|
||||
|
||||
<Panel>
|
||||
<TextBlock Text="Test Control" />
|
||||
</Panel>
|
||||
</Carousel>
|
||||
</spp:PageBase>
|
||||
|
||||
@@ -7,7 +7,7 @@ public partial class DebugPage : PageBase<DebugPageViewModel>
|
||||
public DebugPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.SetViewModel<DebugPageViewModel>(false);
|
||||
this.SetViewModel<DebugPageViewModel>();
|
||||
|
||||
CommandTextBox.KeyUp += (s, e) =>
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
x:Class="BD.WTTS.UI.Views.Pages.HomePage"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:col="using:System.Collections.Generic"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:s="https://steampp.net/services"
|
||||
@@ -125,6 +126,7 @@
|
||||
|
||||
<Grid Margin="0,-10,0,0" RowDefinitions="Auto,*">
|
||||
<spp:CarouselBanner
|
||||
MinHeight="350"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
ItemsSource="{Binding NavigationBanners}">
|
||||
@@ -148,7 +150,7 @@
|
||||
</Border>
|
||||
</spp:AppItem.Status>
|
||||
<spp:AppItem.Image>
|
||||
<spp:Image2 Source="{Binding ImageUrl}" />
|
||||
<spp:Image2 EnableCancelToken="False" Source="{Binding ImageUrl}" />
|
||||
</spp:AppItem.Image>
|
||||
</spp:AppItem>
|
||||
</DataTemplate>
|
||||
@@ -227,31 +229,34 @@
|
||||
DataContext="{Binding Articles[2]}" />
|
||||
</Grid>
|
||||
|
||||
<!--<ItemsControl ItemsSource="{Binding Articles}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Grid ColumnDefinitions="*,*,*" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="spp:ArticleItemDTO">
|
||||
<spp:AppItem
|
||||
Title="{Binding Title}"
|
||||
Classes="Vertical Scale"
|
||||
Description="{Binding Introduction}"
|
||||
Tags="{Binding AuthorName}">
|
||||
<spp:AppItem.Image>
|
||||
<spp:Image2 RenderOptions.BitmapInterpolationMode="HighQuality" Source="{Binding CoverUrl}" />
|
||||
</spp:AppItem.Image>
|
||||
<spp:AppItem.Status>
|
||||
<Border Classes="Status">
|
||||
<TextBlock Text="{Binding CreationTime, StringFormat={}{yyyy-MM-dd}}" />
|
||||
</Border>
|
||||
</spp:AppItem.Status>
|
||||
</spp:AppItem>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>-->
|
||||
<!--<spp:CarouselItems ItemsPerPage="3" ItemsSource="{Binding Articles}">
|
||||
<spp:CarouselItems.ItemTemplate>
|
||||
<DataTemplate x:DataType="spp:ArticleItemDTO">
|
||||
<spp:AppItem
|
||||
Title="{Binding Title}"
|
||||
Classes="Vertical Scale"
|
||||
ClickCommand="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
Cursor="Hand"
|
||||
Description="{Binding Introduction}"
|
||||
Tags="{Binding AuthorName}">
|
||||
<spp:AppItem.ClickCommandParameter>
|
||||
<MultiBinding Converter="{StaticResource StringFormatConverter}">
|
||||
<CompiledBinding Path="." Source="{x:Static spp:HomePageViewModel.OfficialWebsite_Article_Detail_}" />
|
||||
<CompiledBinding Path="Id" />
|
||||
</MultiBinding>
|
||||
</spp:AppItem.ClickCommandParameter>
|
||||
<spp:AppItem.Image>
|
||||
<spp:Image2 RenderOptions.BitmapInterpolationMode="HighQuality" Source="{Binding CoverUrl}" />
|
||||
</spp:AppItem.Image>
|
||||
<spp:AppItem.Status>
|
||||
<Border Classes="Status">
|
||||
<TextBlock Text="{Binding CreationTime, StringFormat={}{0:yyyy-MM-dd}}" />
|
||||
</Border>
|
||||
</spp:AppItem.Status>
|
||||
</spp:AppItem>
|
||||
</DataTemplate>
|
||||
</spp:CarouselItems.ItemTemplate>
|
||||
</spp:CarouselItems>-->
|
||||
|
||||
<DockPanel>
|
||||
<TextBlock
|
||||
@@ -268,7 +273,172 @@
|
||||
Theme="{StaticResource HyperlinkButton}" />
|
||||
</DockPanel>
|
||||
|
||||
<ScrollViewer
|
||||
<spp:CarouselItems
|
||||
MinHeight="250"
|
||||
ItemsPerPage="4"
|
||||
ItemsSource="{Binding Shops}">
|
||||
<spp:CarouselItems.ItemTemplate>
|
||||
<DataTemplate x:DataType="spp:ShopRecommendGoodItem">
|
||||
<Border spp:Animations.EnableAnimations="True">
|
||||
<spp:AppItem
|
||||
Title="{Binding Name}"
|
||||
MaxWidth="260"
|
||||
Classes="Vertical Scale"
|
||||
ClickCommand="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
Cursor="Hand"
|
||||
Tags="{Binding GoodsAllCategorysText}"
|
||||
ToolTip.Tip="{Binding Brief}">
|
||||
<spp:AppItem.ClickCommandParameter>
|
||||
<MultiBinding Converter="{StaticResource StringFormatConverter}">
|
||||
<CompiledBinding Path="." Source="{x:Static spp:HomePageViewModel.WattGame_Goods_Detail_}" />
|
||||
<CompiledBinding Path="Id" />
|
||||
</MultiBinding>
|
||||
</spp:AppItem.ClickCommandParameter>
|
||||
<spp:AppItem.Image>
|
||||
<spp:Image2
|
||||
EnableCancelToken="False"
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality"
|
||||
Source="{Binding Image}" />
|
||||
</spp:AppItem.Image>
|
||||
<spp:AppItem.Status>
|
||||
<Border Classes="Status">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
IsVisible="{Binding !!DiscountValue}"
|
||||
Text="{Binding Mktprice, Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static spp:CurrencyCode.CNY}}"
|
||||
TextDecorations="Strikethrough" />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Text="{Binding Price, Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static spp:CurrencyCode.CNY}}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</spp:AppItem.Status>
|
||||
<spp:AppItem.ActionButton>
|
||||
<Border
|
||||
Height="24"
|
||||
Padding="8,0"
|
||||
Background="#4c6b22"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="4"
|
||||
IsVisible="{Binding !!DiscountValue}">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Foreground="#beee11"
|
||||
Text="{Binding DiscountValue, StringFormat={}-{0}%}" />
|
||||
</Border>
|
||||
</spp:AppItem.ActionButton>
|
||||
</spp:AppItem>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</spp:CarouselItems.ItemTemplate>
|
||||
</spp:CarouselItems>
|
||||
|
||||
<!--<spp:CarouselBanner
|
||||
MinHeight="250"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
ItemsSource="{Binding Shops}">
|
||||
<spp:CarouselBanner.Resources>
|
||||
<DataTemplate x:Key="ShopItem" DataType="spp:ShopRecommendGoodItem">
|
||||
<Border spp:Animations.EnableAnimations="True">
|
||||
<spp:AppItem
|
||||
Title="{Binding Name}"
|
||||
MaxWidth="260"
|
||||
Classes="Vertical Scale"
|
||||
ClickCommand="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
Cursor="Hand"
|
||||
Tags="{Binding GoodsAllCategorysText}"
|
||||
ToolTip.Tip="{Binding Brief}">
|
||||
<spp:AppItem.ClickCommandParameter>
|
||||
<MultiBinding Converter="{StaticResource StringFormatConverter}">
|
||||
<CompiledBinding Path="." Source="{x:Static spp:HomePageViewModel.WattGame_Goods_Detail_}" />
|
||||
<CompiledBinding Path="Id" />
|
||||
</MultiBinding>
|
||||
</spp:AppItem.ClickCommandParameter>
|
||||
<spp:AppItem.Image>
|
||||
<spp:Image2 RenderOptions.BitmapInterpolationMode="HighQuality" Source="{Binding Image}" />
|
||||
</spp:AppItem.Image>
|
||||
<spp:AppItem.Status>
|
||||
<Border Classes="Status">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
IsVisible="{Binding !!DiscountValue}"
|
||||
Text="{Binding Mktprice, Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static spp:CurrencyCode.CNY}}"
|
||||
TextDecorations="Strikethrough" />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Text="{Binding Price, Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static spp:CurrencyCode.CNY}}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</spp:AppItem.Status>
|
||||
<spp:AppItem.ActionButton>
|
||||
<Border
|
||||
Height="24"
|
||||
Padding="8,0"
|
||||
Background="#4c6b22"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="4"
|
||||
IsVisible="{Binding !!DiscountValue}">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Foreground="#beee11"
|
||||
Text="{Binding DiscountValue, StringFormat={}-{0}%}" />
|
||||
</Border>
|
||||
</spp:AppItem.ActionButton>
|
||||
</spp:AppItem>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</spp:CarouselBanner.Resources>
|
||||
<spp:CarouselBanner.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<DataTemplate.DataType>
|
||||
<x:Type x:TypeArguments="spp:ShopRecommendGoodItem" TypeName="col:List" />
|
||||
</DataTemplate.DataType>
|
||||
<Grid ColumnDefinitions="*,10,*,10,*,10,*">
|
||||
<ContentPresenter
|
||||
Grid.Column="0"
|
||||
Content="{Binding [0]}"
|
||||
ContentTemplate="{StaticResource ShopItem}" />
|
||||
<ContentPresenter
|
||||
Grid.Column="2"
|
||||
Content="{Binding [1]}"
|
||||
ContentTemplate="{StaticResource ShopItem}" />
|
||||
<ContentPresenter
|
||||
Grid.Column="4"
|
||||
Content="{Binding [2]}"
|
||||
ContentTemplate="{StaticResource ShopItem}" />
|
||||
<ContentPresenter
|
||||
Grid.Column="6"
|
||||
Content="{Binding [3]}"
|
||||
ContentTemplate="{StaticResource ShopItem}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
-->
|
||||
<!--<DataTemplate>
|
||||
<DataTemplate.DataType>
|
||||
<x:Type x:TypeArguments="spp:ShopRecommendGoodItem" TypeName="col:IEnumerable" />
|
||||
</DataTemplate.DataType>
|
||||
<ItemsControl ItemTemplate="{StaticResource ShopItem}" ItemsSource="{Binding}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Grid ColumnDefinitions="*,*,*,*" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</DataTemplate>-->
|
||||
<!--
|
||||
</spp:CarouselBanner.ItemTemplate>
|
||||
</spp:CarouselBanner>-->
|
||||
|
||||
<!--<ScrollViewer
|
||||
Name="ShopsScrollViewer"
|
||||
Margin="0,0,0,20"
|
||||
Padding="0,0,0,15"
|
||||
@@ -336,7 +506,7 @@
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</ScrollViewer>-->
|
||||
|
||||
<TextBlock
|
||||
Margin="-3,0,0,0"
|
||||
|
||||
@@ -7,7 +7,7 @@ public partial class HomePage : ReactiveUserControl<HomePageViewModel>
|
||||
public HomePage()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.SetViewModel<HomePageViewModel>(false);
|
||||
this.SetViewModel<HomePageViewModel>();
|
||||
|
||||
//ShopsScrollViewer.PointerWheelChanged += ShopsScrollViewer_PointerWheelChanged;
|
||||
}
|
||||
|
||||
@@ -263,19 +263,19 @@
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
<MenuItem Header="-" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
<MenuItem
|
||||
Command="{Binding Path=OpenAuthOfficialCommand, Source={x:Static s:UserService.Current}}"
|
||||
CommandParameter="https://steampp.net/account/center/info"
|
||||
Header="{Binding Path=Res.PersonalCenter, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
|
||||
<!--<MenuItem Header="{Binding Path=Res.MyOrder, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />-->
|
||||
<MenuItem
|
||||
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
<!--<MenuItem Header="{Binding Path=Res.MyOrder, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />-->
|
||||
<MenuItem
|
||||
Command="{Binding Path=OpenAuthOfficialCommand, Source={x:Static s:UserService.Current}}"
|
||||
CommandParameter="https://steampp.net/account/center/bind"
|
||||
Header="{Binding Path=Res.BindThirdPartyAccount, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
<MenuItem
|
||||
Command="{Binding Path=OpenAuthOfficialCommand, Source={x:Static s:UserService.Current}}"
|
||||
CommandParameter="https://steampp.net/account/center/safe"
|
||||
Header="{Binding Path=Res.PersonalSafe, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
|
||||
|
||||
@@ -73,15 +73,19 @@ public sealed partial class MainView : ReactiveUserControl<MainWindowViewModel>
|
||||
|
||||
NavView.SelectedItem = null;
|
||||
|
||||
foreach (var nvi in NavView.MenuItemsSource)
|
||||
{
|
||||
SetSelectedItem(nvi);
|
||||
}
|
||||
var menuItemsSource = NavView.MenuItemsSource;
|
||||
if (menuItemsSource != null)
|
||||
foreach (var nvi in menuItemsSource)
|
||||
{
|
||||
SetSelectedItem(nvi);
|
||||
}
|
||||
|
||||
foreach (var nvi in NavView.FooterMenuItemsSource)
|
||||
{
|
||||
SetSelectedItem(nvi);
|
||||
}
|
||||
var footerMenuItemsSource = NavView.FooterMenuItemsSource;
|
||||
if (footerMenuItemsSource != null)
|
||||
foreach (var nvi in footerMenuItemsSource)
|
||||
{
|
||||
SetSelectedItem(nvi);
|
||||
}
|
||||
|
||||
if (FrameView.BackStackDepth > 0 && !NavView.IsBackButtonVisible)
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ public partial class SettingsPage : PageBase<SettingsPageViewModel>
|
||||
public SettingsPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.SetViewModel<SettingsPageViewModel>(false);
|
||||
this.SetViewModel<SettingsPageViewModel>();
|
||||
|
||||
//SettingsScrollTab.SelectionChanged += SettingsScrollTab_SelectionChanged;
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
Command="{Binding $parent[spp:Settings_Plugin].((spp:SettingsPageViewModel)DataContext).DeletePlugin_Click}"
|
||||
CommandParameter="{Binding Data}"
|
||||
Content="{Binding Path=Res.Plugin_Delete, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Foreground="{DynamicResource SystemFillColorCriticalBrush}"
|
||||
IsClickEnabled="True" />
|
||||
</ui:SettingsExpander>
|
||||
</DataTemplate>
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
Margin="0,5"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Command="{ReflectionBinding $parent[UserControl].DataContext.ChooseChannel}"
|
||||
Command="{Binding $parent[UserControl].((spp:LoginOrRegisterWindowViewModel)DataContext).ChooseChannel}"
|
||||
CommandParameter="{Binding Id, Mode=OneWay}">
|
||||
<DockPanel>
|
||||
<Panel Margin="5,0,0,0" DockPanel.Dock="Left">
|
||||
|
||||
@@ -34,7 +34,7 @@ public partial class LoginOrRegisterPage : ReactiveUserControl<LoginOrRegisterWi
|
||||
protected override void OnLoaded(RoutedEventArgs e)
|
||||
{
|
||||
base.OnLoaded(e);
|
||||
this.SetViewModel<LoginOrRegisterWindowViewModel>(false);
|
||||
this.SetViewModel<LoginOrRegisterWindowViewModel>();
|
||||
|
||||
if (this.ViewModel != null)
|
||||
{
|
||||
@@ -42,9 +42,21 @@ public partial class LoginOrRegisterPage : ReactiveUserControl<LoginOrRegisterWi
|
||||
if (UserService.Current.IsAuthenticated)
|
||||
{
|
||||
Toast.Show(ToastIcon.Info, "当前已是登录状态");
|
||||
this.ViewModel.Close();
|
||||
this.ViewModel.Close(false);
|
||||
}
|
||||
}
|
||||
Application.Current!.UrlsOpened += Current_UrlsOpened;
|
||||
}
|
||||
|
||||
private async void Current_UrlsOpened(object? sender, UrlOpenedEventArgs e)
|
||||
{
|
||||
var loginUrl = e.Urls.Where(x => x.StartsWith(Constants.UrlSchemes.Login)).FirstOrDefault();
|
||||
if (loginUrl != null)
|
||||
{
|
||||
//var token = loginUrl.TrimStart(Constants.UrlSchemes.Login);
|
||||
var token = loginUrl[Constants.UrlSchemes.Login.Length..];
|
||||
await ThirdPartyLoginHelper.LoginForStr(token);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDataContextChanged(EventArgs e)
|
||||
@@ -59,6 +71,7 @@ public partial class LoginOrRegisterPage : ReactiveUserControl<LoginOrRegisterWi
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
Application.Current!.UrlsOpened -= Current_UrlsOpened;
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
if (DataContext is LoginOrRegisterWindowViewModel vm)
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
mc:Ignorable="d">
|
||||
<Panel>
|
||||
<TextBlock
|
||||
Margin="20"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding Path=!!!Notices.Count, Mode=OneWay, Source={x:Static s:NoticeService.Current}}"
|
||||
|
||||
@@ -46,11 +46,24 @@ public sealed partial class MainWindow : ReactiveAppWindow<MainWindowViewModel>
|
||||
}
|
||||
if (AppSplashScreen.IsInitialized)
|
||||
{
|
||||
if (NavigationService.Instance.CurrnetPage == null)
|
||||
{
|
||||
INavigationService.Instance.Navigate(typeof(HomePage));
|
||||
}
|
||||
Task2.InBackground(async () =>
|
||||
{
|
||||
IViewModelManager.Instance.Get<HomePageViewModel>().GetServerContent();
|
||||
await AdvertiseService.Current.RefrshAdvertiseAsync();
|
||||
NoticeService.Current.GetNewsAsync();
|
||||
});
|
||||
try // 在主窗口显示时调用此函数检查是否需要显示新版本通知窗口
|
||||
{
|
||||
Ioc.Get_Nullable<IAppUpdateService>()?.OnMainOpenTryShowNewVersionWindow();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,134 +91,138 @@ public sealed class AppSplashScreen : IApplicationSplashScreen
|
||||
{
|
||||
return Task.Run(
|
||||
async () =>
|
||||
{
|
||||
{
|
||||
if (!AppSplashScreen.IsInitialized)
|
||||
{
|
||||
#if STARTUP_WATCH_TRACE || DEBUG
|
||||
WatchTrace.Start();
|
||||
WatchTrace.Start();
|
||||
#endif
|
||||
|
||||
var s = Instance;
|
||||
if (s.IsMainProcess)
|
||||
{
|
||||
VersionTracking2.Track();
|
||||
var s = Instance;
|
||||
if (s.IsMainProcess)
|
||||
{
|
||||
VersionTracking2.Track();
|
||||
#if STARTUP_WATCH_TRACE || DEBUG
|
||||
WatchTrace.Record("VersionTracking2.Track");
|
||||
WatchTrace.Record("VersionTracking2.Track");
|
||||
#endif
|
||||
|
||||
Migrations.Up();
|
||||
Migrations.Up();
|
||||
#if STARTUP_WATCH_TRACE || DEBUG
|
||||
WatchTrace.Record("Migrations.Up");
|
||||
WatchTrace.Record("Migrations.Up");
|
||||
#endif
|
||||
|
||||
// 仅在主进程中启动 IPC 服务端
|
||||
IPCMainProcessService.Instance.Run();
|
||||
// 仅在主进程中启动 IPC 服务端
|
||||
IPCMainProcessService.Instance.Run();
|
||||
#if STARTUP_WATCH_TRACE || DEBUG
|
||||
WatchTrace.Record("IPC.StartServer");
|
||||
WatchTrace.Record("IPC.StartServer");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
LiveCharts.Configure(config =>
|
||||
{
|
||||
config
|
||||
// registers SkiaSharp as the library backend
|
||||
// REQUIRED unless you build your own
|
||||
.AddSkiaSharp();
|
||||
// adds the default supported types
|
||||
// OPTIONAL but highly recommend
|
||||
//.AddDefaultMappers()
|
||||
LiveCharts.Configure(config =>
|
||||
{
|
||||
config
|
||||
// registers SkiaSharp as the library backend
|
||||
// REQUIRED unless you build your own
|
||||
.AddSkiaSharp();
|
||||
// adds the default supported types
|
||||
// OPTIONAL but highly recommend
|
||||
//.AddDefaultMappers()
|
||||
|
||||
// select a theme, default is Light
|
||||
// OPTIONAL
|
||||
//.AddDarkTheme()
|
||||
// select a theme, default is Light
|
||||
// OPTIONAL
|
||||
//.AddDarkTheme()
|
||||
|
||||
// In case you need a non-Latin based font, you must register a typeface for SkiaSharp
|
||||
config.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('汉')); // <- Chinese // mark
|
||||
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('أ')) // <- Arabic // mark
|
||||
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('あ')) // <- Japanese // mark
|
||||
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('헬')) // <- Korean // mark
|
||||
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('Ж')) // <- Russian // mark
|
||||
// In case you need a non-Latin based font, you must register a typeface for SkiaSharp
|
||||
config.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('汉')); // <- Chinese // mark
|
||||
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('أ')) // <- Arabic // mark
|
||||
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('あ')) // <- Japanese // mark
|
||||
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('헬')) // <- Korean // mark
|
||||
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('Ж')) // <- Russian // mark
|
||||
|
||||
if (App.Instance.Theme != AppTheme.FollowingSystem)
|
||||
{
|
||||
if (App.Instance.Theme == AppTheme.Light)
|
||||
config.AddLightTheme();
|
||||
else
|
||||
config.AddDarkTheme();
|
||||
}
|
||||
else
|
||||
{
|
||||
var dps = IPlatformService.Instance;
|
||||
var isLightOrDarkTheme = dps.IsLightOrDarkTheme;
|
||||
if (isLightOrDarkTheme.HasValue)
|
||||
{
|
||||
var mThemeFS = IApplication.GetAppThemeByIsLightOrDarkTheme(isLightOrDarkTheme.Value);
|
||||
if (mThemeFS == AppTheme.Light)
|
||||
config.AddLightTheme();
|
||||
else
|
||||
config.AddDarkTheme();
|
||||
}
|
||||
}
|
||||
});
|
||||
if (App.Instance.Theme != AppTheme.FollowingSystem)
|
||||
{
|
||||
if (App.Instance.Theme == AppTheme.Light)
|
||||
config.AddLightTheme();
|
||||
else
|
||||
config.AddDarkTheme();
|
||||
}
|
||||
else
|
||||
{
|
||||
var dps = IPlatformService.Instance;
|
||||
var isLightOrDarkTheme = dps.IsLightOrDarkTheme;
|
||||
if (isLightOrDarkTheme.HasValue)
|
||||
{
|
||||
var mThemeFS = IApplication.GetAppThemeByIsLightOrDarkTheme(isLightOrDarkTheme.Value);
|
||||
if (mThemeFS == AppTheme.Light)
|
||||
config.AddLightTheme();
|
||||
else
|
||||
config.AddDarkTheme();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AdvertiseService.Current.InitAdvertise();
|
||||
NoticeService.Current.GetNewsAsync();
|
||||
AdvertiseService.Current.InitAdvertise();
|
||||
NoticeService.Current.GetNewsAsync();
|
||||
|
||||
var mainWindow = App.Instance.MainWindow;
|
||||
mainWindow.ThrowIsNull();
|
||||
var mainWindow = App.Instance.MainWindow;
|
||||
mainWindow.ThrowIsNull();
|
||||
|
||||
#pragma warning disable SA1114 // Parameter list should follow declaration
|
||||
IViewModelManager.Instance.InitViewModels(new TabItemViewModel[]
|
||||
{
|
||||
new MenuTabItemViewModel("Welcome")
|
||||
{
|
||||
PageType = typeof(HomePage),
|
||||
IsResourceGet = true,
|
||||
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/home.ico",
|
||||
},
|
||||
}, ImmutableArray.Create<TabItemViewModel>(
|
||||
IViewModelManager.Instance.InitViewModels(new TabItemViewModel[]
|
||||
{
|
||||
new MenuTabItemViewModel("Welcome")
|
||||
{
|
||||
PageType = typeof(HomePage),
|
||||
IsResourceGet = true,
|
||||
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/home.ico",
|
||||
},
|
||||
},
|
||||
ImmutableArray.Create<TabItemViewModel>(
|
||||
#if DEBUG
|
||||
new MenuTabItemViewModel("Debug")
|
||||
{
|
||||
PageType = typeof(DebugPage),
|
||||
IsResourceGet = false,
|
||||
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/bug.ico",
|
||||
},
|
||||
#endif
|
||||
new MenuTabItemViewModel("Plugin_Store")
|
||||
{
|
||||
PageType = null,
|
||||
IsResourceGet = true,
|
||||
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/store.ico",
|
||||
},
|
||||
new MenuTabItemViewModel("Settings")
|
||||
{
|
||||
PageType = typeof(SettingsPage),
|
||||
IsResourceGet = true,
|
||||
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/settings.ico",
|
||||
}));
|
||||
new MenuTabItemViewModel("Debug")
|
||||
{
|
||||
PageType = typeof(DebugPage),
|
||||
IsResourceGet = false,
|
||||
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/bug.ico",
|
||||
},
|
||||
#endif
|
||||
new MenuTabItemViewModel("Plugin_Store")
|
||||
{
|
||||
PageType = null,
|
||||
IsResourceGet = true,
|
||||
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/store.ico",
|
||||
},
|
||||
new MenuTabItemViewModel("Settings")
|
||||
{
|
||||
PageType = typeof(SettingsPage),
|
||||
IsResourceGet = true,
|
||||
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/settings.ico",
|
||||
}));
|
||||
#pragma warning restore SA1114 // Parameter list should follow declaration
|
||||
IViewModelManager.Instance.MainWindow.ThrowIsNull();
|
||||
IViewModelManager.Instance.MainWindow.ThrowIsNull();
|
||||
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
mainWindow.DataContext = IViewModelManager.Instance.MainWindow;
|
||||
s.InitSettingSubscribe();
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
mainWindow.DataContext = IViewModelManager.Instance.MainWindow;
|
||||
s.InitSettingSubscribe();
|
||||
|
||||
INavigationService.Instance.Navigate(typeof(HomePage));
|
||||
});
|
||||
INavigationService.Instance.Navigate(typeof(HomePage));
|
||||
});
|
||||
#if STARTUP_WATCH_TRACE || DEBUG
|
||||
WatchTrace.Record("InitMainWindowViewModel");
|
||||
WatchTrace.Record("InitMainWindowViewModel");
|
||||
#endif
|
||||
|
||||
#if STARTUP_WATCH_TRACE || DEBUG
|
||||
WatchTrace.Stop();
|
||||
WatchTrace.Stop();
|
||||
#endif
|
||||
s.OnStartup();
|
||||
await IViewModelManager.Instance.MainWindow.Initialize();
|
||||
s.OnStartup();
|
||||
await IViewModelManager.Instance.MainWindow.Initialize();
|
||||
|
||||
App.Instance.CompositeDisposable.Add(IViewModelManager.Instance.MainWindow);
|
||||
App.Instance.CompositeDisposable.Add(SteamConnectService.Current.Dispose);
|
||||
App.Instance.CompositeDisposable.Add(IViewModelManager.Instance.MainWindow);
|
||||
App.Instance.CompositeDisposable.Add(SteamConnectService.Current.Dispose);
|
||||
|
||||
IsInitialized = true;
|
||||
}, cancellationToken: token);
|
||||
IsInitialized = true;
|
||||
}
|
||||
}, cancellationToken: token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +93,9 @@ public interface IPCSubProcessService : IDisposable
|
||||
var nativeLibraryPath = Environment.GetEnvironmentVariable(EnvKey_NativeLibraryPath);
|
||||
if (!string.IsNullOrWhiteSpace(nativeLibraryPath))
|
||||
{
|
||||
var nativeLibraryPaths = nativeLibraryPath.Split(';',
|
||||
StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// 监听当前应用程序域的程序集加载
|
||||
AppDomain.CurrentDomain.AssemblyLoad += (_, args)
|
||||
=> CurrentDomain_AssemblyLoad(args.LoadedAssembly);
|
||||
@@ -119,23 +122,34 @@ public interface IPCSubProcessService : IDisposable
|
||||
return libraryName;
|
||||
}
|
||||
var libraryFileName = GetLibraryFileName(libraryName);
|
||||
var libraryPath = Path.Combine(nativeLibraryPath, libraryFileName);
|
||||
if (File.Exists(libraryPath) &&
|
||||
NativeLibrary.TryLoad(libraryPath, out var handle))
|
||||
string libraryPath = libraryName;
|
||||
try
|
||||
{
|
||||
return handle;
|
||||
}
|
||||
else if (!OperatingSystem.IsWindows() && !libraryFileName.StartsWith("lib", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
libraryFileName = $"lib{libraryFileName}";
|
||||
libraryPath = Path.Combine(nativeLibraryPath, libraryFileName);
|
||||
if (File.Exists(libraryPath) &&
|
||||
NativeLibrary.TryLoad(libraryPath, out handle))
|
||||
foreach (var nativeLibraryPath in nativeLibraryPaths)
|
||||
{
|
||||
return handle;
|
||||
libraryPath = Path.Combine(nativeLibraryPath, libraryFileName);
|
||||
if (File.Exists(libraryPath) &&
|
||||
NativeLibrary.TryLoad(libraryPath, out var handle))
|
||||
{
|
||||
return handle;
|
||||
}
|
||||
else if (!OperatingSystem.IsWindows() && !libraryFileName.StartsWith("lib", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
libraryFileName = $"lib{libraryFileName}";
|
||||
libraryPath = Path.Combine(nativeLibraryPath, libraryFileName);
|
||||
if (File.Exists(libraryPath) &&
|
||||
NativeLibrary.TryLoad(libraryPath, out handle))
|
||||
{
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NativeLibrary.Load(libraryName, assembly, searchPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new FileNotFoundException(null, libraryPath, ex);
|
||||
}
|
||||
return NativeLibrary.Load(libraryName, assembly, searchPath);
|
||||
}
|
||||
NativeLibrary.SetDllImportResolver(loadedAssembly, Delegate);
|
||||
}
|
||||
|
||||
+3
@@ -101,6 +101,9 @@
|
||||
<PackageReference Include="Aigio.Any.AppCenter" />
|
||||
<PackageReference Include="Aigio.Any.AppCenter.Analytics" />
|
||||
<PackageReference Include="Aigio.Any.AppCenter.Crashes" />
|
||||
<PackageReference Include="System.Drawing.Common" />
|
||||
<PackageReference Include="System.Runtime.Caching" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('windows')) AND ($(RuntimeIdentifier.StartsWith('win-')) OR $(RuntimeIdentifier) == '')">
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ public static partial class ServiceCollectionExtensions
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static IServiceCollection AddDnsAnalysisService(this IServiceCollection services)
|
||||
{
|
||||
//services.AddSingleton<IDnsAnalysisService, DnsDohAnalysisService>();
|
||||
services.AddSingleton<IDnsAnalysisService, DnsAnalysisServiceImpl>();
|
||||
services.AddSingleton<DnsDohAnalysisService>();
|
||||
services.AddSingleton<DnsAnalysisServiceImpl>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,7 @@ static void ConfigureServices(IServiceCollection services)
|
||||
|
||||
services.AddHttpClient();
|
||||
services.AddCommonHttpClientFactory();
|
||||
services.AddSingleton<IHttpPlatformHelperService, HttpPlatformHelperConsoleService>();
|
||||
|
||||
services.AddDnsAnalysisService();
|
||||
// 添加反向代理服务(子进程实现)
|
||||
@@ -147,4 +148,11 @@ sealed class RepositorySecureStorage : Repository<KeyValuePair, string>, ISecure
|
||||
var item = await FirstOrDefaultAsync(x => x.Id == key);
|
||||
return item != null;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class HttpPlatformHelperConsoleService : HttpPlatformHelperService
|
||||
{
|
||||
new const string DefaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 Edg/90.0.818.51";
|
||||
|
||||
public override string UserAgent => DefaultUserAgent;
|
||||
}
|
||||
+85
-55
@@ -23,6 +23,20 @@ sealed partial class CertificateManagerImpl : ICertificateManager
|
||||
|
||||
public X509CertificatePackable RootCertificatePackable { get; set; }
|
||||
|
||||
readonly object lock_RootCertificatePackable = new();
|
||||
|
||||
byte[]? ICertificateManager.RootCertificatePackable
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (lock_RootCertificatePackable)
|
||||
{
|
||||
RootCertificate ??= LoadRootCertificate();
|
||||
}
|
||||
return RootCertificate == default ? default : Serializable.SMP2(RootCertificatePackable);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ICertificateManager.PfxPassword"/>
|
||||
public byte[]? PfxPassword { get; set; }
|
||||
|
||||
@@ -74,6 +88,7 @@ sealed partial class CertificateManagerImpl : ICertificateManager
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("use ICertificateManager.Constants.TrustRootCertificate")]
|
||||
void SharedTrustRootCertificate()
|
||||
{
|
||||
if (RootCertificate == null)
|
||||
@@ -180,7 +195,12 @@ sealed partial class CertificateManagerImpl : ICertificateManager
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
if (!GenerateCertificateUnlock(filePath)) return null;
|
||||
if (!GenerateCertificateUnlock(filePath))
|
||||
return null;
|
||||
}
|
||||
else if (RootCertificate == null)
|
||||
{
|
||||
RootCertificate = LoadRootCertificate();
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
@@ -222,44 +242,66 @@ sealed partial class CertificateManagerImpl : ICertificateManager
|
||||
}
|
||||
}
|
||||
|
||||
public void TrustRootCertificate()
|
||||
bool? ICertificateManager.GenerateCertificate()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
try
|
||||
{
|
||||
SharedTrustRootCertificate();
|
||||
}
|
||||
#if DEBUG
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(TAG, e, "SharedTrustRootCertificate Error");
|
||||
}
|
||||
#else
|
||||
catch { }
|
||||
#endif
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
TrustRootCertificateMacOS();
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() && !OperatingSystem.IsAndroid())
|
||||
{
|
||||
TrustRootCertificateLinux();
|
||||
}
|
||||
}
|
||||
#if DEBUG
|
||||
catch (Exception e)
|
||||
{
|
||||
e.LogAndShowT(TAG, msg: "PlatformTrustRootCertificateGuide Error");
|
||||
}
|
||||
#else
|
||||
catch { }
|
||||
#endif
|
||||
return GenerateCertificate(null);
|
||||
}
|
||||
|
||||
// public void TrustRootCertificate()
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// if (OperatingSystem.IsWindows())
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// SharedTrustRootCertificate();
|
||||
// }
|
||||
//#if DEBUG
|
||||
// catch (Exception e)
|
||||
// {
|
||||
// Log.Error(TAG, e, "SharedTrustRootCertificate Error");
|
||||
// }
|
||||
//#else
|
||||
// catch { }
|
||||
//#endif
|
||||
// }
|
||||
// else if (OperatingSystem.IsMacOS())
|
||||
// {
|
||||
// TrustRootCertificateMacOS();
|
||||
// }
|
||||
// else if (OperatingSystem.IsLinux() && !OperatingSystem.IsAndroid())
|
||||
// {
|
||||
// TrustRootCertificateLinux();
|
||||
// }
|
||||
// }
|
||||
//#if DEBUG
|
||||
// catch (Exception e)
|
||||
// {
|
||||
// e.LogAndShowT(TAG, msg: "PlatformTrustRootCertificateGuide Error");
|
||||
// }
|
||||
//#else
|
||||
// catch { }
|
||||
//#endif
|
||||
// }
|
||||
|
||||
public void TrustRootCertificate()
|
||||
{
|
||||
if (RootCertificate == null)
|
||||
{
|
||||
GenerateCertificate();
|
||||
}
|
||||
|
||||
if (RootCertificate == null)
|
||||
{
|
||||
throw new ApplicationException(
|
||||
"Could not install certificate as it is null or empty.");
|
||||
}
|
||||
|
||||
ICertificateManager.Constants.TrustRootCertificate(GetCerFilePathGeneratedWhenNoFileExists, platformService, RootCertificate);
|
||||
}
|
||||
|
||||
[Obsolete("use ICertificateManager.Constants.TrustRootCertificate")]
|
||||
[SupportedOSPlatform("macOS")]
|
||||
void TrustRootCertificateMacOS()
|
||||
{
|
||||
@@ -271,6 +313,7 @@ sealed partial class CertificateManagerImpl : ICertificateManager
|
||||
TrustRootCertificateMacOS();
|
||||
}
|
||||
|
||||
[Obsolete("use ICertificateManager.Constants.TrustRootCertificate")]
|
||||
[SupportedOSPlatform("Linux")]
|
||||
void TrustRootCertificateLinux()
|
||||
{
|
||||
@@ -390,12 +433,13 @@ sealed partial class CertificateManagerImpl : ICertificateManager
|
||||
{
|
||||
get
|
||||
{
|
||||
if (RootCertificate == null)
|
||||
if (GetCerFilePathGeneratedWhenNoFileExists() == null) return false;
|
||||
return IsCertificateInstalled(RootCertificatePackable);
|
||||
var result = ICertificateManager.Constants.IsRootCertificateInstalled(this, platformService, RootCertificatePackable);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
bool? ICertificateManager.IsRootCertificateInstalled2 => IsRootCertificateInstalled;
|
||||
|
||||
/// <summary>
|
||||
/// 检查证书是否已安装并信任
|
||||
/// </summary>
|
||||
@@ -403,21 +447,7 @@ sealed partial class CertificateManagerImpl : ICertificateManager
|
||||
/// <returns></returns>
|
||||
bool IsCertificateInstalled(X509CertificatePackable packable)
|
||||
{
|
||||
X509Certificate2? certificate2 = packable;
|
||||
if (certificate2 == null)
|
||||
return false;
|
||||
if (certificate2.NotAfter <= DateTime.Now)
|
||||
return false;
|
||||
|
||||
if (OperatingSystem.IsAndroid() || OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
return platformService.IsCertificateInstalled(packable);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
|
||||
store.Open(OpenFlags.ReadOnly);
|
||||
return store.Certificates.Contains(certificate2);
|
||||
}
|
||||
var result = ICertificateManager.Constants.IsCertificateInstalled(platformService, packable);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+9
-1
@@ -14,10 +14,18 @@ sealed class LazyCertificateManager : ICertificateManager
|
||||
|
||||
public byte[]? PfxPassword => impl().PfxPassword;
|
||||
|
||||
public X509CertificatePackable RootCertificatePackable { get => impl().RootCertificatePackable; set => impl().RootCertificatePackable = value; }
|
||||
public byte[]? RootCertificatePackable { get => impl().RootCertificatePackable; }
|
||||
|
||||
[Obsolete]
|
||||
public bool IsRootCertificateInstalled => impl().IsRootCertificateInstalled;
|
||||
|
||||
public bool? IsRootCertificateInstalled2 => impl().IsRootCertificateInstalled2;
|
||||
|
||||
public bool? GenerateCertificate()
|
||||
{
|
||||
return impl().GenerateCertificate();
|
||||
}
|
||||
|
||||
public string? GetCerFilePathGeneratedWhenNoFileExists()
|
||||
{
|
||||
return impl().GetCerFilePathGeneratedWhenNoFileExists();
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ sealed class LifetimeHttpHandlerCleaner
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置清理的时间间隔
|
||||
/// 默认10s
|
||||
/// 默认 10s
|
||||
/// </summary>
|
||||
public TimeSpan CleanupInterval { get; set; } = TimeSpan.FromSeconds(10d);
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ sealed class RequestContext
|
||||
public bool IsHttps { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置Sni值
|
||||
/// 获取或设置 Sni 值
|
||||
/// </summary>
|
||||
public TlsSniPattern TlsSniValue { get; }
|
||||
|
||||
|
||||
+2
-5
@@ -24,11 +24,8 @@ sealed class ReverseProxyHttpClientHandler : DelegatingHandler
|
||||
/// <returns></returns>
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
var uri = request.RequestUri;
|
||||
if (uri == null)
|
||||
{
|
||||
var uri = request.RequestUri ??
|
||||
throw new ApplicationException("The requested URI must be specified.");
|
||||
}
|
||||
|
||||
// 请求上下文信息
|
||||
var isHttps = uri.Scheme == Uri.UriSchemeHttps;
|
||||
@@ -52,7 +49,7 @@ sealed class ReverseProxyHttpClientHandler : DelegatingHandler
|
||||
/// 创建转发代理的 <see cref="HttpMessageHandler"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
SocketsHttpHandler CreateSocketsHttpHandler() => new()
|
||||
HttpHandlerType CreateSocketsHttpHandler() => new()
|
||||
{
|
||||
Proxy = HttpNoProxy.Instance,
|
||||
UseProxy = false,
|
||||
|
||||
+4
-7
@@ -20,12 +20,12 @@ sealed class CertService
|
||||
/// <summary>
|
||||
/// 获取 CER 证书文件路径
|
||||
/// </summary>
|
||||
public string CaCerFilePath => ReverseProxyService.CertificateManager.CerFilePath;
|
||||
public string CaCerFilePath => ((ICertificateManager)ReverseProxyService.CertificateManager).CerFilePath;
|
||||
|
||||
/// <summary>
|
||||
/// 获取 PFX 证书文件路径
|
||||
/// </summary>
|
||||
public string CaPfxFilePath => ReverseProxyService.CertificateManager.PfxFilePath;
|
||||
public string CaPfxFilePath => ((ICertificateManager)ReverseProxyService.CertificateManager).PfxFilePath;
|
||||
|
||||
public CertService(
|
||||
IMemoryCache serverCertCache,
|
||||
@@ -69,10 +69,7 @@ sealed class CertService
|
||||
/// <returns></returns>
|
||||
public X509Certificate2? GetOrCreateServerCert(string? domain)
|
||||
{
|
||||
if (this.caCert == null)
|
||||
{
|
||||
this.caCert = new X509Certificate2(fileName: this.CaPfxFilePath, password: default(string));
|
||||
}
|
||||
caCert ??= new X509Certificate2(fileName: CaPfxFilePath, password: default(string));
|
||||
|
||||
var key = $"{nameof(CertService)}:{domain}";
|
||||
return serverCertCache.GetOrCreate(key, GetOrCreateCert);
|
||||
@@ -86,7 +83,7 @@ sealed class CertService
|
||||
entry.SetAbsoluteExpiration(notAfter);
|
||||
|
||||
var subjectName = new X500DistinguishedName($"CN={domain}");
|
||||
using var serverCert = CertGenerator.CreateEndCertificate(this.caCert, subjectName, GetDomains(), notBefore, notAfter);
|
||||
using var serverCert = CertGenerator.CreateEndCertificate(caCert, subjectName, GetDomains(), notBefore, notAfter);
|
||||
var serverCertPfx = serverCert.Export(X509ContentType.Pfx);
|
||||
// 将生成的证书导出后重新创建一个
|
||||
return new X509Certificate2(serverCertPfx);
|
||||
|
||||
+68
-17
@@ -96,14 +96,54 @@ sealed class HttpLocalRequestMiddleware
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(context.Request.QueryString.Value))
|
||||
var url = context.Request.QueryString.Value;
|
||||
if (string.IsNullOrEmpty(url))
|
||||
return;
|
||||
url = HttpUtility.UrlDecode(url.Replace("?request=", ""));
|
||||
Uri requestUri;
|
||||
try
|
||||
{
|
||||
requestUri = new Uri(url);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var method = context.Request.Method;
|
||||
var methodObj = method switch
|
||||
{
|
||||
"GET" => HttpMethod.Get,
|
||||
"PUT" => HttpMethod.Put,
|
||||
"POST" => HttpMethod.Post,
|
||||
"DELETE" => HttpMethod.Delete,
|
||||
"HEAD" => HttpMethod.Head,
|
||||
"OPTIONS" => HttpMethod.Options,
|
||||
"TRACE" => HttpMethod.Trace,
|
||||
"PATCH" => HttpMethod.Patch,
|
||||
"CONNECT" => HttpMethod.Connect,
|
||||
_ => TryParse(method),
|
||||
};
|
||||
if (methodObj == null)
|
||||
return;
|
||||
|
||||
var url = HttpUtility.UrlDecode(context.Request.QueryString.Value.Replace("?request=", ""));
|
||||
string? cookie = context.Request.Headers["cookie-steamTool"];
|
||||
if (string.IsNullOrEmpty(cookie))
|
||||
static HttpMethod? TryParse(string method)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new HttpMethod(method.ToUpperInvariant());
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
StringValues cookie = context.Request.Headers["cookie-steamTool"];
|
||||
if (StringValues.IsNullOrEmpty(cookie))
|
||||
cookie = context.Request.Headers["Cookie"];
|
||||
string? referer = context.Request.Headers["Referer-steamTool"];
|
||||
StringValues referer = context.Request.Headers["Referer-steamTool"];
|
||||
|
||||
context.Response.Headers.AccessControlAllowOrigin = context.Request.Headers.Origin.Count == 0 ? "*" : context.Request.Headers.Origin;
|
||||
context.Response.Headers.AccessControlAllowHeaders = "*";
|
||||
@@ -119,28 +159,39 @@ sealed class HttpLocalRequestMiddleware
|
||||
async Task SendAsync()
|
||||
{
|
||||
var method = context.Request.Method;
|
||||
var hasContent = method != HttpMethods.Get && context.Request.ContentLength > 0;
|
||||
var hasReqContent = !HttpMethods.IsGet(method) && context.Request.ContentLength > 0;
|
||||
var req = new HttpRequestMessage
|
||||
{
|
||||
RequestUri = new Uri(url),
|
||||
Method = new(method),
|
||||
Content = hasContent ? new StreamContent(context.Request.Body) : null,
|
||||
RequestUri = requestUri,
|
||||
Method = methodObj,
|
||||
Content = hasReqContent ? new StreamContent(context.Request.Body) : null,
|
||||
};
|
||||
req.Headers.UserAgent.ParseAdd(context.Request.Headers.UserAgent);
|
||||
if (cookie != null)
|
||||
if (!StringValues.IsNullOrEmpty(cookie))
|
||||
{
|
||||
CookieHttpClient.CookieContainer.Add(new Cookie
|
||||
{
|
||||
CommentUri = req.RequestUri,
|
||||
Domain = req.RequestUri.Host,
|
||||
CommentUri = requestUri,
|
||||
Domain = requestUri.Host,
|
||||
Value = cookie,
|
||||
});
|
||||
// req.Headers.Add("Cookie", cookie);
|
||||
}
|
||||
if (referer != null) req.Headers.Referrer = new Uri(referer);
|
||||
if (hasContent)
|
||||
if (!StringValues.IsNullOrEmpty(referer))
|
||||
{
|
||||
req.Content!.Headers.ContentType = MediaTypeHeaderValue.Parse(context.Request.ContentType);
|
||||
try
|
||||
{
|
||||
var refererUri = new Uri(referer.ToString());
|
||||
req.Headers.Referrer = refererUri;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
if (hasReqContent)
|
||||
{
|
||||
req.Content!.Headers.ContentType = MediaTypeHeaderValue.Parse(context.Request.ContentType.ThrowIsNull());
|
||||
req.Content.Headers.ContentLength = context.Request.ContentLength;
|
||||
}
|
||||
var rsp = await HttpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted).ConfigureAwait(false);
|
||||
@@ -157,12 +208,12 @@ sealed class HttpLocalRequestMiddleware
|
||||
}
|
||||
}
|
||||
|
||||
if (context.Request.Method == HttpMethods.Get)
|
||||
if (HttpMethods.IsGet(context.Request.Method))
|
||||
{
|
||||
await SendAsync();
|
||||
return;
|
||||
}
|
||||
else if (context.Request.Method == HttpMethods.Post)
|
||||
else if (HttpMethods.IsPost(context.Request.Method))
|
||||
{
|
||||
if (context.Request.ContentLength > 0)
|
||||
{
|
||||
|
||||
+4
-4
@@ -8,9 +8,7 @@ namespace BD.WTTS.Services.Implementation;
|
||||
/// </summary>
|
||||
sealed class HttpProxyMiddleware
|
||||
{
|
||||
private readonly HttpParser<HttpRequestHandler> httpParser = new();
|
||||
private static readonly byte[] http200 = Encoding.ASCII.GetBytes("HTTP/1.1 200 Connection Established\r\n\r\n");
|
||||
private static readonly byte[] http400 = Encoding.ASCII.GetBytes("HTTP/1.1 400 Bad Request\r\n\r\n");
|
||||
readonly HttpParser<HttpRequestHandler> httpParser = new();
|
||||
|
||||
/// <summary>
|
||||
/// 执行中间件
|
||||
@@ -39,6 +37,7 @@ sealed class HttpProxyMiddleware
|
||||
if (request.ProxyProtocol == ProxyProtocol.TunnelProxy)
|
||||
{
|
||||
input.AdvanceTo(consumed);
|
||||
ReadOnlyMemory<byte> http200 = "HTTP/1.1 200 Connection Established\r\n\r\n"u8.ToArray();
|
||||
await output.WriteAsync(http200, context.ConnectionClosed);
|
||||
}
|
||||
else
|
||||
@@ -63,6 +62,7 @@ sealed class HttpProxyMiddleware
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ReadOnlyMemory<byte> http400 = "HTTP/1.1 400 Bad Request\r\n\r\n"u8.ToArray();
|
||||
await output.WriteAsync(http400, context.ConnectionClosed);
|
||||
break;
|
||||
}
|
||||
@@ -120,7 +120,7 @@ sealed class HttpProxyMiddleware
|
||||
void IHttpRequestLineHandler.OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span<byte> startLine)
|
||||
{
|
||||
method = versionAndMethod.Method;
|
||||
var host = Encoding.ASCII.GetString(startLine.Slice(targetPath.Offset, targetPath.Length));
|
||||
var host = Encoding.UTF8.GetString(startLine.Slice(targetPath.Offset, targetPath.Length));
|
||||
if (versionAndMethod.Method == AspNetCoreHttpMethod.Connect)
|
||||
{
|
||||
ProxyHost = HostString.FromUriComponent(host);
|
||||
|
||||
+8
-3
@@ -87,7 +87,12 @@ sealed partial class HttpReverseProxyMiddleware
|
||||
context.Request.Headers.UserAgent = domainConfig.UserAgent.Replace("${origin}", context.Request.Headers.UserAgent, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient, ForwarderRequestConfig.Empty, HttpTransformer.Empty);
|
||||
var forwarderRequestConfig = new ForwarderRequestConfig()
|
||||
{
|
||||
Version = context.Request.Protocol.StartsWith("HTTP/2") ? System.Net.HttpVersion.Version20 : System.Net.HttpVersion.Version11,
|
||||
};
|
||||
|
||||
var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient, forwarderRequestConfig, HttpTransformer.Empty);
|
||||
|
||||
if (error != ForwarderError.None)
|
||||
{
|
||||
@@ -282,8 +287,8 @@ sealed partial class HttpReverseProxyMiddleware
|
||||
// var html_start_string = encoding.GetString(html_start.Span);
|
||||
//#endif
|
||||
await bodyCoreWriter.WriteAsync(html_start);
|
||||
var script_xml_start = "<script type=\"text/javascript\" src=\"https://local.steampp.net/"u8.ToArray();
|
||||
var script_xml_end = "\"></script>"u8.ToArray();
|
||||
ReadOnlyMemory<byte> script_xml_start = "<script type=\"text/javascript\" src=\"https://local.steampp.net/"u8.ToArray();
|
||||
ReadOnlyMemory<byte> script_xml_end = "\"></script>"u8.ToArray();
|
||||
foreach (var script in scripts)
|
||||
{
|
||||
await bodyCoreWriter.WriteAsync(script_xml_start);
|
||||
|
||||
+3
-3
@@ -9,9 +9,9 @@ namespace BD.WTTS.Services.Implementation;
|
||||
/// </summary>
|
||||
abstract class TcpReverseProxyHandler : ConnectionHandler
|
||||
{
|
||||
private readonly IDomainResolver domainResolver;
|
||||
private readonly DnsEndPoint endPoint;
|
||||
private readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(10d);
|
||||
readonly IDomainResolver domainResolver;
|
||||
readonly DnsEndPoint endPoint;
|
||||
readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(10d);
|
||||
|
||||
public TcpReverseProxyHandler(IDomainResolver domainResolver, DnsEndPoint endPoint)
|
||||
{
|
||||
|
||||
+13
-11
@@ -5,9 +5,9 @@ using static BD.WTTS.Services.IDnsAnalysisService;
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace BD.WTTS.Services.Implementation;
|
||||
|
||||
sealed class DnsAnalysisServiceImpl : IDnsAnalysisService
|
||||
sealed class DnsAnalysisServiceImpl
|
||||
{
|
||||
static readonly LookupClient lookupClient = new();
|
||||
readonly LookupClient lookupClient = new();
|
||||
|
||||
public async Task<int> AnalysisHostnameTimeAsync(string url, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -24,7 +24,7 @@ sealed class DnsAnalysisServiceImpl : IDnsAnalysisService
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static IEnumerable<ARecord>? GetPingHostNameData(string url)
|
||||
public IEnumerable<ARecord>? GetPingHostNameData(string url)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(url))
|
||||
{
|
||||
@@ -119,11 +119,18 @@ sealed class DnsAnalysisServiceImpl : IDnsAnalysisService
|
||||
{
|
||||
Retries = 1,
|
||||
Timeout = TimeSpan.FromSeconds(1),
|
||||
UseCache = false
|
||||
UseCache = false,
|
||||
};
|
||||
var client = new LookupClient(options);
|
||||
var response = await client.QueryServerAsync(new[] { IPAddress.Parse(PrimaryDNS_IPV6_Ali) }, IPV6_TESTDOMAIN, QueryType.AAAA, QueryClass.IN);
|
||||
if (response.Answers.AaaaRecords().Any(s => s.Address.Equals(IPAddress.Parse(IPV6_TESTDOMAIN_SUCCESS))))
|
||||
var response = await client.QueryServerAsync(
|
||||
new[] {
|
||||
IPAddress.Parse(PrimaryDNS_IPV6_Ali),
|
||||
},
|
||||
IPV6_TESTDOMAIN,
|
||||
QueryType.AAAA,
|
||||
QueryClass.IN);
|
||||
if (response.Answers.AaaaRecords().Any(s =>
|
||||
s.Address.Equals(IPAddress.Parse(IPV6_TESTDOMAIN_SUCCESS))))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -133,9 +140,4 @@ sealed class DnsAnalysisServiceImpl : IDnsAnalysisService
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Task<IPAddress?> GetHostIpv6AddresAsync()
|
||||
{
|
||||
return Task.FromResult<IPAddress?>(null);
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace BD.WTTS.Services.Implementation;
|
||||
|
||||
sealed class DnsAnalysisServiceSwitchImpl : IDnsAnalysisService
|
||||
{
|
||||
readonly DnsAnalysisServiceImpl dnsAnalysisServiceImpl;
|
||||
readonly DnsDohAnalysisService dnsDohAnalysisService;
|
||||
readonly IReverseProxySettings settings;
|
||||
|
||||
public DnsAnalysisServiceSwitchImpl(
|
||||
IReverseProxySettings settings,
|
||||
DnsAnalysisServiceImpl dnsAnalysisServiceImpl,
|
||||
DnsDohAnalysisService dnsDohAnalysisService)
|
||||
{
|
||||
this.settings = settings;
|
||||
this.dnsAnalysisServiceImpl = dnsAnalysisServiceImpl;
|
||||
this.dnsDohAnalysisService = dnsDohAnalysisService;
|
||||
}
|
||||
|
||||
public async Task<int> AnalysisHostnameTimeAsync(string url, CancellationToken cancellationToken = default)
|
||||
{
|
||||
int r;
|
||||
if (settings.UseDoh)
|
||||
{
|
||||
r = await dnsDohAnalysisService.DohAnalysisHostnameTimeAsync(
|
||||
settings.CustomDohAddres, url, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
r = await dnsAnalysisServiceImpl.AnalysisHostnameTimeAsync(url, cancellationToken);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<IPAddress> AnalysisDomainIpAsync(string hostNameOrAddress, IPAddress[]? dnsServers, bool isIPv6, CancellationToken cancellationToken = default)
|
||||
{
|
||||
IAsyncEnumerable<IPAddress> r;
|
||||
if (settings.UseDoh)
|
||||
{
|
||||
r = dnsDohAnalysisService.DohAnalysisDomainIpAsync(settings.CustomDohAddres, hostNameOrAddress, isIPv6, true, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
r = dnsAnalysisServiceImpl.AnalysisDomainIpAsync(hostNameOrAddress, dnsServers, isIPv6, cancellationToken);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
public async Task<string?> GetHostByIPAddressAsync(IPAddress ip)
|
||||
{
|
||||
var r = await dnsAnalysisServiceImpl.GetHostByIPAddressAsync(ip);
|
||||
return r;
|
||||
}
|
||||
|
||||
public async Task<bool> GetIsIpv6SupportAsync()
|
||||
{
|
||||
var r = await dnsAnalysisServiceImpl.GetIsIpv6SupportAsync();
|
||||
return r;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user