Files
YMhut-box-C-/src/box-winUI/Views/Tools/DevEnvironmentConfigToolPage.cs
T

618 lines
28 KiB
C#

using System.Diagnostics;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.System;
using YMhut.Box.Core.DevEnvironments;
using YMhut.Box.Core.Downloads;
using YMhut.Box.Core.Tools;
using YMhut.Box.WinUI.Services;
using YMhut.Box.WinUI.ViewModels.Tools;
namespace YMhut.Box.WinUI.Views.Tools;
public sealed class DevEnvironmentConfigToolViewModel(IToolModule module) : AdaptiveToolViewModel(module);
public sealed class DevEnvironmentConfigToolPage : ToolPageBase
{
private readonly IToolModule _module;
private readonly Action? _goBack;
private readonly IDevEnvironmentDetectionService _detection = AppServices.GetRequiredService<IDevEnvironmentDetectionService>();
private readonly IDevEnvironmentCatalogService _catalog = AppServices.GetRequiredService<IDevEnvironmentCatalogService>();
private readonly IDevTerminalSetupService _terminalSetup = AppServices.GetRequiredService<IDevTerminalSetupService>();
private readonly IDownloadManagerService _downloads = AppServices.GetRequiredService<IDownloadManagerService>();
private readonly StackPanel _environmentList = new() { Spacing = 12 };
private readonly TextBlock _statusText = ModernUi.Text(AppLocalizer.T("准备检测开发环境。", "Ready to detect development environments."), 14, foreground: ModernUi.TextSecondary);
private readonly Dictionary<string, ComboBox> _versionBoxes = new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, ComboBox> _sourceBoxes = new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, Button> _quickButtons = new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, Button> _sourceButtons = new(StringComparer.OrdinalIgnoreCase);
private readonly TextBlock _terminalStatus = ModernUi.Text(AppLocalizer.T("正在检测终端环境...", "Checking terminal environment..."), 14, FontWeights.SemiBold);
private readonly TextBlock _terminalDetail = ModernUi.Text(string.Empty, 12, foreground: ModernUi.TextSecondary, maxLines: 3);
private readonly ProgressBar _terminalProgress = new() { Minimum = 0, Maximum = 100, Visibility = Visibility.Collapsed };
private Button? _terminalInstallButton;
private Button? _terminalOpenButton;
private DevTerminalSnapshot? _terminalSnapshot;
private IReadOnlyDictionary<string, DetectedDevEnvironment> _detected = new Dictionary<string, DetectedDevEnvironment>();
private IReadOnlyDictionary<string, IReadOnlyList<DevEnvironmentVersion>> _versions = new Dictionary<string, IReadOnlyList<DevEnvironmentVersion>>();
public DevEnvironmentConfigToolPage(IToolModule module, DevEnvironmentConfigToolViewModel viewModel, Action? goBack = null)
{
_module = module;
_goBack = goBack;
BindModule(module);
Background = ModernUi.AppBackground;
Content = BuildContent();
Loaded += async (_, _) => await ReloadAsync();
}
private UIElement BuildContent()
{
var root = new StackPanel
{
Padding = new Thickness(24, 20, 24, 28),
Spacing = 18
};
var reload = ModernUi.PillButton(AppLocalizer.T("重新检测", "Detect again"), "\uE895", async () => await ReloadAsync(), primary: true);
var downloads = ModernUi.PillButton(AppLocalizer.T("打开下载管理", "Open downloads"), "\uE896", OpenDownloadManager);
var actionRow = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8, Children = { reload, downloads } };
root.Children.Add(ModernUi.PageHeader(
AppLocalizer.T("开发环境配置", "Development Environment Setup"),
AppLocalizer.T("检测常用开发环境,并从官方来源选择版本和下载源。", "Detect common development environments and select versions from official sources."),
"\uE943",
actions: actionRow,
back: _goBack,
backTooltip: AppLocalizer.T("返回上一级", "Back"),
meta: _statusText));
root.Children.Add(BuildTerminalSetupCard());
root.Children.Add(_environmentList);
return new ScrollViewer
{
HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled,
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
Content = root
};
}
private Border BuildTerminalSetupCard()
{
_terminalInstallButton = ModernUi.PillButton(
AppLocalizer.T("安装 / 修复", "Install / repair"),
"\uE896",
async () => await InstallTerminalAsync(),
primary: true);
_terminalOpenButton = ModernUi.PillButton(
AppLocalizer.T("打开终端", "Open terminal"),
"\uE756",
async () => await OpenTerminalAsync());
var refresh = ModernUi.IconButton("\uE72C", AppLocalizer.T("重新检测终端", "Check terminal again"), async () => await RefreshTerminalAsync());
var actions = new StackPanel
{
Orientation = Orientation.Horizontal,
Spacing = 8,
Children = { _terminalInstallButton, _terminalOpenButton, refresh }
};
var top = new Grid { ColumnSpacing = 14 };
top.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
top.ColumnDefinitions.Add(new ColumnDefinition());
top.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
top.Children.Add(ModernUi.IconTile("\uE756", 44, ModernUi.AccentSoft, ModernUi.Accent, 19));
var copy = new StackPanel
{
Spacing = 3,
Children =
{
ModernUi.Text(AppLocalizer.T("Windows 终端环境", "Windows terminal environment"), 18, FontWeights.SemiBold),
_terminalStatus,
_terminalDetail
}
};
Grid.SetColumn(copy, 1);
top.Children.Add(copy);
Grid.SetColumn(actions, 2);
top.Children.Add(actions);
return ModernUi.Card(new StackPanel
{
Spacing = 12,
Children =
{
top,
_terminalProgress,
ModernUi.Text(
AppLocalizer.T(
"安装 Windows Terminal 与 PowerShell 7;系统支持时自动设置默认终端和默认 PowerShell 配置。",
"Install Windows Terminal and PowerShell 7, then configure supported default terminal settings."),
12,
foreground: ModernUi.TextSecondary,
maxLines: 2)
}
}, new Thickness(16), radius: 8);
}
private async Task ReloadAsync()
{
_statusText.Text = AppLocalizer.T("正在检测本机环境并读取官方版本信息...", "Detecting local environments and reading official version metadata...");
_environmentList.Children.Clear();
_environmentList.Children.Add(ModernUi.Card(ModernUi.Text(AppLocalizer.T("检测中...", "Detecting..."), 14, foreground: ModernUi.TextSecondary), new Thickness(14), radius: 8));
try
{
var terminalTask = _terminalSetup.DetectAsync();
var detectedTask = _detection.DetectAsync();
var detected = await detectedTask;
_detected = detected.ToDictionary(item => item.Id, StringComparer.OrdinalIgnoreCase);
var versionPairs = await Task.WhenAll(_catalog.Definitions.Select(async definition =>
{
var versions = await _catalog.GetVersionsAsync(definition.Id);
return (definition.Id, Versions: versions);
}));
_versions = versionPairs.ToDictionary(item => item.Id, item => item.Versions, StringComparer.OrdinalIgnoreCase);
ApplyTerminalSnapshot(await terminalTask);
Render();
}
catch (Exception exception)
{
_statusText.Text = AppLocalizer.T("开发环境检测失败。", "Development environment detection failed.");
_environmentList.Children.Clear();
_environmentList.Children.Add(ModernUi.Card(
ModernUi.Text(AppLocalizer.SanitizeSensitiveText(exception.Message, 180), 14, foreground: ModernUi.Danger),
new Thickness(14),
radius: 8));
}
}
private async Task RefreshTerminalAsync()
{
SetTerminalBusy(true, AppLocalizer.T("正在检测终端环境...", "Checking terminal environment..."));
try
{
ApplyTerminalSnapshot(await _terminalSetup.DetectAsync());
}
catch (Exception exception)
{
_terminalStatus.Text = AppLocalizer.T("终端检测失败", "Terminal check failed");
_terminalDetail.Text = AppLocalizer.SanitizeSensitiveText(exception.Message, 180);
}
finally
{
SetTerminalBusy(false);
}
}
private async Task InstallTerminalAsync()
{
if (XamlRoot is null)
{
return;
}
var dialog = new ContentDialog
{
XamlRoot = XamlRoot,
Title = AppLocalizer.T("安装终端套件?", "Install terminal suite?"),
Content = ModernUi.Text(
AppLocalizer.T(
"将安装或升级 Windows Terminal 与 PowerShell 7,并在系统支持时更新当前用户的 PATH 和默认终端设置。安装包仅从微软官方来源获取。",
"Windows Terminal and PowerShell 7 will be installed or upgraded from official Microsoft sources. Supported PATH and default-terminal settings will be updated for the current user."),
13,
foreground: ModernUi.TextSecondary,
maxLines: 5),
PrimaryButtonText = AppLocalizer.T("继续", "Continue"),
CloseButtonText = AppLocalizer.T("取消", "Cancel"),
DefaultButton = ContentDialogButton.Primary
};
if (await dialog.ShowAsync() != ContentDialogResult.Primary)
{
return;
}
SetTerminalBusy(true, AppLocalizer.T("正在准备终端安装...", "Preparing terminal setup..."));
try
{
var progress = new Progress<DevTerminalInstallProgress>(item =>
{
_terminalProgress.Value = item.Percent;
_terminalStatus.Text = AppLocalizer.T(item.Message switch
{
"Checking the current terminal environment..." => "正在检查当前终端环境...",
"Installing or upgrading Windows Terminal..." => "正在安装或升级 Windows Terminal...",
"Installing or upgrading PowerShell 7..." => "正在安装或升级 PowerShell 7...",
"Refreshing PATH and verifying terminal commands..." => "正在刷新 PATH 并验证终端命令...",
"Configuring Windows Terminal defaults..." => "正在配置默认终端...",
"Terminal setup completed." => "终端配置已完成。",
_ => item.Message
}, item.Message);
});
var result = await _terminalSetup.InstallOrRepairAsync(progress);
ApplyTerminalSnapshot(result.Snapshot);
if (result.Succeeded)
{
ToastService.Show(AppLocalizer.T("终端套件已配置。", "Terminal suite configured."), ToastKind.Success);
}
else
{
_terminalDetail.Text = string.Join(" ", result.Messages.Append(AppLocalizer.T("请查看状态后重试失败的组件。", "Review the status and retry the failed component.")));
ToastService.Show(AppLocalizer.T("终端套件仅完成了部分配置。", "Terminal setup completed partially."), ToastKind.Warning);
}
}
catch (Exception exception)
{
_terminalStatus.Text = AppLocalizer.T("终端安装失败", "Terminal setup failed");
_terminalDetail.Text = AppLocalizer.SanitizeSensitiveText(exception.Message, 200);
ToastService.Show(AppLocalizer.T("终端安装失败,请查看详细状态。", "Terminal setup failed. Review the status details."), ToastKind.Warning);
}
finally
{
SetTerminalBusy(false);
}
}
private async Task OpenTerminalAsync()
{
if (!await _terminalSetup.OpenTerminalAsync())
{
ToastService.Show(AppLocalizer.T("无法启动终端。", "Could not start a terminal."), ToastKind.Warning);
}
}
private void ApplyTerminalSnapshot(DevTerminalSnapshot snapshot)
{
_terminalSnapshot = snapshot;
var terminal = snapshot.WindowsTerminalInstalled
? $"Windows Terminal {ValueOrDash(snapshot.WindowsTerminalVersion)}"
: AppLocalizer.T("Windows Terminal 未安装", "Windows Terminal not installed");
var powershell = snapshot.PowerShellInstalled
? $"PowerShell {ValueOrDash(snapshot.PowerShellVersion)}"
: AppLocalizer.T("PowerShell 7 未安装", "PowerShell 7 not installed");
_terminalStatus.Text = $"{terminal} · {powershell}";
var defaultState = !snapshot.SupportsDefaultTerminal
? AppLocalizer.T($"Windows {snapshot.WindowsBuild} 不支持系统默认终端切换", $"Windows {snapshot.WindowsBuild} does not support changing the system default terminal")
: snapshot.IsWindowsTerminalDefault
? AppLocalizer.T("Windows Terminal 已设为默认", "Windows Terminal is the default")
: AppLocalizer.T("尚未设为默认终端", "Not yet the default terminal");
var profileState = snapshot.IsPowerShellDefaultProfile
? AppLocalizer.T("PowerShell 7 默认配置已启用", "PowerShell 7 default profile enabled")
: AppLocalizer.T("PowerShell 7 默认配置待设置", "PowerShell 7 default profile pending");
_terminalDetail.Text = $"{defaultState} · {profileState} · {snapshot.Architecture}";
if (_terminalOpenButton is not null)
{
_terminalOpenButton.IsEnabled = snapshot.PowerShellInstalled || snapshot.WindowsTerminalInstalled;
}
}
private void SetTerminalBusy(bool busy, string? message = null)
{
if (_terminalInstallButton is not null)
{
_terminalInstallButton.IsEnabled = !busy;
}
if (_terminalOpenButton is not null)
{
_terminalOpenButton.IsEnabled = !busy && (_terminalSnapshot?.IsReady == true || _terminalSnapshot?.PowerShellInstalled == true);
}
_terminalProgress.Visibility = busy ? Visibility.Visible : Visibility.Collapsed;
_terminalProgress.IsIndeterminate = busy && _terminalProgress.Value <= 0;
if (!string.IsNullOrWhiteSpace(message))
{
_terminalStatus.Text = message;
}
}
private static string ValueOrDash(string value) => string.IsNullOrWhiteSpace(value) ? "--" : value;
private void Render()
{
_versionBoxes.Clear();
_sourceBoxes.Clear();
_quickButtons.Clear();
_sourceButtons.Clear();
_environmentList.Children.Clear();
var installed = _detected.Values.Count(item => item.IsInstalled);
_statusText.Text = AppLocalizer.T($"已检测到 {installed} 个开发环境。", $"{installed} development environments detected.");
foreach (var definition in _catalog.Definitions)
{
_environmentList.Children.Add(BuildEnvironmentCard(definition));
}
}
private Border BuildEnvironmentCard(DevEnvironmentDefinition definition)
{
_detected.TryGetValue(definition.Id, out var detected);
_versions.TryGetValue(definition.Id, out var versions);
versions ??= [];
var box = new ComboBox
{
MinWidth = 190,
PlaceholderText = AppLocalizer.T("选择版本", "Select version"),
HorizontalAlignment = HorizontalAlignment.Stretch
};
foreach (var version in versions.Take(30))
{
box.Items.Add(new ComboBoxItem
{
Content = VersionLabel(version),
Tag = version
});
}
if (box.Items.Count > 0)
{
box.SelectedIndex = 0;
}
_versionBoxes[definition.Id] = box;
var sourceBox = new ComboBox
{
MinWidth = 220,
PlaceholderText = AppLocalizer.T("选择下载源", "Select source"),
HorizontalAlignment = HorizontalAlignment.Stretch
};
_sourceBoxes[definition.Id] = sourceBox;
var quick = ModernUi.PillButton(AppLocalizer.T("快速安装", "Quick install"), "\uE896", async () => await QueueDownloadAsync(definition, DevEnvironmentInstallMode.QuickInstall), primary: true);
var source = ModernUi.PillButton(AppLocalizer.T("源码/编译", "Source build"), "\uE756", async () => await QueueDownloadAsync(definition, DevEnvironmentInstallMode.SourceBuild));
var official = ModernUi.IconButton("\uE8A7", AppLocalizer.T("打开官网", "Open official site"), async () => await Launcher.LaunchUriAsync(new Uri(definition.OfficialHome)));
_quickButtons[definition.Id] = quick;
_sourceButtons[definition.Id] = source;
var top = new Grid { ColumnSpacing = 14 };
top.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
top.ColumnDefinitions.Add(new ColumnDefinition());
top.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
top.Children.Add(ModernUi.IconTile("\uE943", 42, ModernUi.AccentSoft, ModernUi.Accent, 18));
var titlePanel = new StackPanel
{
Spacing = 3,
Children =
{
ModernUi.Text(definition.Name, 18, FontWeights.SemiBold, maxLines: 1),
ModernUi.Text(DetectionLine(detected), 13, foreground: detected?.IsInstalled == true ? ModernUi.Success : ModernUi.TextSecondary, maxLines: 2)
}
};
Grid.SetColumn(titlePanel, 1);
top.Children.Add(titlePanel);
var actions = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8, Children = { official } };
Grid.SetColumn(actions, 2);
top.Children.Add(actions);
var controls = new Grid { ColumnSpacing = 10, RowSpacing = 10 };
controls.ColumnDefinitions.Add(new ColumnDefinition());
controls.ColumnDefinitions.Add(new ColumnDefinition());
controls.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
controls.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
controls.Children.Add(box);
Grid.SetColumn(sourceBox, 1);
controls.Children.Add(sourceBox);
Grid.SetColumn(quick, 2);
Grid.SetColumn(source, 3);
controls.Children.Add(quick);
controls.Children.Add(source);
box.SelectionChanged += (_, _) => RefreshSourceChoices(definition.Id);
sourceBox.SelectionChanged += (_, _) => RefreshActionState(definition.Id);
RefreshSourceChoices(definition.Id);
return ModernUi.Card(new StackPanel
{
Spacing = 12,
Children =
{
top,
ModernUi.Text(AppLocalizer.T($"版本来源:{definition.VersionSource}", $"Version source: {definition.VersionSource}"), 12, foreground: ModernUi.TextSecondary, maxLines: 1),
controls
}
}, new Thickness(16), radius: 8);
}
private async Task QueueDownloadAsync(DevEnvironmentDefinition definition, DevEnvironmentInstallMode mode)
{
var version = SelectedVersion(definition.Id);
if (version is null)
{
ToastService.Show(AppLocalizer.T("没有可下载的版本。", "No downloadable version is available."), ToastKind.Warning);
return;
}
var candidate = SelectedCandidate(definition.Id, mode);
if (candidate is null)
{
ToastService.Show(AppLocalizer.T("请选择可用的下载源。", "Select an available download source."), ToastKind.Warning);
return;
}
if (string.IsNullOrWhiteSpace(candidate.Source.Url) ||
!Uri.TryCreate(candidate.Source.Url, UriKind.Absolute, out var uri) ||
uri.Scheme is not ("http" or "https"))
{
await Launcher.LaunchUriAsync(new Uri(definition.OfficialHome));
ToastService.Show(AppLocalizer.T("该来源需要在官网手动选择下载。", "This source requires manual selection on the official site."), ToastKind.Warning);
return;
}
if (string.Equals(candidate.Source.SourceKind, "Manual", StringComparison.OrdinalIgnoreCase) ||
string.Equals(Path.GetExtension(candidate.Source.FileName), ".url", StringComparison.OrdinalIgnoreCase))
{
await Launcher.LaunchUriAsync(uri);
ToastService.Show(AppLocalizer.T("已打开官方下载安装页面。", "Opened the official download page."), ToastKind.Success);
return;
}
var plan = _catalog.CreateInstallPlan(definition.Id, version, candidate);
var item = await _downloads.EnqueueAsync(
candidate.Source,
new DownloadOptions(
InstallCommand: mode == DevEnvironmentInstallMode.QuickInstall ? "installer" : null,
InstallArguments: plan.InstallArguments,
IsInstaller: mode == DevEnvironmentInstallMode.QuickInstall,
DeleteAfterInstall: mode == DevEnvironmentInstallMode.QuickInstall,
OpenKind: mode == DevEnvironmentInstallMode.QuickInstall ? DownloadOpenKind.Installer : DownloadOpenKind.File));
ToastService.Show(AppLocalizer.T("已加入下载管理。", "Added to Download Manager."), ToastKind.Success);
if (mode == DevEnvironmentInstallMode.SourceBuild)
{
OpenBuildTerminal(plan, item);
}
}
private DevEnvironmentVersion? SelectedVersion(string id)
{
if (!_versionBoxes.TryGetValue(id, out var box) ||
box.SelectedItem is not ComboBoxItem item ||
item.Tag is not DevEnvironmentVersion version)
{
return null;
}
return version;
}
private DevEnvironmentDownloadCandidate? SelectedCandidate(string id, DevEnvironmentInstallMode mode)
{
if (!_sourceBoxes.TryGetValue(id, out var box) ||
box.SelectedItem is not ComboBoxItem item ||
item.Tag is not DevEnvironmentDownloadCandidate candidate)
{
return SelectedVersion(id)?.AllCandidates.FirstOrDefault(candidate => candidate.Mode == mode);
}
if (candidate.Mode == mode)
{
return candidate;
}
return SelectedVersion(id)?.AllCandidates.FirstOrDefault(candidate => candidate.Mode == mode);
}
private void RefreshSourceChoices(string id)
{
if (!_sourceBoxes.TryGetValue(id, out var box))
{
return;
}
box.Items.Clear();
var version = SelectedVersion(id);
if (version is not null)
{
foreach (var candidate in version.AllCandidates)
{
box.Items.Add(new ComboBoxItem
{
Content = CandidateLabel(candidate),
Tag = candidate
});
}
}
if (box.Items.Count > 0)
{
box.SelectedIndex = 0;
}
RefreshActionState(id);
}
private void RefreshActionState(string id)
{
var version = SelectedVersion(id);
var hasQuick = version?.AllCandidates.Any(candidate => candidate.Mode == DevEnvironmentInstallMode.QuickInstall) == true;
var hasSource = version?.AllCandidates.Any(candidate => candidate.Mode == DevEnvironmentInstallMode.SourceBuild) == true &&
!string.Equals(id, "docker", StringComparison.OrdinalIgnoreCase);
if (_quickButtons.TryGetValue(id, out var quick))
{
quick.IsEnabled = hasQuick;
}
if (_sourceButtons.TryGetValue(id, out var source))
{
source.Visibility = hasSource ? Visibility.Visible : Visibility.Collapsed;
source.IsEnabled = hasSource;
}
}
private static void OpenBuildTerminal(DevEnvironmentInstallPlan plan, DownloadItem item)
{
try
{
var script = Path.Combine(Path.GetTempPath(), $"ymhut-build-{plan.EnvironmentId}-{Guid.NewGuid():N}.cmd");
File.WriteAllLines(script, [
"@echo off",
"chcp 65001 > nul",
$"echo {plan.EnvironmentName} {plan.Version.Version}",
$"echo Source download target: {item.TargetPath}",
"echo.",
"if not exist \"%~dp0\" mkdir \"%~dp0\"",
$"set \"ARCHIVE={item.TargetPath}\"",
$"set \"WORKDIR=%USERPROFILE%\\YMhutBuilds\\{plan.EnvironmentId}-{plan.Version.Version}\"",
"echo Waiting for source archive...",
":wait_download",
"if not exist \"%ARCHIVE%\" (timeout /t 2 > nul & goto wait_download)",
"mkdir \"%WORKDIR%\" 2>nul",
"cd /d \"%WORKDIR%\"",
"echo.",
"echo Build recipe:",
$"echo {plan.BuildRecipe.Replace(Environment.NewLine, " & echo ")}",
"echo.",
"echo Extract the archive here, review prerequisites, then run the vendor build commands above.",
"echo.",
"pause"
]);
Process.Start(new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/k \"{script}\"",
UseShellExecute = true
});
}
catch (Exception exception)
{
ToastService.Show(AppLocalizer.SanitizeSensitiveText(exception.Message, 140), ToastKind.Warning);
}
}
private static string DetectionLine(DetectedDevEnvironment? detected)
{
if (detected is null || !detected.IsInstalled)
{
return AppLocalizer.T("未检测到,可选择官方版本下载。", "Not detected. Choose an official version to download.");
}
var path = string.IsNullOrWhiteSpace(detected.Path) ? detected.Source : detected.Path;
return string.IsNullOrWhiteSpace(path)
? AppLocalizer.T($"已安装:{detected.Version}", $"Installed: {detected.Version}")
: AppLocalizer.T($"已安装:{detected.Version} · {path}", $"Installed: {detected.Version} · {path}");
}
private static string VersionLabel(DevEnvironmentVersion version)
{
return version.PublishedAt is DateTimeOffset date
? $"{version.Version} · {date:yyyy-MM-dd}"
: version.Version;
}
private static string CandidateLabel(DevEnvironmentDownloadCandidate candidate)
{
var size = candidate.Source.SizeBytes is long bytes ? $" · {DownloadFormat.FormatBytes(bytes)}" : string.Empty;
var region = string.IsNullOrWhiteSpace(candidate.Region) ? string.Empty : $" · {candidate.Region}";
var mode = candidate.Mode == DevEnvironmentInstallMode.SourceBuild
? AppLocalizer.T("源码", "Source")
: AppLocalizer.T("安装包", "Installer");
return $"{mode} · {candidate.SourceType}{region}{size}";
}
private static void OpenDownloadManager()
{
if (App.CurrentWindow is MainWindow mainWindow)
{
mainWindow.ShowDownloadManagerPage();
}
}
}