mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-18 02:10:21 +08:00
feat: 添加 MirrorChyan 资源更新方式 (#11669)
Co-authored-by: MistEO <mistereo@hotmail.com>
This commit is contained in:
@@ -54,6 +54,7 @@
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=ccast/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=cdfend/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=cguard/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Chyan/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=cmedic/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Collapsal/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=cpione/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
@@ -196,6 +196,8 @@ namespace MaaWpfGui.Constants
|
||||
public const string UpdateProxy = "VersionUpdate.Proxy";
|
||||
public const string ProxyType = "VersionUpdate.ProxyType";
|
||||
public const string VersionType = "VersionUpdate.VersionType";
|
||||
public const string ResourceUpdateSource = "VersionUpdate.ResourceUpdateSource";
|
||||
public const string MirrorChyanCdk = "VersionUpdate.ResourceUpdateSource.MirrorChyanCdk";
|
||||
public const string UpdateCheck = "VersionUpdate.UpdateCheck";
|
||||
public const string UpdateAutoCheck = "VersionUpdate.ScheduledUpdateCheck";
|
||||
public const string ResourceApi = "VersionUpdate.ResourceApi";
|
||||
|
||||
@@ -82,5 +82,11 @@ namespace MaaWpfGui.Constants
|
||||
"zh-tw" => $"{GitHubIssues}/new?assignees=&labels=bug&template=cn-bug-report.yaml",
|
||||
_ => $"{GitHubIssues}/new?assignees=&labels=bug&template=en-bug-report.yaml",
|
||||
};
|
||||
|
||||
// 资源更新更新源
|
||||
public const string GithubResourceUpdate = "https://github.com/MaaAssistantArknights/MaaResource/archive/refs/heads/main.zip";
|
||||
|
||||
public const string MirrorChyanWebsite = "https://mirrorc.top";
|
||||
public const string MirrorChyanResourceUpdate = $"{MirrorChyanWebsite}/api/resources/MaaResource/latest";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,19 +20,19 @@ namespace MaaWpfGui.Extensions
|
||||
{
|
||||
private static readonly ILogger _logger = Serilog.Log.ForContext("SourceContext", "HttpResponseLoggingExtension");
|
||||
|
||||
public static void Log(this HttpResponseMessage response)
|
||||
public static void Log(this HttpResponseMessage response, bool logUri = true)
|
||||
{
|
||||
var method = response.RequestMessage.Method;
|
||||
var url = response.RequestMessage.RequestUri.ToString();
|
||||
var statusCode = response.StatusCode.ToString();
|
||||
var method = response?.RequestMessage?.Method;
|
||||
var url = response?.RequestMessage?.RequestUri?.ToString();
|
||||
var statusCode = response?.StatusCode.ToString();
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
if (response is { IsSuccessStatusCode: true })
|
||||
{
|
||||
_logger.Information("HTTP: {StatusCode} {Method} {Url}", statusCode, method, url);
|
||||
_logger.Information("HTTP: {StatusCode} {Method} {Url}", statusCode, method, logUri ? url : "*****");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warning("HTTP: {StatusCode} {Method} {Url}", statusCode, method, url);
|
||||
_logger.Warning("HTTP: {StatusCode} {Method} {Url}", statusCode, method, logUri ? url : "*****");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using MaaWpfGui.Constants;
|
||||
@@ -26,6 +28,10 @@ using MaaWpfGui.Helper;
|
||||
using MaaWpfGui.Main;
|
||||
using MaaWpfGui.ViewModels;
|
||||
using MaaWpfGui.ViewModels.UI;
|
||||
using MaaWpfGui.ViewModels.UserControl.Settings;
|
||||
using MaaWpfGui.Views.UI;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using Stylet;
|
||||
|
||||
@@ -355,11 +361,14 @@ namespace MaaWpfGui.Models
|
||||
case UpdateResult.Success
|
||||
when !await HttpResponseHelper.SaveResponseToFileAsync(response, saveTo):
|
||||
return UpdateResult.Failed;
|
||||
|
||||
case UpdateResult.Success:
|
||||
ETagCache.Set(response);
|
||||
return UpdateResult.Success;
|
||||
|
||||
case UpdateResult.NotModified:
|
||||
return UpdateResult.NotModified;
|
||||
|
||||
case UpdateResult.Failed:
|
||||
default:
|
||||
await Task.Delay(5000);
|
||||
@@ -423,7 +432,7 @@ namespace MaaWpfGui.Models
|
||||
public static async Task<bool> UpdateFromGithubAsync()
|
||||
{
|
||||
ToastNotification.ShowDirect(LocalizationHelper.GetString("GameResourceUpdating"));
|
||||
bool download = await DownloadFullPackageAsync("https://github.com/MaaAssistantArknights/MaaResource/archive/refs/heads/main.zip", "MaaResource.zip").ConfigureAwait(false);
|
||||
bool download = await DownloadFullPackageAsync(MaaUrls.GithubResourceUpdate, "MaaResource.zip").ConfigureAwait(false);
|
||||
if (!download)
|
||||
{
|
||||
ToastNotification.ShowDirect(LocalizationHelper.GetString("GameResourceFailed"));
|
||||
@@ -482,6 +491,118 @@ namespace MaaWpfGui.Models
|
||||
return true;
|
||||
}
|
||||
|
||||
// 从 MirrorChyan 下载完整包
|
||||
public static async Task<bool> UpdateFromMirrorChyanAsync()
|
||||
{
|
||||
// https://mirrorc.top/api/resources/MaaResource/latest?current_version=<当前版本日期,从 version.json 里拿时间戳>&cdk=<cdk>&sp_id=<唯一识别码>
|
||||
// 响应格式为 {"code":0,"msg":"success","data":{"version_name":"2025-01-22 14:28:32.839","version_number":9,"url":"<增量更新网址>"}}
|
||||
var versionFilePath = Path.Combine(Environment.CurrentDirectory, "MirrorChyanResourceVersion");
|
||||
string? currentVersion = null;
|
||||
if (File.Exists(versionFilePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
currentVersion = await File.ReadAllTextAsync(versionFilePath);
|
||||
currentVersion = DateTime.Parse(currentVersion).ToString("yyyy-MM-dd+HH:mm:ss");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error("Failed to read or parse version file: " + e.Message);
|
||||
currentVersion = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Information("Version file not found, assuming first time download.");
|
||||
}
|
||||
|
||||
var cdk = SettingsViewModel.VersionUpdateSettings.MirrorChyanCdk;
|
||||
var url = currentVersion == null
|
||||
? $"{MaaUrls.MirrorChyanResourceUpdate}?cdk={cdk}"
|
||||
: $"{MaaUrls.MirrorChyanResourceUpdate}?current_version={currentVersion}&cdk={cdk}";
|
||||
|
||||
var response = await Instances.HttpService.GetAsync(new(url), logUri: false);
|
||||
if (response is null)
|
||||
{
|
||||
ToastNotification.ShowDirect(LocalizationHelper.GetString("GameResourceFailed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
var data = (JObject?)JsonConvert.DeserializeObject(json);
|
||||
if (data is null)
|
||||
{
|
||||
ToastNotification.ShowDirect(LocalizationHelper.GetString("GameResourceFailed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
var code = data["code"]?.ToString();
|
||||
if (code is not null && code != "0")
|
||||
{
|
||||
ToastNotification.ShowDirect(data["msg"]?.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
url = data["data"]?["url"]?.ToString();
|
||||
if (string.IsNullOrEmpty(url))
|
||||
{
|
||||
ToastNotification.ShowDirect(LocalizationHelper.GetString("AlreadyLatest"));
|
||||
return false;
|
||||
}
|
||||
|
||||
ToastNotification.ShowDirect(LocalizationHelper.GetString("GameResourceUpdating"));
|
||||
bool download = await DownloadFullPackageAsync(url, "MaaResource.zip").ConfigureAwait(false);
|
||||
if (!download)
|
||||
{
|
||||
ToastNotification.ShowDirect(LocalizationHelper.GetString("GameResourceFailed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 解压到 MaaResource 文件夹
|
||||
try
|
||||
{
|
||||
if (Directory.Exists("MaaResource"))
|
||||
{
|
||||
Directory.Delete("MaaResource", true);
|
||||
}
|
||||
|
||||
ZipFile.ExtractToDirectory("MaaResource.zip", "MaaResource");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error("Failed to extract MaaResource.zip: " + e.Message);
|
||||
ToastNotification.ShowDirect(LocalizationHelper.GetString("GameResourceFailed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 没有 cache 文件夹,直接复制 resource 文件夹复制到当前目录
|
||||
try
|
||||
{
|
||||
string sourcePath = new("MaaResource");
|
||||
string destinationPath = Directory.GetCurrentDirectory();
|
||||
DirectoryMerge(sourcePath, destinationPath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error("Failed to copy folders: " + e.Message);
|
||||
ToastNotification.ShowDirect(LocalizationHelper.GetString("GameResourceFailed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 删除 MaaResource 文件夹 和 MaaResource.zip
|
||||
try
|
||||
{
|
||||
Directory.Delete("MaaResource", true);
|
||||
File.Delete("MaaResource.zip");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error("Failed to delete MaaResource: " + e.Message);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static async Task<bool> DownloadFullPackageAsync(string url, string saveTo)
|
||||
{
|
||||
using var response = await Instances.HttpService.GetAsync(
|
||||
|
||||
@@ -240,6 +240,8 @@ You can cancel this popup by clicking the Settings - About Us - Issue Reporting
|
||||
<system:String x:Key="UpdateCheckNow">Check update</system:String>
|
||||
<system:String x:Key="ShowChangelog">Changelog</system:String>
|
||||
<system:String x:Key="ResourceUpdate">Resource update</system:String>
|
||||
<system:String x:Key="ResourceUpdateSource">Resource Update Source</system:String>
|
||||
<system:String x:Key="MirrorChyan">Mirror-Chyan</system:String>
|
||||
<system:String x:Key="NewVersionFoundTitle">New Version Found</system:String>
|
||||
<system:String x:Key="NewVersionFoundDescDownloading">Downloading in the background……</system:String>
|
||||
<system:String x:Key="NewVersionFoundDescId" xml:space="preserve">Version: </system:String>
|
||||
|
||||
@@ -240,6 +240,8 @@
|
||||
<system:String x:Key="UpdateCheckNow">アップデートを確認する</system:String>
|
||||
<system:String x:Key="ShowChangelog">変更履歴</system:String>
|
||||
<system:String x:Key="ResourceUpdate">リソース更新</system:String>
|
||||
<system:String x:Key="ResourceUpdateSource">リソース更新ソース</system:String>
|
||||
<system:String x:Key="MirrorChyan">ミラーちゃん</system:String>
|
||||
<system:String x:Key="NewVersionFoundTitle">新しいバージョンが見つかりました</system:String>
|
||||
<system:String x:Key="NewVersionFoundDescDownloading">バックグラウンドでダウンロードを行います……</system:String>
|
||||
<system:String x:Key="NewVersionFoundDescId" xml:space="preserve">バージョン: </system:String>
|
||||
|
||||
@@ -240,6 +240,8 @@
|
||||
<system:String x:Key="UpdateCheckNow">지금 업데이트</system:String>
|
||||
<system:String x:Key="ShowChangelog">변경 내역</system:String>
|
||||
<system:String x:Key="ResourceUpdate">리소스 업데이트</system:String>
|
||||
<system:String x:Key="ResourceUpdateSource">리소스 업데이트 소스</system:String>
|
||||
<system:String x:Key="MirrorChyan">미러짱</system:String>
|
||||
<system:String x:Key="NewVersionFoundTitle">새로운 버전 확인</system:String>
|
||||
<system:String x:Key="NewVersionFoundDescDownloading">백그라운드에서 다운로드 중……</system:String>
|
||||
<system:String x:Key="NewVersionFoundDescId" xml:space="preserve">버전: </system:String>
|
||||
|
||||
@@ -243,6 +243,8 @@
|
||||
<system:String x:Key="UpdateCheckNow">检查更新</system:String>
|
||||
<system:String x:Key="ShowChangelog">更新日志</system:String>
|
||||
<system:String x:Key="ResourceUpdate">资源更新</system:String>
|
||||
<system:String x:Key="ResourceUpdateSource">资源更新更新源</system:String>
|
||||
<system:String x:Key="MirrorChyan">Mirror 酱</system:String>
|
||||
<system:String x:Key="NewVersionFoundTitle">检测到新版本</system:String>
|
||||
<system:String x:Key="NewVersionFoundDescDownloading">正在后台下载……</system:String>
|
||||
<system:String x:Key="NewVersionFoundDescId" xml:space="preserve">版本号: </system:String>
|
||||
|
||||
@@ -240,6 +240,8 @@
|
||||
<system:String x:Key="UpdateCheckNow">檢查更新</system:String>
|
||||
<system:String x:Key="ShowChangelog">更新日誌</system:String>
|
||||
<system:String x:Key="ResourceUpdate">資源更新</system:String>
|
||||
<system:String x:Key="ResourceUpdateSource">資源更新來源</system:String>
|
||||
<system:String x:Key="MirrorChyan">Mirror 酱</system:String>
|
||||
<system:String x:Key="NewVersionFoundTitle">檢測到新版本</system:String>
|
||||
<system:String x:Key="NewVersionFoundDescDownloading">正在後臺下載……</system:String>
|
||||
<system:String x:Key="NewVersionFoundDescId" xml:space="preserve">版本號: </system:String>
|
||||
|
||||
@@ -131,7 +131,7 @@ namespace MaaWpfGui.Services.Web
|
||||
return await response.Content.ReadAsStreamAsync();
|
||||
}
|
||||
|
||||
public async Task<HttpResponseMessage?> GetAsync(Uri uri, Dictionary<string, string>? extraHeader = null, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead)
|
||||
public async Task<HttpResponseMessage?> GetAsync(Uri uri, Dictionary<string, string>? extraHeader = null, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead, bool logUri = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -146,7 +146,7 @@ namespace MaaWpfGui.Services.Web
|
||||
}
|
||||
|
||||
var response = await _client.SendAsync(request, httpCompletionOption);
|
||||
response.Log();
|
||||
response.Log(logUri);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -53,8 +53,9 @@ namespace MaaWpfGui.Services.Web
|
||||
/// <param name="uri">Target Uri</param>
|
||||
/// <param name="extraHeader">Extra HTTP Request Headers</param>
|
||||
/// <param name="httpCompletionOption">The HTTP completion option</param>
|
||||
/// <param name="logUri">Whether to log uri</param>
|
||||
/// <returns><see cref="HttpRequestMessage"/> object</returns>
|
||||
Task<HttpResponseMessage?> GetAsync(Uri uri, Dictionary<string, string>? extraHeader = null, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead);
|
||||
Task<HttpResponseMessage?> GetAsync(Uri uri, Dictionary<string, string>? extraHeader = null, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead, bool logUri = true);
|
||||
|
||||
/// <summary>
|
||||
/// Send HTTP POST request and a string response
|
||||
|
||||
@@ -26,6 +26,7 @@ using System.Windows;
|
||||
using MaaWpfGui.Constants;
|
||||
using MaaWpfGui.Helper;
|
||||
using MaaWpfGui.Main;
|
||||
using MaaWpfGui.Models;
|
||||
using MaaWpfGui.Services;
|
||||
using MaaWpfGui.States;
|
||||
using Newtonsoft.Json;
|
||||
@@ -465,6 +466,22 @@ public class VersionUpdateViewModel : Screen
|
||||
}
|
||||
*/
|
||||
|
||||
// 可以用 MirrorChyan 资源更新了喵
|
||||
switch (SettingsViewModel.VersionUpdateSettings.ResourceUpdateSource)
|
||||
{
|
||||
case "MirrorChyan":
|
||||
if (!string.IsNullOrEmpty(SettingsViewModel.VersionUpdateSettings.MirrorChyanCdk))
|
||||
{
|
||||
if (await ResourceUpdater.UpdateFromMirrorChyanAsync())
|
||||
{
|
||||
SettingsViewModel.VersionUpdateSettings.IsCheckingForUpdates = false;
|
||||
return CheckUpdateRetT.OnlyGameResourceUpdated;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
SettingsViewModel.VersionUpdateSettings.IsCheckingForUpdates = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -228,6 +228,39 @@ public class VersionUpdateSettingsUserControlModel : PropertyChangedBase
|
||||
|
||||
private bool _updateCheck = Convert.ToBoolean(ConfigurationHelper.GetGlobalValue(ConfigurationKeys.UpdateCheck, bool.TrueString));
|
||||
|
||||
public List<GenericCombinedData<string>> ResourceUpdateSourceList { get; } = [
|
||||
new() { Display = "Github", Value = "Github" },
|
||||
new() { Display = LocalizationHelper.GetString("MirrorChyan"), Value = "MirrorChyan" },
|
||||
];
|
||||
|
||||
private string _resourceUpdateSource = ConfigurationHelper.GetGlobalValue(ConfigurationKeys.ResourceUpdateSource, "Github");
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of version to update.
|
||||
/// </summary>
|
||||
public string ResourceUpdateSource
|
||||
{
|
||||
get => _resourceUpdateSource;
|
||||
set
|
||||
{
|
||||
SetAndNotify(ref _resourceUpdateSource, value);
|
||||
ConfigurationHelper.SetGlobalValue(ConfigurationKeys.ResourceUpdateSource, value.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private string _mirrorChyanCdk = SimpleEncryptionHelper.Decrypt(ConfigurationHelper.GetGlobalValue(ConfigurationKeys.MirrorChyanCdk, string.Empty));
|
||||
|
||||
public string MirrorChyanCdk
|
||||
{
|
||||
get => _mirrorChyanCdk;
|
||||
set
|
||||
{
|
||||
SetAndNotify(ref _mirrorChyanCdk, value);
|
||||
value = SimpleEncryptionHelper.Encrypt(value);
|
||||
ConfigurationHelper.SetGlobalValue(ConfigurationKeys.MirrorChyanCdk, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to check update.
|
||||
/// </summary>
|
||||
@@ -392,7 +425,26 @@ public class VersionUpdateSettingsUserControlModel : PropertyChangedBase
|
||||
public async Task ManualUpdateResource()
|
||||
{
|
||||
IsCheckingForUpdates = true;
|
||||
if (await ResourceUpdater.UpdateFromGithubAsync())
|
||||
var success = false;
|
||||
switch (ResourceUpdateSource)
|
||||
{
|
||||
case "Github":
|
||||
if (await ResourceUpdater.UpdateFromGithubAsync())
|
||||
{
|
||||
success = true;
|
||||
}
|
||||
|
||||
break;
|
||||
case "MirrorChyan":
|
||||
if (await ResourceUpdater.UpdateFromMirrorChyanAsync())
|
||||
{
|
||||
success = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
if (AutoInstallUpdatePackage)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:c="clr-namespace:CalcBinding;assembly=CalcBinding"
|
||||
xmlns:constants="clr-namespace:MaaWpfGui.Constants"
|
||||
xmlns:controls="clr-namespace:MaaWpfGui.Styles.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:dd="urn:gong-wpf-dragdrop"
|
||||
@@ -72,6 +73,35 @@
|
||||
ItemsSource="{Binding VersionTypeList}"
|
||||
SelectedValue="{Binding VersionType}"
|
||||
SelectedValuePath="Value" />
|
||||
<StackPanel>
|
||||
<hc:ComboBox
|
||||
Width="150"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
hc:TitleElement.Title="{DynamicResource ResourceUpdateSource}"
|
||||
DisplayMemberPath="Display"
|
||||
ItemsSource="{Binding ResourceUpdateSourceList}"
|
||||
SelectedValue="{Binding ResourceUpdateSource}"
|
||||
SelectedValuePath="Value" />
|
||||
<StackPanel Visibility="{c:Binding 'ResourceUpdateSource == "MirrorChyan"'}">
|
||||
<hc:TextBox
|
||||
Width="150"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
hc:TitleElement.Title="CDK"
|
||||
Text="{Binding MirrorChyanCdk}" />
|
||||
<controls:TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<Hyperlink
|
||||
Cursor="Hand"
|
||||
NavigateUri="{Binding Source={x:Static constants:MaaUrls.MirrorChyanWebsite}}"
|
||||
TextDecorations="None">
|
||||
<TextBlock Text="{DynamicResource MirrorChyan}" />
|
||||
</Hyperlink>
|
||||
</controls:TextBlock>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
|
||||
Reference in New Issue
Block a user