初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
<Grid
|
||||
x:Class="PCL.MyLocalCompItem"
|
||||
x:Name="PanBack"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL;assembly="
|
||||
Height="44"
|
||||
Background="{StaticResource ColorBrushSemiTransparent}"
|
||||
RenderTransformOrigin="0.5,0.5"
|
||||
SnapsToDevicePixels="True">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="6" />
|
||||
<ColumnDefinition Width="34" />
|
||||
<ColumnDefinition Width="7" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition x:Name="ColumnPaddingRight" Width="4" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="1*" />
|
||||
<RowDefinition Height="20" />
|
||||
<RowDefinition Height="18" />
|
||||
<RowDefinition Height="1*" />
|
||||
</Grid.RowDefinitions>
|
||||
<!-- Logo -->
|
||||
<local:MyImage
|
||||
x:Name="PathLogo"
|
||||
Grid.Row="1"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
Width="34"
|
||||
Height="34"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="6"
|
||||
FallbackSource="pack://application:,,,/images/Icons/NoIcon.png"
|
||||
IsHitTestVisible="False"
|
||||
RenderOptions.BitmapScalingMode="HighQuality"
|
||||
SnapsToDevicePixels="True" />
|
||||
<!-- 标题 -->
|
||||
<Grid
|
||||
x:Name="PanTitle"
|
||||
Grid.Row="1"
|
||||
Grid.Column="3"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0,1,0,0"
|
||||
VerticalAlignment="Center"
|
||||
SnapsToDevicePixels="False"
|
||||
UseLayoutRounding="False">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition x:Name="ColumnTitle" Width="Auto" />
|
||||
<ColumnDefinition x:Name="ColumnSubtitle" Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition x:Name="ColumnExtend" Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
x:Name="LabTitle"
|
||||
FontSize="14"
|
||||
IsHitTestVisible="False"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
<TextBlock
|
||||
x:Name="LabSubtitle"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource ColorBrushGray1}"
|
||||
IsHitTestVisible="False"
|
||||
Opacity="0.4"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
Visibility="Collapsed" />
|
||||
<!-- 更新按钮 -->
|
||||
<local:MyIconButton
|
||||
x:Name="BtnUpdate"
|
||||
Grid.Column="2"
|
||||
Width="21"
|
||||
Height="21"
|
||||
Margin="-2,-1.6,0,0"
|
||||
SvgIcon="lucide/upload"
|
||||
Opacity="0.4"
|
||||
Theme="Black"
|
||||
ToolTipService.InitialShowDelay="100"
|
||||
ToolTipService.Placement="Right"
|
||||
ToolTipService.VerticalOffset="-9"
|
||||
Visibility="Collapsed" />
|
||||
</Grid>
|
||||
<!-- Tag 与 详情 -->
|
||||
<StackPanel
|
||||
x:Name="PanTags"
|
||||
Grid.Row="2"
|
||||
Grid.Column="3"
|
||||
Margin="-1,0,1,0"
|
||||
VerticalAlignment="Center"
|
||||
IsHitTestVisible="False"
|
||||
Orientation="Horizontal"
|
||||
Visibility="Collapsed">
|
||||
<!--<corelocal:BlurBorder Background="{DynamicResource ColorBrush6}" Padding="3,1" CornerRadius="3" Margin="0,0,4,0" SnapsToDevicePixels="True" UseLayoutRounding="False">
|
||||
<TextBlock Text="科技" Foreground="{DynamicResource ColorBrush2}" FontSize="11" />
|
||||
</corelocal:BlurBorder>-->
|
||||
</StackPanel>
|
||||
<TextBlock
|
||||
x:Name="LabInfo"
|
||||
Grid.Row="2"
|
||||
Grid.Column="4"
|
||||
Margin="0,0,3,1"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource ColorBrushGray4}"
|
||||
IsHitTestVisible="False"
|
||||
SnapsToDevicePixels="False"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
UseLayoutRounding="False" />
|
||||
</Grid>
|
||||
@@ -0,0 +1,886 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Microsoft.VisualBasic;
|
||||
using PCL.Core.App;
|
||||
using PCL.Core.Utils;
|
||||
using PCL.Core.Utils.Exts;
|
||||
using PCL.Core.App.Localization;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
public partial class MyLocalCompItem
|
||||
{
|
||||
private string GetUpdateCompareDescription()
|
||||
{
|
||||
var currentName = Entry.compFile.FileName.Replace(".jar", "");
|
||||
var newestName = Entry.UpdateFile.FileName.Replace(".jar", "");
|
||||
// 简化名称对比
|
||||
var currentSegs = currentName.Split('-').ToList();
|
||||
var newestSegs = newestName.Split('-').ToList();
|
||||
var shortened = false;
|
||||
foreach (var Seg in currentSegs.ToList())
|
||||
{
|
||||
if (!newestSegs.Contains(Seg))
|
||||
continue;
|
||||
currentSegs.Remove(Seg);
|
||||
newestSegs.Remove(Seg);
|
||||
shortened = true;
|
||||
}
|
||||
|
||||
if (shortened && currentSegs.Any() && newestSegs.Any())
|
||||
{
|
||||
currentName = currentSegs.Join("-");
|
||||
newestName = newestSegs.Join("-");
|
||||
Entry._Version = currentName; // 使用网络信息作为显示的版本号
|
||||
}
|
||||
|
||||
return
|
||||
Lang.Text("Instance.Resource.Item.UpdateCompare", currentName, Lang.TimeSpan(Entry.compFile.ReleaseDate - DateTime.Now), newestName, Lang.TimeSpan(Entry.UpdateFile.ReleaseDate - DateTime.Now));
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Func<Task>(async () =>
|
||||
{
|
||||
// 更新
|
||||
if (Entry.CanUpdate)
|
||||
{
|
||||
BtnUpdate.Visibility = Visibility.Visible;
|
||||
BtnUpdate.ToolTip = $"{GetUpdateCompareDescription()}\r\n{Lang.Text("Instance.Resource.Item.UpdateToolTip")}";
|
||||
}
|
||||
else
|
||||
{
|
||||
BtnUpdate.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
// 标题与描述
|
||||
string descFileName;
|
||||
if (Entry.IsFolder)
|
||||
// 文件夹项的特殊处理
|
||||
descFileName = Entry.Name;
|
||||
else
|
||||
switch (Entry.State)
|
||||
{
|
||||
case ModLocalComp.LocalCompFile.LocalFileStatus.Fine:
|
||||
{
|
||||
descFileName = ModBase.GetFileNameWithoutExtentionFromPath(Entry.path);
|
||||
break;
|
||||
}
|
||||
case ModLocalComp.LocalCompFile.LocalFileStatus.Disabled:
|
||||
{
|
||||
descFileName =
|
||||
ModBase.GetFileNameWithoutExtentionFromPath(Entry.path.Replace(".disabled", "")
|
||||
.Replace(".old", "")); // McMod.McModState.Unavailable
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
descFileName = ModBase.GetFileNameFromPath(Entry.path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
string newDescription;
|
||||
var compTemp = Entry.Comp;
|
||||
if (Entry.IsFolder)
|
||||
{
|
||||
// 文件夹项的特殊显示
|
||||
Title = Entry.Name;
|
||||
newDescription = Entry.Description;
|
||||
}
|
||||
else if (Config.Download.Comp.UiCompNameSolution == 1)
|
||||
{
|
||||
// 标题显示文件名,详情显示译名
|
||||
// 标题
|
||||
Title = descFileName;
|
||||
SubTitle = "";
|
||||
// 描述
|
||||
if (Entry.Comp is null)
|
||||
{
|
||||
newDescription = Entry.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
var titles = await Task.Run(() => compTemp.GetControlTitle(false));
|
||||
newDescription = titles.Key + titles.Value;
|
||||
}
|
||||
|
||||
newDescription = newDescription.Replace(" | ", " / ");
|
||||
if (Entry.Version is not null)
|
||||
newDescription += $" ({Entry.Version})";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 标题显示译名,详情显示文件名
|
||||
// 标题
|
||||
if (Entry.Comp is null)
|
||||
{
|
||||
Title = Entry.Name;
|
||||
SubTitle = Entry.Version is null ? "" : " | " + Entry.Version;
|
||||
}
|
||||
else
|
||||
{
|
||||
var titles = await Task.Run(() => compTemp.GetControlTitle(false));
|
||||
Title = titles.Key;
|
||||
SubTitle = titles.Value + (Entry.Version is null ? "" : " | " + Entry.Version);
|
||||
}
|
||||
|
||||
// 描述
|
||||
newDescription = descFileName;
|
||||
}
|
||||
|
||||
if (Entry.Comp is not null)
|
||||
newDescription += ": " + Entry.Comp.Description.Replace("\r", "").Replace("\n", "");
|
||||
else if (Entry.Description is not null)
|
||||
newDescription += ": " + Entry.Description.Replace("\r", "").Replace("\n", "");
|
||||
else if (!Entry.IsFileAvailable) newDescription += ": " + Lang.Text("Instance.Resource.Item.InfoUnavailable");
|
||||
Description = newDescription;
|
||||
if (Checked)
|
||||
LabTitle.SetResourceReference(TextBlock.ForegroundProperty,
|
||||
Entry.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine ? "ColorBrush2" : "ColorBrush5");
|
||||
else
|
||||
LabTitle.SetResourceReference(TextBlock.ForegroundProperty,
|
||||
Entry.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine ? "ColorBrush1" : "ColorBrushGray4");
|
||||
// 主 Logo
|
||||
Logo = Entry.GetLogo();
|
||||
|
||||
// 图标右下角的 Logo
|
||||
if (Entry.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine)
|
||||
{
|
||||
if (imgState is not null)
|
||||
{
|
||||
Children.Remove(imgState);
|
||||
imgState = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (imgState is null)
|
||||
{
|
||||
imgState = new Image
|
||||
{
|
||||
Width = 20d,
|
||||
Height = 20d,
|
||||
Margin = new Thickness(0d, 0d, -5, -3),
|
||||
IsHitTestVisible = false,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
VerticalAlignment = VerticalAlignment.Bottom
|
||||
};
|
||||
RenderOptions.SetBitmapScalingMode(imgState, BitmapScalingMode.HighQuality);
|
||||
SetColumn(imgState, 1);
|
||||
SetRow(imgState, 1);
|
||||
SetRowSpan(imgState, 2);
|
||||
Children.Add(imgState);
|
||||
// <Image x:Name="ImgState" RenderOptions.BitmapScalingMode="HighQuality" Width="16" Height="16" Margin="0,0,-3,-1"
|
||||
// Grid.Column="1" Grid.Row="1" Grid.RowSpan="2" IsHitTestVisible="False"
|
||||
// HorizontalAlignment="Right" VerticalAlignment="Bottom"
|
||||
// Source="/Images/Icons/Unavailable.png" />
|
||||
}
|
||||
|
||||
imgState.Source = new MyBitmap(ModBase.pathImage + $"Icons/{Entry.State}.png");
|
||||
}
|
||||
|
||||
// 标签
|
||||
if (Entry.IsFolder)
|
||||
// 为文件夹添加标签
|
||||
Tags = new List<string> { Lang.Text("Instance.Resource.Item.FolderTag") };
|
||||
else if (Entry.Comp is not null) Tags = Entry.Comp.Tags;
|
||||
}));
|
||||
}
|
||||
|
||||
public void RefreshColor(object sender, EventArgs e)
|
||||
{
|
||||
InitLate(sender, e);
|
||||
// 触发颜色动画
|
||||
var time = IsMouseOver ? 120 : 180;
|
||||
var ani = new List<ModAnimation.AniData>();
|
||||
// ButtonStack
|
||||
if (buttonStack is not null)
|
||||
{
|
||||
if (IsMouseOver)
|
||||
{
|
||||
ani.Add(ModAnimation.AaOpacity(buttonStack, 1d - buttonStack.Opacity, (int)Math.Round(time * 0.7d),
|
||||
(int)Math.Round(time * 0.3d)));
|
||||
ani.Add(ModAnimation.AaDouble(
|
||||
i => ColumnPaddingRight.Width =
|
||||
new GridLength(Math.Max(0, ColumnPaddingRight.Width.Value + (double)i)),
|
||||
5 + Buttons.Count() * 25 - ColumnPaddingRight.Width.Value, (int)Math.Round(time * 0.3d),
|
||||
(int)Math.Round(time * 0.7d)));
|
||||
}
|
||||
else
|
||||
{
|
||||
ani.Add(ModAnimation.AaOpacity(buttonStack, -buttonStack.Opacity, (int)Math.Round(time * 0.4d)));
|
||||
ani.Add(ModAnimation.AaDouble(
|
||||
i => ColumnPaddingRight.Width =
|
||||
new GridLength(Math.Max(0, ColumnPaddingRight.Width.Value + (double)i)),
|
||||
4d - ColumnPaddingRight.Width.Value, (int)Math.Round(time * 0.4d)));
|
||||
}
|
||||
}
|
||||
|
||||
// RectBack
|
||||
if (IsMouseOver || Checked)
|
||||
{
|
||||
ani.AddRange(new[]
|
||||
{
|
||||
ModAnimation.AaColor(RectBack, Border.BackgroundProperty, isMouseDown ? "ColorBrush6" : "ColorBrushBg1",
|
||||
time),
|
||||
ModAnimation.AaOpacity(RectBack, 1d - RectBack.Opacity, time, ease: new ModAnimation.AniEaseOutFluent())
|
||||
});
|
||||
if (isMouseDown)
|
||||
ani.Add(ModAnimation.AaScaleTransform(RectBack,
|
||||
0.996d - ((ScaleTransform)RectBack.RenderTransform).ScaleX, (int)Math.Round(time * 1.2d),
|
||||
ease: new ModAnimation.AniEaseOutFluent()));
|
||||
else
|
||||
ani.Add(ModAnimation.AaScaleTransform(RectBack, 1d - ((ScaleTransform)RectBack.RenderTransform).ScaleX,
|
||||
(int)Math.Round(time * 1.2d), ease: new ModAnimation.AniEaseOutFluent()));
|
||||
}
|
||||
else
|
||||
{
|
||||
ani.AddRange(new[]
|
||||
{
|
||||
ModAnimation.AaOpacity(RectBack, -RectBack.Opacity, time),
|
||||
ModAnimation.AaScaleTransform(RectBack, 0.996d - ((ScaleTransform)RectBack.RenderTransform).ScaleX,
|
||||
time, ease: new ModAnimation.AniEaseOutFluent()),
|
||||
ModAnimation.AaScaleTransform(RectBack, -0.196d, 1, after: true)
|
||||
});
|
||||
}
|
||||
|
||||
ModAnimation.AniStart(ani, "LocalModItem Color " + Uuid);
|
||||
}
|
||||
|
||||
// 触发虚拟化内容
|
||||
private void InitLate(object sender, EventArgs e)
|
||||
{
|
||||
if (buttonHandler is not null)
|
||||
{
|
||||
buttonHandler((MyLocalCompItem)sender, e);
|
||||
buttonHandler = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示更新日志
|
||||
private void BtnUpdate_PreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
e.Handled = true;
|
||||
ShowUpdateLog();
|
||||
}
|
||||
|
||||
private void ShowUpdateLog()
|
||||
{
|
||||
if (Entry.Comp is not null)
|
||||
{
|
||||
if (!Information.IsNumeric(Entry.Comp.Id))
|
||||
{
|
||||
var modrinthUrl = Entry.changelogUrls.FirstOrDefault(x => x.Contains("modrinth.com"));
|
||||
if (modrinthUrl is not null)
|
||||
{
|
||||
ModBase.OpenWebsite(modrinthUrl);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var curseForgeUrl = Entry.changelogUrls.FirstOrDefault(x => x.Contains("curseforge.com"));
|
||||
if (curseForgeUrl is not null)
|
||||
{
|
||||
ModBase.OpenWebsite(curseForgeUrl);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ModBase.Log(
|
||||
Lang.Text("Instance.Resource.Item.OpenChangelogFailed"),
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Instance.Resource.Item.OpenChangelogFailed"));
|
||||
}
|
||||
|
||||
// 触发更新
|
||||
private void BtnUpdate_Click(object sender, EventArgs e)
|
||||
{
|
||||
switch (ModMain.MyMsgBox(
|
||||
$"{Lang.Text("Instance.Resource.Item.UpdateConfirm.Message", Entry.Name)}\r\n\r\n{GetUpdateCompareDescription()}",
|
||||
Lang.Text("Instance.Resource.Item.UpdateConfirm.Title"),
|
||||
Lang.Text("Instance.Resource.Item.Update"), Lang.Text("Instance.Resource.Item.ViewChangelog"), Lang.Text("Common.Action.Cancel")))
|
||||
{
|
||||
case 1: // 更新
|
||||
{
|
||||
switch (Entry.Comp.Type)
|
||||
{
|
||||
case ModComp.CompType.Mod:
|
||||
{
|
||||
ModMain.frmInstanceMod ??= new PageInstanceCompResource(ModComp.CompType.Mod);
|
||||
ModMain.frmInstanceMod.UpdateResource(new[] { Entry });
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.ResourcePack:
|
||||
{
|
||||
ModMain.frmInstanceResourcePack ??= new PageInstanceCompResource(ModComp.CompType.ResourcePack);
|
||||
ModMain.frmInstanceResourcePack.UpdateResource(new[] { Entry });
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.Shader:
|
||||
{
|
||||
ModMain.frmInstanceShader ??= new PageInstanceCompResource(ModComp.CompType.Shader);
|
||||
ModMain.frmInstanceShader.UpdateResource(new[] { Entry });
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.DataPack:
|
||||
{
|
||||
ModMain.frmInstanceSavesDatapack ??= new PageInstanceSavesDatapack();
|
||||
ModMain.frmInstanceSavesDatapack.UpdateResource(new[] { Entry });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 2: // 查看更新日志
|
||||
{
|
||||
ShowUpdateLog();
|
||||
break;
|
||||
}
|
||||
case 3: // 取消
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 自适应(#4465)
|
||||
private void PanTitle_SizeChanged(object sender, SizeChangedEventArgs sizeChangedEventArgs)
|
||||
{
|
||||
// 0:全部舒展:Auto - Auto - (Auto) - 1*
|
||||
// 1:压缩 Subtitle:Auto - 1* - (Auto) - 0
|
||||
// 2:继续压缩 Title:1* - 0 - (Auto) - 0
|
||||
var currentCompressLevel =
|
||||
ColumnExtend.Width.IsStar ? 0 : ColumnTitle.Width.IsStar ? 2 : 1; // Subtitle 可能是 Collapsed
|
||||
var newCompressLevel = default(int);
|
||||
switch (currentCompressLevel)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
if (ColumnExtend.ActualWidth < 0.5d)
|
||||
newCompressLevel = LabSubtitle.Visibility == Visibility.Collapsed ? 2 : 1;
|
||||
else
|
||||
return;
|
||||
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
if (ColumnSubtitle.ActualWidth < 0.5d)
|
||||
newCompressLevel = 2;
|
||||
else if (!LabSubtitle.IsTextTrimmed())
|
||||
newCompressLevel = 0;
|
||||
else
|
||||
return;
|
||||
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
if (!LabTitle.IsTextTrimmed())
|
||||
newCompressLevel = LabSubtitle.Visibility == Visibility.Collapsed ? 0 : 1;
|
||||
else
|
||||
return;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (newCompressLevel)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
// 全部舒展:Auto - Auto - (Auto) - 1*
|
||||
ColumnTitle.Width = GridLength.Auto;
|
||||
ColumnSubtitle.Width = GridLength.Auto;
|
||||
ColumnExtend.Width = new GridLength(1d, GridUnitType.Star);
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
// 压缩 Subtitle:Auto - 1* - (Auto) - 0
|
||||
ColumnTitle.Width = GridLength.Auto;
|
||||
ColumnSubtitle.Width = new GridLength(1d, GridUnitType.Star);
|
||||
ColumnExtend.Width = new GridLength(0d, GridUnitType.Pixel);
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
// 继续压缩 Title:1* - 0 - (Auto) - 0
|
||||
ColumnTitle.Width = new GridLength(1d, GridUnitType.Star);
|
||||
ColumnSubtitle.Width = new GridLength(0d, GridUnitType.Pixel);
|
||||
ColumnExtend.Width = new GridLength(0d, GridUnitType.Pixel);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region 基础属性
|
||||
|
||||
public int Uuid = ModBase.GetUuid();
|
||||
|
||||
// Logo
|
||||
public string Logo
|
||||
{
|
||||
get => PathLogo.Source;
|
||||
set => PathLogo.Source = value;
|
||||
}
|
||||
|
||||
// 标题
|
||||
public string Title
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
var rawValue = value;
|
||||
switch (Entry.State)
|
||||
{
|
||||
case ModLocalComp.LocalCompFile.LocalFileStatus.Fine:
|
||||
{
|
||||
LabTitle.TextDecorations = null;
|
||||
break;
|
||||
}
|
||||
case ModLocalComp.LocalCompFile.LocalFileStatus.Disabled:
|
||||
{
|
||||
LabTitle.TextDecorations = TextDecorations.Strikethrough;
|
||||
break;
|
||||
}
|
||||
case ModLocalComp.LocalCompFile.LocalFileStatus.Unavailable:
|
||||
{
|
||||
LabTitle.TextDecorations = TextDecorations.Strikethrough;
|
||||
value += Lang.Text("Instance.Resource.Item.ErrorSuffix");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((LabTitle.Text ?? "") == (value ?? ""))
|
||||
return;
|
||||
LabTitle.Text = value;
|
||||
field = rawValue;
|
||||
}
|
||||
}
|
||||
|
||||
// 副标题
|
||||
public string SubTitle
|
||||
{
|
||||
get => LabSubtitle?.Text ?? "";
|
||||
set
|
||||
{
|
||||
if ((LabSubtitle.Text ?? "") == (value ?? ""))
|
||||
return;
|
||||
LabSubtitle.Text = value;
|
||||
LabSubtitle.Visibility = string.IsNullOrEmpty(value) ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
// 描述
|
||||
public string Description
|
||||
{
|
||||
get => LabInfo.Text;
|
||||
set
|
||||
{
|
||||
if ((LabInfo.Text ?? "") == (value ?? ""))
|
||||
return;
|
||||
LabInfo.Text = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Tag
|
||||
public List<string> Tags
|
||||
{
|
||||
set
|
||||
{
|
||||
PanTags.Children.Clear();
|
||||
PanTags.Visibility = value.Any() ? Visibility.Visible : Visibility.Collapsed;
|
||||
foreach (var TagText in value)
|
||||
{
|
||||
var newTag = new Border
|
||||
{
|
||||
Background = new SolidColorBrush(Color.FromArgb(12, 0, 0, 0)),
|
||||
Padding = new Thickness(3d, 1d, 3d, 1d),
|
||||
CornerRadius = new CornerRadius(3d),
|
||||
Margin = new Thickness(0d, 0d, 3d, 0d),
|
||||
SnapsToDevicePixels = true,
|
||||
UseLayoutRounding = false
|
||||
};
|
||||
var tagTextBlock = new TextBlock
|
||||
{
|
||||
Text = TagText,
|
||||
Foreground = new SolidColorBrush(ThemeManager.IsDarkMode
|
||||
? Color.FromArgb(88, 255, 255, 255)
|
||||
: Color.FromArgb(88, 136, 136, 136)),
|
||||
FontSize = 11d
|
||||
};
|
||||
newTag.Child = tagTextBlock;
|
||||
PanTags.Children.Add(newTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 相关联的 Mod
|
||||
public ModLocalComp.LocalCompFile Entry
|
||||
{
|
||||
get => (ModLocalComp.LocalCompFile)Tag;
|
||||
set => Tag = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 点击与勾选
|
||||
|
||||
// 触发点击事件
|
||||
public event ClickEventHandler? Click;
|
||||
|
||||
public delegate void ClickEventHandler(object sender, MouseButtonEventArgs e);
|
||||
|
||||
public MyLocalCompItem()
|
||||
{
|
||||
InitializeComponent();
|
||||
PreviewMouseLeftButtonUp += Button_MouseUp;
|
||||
PreviewMouseLeftButtonDown += Button_MouseDown;
|
||||
MouseLeave += Button_MouseLeave;
|
||||
PreviewMouseLeftButtonUp += Button_MouseLeave;
|
||||
MouseLeftButtonDown += Button_MouseSwipeStart;
|
||||
MouseEnter += Button_MouseSwipe;
|
||||
MouseLeave += Button_MouseSwipe;
|
||||
MouseLeftButtonUp += Button_MouseSwipe;
|
||||
Loaded += (_, _) => Refresh();
|
||||
MouseEnter += RefreshColor;
|
||||
MouseLeave += RefreshColor;
|
||||
MouseLeftButtonDown += RefreshColor;
|
||||
MouseLeftButtonUp += RefreshColor;
|
||||
Changed += RefreshColor;
|
||||
// Handles
|
||||
BtnUpdate.PreviewMouseRightButtonUp += BtnUpdate_PreviewMouseRightButtonUp;
|
||||
BtnUpdate.Click += BtnUpdate_Click;
|
||||
PanTitle.SizeChanged += PanTitle_SizeChanged;
|
||||
}
|
||||
|
||||
private void Button_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (isMouseDown)
|
||||
{
|
||||
Click?.Invoke(sender, e);
|
||||
if (e.Handled)
|
||||
return;
|
||||
ModBase.Log("[Control] 按下本地 Mod 列表项:" + LabTitle.Text);
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标点击判定
|
||||
private bool isMouseDown;
|
||||
|
||||
private void Button_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!IsMouseDirectlyOver)
|
||||
return;
|
||||
isMouseDown = true;
|
||||
if (buttonStack is not null)
|
||||
buttonStack.IsHitTestVisible = false;
|
||||
}
|
||||
|
||||
private void Button_MouseLeave(object sender, object e)
|
||||
{
|
||||
isMouseDown = false;
|
||||
if (buttonStack is not null)
|
||||
buttonStack.IsHitTestVisible = true;
|
||||
}
|
||||
|
||||
// 滑动选中
|
||||
public class SwipeSelect
|
||||
{
|
||||
public int Start { get; set; }
|
||||
public int End { get; set; }
|
||||
|
||||
public bool Swiping
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
if (TargetFrm is not null)
|
||||
try
|
||||
{
|
||||
var cardSelect = Interaction.CallByName(TargetFrm, "CardSelect", CallType.Get);
|
||||
Interaction.CallByName(cardSelect, "IsHitTestVisible", CallType.Set, !value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool SwipeToState { get; set; }
|
||||
public object TargetFrm { get; set; }
|
||||
}
|
||||
|
||||
public SwipeSelect CurrentSwipe { get; set; }
|
||||
|
||||
private void Button_MouseSwipeStart(object sender, object e)
|
||||
{
|
||||
if (Parent is null)
|
||||
return; // Mod 可能已被删除(#3824)
|
||||
// 开始滑动
|
||||
var index = ((StackPanel)Parent).Children.IndexOf(this);
|
||||
CurrentSwipe.Start = index;
|
||||
CurrentSwipe.End = index;
|
||||
CurrentSwipe.Swiping = true;
|
||||
CurrentSwipe.SwipeToState = !Checked;
|
||||
}
|
||||
|
||||
private void Button_MouseSwipe(object sender, object e)
|
||||
{
|
||||
if (Parent is null)
|
||||
return; // Mod 可能已被删除(#3824)
|
||||
// 结束滑动
|
||||
if (Mouse.LeftButton != MouseButtonState.Pressed || !(Mouse.DirectlyOver is MyLocalCompItem)) // #5771
|
||||
{
|
||||
CurrentSwipe.Swiping = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算滑动范围
|
||||
var elements = ((StackPanel)Parent).Children;
|
||||
var index = elements.IndexOf(this);
|
||||
CurrentSwipe.Start =
|
||||
(int)Math.Round(ModBase.MathClamp(Math.Min(CurrentSwipe.Start, index), 0d, elements.Count - 1));
|
||||
CurrentSwipe.End =
|
||||
(int)Math.Round(ModBase.MathClamp(Math.Max(CurrentSwipe.End, index), 0d, elements.Count - 1));
|
||||
// 勾选所有范围中的项
|
||||
if (CurrentSwipe.Start == CurrentSwipe.End)
|
||||
return;
|
||||
for (int i = CurrentSwipe.Start, loopTo = CurrentSwipe.End; i <= loopTo; i++)
|
||||
{
|
||||
var item = (MyLocalCompItem)elements[i];
|
||||
item.InitLate(item, (EventArgs)e);
|
||||
item.Checked = CurrentSwipe.SwipeToState;
|
||||
}
|
||||
}
|
||||
|
||||
// 勾选状态
|
||||
public event CheckEventHandler? Check;
|
||||
|
||||
public delegate void CheckEventHandler(object sender, ModBase.RouteEventArgs e);
|
||||
|
||||
public event ChangedEventHandler? Changed;
|
||||
|
||||
public delegate void ChangedEventHandler(object sender, ModBase.RouteEventArgs e);
|
||||
|
||||
public bool Checked
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
try
|
||||
{
|
||||
// 触发属性值修改
|
||||
var rawValue = field;
|
||||
if (value == field)
|
||||
return;
|
||||
field = value;
|
||||
var ChangedEventArgs = new ModBase.RouteEventArgs();
|
||||
if (IsInitialized)
|
||||
{
|
||||
Changed?.Invoke(this, ChangedEventArgs);
|
||||
if (ChangedEventArgs.handled)
|
||||
{
|
||||
field = rawValue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (value)
|
||||
{
|
||||
var checkEventArgs = new ModBase.RouteEventArgs();
|
||||
Check?.Invoke(this, checkEventArgs);
|
||||
if (checkEventArgs.handled)
|
||||
return;
|
||||
}
|
||||
|
||||
// 更改动画
|
||||
if (this.IsVisibleInWindow(ModMain.frmMain))
|
||||
{
|
||||
var anim = new List<ModAnimation.AniData>();
|
||||
if (Checked)
|
||||
{
|
||||
// 由无变有
|
||||
var delta = 32d - RectCheck.ActualHeight;
|
||||
anim.Add(ModAnimation.AaHeight(RectCheck, delta * 0.4d, 200,
|
||||
ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak)));
|
||||
anim.Add(ModAnimation.AaHeight(RectCheck, delta * 0.6d, 300,
|
||||
ease: new ModAnimation.AniEaseOutBack(ModAnimation.AniEasePower.Weak)));
|
||||
anim.Add(ModAnimation.AaOpacity(RectCheck, 1d - RectCheck.Opacity, 30));
|
||||
RectCheck.VerticalAlignment = VerticalAlignment.Center;
|
||||
RectCheck.Margin = new Thickness(-3, 0d, 0d, 0d);
|
||||
anim.Add(ModAnimation.AaColor(LabTitle, TextBlock.ForegroundProperty,
|
||||
Entry.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine
|
||||
? "ColorBrush2"
|
||||
: "ColorBrush5", 200));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 由有变无
|
||||
anim.Add(ModAnimation.AaHeight(RectCheck, -RectCheck.ActualHeight, 120,
|
||||
ease: new ModAnimation.AniEaseInFluent(ModAnimation.AniEasePower.Weak)));
|
||||
anim.Add(ModAnimation.AaOpacity(RectCheck, -RectCheck.Opacity, 70, 40));
|
||||
RectCheck.VerticalAlignment = VerticalAlignment.Center;
|
||||
anim.Add(ModAnimation.AaColor(LabTitle, TextBlock.ForegroundProperty,
|
||||
LabTitle.TextDecorations is null ? "ColorBrush1" : "ColorBrushGray4", 120));
|
||||
}
|
||||
|
||||
ModAnimation.AniStart(anim, "MyLocalCompItem Checked " + Uuid);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 不在窗口上时直接设置
|
||||
RectCheck.VerticalAlignment = VerticalAlignment.Center;
|
||||
RectCheck.Margin = new Thickness(-3, 0d, 0d, 0d);
|
||||
if (Checked)
|
||||
{
|
||||
RectCheck.Height = 32d;
|
||||
RectCheck.Opacity = 1d;
|
||||
LabTitle.SetResourceReference(TextBlock.ForegroundProperty,
|
||||
Entry.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine
|
||||
? "ColorBrush2"
|
||||
: "ColorBrush5");
|
||||
}
|
||||
else
|
||||
{
|
||||
RectCheck.Height = 0d;
|
||||
RectCheck.Opacity = 0d;
|
||||
LabTitle.SetResourceReference(TextBlock.ForegroundProperty,
|
||||
Entry.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine
|
||||
? "ColorBrush1"
|
||||
: "ColorBrushGray4");
|
||||
}
|
||||
|
||||
ModAnimation.AniStop("MyLocalCompItem Checked " + Uuid);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "设置 Checked 失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 后加载内容
|
||||
|
||||
// 右下角状态指示图标
|
||||
private Image imgState;
|
||||
|
||||
// 指向背景
|
||||
public Border RectBack
|
||||
{
|
||||
get
|
||||
{
|
||||
if (field is null)
|
||||
{
|
||||
var rect = new Border
|
||||
{
|
||||
Name = "RectBack",
|
||||
CornerRadius = new CornerRadius(3d),
|
||||
RenderTransform = new ScaleTransform(0.8d, 0.8d),
|
||||
RenderTransformOrigin = new Point(0.5d, 0.5d),
|
||||
BorderThickness = new Thickness(ModBase.GetWPFSize(1d)),
|
||||
SnapsToDevicePixels = true,
|
||||
IsHitTestVisible = false,
|
||||
Opacity = 0d
|
||||
};
|
||||
rect.SetResourceReference(Border.BackgroundProperty, "ColorBrush7");
|
||||
rect.SetResourceReference(Border.BorderBrushProperty, "ColorBrush6");
|
||||
SetColumnSpan(rect, 999);
|
||||
SetRowSpan(rect, 999);
|
||||
Children.Insert(0, rect);
|
||||
field = rect;
|
||||
// <!--<corelocal:BlurBorder x:Name = "RectBack" CornerRadius="3" RenderTransformOrigin="0.5,0.5" SnapsToDevicePixels="True"
|
||||
// IsHitTestVisible = "False" Opacity="0" BorderThickness="1"
|
||||
// Grid.ColumnSpan = "4" Background="{DynamicResource ColorBrush7}" BorderBrush="{DynamicResource ColorBrush6}"/>-->
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
// 按钮
|
||||
public Action<MyLocalCompItem, EventArgs> buttonHandler;
|
||||
public FrameworkElement buttonStack;
|
||||
public IEnumerable<MyIconButton> Buttons
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
// 移除原 Stack
|
||||
if (buttonStack is not null)
|
||||
{
|
||||
Children.Remove(buttonStack);
|
||||
buttonStack = null;
|
||||
}
|
||||
|
||||
if (!value.Any())
|
||||
return;
|
||||
// 添加新 Stack
|
||||
buttonStack = new StackPanel
|
||||
{
|
||||
Opacity = 0d,
|
||||
Margin = new Thickness(0d, 0d, 5d, 0d),
|
||||
SnapsToDevicePixels = false,
|
||||
Orientation = (Orientation)System.Windows.Forms.Orientation.Horizontal,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
UseLayoutRounding = false
|
||||
};
|
||||
SetColumnSpan(buttonStack, 10);
|
||||
SetRowSpan(buttonStack, 10);
|
||||
// 构造按钮
|
||||
foreach (var Btn in value)
|
||||
{
|
||||
if (Btn.Height.Equals(double.NaN))
|
||||
Btn.Height = 25d;
|
||||
if (Btn.Width.Equals(double.NaN))
|
||||
Btn.Width = 25d;
|
||||
((StackPanel)buttonStack).Children.Add(Btn);
|
||||
}
|
||||
|
||||
Children.Add(buttonStack);
|
||||
}
|
||||
}
|
||||
|
||||
// 勾选条
|
||||
public Border RectCheck
|
||||
{
|
||||
get
|
||||
{
|
||||
if (field is null)
|
||||
{
|
||||
field = new Border
|
||||
{
|
||||
Width = 5d,
|
||||
Height = Checked ? double.NaN : 0d,
|
||||
CornerRadius = new CornerRadius(2d, 2d, 2d, 2d),
|
||||
VerticalAlignment = Checked ? VerticalAlignment.Stretch : VerticalAlignment.Center,
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
UseLayoutRounding = false,
|
||||
SnapsToDevicePixels = false,
|
||||
Margin = Checked ? new Thickness(-3, 6d, 0d, 6d) : new Thickness(-3, 0d, 0d, 0d)
|
||||
};
|
||||
field.SetResourceReference(Border.BackgroundProperty, "ColorBrush3");
|
||||
SetRowSpan(field, 10);
|
||||
Children.Add(field);
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
<local:MyPageRight
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" x:Class="PCL.PageInstanceCompResource"
|
||||
PanScroll="{Binding ElementName=PanBack}">
|
||||
<Grid>
|
||||
<Grid x:Name="PanAllBack">
|
||||
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled"
|
||||
x:Name="PanBack">
|
||||
<StackPanel x:Name="PanMain" Margin="25,10,25,10" Grid.IsSharedSizeScope="True"> <!-- 若显示多选模块则为 92 -->
|
||||
<local:MySearchBox Margin="0,15" HintText="{DynamicResource Instance.Resource.Search.Hint}" x:Name="SearchBox" />
|
||||
<local:MyCard Margin="0,0,0,15" x:Name="PanManage">
|
||||
<WrapPanel Margin="15,8,0,15">
|
||||
<local:MyButton x:Name="BtnManageBack" MinWidth="110" Text="{DynamicResource Instance.Resource.BackToParent}" Padding="13,7"
|
||||
Margin="0,7,15,0" HorizontalAlignment="Left" Visibility="Collapsed" />
|
||||
<local:MyButton x:Name="BtnManageOpen" MinWidth="110" Text="{DynamicResource Common.Action.OpenFolder}" Padding="13,7"
|
||||
Margin="0,7,15,0" HorizontalAlignment="Left" ColorType="Highlight" />
|
||||
|
||||
<local:MyButton x:Name="BtnManageInstall" MinWidth="110" Text="{DynamicResource Instance.Resource.InstallFromFiles}" Padding="13,7"
|
||||
Margin="0,7,15,0" HorizontalAlignment="Left" />
|
||||
<local:MyButton x:Name="BtnManageDownload" MinWidth="110" Text="{DynamicResource Instance.Resource.DownloadNew}" Padding="13,7"
|
||||
Margin="0,7,15,0" HorizontalAlignment="Left" />
|
||||
<local:MyButton x:Name="BtnManageSelectAll" MinWidth="110" Text="{DynamicResource Instance.Resource.SelectAll}" Padding="13,7"
|
||||
Margin="0,7,15,0" HorizontalAlignment="Left" />
|
||||
<local:MyButton x:Name="BtnManageInfoExport" MinWidth="110" Text="{DynamicResource Instance.Resource.ExportInfo}" Padding="13,7"
|
||||
Margin="0,7,15,0" HorizontalAlignment="Left" ToolTip="{DynamicResource Instance.Resource.ExportInfo.ToolTip}" />
|
||||
<local:MyButton x:Name="BtnManageCheck" MinWidth="110" Text="{DynamicResource Instance.Resource.CheckMods}" Padding="13,7"
|
||||
Margin="0,7,15,0" HorizontalAlignment="Left"
|
||||
ToolTip="{DynamicResource Instance.Resource.CheckMods.ToolTip}"
|
||||
Visibility="Collapsed" />
|
||||
</WrapPanel>
|
||||
</local:MyCard>
|
||||
<local:MyCard x:Name="PanListBack" VerticalAlignment="Top" Opacity="0" Margin="0,0,0,14" Title=" "
|
||||
MinHeight="55">
|
||||
<StackPanel x:Name="PanFilter" Margin="15,13,0,0" Orientation="Horizontal"
|
||||
VerticalAlignment="Top" HorizontalAlignment="Left" Height="28">
|
||||
<local:MyRadioButton Tag="0" ColorType="Highlight" VerticalAlignment="Center" Margin="2,0"
|
||||
Text="{DynamicResource Instance.Resource.Filter.All}" x:Name="BtnFilterAll" Checked="True" />
|
||||
<local:MyRadioButton Tag="1" ColorType="Highlight" VerticalAlignment="Center" Margin="2,0"
|
||||
Text="{DynamicResource Instance.Resource.Filter.Enabled}" x:Name="BtnFilterEnabled" />
|
||||
<local:MyRadioButton Tag="2" ColorType="Highlight" VerticalAlignment="Center" Margin="2,0"
|
||||
Text="{DynamicResource Instance.Resource.Filter.Disabled}" x:Name="BtnFilterDisabled" />
|
||||
<local:MyRadioButton Tag="3" ColorType="Highlight" VerticalAlignment="Center" Margin="2,0"
|
||||
Text="{DynamicResource Instance.Resource.Filter.Updatable}" x:Name="BtnFilterCanUpdate" />
|
||||
<local:MyRadioButton Tag="4" ColorType="Highlight" VerticalAlignment="Center" Margin="2,0"
|
||||
Text="{DynamicResource Instance.Resource.Filter.Error}" x:Name="BtnFilterError" />
|
||||
<local:MyRadioButton Tag="5" ColorType="Highlight" VerticalAlignment="Center" Margin="2,0"
|
||||
Text="{DynamicResource Instance.Resource.Filter.Duplicate}" x:Name="BtnFilterDuplicate" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,13,15,0" Orientation="Horizontal" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right" Height="28">
|
||||
<local:MyIconTextButton x:Name="BtnSort" Text="{DynamicResource Instance.Resource.Sort.Text}"
|
||||
SvgIcon="lucide/arrow-up-down" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="20,48,18,22" Name="PanList" VerticalAlignment="Top" />
|
||||
</local:MyCard>
|
||||
</StackPanel>
|
||||
</local:MyScrollViewer>
|
||||
<local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" Margin="40" x:Name="PanEmpty">
|
||||
<StackPanel Margin="20,17">
|
||||
<TextBlock x:Name="TxtEmptyTitle" Margin="0,0,0,9" HorizontalAlignment="Center" Text="{DynamicResource Instance.Resource.Empty.Title}"
|
||||
FontSize="19" UseLayoutRounding="True" SnapsToDevicePixels="True"
|
||||
Foreground="{DynamicResource ColorBrush3}" />
|
||||
<Rectangle HorizontalAlignment="Stretch" Height="2" Fill="{DynamicResource ColorBrush3}" />
|
||||
<TextBlock x:Name="TxtEmptyDescription" Margin="10,10,10,0"
|
||||
Text="{DynamicResource Instance.Resource.Empty.Description}" TextWrapping="Wrap" />
|
||||
<WrapPanel Margin="10,20,10,5" HorizontalAlignment="Center" Orientation="Horizontal">
|
||||
<local:MyButton Height="35" x:Name="BtnHintBack" MinWidth="130" Text="{DynamicResource Instance.Resource.BackToParent}" Margin="5,0"
|
||||
Padding="12,0" Visibility="Collapsed" />
|
||||
<local:MyButton Height="35" x:Name="BtnHintInstall" MinWidth="130" Text="{DynamicResource Instance.Resource.InstallFromFiles}" Margin="5,0"
|
||||
Padding="12,0" ColorType="Highlight" />
|
||||
<local:MyButton Height="35" x:Name="BtnHintDownload" MinWidth="130" Text="{DynamicResource Instance.Resource.DownloadNew}" Margin="5,0"
|
||||
Padding="12,0" />
|
||||
<local:MyButton Height="35" x:Name="BtnHintOpen" MinWidth="130" Text="{DynamicResource Common.Action.OpenFolder}" Margin="5,0"
|
||||
Padding="12,0" />
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
<local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" Margin="40"
|
||||
x:Name="PanSchematicEmpty" Visibility="Collapsed">
|
||||
<StackPanel Margin="20,17">
|
||||
<TextBlock Margin="0,0,0,9" HorizontalAlignment="Center" Text="{DynamicResource Instance.Resource.Schematic.Unavailable.Title}" FontSize="19"
|
||||
UseLayoutRounding="True" SnapsToDevicePixels="True"
|
||||
Foreground="{DynamicResource ColorBrush3}" />
|
||||
<Rectangle HorizontalAlignment="Stretch" Height="2" Fill="{DynamicResource ColorBrush3}" />
|
||||
<TextBlock Margin="10,10,10,0"
|
||||
Text="{DynamicResource Instance.Resource.Schematic.Unavailable.Message}"
|
||||
TextWrapping="Wrap" HorizontalAlignment="Center" />
|
||||
<WrapPanel Margin="10,20,10,5" HorizontalAlignment="Center" Orientation="Horizontal">
|
||||
<local:MyButton Height="35" x:Name="BtnSchematicDownloadMod" MinWidth="130" Text="{DynamicResource Instance.Resource.Schematic.DownloadMod}"
|
||||
Margin="5,0" Padding="12,0" ColorType="Highlight" />
|
||||
<local:MyButton Height="35" x:Name="BtnSchematicVersionSelect" MinWidth="130" Text="{DynamicResource Instance.Resource.Mod.Disabled.SelectInstance}"
|
||||
Margin="5,0" Padding="12,0" />
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
</Grid>
|
||||
<local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" Margin="40,0" SnapsToDevicePixels="True"
|
||||
x:Name="PanLoad" UseAnimation="False">
|
||||
<local:MyLoading Text="{DynamicResource Instance.Resource.Loading}" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</local:MyCard>
|
||||
<local:MyCard x:Name="CardSelect" Visibility="Collapsed" Opacity="0"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="25,25,25,0" UseAnimation="False">
|
||||
<local:MyCard.RenderTransform>
|
||||
<TranslateTransform x:Name="TransSelect" Y="-10" />
|
||||
</local:MyCard.RenderTransform>
|
||||
<TextBlock x:Name="LabSelect" Text="" HorizontalAlignment="Center" VerticalAlignment="Top"
|
||||
Margin="9" Foreground="{DynamicResource ColorBrush2}" />
|
||||
<StackPanel Orientation="Horizontal" Margin="5,28,5,5">
|
||||
<local:MyIconTextButton x:Name="BtnSelectUpdate" Text="{DynamicResource Instance.Resource.Update}"
|
||||
LogoScale="1"
|
||||
SvgIcon="lucide/upload" />
|
||||
<local:MyIconTextButton x:Name="BtnSelectEnable" Text="{DynamicResource Instance.Resource.Enable}"
|
||||
LogoScale="1.05"
|
||||
SvgIcon="lucide/circle-check" />
|
||||
<local:MyIconTextButton x:Name="BtnSelectDisable" Text="{DynamicResource Instance.Resource.Disable}"
|
||||
LogoScale="1"
|
||||
SvgIcon="lucide/circle-minus" />
|
||||
<local:MyIconTextButton x:Name="BtnSelectFavorites" Text="{DynamicResource Instance.Resource.Favorite}"
|
||||
LogoScale="1"
|
||||
SvgIcon="lucide/heart" />
|
||||
<local:MyIconTextButton x:Name="BtnSelectShare" Text="{DynamicResource Instance.Resource.ShareSelected}"
|
||||
LogoScale="1"
|
||||
SvgIcon="lucide/share-2" />
|
||||
<local:MyIconTextButton x:Name="BtnSelectDelete" Text="{DynamicResource Common.Action.Delete}"
|
||||
LogoScale="0.96"
|
||||
SvgIcon="lucide/trash-2" />
|
||||
<local:MyIconTextButton x:Name="BtnSelectCancel" Text="{DynamicResource Instance.Resource.CancelSelection}"
|
||||
LogoScale="0.8"
|
||||
SvgIcon="lucide/x" />
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
</Grid>
|
||||
</local:MyPageRight>
|
||||
+2961
@@ -0,0 +1,2961 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
using Microsoft.VisualBasic.FileIO;
|
||||
using PCL.Core.App;
|
||||
using PCL.Core.Logging;
|
||||
using PCL.Core.UI;
|
||||
using PCL.Core.UI.Theme;
|
||||
using PCL.Network;
|
||||
using PCL.Network.Loaders;
|
||||
using FileSystem = Microsoft.VisualBasic.FileSystem;
|
||||
using SearchOption = System.IO.SearchOption;
|
||||
using PCL.Core.App.Localization;
|
||||
using PCL.Core.Utils;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageInstanceCompResource : IRefreshable
|
||||
{
|
||||
#region 模组信息缓存
|
||||
|
||||
// 模组信息缓存 - 解决排序时重复创建FileInfo导致的性能问题
|
||||
private readonly Dictionary<string, (DateTime CreationTime, long Length)> modFileInfoCache = new();
|
||||
|
||||
public PageInstanceCompResource()
|
||||
{
|
||||
InitializeComponent();
|
||||
Unloaded += Page_Unloaded;
|
||||
Loaded += (_, _) => PageOther_Loaded();
|
||||
Initialized += (_, _) => LoaderInit();
|
||||
PageExit += UnselectedAllWithAnimation;
|
||||
Load.Click += Load_Click;
|
||||
BtnManageBack.Click += BtnManageBack_Click;
|
||||
BtnHintBack.Click += BtnHintBack_Click;
|
||||
BtnManageOpen.Click += BtnManageOpen_Click;
|
||||
BtnHintOpen.Click += BtnManageOpen_Click;
|
||||
BtnManageSelectAll.Click += BtnManageSelectAll_Click;
|
||||
BtnManageInstall.Click += BtnManageInstall_Click;
|
||||
BtnHintInstall.Click += BtnManageInstall_Click;
|
||||
BtnManageInfoExport.Click += BtnManageInfoExport_Click;
|
||||
BtnManageDownload.Click += BtnManageDownload_Click;
|
||||
BtnHintDownload.Click += BtnManageDownload_Click;
|
||||
BtnSchematicDownloadMod.Click += BtnSchematicDownloadMod_Click;
|
||||
BtnSchematicVersionSelect.Click += BtnSchematicVersionSelect_Click;
|
||||
Load.StateChanged += (_, _, _) => UnselectedAllWithAnimation();
|
||||
SearchBox.PreviewKeyDown += SearchBox_PreviewKeyDown;
|
||||
BtnFilterAll.Check += ChangeFilter;
|
||||
BtnFilterCanUpdate.Check += ChangeFilter;
|
||||
BtnFilterDisabled.Check += ChangeFilter;
|
||||
BtnFilterEnabled.Check += ChangeFilter;
|
||||
BtnFilterError.Check += ChangeFilter;
|
||||
BtnFilterDuplicate.Check += ChangeFilter;
|
||||
BtnSort.Click += BtnSortClick;
|
||||
BtnSelectEnable.Click += BtnSelectED_Click;
|
||||
BtnSelectDisable.Click += BtnSelectED_Click;
|
||||
BtnSelectUpdate.Click += BtnSelectUpdate_Click;
|
||||
BtnSelectDelete.Click += BtnSelectDelete_Click;
|
||||
BtnSelectCancel.Click += BtnSelectCancel_Click;
|
||||
BtnSelectFavorites.Click += BtnSelectFavorites_Click;
|
||||
BtnSelectShare.Click += BtnSelectShare_Click;
|
||||
SearchBox.TextChanged += SearchRun;
|
||||
}
|
||||
|
||||
// 获取模组信息(带缓存)
|
||||
private (DateTime CreationTime, long Length) GetModFileInfo(string path)
|
||||
{
|
||||
(DateTime CreationTime, long Length) cacheItem;
|
||||
if (modFileInfoCache.TryGetValue(path, out cacheItem)) return cacheItem;
|
||||
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
var newItem = (fileInfo.CreationTime, fileInfo.Length);
|
||||
if (!modFileInfoCache.ContainsKey(path)) modFileInfoCache.Add(path, newItem);
|
||||
return newItem;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "获取模组信息失败: " + path);
|
||||
return (DateTime.MinValue, 0L);
|
||||
}
|
||||
}
|
||||
|
||||
// 页面关闭时清理缓存
|
||||
private void Page_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
modFileInfoCache.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 初始化
|
||||
|
||||
private readonly ModComp.CompType currentCompType = ModComp.CompType.Mod;
|
||||
|
||||
private readonly MyLocalCompItem.SwipeSelect currentSwipSelect;
|
||||
|
||||
public PageInstanceCompResource(ModComp.CompType loadCompType)
|
||||
{
|
||||
currentCompType = loadCompType;
|
||||
CurrentFolderPath = ""; // 确保文件夹路径被重置为根目录
|
||||
currentSwipSelect = new MyLocalCompItem.SwipeSelect { TargetFrm = this };
|
||||
|
||||
// 此调用是设计器所必需的。
|
||||
InitializeComponent();
|
||||
|
||||
// 在 InitializeComponent() 调用之后添加任何初始化。
|
||||
|
||||
if (new[] { ModComp.CompType.Shader, ModComp.CompType.ResourcePack, ModComp.CompType.Schematic }.Contains(
|
||||
currentCompType))
|
||||
{
|
||||
BtnSelectEnable.Visibility = Visibility.Collapsed;
|
||||
BtnSelectDisable.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
// 投影文件管理页隐藏下载按钮
|
||||
if (currentCompType == ModComp.CompType.Schematic)
|
||||
{
|
||||
BtnManageDownload.Visibility = Visibility.Collapsed;
|
||||
BtnHintDownload.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
Unloaded += Page_Unloaded;
|
||||
Loaded += (_, _) => PageOther_Loaded();
|
||||
LoaderInit();
|
||||
PageExit += UnselectedAllWithAnimation;
|
||||
// Handles
|
||||
Load.Click += Load_Click;
|
||||
BtnManageBack.Click += BtnManageBack_Click;
|
||||
BtnHintBack.Click += BtnHintBack_Click;
|
||||
BtnManageOpen.Click += BtnManageOpen_Click;
|
||||
BtnHintOpen.Click += BtnManageOpen_Click;
|
||||
BtnManageSelectAll.Click += BtnManageSelectAll_Click;
|
||||
BtnManageInstall.Click += BtnManageInstall_Click;
|
||||
BtnHintInstall.Click += BtnManageInstall_Click;
|
||||
BtnManageDownload.Click += BtnManageDownload_Click;
|
||||
BtnHintDownload.Click += BtnManageDownload_Click;
|
||||
BtnManageInfoExport.Click += BtnManageInfoExport_Click;
|
||||
BtnSchematicDownloadMod.Click += BtnSchematicDownloadMod_Click;
|
||||
BtnSchematicVersionSelect.Click += BtnSchematicVersionSelect_Click;
|
||||
Load.StateChanged += (_, _, _) => UnselectedAllWithAnimation();
|
||||
SearchBox.PreviewKeyDown += SearchBox_PreviewKeyDown;
|
||||
BtnFilterAll.Check += ChangeFilter;
|
||||
BtnFilterCanUpdate.Check += ChangeFilter;
|
||||
BtnFilterDisabled.Check += ChangeFilter;
|
||||
BtnFilterEnabled.Check += ChangeFilter;
|
||||
BtnFilterError.Check += ChangeFilter;
|
||||
BtnFilterDuplicate.Check += ChangeFilter;
|
||||
BtnSort.Click += BtnSortClick;
|
||||
BtnSelectEnable.Click += BtnSelectED_Click;
|
||||
BtnSelectDisable.Click += BtnSelectED_Click;
|
||||
BtnSelectUpdate.Click += BtnSelectUpdate_Click;
|
||||
BtnSelectDelete.Click += BtnSelectDelete_Click;
|
||||
BtnSelectCancel.Click += BtnSelectCancel_Click;
|
||||
BtnSelectFavorites.Click += BtnSelectFavorites_Click;
|
||||
BtnSelectShare.Click += BtnSelectShare_Click;
|
||||
SearchBox.TextChanged += SearchRun;
|
||||
}
|
||||
|
||||
private ModLocalComp.CompLocalLoaderData GetRequireLoaderData()
|
||||
{
|
||||
var res = new ModLocalComp.CompLocalLoaderData();
|
||||
res.gameVersion = PageInstanceLeft.McInstance;
|
||||
res.frm = this;
|
||||
var requireLoaders = new List<ModComp.CompLoaderType>();
|
||||
switch (currentCompType)
|
||||
{
|
||||
case ModComp.CompType.Mod:
|
||||
{
|
||||
requireLoaders = ModLocalComp.GetCurrentVersionModLoader();
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.ResourcePack:
|
||||
{
|
||||
requireLoaders = new[] { ModComp.CompLoaderType.Minecraft }.ToList();
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.Shader:
|
||||
{
|
||||
requireLoaders = new[]
|
||||
{
|
||||
ModComp.CompLoaderType.OptiFine, ModComp.CompLoaderType.Iris, ModComp.CompLoaderType.Vanilla,
|
||||
ModComp.CompLoaderType.Canvas
|
||||
}.ToList();
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.Schematic:
|
||||
{
|
||||
requireLoaders = new[] { ModComp.CompLoaderType.Minecraft }.ToList();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
res.loaders = requireLoaders;
|
||||
res.compPath = PageInstanceLeft.McInstance.PathIndie +
|
||||
(PageInstanceLeft.McInstance.Info.HasLabyMod
|
||||
? Path.Combine("labymod-neo", "fabric", PageInstanceLeft.McInstance.Info.VanillaName)
|
||||
: "") + ModLocalComp.GetPathNameByCompType(currentCompType) + @"\";
|
||||
res.compType = currentCompType;
|
||||
return res;
|
||||
}
|
||||
|
||||
private bool isLoad;
|
||||
|
||||
public void PageOther_Loaded()
|
||||
{
|
||||
CurrentFolderPath = string.Empty;
|
||||
|
||||
if (ModMain.frmMain.pageLast.page != FormMain.PageType.CompDetail)
|
||||
PanBack.ScrollToHome();
|
||||
ModAnimation.AniControlEnabled += 1;
|
||||
selectedMods.Clear();
|
||||
ReloadCompFileList();
|
||||
ChangeAllSelected(false);
|
||||
ModAnimation.AniControlEnabled -= 1;
|
||||
|
||||
// 非重复加载部分
|
||||
if (isLoad)
|
||||
return;
|
||||
isLoad = true;
|
||||
|
||||
// 检查是否为原理图管理界面且首次打开
|
||||
if (currentCompType == ModComp.CompType.Schematic && !States.Hint.SchematicFirstTime)
|
||||
// 显示首次打开提示
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
ModMain.MyMsgBox(Lang.Text("Instance.Saves.Folder.DoubleClickHint.Message"), Lang.Text("Instance.Saves.Folder.DoubleClickHint.Title"), Lang.Text("Common.Action.GotIt"));
|
||||
States.Hint.SchematicFirstTime = true;
|
||||
}, true);
|
||||
|
||||
ModMain.frmMain.KeyDown += FrmMain_KeyDown;
|
||||
// 调整按钮边距(这玩意儿没法从 XAML 改)
|
||||
foreach (MyRadioButton Btn in PanFilter.Children)
|
||||
Btn.LabText.Margin = new Thickness(-2, 0d, 8d, 0d);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新 Mod 列表。
|
||||
/// </summary>
|
||||
public void ReloadCompFileList(bool forceReload = false)
|
||||
{
|
||||
if (LoaderRun(forceReload
|
||||
? ModLoader.LoaderFolderRunType.ForceRun
|
||||
: ModLoader.LoaderFolderRunType.RunOnUpdated))
|
||||
{
|
||||
ModBase.Log($"[System] 已刷新 {currentCompType} 列表");
|
||||
modFileInfoCache.Clear();
|
||||
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
Filter = FilterType.All;
|
||||
PanBack.ScrollToHome();
|
||||
SearchBox.Text = "";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 强制刷新
|
||||
private void RefreshSelf()
|
||||
{
|
||||
Refresh(currentCompType);
|
||||
}
|
||||
|
||||
void IRefreshable.Refresh()
|
||||
{
|
||||
RefreshSelf();
|
||||
}
|
||||
|
||||
public static void Refresh(ModComp.CompType whichPage)
|
||||
{
|
||||
// 强制刷新
|
||||
try
|
||||
{
|
||||
ModComp.compProjectCache.Clear();
|
||||
ModComp.compFilesCache.Clear();
|
||||
File.Delete(ModBase.pathTemp + @"Cache\LocalComp.json");
|
||||
ModBase.Log("[CompResource] 由于点击刷新按钮,清理本地工程信息缓存");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "强制刷新时清理本地工程信息缓存失败");
|
||||
}
|
||||
|
||||
switch (whichPage)
|
||||
{
|
||||
case ModComp.CompType.Mod:
|
||||
{
|
||||
if (ModMain.frmInstanceMod is not null)
|
||||
ModMain.frmInstanceMod.ReloadCompFileList(true); // 无需 Else,还没加载刷个鬼的新
|
||||
ModMain.frmInstanceLeft.ItemMod.Checked = true;
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.ResourcePack:
|
||||
{
|
||||
if (ModMain.frmInstanceResourcePack is not null)
|
||||
ModMain.frmInstanceResourcePack.ReloadCompFileList(true);
|
||||
ModMain.frmInstanceLeft.ItemResourcePack.Checked = true;
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.Shader:
|
||||
{
|
||||
if (ModMain.frmInstanceShader is not null)
|
||||
ModMain.frmInstanceShader.ReloadCompFileList(true);
|
||||
ModMain.frmInstanceLeft.ItemShader.Checked = true;
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.Schematic:
|
||||
{
|
||||
if (ModMain.frmInstanceSchematic is not null)
|
||||
ModMain.frmInstanceSchematic.ReloadCompFileList(true);
|
||||
ModMain.frmInstanceLeft.ItemSchematic.Checked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
HintService.Hint(Lang.Text("Instance.Left.Refreshing"), log: false);
|
||||
}
|
||||
|
||||
private void LoaderInit()
|
||||
{
|
||||
PageLoaderInit(Load, PanLoad, PanAllBack, null, ModLocalComp.compResourceListLoader,
|
||||
_ => LoadUIFromLoaderOutput(), () => currentCompType, false);
|
||||
}
|
||||
|
||||
private void Load_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (ModLocalComp.compResourceListLoader.State == ModBase.LoadState.Failed)
|
||||
LoaderRun(ModLoader.LoaderFolderRunType.ForceRun);
|
||||
}
|
||||
|
||||
public bool LoaderRun(ModLoader.LoaderFolderRunType type)
|
||||
{
|
||||
string loadPath;
|
||||
if (string.IsNullOrEmpty(CurrentFolderPath))
|
||||
// 加载根目录
|
||||
loadPath = PageInstanceLeft.McInstance.PathIndie +
|
||||
(PageInstanceLeft.McInstance.Info.HasLabyMod
|
||||
? Path.Combine("labymod-neo", "fabric", PageInstanceLeft.McInstance.Info.VanillaName)
|
||||
: "") + ModLocalComp.GetPathNameByCompType(currentCompType) + @"\";
|
||||
else
|
||||
// 加载当前文件夹
|
||||
loadPath = CurrentFolderPath;
|
||||
return ModLoader.LoaderFolderRun(ModLocalComp.compResourceListLoader, loadPath, type,
|
||||
loaderInput: GetRequireLoaderData());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 文件夹导航
|
||||
|
||||
/// <summary>
|
||||
/// 当前显示的文件夹路径。空字符串表示根目录。
|
||||
/// </summary>
|
||||
public string CurrentFolderPath { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 进入指定的文件夹。
|
||||
/// </summary>
|
||||
private void EnterFolder(string folderPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(folderPath) || !Directory.Exists(folderPath))
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Saves.Folder.NotFound"), HintType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentFolderPath = folderPath;
|
||||
ModBase.Log($"[原理图] 进入文件夹:{folderPath}");
|
||||
|
||||
ModLoader.LoaderFolderRun(ModLocalComp.compResourceListLoader, folderPath,
|
||||
ModLoader.LoaderFolderRunType.ForceRun, loaderInput: GetRequireLoaderData());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"进入文件夹失败",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Resource.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 进入指定文件夹。
|
||||
/// </summary>
|
||||
private void EnterFolderWithCheck(string folderPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
EnterFolder(folderPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"进入文件夹失败",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Resource.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回上级文件夹。
|
||||
/// </summary>
|
||||
private void GoBackToParentFolder()
|
||||
{
|
||||
if (string.IsNullOrEmpty(CurrentFolderPath))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// 获取根路径
|
||||
var rootPath = PageInstanceLeft.McInstance.PathIndie +
|
||||
(PageInstanceLeft.McInstance.Info.HasLabyMod
|
||||
? Path.Combine("labymod-neo", "fabric", PageInstanceLeft.McInstance.Info.VanillaName)
|
||||
: "") + ModLocalComp.GetPathNameByCompType(currentCompType) + @"\";
|
||||
rootPath = Path.GetFullPath(rootPath.TrimEnd('\\'));
|
||||
|
||||
// 获取父级路径
|
||||
var parentPath = Directory.GetParent(CurrentFolderPath)?.FullName;
|
||||
|
||||
// 如果父级路径就是根路径或者父级路径不在根路径范围内,则返回根目录
|
||||
if (parentPath is null || parentPath.Equals(rootPath, StringComparison.OrdinalIgnoreCase) ||
|
||||
!parentPath.StartsWith(rootPath + @"\", StringComparison.OrdinalIgnoreCase))
|
||||
CurrentFolderPath = "";
|
||||
else
|
||||
CurrentFolderPath = parentPath;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "路径处理失败");
|
||||
// 发生错误时直接返回根目录
|
||||
CurrentFolderPath = "";
|
||||
}
|
||||
|
||||
ModBase.Log($"[原理图] 返回上级文件夹:{(string.IsNullOrEmpty(CurrentFolderPath) ? "根目录" : CurrentFolderPath)}");
|
||||
|
||||
// 重新加载当前文件夹的内容
|
||||
string loadPath;
|
||||
if (string.IsNullOrEmpty(CurrentFolderPath))
|
||||
// 返回到根目录
|
||||
loadPath = PageInstanceLeft.McInstance.PathIndie +
|
||||
(PageInstanceLeft.McInstance.Info.HasLabyMod
|
||||
? Path.Combine("labymod-neo", "fabric", PageInstanceLeft.McInstance.Info.VanillaName)
|
||||
: "") + ModLocalComp.GetPathNameByCompType(currentCompType) + @"\";
|
||||
else
|
||||
// 加载当前文件夹
|
||||
loadPath = CurrentFolderPath;
|
||||
|
||||
// 强制刷新UI状态
|
||||
// 确保按钮状态正确
|
||||
ModBase.RunInUi(() =>
|
||||
BtnManageBack.Visibility =
|
||||
!string.IsNullOrEmpty(CurrentFolderPath) ? Visibility.Visible : Visibility.Collapsed);
|
||||
|
||||
// 延迟一帧后再加载,确保UI状态已更新
|
||||
ModBase.RunInUi(
|
||||
() => ModLoader.LoaderFolderRun(ModLocalComp.compResourceListLoader, loadPath,
|
||||
ModLoader.LoaderFolderRunType.ForceRun, loaderInput: GetRequireLoaderData()), true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UI 化
|
||||
|
||||
/// <summary>
|
||||
/// 已加载的 Mod UI 缓存,不确保按显示顺序排列。Key 为 Mod 的 RawPath。
|
||||
/// </summary>
|
||||
public Dictionary<string, MyLocalCompItem> modItems = new();
|
||||
|
||||
/// <summary>
|
||||
/// 将加载器结果的 Mod 列表加载为 UI。
|
||||
/// </summary>
|
||||
private void LoadUIFromLoaderOutput()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 判断应该显示哪一个页面
|
||||
if (ModLocalComp.compResourceListLoader.output.Any())
|
||||
{
|
||||
PanBack.Visibility = Visibility.Visible;
|
||||
PanEmpty.Visibility = Visibility.Collapsed;
|
||||
PanSchematicEmpty.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 检查是否为投影文件类型且schematics文件夹不存在
|
||||
if (currentCompType == ModComp.CompType.Schematic)
|
||||
{
|
||||
var schematicsPath = PageInstanceLeft.McInstance.PathIndie + @"schematics\";
|
||||
if (!Directory.Exists(schematicsPath))
|
||||
{
|
||||
PanSchematicEmpty.Visibility = Visibility.Visible;
|
||||
PanEmpty.Visibility = Visibility.Collapsed;
|
||||
PanBack.Visibility = Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 根据组件类型设置PanEmpty的文本内容
|
||||
if (currentCompType == ModComp.CompType.Schematic)
|
||||
{
|
||||
// 检查是否在子文件夹中
|
||||
if (!string.IsNullOrEmpty(CurrentFolderPath))
|
||||
{
|
||||
// 子文件夹为空的提示
|
||||
TxtEmptyTitle.Text = Lang.Text("Instance.Resource.EmptyFolder.Title");
|
||||
TxtEmptyDescription.Text = Lang.Text("Instance.Resource.EmptyFolder.Description");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 根目录为空的提示
|
||||
TxtEmptyTitle.Text = Lang.Text("Instance.Resource.Empty.Title");
|
||||
TxtEmptyDescription.Text = Lang.Text("Instance.Resource.Empty.Description");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TxtEmptyTitle.Text = Lang.Text("Instance.Resource.Empty.Title");
|
||||
TxtEmptyDescription.Text = Lang.Text("Instance.Resource.Empty.DescriptionWithDownload");
|
||||
}
|
||||
|
||||
// 如果当前在子文件夹中,显示返回上一级按钮
|
||||
if (!string.IsNullOrEmpty(CurrentFolderPath))
|
||||
BtnHintBack.Visibility = Visibility.Visible;
|
||||
else
|
||||
BtnHintBack.Visibility = Visibility.Collapsed;
|
||||
|
||||
PanEmpty.Visibility = Visibility.Visible;
|
||||
PanBack.Visibility = Visibility.Collapsed;
|
||||
PanSchematicEmpty.Visibility = Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
|
||||
// 修改缓存
|
||||
modItems.Clear();
|
||||
var rootPath = PageInstanceLeft.McInstance.PathIndie +
|
||||
(PageInstanceLeft.McInstance.Info.HasLabyMod
|
||||
? Path.Combine("labymod-neo", "fabric", PageInstanceLeft.McInstance.Info.VanillaName)
|
||||
: "") + ModLocalComp.GetPathNameByCompType(currentCompType) + @"\";
|
||||
rootPath = Path.GetFullPath(rootPath.TrimEnd('\\'));
|
||||
|
||||
var itemsToShow = ModLocalComp.compResourceListLoader.output.Where(item =>
|
||||
{
|
||||
var itemPath = item.IsFolder ? item.ActualPath : item.path;
|
||||
var parentDir = Directory.GetParent(itemPath)?.FullName;
|
||||
if (string.IsNullOrEmpty(CurrentFolderPath))
|
||||
return parentDir.Equals(rootPath, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
return parentDir.Equals(CurrentFolderPath, StringComparison.OrdinalIgnoreCase);
|
||||
}).ToList();
|
||||
|
||||
foreach (var ModEntity in itemsToShow)
|
||||
modItems[ModEntity.RawPath] = BuildLocalCompItem(ModEntity);
|
||||
// 显示结果
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
Filter = FilterType.All;
|
||||
SearchBox.Text = ""; // 这会触发结果刷新,所以需要在 ModItems 更新之后,详见 #3124 的视频
|
||||
RefreshUI();
|
||||
SetSortMethod(SortMethod.CompName);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"加载 {currentCompType} 列表 UI 失败",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Resource.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
private MyLocalCompItem BuildLocalCompItem(ModLocalComp.LocalCompFile entry)
|
||||
{
|
||||
try
|
||||
{
|
||||
ModAnimation.AniControlEnabled += 1;
|
||||
var newItem = new MyLocalCompItem
|
||||
{
|
||||
SnapsToDevicePixels = true,
|
||||
Entry = entry,
|
||||
buttonHandler = BuildLocalCompItemBtnHandler,
|
||||
Checked = selectedMods.Contains(entry.RawPath)
|
||||
};
|
||||
newItem.CurrentSwipe = currentSwipSelect;
|
||||
newItem.Tags = entry.Tags;
|
||||
entry.OnCompUpdate += _ => newItem.Refresh();
|
||||
// AddHandler Entry.OnCompUpdate, Sub() RunInUi(Sub() DoSort())
|
||||
newItem.Refresh();
|
||||
ModAnimation.AniControlEnabled -= 1;
|
||||
return newItem;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModAnimation.AniControlEnabled -= 1;
|
||||
ModBase.Log(ex, $"创建 UI 项失败:{entry.RawPath}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildLocalCompItemBtnHandler(MyLocalCompItem sender, EventArgs e)
|
||||
{
|
||||
// 点击事件
|
||||
sender.Changed += (ss, ee) => CheckChanged((MyLocalCompItem)ss, ee);
|
||||
if (sender.Entry.IsFolder)
|
||||
{
|
||||
// 文件夹项的点击事件:双击进入文件夹,单击切换选中状态
|
||||
var lastClickTime = DateTime.MinValue;
|
||||
sender.Click += (sss, _) =>
|
||||
{
|
||||
var ss = (MyLocalCompItem)sss;
|
||||
var currentTime = DateTime.Now;
|
||||
var timeDiff = (currentTime - lastClickTime).TotalMilliseconds;
|
||||
|
||||
if (timeDiff <= 300d)
|
||||
// 300ms内双击,进入文件夹
|
||||
EnterFolderWithCheck(ss.Entry.ActualPath);
|
||||
else
|
||||
// 单击切换选中状态
|
||||
ss.Checked = !ss.Checked;
|
||||
|
||||
lastClickTime = currentTime;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// 文件项的点击事件:切换选中状态
|
||||
sender.Click += (sss, _) =>
|
||||
{
|
||||
var ss = (MyLocalCompItem)sss;
|
||||
ss.Checked = !ss.Checked;
|
||||
};
|
||||
}
|
||||
|
||||
// 图标按钮
|
||||
var btnOpen = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/folder-open", Tag = sender };
|
||||
btnOpen.ToolTip = Lang.Text("Instance.Saves.OpenFileLocation");
|
||||
ToolTipService.SetPlacement(btnOpen, PlacementMode.Center);
|
||||
ToolTipService.SetVerticalOffset(btnOpen, 30d);
|
||||
ToolTipService.SetHorizontalOffset(btnOpen, 2d);
|
||||
btnOpen.Click += (ss, ee) => Open_Click((MyIconButton)ss, ee);
|
||||
var btnCont = new MyIconButton { LogoScale = 1d, SvgIcon = "lucide/info", Tag = sender };
|
||||
btnCont.ToolTip = Lang.Text("Instance.Saves.Detail");
|
||||
ToolTipService.SetPlacement(btnCont, PlacementMode.Center);
|
||||
ToolTipService.SetVerticalOffset(btnCont, 30d);
|
||||
ToolTipService.SetHorizontalOffset(btnCont, 2d);
|
||||
btnCont.Click += Info_Click;
|
||||
sender.MouseRightButtonUp += Info_Click;
|
||||
var btnDelete = new MyIconButton { LogoScale = 1d, SvgIcon = "lucide/trash-2", Tag = sender };
|
||||
btnDelete.ToolTip = Lang.Text("Common.Action.Delete");
|
||||
ToolTipService.SetPlacement(btnDelete, PlacementMode.Center);
|
||||
ToolTipService.SetVerticalOffset(btnDelete, 30d);
|
||||
ToolTipService.SetHorizontalOffset(btnDelete, 2d);
|
||||
btnDelete.Click += (ss, ee) => Delete_Click((MyIconButton)ss, ee);
|
||||
if (currentCompType != ModComp.CompType.Mod ||
|
||||
sender.Entry.State == ModLocalComp.LocalCompFile.LocalFileStatus.Unavailable)
|
||||
{
|
||||
sender.Buttons = new[] { btnCont, btnOpen, btnDelete };
|
||||
}
|
||||
else
|
||||
{
|
||||
var btnED = new MyIconButton
|
||||
{
|
||||
LogoScale = 1d,
|
||||
SvgIcon = sender.Entry.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine
|
||||
? "lucide/circle-minus"
|
||||
: "lucide/circle-check",
|
||||
Tag = sender
|
||||
};
|
||||
btnED.ToolTip = sender.Entry.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine ? Lang.Text("Instance.Resource.Disable") : Lang.Text("Instance.Resource.Enable");
|
||||
ToolTipService.SetPlacement(btnED, PlacementMode.Center);
|
||||
ToolTipService.SetVerticalOffset(btnED, 30d);
|
||||
ToolTipService.SetHorizontalOffset(btnED, 2d);
|
||||
btnED.Click += (ss, ee) => ED_Click((MyIconButton)ss, ee);
|
||||
sender.Buttons = new[] { btnCont, btnOpen, btnED, btnDelete };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新整个 UI。
|
||||
/// </summary>
|
||||
public void RefreshUI()
|
||||
{
|
||||
if (PanList is null)
|
||||
return;
|
||||
var showingMods = (IsSearching ? searchResult : modItems.Values.Select(i => i.Entry))
|
||||
.Where(m => CanPassFilter(m)).ToList();
|
||||
|
||||
// 对显示的资源进行排序,确保文件夹置顶
|
||||
if (showingMods.Any())
|
||||
{
|
||||
var sortMethod = GetSortMethod(currentSortMethod);
|
||||
showingMods.Sort((a, b) => sortMethod(a, b));
|
||||
}
|
||||
|
||||
// 重新列出列表
|
||||
ModAnimation.AniControlEnabled += 1;
|
||||
if (showingMods.Any())
|
||||
{
|
||||
PanList.Visibility = Visibility.Visible;
|
||||
PanList.Children.Clear();
|
||||
foreach (var TargetMod in showingMods)
|
||||
{
|
||||
if (!modItems.ContainsKey(TargetMod.RawPath))
|
||||
continue;
|
||||
var item = modItems[TargetMod.RawPath];
|
||||
|
||||
// 确保元素没有父容器,避免重复添加异常
|
||||
if (item.Parent is not null) ((Panel)item.Parent).Children.Remove(item);
|
||||
|
||||
ModStyle.MinecraftFormatter.SetColorfulTextLab(item.LabTitle.Text, item.LabTitle,
|
||||
ThemeService.IsDarkMode);
|
||||
ModStyle.MinecraftFormatter.SetColorfulTextLab(item.LabInfo.Text, item.LabInfo,
|
||||
ThemeService.IsDarkMode);
|
||||
item.Checked = selectedMods.Contains(TargetMod.RawPath); // 更新选中状态
|
||||
PanList.Children.Add(item);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PanList.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
ModAnimation.AniControlEnabled -= 1;
|
||||
selectedMods =
|
||||
new HashSet<string>(selectedMods.Where(m => showingMods.Any(s => (s.RawPath ?? "") == (m ?? ""))));
|
||||
RefreshBars();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新顶栏和底栏显示。
|
||||
/// </summary>
|
||||
public void RefreshBars()
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Func<Task>(async () =>
|
||||
{
|
||||
// -----------------
|
||||
// 顶部栏
|
||||
// -----------------
|
||||
|
||||
// 计数
|
||||
var anyCount = 0;
|
||||
var enabledCount = 0;
|
||||
var disabledCount = 0;
|
||||
var updateCount = 0;
|
||||
var unavalialeCount = 0;
|
||||
var itemSource = (IsSearching ? searchResult : modItems.Values.Select(i => i.Entry)).ToArray();
|
||||
await Task.Run(() =>
|
||||
{
|
||||
foreach (var item in itemSource)
|
||||
{
|
||||
anyCount += 1;
|
||||
if (item.CanUpdate) updateCount += 1;
|
||||
if (item.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine) enabledCount += 1;
|
||||
if (item.State == ModLocalComp.LocalCompFile.LocalFileStatus.Disabled) disabledCount += 1;
|
||||
if (item.State == ModLocalComp.LocalCompFile.LocalFileStatus.Unavailable) unavalialeCount += 1;
|
||||
}
|
||||
});
|
||||
// 显示
|
||||
BtnFilterAll.Text = IsSearching ? Lang.Text("Instance.Resource.Filter.SearchResult") : Lang.Text("Instance.Resource.Filter.AllWithCount", anyCount);
|
||||
BtnFilterCanUpdate.Text = Lang.Text("Instance.Resource.Filter.UpdatableWithCount", updateCount);
|
||||
BtnFilterCanUpdate.Visibility = Filter == FilterType.CanUpdate || updateCount > 0
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
BtnFilterEnabled.Text = Lang.Text("Instance.Resource.Filter.EnabledWithCount", enabledCount);
|
||||
BtnFilterEnabled.Visibility = Filter == FilterType.Enabled || (enabledCount > 0 && enabledCount < anyCount)
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
BtnFilterDisabled.Text = Lang.Text("Instance.Resource.Filter.DisabledWithCount", disabledCount);
|
||||
BtnFilterDisabled.Visibility = Filter == FilterType.Disabled || disabledCount > 0
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
BtnFilterError.Text = Lang.Text("Instance.Resource.Filter.ErrorWithCount", unavalialeCount);
|
||||
BtnFilterError.Visibility = Filter == FilterType.Unavailable || unavalialeCount > 0
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
// 查找重复项目
|
||||
var duplicateItems = await Task.Run(() => itemSource.GroupBy(m =>
|
||||
{
|
||||
if (m.Comp is null) return ":Nothing:";
|
||||
|
||||
return m.Comp.Id;
|
||||
}).Where(g => g.Count() > 1 && g.First().Comp is not null).SelectMany(g => g).ToList());
|
||||
BtnFilterDuplicate.Text = Lang.Text("Instance.Resource.Filter.DuplicateWithCount", duplicateItems.Count);
|
||||
BtnFilterDuplicate.Visibility = Filter == FilterType.Duplicate || duplicateItems.Any()
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
|
||||
// 返回按钮显示控制(在子文件夹中时显示)
|
||||
if (!string.IsNullOrEmpty(CurrentFolderPath))
|
||||
BtnManageBack.Visibility = Visibility.Visible;
|
||||
else
|
||||
BtnManageBack.Visibility = Visibility.Collapsed;
|
||||
|
||||
// -----------------
|
||||
// 底部栏
|
||||
// -----------------
|
||||
|
||||
// 计数
|
||||
var newCount = selectedMods.Count;
|
||||
var selected = newCount > 0;
|
||||
if (selected)
|
||||
LabSelect.Text = Lang.Text("Instance.Resource.SelectedCount", newCount); // 取消所有选择时不更新数字
|
||||
// 按钮可用性
|
||||
if (selected)
|
||||
{
|
||||
var hasUpdate = false;
|
||||
var hasEnabled = false;
|
||||
var hasDisabled = false;
|
||||
var canFavoriteAndShare = true; // 是否可以收藏和分享
|
||||
|
||||
|
||||
// 检查是否所有选中的资源都有有效的项目信息(即已完成联网更新)
|
||||
await Task.Run(() =>
|
||||
{
|
||||
foreach (var ModEntity in ModLocalComp.compResourceListLoader.output)
|
||||
if (selectedMods.Contains(ModEntity.RawPath))
|
||||
{
|
||||
if (ModEntity.CanUpdate) hasUpdate = true;
|
||||
if (ModEntity.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine)
|
||||
hasEnabled = true;
|
||||
else if (ModEntity.State == ModLocalComp.LocalCompFile.LocalFileStatus.Disabled)
|
||||
hasDisabled = true;
|
||||
if (ModEntity.Comp is null || string.IsNullOrEmpty(ModEntity.Comp.Id))
|
||||
canFavoriteAndShare = false;
|
||||
}
|
||||
});
|
||||
|
||||
BtnSelectDisable.IsEnabled = hasEnabled;
|
||||
BtnSelectEnable.IsEnabled = hasDisabled;
|
||||
BtnSelectUpdate.IsEnabled = hasUpdate;
|
||||
|
||||
// 针对投影原理图隐藏分享 更新 收藏按钮
|
||||
if (currentCompType == ModComp.CompType.Schematic)
|
||||
{
|
||||
BtnSelectUpdate.Visibility = Visibility.Collapsed;
|
||||
BtnSelectFavorites.Visibility = Visibility.Collapsed;
|
||||
BtnSelectShare.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
BtnSelectUpdate.Visibility = Visibility.Visible;
|
||||
BtnSelectFavorites.Visibility = Visibility.Visible;
|
||||
BtnSelectShare.Visibility = Visibility.Visible;
|
||||
|
||||
// 根据是否已加载项目信息来启用/禁用收藏和分享按钮
|
||||
BtnSelectFavorites.IsEnabled = canFavoriteAndShare;
|
||||
BtnSelectShare.IsEnabled = canFavoriteAndShare;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新显示状态
|
||||
if (ModAnimation.AniControlEnabled == 0)
|
||||
{
|
||||
PanListBack.Margin = new Thickness(0d, 0d, 0d, selected ? 95 : 15);
|
||||
if (selected)
|
||||
{
|
||||
// 仅在数量增加时播放出现/跳跃动画
|
||||
if (bottomBarShownCount >= newCount)
|
||||
{
|
||||
bottomBarShownCount = newCount;
|
||||
return;
|
||||
}
|
||||
|
||||
bottomBarShownCount = newCount;
|
||||
// 出现/跳跃动画
|
||||
CardSelect.Visibility = Visibility.Visible;
|
||||
ModAnimation.AniStart(
|
||||
new[]
|
||||
{
|
||||
ModAnimation.AaOpacity(CardSelect, 1d - CardSelect.Opacity, 60),
|
||||
ModAnimation.AaTranslateY(CardSelect, -27 - TransSelect.Y, 120,
|
||||
ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak)),
|
||||
ModAnimation.AaTranslateY(CardSelect, 3d, 150, 120,
|
||||
new ModAnimation.AniEaseInoutFluent(ModAnimation.AniEasePower.Weak)),
|
||||
ModAnimation.AaTranslateY(CardSelect, -1, 90, 270,
|
||||
new ModAnimation.AniEaseInoutFluent(ModAnimation.AniEasePower.Weak))
|
||||
}, "Mod Sidebar");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 不重复播放隐藏动画
|
||||
if (bottomBarShownCount == 0)
|
||||
return;
|
||||
bottomBarShownCount = 0;
|
||||
// 隐藏动画
|
||||
ModAnimation.AniStart(
|
||||
new[]
|
||||
{
|
||||
ModAnimation.AaOpacity(CardSelect, -CardSelect.Opacity, 90),
|
||||
ModAnimation.AaTranslateY(CardSelect, -10 - TransSelect.Y, 90,
|
||||
ease: new ModAnimation.AniEaseInFluent(ModAnimation.AniEasePower.Weak)),
|
||||
ModAnimation.AaCode(() => CardSelect.Visibility = Visibility.Collapsed, after: true)
|
||||
}, "Mod Sidebar");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ModAnimation.AniStop("Mod Sidebar");
|
||||
bottomBarShownCount = newCount;
|
||||
if (selected)
|
||||
{
|
||||
CardSelect.Visibility = Visibility.Visible;
|
||||
CardSelect.Opacity = 1d;
|
||||
TransSelect.Y = -25;
|
||||
}
|
||||
else
|
||||
{
|
||||
CardSelect.Visibility = Visibility.Collapsed;
|
||||
CardSelect.Opacity = 0d;
|
||||
TransSelect.Y = -10;
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private int bottomBarShownCount;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 管理
|
||||
|
||||
/// <summary>
|
||||
/// 打开 Mods 文件夹。
|
||||
/// </summary>
|
||||
private void BtnManageBack_Click(object sender, EventArgs e)
|
||||
{
|
||||
GoBackToParentFolder();
|
||||
}
|
||||
|
||||
private void BtnHintBack_Click(object sender, EventArgs e)
|
||||
{
|
||||
GoBackToParentFolder();
|
||||
}
|
||||
|
||||
private void BtnManageOpen_Click(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
string compFilePath;
|
||||
|
||||
// 如果当前在子文件夹中,则打开当前子文件夹;否则打开根目录
|
||||
if (string.IsNullOrEmpty(CurrentFolderPath))
|
||||
// 打开根目录
|
||||
compFilePath = PageInstanceLeft.McInstance.PathIndie +
|
||||
(PageInstanceLeft.McInstance.Info.HasLabyMod
|
||||
? Path.Combine("labymod-neo", "fabric", PageInstanceLeft.McInstance.Info.VanillaName)
|
||||
: "") + ModLocalComp.GetPathNameByCompType(currentCompType) + @"\";
|
||||
else
|
||||
// 打开当前子文件夹
|
||||
compFilePath = CurrentFolderPath.EndsWith(@"\") ? CurrentFolderPath : CurrentFolderPath + @"\";
|
||||
Directory.CreateDirectory(compFilePath);
|
||||
ModBase.OpenExplorer(compFilePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"打开 Mods 文件夹失败",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Resource.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 全选。
|
||||
/// </summary>
|
||||
private void BtnManageSelectAll_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
ChangeAllSelected(selectedMods.Count < PanList.Children.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安装 Mod。
|
||||
/// </summary>
|
||||
private void BtnManageInstall_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
string[] fileList = null;
|
||||
switch (currentCompType)
|
||||
{
|
||||
case ModComp.CompType.Mod:
|
||||
{
|
||||
fileList = SystemDialogs.SelectFiles(
|
||||
Lang.Text("Instance.Resource.Install.FileDialog.Mod.Filter"),
|
||||
Lang.Text("Instance.Resource.Install.FileDialog.Mod.Title"));
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.ResourcePack:
|
||||
{
|
||||
fileList = SystemDialogs.SelectFiles(
|
||||
Lang.Text("Instance.Resource.Install.FileDialog.ResourcePack.Filter"),
|
||||
Lang.Text("Instance.Resource.Install.FileDialog.ResourcePack.Title"));
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.Shader:
|
||||
{
|
||||
fileList = SystemDialogs.SelectFiles(
|
||||
Lang.Text("Instance.Resource.Install.FileDialog.Shader.Filter"),
|
||||
Lang.Text("Instance.Resource.Install.FileDialog.Shader.Title"));
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.Schematic:
|
||||
{
|
||||
fileList = SystemDialogs.SelectFiles(
|
||||
Lang.Text("Instance.Resource.Install.FileDialog.Schematic.Filter"),
|
||||
Lang.Text("Instance.Resource.Install.FileDialog.Schematic.Title"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fileList is null || !fileList.Any())
|
||||
return;
|
||||
InstallCompFiles(fileList, currentCompType, CurrentFolderPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试安装 Mod。
|
||||
/// 返回输入的文件是否为一个 Mod 文件,仅用于判断拖拽行为。
|
||||
/// </summary>
|
||||
public static bool InstallMods(IEnumerable<string> filePathList)
|
||||
{
|
||||
if (!filePathList.Any()) return false;
|
||||
|
||||
// 1. Check file extension
|
||||
var firstFile = filePathList.First();
|
||||
var extension = firstFile.Split('.').LastOrDefault()?.ToLower();
|
||||
string[] allowedExtensions = { "jar", "litemod", "disabled", "old" };
|
||||
|
||||
if (!allowedExtensions.Contains(extension)) return false;
|
||||
|
||||
LogWrapper.Info("[System] 文件格式为 jar/litemod,尝试安装为 Mod");
|
||||
|
||||
// 2. Check recycle bin
|
||||
if (firstFile.Contains(@":\$RECYCLE.BIN\"))
|
||||
{
|
||||
HintWrapper.Show(Lang.Text("Instance.Resource.Install.RestoreFromRecycleBin"), HintTheme.Error);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. Determine target instance
|
||||
var targetInstance = ModInstanceList.McMcInstanceSelected;
|
||||
if (ModMain.frmMain.pageCurrent == FormMain.PageType.InstanceSetup) targetInstance = PageInstanceLeft.McInstance;
|
||||
|
||||
// 4. Validate instance status
|
||||
if (ModMain.frmMain.pageCurrent == FormMain.PageType.InstanceSelect || targetInstance is null ||
|
||||
!targetInstance.Modable)
|
||||
{
|
||||
HintWrapper.Show(Lang.Text("Instance.Resource.Install.SelectModableInstance"));
|
||||
return true;
|
||||
}
|
||||
|
||||
// 5. Check if user confirmation is required
|
||||
var isModPage = ModMain.frmMain.pageCurrent == FormMain.PageType.InstanceSetup &&
|
||||
ModMain.frmMain.PageCurrentSub == FormMain.PageSubType.VersionMod;
|
||||
|
||||
if (!isModPage)
|
||||
{
|
||||
if (ModMain.MyMsgBox(Lang.Text("Instance.Resource.Install.ModConfirm.Message", targetInstance.Name), Lang.Text("Instance.Resource.Install.ModConfirm.Title"), Lang.Text("Common.Action.Confirm"), Lang.Text("Common.Action.Cancel")) !=
|
||||
1) return true;
|
||||
}
|
||||
|
||||
// 6. Execution: Install Mods
|
||||
ExecuteModInstallation(targetInstance, filePathList, isModPage);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void ExecuteModInstallation(McInstance targetMcInstance, IEnumerable<string> filePathList,
|
||||
bool refreshList)
|
||||
{
|
||||
// Path resolution logic
|
||||
var modPathSuffix = targetMcInstance.Info.HasLabyMod
|
||||
? $@"labymod-neo\fabric\{targetMcInstance.Info.VanillaName}\"
|
||||
: "";
|
||||
var modFolder = $@"{targetMcInstance.PathIndie}{modPathSuffix}mods\";
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var modFile in filePathList)
|
||||
{
|
||||
var fileName = ModBase.GetFileNameFromPath(modFile)
|
||||
.Replace(".disabled", "")
|
||||
.Replace(".old", "");
|
||||
|
||||
if (!fileName.Contains(".")) fileName += ".jar"; // Ensure extension (#4227)
|
||||
|
||||
ModBase.CopyFile(modFile, Path.Combine(modFolder, fileName));
|
||||
}
|
||||
|
||||
// Success hint
|
||||
if (filePathList.Count() == 1)
|
||||
{
|
||||
var installedName = ModBase.GetFileNameFromPath(filePathList.First()).Replace(".disabled", "")
|
||||
.Replace(".old", "");
|
||||
HintWrapper.Show(Lang.Text("Instance.Resource.Install.SuccessSingle", installedName), HintTheme.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
HintWrapper.Show(Lang.Text("Instance.Resource.Install.SuccessMultiple", filePathList.Count(), Lang.Text("Download.Comp.Type.Mod")), HintTheme.Success);
|
||||
}
|
||||
|
||||
// 7. Refresh list if necessary
|
||||
if (refreshList)
|
||||
ModLoader.LoaderFolderRun(ModLocalComp.compResourceListLoader,
|
||||
modFolder,
|
||||
ModLoader.LoaderFolderRunType.ForceRun,
|
||||
loaderInput: ModMain.frmInstanceMod.GetRequireLoaderData()
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogWrapper.Error(ex, "拷贝文件失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安装组件文件(Mod、资源包、光影包、投影文件等)。
|
||||
/// </summary>
|
||||
public static void InstallCompFiles(IEnumerable<string> filePathList, ModComp.CompType compType,
|
||||
string targetFolderPath = "")
|
||||
{
|
||||
if (!filePathList.Any())
|
||||
return;
|
||||
|
||||
var extension = filePathList.First().AfterLast(".").ToLower();
|
||||
string[] validExtensions = null;
|
||||
var compTypeName = "";
|
||||
var compFolder = "";
|
||||
|
||||
// 检查回收站:回收站中的文件有错误的文件名
|
||||
if (filePathList.First().Contains(@":\$RECYCLE.BIN\"))
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Install.RestoreFromRecycleBin"), HintType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取并检查目标实例
|
||||
var targetInstance = ModInstanceList.McMcInstanceSelected;
|
||||
if (ModMain.frmMain.pageCurrent == FormMain.PageType.InstanceSetup)
|
||||
targetInstance = PageInstanceLeft.McInstance;
|
||||
|
||||
// 根据组件类型设置相关参数
|
||||
switch (compType)
|
||||
{
|
||||
case ModComp.CompType.Mod:
|
||||
{
|
||||
validExtensions = new[] { "jar", "litemod", "disabled", "old" };
|
||||
compTypeName = "Mod";
|
||||
if (string.IsNullOrEmpty(targetFolderPath))
|
||||
compFolder = targetInstance.PathIndie +
|
||||
(targetInstance.Info.HasLabyMod
|
||||
? Path.Combine("labymod-neo", "fabric", targetInstance.Info.VanillaName)
|
||||
: "") + @"mods\";
|
||||
else
|
||||
compFolder = targetFolderPath;
|
||||
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.ResourcePack:
|
||||
{
|
||||
validExtensions = new[] { "zip" };
|
||||
compTypeName = Lang.Text("Download.Comp.Type.ResourcePack");
|
||||
if (string.IsNullOrEmpty(targetFolderPath))
|
||||
compFolder = targetInstance.PathIndie + @"resourcepacks\";
|
||||
else
|
||||
compFolder = targetFolderPath;
|
||||
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.Shader:
|
||||
{
|
||||
validExtensions = new[] { "zip" };
|
||||
compTypeName = Lang.Text("Download.Comp.Type.Shader");
|
||||
if (string.IsNullOrEmpty(targetFolderPath))
|
||||
compFolder = targetInstance.PathIndie + @"shaderpacks\";
|
||||
else
|
||||
compFolder = targetFolderPath;
|
||||
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.Schematic:
|
||||
{
|
||||
validExtensions = new[] { "litematic", "nbt", "schematic", "schem" };
|
||||
compTypeName = Lang.Text("Download.Comp.Type.Schematic");
|
||||
if (string.IsNullOrEmpty(targetFolderPath))
|
||||
compFolder = targetInstance.PathIndie + @"schematics\";
|
||||
else
|
||||
compFolder = targetFolderPath;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查文件扩展名
|
||||
if (!validExtensions.Contains(extension))
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Install.UnsupportedFormat", extension, compTypeName, string.Join(", ", validExtensions)),
|
||||
HintType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
ModBase.Log($"[System] 文件为 {extension} 格式,尝试作为{compTypeName}安装");
|
||||
|
||||
// 检查实例兼容性
|
||||
if (compType == ModComp.CompType.Mod && (ModMain.frmMain.pageCurrent == FormMain.PageType.InstanceSelect ||
|
||||
targetInstance is null || !targetInstance.Modable))
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Install.SelectModableInstance"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 确认安装
|
||||
var currentPage = FormMain.PageSubType.VersionMod;
|
||||
switch (compType)
|
||||
{
|
||||
case ModComp.CompType.Mod:
|
||||
{
|
||||
currentPage = FormMain.PageSubType.VersionMod;
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.ResourcePack:
|
||||
{
|
||||
currentPage = FormMain.PageSubType.VersionResourcePack;
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.Shader:
|
||||
{
|
||||
currentPage = FormMain.PageSubType.VersionShader;
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.Schematic:
|
||||
{
|
||||
currentPage = FormMain.PageSubType.VersionSchematic;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(ModMain.frmMain.pageCurrent == FormMain.PageType.InstanceSetup &&
|
||||
ModMain.frmMain.PageCurrentSub == currentPage))
|
||||
if (ModMain.MyMsgBox(
|
||||
Lang.Text("Instance.Resource.Install.GenericConfirm.Message", compTypeName, targetInstance.Name),
|
||||
Lang.Text("Instance.Resource.Install.GenericConfirm.Title", compTypeName), Lang.Text("Common.Action.Confirm"), Lang.Text("Common.Action.Cancel")) != 1)
|
||||
return;
|
||||
|
||||
// 执行安装
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(compFolder);
|
||||
foreach (var FilePath in filePathList)
|
||||
{
|
||||
var newFileName = ModBase.GetFileNameFromPath(FilePath);
|
||||
if (compType == ModComp.CompType.Mod)
|
||||
{
|
||||
newFileName = newFileName.Replace(".disabled", "").Replace(".old", "");
|
||||
if (!newFileName.Contains("."))
|
||||
newFileName += ".jar";
|
||||
}
|
||||
|
||||
var destFile = compFolder + newFileName;
|
||||
if (File.Exists(destFile))
|
||||
if (ModMain.MyMsgBox(Lang.Text("Instance.Resource.Install.OverwriteConfirm.Message", newFileName), Lang.Text("Instance.Resource.Install.OverwriteConfirm.Title"), Lang.Text("Common.Action.Overwrite"), Lang.Text("Common.Action.Cancel")) != 1)
|
||||
continue;
|
||||
|
||||
ModBase.CopyFile(FilePath, destFile);
|
||||
}
|
||||
|
||||
if (filePathList.Count() == 1)
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Install.SuccessSingle", ModBase.GetFileNameFromPath(filePathList.First())), HintType.Success);
|
||||
else
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Install.SuccessMultiple", filePathList.Count(), compTypeName), HintType.Success);
|
||||
|
||||
// 刷新列表
|
||||
if (ModMain.frmMain.pageCurrent == FormMain.PageType.InstanceSetup &&
|
||||
ModMain.frmMain.PageCurrentSub == currentPage)
|
||||
switch (compType)
|
||||
{
|
||||
case ModComp.CompType.Mod:
|
||||
{
|
||||
if (ModMain.frmInstanceMod is not null)
|
||||
ModLoader.LoaderFolderRun(ModLocalComp.compResourceListLoader, compFolder,
|
||||
ModLoader.LoaderFolderRunType.ForceRun,
|
||||
loaderInput: ModMain.frmInstanceMod?.GetRequireLoaderData());
|
||||
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.ResourcePack:
|
||||
case ModComp.CompType.Shader:
|
||||
case ModComp.CompType.Schematic:
|
||||
{
|
||||
var currentForm = GetCurrentCompResourceForm();
|
||||
if (currentForm is not null) ModBase.RunInUi(() => currentForm.ReloadCompFileList(true));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"复制{compTypeName}文件失败",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Resource.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前的组件资源管理窗体。
|
||||
/// </summary>
|
||||
private static PageInstanceCompResource GetCurrentCompResourceForm()
|
||||
{
|
||||
switch (ModMain.frmMain.PageCurrentSub)
|
||||
{
|
||||
case FormMain.PageSubType.VersionMod:
|
||||
{
|
||||
return ModMain.frmInstanceMod;
|
||||
}
|
||||
case FormMain.PageSubType.VersionResourcePack:
|
||||
{
|
||||
return ModMain.frmInstanceResourcePack;
|
||||
}
|
||||
case FormMain.PageSubType.VersionShader:
|
||||
{
|
||||
return ModMain.frmInstanceShader;
|
||||
}
|
||||
case FormMain.PageSubType.VersionSchematic:
|
||||
{
|
||||
return ModMain.frmInstanceSchematic;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnManageInfoExport_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var choice =
|
||||
ModMain.MyMsgBox(
|
||||
Lang.Text("Instance.Resource.Export.Mode.Message"), Lang.Text("Instance.Resource.Export.Mode.Title"), Lang.Text("Instance.Resource.Export.Mode.Txt"), Lang.Text("Instance.Resource.Export.Mode.Csv"), Lang.Text("Common.Action.Cancel"));
|
||||
|
||||
void ExportText(string content, string fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var savePath =
|
||||
SystemDialogs.SelectSaveFile(Lang.Text("Instance.Resource.Export.SelectSaveLocation"), fileName, Lang.Text("Instance.Resource.Export.FilesFilter"));
|
||||
if (string.IsNullOrWhiteSpace(savePath)) return;
|
||||
File.WriteAllText(savePath, content, Encoding.UTF8);
|
||||
ModBase.OpenExplorer(savePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"导出资源信息失败",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Resource.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
;
|
||||
switch (choice)
|
||||
{
|
||||
case 1: // TXT
|
||||
{
|
||||
var exportContent = new List<string>();
|
||||
foreach (var ModEntity in ModLocalComp.compResourceListLoader.output)
|
||||
{
|
||||
exportContent.Add(ModEntity.FileName);
|
||||
_AppendEmbeddedForExport(exportContent, ModEntity.EmbeddedMods, 1);
|
||||
}
|
||||
ExportText(exportContent.Join("\r\n"), PageInstanceLeft.McInstance.Name + "已安装的资源信息.txt");
|
||||
break;
|
||||
}
|
||||
|
||||
case 2: // CSV
|
||||
{
|
||||
var exportContent = new List<string>();
|
||||
exportContent.Add("文件名,资源名称,资源版本,此版本更新时间,Mod ID,对应平台工程 ID,文件大小(字节),文件路径,内嵌模组");
|
||||
foreach (var ModEntity in ModLocalComp.compResourceListLoader.output)
|
||||
exportContent.Add(
|
||||
$"{ModEntity.FileName},{ModEntity.Comp?.TranslatedName},{ModEntity.Version},{ModEntity.compFile?.ReleaseDate},{ModEntity.ModId},{ModEntity.Comp?.Id},{GetModFileInfo(ModEntity.path).Length},{ModEntity.path},{string.Join(";", _FlattenEmbeddedNames(ModEntity.EmbeddedMods))}");
|
||||
ExportText(exportContent.Join("\r\n"), PageInstanceLeft.McInstance.Name + "已安装的资源信息.csv");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void _AppendEmbeddedForExport(List<string> lines, List<ModLocalComp.LocalCompFile> mods, int depth)
|
||||
{
|
||||
var indent = new string('\t', depth);
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
var line = indent + "└ " + (mod.Name ?? mod.ModId ?? mod.FileName);
|
||||
if (!string.IsNullOrWhiteSpace(mod.Version))
|
||||
line += $" ({mod.Version})";
|
||||
lines.Add(line);
|
||||
if (mod.EmbeddedMods is { Count: > 0 })
|
||||
_AppendEmbeddedForExport(lines, mod.EmbeddedMods, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<string> _FlattenEmbeddedNames(List<ModLocalComp.LocalCompFile> mods)
|
||||
{
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
yield return mod.Name ?? mod.ModId ?? mod.FileName;
|
||||
if (mod.EmbeddedMods is { Count: > 0 })
|
||||
foreach (var child in _FlattenEmbeddedNames(mod.EmbeddedMods))
|
||||
yield return child;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载 Mod。
|
||||
/// </summary>
|
||||
private void BtnManageDownload_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
switch (currentCompType)
|
||||
{
|
||||
case ModComp.CompType.Mod:
|
||||
{
|
||||
ModMain.frmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadMod);
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.ResourcePack:
|
||||
{
|
||||
ModMain.frmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadResourcePack);
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.Shader:
|
||||
{
|
||||
ModMain.frmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadShader);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PageComp.targetVersion = PageInstanceLeft.McInstance; // 将当前实例设置为筛选器
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载投影Mod按钮点击事件。
|
||||
/// </summary>
|
||||
private void BtnSchematicDownloadMod_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
ModMain.frmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadMod);
|
||||
PageComp.targetVersion = PageInstanceLeft.McInstance; // 将当前实例设置为筛选器
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实例选择按钮点击事件。
|
||||
/// </summary>
|
||||
private void BtnSchematicVersionSelect_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
ModMain.frmMain.PageChange(FormMain.PageType.Launch);
|
||||
ModMain.frmMain.PageChange(FormMain.PageType.InstanceSelect);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 选择
|
||||
|
||||
/// <summary>
|
||||
/// 选择的 Mod 的路径(不含 .disabled 和 .old)。
|
||||
/// </summary>
|
||||
public HashSet<string> selectedMods = new();
|
||||
|
||||
// 单项切换选择状态
|
||||
public void CheckChanged(MyLocalCompItem sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
if (ModAnimation.AniControlEnabled != 0)
|
||||
return;
|
||||
// 更新选择了的内容
|
||||
var selectedKey = sender.Entry.RawPath;
|
||||
if (sender.Checked)
|
||||
selectedMods.Add(selectedKey);
|
||||
else
|
||||
selectedMods.Remove(selectedKey);
|
||||
RefreshBars();
|
||||
}
|
||||
|
||||
// 切换所有项的选择状态
|
||||
private void ChangeAllSelected(bool value)
|
||||
{
|
||||
ModAnimation.AniControlEnabled += 1;
|
||||
selectedMods.Clear();
|
||||
foreach (var Item in modItems.Values)
|
||||
{
|
||||
// #4992,Mod 从过滤器看可能不应在列表中,但因为刚切换状态所以依然保留在列表中,所以应该从列表 UI 判断,而非从过滤器判断
|
||||
var shouldSelected = value && PanList.Children.Contains(Item);
|
||||
Item.Checked = shouldSelected;
|
||||
if (shouldSelected)
|
||||
selectedMods.Add(Item.Entry.RawPath);
|
||||
}
|
||||
|
||||
ModAnimation.AniControlEnabled -= 1;
|
||||
RefreshBars();
|
||||
}
|
||||
|
||||
private void UnselectedAllWithAnimation()
|
||||
{
|
||||
var cacheAniControlEnabled = ModAnimation.AniControlEnabled;
|
||||
ModAnimation.AniControlEnabled = 0;
|
||||
ChangeAllSelected(false);
|
||||
ModAnimation.AniControlEnabled += cacheAniControlEnabled;
|
||||
}
|
||||
|
||||
private void FrmMain_KeyDown(object sender, KeyEventArgs e) // 若监听自己的事件则在进入页面后需点击右侧控件才可监听到 (#4311)
|
||||
{
|
||||
if (!ReferenceEquals(ModMain.frmMain.pageRight, this))
|
||||
return;
|
||||
if ((Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) && e.Key == Key.A)
|
||||
ChangeAllSelected(true);
|
||||
}
|
||||
|
||||
private void SearchBox_PreviewKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
// Ctrl + A 会被搜索框捕获,导致无法全选,所以在按下 Ctrl + A 时转移焦点以便捕获
|
||||
if (SearchBox.Text.Any())
|
||||
return;
|
||||
if ((Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) && e.Key == Key.A)
|
||||
PanBack.Focus();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 筛选
|
||||
|
||||
public FilterType Filter
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
if (field == value)
|
||||
return;
|
||||
field = value;
|
||||
switch (value)
|
||||
{
|
||||
case FilterType.All:
|
||||
{
|
||||
BtnFilterAll.Checked = true;
|
||||
break;
|
||||
}
|
||||
case FilterType.Enabled:
|
||||
{
|
||||
BtnFilterEnabled.Checked = true;
|
||||
break;
|
||||
}
|
||||
case FilterType.Disabled:
|
||||
{
|
||||
BtnFilterDisabled.Checked = true;
|
||||
break;
|
||||
}
|
||||
case FilterType.CanUpdate:
|
||||
{
|
||||
BtnFilterCanUpdate.Checked = true;
|
||||
break;
|
||||
}
|
||||
case FilterType.Duplicate:
|
||||
{
|
||||
BtnFilterDuplicate.Checked = true;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
BtnFilterError.Checked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
RefreshUI();
|
||||
}
|
||||
} = FilterType.All;
|
||||
|
||||
public enum FilterType
|
||||
{
|
||||
All = 0,
|
||||
Enabled = 1,
|
||||
Disabled = 2,
|
||||
CanUpdate = 3,
|
||||
Unavailable = 4,
|
||||
Duplicate = 5
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查该 Mod 项是否符合当前筛选的类别。
|
||||
/// </summary>
|
||||
private bool CanPassFilter(ModLocalComp.LocalCompFile checkingMod)
|
||||
{
|
||||
switch (Filter)
|
||||
{
|
||||
case FilterType.All:
|
||||
{
|
||||
return true;
|
||||
}
|
||||
case FilterType.Enabled:
|
||||
{
|
||||
return checkingMod.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine;
|
||||
}
|
||||
case FilterType.Disabled:
|
||||
{
|
||||
return checkingMod.State == ModLocalComp.LocalCompFile.LocalFileStatus.Disabled;
|
||||
}
|
||||
case FilterType.CanUpdate:
|
||||
{
|
||||
return checkingMod.CanUpdate;
|
||||
}
|
||||
case FilterType.Unavailable:
|
||||
{
|
||||
return checkingMod.State == ModLocalComp.LocalCompFile.LocalFileStatus.Unavailable;
|
||||
}
|
||||
case FilterType.Duplicate:
|
||||
{
|
||||
var itemSource = IsSearching
|
||||
? searchResult
|
||||
: ModLocalComp.compResourceListLoader.output ?? new List<ModLocalComp.LocalCompFile>();
|
||||
return itemSource is not null && itemSource.Where(m =>
|
||||
checkingMod.Comp is not null && m.Comp is not null &&
|
||||
(checkingMod.Comp.Id ?? "") == (m.Comp.Id ?? "")).Skip(1).Any();
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 点击筛选项触发的改变
|
||||
private void ChangeFilter(MyRadioButton sender, bool raiseByMouse)
|
||||
{
|
||||
Filter = (FilterType)Convert.ToInt32(sender.Tag);
|
||||
RefreshUI();
|
||||
DoSort();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 排序
|
||||
|
||||
private SortMethod currentSortMethod = SortMethod.CompName;
|
||||
|
||||
private void SetSortMethod(SortMethod target)
|
||||
{
|
||||
currentSortMethod = target;
|
||||
BtnSort.Text = Lang.Text("Instance.Resource.Sort.Text", GetSortName(target));
|
||||
// RefreshUI()
|
||||
DoSort();
|
||||
}
|
||||
|
||||
private enum SortMethod
|
||||
{
|
||||
FileName,
|
||||
CompName,
|
||||
TagNums,
|
||||
CreateTime,
|
||||
ModFileSize
|
||||
}
|
||||
|
||||
private string GetSortName(SortMethod method)
|
||||
{
|
||||
switch (method)
|
||||
{
|
||||
case SortMethod.FileName:
|
||||
{
|
||||
return Lang.Text("Instance.Resource.Sort.FileName");
|
||||
}
|
||||
case SortMethod.CompName:
|
||||
{
|
||||
return Lang.Text("Instance.Resource.Sort.ResourceName");
|
||||
}
|
||||
case SortMethod.TagNums:
|
||||
{
|
||||
return Lang.Text("Instance.Resource.Sort.TagCount");
|
||||
}
|
||||
case SortMethod.CreateTime:
|
||||
{
|
||||
return Lang.Text("Instance.Resource.Sort.AddTime");
|
||||
}
|
||||
case SortMethod.ModFileSize:
|
||||
{
|
||||
return Lang.Text("Instance.Resource.Sort.FileSize");
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return Lang.Text("Instance.Resource.Sort.ResourceName");
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private void BtnSortClick(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
var body = new ContextMenu();
|
||||
foreach (SortMethod i in Enum.GetValues(typeof(SortMethod)))
|
||||
{
|
||||
var item = new MyMenuItem();
|
||||
item.Header = GetSortName(i);
|
||||
item.Click += (_, _) => SetSortMethod(i);
|
||||
body.Items.Add(item);
|
||||
}
|
||||
|
||||
body.PlacementTarget = (UIElement)sender;
|
||||
body.Placement = PlacementMode.Bottom;
|
||||
body.IsOpen = true;
|
||||
}
|
||||
|
||||
private readonly object sortLock = new();
|
||||
|
||||
private void DoSort()
|
||||
{
|
||||
lock (sortLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (PanList is null || PanList.Children.Count < 2)
|
||||
return;
|
||||
|
||||
// 将子元素转换为可排序的列表
|
||||
var items = PanList.Children.OfType<MyLocalCompItem>().ToList();
|
||||
var method = GetSortMethod(currentSortMethod);
|
||||
|
||||
// 分离有效和无效项(保持原始相对顺序)
|
||||
var invalid = items.Where(i =>
|
||||
i.Entry is null || (currentSortMethod == SortMethod.TagNums && i.Entry.Comp is null &&
|
||||
!i.Entry.IsFolder)).ToList();
|
||||
var valid = items.Except(invalid).ToList();
|
||||
// 仅对有效项进行排序
|
||||
valid.Sort((x, y) => method(x.Entry, y.Entry));
|
||||
// 合并保持无效项的原始顺序
|
||||
items = valid.Concat(invalid).ToList();
|
||||
|
||||
// 批量更新UI元素
|
||||
PanList.Children.Clear();
|
||||
items.ForEach(i => PanList.Children.Add(i));
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"执行排序时出错",
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Instance.Resource.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Func<ModLocalComp.LocalCompFile, ModLocalComp.LocalCompFile, int> GetSortMethod(SortMethod method)
|
||||
{
|
||||
// 通用的文件夹置顶比较函数
|
||||
int folderFirstCompare(ModLocalComp.LocalCompFile a, ModLocalComp.LocalCompFile b)
|
||||
{
|
||||
if (a.IsFolder && !b.IsFolder)
|
||||
return -1;
|
||||
if (!a.IsFolder && b.IsFolder)
|
||||
return 1;
|
||||
return 0; // 相同类型,需要进一步比较
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
switch (method)
|
||||
{
|
||||
case SortMethod.FileName:
|
||||
{
|
||||
return (a, b) =>
|
||||
{
|
||||
// 文件夹始终排在最前面
|
||||
var folderResult = folderFirstCompare(a, b);
|
||||
if (folderResult != 0)
|
||||
return folderResult;
|
||||
// 如果都是文件夹或都是文件,则按文件名排序
|
||||
return string.Compare(a.FileName, b.FileName, StringComparison.OrdinalIgnoreCase);
|
||||
};
|
||||
}
|
||||
case SortMethod.CompName:
|
||||
{
|
||||
return (a, b) =>
|
||||
{
|
||||
// 文件夹始终排在最前面
|
||||
var folderResult = folderFirstCompare(a, b);
|
||||
if (folderResult != 0)
|
||||
return folderResult;
|
||||
// 如果都是文件夹或都是文件,则按资源名称排序
|
||||
return string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase);
|
||||
};
|
||||
}
|
||||
case SortMethod.TagNums:
|
||||
{
|
||||
return (a, b) =>
|
||||
{
|
||||
// 文件夹始终排在最前面
|
||||
var folderResult = folderFirstCompare(a, b);
|
||||
if (folderResult != 0)
|
||||
return folderResult;
|
||||
// 如果都是文件夹,则按名称排序
|
||||
if (a.IsFolder && b.IsFolder)
|
||||
return string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase);
|
||||
// 如果都是文件,则按标签数量排序(标签多的在前)
|
||||
if (!a.IsFolder && !b.IsFolder)
|
||||
{
|
||||
// 安全检查,确保Comp不为空
|
||||
var aTagCount = a.Comp?.Tags?.Count ?? 0;
|
||||
var bTagCount = b.Comp?.Tags?.Count ?? 0;
|
||||
return bTagCount.CompareTo(aTagCount);
|
||||
}
|
||||
|
||||
// 理论上不会到达这里,但为了安全起见
|
||||
return string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase);
|
||||
};
|
||||
}
|
||||
case SortMethod.CreateTime:
|
||||
{
|
||||
return (a, b) =>
|
||||
{
|
||||
// 文件夹始终排在最前面
|
||||
var folderResult = folderFirstCompare(a, b);
|
||||
if (folderResult != 0)
|
||||
return folderResult;
|
||||
// 如果都是文件夹或都是文件,则按创建时间排序(新的在前)
|
||||
var aPath = a.IsFolder ? a.ActualPath : a.path;
|
||||
var bPath = b.IsFolder ? b.ActualPath : b.path;
|
||||
var aDate = GetModFileInfo(aPath).CreationTime;
|
||||
var bDate = GetModFileInfo(bPath).CreationTime;
|
||||
if (aDate == DateTime.MinValue && bDate == DateTime.MinValue)
|
||||
return string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (aDate == DateTime.MinValue) return 1; // 出错的文件排在后面
|
||||
|
||||
if (bDate == DateTime.MinValue) return -1;
|
||||
return bDate.CompareTo(aDate);
|
||||
};
|
||||
}
|
||||
case SortMethod.ModFileSize:
|
||||
{
|
||||
return (a, b) =>
|
||||
{
|
||||
// 文件夹始终排在最前面
|
||||
var folderResult = folderFirstCompare(a, b);
|
||||
if (folderResult != 0)
|
||||
return folderResult;
|
||||
// 如果都是文件夹,则按名称排序
|
||||
if (a.IsFolder && b.IsFolder)
|
||||
return string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase);
|
||||
// 如果都是文件,则按文件大小排序(大的在前)
|
||||
if (!a.IsFolder && !b.IsFolder)
|
||||
{
|
||||
var aSize = GetModFileInfo(a.ActualPath).Length;
|
||||
var bSize = GetModFileInfo(b.ActualPath).Length;
|
||||
if (aSize == 0L && bSize == 0L)
|
||||
return string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (aSize == 0L) return 1;
|
||||
|
||||
if (bSize == 0L) return -1;
|
||||
return bSize.CompareTo(aSize);
|
||||
}
|
||||
|
||||
// 理论上不会到达这里,但为了安全起见
|
||||
return string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase);
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return (a, b) =>
|
||||
{
|
||||
// 文件夹始终排在最前面
|
||||
var folderResult = folderFirstCompare(a, b);
|
||||
if (folderResult != 0)
|
||||
return folderResult;
|
||||
// 如果都是文件夹或都是文件,则按名称排序
|
||||
return string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 下边栏
|
||||
|
||||
// 启用 / 禁用
|
||||
private void BtnSelectED_Click(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
EDMods(ModLocalComp.compResourceListLoader.output.Where(m => selectedMods.Contains(m.RawPath)).ToList(),
|
||||
!sender.Equals(BtnSelectDisable));
|
||||
ChangeAllSelected(false);
|
||||
}
|
||||
|
||||
private void EDMods(IEnumerable<ModLocalComp.LocalCompFile> modList, bool isEnable)
|
||||
{
|
||||
var isSuccessful = true;
|
||||
foreach (var ModE in modList)
|
||||
{
|
||||
var modEntity = ModE; // 仅用于去除迭代变量无法修改的限制
|
||||
string newPath = null;
|
||||
if (modEntity.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine && !isEnable)
|
||||
// 禁用
|
||||
newPath = modEntity.path + (File.Exists(modEntity.path + ".old") ? ".old" : ".disabled");
|
||||
else if (modEntity.State == ModLocalComp.LocalCompFile.LocalFileStatus.Disabled && isEnable)
|
||||
// 启用
|
||||
newPath = modEntity.RawPath;
|
||||
else
|
||||
continue;
|
||||
// 重命名
|
||||
try
|
||||
{
|
||||
if (File.Exists(newPath))
|
||||
{
|
||||
if (File.Exists(modEntity.path))
|
||||
{
|
||||
// 同时存在两个名称的 Mod
|
||||
if ((ModBase.GetFileMD5(modEntity.path) ?? "") != (ModBase.GetFileMD5(newPath) ?? ""))
|
||||
{
|
||||
ModMain.MyMsgBox(
|
||||
Lang.Text("Instance.Resource.Ed.FileConflict.Message", newPath, modEntity.path),
|
||||
Lang.Text("Instance.Resource.Ed.FileConflict"));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 已经重命名过了
|
||||
ModBase.Log("[Mod] Mod 的状态已被切换", ModBase.LogLevel.Debug);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
File.Delete(newPath);
|
||||
FileSystem.Rename(modEntity.path, newPath);
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"未找到需要重命名的 Mod({modEntity.path ?? "null"})",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Resource.Error.OperationFailed"));
|
||||
ReloadCompFileList(true);
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, $"重命名 Mod 失败({modEntity.path ?? "null"})");
|
||||
isSuccessful = false;
|
||||
}
|
||||
|
||||
// 更改 Loader 中的列表
|
||||
var newModEntity = new ModLocalComp.LocalCompFile(newPath);
|
||||
newModEntity.FromJson(modEntity.ToJson());
|
||||
if (ModLocalComp.compResourceListLoader.output.Contains(modEntity))
|
||||
{
|
||||
var indexOfLoader = ModLocalComp.compResourceListLoader.output.IndexOf(modEntity);
|
||||
ModLocalComp.compResourceListLoader.output.RemoveAt(indexOfLoader);
|
||||
ModLocalComp.compResourceListLoader.output.Insert(indexOfLoader, newModEntity);
|
||||
}
|
||||
|
||||
if (searchResult is not null && searchResult.Contains(modEntity)) // #4862
|
||||
{
|
||||
var indexOfResult = searchResult.IndexOf(modEntity);
|
||||
searchResult.Remove(modEntity);
|
||||
searchResult.Insert(indexOfResult, newModEntity);
|
||||
}
|
||||
|
||||
// 更改 UI 中的列表
|
||||
try
|
||||
{
|
||||
var newItem = BuildLocalCompItem(newModEntity);
|
||||
modItems[modEntity.RawPath] = newItem;
|
||||
var indexOfUi = PanList.Children.IndexOf(PanList.Children.OfType<MyLocalCompItem>()
|
||||
.FirstOrDefault(i => ReferenceEquals(i.Entry, modEntity)));
|
||||
if (indexOfUi == -1)
|
||||
continue; // 因为未知原因 Mod 的状态已经切换完了
|
||||
PanList.Children.RemoveAt(indexOfUi);
|
||||
PanList.Children.Insert(indexOfUi, newItem);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"更新 UI 列表项失败:{modEntity.FileName}",
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Instance.Resource.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
Dispatcher.Invoke(() => PanList.UpdateLayout(), DispatcherPriority.Background);
|
||||
if (isSuccessful)
|
||||
{
|
||||
RefreshBars();
|
||||
}
|
||||
else
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Ed.ToggleFailed"), HintType.Error);
|
||||
ReloadCompFileList(true);
|
||||
}
|
||||
|
||||
LoaderRun(ModLoader.LoaderFolderRunType.UpdateOnly);
|
||||
}
|
||||
|
||||
// 更新
|
||||
private void BtnSelectUpdate_Click(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
var updateList = ModLocalComp.compResourceListLoader.output
|
||||
.Where(m => selectedMods.Contains(m.RawPath) && m.CanUpdate).ToList();
|
||||
if (!updateList.Any())
|
||||
return;
|
||||
UpdateResource(updateList);
|
||||
ChangeAllSelected(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录正在进行 Mod 更新的 mods 文件夹路径。
|
||||
/// </summary>
|
||||
public static List<string> updatingVersions = new();
|
||||
|
||||
public void UpdateResource(IEnumerable<ModLocalComp.LocalCompFile> modList)
|
||||
{
|
||||
// 更新前警告
|
||||
if (currentCompType == ModComp.CompType.Mod && (!States.Hint.UpdateMod || modList.Count() >= 15))
|
||||
{
|
||||
if (ModMain.MyMsgBox(
|
||||
Lang.Text("Instance.Resource.Update.Warning.Message"),
|
||||
Lang.Text("Instance.Resource.Update.Warning.Title"), Lang.Text("Instance.Resource.Update.Warning.Confirm"), Lang.Text("Common.Action.Cancel"), isWarn: true) == 1)
|
||||
States.Hint.UpdateMod = true;
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 构造下载信息
|
||||
modList = modList.ToList(); // 防止刷新影响迭代器
|
||||
var fileList = new List<DownloadFile>();
|
||||
var fileCopyList = new Dictionary<string, string>();
|
||||
foreach (var Entry in modList)
|
||||
{
|
||||
var file = Entry.UpdateFile;
|
||||
if (!file.Available)
|
||||
continue;
|
||||
// 确认更新后的文件名
|
||||
var currentReplaceName = Entry.compFile.FileName.Replace(".jar", "").Replace(".old", "")
|
||||
.Replace(".disabled", "");
|
||||
var newestReplaceName = Entry.UpdateFile.FileName.Replace(".jar", "").Replace(".old", "")
|
||||
.Replace(".disabled", "");
|
||||
var currentSegs = currentReplaceName.Split('-').ToList();
|
||||
var newestSegs = newestReplaceName.Split('-').ToList();
|
||||
var shortened = false;
|
||||
while (true) // 移除前导相同部分(不能移除所有相同项,这会导致例如 1.2-forge-2 和 1.3-forge-3 中间的 forge 被去掉,导致尝试替换 1.2-2)
|
||||
{
|
||||
if (!currentSegs.Any() || !newestSegs.Any())
|
||||
break;
|
||||
if ((currentSegs.First() ?? "") != (newestSegs.First() ?? ""))
|
||||
break;
|
||||
currentSegs.RemoveAt(0);
|
||||
newestSegs.RemoveAt(0);
|
||||
shortened = true;
|
||||
}
|
||||
|
||||
while (true) // 移除后导相同部分
|
||||
{
|
||||
if (!currentSegs.Any() || !newestSegs.Any())
|
||||
break;
|
||||
if ((currentSegs.Last() ?? "") != (newestSegs.Last() ?? ""))
|
||||
break;
|
||||
currentSegs.RemoveAt(currentSegs.Count - 1);
|
||||
newestSegs.RemoveAt(newestSegs.Count - 1);
|
||||
shortened = true;
|
||||
}
|
||||
|
||||
if (shortened && currentSegs.Any() && newestSegs.Any())
|
||||
{
|
||||
currentReplaceName = currentSegs.Join("-");
|
||||
newestReplaceName = newestSegs.Join("-");
|
||||
}
|
||||
|
||||
// 添加到下载列表
|
||||
var tempAddress = ModBase.pathTemp + @"DownloadedComp\" +
|
||||
Entry.FileName.Replace(currentReplaceName, newestReplaceName);
|
||||
var realAddress = ModBase.GetPathFromFullPath(Entry.path) +
|
||||
Entry.FileName.Replace(currentReplaceName, newestReplaceName);
|
||||
fileList.Add(file.ToNetFile(tempAddress, ModComp.DownloadReason.Update));
|
||||
fileCopyList[tempAddress] = realAddress;
|
||||
}
|
||||
|
||||
// 构造加载器
|
||||
var installLoaders = new List<ModLoader.LoaderBase>();
|
||||
var finishedFileNames = new List<string>();
|
||||
installLoaders.Add(new LoaderDownload(Lang.Text("Instance.Resource.Update.Task.DownloadFiles"), fileList)
|
||||
{ ProgressWeight = modList.Count() * 1.5d }); // 每个 Mod 需要 1.5s
|
||||
installLoaders.Add(new ModLoader.LoaderTask<int, int>(
|
||||
Lang.Text("Instance.Resource.Update.Task.ReplaceFiles"), _ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var Entry in modList)
|
||||
if (File.Exists(Entry.path))
|
||||
Microsoft.VisualBasic.FileIO.FileSystem.DeleteFile(Entry.path, UIOption.AllDialogs,
|
||||
RecycleOption.SendToRecycleBin);
|
||||
else
|
||||
ModBase.Log($"[CompUpdate] 未找到更新前的资源文件,跳过对它的删除:{Entry.path}", ModBase.LogLevel.Debug);
|
||||
|
||||
foreach (var Entry in fileCopyList)
|
||||
{
|
||||
if (File.Exists(Entry.Value))
|
||||
{
|
||||
Microsoft.VisualBasic.FileIO.FileSystem.DeleteFile(Entry.Value, UIOption.AllDialogs,
|
||||
RecycleOption.SendToRecycleBin);
|
||||
ModBase.Log($"[Mod] 更新后的资源文件已存在,将会把它放入回收站:{Entry.Value}", ModBase.LogLevel.Debug);
|
||||
}
|
||||
|
||||
if (Directory.Exists(ModBase.GetPathFromFullPath(Entry.Value)))
|
||||
{
|
||||
File.Move(Entry.Key, Entry.Value);
|
||||
finishedFileNames.Add(ModBase.GetFileNameFromPath(Entry.Value));
|
||||
}
|
||||
else
|
||||
{
|
||||
ModBase.Log($"[Mod] 更新后的目标文件夹已被删除:{Entry.Value}", ModBase.LogLevel.Debug);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
ModBase.Log(ex, "替换旧版资源文件时被主动取消");
|
||||
}
|
||||
}));
|
||||
// 结束处理
|
||||
var loader =
|
||||
new ModLoader.LoaderCombo<IEnumerable<ModLocalComp.LocalCompFile>>(
|
||||
Lang.Text("Instance.Resource.Update.Task.Title", PageInstanceLeft.McInstance.Name), installLoaders);
|
||||
var pathMods = PageInstanceLeft.McInstance.PathIndie +
|
||||
(PageInstanceLeft.McInstance.Info.HasLabyMod
|
||||
? Path.Combine("labymod-neo", "fabric", PageInstanceLeft.McInstance.Info.VanillaName)
|
||||
: "") + ModLocalComp.GetPathNameByCompType(currentCompType) + @"\";
|
||||
loader.OnStateChanged = _ =>
|
||||
{
|
||||
// 结果提示
|
||||
switch (loader.State)
|
||||
{
|
||||
case ModBase.LoadState.Finished:
|
||||
{
|
||||
switch (finishedFileNames.Count)
|
||||
{
|
||||
case 0: // 一般是由于 Mod 文件被占用,然后玩家主动取消
|
||||
{
|
||||
ModBase.Log("[CompUpdate] 没有资源被成功更新");
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Update.SuccessSingle", finishedFileNames.Single()), HintType.Success);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Update.SuccessMultiple", finishedFileNames.Count), HintType.Success);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ModBase.LoadState.Failed:
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Update.Failed", loader.Error.Message), HintType.Error);
|
||||
break;
|
||||
}
|
||||
case ModBase.LoadState.Aborted:
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Update.Aborted"));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ModBase.Log($"[CompUpdate] 已从正在进行资源更新的文件夹列表移除:{pathMods}");
|
||||
updatingVersions.Remove(pathMods);
|
||||
// 清理缓存
|
||||
ModBase.RunInNewThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var TempFile in fileCopyList.Keys)
|
||||
if (File.Exists(TempFile))
|
||||
File.Delete(TempFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "清理资源更新缓存失败");
|
||||
}
|
||||
}, "Clean Comp Update Cache", ThreadPriority.BelowNormal);
|
||||
};
|
||||
// 启动加载器
|
||||
ModBase.Log($"[CompUpdate] 开始更新 {modList.Count()} 个资源:{pathMods}");
|
||||
updatingVersions.Add(pathMods);
|
||||
loader.Start();
|
||||
ModLoader.LoaderTaskbarAdd(loader);
|
||||
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
|
||||
ModMain.frmMain.BtnExtraDownload.Ribble();
|
||||
ReloadCompFileList(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "初始化资源更新失败");
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
private void BtnSelectDelete_Click(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
DeleteMods(ModLocalComp.compResourceListLoader.output.Where(m => selectedMods.Contains(m.RawPath)));
|
||||
ChangeAllSelected(false);
|
||||
}
|
||||
|
||||
private void DeleteMods(IEnumerable<ModLocalComp.LocalCompFile> modList)
|
||||
{
|
||||
try
|
||||
{
|
||||
var isSuccessful = true;
|
||||
var isShiftPressed = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
|
||||
// 确认需要删除的文件
|
||||
// 文件夹只需要删除自身
|
||||
modList = modList.SelectMany(target =>
|
||||
{
|
||||
if (target.IsFolder) return new[] { target.path };
|
||||
|
||||
if (target.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine)
|
||||
return new[]
|
||||
{ target.path, target.path + (File.Exists(target.path + ".old") ? ".old" : ".disabled") };
|
||||
|
||||
return new[] { target.path, target.RawPath };
|
||||
}).Distinct()
|
||||
.Where(m => m.EndsWithF(@"\__FOLDER__", true)
|
||||
? Directory.Exists(m.Replace(@"\__FOLDER__", ""))
|
||||
: File.Exists(m)).Select(m => new ModLocalComp.LocalCompFile(m)).ToList();
|
||||
// 实际删除文件
|
||||
foreach (var ModEntity in modList)
|
||||
{
|
||||
// 删除
|
||||
try
|
||||
{
|
||||
if (ModEntity.IsFolder)
|
||||
{
|
||||
// 删除文件夹
|
||||
if (isShiftPressed)
|
||||
Directory.Delete(ModEntity.ActualPath, true);
|
||||
else
|
||||
Microsoft.VisualBasic.FileIO.FileSystem.DeleteDirectory(ModEntity.ActualPath,
|
||||
UIOption.AllDialogs, RecycleOption.SendToRecycleBin);
|
||||
}
|
||||
// 删除文件
|
||||
else if (isShiftPressed)
|
||||
{
|
||||
File.Delete(ModEntity.path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Microsoft.VisualBasic.FileIO.FileSystem.DeleteFile(ModEntity.path, UIOption.OnlyErrorDialogs,
|
||||
RecycleOption.SendToRecycleBin);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
ModBase.Log(ex, "删除资源被主动取消");
|
||||
ReloadCompFileList(true);
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"删除资源失败({ModEntity.path})",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Resource.Error.OperationFailed"));
|
||||
isSuccessful = false;
|
||||
}
|
||||
|
||||
// 取消选中
|
||||
selectedMods.Remove(ModEntity.RawPath);
|
||||
// 更改 Loader 和 UI 中的列表
|
||||
ModLocalComp.compResourceListLoader.output.Remove(ModEntity);
|
||||
searchResult?.Remove(ModEntity);
|
||||
modItems.Remove(ModEntity.RawPath);
|
||||
var indexOfUi = PanList.Children.IndexOf(PanList.Children.OfType<MyLocalCompItem>()
|
||||
.FirstOrDefault(i => i.Entry.Equals(ModEntity)));
|
||||
if (indexOfUi >= 0)
|
||||
PanList.Children.RemoveAt(indexOfUi);
|
||||
}
|
||||
|
||||
RefreshBars();
|
||||
if (!isSuccessful)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Delete.Failed"), HintType.Error);
|
||||
ReloadCompFileList(true);
|
||||
}
|
||||
else if (PanList.Children.Count == 0)
|
||||
{
|
||||
ReloadCompFileList(true); // 删除了全部项目
|
||||
}
|
||||
else
|
||||
{
|
||||
RefreshBars();
|
||||
}
|
||||
|
||||
// 显示结果提示
|
||||
if (!isSuccessful)
|
||||
return;
|
||||
if (isShiftPressed)
|
||||
{
|
||||
if (modList.Count() == 1)
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Delete.PermanentSingle", modList.Single().FileName), HintType.Success);
|
||||
else
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Delete.PermanentMultiple", modList.Count()), HintType.Success);
|
||||
}
|
||||
else if (modList.Count() == 1)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Delete.RecycleSingle", modList.Single().FileName), HintType.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Delete.RecycleMultiple", modList.Count()), HintType.Success);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
ModBase.Log(ex, "删除资源被主动取消");
|
||||
ReloadCompFileList(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"删除资源出现未知错误",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Resource.Error.OperationFailed"));
|
||||
ReloadCompFileList(true);
|
||||
}
|
||||
|
||||
LoaderRun(ModLoader.LoaderFolderRunType.UpdateOnly);
|
||||
}
|
||||
|
||||
// 取消选择
|
||||
private void BtnSelectCancel_Click(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
ChangeAllSelected(false);
|
||||
}
|
||||
|
||||
// 收藏
|
||||
private void BtnSelectFavorites_Click(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
var selected = ModLocalComp.compResourceListLoader.output
|
||||
.Where(m => selectedMods.Contains(m.RawPath) && m.Comp is not null).Select(i => i.Comp).ToList();
|
||||
ModComp.CompFavorites.ShowMenu(selected, (UIElement)sender);
|
||||
}
|
||||
|
||||
// 分享
|
||||
private void BtnSelectShare_Click(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
var shareList = ModLocalComp.compResourceListLoader.output
|
||||
.Where(m => selectedMods.Contains(m.RawPath) && m.Comp is not null).Select(i => i.Comp.Id).ToHashSet();
|
||||
ModBase.ClipboardSet(ModComp.CompFavorites.GetShareCode(shareList));
|
||||
ChangeAllSelected(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 单个资源项
|
||||
|
||||
// 详情
|
||||
public void Info_Click(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var modEntry = ((MyLocalCompItem)(sender is MyIconButton iconButton ? iconButton.Tag : sender)).Entry;
|
||||
// 判断该 LabyMod 是否支持安装 Fabric Mod
|
||||
var moddedLabyMod = PageInstanceLeft.McInstance.Info.HasLabyMod && PageInstanceLeft.McInstance.Modable;
|
||||
// 加载失败信息
|
||||
if (modEntry.State == ModLocalComp.LocalCompFile.LocalFileStatus.Unavailable)
|
||||
{
|
||||
ModMain.MyMsgBox(
|
||||
Lang.Text("Instance.Resource.Item.Info.FailedMessage.WithDetail",
|
||||
modEntry.FileUnavailableReason.ToString()),
|
||||
Lang.Text("Instance.Resource.Item.Info.FailedTitle"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (modEntry.Comp is not null)
|
||||
{
|
||||
// 跳转到 Mod 下载页面
|
||||
ModMain.frmMain.PageChange(new FormMain.PageStackData
|
||||
{
|
||||
page = FormMain.PageType.CompDetail,
|
||||
additional = (modEntry.Comp, new List<string>(), PageInstanceLeft.McInstance.Info.VanillaName,
|
||||
PageInstanceLeft.McInstance.Info.HasForge ? ModComp.CompLoaderType.Forge :
|
||||
PageInstanceLeft.McInstance.Info.HasNeoForge ? ModComp.CompLoaderType.NeoForge :
|
||||
PageInstanceLeft.McInstance.Info.HasFabric || moddedLabyMod ? ModComp.CompLoaderType.Fabric :
|
||||
ModComp.CompLoaderType.Any,
|
||||
currentCompType, null)
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// 对于原理图文件,使用异步加载避免UI卡顿
|
||||
if (modEntry.path.EndsWithF(".litematic", true) || modEntry.path.EndsWithF(".schem", true) ||
|
||||
modEntry.path.EndsWithF(".schematic", true) || modEntry.path.EndsWithF(".nbt", true))
|
||||
{
|
||||
ShowSchematicInfoAsync(modEntry);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取信息
|
||||
var contentLines = new List<string>();
|
||||
|
||||
// 检查是否为文件夹
|
||||
if (modEntry.IsFolder)
|
||||
{
|
||||
// 处理文件夹详情
|
||||
var folderPath = modEntry.ActualPath;
|
||||
if (Directory.Exists(folderPath))
|
||||
{
|
||||
var fileCount = 0;
|
||||
try
|
||||
{
|
||||
// 根据当前资源类型计算文件数量
|
||||
switch (currentCompType)
|
||||
{
|
||||
case ModComp.CompType.Schematic:
|
||||
{
|
||||
fileCount = new DirectoryInfo(folderPath)
|
||||
.EnumerateFiles("*", SearchOption.AllDirectories).Where(f =>
|
||||
ModLocalComp.LocalCompFile.IsCompFile(f.FullName,
|
||||
ModComp.CompType.Schematic)).Count();
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.Mod:
|
||||
{
|
||||
fileCount = new DirectoryInfo(folderPath)
|
||||
.EnumerateFiles("*.jar", SearchOption.AllDirectories).Count();
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.ResourcePack:
|
||||
{
|
||||
fileCount = new DirectoryInfo(folderPath)
|
||||
.EnumerateFiles("*.zip", SearchOption.AllDirectories).Count();
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.Shader:
|
||||
{
|
||||
fileCount = new DirectoryInfo(folderPath)
|
||||
.EnumerateFiles("*.zip", SearchOption.AllDirectories).Count();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
fileCount = new DirectoryInfo(folderPath)
|
||||
.EnumerateFiles("*", SearchOption.AllDirectories).Count();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
fileCount = 0;
|
||||
}
|
||||
|
||||
if (fileCount == 0)
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Info.EmptyFolder") + "\r\n");
|
||||
else if (fileCount == 1)
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Info.ContainsOne") + "\r\n");
|
||||
else
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Info.ContainsMany", fileCount) + "\r\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Info.FolderNotFound") + "\r\n");
|
||||
}
|
||||
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Info.Path", folderPath));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 处理普通文件详情
|
||||
if (modEntry.Description is not null)
|
||||
contentLines.Add(modEntry.Description + "\r\n");
|
||||
if (modEntry.Authors is not null)
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Info.Author", modEntry.Authors));
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Info.File", modEntry.FileName, ModBase.GetString(GetModFileInfo(modEntry.path).Length)));
|
||||
if (modEntry.Version is not null)
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Info.Version", modEntry.Version));
|
||||
|
||||
// 原理图文件的详情信息已通过异步方法处理
|
||||
}
|
||||
|
||||
// 只有普通文件才显示调试信息
|
||||
if (!modEntry.IsFolder)
|
||||
{
|
||||
var debugInfo = new List<string>();
|
||||
if (modEntry.ModId is not null) debugInfo.Add(Lang.Text("Instance.Resource.Item.Info.ModId", modEntry.ModId));
|
||||
if (modEntry.Dependencies.Any())
|
||||
{
|
||||
debugInfo.Add(Lang.Text("Instance.Resource.Item.Info.Dependency"));
|
||||
foreach (var Dep in modEntry.Dependencies)
|
||||
debugInfo.Add(" - " + (Dep.Value is null
|
||||
? Dep.Key
|
||||
: Lang.Text("Instance.Resource.Item.Info.DependencyVersion", Dep.Key, Dep.Value)));
|
||||
}
|
||||
|
||||
if (debugInfo.Any())
|
||||
{
|
||||
contentLines.Add("");
|
||||
contentLines.AddRange(debugInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// 显示详情信息
|
||||
if (modEntry.IsFolder)
|
||||
{
|
||||
// 文件夹只显示基本信息,不提供搜索功能
|
||||
ModMain.MyMsgBox(contentLines.Join("\r\n"), modEntry.Name, Lang.Text("Instance.Resource.Item.Info.Return"));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 获取用于搜索的 Mod 名称
|
||||
var modOriginalName = modEntry.Name.Replace(" ", "+");
|
||||
var modSearchName = modOriginalName.Substring(0, 1);
|
||||
for (int i = 1, loopTo = modOriginalName.Count() - 1; i <= loopTo; i++)
|
||||
{
|
||||
var isLastLower = modOriginalName[i - 1].ToString().ToLower()
|
||||
.Equals(modOriginalName[i - 1].ToString());
|
||||
var isCurrentLower = modOriginalName[i].ToString().ToLower()
|
||||
.Equals(modOriginalName[i].ToString());
|
||||
if (isLastLower && !isCurrentLower)
|
||||
// 上一个字母为小写,这一个字母为大写
|
||||
modSearchName += "+";
|
||||
modSearchName += modOriginalName[i].ToString();
|
||||
}
|
||||
|
||||
modSearchName = modSearchName.Replace("++", "+").Replace("pti+Fine", "ptiFine");
|
||||
// 显示
|
||||
if (currentCompType == ModComp.CompType.Schematic || !Lang.IsChineseMainland)
|
||||
{
|
||||
// 投影原理图文件或非中文区域不显示百科搜索选项
|
||||
if (modEntry.Url is null)
|
||||
ModMain.MyMsgBox(contentLines.Join("\r\n"), modEntry.Name, Lang.Text("Instance.Resource.Item.Info.Return"));
|
||||
else if (ModMain.MyMsgBox(contentLines.Join("\r\n"), modEntry.Name, Lang.Text("Instance.Resource.Item.Info.OpenWebsite"), Lang.Text("Instance.Resource.Item.Info.Return")) ==
|
||||
1) ModBase.OpenWebsite(modEntry.Url);
|
||||
}
|
||||
// 其他资源类型保留百科搜索功能
|
||||
else if (modEntry.Url is null)
|
||||
{
|
||||
if (ModMain.MyMsgBox(contentLines.Join("\r\n"), modEntry.Name, Lang.Text("Instance.Resource.Item.Info.McMod"), Lang.Text("Instance.Resource.Item.Info.Return")) == 1)
|
||||
ModBase.OpenWebsite("https://www.mcmod.cn/s?key=" + modSearchName + "&site=all&filter=0");
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (ModMain.MyMsgBox(contentLines.Join("\r\n"), modEntry.Name, Lang.Text("Instance.Resource.Item.Info.OpenWebsite"), Lang.Text("Instance.Resource.Item.Info.McMod"),
|
||||
Lang.Text("Instance.Resource.Item.Info.Return")))
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
ModBase.OpenWebsite(modEntry.Url);
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
ModBase.OpenWebsite(
|
||||
"https://www.mcmod.cn/s?key=" + modSearchName + "&site=all&filter=0");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"获取资源详情失败",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Resource.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 打开文件所在的位置
|
||||
public void Open_Click(MyIconButton sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var listItem = (MyLocalCompItem)sender.Tag;
|
||||
// 对于文件夹使用实际路径,对于文件使用原路径
|
||||
var targetPath = listItem.Entry.IsFolder ? listItem.Entry.ActualPath : listItem.Entry.path;
|
||||
ModBase.OpenExplorer(targetPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"打开资源文件位置失败",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Resource.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
public void Delete_Click(MyIconButton sender, EventArgs e)
|
||||
{
|
||||
var listItem = (MyLocalCompItem)sender.Tag;
|
||||
DeleteMods(new[] { listItem.Entry });
|
||||
}
|
||||
|
||||
// 启用 / 禁用
|
||||
public void ED_Click(MyIconButton sender, EventArgs e)
|
||||
{
|
||||
var listItem = (MyLocalCompItem)sender.Tag;
|
||||
EDMods(new[] { listItem.Entry }, listItem.Entry.State == ModLocalComp.LocalCompFile.LocalFileStatus.Disabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步显示原理图详情信息,避免UI卡顿
|
||||
/// </summary>
|
||||
private void ShowSchematicInfoAsync(ModLocalComp.LocalCompFile modEntry)
|
||||
{
|
||||
// 显示加载提示
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Item.Info.LoadingDetail"));
|
||||
|
||||
// 在后台线程中加载NBT数据
|
||||
// 确保 NBT 数据已加载
|
||||
|
||||
// 在 UI 线程中显示详情
|
||||
// 构建详情信息
|
||||
|
||||
|
||||
// 根据文件类型显示详细信息
|
||||
|
||||
// 显示调试信息
|
||||
|
||||
// 显示详情对话框
|
||||
|
||||
|
||||
// 记录错误日志但不显示错误提示,因为通用的文件状态检查已经处理了
|
||||
ModBase.RunInNewThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
modEntry.LoadNbtDataIfNeeded();
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var contentLines = new List<string>();
|
||||
if (modEntry.Description is not null) contentLines.Add(modEntry.Description + "\r\n");
|
||||
if (modEntry.Authors is not null) contentLines.Add(Lang.Text("Instance.Resource.Item.Info.Author", modEntry.Authors));
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Info.File", modEntry.FileName, ModBase.GetString(GetModFileInfo(modEntry.path).Length)));
|
||||
if (modEntry.Version is not null) contentLines.Add(Lang.Text("Instance.Resource.Item.Info.Version", modEntry.Version));
|
||||
if (modEntry.path.EndsWithF(".litematic", true))
|
||||
ShowLitematicDetails(contentLines, modEntry);
|
||||
else if (modEntry.path.EndsWithF(".schem", true))
|
||||
ShowSchemDetails(contentLines, modEntry);
|
||||
else if (modEntry.path.EndsWithF(".schematic", true))
|
||||
ShowSchematicDetails(contentLines, modEntry);
|
||||
else if (modEntry.path.EndsWithF(".nbt", true)) ShowNbtDetails(contentLines, modEntry);
|
||||
ShowDebugInfo(contentLines, modEntry);
|
||||
ShowSchematicDialog(contentLines, modEntry);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"显示原理图详情失败",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Resource.Error.OperationFailed"));
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"加载原理图 NBT 数据失败",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Resource.Error.OperationFailed"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#region 原理图文件详细信息显示
|
||||
|
||||
/// <summary>
|
||||
/// 显示 Litematic 文件的详细信息
|
||||
/// </summary>
|
||||
private void ShowLitematicDetails(List<string> contentLines, ModLocalComp.LocalCompFile modEntry)
|
||||
{
|
||||
contentLines.Add("");
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.DetailInfo"));
|
||||
|
||||
// 显示原始名称(从 NBT Metadata/Name 读取)
|
||||
if (modEntry.LitematicOriginalName is not null) contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.OriginalName") + modEntry.LitematicOriginalName);
|
||||
|
||||
// 显示版本信息
|
||||
if (modEntry.LitematicVersion.HasValue) contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.Version") + modEntry.LitematicVersion.Value);
|
||||
|
||||
// 显示尺寸信息
|
||||
if (modEntry.LitematicEnclosingSize is not null) contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.EnclosingSize") + modEntry.LitematicEnclosingSize);
|
||||
|
||||
// 显示方块和体积统计
|
||||
if (modEntry.LitematicTotalBlocks.HasValue)
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.TotalBlocks") + Lang.Number(modEntry.LitematicTotalBlocks.Value, "N0"));
|
||||
|
||||
if (modEntry.LitematicTotalVolume.HasValue)
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.TotalVolume") + Lang.Number(modEntry.LitematicTotalVolume.Value, "N0"));
|
||||
|
||||
// 显示区域数量
|
||||
if (modEntry.LitematicRegionCount.HasValue) contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.RegionCount") + modEntry.LitematicRegionCount.Value);
|
||||
|
||||
// 显示时间信息
|
||||
if (modEntry.LitematicTimeCreated.HasValue)
|
||||
try
|
||||
{
|
||||
var createdTime = DateTimeOffset.FromUnixTimeMilliseconds(modEntry.LitematicTimeCreated.Value)
|
||||
.ToLocalTime().DateTime;
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.CreatedTime") + Lang.Date(createdTime, "G"));
|
||||
}
|
||||
catch
|
||||
{
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.CreatedTime") + modEntry.LitematicTimeCreated.Value);
|
||||
}
|
||||
|
||||
if (modEntry.LitematicTimeModified.HasValue)
|
||||
try
|
||||
{
|
||||
var modifiedTime = DateTimeOffset.FromUnixTimeMilliseconds(modEntry.LitematicTimeModified.Value)
|
||||
.ToLocalTime().DateTime;
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.ModifiedTime") + Lang.Date(modifiedTime, "G"));
|
||||
}
|
||||
catch
|
||||
{
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.ModifiedTime") + modEntry.LitematicTimeModified.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示 Schem 文件的详细信息
|
||||
/// </summary>
|
||||
private void ShowSchemDetails(List<string> contentLines, ModLocalComp.LocalCompFile modEntry)
|
||||
{
|
||||
contentLines.Add("");
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.DetailInfo"));
|
||||
|
||||
// 显示原始名称(从 NBT Metadata/Name 读取)
|
||||
if (modEntry.SchemOriginalName is not null) contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.OriginalName") + modEntry.SchemOriginalName);
|
||||
|
||||
// 显示版本信息
|
||||
if (modEntry.StructureGameVersion is not null) contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.GameVersion") + modEntry.StructureGameVersion);
|
||||
|
||||
if (modEntry.SpongeVersion.HasValue) contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.SpongeVersion") + modEntry.SpongeVersion.Value);
|
||||
|
||||
if (modEntry.StructureDataVersion.HasValue) contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.DataVersion") + modEntry.StructureDataVersion.Value);
|
||||
|
||||
// 显示尺寸信息
|
||||
if (modEntry.LitematicEnclosingSize is not null) contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.EnclosingDimensions") + modEntry.LitematicEnclosingSize);
|
||||
|
||||
// 显示方块和体积统计
|
||||
if (modEntry.LitematicTotalBlocks.HasValue)
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.TotalBlocks") + Lang.Number(modEntry.LitematicTotalBlocks.Value, "N0"));
|
||||
|
||||
if (modEntry.LitematicTotalVolume.HasValue)
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.TotalVolume") + Lang.Number(modEntry.LitematicTotalVolume.Value, "N0"));
|
||||
|
||||
// 显示区域数量
|
||||
if (modEntry.LitematicRegionCount.HasValue) contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.RegionCount") + modEntry.LitematicRegionCount.Value);
|
||||
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.FileType",
|
||||
Lang.Text("Instance.Resource.Item.Schematic.FileType.Sponge")));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示 Schematic 文件的详细信息
|
||||
/// </summary>
|
||||
private void ShowSchematicDetails(List<string> contentLines, ModLocalComp.LocalCompFile modEntry)
|
||||
{
|
||||
contentLines.Add("");
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.DetailInfo"));
|
||||
|
||||
// 显示尺寸信息
|
||||
if (modEntry.LitematicEnclosingSize is not null) contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.Size") + modEntry.LitematicEnclosingSize);
|
||||
|
||||
// 显示方块和体积统计
|
||||
if (modEntry.LitematicTotalBlocks.HasValue)
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.TotalBlocks") + Lang.Number(modEntry.LitematicTotalBlocks.Value, "N0"));
|
||||
|
||||
if (modEntry.LitematicTotalVolume.HasValue)
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.TotalVolume") + Lang.Number(modEntry.LitematicTotalVolume.Value, "N0"));
|
||||
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.FileType",
|
||||
Lang.Text("Instance.Resource.Item.Schematic.FileType.Mcedit")));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示 NBT 结构文件的详细信息
|
||||
/// </summary>
|
||||
private void ShowNbtDetails(List<string> contentLines, ModLocalComp.LocalCompFile modEntry)
|
||||
{
|
||||
contentLines.Add("");
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.DetailInfo"));
|
||||
|
||||
// 显示作者信息
|
||||
if (modEntry.StructureAuthor is not null) contentLines.Add(Lang.Text("Instance.Resource.Item.Info.Author", modEntry.StructureAuthor));
|
||||
|
||||
// 显示版本信息
|
||||
if (modEntry.StructureGameVersion is not null) contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.GameVersion") + modEntry.StructureGameVersion);
|
||||
|
||||
if (modEntry.StructureDataVersion.HasValue) contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.DataVersion") + modEntry.StructureDataVersion.Value);
|
||||
|
||||
// 显示尺寸信息
|
||||
if (modEntry.LitematicEnclosingSize is not null) contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.EnclosingDimensions") + modEntry.LitematicEnclosingSize);
|
||||
|
||||
// 显示方块和体积统计
|
||||
if (modEntry.LitematicTotalBlocks.HasValue)
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.TotalBlocks") + Lang.Number(modEntry.LitematicTotalBlocks.Value, "N0"));
|
||||
|
||||
if (modEntry.LitematicTotalVolume.HasValue)
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.TotalVolume") + Lang.Number(modEntry.LitematicTotalVolume.Value, "N0"));
|
||||
|
||||
// 显示区域数量
|
||||
if (modEntry.LitematicRegionCount.HasValue) contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.RegionCount") + modEntry.LitematicRegionCount.Value);
|
||||
|
||||
contentLines.Add(Lang.Text("Instance.Resource.Item.Schematic.FileType",
|
||||
Lang.Text("Instance.Resource.Item.Schematic.FileType.Nbt")));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void ShowDebugInfo(List<string> contentLines, ModLocalComp.LocalCompFile modEntry)
|
||||
{
|
||||
var debugInfo = new List<string>();
|
||||
if (modEntry.ModId is not null) debugInfo.Add(Lang.Text("Instance.Resource.Item.Info.ModId", modEntry.ModId));
|
||||
if (modEntry.Dependencies.Any())
|
||||
{
|
||||
debugInfo.Add(Lang.Text("Instance.Resource.Item.Info.Dependency"));
|
||||
foreach (var Dep in modEntry.Dependencies)
|
||||
debugInfo.Add(" - " + Dep.Key + (Dep.Value is null
|
||||
? Dep.Key
|
||||
: Lang.Text("Instance.Resource.Item.Info.DependencyVersion", Dep.Key, Dep.Value)));
|
||||
}
|
||||
|
||||
if (debugInfo.Any())
|
||||
{
|
||||
contentLines.Add("");
|
||||
contentLines.AddRange(debugInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowSchematicDialog(List<string> contentLines, ModLocalComp.LocalCompFile modEntry)
|
||||
{
|
||||
// 投影原理图文件不显示百科搜索选项
|
||||
if (modEntry.Url is null)
|
||||
ModMain.MyMsgBox(contentLines.Join("\r\n"), modEntry.Name, Lang.Text("Instance.Resource.Item.Info.Return"));
|
||||
else if (ModMain.MyMsgBox(contentLines.Join("\r\n"), modEntry.Name, Lang.Text("Instance.Resource.Item.Info.OpenWebsite"), Lang.Text("Instance.Resource.Item.Info.Return")) == 1)
|
||||
ModBase.OpenWebsite(modEntry.Url);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 搜索
|
||||
|
||||
public bool IsSearching => !string.IsNullOrWhiteSpace(SearchBox.Text);
|
||||
private List<ModLocalComp.LocalCompFile> searchResult;
|
||||
private CancellationTokenSource _cancelToken;
|
||||
|
||||
public void SearchRun(object sender, EventArgs e)
|
||||
{
|
||||
var curToken = new CancellationTokenSource();
|
||||
var oldToken = Interlocked.Exchange(ref _cancelToken, curToken);
|
||||
oldToken?.Cancel();
|
||||
oldToken?.Dispose();
|
||||
|
||||
// this exception is ignored
|
||||
Dispatcher.BeginInvoke(new Func<Task>(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var token = curToken.Token;
|
||||
await Task.Delay(350, token);
|
||||
if (token.IsCancellationRequested) return;
|
||||
if (IsSearching)
|
||||
{
|
||||
var searchText = SearchBox.Text;
|
||||
searchResult = await Task.Run(() => GetSearchResult(searchText), token);
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested) return;
|
||||
RefreshUI();
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "搜索过程中发生异常");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private List<ModLocalComp.LocalCompFile> GetSearchResult(string query)
|
||||
{
|
||||
// 构造请求
|
||||
var queryList = new List<ModBase.SearchEntry<ModLocalComp.LocalCompFile>>();
|
||||
foreach (var Entry in ModLocalComp.compResourceListLoader.output.AsReadOnly())
|
||||
{
|
||||
var searchSource = new List<ModBase.SearchSource>();
|
||||
searchSource.Add(new ModBase.SearchSource(Entry.Name, 1d));
|
||||
searchSource.Add(new ModBase.SearchSource(Entry.FileName, 1d));
|
||||
if (Entry.Version is not null) searchSource.Add(new ModBase.SearchSource(Entry.Version, 0.2d));
|
||||
if (Entry.Description is not null && !string.IsNullOrEmpty(Entry.Description))
|
||||
searchSource.Add(new ModBase.SearchSource(Entry.Description, 0.4d));
|
||||
if (Entry.Comp is not null)
|
||||
{
|
||||
if ((Entry.Comp.RawName ?? "") != (Entry.Name ?? ""))
|
||||
searchSource.Add(new ModBase.SearchSource(Entry.Comp.RawName, 1d));
|
||||
if ((Entry.Comp.TranslatedName ?? "") != (Entry.Comp.RawName ?? ""))
|
||||
searchSource.Add(new ModBase.SearchSource(Entry.Comp.TranslatedName, 1d));
|
||||
if ((Entry.Comp.Description ?? "") != (Entry.Description ?? ""))
|
||||
searchSource.Add(new ModBase.SearchSource(Entry.Comp.Description, 0.4d));
|
||||
searchSource.Add(new ModBase.SearchSource(string.Join("", Entry.Comp.Tags), 0.2d));
|
||||
}
|
||||
|
||||
queryList.Add(new ModBase.SearchEntry<ModLocalComp.LocalCompFile>
|
||||
{ item = Entry, searchSource = searchSource });
|
||||
}
|
||||
|
||||
// 进行搜索
|
||||
return ModBase.Search(queryList, query, ModBase.MaxLocalSearchDepth, 0.35d).Select(r => r.item).ToList();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
<local:MyPageRight
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" x:Class="PCL.PageInstanceExport"
|
||||
PanScroll="{Binding ElementName=PanBack}">
|
||||
<Grid x:Name="PanAllBack" AllowDrop="True">
|
||||
<Grid.Resources>
|
||||
<Style TargetType="local:MyCheckBox">
|
||||
<Setter Property="Height" Value="26" />
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled"
|
||||
x:Name="PanBack">
|
||||
<StackPanel Margin="25,10,25,65">
|
||||
<local:MyHint Margin="0,15,0,0" Text="{DynamicResource Instance.Export.ModrinthWarning}" Theme="Yellow"
|
||||
x:Name="HintOptiFine" />
|
||||
<local:MyCard Margin="0,15">
|
||||
<Grid Margin="22,15,25,15">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="0.3*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="28" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
Text="{DynamicResource Instance.Export.PackName}" Margin="0,0,20,0" />
|
||||
<local:MyTextBox Grid.Row="0" Grid.Column="1" x:Name="TextExportName" MaxLength="100" />
|
||||
<TextBlock Grid.Row="0" Grid.Column="2" VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
Text="{DynamicResource Instance.Export.PackVersion}" Margin="30,0,20,0" />
|
||||
<local:MyTextBox Grid.Row="0" Grid.Column="3" x:Name="TextExportVersion"
|
||||
Tag="VersionArgumentTitle" MaxLength="100" />
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
<local:MyCard Margin="0,0,0,15" x:Name="CardOptions" MinHeight="39">
|
||||
<local:MyIconTextButton x:Name="BtnOverrideCancel" Height="24" HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center" Margin="0,0,5,0"
|
||||
IsHitTestVisible="False" Opacity="0.5"
|
||||
Text="{DynamicResource Common.Action.Reset}"
|
||||
SvgIcon="lucide/rotate-ccw"
|
||||
LogoScale="0.9" />
|
||||
<StackPanel Margin="25,38,15,15" Name="PanOptions">
|
||||
<local:MyCheckBox IsEnabled="False" x:Name="CheckOptionsBasic">
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.Game}"
|
||||
DefaultChecked="True" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
<local:MyCheckBox x:Name="CheckOptionsOptions" Margin="30,0,0,0">
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.GameSettings}"
|
||||
Description="{DynamicResource Instance.Export.Option.GameSettings.Desc}"
|
||||
Rules="options.txt|configureddefaults/"
|
||||
DefaultChecked="True" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
<local:MyCheckBox Margin="30,0,0,0">
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.GameProfile}"
|
||||
Description="{DynamicResource Instance.Export.Option.GameProfile.Desc}"
|
||||
Rules="hotbar.nbt|command_history.txt"
|
||||
DefaultChecked="False" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
<local:MyCheckBox Margin="30,0,0,0">
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.OptiFine}"
|
||||
Rules="optionsof.txt|optionsshaders.txt"
|
||||
DefaultChecked="True"
|
||||
RequireOptiFine="True" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
<local:MyCheckBox x:Name="CheckOptionsMod">
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.Mod}"
|
||||
Description="{DynamicResource Instance.Export.Option.Mod.Desc}"
|
||||
Rules="mods/|!mods/*.disabled|!mods/*.old|!mods/.connector/|coremods/|lib/|!mods/mcef-libraries/|!mods/mcef-cache/"
|
||||
DefaultChecked="True"
|
||||
RequireModLoader="True" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
<StackPanel Margin="30,0,0,0"
|
||||
Visibility="{Binding Checked, ElementName=CheckOptionsMod, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<local:MyCheckBox>
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.DisabledMod}"
|
||||
Rules="mods/*.disabled|mods/*.old" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
<local:MyCheckBox>
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.ImportantData}"
|
||||
Description="{DynamicResource Instance.Export.Option.ImportantData.Desc}"
|
||||
Rules="addons/|multiblocked/|modpack-update-checker/|global_packs/|global_resource_packs/|global_data_packs/|optional_data_packs/|maps/|icon.png|mods-resourcepacks/|matmos/|resource_assorts/|resource_assorts.json|patchouli_books/|datapacks/|kubejs*/|!kubejs*/probe/|!kubejs*/exported/|!kubejs*/jsconfig.json|!kubejs*/README.txt|openloader/|worldshape/|resources/|scripts/|structures/|fontfiles/|oresources/|packmenu/|craftpresence/|pointblanks/|template*/|!template*/playerdata/|!template*/stats/"
|
||||
DefaultChecked="True" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
<local:MyCheckBox>
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.ModSettings}"
|
||||
Rules="config/|!config/accountsx/|!config/jei/world/|!config/worldedit/|config/worldedit/worldedit.properties|!config/spark/|config/spark/config.json|defaultconfigs/|journeymap/config/|journeymap/server/|TrashSlotSaveState.json|customfov.txt|gg.essential.mod/|essential/|!essential/*/|!essential/*.jar*|!essential/screenshot-checksum-caches.json|!essential/microsoft_accounts.json|paragliderSettings.nbt|local/client_config.json|local/ftbl.json|local/client/sidebar_buttons.json|local/client/ftbutilities.cfg|local/client/ftblib.cfg|local/client/xencraft.cfg|liteloader.properties|default_reference.xml|CustomSkinLoader/CustomSkinLoader.json"
|
||||
DefaultChecked="True" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
<local:MyCheckBox>
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.Maps}"
|
||||
Description="{DynamicResource Instance.Export.Option.Maps.Desc}"
|
||||
Rules="journeymap/data/|xaero/|XaeroWaypoints/|XaeroWorldMap/"
|
||||
DefaultChecked="False" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
<local:MyCheckBox>
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.Jei}"
|
||||
Description="{DynamicResource Instance.Export.Option.Jei.Desc}"
|
||||
Rules="config/jei/world/"
|
||||
DefaultChecked="False" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
<local:MyCheckBox>
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.Emi}"
|
||||
Description="{DynamicResource Instance.Export.Option.Emi.Desc}"
|
||||
Rules="emi.json"
|
||||
DefaultChecked="False" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
<local:MyCheckBox>
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.Patchouli}"
|
||||
Description="{DynamicResource Instance.Export.Option.Patchouli.Desc}"
|
||||
Rules="patchouli_data.json"
|
||||
DefaultChecked="False" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
</StackPanel>
|
||||
<local:MyCheckBox x:Name="CheckOptionsResourcePacks">
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.ResourcePacks}"
|
||||
Description="{DynamicResource Instance.Export.Option.ResourcePacks.Desc}"
|
||||
ShowRules="resourcepacks/|texturepacks/"
|
||||
DefaultChecked="True" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
<StackPanel Margin="30,0,0,0" x:Name="PanOptionsResourcePacks"
|
||||
Visibility="{Binding Checked, ElementName=CheckOptionsResourcePacks, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
<local:MyCheckBox x:Name="CheckOptionsShaderPacks">
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.ShaderPacks}"
|
||||
ShowRules="shaderpacks/"
|
||||
DefaultChecked="True"
|
||||
RequireModLoaderOrOptiFine="True" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
<StackPanel Margin="30,0,0,0" x:Name="PanOptionsShaderPacks"
|
||||
Visibility="{Binding Checked, ElementName=CheckOptionsShaderPacks, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
<local:MyCheckBox>
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.Screenshots}"
|
||||
Rules="screenshots/"
|
||||
DefaultChecked="False" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
<local:MyCheckBox>
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.Structures}"
|
||||
Description="{DynamicResource Instance.Export.Option.Structures.Desc}"
|
||||
Rules="schematics/"
|
||||
DefaultChecked="False" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
<local:MyCheckBox>
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.Replay}"
|
||||
Description="{DynamicResource Instance.Export.Option.Replay.Desc}"
|
||||
Rules="replay_recordings/|replay_videos/"
|
||||
RequireModLoader="True"
|
||||
DefaultChecked="False" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
<local:MyCheckBox x:Name="CheckOptionsSaves">
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.Saves}"
|
||||
Description="{DynamicResource Instance.Export.Option.Saves.Desc}"
|
||||
ShowRules="saves/"
|
||||
DefaultChecked="False" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
<StackPanel Margin="30,0,0,0" x:Name="PanOptionsSaves"
|
||||
Visibility="{Binding Checked, ElementName=CheckOptionsSaves, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
<local:MyCheckBox>
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.Servers}"
|
||||
Rules="servers.dat"
|
||||
DefaultChecked="False" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
<local:MyCheckBox x:Name="CheckOptionsOtherFolders" Change="CheckOptionsOtherFolders_Change">
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.OtherFolders}"
|
||||
Description="{DynamicResource Instance.Export.Option.OtherFolders.Desc}"
|
||||
DefaultChecked="False" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
<StackPanel Margin="30,0,0,0" x:Name="PanOptionsOtherFolders"
|
||||
Visibility="{Binding Checked, ElementName=CheckOptionsOtherFolders, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
<local:MyCheckBox x:Name="CheckOptionsPcl">
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.Launcher}"
|
||||
Description="{DynamicResource Instance.Export.Option.Launcher.Desc}"
|
||||
DefaultChecked="False" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
<StackPanel Margin="30,0,0,0"
|
||||
Visibility="{Binding Checked, ElementName=CheckOptionsPcl, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<local:MyCheckBox x:Name="CheckOptionsPclCustom">
|
||||
<local:MyCheckBox.Tag>
|
||||
<local:ExportOption
|
||||
Title="{DynamicResource Instance.Export.Option.LauncherCustom}"
|
||||
Description="{DynamicResource Instance.Export.Option.LauncherCustom.Desc}"
|
||||
DefaultChecked="True" />
|
||||
</local:MyCheckBox.Tag>
|
||||
</local:MyCheckBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
<local:MyCard Title="{DynamicResource Instance.Export.Advanced.Title}" Margin="0,0,0,15" CanSwap="True"
|
||||
IsSwapped="True">
|
||||
<StackPanel Margin="25,37,23,20">
|
||||
<local:MyHint
|
||||
Text="{DynamicResource Instance.Export.Advanced.BundleHint}"
|
||||
Theme="Yellow" Margin="0,2,0,8"
|
||||
Visibility="{Binding Checked, ElementName=CheckAdvancedInclude, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
<local:MyCheckBox Text="{DynamicResource Instance.Export.Advanced.BundleFiles}"
|
||||
x:Name="CheckAdvancedInclude"
|
||||
Change="CheckAdvancedInclude_Change"
|
||||
ToolTip="{DynamicResource Instance.Export.Advanced.BundleFiles.ToolTip}" />
|
||||
<local:MyCheckBox Text="{DynamicResource Instance.Export.Advanced.Modrinth}"
|
||||
x:Name="CheckAdvancedModrinth"
|
||||
Change="CheckAdvancedModrinth_Change"
|
||||
ToolTip="{DynamicResource Instance.Export.Advanced.Modrinth.ToolTip}" />
|
||||
<local:MyHint Theme="Blue" RelativeSetup="HintExportConfig" CanClose="True" Margin="0,20,0,2"
|
||||
Text="{DynamicResource Instance.Export.Advanced.ConfigHint}" />
|
||||
<Grid Height="35" Margin="0,12,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<local:MyButton Grid.Column="0" x:Name="BtnAdvancedImport" MinWidth="140"
|
||||
Text="{DynamicResource Instance.Export.Advanced.ImportConfig}"
|
||||
Padding="13,0" Margin="0,0,20,0" ColorType="Highlight" />
|
||||
<local:MyButton Grid.Column="1" x:Name="BtnAdvancedExport" MinWidth="140"
|
||||
Text="{DynamicResource Instance.Export.Advanced.ExportConfig}"
|
||||
Padding="13,0" Margin="0,0,20,0" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
</StackPanel>
|
||||
</local:MyScrollViewer>
|
||||
<local:MyExtraTextButton HorizontalAlignment="Center" VerticalAlignment="Bottom"
|
||||
x:Name="BtnExport" Text="{DynamicResource Instance.Export.StartExport}"
|
||||
LogoScale="1.1"
|
||||
SvgIcon="lucide/package" />
|
||||
</Grid>
|
||||
</local:MyPageRight>
|
||||
+1226
@@ -0,0 +1,1226 @@
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using DotNet.Globbing;
|
||||
using PCL.Core.App;
|
||||
using PCL.Core.UI;
|
||||
using PCL.Core.App.Localization;
|
||||
using PCL.Core.Utils;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
public class ExportOption : DependencyObject
|
||||
{
|
||||
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(
|
||||
nameof(Title), typeof(string), typeof(ExportOption)
|
||||
);
|
||||
|
||||
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(
|
||||
nameof(Description), typeof(string), typeof(ExportOption)
|
||||
);
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => (string)GetValue(TitleProperty);
|
||||
set => SetValue(TitleProperty, value);
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
get => (string)GetValue(DescriptionProperty);
|
||||
set => SetValue(DescriptionProperty, value);
|
||||
}
|
||||
|
||||
public string Rules { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 如果 Rules 为空,则根据 ShowRules 的内容判断是否应该显示这个复选框。
|
||||
/// 如果 ShowRules 也为空,则始终显示。
|
||||
/// </summary>
|
||||
public string ShowRules { get; set; }
|
||||
|
||||
public bool DefaultChecked { get; set; }
|
||||
public bool RequireModLoader { get; set; }
|
||||
public bool RequireOptiFine { get; set; }
|
||||
public bool RequireModLoaderOrOptiFine { get; set; }
|
||||
}
|
||||
|
||||
public partial class PageInstanceExport : IRefreshable
|
||||
{
|
||||
private string currentVersion = "";
|
||||
|
||||
public PageInstanceExport()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += (_, _) => PageInstanceExport_Loaded();
|
||||
CardOptions.MouseLeftButtonDown += CardOptions_MouseLeftButtonDown;
|
||||
BtnAdvancedExport.Click += ExportConfig;
|
||||
BtnAdvancedImport.Click += ImportConfig;
|
||||
BtnExport.Click += StartExport;
|
||||
TextExportName.GotFocus += TextExportName_GotFocus;
|
||||
CheckAdvancedModrinth.Change += CheckAdvancedModrinth_Change;
|
||||
CheckAdvancedInclude.Change += CheckAdvancedInclude_Change;
|
||||
}
|
||||
|
||||
void IRefreshable.Refresh()
|
||||
{
|
||||
RefreshAll();
|
||||
}
|
||||
|
||||
private void PageInstanceExport_Loaded()
|
||||
{
|
||||
ModAnimation.AniControlEnabled += 1;
|
||||
if ((currentVersion ?? "") != (PageInstanceLeft.McInstance.PathInstance ?? ""))
|
||||
RefreshAll(); // 切换到了另一个实例,重置页面
|
||||
ModAnimation.AniControlEnabled -= 1;
|
||||
}
|
||||
|
||||
public void RefreshAll()
|
||||
{
|
||||
ModBase.Log("[Export] 刷新导出页面");
|
||||
HintOptiFine.Visibility =
|
||||
PageInstanceLeft.McInstance.Info.HasOptiFine ? Visibility.Visible : Visibility.Collapsed;
|
||||
currentVersion = PageInstanceLeft.McInstance.PathInstance;
|
||||
TextExportName.Text = "";
|
||||
TextExportName.HintText = PageInstanceLeft.McInstance.Name;
|
||||
TextExportVersion.Text = "";
|
||||
TextExportVersion.HintText = "1.0.0";
|
||||
CheckAdvancedInclude.Checked = false;
|
||||
CheckAdvancedModrinth.Checked = false;
|
||||
GetExportOption(CheckOptionsBasic).Description = PageInstanceLeft.McInstance.GetDefaultDescription();
|
||||
ResetConfigOverrides();
|
||||
ReloadAllSubOptions();
|
||||
RefreshAllOptionsUI();
|
||||
PanBack.ScrollToHome();
|
||||
}
|
||||
|
||||
// 自动填写整合包名称
|
||||
private void TextExportName_GotFocus(object sender, RoutedEventArgs routedEventArgs)
|
||||
{
|
||||
if (string.IsNullOrEmpty(TextExportName.Text))
|
||||
{
|
||||
TextExportName.Text = TextExportName.HintText;
|
||||
TextExportName.SelectionStart = TextExportName.Text.Length;
|
||||
}
|
||||
}
|
||||
|
||||
// 勾选 Modrinth 上传模式时,禁止打包 PCL
|
||||
private void CheckAdvancedModrinth_Change(object sender, bool user)
|
||||
{
|
||||
if (CheckAdvancedModrinth.Checked == true)
|
||||
CheckOptionsPcl.Checked = false;
|
||||
CheckOptionsPcl.IsEnabled = (bool)!CheckAdvancedModrinth.Checked;
|
||||
}
|
||||
|
||||
// 勾选"其他文件夹"时,同步勾选/取消所有子选项
|
||||
private void CheckOptionsOtherFolders_Change(object sender, bool user)
|
||||
{
|
||||
if (!user) return;
|
||||
foreach (var child in PanOptionsOtherFolders.Children)
|
||||
if (child is MyCheckBox childBox)
|
||||
childBox.Checked = CheckOptionsOtherFolders.Checked;
|
||||
}
|
||||
|
||||
// 勾选打包资源文件时,禁止开启 Modrinth 上传模式
|
||||
private void CheckAdvancedInclude_Change(object sender, bool user)
|
||||
{
|
||||
if (CheckAdvancedInclude.Checked == true)
|
||||
CheckAdvancedModrinth.Checked = false;
|
||||
CheckAdvancedModrinth.IsEnabled = (bool)!CheckAdvancedInclude.Checked;
|
||||
}
|
||||
|
||||
#region 子选项
|
||||
|
||||
private readonly string[] subOptionBlackList = new[] { "Quark Programmer Art.zip", "+ EuphoriaPatches_" };
|
||||
|
||||
/// <summary>
|
||||
/// 动态生成子文件夹下的选项,例如资源包、存档等。
|
||||
/// </summary>
|
||||
private void ReloadAllSubOptions()
|
||||
{
|
||||
ReloadSubOptions(PanOptionsResourcePacks, true, true, "resourcepacks", "texturepacks");
|
||||
ReloadSubOptions(PanOptionsSaves, false, true, "saves");
|
||||
ReloadSubOptions(PanOptionsShaderPacks, true, true, "shaderpacks");
|
||||
ReloadOtherFolders();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 扫描实例根目录下未被已有选项覆盖的文件夹,生成独立的复选框。
|
||||
/// </summary>
|
||||
private void ReloadOtherFolders()
|
||||
{
|
||||
PanOptionsOtherFolders.Children.Clear();
|
||||
|
||||
var pathIndie = PageInstanceLeft.McInstance.PathIndie;
|
||||
var rootDir = new DirectoryInfo(pathIndie);
|
||||
if (!rootDir.Exists)
|
||||
{
|
||||
CheckOptionsOtherFolders.Visibility = Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
|
||||
var coveredFolders = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
// 模组
|
||||
"mods", "coremods", "lib",
|
||||
// 整合包重要数据
|
||||
"addons", "multiblocked", "modpack-update-checker", "global_packs",
|
||||
"global_resource_packs", "global_data_packs", "optional_data_packs", "maps",
|
||||
"mods-resourcepacks", "matmos", "resource_assorts",
|
||||
"patchouli_books", "datapacks",
|
||||
"openloader", "worldshape", "resources", "scripts", "structures",
|
||||
"fontfiles", "oresources", "packmenu", "craftpresence", "pointblanks",
|
||||
// 模组设置
|
||||
"config", "defaultconfigs", "journeymap", "local", "essential", "gg.essential.mod",
|
||||
"CustomSkinLoader",
|
||||
// 地图
|
||||
"xaero", "XaeroWaypoints", "XaeroWorldMap",
|
||||
// 资源包
|
||||
"resourcepacks", "texturepacks",
|
||||
// 光影
|
||||
"shaderpacks",
|
||||
// 截图 / 结构 / 录像
|
||||
"screenshots", "schematics",
|
||||
"replay_recordings", "replay_videos",
|
||||
// 存档 / 设置文件夹
|
||||
"saves", "configureddefaults",
|
||||
// 始终跳过(大量文件或无用缓存)
|
||||
"assets", "versions", "libraries", "structureCacheV1",
|
||||
".fabric", ".git", "avatar-cache", "cosmetic-cache",
|
||||
// PCL 单独处理
|
||||
"PCL",
|
||||
};
|
||||
|
||||
var coveredPrefixes = new[] { "kubejs", "template" };
|
||||
var coveredSuffixes = new[] { "-natives" };
|
||||
|
||||
foreach (var subDir in rootDir.EnumerateDirectories())
|
||||
{
|
||||
if (coveredFolders.Contains(subDir.Name))
|
||||
continue;
|
||||
if (coveredPrefixes.Any(p => subDir.Name.StartsWithF(p)))
|
||||
continue;
|
||||
if (coveredSuffixes.Any(s => subDir.Name.EndsWithF(s, true)))
|
||||
continue;
|
||||
|
||||
PanOptionsOtherFolders.Children.Add(new MyCheckBox
|
||||
{
|
||||
Tag = new ExportOption
|
||||
{
|
||||
Title = subDir.Name,
|
||||
DefaultChecked = false,
|
||||
Rules = ModBase.EscapeLikePattern($"{subDir.Name}/")
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
CheckOptionsOtherFolders.Visibility = PanOptionsOtherFolders.Children.Count > 0
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void ReloadSubOptions(StackPanel panel, bool acceptCompressedFile, bool acceptFolder,
|
||||
params string[] folders)
|
||||
{
|
||||
panel.Children.Clear();
|
||||
foreach (var Folder in folders)
|
||||
{
|
||||
var targetFolder = new DirectoryInfo(PageInstanceLeft.McInstance.PathIndie + Folder);
|
||||
if (!targetFolder.Exists)
|
||||
continue;
|
||||
// 查找文件夹下的对应项
|
||||
if (acceptCompressedFile)
|
||||
foreach (var File in targetFolder.EnumerateFiles("*.zip").Concat(targetFolder.EnumerateFiles("*.rar")))
|
||||
{
|
||||
if (subOptionBlackList.Any(b => File.Name.ContainsF(b)))
|
||||
continue;
|
||||
panel.Children.Add(new MyCheckBox
|
||||
{
|
||||
Tag = new ExportOption
|
||||
{
|
||||
Title = File.Name, DefaultChecked = true,
|
||||
Rules = ModBase.EscapeLikePattern($"{Folder}/{File.Name}")
|
||||
}
|
||||
});
|
||||
if (Folder == "shaderpacks") // 处理光影包的配置文件
|
||||
{
|
||||
var shaderConfig = new FileInfo(Path.Combine(File.Directory.FullName,
|
||||
$"{File.Name}.txt"));
|
||||
if (shaderConfig.Exists)
|
||||
panel.Children.Add(new MyCheckBox
|
||||
{
|
||||
Margin = new Thickness(30, 0, 0, 0),
|
||||
Tag = new ExportOption
|
||||
{
|
||||
Title = $"{shaderConfig.Name}", DefaultChecked = true,
|
||||
Description = Lang.Text("Instance.Export.Config.ShaderConfigSuffix"),
|
||||
Rules = ModBase.EscapeLikePattern($"{Folder}/{shaderConfig.Name}")
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (acceptFolder)
|
||||
foreach (var SubFolder in targetFolder.EnumerateDirectories().OrderByDescending(f => f.LastWriteTime))
|
||||
{
|
||||
if (subOptionBlackList.Any(b => SubFolder.Name.ContainsF(b)))
|
||||
continue;
|
||||
if (!SubFolder.EnumerateFileSystemInfos().Any())
|
||||
continue;
|
||||
var newCheckBox = new MyCheckBox
|
||||
{
|
||||
Tag = new ExportOption
|
||||
{
|
||||
Title = SubFolder.Name, DefaultChecked = true,
|
||||
Rules = ModBase.EscapeLikePattern($"{Folder}/{SubFolder.Name}/")
|
||||
}
|
||||
};
|
||||
if (ReferenceEquals(panel, PanOptionsSaves))
|
||||
GetExportOption(newCheckBox).Description =
|
||||
Lang.Date(SubFolder.LastWriteTime, "g");
|
||||
panel.Children.Add(newCheckBox);
|
||||
if (Folder == "shaderpacks") // 处理文件夹形式光影包的配置文件
|
||||
{
|
||||
var shaderConfig = new FileInfo(Path.Combine(targetFolder.FullName,
|
||||
$"{SubFolder.Name}.txt"));
|
||||
if (shaderConfig.Exists)
|
||||
panel.Children.Add(new MyCheckBox
|
||||
{
|
||||
Margin = new Thickness(30, 0, 0, 0),
|
||||
Tag = new ExportOption
|
||||
{
|
||||
Title = $"{shaderConfig.Name}", DefaultChecked = true,
|
||||
Description = Lang.Text("Instance.Export.Config.ShaderConfigSuffix"),
|
||||
Rules = ModBase.EscapeLikePattern($"{Folder}/{shaderConfig.Name}")
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 选项
|
||||
|
||||
/// <summary>
|
||||
/// 重新确认是否应该显示每个选项,并将 ExportOption 同步到 UI。
|
||||
/// </summary>
|
||||
private void RefreshAllOptionsUI()
|
||||
{
|
||||
// 预先归纳所有至多二级的文件/文件夹
|
||||
var allEntries = new List<string>();
|
||||
|
||||
bool IsValidDirectory(DirectoryInfo folder)
|
||||
{
|
||||
try
|
||||
{
|
||||
return folder.Exists && folder.EnumerateFileSystemInfos()
|
||||
.Any(i => !subOptionBlackList.Any(b => i.Name.ContainsF(b)));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
; // 检查文件夹不为空
|
||||
// 一般是由于无法访问,或是一个指向已不存在的文件夹的链接(例如使用 mklink 创造的 resource 文件夹链接)
|
||||
var pathInfo = new DirectoryInfo(PageInstanceLeft.McInstance.PathIndie);
|
||||
allEntries.AddRange(pathInfo.EnumerateFiles().Select(f => f.Name));
|
||||
foreach (var SubFolder in pathInfo.EnumerateDirectories().Where(IsValidDirectory))
|
||||
{
|
||||
allEntries.Add($@"{SubFolder.Name}\");
|
||||
allEntries.AddRange(SubFolder.EnumerateFiles().Select(f => $@"{SubFolder.Name}\{f.Name}"));
|
||||
allEntries.AddRange(SubFolder.EnumerateDirectories().Where(IsValidDirectory)
|
||||
.Select(d => $@"{SubFolder.Name}\{d.Name}\"));
|
||||
}
|
||||
|
||||
ModBase.Log($"[Export] 共发现 {allEntries.Count} 个可行的二级文件/文件夹");
|
||||
|
||||
// 确认选项是否应该被显示
|
||||
bool IsVisible(ExportOption targetOption)
|
||||
{
|
||||
// 检查需要 OptiFine 或 Mod 加载器
|
||||
if (targetOption.RequireOptiFine && !PageInstanceLeft.McInstance.Info.HasOptiFine)
|
||||
return false;
|
||||
if (targetOption.RequireModLoader && !PageInstanceLeft.McInstance.Modable)
|
||||
return false;
|
||||
if (targetOption.RequireModLoaderOrOptiFine && !PageInstanceLeft.McInstance.Info.HasOptiFine &&
|
||||
!PageInstanceLeft.McInstance.Modable)
|
||||
return false;
|
||||
// 粗略检查是否可能有符合规则的文件/文件夹
|
||||
return StandardizeLines((targetOption.Rules ?? targetOption.ShowRules).Split('|'), true).Any(rule =>
|
||||
{
|
||||
if (rule.StartsWithF("!"))
|
||||
return false; // 只看正向规则
|
||||
// 检查前两级
|
||||
try
|
||||
{
|
||||
if (allEntries.Any(entry => LikeString(entry, rule)))
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"错误的规则:{rule}",
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Instance.Export.Error.OperationFailed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 粗略检查所有级
|
||||
rule = rule.Trim("*?".ToCharArray());
|
||||
if (rule.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries).Count() >= 3)
|
||||
{
|
||||
if (rule.EndsWithF(@"\"))
|
||||
return IsValidDirectory(new DirectoryInfo(PageInstanceLeft.McInstance.PathIndie + rule)); // 文件夹有效
|
||||
|
||||
return File.Exists(PageInstanceLeft.McInstance.PathIndie + rule);
|
||||
// 文件有效
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
;
|
||||
// 逐个检查选项
|
||||
foreach (var CheckBox in GetAllOptions(true))
|
||||
{
|
||||
var targetOption = GetExportOption(CheckBox);
|
||||
// 名称与简介
|
||||
CheckBox.Inlines.Clear();
|
||||
CheckBox.Inlines.Add(new Run(targetOption.Title));
|
||||
if (!string.IsNullOrEmpty(targetOption.Description))
|
||||
CheckBox.Inlines.Add(new Run(" " + targetOption.Description) { Foreground = ThemeManager.colorGray5 });
|
||||
// 可见性、默认勾选
|
||||
if (string.IsNullOrEmpty(targetOption.Rules) && string.IsNullOrEmpty(targetOption.ShowRules))
|
||||
{
|
||||
CheckBox.Visibility = Visibility.Visible;
|
||||
CheckBox.Checked = targetOption.DefaultChecked;
|
||||
}
|
||||
else
|
||||
{
|
||||
var pass = IsVisible(targetOption);
|
||||
CheckBox.Visibility = pass ? Visibility.Visible : Visibility.Collapsed;
|
||||
CheckBox.Checked = targetOption.DefaultChecked && pass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对文本行进行标准化处理,以便使用 Like 进行匹配。
|
||||
/// </summary>
|
||||
private IEnumerable<string> StandardizeLines(IEnumerable<string> raw, bool addSuffixStarToFolderPath)
|
||||
{
|
||||
foreach (var IgnoreLineRaw in raw)
|
||||
{
|
||||
var ignoreLine = IgnoreLineRaw;
|
||||
ignoreLine = ignoreLine.Trim();
|
||||
if (string.IsNullOrEmpty(ignoreLine) || ignoreLine.StartsWithF("#") || ignoreLine.StartsWithF("="))
|
||||
continue;
|
||||
ignoreLine = ignoreLine.Replace("/", @"\");
|
||||
yield return ignoreLine + (ignoreLine.EndsWithF(@"\") && addSuffixStarToFolderPath ? "**" : "");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有可作为选项的 CheckBox。
|
||||
/// </summary>
|
||||
private IEnumerable<MyCheckBox> GetAllOptions(bool includeHidden)
|
||||
{
|
||||
foreach (var Element in PanOptions.Children)
|
||||
{
|
||||
if (!includeHidden &&
|
||||
((UIElement)Element).Visibility != Visibility.Visible)
|
||||
continue;
|
||||
if (Element is MyCheckBox)
|
||||
yield return (MyCheckBox)Element;
|
||||
else if (Element is StackPanel)
|
||||
foreach (var SubElement in ((StackPanel)Element).Children)
|
||||
{
|
||||
if (!includeHidden && ((UIElement)SubElement).Visibility != Visibility.Visible)
|
||||
continue;
|
||||
if (SubElement is MyCheckBox)
|
||||
yield return (MyCheckBox)SubElement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取该 CheckBox 对应的 ExportOption。
|
||||
/// </summary>
|
||||
private ExportOption GetExportOption(MyCheckBox checkBox)
|
||||
{
|
||||
return (ExportOption)checkBox.Tag;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 配置文件
|
||||
|
||||
private const string sperator = "==============================================================";
|
||||
|
||||
// ================ 导出内容段 ================
|
||||
|
||||
/// <summary>
|
||||
/// 从配置文件中读取的规则。
|
||||
/// 如果不为 Nothing,则会覆写当前勾选的规则并禁用对应 UI。
|
||||
/// </summary>
|
||||
private List<string> RulesOverrides
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
if (value is null)
|
||||
{
|
||||
BtnOverrideCancel.Visibility = Visibility.Collapsed;
|
||||
PanOptions.Visibility = Visibility.Visible;
|
||||
CardOptions.Inlines.Clear();
|
||||
CardOptions.Inlines.Add(new Run(Lang.Text("Instance.Export.OptionListTitle")) { FontWeight = FontWeights.Bold });
|
||||
}
|
||||
else
|
||||
{
|
||||
BtnOverrideCancel.Visibility = Visibility.Visible;
|
||||
PanOptions.Visibility = Visibility.Collapsed;
|
||||
CardOptions.Inlines.Clear();
|
||||
CardOptions.Inlines.Add(new Run(Lang.Text("Instance.Export.OptionListTitle") + ": ") { FontWeight = FontWeights.Bold });
|
||||
CardOptions.Inlines.Add(new Run(Lang.Text("Instance.Export.OptionList.FromConfig")) { FontWeight = FontWeights.Normal });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前实际生效的所有规则。
|
||||
/// </summary>
|
||||
private IEnumerable<string> GetAllRules()
|
||||
{
|
||||
if (RulesOverrides is not null)
|
||||
{
|
||||
// 返回覆盖的列表
|
||||
foreach (var Rule in RulesOverrides)
|
||||
yield return Rule;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 从当前勾选的所有选项中获取所有规则行
|
||||
yield return "";
|
||||
yield return "# " + Lang.Text("Instance.Export.Config.Comment.ModifyRules");
|
||||
yield return "# " + Lang.Text("Instance.Export.Config.Comment.ReverseMatch");
|
||||
yield return "";
|
||||
foreach (var CheckBox in GetAllOptions(false))
|
||||
{
|
||||
if (CheckBox.Checked == false)
|
||||
continue;
|
||||
var targetOption = GetExportOption(CheckBox);
|
||||
if (targetOption.Rules is null)
|
||||
continue;
|
||||
yield return $"# {targetOption.Title}";
|
||||
foreach (var Rule in targetOption.Rules.Split('|'))
|
||||
yield return Rule;
|
||||
yield return "";
|
||||
}
|
||||
|
||||
yield return "# " + Lang.Text("Instance.Export.Config.Comment.ExcludedFiles");
|
||||
yield return "!*.log";
|
||||
yield return "!*.dat_old";
|
||||
yield return "!*.BakaCoreInfo";
|
||||
yield return "!hmclversion.cfg";
|
||||
yield return "!log4j2.xml";
|
||||
yield return "";
|
||||
}
|
||||
}
|
||||
|
||||
// ================ 追加内容段 ================
|
||||
|
||||
private List<string> extraFiles;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前实际生效的追加内容。
|
||||
/// </summary>
|
||||
private IEnumerable<string> GetExtraFileLines()
|
||||
{
|
||||
if (extraFiles is not null)
|
||||
{
|
||||
// 返回覆盖的列表
|
||||
foreach (var File in extraFiles)
|
||||
yield return File;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 从当前勾选的所有选项中获取所有规则行
|
||||
yield return "";
|
||||
yield return "# " + Lang.Text("Instance.Export.Config.Comment.ExtraFiles");
|
||||
yield return "# " + Lang.Text("Instance.Export.Config.Comment.ExtraFiles2");
|
||||
yield return "";
|
||||
}
|
||||
}
|
||||
|
||||
// ================ 重置 ================
|
||||
|
||||
/// <summary>
|
||||
/// 重置配置文件所带来的影响。
|
||||
/// </summary>
|
||||
private void ResetConfigOverrides()
|
||||
{
|
||||
RulesOverrides = null;
|
||||
configPackPath = null;
|
||||
extraFiles = null;
|
||||
PanBack.ScrollToHome();
|
||||
}
|
||||
|
||||
private void CardOptions_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (RulesOverrides is null)
|
||||
return;
|
||||
ResetConfigOverrides();
|
||||
}
|
||||
|
||||
// ================ 保存 / 读取 ================
|
||||
|
||||
// 保存配置文件
|
||||
private void ExportConfig(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var configPath = SystemDialogs.SelectSaveFile(Lang.Text("Instance.Export.SelectFileLocation"), "export_config.txt", Lang.Text("Instance.Export.Config.FileFilter"),
|
||||
(string?)States.System.ExportConfigPath);
|
||||
if (string.IsNullOrEmpty(configPath))
|
||||
return;
|
||||
States.System.ExportConfigPath = configPath;
|
||||
var configLines = new List<string>();
|
||||
// ini 段
|
||||
configLines.Add("Name:" + TextExportName.Text);
|
||||
configLines.Add("Version:" + TextExportVersion.Text);
|
||||
configLines.Add("");
|
||||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.IncludeLauncher"));
|
||||
configLines.Add("IncludeLauncher:" + CheckOptionsPcl.Checked);
|
||||
configLines.Add("");
|
||||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.IncludeLauncherCustom"));
|
||||
configLines.Add("IncludeLauncherCustom:" + CheckOptionsPclCustom.Checked);
|
||||
configLines.Add("");
|
||||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.BundleFiles"));
|
||||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.BundleFiles2"));
|
||||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.BundleFiles3"));
|
||||
configLines.Add("DontCheckHostedAssets:" + CheckAdvancedInclude.Checked);
|
||||
configLines.Add("");
|
||||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.Modrinth"));
|
||||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.Modrinth2"));
|
||||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.Modrinth3"));
|
||||
configLines.Add("ModrinthUploadMode:" + CheckAdvancedModrinth.Checked);
|
||||
configLines.Add("");
|
||||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.PackPath"));
|
||||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.PackPath2"));
|
||||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.PackPath3"));
|
||||
configLines.Add("PackPath:" + (configPackPath ?? ""));
|
||||
configLines.Add("");
|
||||
// 导出内容段
|
||||
configLines.Add(sperator);
|
||||
configLines.AddRange(GetAllRules());
|
||||
// 追加内容段
|
||||
configLines.Add(sperator);
|
||||
configLines.AddRange(GetExtraFileLines());
|
||||
// 结束
|
||||
ModBase.WriteFile(configPath, configLines.Join("\r\n"));
|
||||
HintService.Hint(Lang.Text("Instance.Export.SaveSuccess", configPath), HintType.Success);
|
||||
ModBase.OpenExplorer(configPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"保存配置失败",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Export.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
#region 配置文件核心读取逻辑
|
||||
|
||||
/// <summary>
|
||||
/// 从指定路径读取配置文件(供按钮和拖放调用)
|
||||
/// </summary>
|
||||
/// <param name="configPath">配置文件路径</param>
|
||||
private void ReadConfigFile(string configPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 保存配置文件路径到缓存
|
||||
States.System.ExportConfigPath = configPath;
|
||||
|
||||
var fileContent = ModBase.ReadFile(configPath);
|
||||
var segments = fileContent.Split(sperator);
|
||||
|
||||
if (segments.Length == 0)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Export.Config.Invalid"), HintType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// === 解析INI段 ===
|
||||
var ini = new Dictionary<string, string>();
|
||||
foreach (var LineRaw in segments[0].Split("\r\n".ToCharArray()))
|
||||
{
|
||||
var line = LineRaw;
|
||||
line = line.Trim();
|
||||
if (string.IsNullOrEmpty(line) || line.StartsWithF("#") || line.StartsWithF("="))
|
||||
continue;
|
||||
var index = line.IndexOfF(":");
|
||||
if (index > 0) ini[line.Substring(0, index)] = line.Substring(index + 1);
|
||||
}
|
||||
|
||||
// 赋值到界面控件
|
||||
TextExportName.Text = ini.GetOrDefault("Name", "");
|
||||
TextExportVersion.Text = ini.GetOrDefault("Version", "");
|
||||
CheckOptionsPcl.Checked =
|
||||
Convert.ToBoolean(ini.GetOrDefault("IncludeLauncher", false.ToString()));
|
||||
CheckOptionsPclCustom.Checked =
|
||||
Convert.ToBoolean(ini.GetOrDefault("IncludeLauncherCustom", true.ToString()));
|
||||
CheckAdvancedModrinth.Checked =
|
||||
Convert.ToBoolean(ini.GetOrDefault("ModrinthUploadMode", false.ToString()));
|
||||
CheckAdvancedInclude.Checked =
|
||||
Convert.ToBoolean(ini.GetOrDefault("DontCheckHostedAssets", false.ToString()));
|
||||
configPackPath = ini.GetOrDefault("PackPath");
|
||||
|
||||
// === 解析导出内容段 ===
|
||||
RulesOverrides = segments[1].Replace("\r", "\n")
|
||||
.Replace("\n" + "\n", "\n").Split("\n").ToList();
|
||||
|
||||
// === 解析追加内容段 ===
|
||||
if (segments.Length > 2)
|
||||
extraFiles = segments[2].Replace("\r", "\n")
|
||||
.Replace("\n" + "\n", "\n").Split("\n").ToList();
|
||||
else
|
||||
extraFiles = null;
|
||||
|
||||
// 提示成功
|
||||
HintService.Hint(Lang.Text("Instance.Export.ReadSuccess", configPath), HintType.Success);
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"读取配置文件失败:{configPath}",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Export.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// 读取配置文件
|
||||
private void ImportConfig(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var configPath = SystemDialogs.SelectFile(Lang.Text("Instance.Export.Config.FileFilter"), Lang.Text("Instance.Export.SelectConfigFile"),
|
||||
(string?)States.System.ExportConfigPath);
|
||||
if (string.IsNullOrEmpty(configPath))
|
||||
return;
|
||||
|
||||
// 调用核心读取逻辑
|
||||
ReadConfigFile(configPath);
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"选择配置文件失败",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Export.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
#region 拖放事件处理
|
||||
|
||||
/// <summary>
|
||||
/// 文件拖入界面时触发:验证文件类型
|
||||
/// </summary>
|
||||
private void PanAllBack_DragEnter(object sender, DragEventArgs e)
|
||||
{
|
||||
// 检查是否包含文件拖放数据
|
||||
if (e.Data.GetDataPresent(DataFormats.FileDrop))
|
||||
{
|
||||
// 获取拖入的文件路径数组
|
||||
var files = (string[])e.Data.GetData(DataFormats.FileDrop);
|
||||
|
||||
// 验证:仅允许单个.txt文件
|
||||
if (files.Length == 1 &&
|
||||
files[0].EndsWithF(".txt", true))
|
||||
e.Effects = DragDropEffects.Copy; // 设置拖放效果为“复制”
|
||||
else
|
||||
e.Effects = DragDropEffects.None; // 不允许拖放
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Effects = DragDropEffects.None;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文件放下时触发:读取配置文件
|
||||
/// </summary>
|
||||
private void PanAllBack_Drop(object sender, DragEventArgs e)
|
||||
{
|
||||
// 获取拖入的文件路径
|
||||
if (e.Data.GetDataPresent(DataFormats.FileDrop))
|
||||
{
|
||||
var files = (string[])e.Data.GetData(DataFormats.FileDrop);
|
||||
var configPath = files[0];
|
||||
|
||||
// 调用核心读取逻辑
|
||||
ReadConfigFile(configPath);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region 导出
|
||||
|
||||
/// <summary>
|
||||
/// 配置文件中指定的导出位置。
|
||||
/// </summary>
|
||||
private string configPackPath;
|
||||
|
||||
/// <summary>
|
||||
/// 开始导出。
|
||||
/// </summary>
|
||||
private void StartExport(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var packName = string.IsNullOrEmpty(TextExportName.Text) ? TextExportName.HintText : TextExportName.Text;
|
||||
var packVersion = string.IsNullOrEmpty(TextExportVersion.Text) ? "1.0.0" : TextExportVersion.Text;
|
||||
|
||||
// 重复任务检查
|
||||
var loaderName = Lang.Text("Instance.Export.ExportTask.Prefix") + packName;
|
||||
foreach (var OngoingLoader in ModLoader.loaderTaskbar)
|
||||
{
|
||||
if ((OngoingLoader.name ?? "") != (loaderName ?? ""))
|
||||
continue;
|
||||
ModMain.frmMain.PageChange(FormMain.PageType.TaskManager);
|
||||
return;
|
||||
}
|
||||
|
||||
// 确认导出位置
|
||||
string packPath = null;
|
||||
if (!string.IsNullOrWhiteSpace(configPackPath) && !configPackPath.EndsWithF(@"\") &&
|
||||
!configPackPath.EndsWithF("/"))
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(ModBase.GetPathFromFullPath(configPackPath));
|
||||
packPath = configPackPath;
|
||||
ModBase.Log($"[Export] 使用配置文件中指定的导出路径:{configPackPath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, $"无法使用配置文件中指定的导出路径({configPackPath})");
|
||||
if (ModMain.MyMsgBox(
|
||||
Lang.Text("Instance.Export.PackPathInvalid.WithDetail", configPackPath, ex.ToString()),
|
||||
Lang.Text("Instance.Export.PackPathInvalid.Title"), Lang.Text("Common.Action.Confirm"),
|
||||
Lang.Text("Common.Action.Cancel")) == 2)
|
||||
return;
|
||||
}
|
||||
|
||||
if (packPath is null)
|
||||
{
|
||||
var extensions = new List<string>();
|
||||
if (CheckAdvancedModrinth.Checked == false)
|
||||
extensions.Add(Lang.Text("Instance.Export.ZipFilter"));
|
||||
if (CheckOptionsPcl.Checked == false)
|
||||
extensions.Add(Lang.Text("Instance.Export.MrpackFilter"));
|
||||
packPath = SystemDialogs.SelectSaveFile(Lang.Text("Instance.Export.SelectSaveLocation"),
|
||||
packName + (string.IsNullOrEmpty(TextExportVersion.Text) ? "" : " " + TextExportVersion.Text),
|
||||
extensions.Join("|"));
|
||||
ModBase.Log($"[Export] 手动指定的导出路径:{packPath}");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(packPath))
|
||||
return;
|
||||
|
||||
// 缓存所需参数
|
||||
var cacheFolder = ModMain.RequestTaskTempFolder();
|
||||
var overridesFolder = Path.Combine(cacheFolder, "modpack", "overrides");
|
||||
var mcInstance = PageInstanceLeft.McInstance;
|
||||
var pathIndie = mcInstance.PathIndie;
|
||||
var checkHostedAssets = (bool)!CheckAdvancedInclude.Checked;
|
||||
var modrinthUploadMode = (bool)CheckAdvancedModrinth.Checked;
|
||||
var includePCL = (bool)CheckOptionsPcl.Checked;
|
||||
var includePCLCustom = (bool)(includePCL ? CheckOptionsPclCustom.Checked : (bool?)false);
|
||||
var allRules = StandardizeLines(GetAllRules(), true).ToList();
|
||||
var allExtraFiles = StandardizeLines(GetExtraFileLines(), false).ToList();
|
||||
ModBase.Log($"[Export] 准备导出整合包,共有 {allRules.Count} 条规则,{allExtraFiles.Count} 条追加内容行");
|
||||
|
||||
// 构造步骤加载器
|
||||
var loaders = new List<ModLoader.LoaderBase>();
|
||||
|
||||
#region 准备 PCL 文件
|
||||
|
||||
#if !RELEASE
|
||||
if (includePCL)
|
||||
loaders.Add(new ModLoader.LoaderTask<int, int>(Lang.Text("Instance.Export.Task.DownloadPclRelease"),
|
||||
loader =>
|
||||
{
|
||||
UpdateManager.DownloadLatestPCL(loader);
|
||||
ModBase.CopyFile(Path.Combine(ModBase.pathTemp, "CE-Latest.exe"),
|
||||
Path.Combine(cacheFolder, "Plain Craft Launcher.exe"));
|
||||
})
|
||||
{
|
||||
ProgressWeight = 0.5d,
|
||||
block = false
|
||||
});
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region 复制文件
|
||||
|
||||
loaders.Add(new ModLoader.LoaderTask<int, List<ModLocalComp.LocalCompFile>>(
|
||||
Lang.Text("Instance.Export.Task.CopyContent"), loader =>
|
||||
{
|
||||
loader.output = [];
|
||||
// 复制实例文件
|
||||
var progress = 0;
|
||||
Action<DirectoryInfo> searchFolder = null;
|
||||
searchFolder = folder =>
|
||||
{
|
||||
// 文件夹:进一步搜索
|
||||
foreach (var SubFolder in folder.EnumerateDirectories("*", SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
// 跳过部分又没用文件又多的文件夹,加快搜索
|
||||
if ((folder.FullName ?? "") == (pathIndie ?? "") &&
|
||||
new[] { "assets", "versions", "libraries" }.Contains(SubFolder.Name))
|
||||
continue;
|
||||
if (new[] { "structureCacheV1", ".fabric", ".git", "avatar-cache", "cosmetic-cache" }.Contains(
|
||||
SubFolder.Name))
|
||||
continue;
|
||||
searchFolder(SubFolder);
|
||||
}
|
||||
|
||||
// 文件:检查规则并复制
|
||||
foreach (var Entry in folder.EnumerateFiles("*", SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
var relativePath = Entry.FullName.AfterFirst(pathIndie);
|
||||
// 检查规则
|
||||
var shouldKeep = false;
|
||||
foreach (var Rule in allRules)
|
||||
{
|
||||
var revert = Rule.StartsWith("!");
|
||||
if (LikeString(relativePath, Rule.TrimStart('!')))
|
||||
shouldKeep = !revert;
|
||||
}
|
||||
|
||||
if (!shouldKeep)
|
||||
continue;
|
||||
var targetPath = Path.Combine(overridesFolder, relativePath);
|
||||
ModBase.CopyFile(Entry.FullName, targetPath);
|
||||
// 若为压缩包,考虑联网获取路径
|
||||
if (checkHostedAssets &&
|
||||
new[] { ".zip", ".rar", ".jar", ".disabled", ".old" }.Contains(Entry.Extension.ToLower()) &&
|
||||
new[] { "mods", "packs", "openloader", "resource" }.Any(s => relativePath.Contains(s)))
|
||||
{
|
||||
var modFile = new ModLocalComp.LocalCompFile(targetPath);
|
||||
var unused = modFile.ModrinthHash; // 提前计算 Hash
|
||||
unused = modFile.CurseForgeHash.ToString();
|
||||
loader.output.Add(modFile);
|
||||
}
|
||||
|
||||
// 更新进度(进度并不准确,主要突出一个我还没似)
|
||||
progress += 1;
|
||||
if (progress == 25)
|
||||
{
|
||||
loader.Progress += (0.94d - loader.Progress) * 0.012d;
|
||||
progress = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
searchFolder(new DirectoryInfo(pathIndie));
|
||||
ModBase.Log($"[Export] 复制 overrides 文件完成,有 {loader.output.Count} 个文件需要联网检查");
|
||||
loader.Progress = 0.95d;
|
||||
// 复制追加内容到根目录
|
||||
var baseFolder = includePCL ? cacheFolder : Path.Combine(cacheFolder, "modpack");
|
||||
foreach (var Line in allExtraFiles)
|
||||
if (Line.EndsWithF(@"\") || Line.EndsWithF("/"))
|
||||
{
|
||||
if (Directory.Exists(Line))
|
||||
ModBase.CopyDirectory(Line, Path.Combine(baseFolder, ModBase.GetFolderNameFromPath(Line)) + @"\");
|
||||
else
|
||||
HintService.Hint(Lang.Text("Instance.Export.Config.FolderNotFound", Line), HintType.Error);
|
||||
}
|
||||
else if (File.Exists(Line))
|
||||
{
|
||||
ModBase.CopyFile(Line, Path.Combine(baseFolder, ModBase.GetFileNameFromPath(Line)));
|
||||
}
|
||||
else
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Export.Config.FileNotFound", Line), HintType.Error);
|
||||
}
|
||||
|
||||
loader.Progress = 0.97d;
|
||||
// 复制 PCL 实例设置
|
||||
ModBase.CopyDirectory(Path.Combine(mcInstance.PathInstance, "PCL"), Path.Combine(overridesFolder, "PCL"));
|
||||
#if RELEASE
|
||||
// 复制 PCL 本体
|
||||
if (includePCL) ModBase.CopyFile(Basics.ExecutablePath, Path.Combine(cacheFolder, Basics.ExecutableName));
|
||||
#endif
|
||||
// 复制 PCL 个性化内容
|
||||
if (includePCLCustom)
|
||||
{
|
||||
if (Directory.Exists(Path.Combine(ModBase.exePath, "PCL", "Pictures")))
|
||||
ModBase.CopyDirectory(Path.Combine(ModBase.exePath, "PCL", "Pictures"), Path.Combine(cacheFolder, "PCL", "Pictures"));
|
||||
if (Directory.Exists(Path.Combine(ModBase.exePath, "PCL", "Musics")))
|
||||
ModBase.CopyDirectory(Path.Combine(ModBase.exePath, "PCL", "Musics"), Path.Combine(cacheFolder, "PCL", "Musics"));
|
||||
if (File.Exists(Path.Combine(ModBase.exePath, "PCL", "Custom.xaml")))
|
||||
ModBase.CopyFile(Path.Combine(ModBase.exePath, "PCL", "Custom.xaml"), Path.Combine(cacheFolder, "PCL", "Custom.xaml"));
|
||||
if (File.Exists(Path.Combine(ModBase.exePath, "PCL", "Setup.ini")))
|
||||
ModBase.CopyFile(Path.Combine(ModBase.exePath, "PCL", "Setup.ini"), Path.Combine(cacheFolder, "PCL", "Setup.ini"));
|
||||
if (File.Exists(Path.Combine(ModBase.exePath, "PCL", "hints.txt")))
|
||||
ModBase.CopyFile(Path.Combine(ModBase.exePath, "PCL", "hints.txt"), Path.Combine(cacheFolder, "PCL", "hints.txt"));
|
||||
if (File.Exists(Path.Combine(ModBase.exePath, "PCL", "Logo.png")))
|
||||
ModBase.CopyFile(Path.Combine(ModBase.exePath, "PCL", "Logo.png"), Path.Combine(cacheFolder, "PCL", "Logo.png"));
|
||||
}
|
||||
})
|
||||
{
|
||||
ProgressWeight = 5d
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
#region 联网检查
|
||||
|
||||
loaders.Add(
|
||||
new ModLoader.LoaderTask<List<ModLocalComp.LocalCompFile>,
|
||||
Dictionary<ModLocalComp.LocalCompFile, List<string>>>(Lang.Text("Instance.Export.Task.FetchFileInfo"),
|
||||
loader =>
|
||||
{
|
||||
loader.output = new Dictionary<ModLocalComp.LocalCompFile, List<string>>();
|
||||
if (!checkHostedAssets)
|
||||
{
|
||||
ModBase.Log("[Export] 要求跳过联网获取步骤");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!loader.input.Any())
|
||||
{
|
||||
ModBase.Log("[Export] 没有需要联网检查的文件,跳过联网获取步骤");
|
||||
return;
|
||||
}
|
||||
|
||||
// 分平台获取下载地址
|
||||
var endedThreadCount = 0;
|
||||
var failedExceptions = new List<Exception>();
|
||||
|
||||
// 从 Modrinth 获取信息
|
||||
// 查找对应的文件
|
||||
// 写入下载地址
|
||||
ModBase.RunInNewThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var modrinthHashes = loader.input.Select(m => m.ModrinthHash);
|
||||
var modrinthRaw = (JsonObject)ModBase.GetJson(ModDownload.DlModRequest(
|
||||
"https://api.modrinth.com/v2/version_files", "POST",
|
||||
$"{{\"hashes\": [\"{modrinthHashes.Join("\",\"")}\"], \"algorithm\": \"sha1\"}}",
|
||||
"application/json"));
|
||||
foreach (var ModFile in loader.input)
|
||||
{
|
||||
if (!modrinthRaw.ContainsKey(ModFile.ModrinthHash)) continue;
|
||||
if ((string)modrinthRaw[ModFile.ModrinthHash]?["files"]?[0]["hashes"]?["sha1"] !=
|
||||
ModFile.ModrinthHash) continue;
|
||||
loader.output.AddToList(ModFile,
|
||||
(string)modrinthRaw[ModFile.ModrinthHash]["files"][0]["url"]);
|
||||
}
|
||||
|
||||
ModBase.Log($"[Export] 从 Modrinth 获取到 {modrinthRaw.Count} 个本地资源项的对应信息");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "从 Modrinth 获取本地 Mod 信息失败");
|
||||
failedExceptions.Add(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
endedThreadCount += 1;
|
||||
loader.Progress += 0.45d;
|
||||
}
|
||||
}, "Modrinth - " + loaderName);
|
||||
|
||||
// 从 CurseForge 获取信息
|
||||
// 查找对应的文件
|
||||
// 写入下载地址
|
||||
ModBase.RunInNewThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (modrinthUploadMode) return;
|
||||
var curseForgeHashes = loader.input.Select(m => m.CurseForgeHash);
|
||||
var curseForgeRaw = (JsonNode)((JsonObject)ModBase.GetJson(
|
||||
ModDownload.DlModRequest("https://api.curseforge.com/v1/fingerprints/432/", "POST",
|
||||
$"{{\"fingerprints\": [{curseForgeHashes.Join(",")}]}}",
|
||||
"application/json")))["data"][
|
||||
"exactMatches"];
|
||||
foreach (JsonObject ResultJson in curseForgeRaw.AsArray())
|
||||
{
|
||||
if (!ResultJson.ContainsKey("file")) continue;
|
||||
var file = (JsonObject)ResultJson["file"];
|
||||
if (string.IsNullOrEmpty((string)file["downloadUrl"])) continue;
|
||||
var modFile = loader.input.FirstOrDefault(m =>
|
||||
m.CurseForgeHash == file["fileFingerprint"].ToObject<uint>());
|
||||
if (modFile is null) continue;
|
||||
loader.output.AddToList(modFile,
|
||||
ModComp.CompFile.HandleCurseForgeDownloadUrls(file["downloadUrl"].ToString()));
|
||||
}
|
||||
|
||||
ModBase.Log($"[Export] 从 CurseForge 获取到 {curseForgeRaw.AsArray().Count} 个本地资源项的对应信息");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "从 CurseForge 获取本地 Mod 信息失败");
|
||||
failedExceptions.Add(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
endedThreadCount += 1;
|
||||
loader.Progress += 0.45d;
|
||||
}
|
||||
}, "CurseForge - " + loaderName); // Modrinth 上传模式下,不能从 CurseForge 获取信息
|
||||
|
||||
// 等待线程结束
|
||||
while (endedThreadCount != 2)
|
||||
{
|
||||
if (loader.IsAborted)
|
||||
return;
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
|
||||
// 若失败,确认是否继续
|
||||
if (failedExceptions.Count == 1)
|
||||
{
|
||||
if (ModMain.MyMsgBox(
|
||||
Lang.Text("Instance.Export.NetCheckPartialFailed.Message"),
|
||||
Lang.Text("Instance.Export.NetCheckPartialFailed.Title"),
|
||||
Lang.Text("Common.Action.Continue"), Lang.Text("Common.Action.Cancel")) == 2)
|
||||
throw failedExceptions.First();
|
||||
}
|
||||
else if (failedExceptions.Count > 1)
|
||||
{
|
||||
if (ModMain.MyMsgBox(
|
||||
Lang.Text("Instance.Export.NetCheckAllFailed.Message"),
|
||||
Lang.Text("Instance.Export.NetCheckAllFailed.Title"),
|
||||
Lang.Text("Common.Action.Continue"), Lang.Text("Common.Action.Cancel")) == 2)
|
||||
throw failedExceptions.First();
|
||||
}
|
||||
})
|
||||
{
|
||||
show = checkHostedAssets,
|
||||
ProgressWeight = checkHostedAssets ? 2d : 0.01d
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
#region 生成压缩包
|
||||
|
||||
loaders.Add(new ModLoader.LoaderTask<Dictionary<ModLocalComp.LocalCompFile, List<string>>, int>(
|
||||
Lang.Text("Instance.Export.Task.CreateArchive"),
|
||||
loader =>
|
||||
{
|
||||
// 整理文件列表
|
||||
var files = new JsonArray();
|
||||
foreach (var Pair in loader.input)
|
||||
{
|
||||
var modFile = Pair.Key;
|
||||
files.Add(new JsonObject
|
||||
{
|
||||
{ "path", Path.GetRelativePath(overridesFolder, modFile.path).Replace(@"\", "/") },
|
||||
{
|
||||
"hashes",
|
||||
new JsonObject
|
||||
{
|
||||
{ "sha1", modFile.ModrinthHash }, { "sha512", ModBase.GetFileSHA512(modFile.path) }
|
||||
}
|
||||
},
|
||||
{ "downloads", new JsonArray(Pair.Value.OrderByDescending(u => u.Contains("modrinth.com")).Select(s => (JsonNode)s).ToArray()) },
|
||||
{ "fileSize", new FileInfo(modFile.path).Length }
|
||||
});
|
||||
File.Delete(modFile.path);
|
||||
}
|
||||
|
||||
loader.Progress = 0.2d;
|
||||
// 导出最终 JSON 文件
|
||||
var dependencies = new JsonObject { { "minecraft", mcInstance.Info.VanillaName } };
|
||||
if (mcInstance.Info.HasForge)
|
||||
dependencies.Add("forge", mcInstance.Info.Forge);
|
||||
if (mcInstance.Info.HasFabric)
|
||||
dependencies.Add("fabric-loader", mcInstance.Info.Fabric);
|
||||
if (mcInstance.Info.HasNeoForge)
|
||||
dependencies.Add("neoforge", mcInstance.Info.NeoForge);
|
||||
var resultJson = new JsonObject
|
||||
{
|
||||
{ "game", "minecraft" }, { "formatVersion", 1 }, { "versionId", packVersion }, { "name", packName },
|
||||
{ "summary", mcInstance.Desc }, { "files", files }, { "dependencies", dependencies }
|
||||
};
|
||||
File.WriteAllText(Path.Combine(cacheFolder, "modpack", "modrinth.index.json"),
|
||||
resultJson.ToJsonString(new JsonSerializerOptions(JsonCompat.SerializerOptions) { WriteIndented = true }));
|
||||
// 打包
|
||||
Directory.CreateDirectory(ModBase.GetPathFromFullPath(packPath));
|
||||
if (File.Exists(packPath))
|
||||
File.Delete(packPath);
|
||||
if (includePCL)
|
||||
{
|
||||
// 首次压缩整合包
|
||||
ZipFile.CreateFromDirectory(Path.Combine(cacheFolder, "modpack"), Path.Combine(cacheFolder, "modpack.mrpack"));
|
||||
loader.Progress = 0.5d;
|
||||
Directory.Delete(Path.Combine(cacheFolder, "modpack"), true);
|
||||
loader.Progress = 0.6d;
|
||||
// 二次压缩整合包
|
||||
ZipFile.CreateFromDirectory(cacheFolder, packPath);
|
||||
loader.Progress = 0.9d;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 直接压缩整合包
|
||||
ZipFile.CreateFromDirectory(Path.Combine(cacheFolder, "modpack"), packPath);
|
||||
loader.Progress = 0.8d;
|
||||
}
|
||||
|
||||
Directory.Delete(cacheFolder, true);
|
||||
ModBase.OpenExplorer(packPath);
|
||||
})
|
||||
{
|
||||
ProgressWeight = 6d
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
// 启动
|
||||
var mainLoader = new ModLoader.LoaderCombo<string>(loaderName, loaders)
|
||||
{ OnStateChanged = ModDownloadLib.LoaderStateChangedHintOnly };
|
||||
mainLoader.Start();
|
||||
ModLoader.LoaderTaskbarAdd(mainLoader);
|
||||
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
|
||||
ModMain.frmMain.BtnExtraDownload.Ribble();
|
||||
ModMain.frmMain.PageChange(FormMain.PageType.TaskManager);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static bool LikeString(string input, string pattern)
|
||||
{
|
||||
pattern = pattern.Replace("#", "[0-9]");
|
||||
var options = new GlobOptions { Evaluation = { CaseInsensitive = true } };
|
||||
var glob = Glob.Parse(pattern, options);
|
||||
return glob.IsMatch(input);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
<local:MyPageRight
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" x:Class="PCL.PageInstanceInstall"
|
||||
PanScroll="{Binding ElementName=PanBack}">
|
||||
<Grid>
|
||||
<Grid x:Name="PanAllBack">
|
||||
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled"
|
||||
x:Name="PanBack">
|
||||
<Grid Margin="25,10,25,75">
|
||||
<StackPanel Grid.Row="1" Name="PanMinecraft" Grid.IsSharedSizeScope="True" Margin="0,0,0,-10"
|
||||
Visibility="Collapsed">
|
||||
<StackPanel.RenderTransform>
|
||||
<TranslateTransform />
|
||||
</StackPanel.RenderTransform>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="1" x:Name="PanSelect" Visibility="Visible" Opacity="0"
|
||||
IsHitTestVisible="False">
|
||||
<StackPanel.RenderTransform>
|
||||
<TranslateTransform />
|
||||
</StackPanel.RenderTransform>
|
||||
<local:MyHint Text="{DynamicResource Download.Install.Warning.LegacyOptiFabric}"
|
||||
Margin="0,10,0,0" x:Name="HintLegacyOptiFabric" Theme="Yellow" />
|
||||
<local:MyHint Text="{DynamicResource Download.Install.Warning.LegacyFabricApi}"
|
||||
Margin="0,10,0,0"
|
||||
x:Name="HintLegacyFabricAPI" Theme="Red" />
|
||||
<local:MyHint Text="{DynamicResource Download.Install.Warning.FabricApi}"
|
||||
Margin="0,1,0,7"
|
||||
x:Name="HintFabricAPI" IsWarn="True" />
|
||||
<local:MyHint Text="{DynamicResource Download.Install.Warning.OptiFabric}"
|
||||
Margin="0,1,0,7"
|
||||
x:Name="HintOptiFabric" IsWarn="True" />
|
||||
<local:MyHint Text="{DynamicResource Download.Install.Warning.OptiFabricOld}"
|
||||
Margin="0,1,0,7" x:Name="HintOptiFabricOld" IsWarn="False" />
|
||||
<local:MyHint Text="{DynamicResource Download.Install.Warning.ModOptiFine}" Margin="0,1,0,7"
|
||||
x:Name="HintModOptiFine"
|
||||
IsWarn="False" />
|
||||
<local:MyCard Margin="0,15,0,0" UseAnimation="False">
|
||||
<StackPanel Margin="15,7,15,7">
|
||||
<local:MyListItem x:Name="ItemSelect" IsHitTestVisible="False" Margin="-7,0,0,0"
|
||||
Height="42" SnapsToDevicePixels="True" Type="None" />
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
<local:MyCard Title="{DynamicResource Common.Installation.Minecraft}" Height="40" Margin="0,15,0,0"
|
||||
x:Name="CardMinecraft"
|
||||
PreviewSwap="CardMinecraft_PreviewSwap">
|
||||
<Grid x:Name="PanMinecraftInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top">
|
||||
<Grid.RenderTransform>
|
||||
<TranslateTransform />
|
||||
</Grid.RenderTransform>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image x:Name="ImgMinecraft" Margin="0,0,7,0" SnapsToDevicePixels="True" Height="18"
|
||||
RenderOptions.BitmapScalingMode="Linear" />
|
||||
<TextBlock x:Name="LabMinecraft" VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis" Grid.Column="1" />
|
||||
<Path
|
||||
Data="M7.736 1.56a1.914 1.914 0 0 1 2.707 2.708l-.234.234l-2.707-2.707zm-.941.942L1.65 7.646a.5.5 0 0 0-.136.255l-.504 2.5a.5.5 0 0 0 .588.59l2.504-.5a.5.5 0 0 0 .255-.137l5.145-5.145z"
|
||||
Height="12" Width="12" Stretch="Uniform" VerticalAlignment="Center"
|
||||
Grid.Column="2" Fill="{StaticResource ColorBrushGray4}" Margin="0,1,6,0"
|
||||
RenderTransformOrigin="0.5,0.5" />
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{DynamicResource Instance.Install.Action.ModifyLabel}" Grid.Column="3"
|
||||
Foreground="{StaticResource ColorBrushGray4}" />
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
<local:MyCard Title="{DynamicResource Common.Installation.Forge}" Height="40" Margin="0,15,0,0"
|
||||
x:Name="CardForge" IsSwapped="True"
|
||||
CanSwap="True" SwapLogoRight="True" PreviewSwap="CardForge_PreviewSwap">
|
||||
<StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanForge" />
|
||||
<Grid x:Name="PanForgeInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top"
|
||||
Tag="True">
|
||||
<Grid.RenderTransform>
|
||||
<TranslateTransform />
|
||||
</Grid.RenderTransform>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image x:Name="ImgForge" Margin="0,0,7,0" SnapsToDevicePixels="True" Height="18"
|
||||
RenderOptions.BitmapScalingMode="Linear"
|
||||
Source="pack://application:,,,/images/Blocks/Anvil.png" />
|
||||
<TextBlock x:Name="LabForge" VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis" Grid.Column="1" />
|
||||
</Grid>
|
||||
<Grid x:Name="BtnForgeClear" Height="30" Width="30" HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top" Margin="0,5,32,0" MouseLeftButtonUp="Forge_Clear"
|
||||
Background="{StaticResource ColorBrushSemiTransparent}">
|
||||
<Path x:Name="BtnForgeClearInner" Height="10" Width="10" Stretch="Uniform"
|
||||
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
<local:MyCard Title="{DynamicResource Common.Installation.Cleanroom}" Height="40"
|
||||
Margin="0,15,0,0" x:Name="CardCleanroom"
|
||||
IsSwapped="True" CanSwap="True" SwapLogoRight="True"
|
||||
PreviewSwap="CardCleanroom_PreviewSwap">
|
||||
<StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanCleanroom" />
|
||||
<Grid x:Name="PanCleanroomInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top"
|
||||
Tag="True">
|
||||
<Grid.RenderTransform>
|
||||
<TranslateTransform />
|
||||
</Grid.RenderTransform>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image x:Name="ImgCleanroom" Margin="0,0,7,0" SnapsToDevicePixels="True" Height="18"
|
||||
RenderOptions.BitmapScalingMode="Linear"
|
||||
Source="pack://application:,,,/images/Blocks/Cleanroom.png" />
|
||||
<TextBlock x:Name="LabCleanroom" VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis" Grid.Column="1" />
|
||||
</Grid>
|
||||
<Grid x:Name="BtnCleanroomClear" Height="30" Width="30" HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top" Margin="0,5,32,0" MouseLeftButtonUp="Cleanroom_Clear"
|
||||
Background="{StaticResource ColorBrushSemiTransparent}">
|
||||
<Path x:Name="BtnCleanroomClearInner" Height="10" Width="10" Stretch="Uniform"
|
||||
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
<local:MyCard Title="{DynamicResource Common.Installation.NeoForge}" Height="40"
|
||||
Margin="0,15,0,0" x:Name="CardNeoForge"
|
||||
IsSwapped="True" CanSwap="True" SwapLogoRight="True"
|
||||
PreviewSwap="CardNeoForge_PreviewSwap">
|
||||
<StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanNeoForge" />
|
||||
<Grid x:Name="PanNeoForgeInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top"
|
||||
Tag="True">
|
||||
<Grid.RenderTransform>
|
||||
<TranslateTransform />
|
||||
</Grid.RenderTransform>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image x:Name="ImgNeoForge" Margin="0,0,7,0" SnapsToDevicePixels="True" Height="18"
|
||||
RenderOptions.BitmapScalingMode="Linear"
|
||||
Source="pack://application:,,,/images/Blocks/NeoForge.png" />
|
||||
<TextBlock x:Name="LabNeoForge" VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis" Grid.Column="1" />
|
||||
</Grid>
|
||||
<Grid x:Name="BtnNeoForgeClear" Height="30" Width="30" HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top" Margin="0,5,32,0" MouseLeftButtonUp="NeoForge_Clear"
|
||||
Background="{StaticResource ColorBrushSemiTransparent}">
|
||||
<Path x:Name="BtnNeoForgeClearInner" Height="10" Width="10" Stretch="Uniform"
|
||||
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
<local:MyCard Title="{DynamicResource Common.Installation.Fabric}" Height="40" Margin="0,15,0,0"
|
||||
x:Name="CardFabric" IsSwapped="True"
|
||||
CanSwap="True" SwapLogoRight="True" PreviewSwap="CardFabric_PreviewSwap">
|
||||
<StackPanel Margin="20,40,18,0" VerticalAlignment="Top" Name="PanFabric" />
|
||||
<Grid x:Name="PanFabricInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top"
|
||||
Tag="True">
|
||||
<Grid.RenderTransform>
|
||||
<TranslateTransform />
|
||||
</Grid.RenderTransform>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image x:Name="ImgFabric" Margin="0,0,7,0" SnapsToDevicePixels="True" Height="18"
|
||||
RenderOptions.BitmapScalingMode="Linear"
|
||||
Source="pack://application:,,,/images/Blocks/Fabric.png" />
|
||||
<TextBlock x:Name="LabFabric" VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis" Grid.Column="1" />
|
||||
</Grid>
|
||||
<Grid x:Name="BtnFabricClear" Height="30" Width="30" HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top" Margin="0,5,32,0" MouseLeftButtonUp="Fabric_Clear"
|
||||
Background="{StaticResource ColorBrushSemiTransparent}">
|
||||
<Path x:Name="BtnFabricClearInner" Height="10" Width="10" Stretch="Uniform"
|
||||
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
<local:MyCard Title="{DynamicResource Common.Installation.LegacyFabric}" Height="40"
|
||||
Margin="0,12,0,0" x:Name="CardLegacyFabric"
|
||||
IsSwapped="True" CanSwap="True" SwapLogoRight="True"
|
||||
PreviewSwap="CardLegacyFabric_PreviewSwap">
|
||||
<StackPanel Margin="20,40,18,0" VerticalAlignment="Top" Name="PanLegacyFabric" />
|
||||
<Grid x:Name="PanLegacyFabricInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top"
|
||||
Tag="True">
|
||||
<Grid.RenderTransform>
|
||||
<TranslateTransform />
|
||||
</Grid.RenderTransform>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image x:Name="ImgLegacyFabric" Margin="0,0,7,0" SnapsToDevicePixels="True" Height="18"
|
||||
RenderOptions.BitmapScalingMode="Linear"
|
||||
Source="pack://application:,,,/images/Blocks/Fabric.png" />
|
||||
<TextBlock x:Name="LabLegacyFabric" VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis" Grid.Column="1" />
|
||||
</Grid>
|
||||
<Grid x:Name="BtnLegacyFabricClear" Height="30" Width="30" HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top" Margin="0,5,32,0" MouseLeftButtonUp="LegacyFabric_Clear"
|
||||
Background="{StaticResource ColorBrushSemiTransparent}">
|
||||
<Path x:Name="BtnLegacyFabricClearInner" Height="10" Width="10" Stretch="Uniform"
|
||||
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
<local:MyCard Title="{DynamicResource Common.Installation.FabricApi}" Height="40"
|
||||
Margin="0,15,0,0" x:Name="CardFabricApi"
|
||||
IsSwapped="True" CanSwap="True" SwapLogoRight="True"
|
||||
PreviewSwap="CardFabricApi_PreviewSwap">
|
||||
<StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanFabricApi" />
|
||||
<Grid x:Name="PanFabricApiInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top"
|
||||
Tag="True">
|
||||
<Grid.RenderTransform>
|
||||
<TranslateTransform />
|
||||
</Grid.RenderTransform>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image x:Name="ImgFabricApi" Margin="0,0,7,0" SnapsToDevicePixels="True" Height="18"
|
||||
RenderOptions.BitmapScalingMode="Linear"
|
||||
Source="pack://application:,,,/images/Blocks/Fabric.png" />
|
||||
<TextBlock x:Name="LabFabricApi" VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis" Grid.Column="1" />
|
||||
</Grid>
|
||||
<Grid x:Name="BtnFabricApiClear" Height="30" Width="30" HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top" Margin="0,5,32,0" MouseLeftButtonUp="FabricApi_Clear"
|
||||
Background="{StaticResource ColorBrushSemiTransparent}">
|
||||
<Path x:Name="BtnFabricApiClearInner" Height="10" Width="10" Stretch="Uniform"
|
||||
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
<local:MyCard Title="{DynamicResource Common.Installation.LegacyFabricApi}" Height="40"
|
||||
Margin="0,12,0,0"
|
||||
x:Name="CardLegacyFabricApi" IsSwapped="True" CanSwap="True" SwapLogoRight="True"
|
||||
PreviewSwap="CardLegacyFabricApi_PreviewSwap">
|
||||
<StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanLegacyFabricApi" />
|
||||
<Grid x:Name="PanLegacyFabricApiInfo" Height="18" Margin="132,11,15,0"
|
||||
VerticalAlignment="Top" Tag="True">
|
||||
<Grid.RenderTransform>
|
||||
<TranslateTransform />
|
||||
</Grid.RenderTransform>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image x:Name="ImgLegacyFabricApi" Margin="0,0,7,0" SnapsToDevicePixels="True"
|
||||
Height="18" RenderOptions.BitmapScalingMode="Linear"
|
||||
Source="pack://application:,,,/images/Blocks/Fabric.png" />
|
||||
<TextBlock x:Name="LabLegacyFabricApi" VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis" Grid.Column="1" />
|
||||
</Grid>
|
||||
<Grid x:Name="BtnLegacyFabricApiClear" Height="30" Width="30" HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top" Margin="0,5,32,0" MouseLeftButtonUp="LegacyFabricApi_Clear"
|
||||
Background="{StaticResource ColorBrushSemiTransparent}">
|
||||
<Path x:Name="BtnLegacyFabricApiClearInner" Height="10" Width="10" Stretch="Uniform"
|
||||
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
<local:MyCard Title="{DynamicResource Common.Installation.LabyMod}" Height="40" Margin="0,15,0,0"
|
||||
x:Name="CardLabyMod"
|
||||
IsSwapped="True" CanSwap="True" SwapLogoRight="True"
|
||||
PreviewSwap="CardLabyMod_PreviewSwap">
|
||||
<StackPanel Margin="20,40,18,0" VerticalAlignment="Top" Name="PanLabyMod" />
|
||||
<Grid x:Name="PanLabyModInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top"
|
||||
Tag="True">
|
||||
<Grid.RenderTransform>
|
||||
<TranslateTransform />
|
||||
</Grid.RenderTransform>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image x:Name="ImgLabyMod" Margin="0,0,7,0" SnapsToDevicePixels="True" Height="18"
|
||||
RenderOptions.BitmapScalingMode="Linear"
|
||||
Source="pack://application:,,,/images/Blocks/LabyMod.png" />
|
||||
<TextBlock x:Name="LabLabyMod" VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis" Grid.Column="1" />
|
||||
</Grid>
|
||||
<Grid x:Name="BtnLabyModClear" Height="30" Width="30" HorizontalAlignment="Right"
|
||||
MouseLeftButtonUp="LabyMod_Clear"
|
||||
VerticalAlignment="Top" Margin="0,5,32,0"
|
||||
Background="{StaticResource ColorBrushSemiTransparent}">
|
||||
<Path x:Name="BtnLabyModClearInner" Height="10" Width="10" Stretch="Uniform"
|
||||
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
<local:MyCard Title="{DynamicResource Common.Installation.OptiFine}" Height="40"
|
||||
Margin="0,15,0,0" x:Name="CardOptiFine"
|
||||
IsSwapped="True" CanSwap="True" SwapLogoRight="True"
|
||||
PreviewSwap="CardOptiFine_PreviewSwap">
|
||||
<StackPanel Margin="20,40,18,15" VerticalAlignment="Top">
|
||||
<StackPanel Name="PanOptiFine" />
|
||||
</StackPanel>
|
||||
<Grid x:Name="PanOptiFineInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top"
|
||||
Tag="True">
|
||||
<Grid.RenderTransform>
|
||||
<TranslateTransform />
|
||||
</Grid.RenderTransform>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image x:Name="ImgOptiFine" Margin="0,0,7,0" SnapsToDevicePixels="True" Height="18"
|
||||
RenderOptions.BitmapScalingMode="Linear"
|
||||
Source="pack://application:,,,/images/Blocks/GrassPath.png" />
|
||||
<TextBlock x:Name="LabOptiFine" VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis" Grid.Column="1" />
|
||||
</Grid>
|
||||
<Grid x:Name="BtnOptiFineClear" Height="30" Width="30" HorizontalAlignment="Right"
|
||||
MouseLeftButtonUp="OptiFine_Clear"
|
||||
VerticalAlignment="Top" Margin="0,5,32,0"
|
||||
Background="{StaticResource ColorBrushSemiTransparent}">
|
||||
<Path x:Name="BtnOptiFineClearInner" Height="10" Width="10" Stretch="Uniform"
|
||||
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
<local:MyCard Title="{DynamicResource Common.Installation.OptiFabric}" Height="40"
|
||||
Margin="0,15,0,0" x:Name="CardOptiFabric"
|
||||
IsSwapped="True" CanSwap="True" SwapLogoRight="True"
|
||||
PreviewSwap="CardOptiFabric_PreviewSwap">
|
||||
<StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanOptiFabric" />
|
||||
<Grid x:Name="PanOptiFabricInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top"
|
||||
Tag="True">
|
||||
<Grid.RenderTransform>
|
||||
<TranslateTransform />
|
||||
</Grid.RenderTransform>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image x:Name="ImgOptiFabric" Margin="0,0,7,0" SnapsToDevicePixels="True" Height="18"
|
||||
RenderOptions.BitmapScalingMode="HighQuality"
|
||||
Source="pack://application:,,,/images/Blocks/OptiFabric.png" />
|
||||
<TextBlock x:Name="LabOptiFabric" VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis" Grid.Column="1" />
|
||||
</Grid>
|
||||
<Grid x:Name="BtnOptiFabricClear" Height="30" Width="30" HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top" Margin="0,5,32,0" MouseLeftButtonUp="OptiFabric_Clear"
|
||||
Background="{StaticResource ColorBrushSemiTransparent}">
|
||||
<Path x:Name="BtnOptiFabricClearInner" Height="10" Width="10" Stretch="Uniform"
|
||||
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
<local:MyCard Title="{DynamicResource Common.Installation.LiteLoader}" Height="40"
|
||||
Margin="0,15,0,0" x:Name="CardLiteLoader"
|
||||
PreviewSwap="CardLiteLoader_PreviewSwap"
|
||||
IsSwapped="True" CanSwap="True" SwapLogoRight="True">
|
||||
<StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanLiteLoader" />
|
||||
<Grid x:Name="PanLiteLoaderInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top"
|
||||
Tag="True">
|
||||
<Grid.RenderTransform>
|
||||
<TranslateTransform />
|
||||
</Grid.RenderTransform>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image x:Name="ImgLiteLoader" Margin="-1,0,6,0" SnapsToDevicePixels="True" Height="20"
|
||||
RenderOptions.BitmapScalingMode="Linear"
|
||||
Source="pack://application:,,,/images/Blocks/Egg.png" />
|
||||
<TextBlock x:Name="LabLiteLoader" VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis" Grid.Column="1" />
|
||||
</Grid>
|
||||
<Grid x:Name="BtnLiteLoaderClear" Height="30" Width="30" HorizontalAlignment="Right"
|
||||
MouseLeftButtonUp="LiteLoader_Clear"
|
||||
VerticalAlignment="Top" Margin="0,5,32,0"
|
||||
Background="{StaticResource ColorBrushSemiTransparent}">
|
||||
<Path x:Name="BtnLiteLoaderClearInner" Height="10" Width="10" Stretch="Uniform"
|
||||
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</local:MyScrollViewer>
|
||||
<local:MyExtraTextButton x:Name="BtnSelectStart"
|
||||
Text="{DynamicResource Instance.Install.Action.StartModify}"
|
||||
Click="BtnSelectStart_Click"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Bottom" IsEnabled="False"
|
||||
LogoScale="0.95"
|
||||
SvgIcon="lucide/download" />
|
||||
</Grid>
|
||||
<local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" Margin="40,0" SnapsToDevicePixels="True"
|
||||
x:Name="PanLoad" UseAnimation="False">
|
||||
<local:MyLoading Text="" Margin="20,20,20,17" x:Name="LoadMinecraft" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
<local:MyLoading Visibility="Collapsed" x:Name="LoadOptiFine" HasAnimation="False" />
|
||||
<local:MyLoading Visibility="Collapsed" x:Name="LoadForge" HasAnimation="False" />
|
||||
<local:MyLoading Visibility="Collapsed" x:Name="LoadNeoForge" HasAnimation="False" />
|
||||
<local:MyLoading Visibility="Collapsed" x:Name="LoadCleanroom" HasAnimation="False" />
|
||||
<local:MyLoading Visibility="Collapsed" x:Name="LoadLiteLoader" HasAnimation="False" />
|
||||
<local:MyLoading Visibility="Collapsed" x:Name="LoadFabric" HasAnimation="False" />
|
||||
<local:MyLoading Visibility="Collapsed" x:Name="LoadLegacyFabric" HasAnimation="False" />
|
||||
<local:MyLoading Visibility="Collapsed" x:Name="LoadFabricApi" HasAnimation="False" />
|
||||
<local:MyLoading Visibility="Collapsed" x:Name="LoadLegacyFabricApi" HasAnimation="False" />
|
||||
<local:MyLoading Visibility="Collapsed" x:Name="LoadLabyMod" HasAnimation="False" />
|
||||
<local:MyLoading Visibility="Collapsed" x:Name="LoadOptiFabric" HasAnimation="False" />
|
||||
</local:MyCard>
|
||||
</Grid>
|
||||
</local:MyPageRight>
|
||||
+2553
@@ -0,0 +1,2553 @@
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using PCL.Core.App;
|
||||
using PCL.Core.App.Localization;
|
||||
using PCL.Core.UI;
|
||||
using PCL.Core.Utils;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageInstanceInstall
|
||||
{
|
||||
private enum InstallAction
|
||||
{
|
||||
Modify,
|
||||
Reset
|
||||
}
|
||||
|
||||
private bool isLoad;
|
||||
private string lastVersionName;
|
||||
private InstallAction _installAction;
|
||||
|
||||
public PageInstanceInstall()
|
||||
{
|
||||
Initialized += (a, b) => LoaderInit();
|
||||
Loaded += (a, b) => Init();
|
||||
InitializeComponent();
|
||||
LoadMinecraft.Text = Lang.Text("Download.Version.LoadingList");
|
||||
}
|
||||
|
||||
private void LoaderInit()
|
||||
{
|
||||
disabledPageAnimControls.Add(BtnSelectStart);
|
||||
// PageLoaderInit(LoadMinecraft, PanLoad, PanBack, Nothing, DlClientListLoader, AddressOf LoadMinecraft_OnFinish)
|
||||
PageLoaderInit(LoadMinecraft, PanLoad, PanAllBack, null, ModDownload.dlClientListLoader, _ => GetCurrentInfo());
|
||||
LoadOptiFine.StateChanged += (_, _, _) => { OptiFine_Loaded(); ReloadSelected(); };
|
||||
LoadLiteLoader.StateChanged += (_, _, _) => { LiteLoader_Loaded(); ReloadSelected(); };
|
||||
LoadForge.StateChanged += (_, _, _) => { Forge_Loaded(); ReloadSelected(); };
|
||||
LoadNeoForge.StateChanged += (_, _, _) => { NeoForge_Loaded(); ReloadSelected(); };
|
||||
LoadCleanroom.StateChanged += (_, _, _) => { Cleanroom_Loaded(); ReloadSelected(); };
|
||||
LoadFabric.StateChanged += (_, _, _) => { Fabric_Loaded(); ReloadSelected(); };
|
||||
LoadFabricApi.StateChanged += (_, _, _) => { FabricApi_Loaded(); ReloadSelected(); };
|
||||
LoadLegacyFabric.StateChanged += (_, _, _) => { LegacyFabric_Loaded(); ReloadSelected(); };
|
||||
LoadLegacyFabricApi.StateChanged += (_, _, _) => { LegacyFabricApi_Loaded(); ReloadSelected(); };
|
||||
LoadOptiFabric.StateChanged += (_, _, _) => { OptiFabric_Loaded(); ReloadSelected(); };
|
||||
LoadLabyMod.StateChanged += (_, _, _) => { LabyMod_Loaded(); ReloadSelected(); };
|
||||
PageExit += () => isInSelectPage = false;
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
PanBack.ScrollToHome();
|
||||
|
||||
GetCurrentInfo();
|
||||
|
||||
var needRefresh = lastVersionName is null || (lastVersionName ?? "") != (_vanillaName ?? "");
|
||||
lastVersionName = _vanillaName;
|
||||
|
||||
ModDownload.dlOptiFineListLoader.Start(isForceRestart: needRefresh);
|
||||
ModDownload.dlLiteLoaderListLoader.Start(isForceRestart: needRefresh);
|
||||
ModDownload.dlFabricListLoader.Start(isForceRestart: needRefresh);
|
||||
ModDownload.dlNeoForgeListLoader.Start(isForceRestart: needRefresh);
|
||||
ModDownload.dlCleanroomListLoader.Start(isForceRestart: needRefresh);
|
||||
ModDownload.dlLabyModListLoader.Start(isForceRestart: needRefresh);
|
||||
ModDownload.dlLegacyFabricListLoader.Start(isForceRestart: needRefresh);
|
||||
ModDownload.dlFabricApiLoader.Start(isForceRestart: needRefresh);
|
||||
ModDownload.dlLegacyFabricApiLoader.Start(isForceRestart: needRefresh);
|
||||
ModDownload.dlOptiFabricLoader.Start(isForceRestart: needRefresh);
|
||||
|
||||
// 非重复加载部分
|
||||
if (isLoad)
|
||||
{
|
||||
ReloadSelected();
|
||||
return;
|
||||
}
|
||||
isLoad = true;
|
||||
|
||||
ModDownloadLib.McDownloadForgeRecommendedRefresh();
|
||||
|
||||
LoadOptiFine.State = ModDownload.dlOptiFineListLoader;
|
||||
LoadLiteLoader.State = ModDownload.dlLiteLoaderListLoader;
|
||||
LoadFabric.State = ModDownload.dlFabricListLoader;
|
||||
LoadFabricApi.State = ModDownload.dlFabricApiLoader;
|
||||
LoadNeoForge.State = ModDownload.dlNeoForgeListLoader;
|
||||
LoadCleanroom.State = ModDownload.dlCleanroomListLoader;
|
||||
LoadOptiFabric.State = ModDownload.dlOptiFabricLoader;
|
||||
LoadLabyMod.State = ModDownload.dlLabyModListLoader;
|
||||
LoadLegacyFabric.State = ModDownload.dlLegacyFabricListLoader;
|
||||
LoadLegacyFabricApi.State = ModDownload.dlLegacyFabricApiLoader;
|
||||
}
|
||||
|
||||
#region 安装
|
||||
|
||||
private void BtnSelectStart_Click(object sender, MouseButtonEventArgs mouseButtonEventArgs)
|
||||
{
|
||||
// Quilt 实例无法通过安装管线重装/修改(已移除 Quilt 安装支持)
|
||||
if (PageInstanceLeft.McInstance.Info.HasQuilt)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Overall.Reset.QuiltUnsupported"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 确认版本隔离
|
||||
if (selectedLoaderName is not null &&
|
||||
(Config.Launch.IndieSolutionV2 == 0 ||
|
||||
Config.Launch.IndieSolutionV2 == 2))
|
||||
if (ModMain.MyMsgBox(
|
||||
Lang.Text("Download.Install.InstanceIsolation.Warning.Message"), Lang.Text("Download.Install.InstanceIsolation.Warning.Title"), Lang.Text("Download.Install.InstanceIsolation.Warning.Cancel"), Lang.Text("Download.Install.InstanceIsolation.Warning.Continue")) == 1)
|
||||
return;
|
||||
|
||||
if (_installAction == InstallAction.Reset)
|
||||
if (ModMain.MyMsgBox(
|
||||
Lang.Text("Instance.Install.Reset.Message"),
|
||||
Lang.Text("Instance.Install.Reset.Title"),
|
||||
Lang.Text("Common.Action.Continue"),
|
||||
Lang.Text("Common.Action.Cancel")
|
||||
) == 2)
|
||||
return;
|
||||
|
||||
// 删除 LabyMod Neo 文件
|
||||
if ((PageInstanceLeft.McInstance.PathIndie ?? "") != (PageInstanceLeft.McInstance.PathInstance ?? "") &&
|
||||
PageInstanceLeft.McInstance.Info.HasLabyMod)
|
||||
Directory.Delete(System.IO.Path.Combine(PageInstanceLeft.McInstance.PathIndie, "labymod-neo"), true);
|
||||
// 备份实例核心文件
|
||||
ModBase.CopyFile(PageInstanceLeft.McInstance.PathInstance + PageInstanceLeft.McInstance.Name + ".json",
|
||||
PageInstanceLeft.McInstance.PathInstance + @"PCLInstallBackups\" + PageInstanceLeft.McInstance.Name + ".json");
|
||||
if (File.Exists(PageInstanceLeft.McInstance.PathInstance + PageInstanceLeft.McInstance.Name + ".jar"))
|
||||
ModBase.CopyFile(PageInstanceLeft.McInstance.PathInstance + PageInstanceLeft.McInstance.Name + ".jar",
|
||||
PageInstanceLeft.McInstance.PathInstance + @"PCLInstallBackups\" + PageInstanceLeft.McInstance.Name +
|
||||
".jar");
|
||||
// 确认独立 API (如 Fabric API 等) 是否需要被修改
|
||||
if (selectedFabricApi?.Equals(_currentFabricApi) == true)
|
||||
selectedFabricApi = null;
|
||||
if (selectedLegacyFabricApi?.Equals(_currentLegacyFabricApi) == true)
|
||||
selectedLegacyFabricApi = null;
|
||||
if (selectedOptiFabric?.Equals(_currentOptiFabric) == true)
|
||||
selectedOptiFabric = null;
|
||||
// 提交安装申请
|
||||
var request = new ModDownloadLib.McInstallRequest
|
||||
{
|
||||
targetInstanceName = PageInstanceLeft.McInstance.Name,
|
||||
targetInstanceFolder = $@"{ModFolder.mcFolderSelected}versions\{PageInstanceLeft.McInstance.Name}\",
|
||||
minecraftJson = _vanillaData?["url"].ToString(),
|
||||
minecraftName = _vanillaName,
|
||||
optiFineEntry = selectedOptiFine,
|
||||
forgeEntry = selectedForge,
|
||||
neoForgeEntry = selectedNeoForge,
|
||||
neoForgeVersion = selectedNeoForgeVersion,
|
||||
cleanroomEntry = selectedCleanroom,
|
||||
cleanroomVersion = selectedCleanroomVersion,
|
||||
fabricVersion = selectedFabric,
|
||||
fabricApi = selectedFabricApi,
|
||||
optiFabric = selectedOptiFabric,
|
||||
liteLoaderEntry = selectedLiteLoader,
|
||||
labyModChannel = selectedLabyModChannel,
|
||||
labyModCommitRef = selectedLabyModCommitRef,
|
||||
legacyFabricVersion = selectedLegacyFabric,
|
||||
legacyFabricApi = selectedLegacyFabricApi
|
||||
};
|
||||
BtnSelectStart.IsEnabled = false;
|
||||
if (!ModDownloadLib.McInstall(request, _installAction == InstallAction.Modify ? Lang.Text("Instance.Install.Action.ModifyLabel") : Lang.Text("Common.Action.Reset")))
|
||||
return;
|
||||
// 删除旧的独立 API 文件
|
||||
if (selectedFabricApi is not null && _currentFabricApiPath is not null)
|
||||
File.Delete(_currentFabricApiPath);
|
||||
if (selectedLegacyFabricApi is not null && _currentLegacyFabricApiPath is not null)
|
||||
File.Delete(_currentLegacyFabricApiPath);
|
||||
if (selectedOptiFabric is not null && _currentOptiFabricPath is not null)
|
||||
File.Delete(_currentOptiFabricPath);
|
||||
// 返回主页
|
||||
ModMain.frmMain.PageChange(new FormMain.PageStackData { page = FormMain.PageType.Launch });
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string GetLoaderError(MyLoading loader)
|
||||
{
|
||||
if (loader is null || !loader.State.IsLoader)
|
||||
return Lang.Text("Download.Install.State.Getting");
|
||||
switch (loader.State.LoadingState)
|
||||
{
|
||||
case MyLoading.MyLoadingState.Run:
|
||||
{
|
||||
return Lang.Text("Download.Install.State.Getting");
|
||||
}
|
||||
case MyLoading.MyLoadingState.Error:
|
||||
{
|
||||
var message = ((ModLoader.LoaderBase)loader.State).Error.Message;
|
||||
return message == Lang.Text("Download.Install.State.NoVersion") ? Lang.Text("Download.Install.State.NoVersion") : Lang.Text("Download.Install.State.GetFailed", message);
|
||||
}
|
||||
case MyLoading.MyLoadingState.Unloaded:
|
||||
{
|
||||
return Lang.Text("Download.Install.State.UnknownUnloaded");
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region 页面切换
|
||||
|
||||
// 页面切换动画
|
||||
public bool isInSelectPage;
|
||||
private bool isFirstLoaded;
|
||||
|
||||
private void EnterSelectPage()
|
||||
{
|
||||
if (isInSelectPage)
|
||||
return;
|
||||
isInSelectPage = true;
|
||||
|
||||
disabledPageAnimControls.Remove(BtnSelectStart);
|
||||
BtnSelectStart.Show = true;
|
||||
autoSelectedFabricApi = false;
|
||||
autoSelectedOptiFabric = false;
|
||||
PanSelect.Visibility = Visibility.Visible;
|
||||
PanSelect.IsHitTestVisible = true;
|
||||
PanMinecraft.IsHitTestVisible = false;
|
||||
PanBack.IsHitTestVisible = false;
|
||||
PanBack.ScrollToHome();
|
||||
|
||||
CardMinecraft.IsSwapped = true;
|
||||
CardOptiFine.IsSwapped = true;
|
||||
CardLiteLoader.IsSwapped = true;
|
||||
CardForge.IsSwapped = true;
|
||||
CardNeoForge.IsSwapped = true;
|
||||
CardCleanroom.IsSwapped = true;
|
||||
CardFabric.IsSwapped = true;
|
||||
CardFabricApi.IsSwapped = true;
|
||||
CardOptiFabric.IsSwapped = true;
|
||||
CardLabyMod.IsSwapped = true;
|
||||
CardLegacyFabric.IsSwapped = true;
|
||||
CardLegacyFabricApi.IsSwapped = true;
|
||||
|
||||
if (!(bool)States.Hint.InstallPageBack)
|
||||
{
|
||||
States.Hint.InstallPageBack = true;
|
||||
HintService.Hint(Lang.Text("Download.Install.Hint.MinecraftBack"));
|
||||
}
|
||||
|
||||
// 如果在选择页面按了刷新键,选择页的东西可能会由于动画被隐藏,但不会由于加载结束而再次显示,因此这里需要手动恢复
|
||||
foreach (var Card in GetAllAnimControls(PanSelect))
|
||||
{
|
||||
Card.Opacity = 1d;
|
||||
Card.RenderTransform = new TranslateTransform();
|
||||
}
|
||||
|
||||
// 启动 Forge 加载
|
||||
if (McInstanceInfo.IsFormatFit(_vanillaName))
|
||||
{
|
||||
var forgeLoader =
|
||||
new ModLoader.LoaderTask<string, List<ModDownload.DlForgeVersionEntry>>(
|
||||
"DlForgeVersion " + _vanillaName, ModDownload.DlForgeVersionMain);
|
||||
LoadForge.State = forgeLoader;
|
||||
forgeLoader.Start(_vanillaName);
|
||||
}
|
||||
|
||||
// 启动 Fabric API、Legacy Fabric API、OptiFabric 加载
|
||||
ModDownload.dlFabricApiLoader.Start();
|
||||
ModDownload.dlLegacyFabricApiLoader.Start();
|
||||
ModDownload.dlOptiFabricLoader.Start();
|
||||
|
||||
ModAnimation.AniStart(new[]
|
||||
{
|
||||
ModAnimation.AaOpacity(PanMinecraft, -PanMinecraft.Opacity, 100, 10),
|
||||
ModAnimation.AaCode(() =>
|
||||
{
|
||||
PanBack.ScrollToHome();
|
||||
OptiFine_Loaded();
|
||||
LiteLoader_Loaded();
|
||||
Forge_Loaded();
|
||||
NeoForge_Loaded();
|
||||
Cleanroom_Loaded();
|
||||
Fabric_Loaded();
|
||||
LegacyFabric_Loaded();
|
||||
FabricApi_Loaded();
|
||||
LegacyFabricApi_Loaded();
|
||||
LabyMod_Loaded();
|
||||
OptiFabric_Loaded();
|
||||
ReloadSelected();
|
||||
}, after: true),
|
||||
ModAnimation.AaOpacity(PanSelect, 1d - PanSelect.Opacity, 250, 150),
|
||||
ModAnimation.AaCode(() =>
|
||||
{
|
||||
PanMinecraft.Visibility = Visibility.Collapsed;
|
||||
PanBack.IsHitTestVisible = true;
|
||||
// 初始化 Binding
|
||||
if (isFirstLoaded)
|
||||
return;
|
||||
isFirstLoaded = true;
|
||||
BtnOptiFineClearInner.SetBinding(Shape.FillProperty,
|
||||
new Binding("Foreground") { Source = CardOptiFine.MainTextBlock, Mode = BindingMode.OneWay });
|
||||
BtnLiteLoaderClearInner.SetBinding(Shape.FillProperty,
|
||||
new Binding("Foreground") { Source = CardLiteLoader.MainTextBlock, Mode = BindingMode.OneWay });
|
||||
BtnForgeClearInner.SetBinding(Shape.FillProperty,
|
||||
new Binding("Foreground") { Source = CardForge.MainTextBlock, Mode = BindingMode.OneWay });
|
||||
BtnLegacyFabricClearInner.SetBinding(Shape.FillProperty,
|
||||
new Binding("Foreground") { Source = CardLegacyFabric.MainTextBlock, Mode = BindingMode.OneWay });
|
||||
BtnNeoForgeClearInner.SetBinding(Shape.FillProperty,
|
||||
new Binding("Foreground") { Source = CardNeoForge.MainTextBlock, Mode = BindingMode.OneWay });
|
||||
BtnCleanroomClearInner.SetBinding(Shape.FillProperty,
|
||||
new Binding("Foreground") { Source = CardCleanroom.MainTextBlock, Mode = BindingMode.OneWay });
|
||||
BtnFabricClearInner.SetBinding(Shape.FillProperty,
|
||||
new Binding("Foreground") { Source = CardFabric.MainTextBlock, Mode = BindingMode.OneWay });
|
||||
BtnLegacyFabricApiClearInner.SetBinding(Shape.FillProperty,
|
||||
new Binding("Foreground")
|
||||
{ Source = CardLegacyFabricApi.MainTextBlock, Mode = BindingMode.OneWay });
|
||||
BtnFabricApiClearInner.SetBinding(Shape.FillProperty,
|
||||
new Binding("Foreground") { Source = CardFabricApi.MainTextBlock, Mode = BindingMode.OneWay });
|
||||
BtnLabyModClearInner.SetBinding(Shape.FillProperty,
|
||||
new Binding("Foreground") { Source = CardLabyMod.MainTextBlock, Mode = BindingMode.OneWay });
|
||||
BtnOptiFabricClearInner.SetBinding(Shape.FillProperty,
|
||||
new Binding("Foreground") { Source = CardOptiFabric.MainTextBlock, Mode = BindingMode.OneWay });
|
||||
}, after: true)
|
||||
}, "FrmInstanceInstall SelectPageSwitch", true);
|
||||
}
|
||||
|
||||
public void ExitSelectPage()
|
||||
{
|
||||
if (!isInSelectPage)
|
||||
return;
|
||||
isInSelectPage = false;
|
||||
|
||||
LoadMinecraft_OnFinish();
|
||||
|
||||
disabledPageAnimControls.Add(BtnSelectStart);
|
||||
BtnSelectStart.Show = false;
|
||||
|
||||
ClearSelected(); // 清除已选择项
|
||||
PanMinecraft.Visibility = Visibility.Visible;
|
||||
PanSelect.IsHitTestVisible = false;
|
||||
PanMinecraft.IsHitTestVisible = true;
|
||||
PanBack.IsHitTestVisible = false;
|
||||
PanBack.ScrollToHome();
|
||||
|
||||
ModAnimation.AniStart(new[]
|
||||
{
|
||||
ModAnimation.AaOpacity(PanSelect, -PanSelect.Opacity, 90, 10),
|
||||
ModAnimation.AaCode(() => PanBack.ScrollToHome(), after: true),
|
||||
ModAnimation.AaOpacity(PanMinecraft, 1d - PanMinecraft.Opacity, 150, 100),
|
||||
ModAnimation.AaCode(() =>
|
||||
{
|
||||
PanSelect.Visibility = Visibility.Collapsed;
|
||||
PanBack.IsHitTestVisible = true;
|
||||
}, after: true)
|
||||
}, "FrmInstanceInstall SelectPageSwitch");
|
||||
}
|
||||
|
||||
// 页面切换触发
|
||||
public void MinecraftSelected(MyListItem sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_vanillaName = sender.Title;
|
||||
_vanillaData = (JsonObject)sender.Tag;
|
||||
_vanillaIcon = sender.Logo;
|
||||
EnterSelectPage();
|
||||
}
|
||||
|
||||
private void CardMinecraft_PreviewSwap(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
ExitSelectPage();
|
||||
e.handled = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 选择
|
||||
|
||||
// Minecraft
|
||||
private string? _vanillaName;
|
||||
private JsonObject? _vanillaData;
|
||||
private string? _vanillaIcon;
|
||||
private int VanillaDrop => McInstanceInfo.VersionToDrop(_vanillaName, true);
|
||||
|
||||
// OptiFine
|
||||
private ModDownload.DlOptiFineListEntry? selectedOptiFine;
|
||||
|
||||
/// <summary>
|
||||
/// 选定的 Mod Loader 名称,内容应为 Forge / NeoForge / Fabric / Cleanroom / LabyMod / LegacyFabric
|
||||
/// </summary>
|
||||
private string? selectedLoaderName;
|
||||
|
||||
/// <summary>
|
||||
/// 选定的 Mod Loader API 名称,内容应为 Fabric API
|
||||
/// </summary>
|
||||
private string? selectedAPIName;
|
||||
|
||||
// LiteLoader
|
||||
private ModDownload.DlLiteLoaderListEntry? selectedLiteLoader;
|
||||
|
||||
// Forge
|
||||
private ModDownload.DlForgeVersionEntry? selectedForge;
|
||||
|
||||
// Cleanroom
|
||||
private ModDownload.DlCleanroomListEntry? selectedCleanroom;
|
||||
private string? selectedCleanroomVersion;
|
||||
|
||||
// NeoForge
|
||||
private ModDownload.DlNeoForgeListEntry? selectedNeoForge;
|
||||
private string? selectedNeoForgeVersion;
|
||||
|
||||
// Fabric
|
||||
private string? selectedFabric;
|
||||
|
||||
// FabricApi
|
||||
private ModComp.CompFile? selectedFabricApi;
|
||||
|
||||
// LegacyFabric
|
||||
private string? selectedLegacyFabric;
|
||||
|
||||
// Legacy FabricApi
|
||||
private ModComp.CompFile? selectedLegacyFabricApi;
|
||||
|
||||
// LabyMod
|
||||
private string? selectedLabyModChannel;
|
||||
private string? selectedLabyModCommitRef;
|
||||
private string? selectedLabyModVersion;
|
||||
|
||||
// OptiFabric
|
||||
private ModComp.CompFile? selectedOptiFabric;
|
||||
|
||||
private bool _ReloadSelected_Ongoing; // #3742 中,LoadOptiFineGetError 会初始化 LoadOptiFine,触发事件 LoadOptiFine.StateChanged,导致再次调用 SelectReload
|
||||
|
||||
/// <summary>
|
||||
/// 重载已选择的项目的显示。
|
||||
/// </summary>
|
||||
private void ReloadSelected()
|
||||
{
|
||||
if (_vanillaName is null || _ReloadSelected_Ongoing)
|
||||
return;
|
||||
_ReloadSelected_Ongoing = true;
|
||||
try
|
||||
{
|
||||
var selectedInfo = GetSelectInfo();
|
||||
// 主预览
|
||||
ItemSelect.Title = PageInstanceLeft.McInstance.Name;
|
||||
ItemSelect.Logo = GetSelectLogo();
|
||||
BtnSelectStart.IsEnabled = true;
|
||||
if ((selectedInfo ?? "") == (currentInfo ?? ""))
|
||||
{
|
||||
ItemSelect.Info = selectedInfo;
|
||||
BtnSelectStart.Text = Lang.Text("Instance.Install.Action.StartReset");
|
||||
_installAction = InstallAction.Reset;
|
||||
BtnSelectStart.SvgIcon = "lucide/rotate-ccw";
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemSelect.Info = currentInfo + " → " + selectedInfo;
|
||||
BtnSelectStart.Text = Lang.Text("Instance.Install.Action.StartModify");
|
||||
_installAction = InstallAction.Modify;
|
||||
BtnSelectStart.SvgIcon = "lucide/pencil";
|
||||
}
|
||||
|
||||
// Minecraft
|
||||
ImgMinecraft.Source = new MyBitmap(_vanillaIcon);
|
||||
LabMinecraft.Text = _vanillaName;
|
||||
LabMinecraft.Foreground = ThemeManager.colorGray1;
|
||||
// OptiFine
|
||||
if (!McVersionComparer.CompareVersionGe(_vanillaName, "1.7.2"))
|
||||
{
|
||||
CardOptiFine.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
CardOptiFine.Visibility = Visibility.Visible;
|
||||
var optiFineError = LoadOptiFineGetError();
|
||||
CardOptiFine.MainSwap.Visibility = optiFineError is null ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (optiFineError is not null)
|
||||
CardOptiFine.IsSwapped = true;
|
||||
SetPanelVisibility(PanOptiFineInfo, CardOptiFine.IsSwapped);
|
||||
if (selectedOptiFine is null)
|
||||
{
|
||||
BtnOptiFineClear.Visibility = Visibility.Collapsed;
|
||||
ImgOptiFine.Visibility = Visibility.Collapsed;
|
||||
LabOptiFine.Text = optiFineError ?? Lang.Text("Download.Install.State.CanAdd");
|
||||
LabOptiFine.Foreground = ThemeManager.colorGray4;
|
||||
}
|
||||
else
|
||||
{
|
||||
BtnOptiFineClear.Visibility = Visibility.Visible;
|
||||
ImgOptiFine.Visibility = Visibility.Visible;
|
||||
LabOptiFine.Text = selectedOptiFine.DisplayName.Replace(_vanillaName + " ", "");
|
||||
LabOptiFine.Foreground = ThemeManager.colorGray1;
|
||||
}
|
||||
}
|
||||
|
||||
// LiteLoader
|
||||
if (!McInstanceInfo.IsFormatFit(_vanillaName)
|
||||
|| !McVersionComparer.CompareVersionGe(_vanillaName, "1.5.2")
|
||||
|| !McVersionComparer.CompareVersionGe("1.12.2", _vanillaName))
|
||||
{
|
||||
CardLiteLoader.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
CardLiteLoader.Visibility = Visibility.Visible;
|
||||
var liteLoaderError = LoadLiteLoaderGetError();
|
||||
CardLiteLoader.MainSwap.Visibility = liteLoaderError is null ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (liteLoaderError is not null)
|
||||
CardLiteLoader.IsSwapped = true; // 例如在同时展开卡片时选择了不兼容项则强制折叠
|
||||
SetPanelVisibility(PanLiteLoaderInfo, CardLiteLoader.IsSwapped);
|
||||
if (selectedLiteLoader is null)
|
||||
{
|
||||
BtnLiteLoaderClear.Visibility = Visibility.Collapsed;
|
||||
ImgLiteLoader.Visibility = Visibility.Collapsed;
|
||||
LabLiteLoader.Text = liteLoaderError ?? Lang.Text("Download.Install.State.CanAdd");
|
||||
LabLiteLoader.Foreground = ThemeManager.colorGray4;
|
||||
}
|
||||
else
|
||||
{
|
||||
BtnLiteLoaderClear.Visibility = Visibility.Visible;
|
||||
ImgLiteLoader.Visibility = Visibility.Visible;
|
||||
LabLiteLoader.Text = selectedLiteLoader.Inherit;
|
||||
LabLiteLoader.Foreground = ThemeManager.colorGray1;
|
||||
}
|
||||
}
|
||||
|
||||
// Forge
|
||||
if (!McInstanceInfo.IsFormatFit(_vanillaName)
|
||||
|| !McVersionComparer.CompareVersionGe(_vanillaName, "1.1"))
|
||||
{
|
||||
CardForge.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
CardForge.Visibility = Visibility.Visible;
|
||||
var forgeError = LoadForgeGetError();
|
||||
CardForge.MainSwap.Visibility = forgeError is null ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (forgeError is not null)
|
||||
CardForge.IsSwapped = true;
|
||||
SetPanelVisibility(PanForgeInfo, CardForge.IsSwapped);
|
||||
if (selectedForge is null)
|
||||
{
|
||||
BtnForgeClear.Visibility = Visibility.Collapsed;
|
||||
ImgForge.Visibility = Visibility.Collapsed;
|
||||
LabForge.Text = forgeError ?? Lang.Text("Download.Install.State.CanAdd");
|
||||
LabForge.Foreground = ThemeManager.colorGray4;
|
||||
}
|
||||
else
|
||||
{
|
||||
BtnForgeClear.Visibility = Visibility.Visible;
|
||||
ImgForge.Visibility = Visibility.Visible;
|
||||
LabForge.Text = selectedForge.VersionName;
|
||||
LabForge.Foreground = ThemeManager.colorGray1;
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanroom
|
||||
if (_vanillaName == "1.12.2")
|
||||
{
|
||||
CardCleanroom.Visibility = Visibility.Visible;
|
||||
var cleanroomError = LoadCleanroomGetError();
|
||||
CardCleanroom.MainSwap.Visibility = cleanroomError is null ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (cleanroomError is not null)
|
||||
CardCleanroom.IsSwapped = true;
|
||||
SetPanelVisibility(PanCleanroomInfo, CardCleanroom.IsSwapped);
|
||||
if (selectedCleanroom is null)
|
||||
{
|
||||
BtnCleanroomClear.Visibility = Visibility.Collapsed;
|
||||
ImgCleanroom.Visibility = Visibility.Collapsed;
|
||||
LabCleanroom.Text = cleanroomError ?? Lang.Text("Download.Install.State.CanAdd");
|
||||
LabCleanroom.Foreground = ThemeManager.colorGray4;
|
||||
}
|
||||
else
|
||||
{
|
||||
BtnCleanroomClear.Visibility = Visibility.Visible;
|
||||
ImgCleanroom.Visibility = Visibility.Visible;
|
||||
LabCleanroom.Text = selectedCleanroom.VersionName;
|
||||
LabCleanroom.Foreground = ThemeManager.colorGray1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CardCleanroom.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
// NeoForge
|
||||
if (!McVersionComparer.CompareVersionGe(_vanillaName, "1.20.1"))
|
||||
{
|
||||
CardNeoForge.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
CardNeoForge.Visibility = Visibility.Visible;
|
||||
var neoForgeError = LoadNeoForgeGetError();
|
||||
CardNeoForge.MainSwap.Visibility = neoForgeError is null ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (neoForgeError is not null)
|
||||
CardNeoForge.IsSwapped = true;
|
||||
SetPanelVisibility(PanNeoForgeInfo, CardNeoForge.IsSwapped);
|
||||
if (selectedNeoForge is null)
|
||||
{
|
||||
BtnNeoForgeClear.Visibility = Visibility.Collapsed;
|
||||
ImgNeoForge.Visibility = Visibility.Collapsed;
|
||||
LabNeoForge.Text = neoForgeError ?? Lang.Text("Download.Install.State.CanAdd");
|
||||
LabNeoForge.Foreground = ThemeManager.colorGray4;
|
||||
}
|
||||
else
|
||||
{
|
||||
BtnNeoForgeClear.Visibility = Visibility.Visible;
|
||||
ImgNeoForge.Visibility = Visibility.Visible;
|
||||
LabNeoForge.Text = selectedNeoForge.VersionName;
|
||||
LabNeoForge.Foreground = ThemeManager.colorGray1;
|
||||
}
|
||||
}
|
||||
|
||||
// Fabric
|
||||
if (VanillaDrop < 130
|
||||
|| (VanillaDrop == 130 && !McVersionComparer.CompareVersionGe(_vanillaName, "18w43b")))
|
||||
{
|
||||
CardFabric.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
CardFabric.Visibility = Visibility.Visible;
|
||||
var fabricError = LoadFabricGetError();
|
||||
CardFabric.MainSwap.Visibility = fabricError is null ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (fabricError is not null)
|
||||
CardFabric.IsSwapped = true;
|
||||
SetPanelVisibility(PanFabricInfo, CardFabric.IsSwapped);
|
||||
if (selectedFabric is null)
|
||||
{
|
||||
BtnFabricClear.Visibility = Visibility.Collapsed;
|
||||
ImgFabric.Visibility = Visibility.Collapsed;
|
||||
LabFabric.Text = fabricError ?? Lang.Text("Download.Install.State.CanAdd");
|
||||
LabFabric.Foreground = ThemeManager.colorGray4;
|
||||
}
|
||||
else
|
||||
{
|
||||
BtnFabricClear.Visibility = Visibility.Visible;
|
||||
ImgFabric.Visibility = Visibility.Visible;
|
||||
LabFabric.Text = selectedFabric.Replace("+build", "");
|
||||
LabFabric.Foreground = ThemeManager.colorGray1;
|
||||
}
|
||||
}
|
||||
|
||||
// FabricApi
|
||||
if (selectedFabric is null)
|
||||
{
|
||||
CardFabricApi.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
CardFabricApi.Visibility = Visibility.Visible;
|
||||
var fabricApiError = LoadFabricApiGetError();
|
||||
CardFabricApi.MainSwap.Visibility = fabricApiError is null ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (fabricApiError is not null || selectedFabric is null)
|
||||
CardFabricApi.IsSwapped = true;
|
||||
SetPanelVisibility(PanFabricApiInfo, CardFabricApi.IsSwapped);
|
||||
if (selectedFabricApi is null)
|
||||
{
|
||||
BtnFabricApiClear.Visibility = Visibility.Collapsed;
|
||||
ImgFabricApi.Visibility = Visibility.Collapsed;
|
||||
LabFabricApi.Text = fabricApiError ?? Lang.Text("Download.Install.State.CanAdd");
|
||||
LabFabricApi.Foreground = ThemeManager.colorGray4;
|
||||
}
|
||||
else
|
||||
{
|
||||
BtnFabricApiClear.Visibility = Visibility.Visible;
|
||||
ImgFabricApi.Visibility = Visibility.Visible;
|
||||
LabFabricApi.Text = selectedFabricApi.DisplayName.Split("]")[1].Replace("Fabric API ", "")
|
||||
.Replace(" build ", ".").Split("+").First().Trim();
|
||||
LabFabricApi.Foreground = ThemeManager.colorGray1;
|
||||
}
|
||||
}
|
||||
|
||||
// LegacyFabric
|
||||
if (VanillaDrop < 30 || VanillaDrop > 130)
|
||||
{
|
||||
CardLegacyFabric.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
CardLegacyFabric.Visibility = Visibility.Visible;
|
||||
var legacyFabricError = LoadLegacyFabricGetError();
|
||||
CardLegacyFabric.MainSwap.Visibility =
|
||||
legacyFabricError is null ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (legacyFabricError is not null)
|
||||
CardLegacyFabric.IsSwapped = true;
|
||||
SetPanelVisibility(PanLegacyFabricInfo, CardLegacyFabric.IsSwapped);
|
||||
if (selectedLegacyFabric is null)
|
||||
{
|
||||
BtnLegacyFabricClear.Visibility = Visibility.Collapsed;
|
||||
ImgLegacyFabric.Visibility = Visibility.Collapsed;
|
||||
LabLegacyFabric.Text = legacyFabricError ?? Lang.Text("Download.Install.State.CanAdd");
|
||||
LabLegacyFabric.Foreground = ThemeManager.colorGray4;
|
||||
}
|
||||
else
|
||||
{
|
||||
BtnLegacyFabricClear.Visibility = Visibility.Visible;
|
||||
ImgLegacyFabric.Visibility = Visibility.Visible;
|
||||
LabLegacyFabric.Text = selectedLegacyFabric.Replace("+build", "");
|
||||
LabLegacyFabric.Foreground = ThemeManager.colorGray1;
|
||||
}
|
||||
}
|
||||
|
||||
// LegacyFabricApi
|
||||
if (selectedLegacyFabric is null)
|
||||
{
|
||||
CardLegacyFabricApi.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
CardLegacyFabricApi.Visibility = Visibility.Visible;
|
||||
var legacyFabricApiError = LoadLegacyFabricApiGetError();
|
||||
CardLegacyFabricApi.MainSwap.Visibility =
|
||||
legacyFabricApiError is null ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (legacyFabricApiError is not null || selectedLegacyFabric is null)
|
||||
CardLegacyFabricApi.IsSwapped = true;
|
||||
SetPanelVisibility(PanLegacyFabricApiInfo, CardLegacyFabricApi.IsSwapped);
|
||||
if (selectedLegacyFabricApi is null)
|
||||
{
|
||||
BtnLegacyFabricApiClear.Visibility = Visibility.Collapsed;
|
||||
ImgLegacyFabricApi.Visibility = Visibility.Collapsed;
|
||||
LabLegacyFabricApi.Text = legacyFabricApiError ?? Lang.Text("Download.Install.State.CanAdd");
|
||||
LabLegacyFabricApi.Foreground = ThemeManager.colorGray4;
|
||||
}
|
||||
else
|
||||
{
|
||||
BtnLegacyFabricApiClear.Visibility = Visibility.Visible;
|
||||
ImgLegacyFabricApi.Visibility = Visibility.Visible;
|
||||
LabLegacyFabricApi.Text = selectedLegacyFabricApi.DisplayName.Replace("Legacy Fabric API ", "");
|
||||
LabLegacyFabricApi.Foreground = ThemeManager.colorGray1;
|
||||
}
|
||||
}
|
||||
|
||||
// LabyMod
|
||||
if (!McInstanceInfo.IsFormatFit(_vanillaName)
|
||||
|| !McVersionComparer.CompareVersionGe(_vanillaName, "1.8.9"))
|
||||
{
|
||||
CardLabyMod.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
CardLabyMod.Visibility = Visibility.Visible;
|
||||
var labyModError = LoadLabyModGetError();
|
||||
CardLabyMod.MainSwap.Visibility = labyModError is null ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (labyModError is not null)
|
||||
CardLabyMod.IsSwapped = true;
|
||||
SetPanelVisibility(PanLabyModInfo, CardLabyMod.IsSwapped);
|
||||
if (selectedLabyModVersion is null)
|
||||
{
|
||||
BtnLabyModClear.Visibility = Visibility.Collapsed;
|
||||
ImgLabyMod.Visibility = Visibility.Collapsed;
|
||||
LabLabyMod.Text = labyModError ?? Lang.Text("Download.Install.State.CanAdd");
|
||||
LabLabyMod.Foreground = ThemeManager.colorGray4;
|
||||
}
|
||||
else
|
||||
{
|
||||
BtnLabyModClear.Visibility = Visibility.Visible;
|
||||
ImgLabyMod.Visibility = Visibility.Visible;
|
||||
LabLabyMod.Text = selectedLabyModVersion;
|
||||
LabLabyMod.Foreground = ThemeManager.colorGray1;
|
||||
}
|
||||
}
|
||||
|
||||
// OptiFabric
|
||||
if (selectedFabric is null || selectedOptiFine is null)
|
||||
{
|
||||
CardOptiFabric.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
CardOptiFabric.Visibility = Visibility.Visible;
|
||||
var optiFabricError = LoadOptiFabricGetError();
|
||||
CardOptiFabric.MainSwap.Visibility = optiFabricError is null ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (optiFabricError is not null || selectedFabric is null)
|
||||
CardOptiFabric.IsSwapped = true;
|
||||
SetPanelVisibility(PanOptiFabricInfo, CardOptiFabric.IsSwapped);
|
||||
if (selectedOptiFabric is null)
|
||||
{
|
||||
BtnOptiFabricClear.Visibility = Visibility.Collapsed;
|
||||
ImgOptiFabric.Visibility = Visibility.Collapsed;
|
||||
LabOptiFabric.Text = optiFabricError ?? Lang.Text("Download.Install.State.CanAdd");
|
||||
LabOptiFabric.Foreground = ThemeManager.colorGray4;
|
||||
}
|
||||
else
|
||||
{
|
||||
BtnOptiFabricClear.Visibility = Visibility.Visible;
|
||||
ImgOptiFabric.Visibility = Visibility.Visible;
|
||||
LabOptiFabric.Text = selectedOptiFabric.DisplayName.ToLower().Replace("optifabric-", "")
|
||||
.Replace(".jar", "").Trim().TrimStart('v');
|
||||
LabOptiFabric.Foreground = ThemeManager.colorGray1;
|
||||
}
|
||||
}
|
||||
|
||||
// 主警告
|
||||
if (selectedFabric is not null && selectedFabricApi is null)
|
||||
HintFabricAPI.Visibility = Visibility.Visible;
|
||||
else
|
||||
HintFabricAPI.Visibility = Visibility.Collapsed;
|
||||
if (selectedLegacyFabric is not null && selectedLegacyFabricApi is null)
|
||||
HintLegacyFabricAPI.Visibility = Visibility.Visible;
|
||||
else
|
||||
HintLegacyFabricAPI.Visibility = Visibility.Collapsed;
|
||||
|
||||
if ((selectedFabric is not null || selectedLegacyFabric is not null) && selectedOptiFine is not null &&
|
||||
selectedOptiFabric is null)
|
||||
{
|
||||
if (VanillaDrop >= 140 && VanillaDrop <= 150)
|
||||
{
|
||||
HintOptiFabric.Visibility = Visibility.Collapsed;
|
||||
HintLegacyOptiFabric.Visibility = Visibility.Collapsed;
|
||||
HintOptiFabricOld.Visibility = Visibility.Visible;
|
||||
}
|
||||
else if (selectedLegacyFabric is not null)
|
||||
{
|
||||
HintOptiFabric.Visibility = Visibility.Collapsed;
|
||||
HintLegacyOptiFabric.Visibility = Visibility.Visible;
|
||||
HintOptiFabricOld.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
HintOptiFabric.Visibility = Visibility.Visible;
|
||||
HintOptiFabricOld.Visibility = Visibility.Collapsed;
|
||||
HintLegacyOptiFabric.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
HintOptiFabric.Visibility = Visibility.Collapsed;
|
||||
HintOptiFabricOld.Visibility = Visibility.Collapsed;
|
||||
HintLegacyOptiFabric.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
if (VanillaDrop >= 160 && selectedOptiFine is not null &&
|
||||
(selectedForge is not null || selectedFabric is not null))
|
||||
HintModOptiFine.Visibility = Visibility.Visible;
|
||||
else
|
||||
HintModOptiFine.Visibility = Visibility.Collapsed;
|
||||
// 结束
|
||||
}
|
||||
finally
|
||||
{
|
||||
_ReloadSelected_Ongoing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空已选择的项目。
|
||||
/// </summary>
|
||||
private void ClearSelected()
|
||||
{
|
||||
_vanillaName = null;
|
||||
_vanillaData = null;
|
||||
_vanillaIcon = null;
|
||||
selectedOptiFine = null;
|
||||
selectedLiteLoader = null;
|
||||
selectedLoaderName = null;
|
||||
selectedAPIName = null;
|
||||
selectedForge = null;
|
||||
selectedNeoForge = null;
|
||||
selectedNeoForgeVersion = null;
|
||||
selectedCleanroom = null;
|
||||
selectedCleanroomVersion = null;
|
||||
selectedFabric = null;
|
||||
selectedFabricApi = null;
|
||||
selectedOptiFabric = null;
|
||||
selectedLabyModCommitRef = null;
|
||||
selectedLabyModVersion = null;
|
||||
selectedLabyModChannel = null;
|
||||
selectedLegacyFabric = null;
|
||||
selectedLegacyFabricApi = null;
|
||||
}
|
||||
|
||||
// 信息栏动画
|
||||
private void SetPanelVisibility(Grid panel, bool visible)
|
||||
{
|
||||
if (Equals(panel.Tag, visible.ToString()))
|
||||
return;
|
||||
panel.Tag = visible.ToString();
|
||||
if (visible)
|
||||
ModAnimation.AniStart(
|
||||
new[]
|
||||
{
|
||||
ModAnimation.AaTranslateY(panel, -((TranslateTransform)panel.RenderTransform).Y, 150,
|
||||
ease: new ModAnimation.AniEaseOutFluent()),
|
||||
ModAnimation.AaOpacity(panel, 1d - panel.Opacity, 60)
|
||||
}, "PageDownloadInstall Visibility " + panel.Name);
|
||||
else
|
||||
ModAnimation.AniStart(
|
||||
new[]
|
||||
{
|
||||
ModAnimation.AaTranslateY(panel, 6d - ((TranslateTransform)panel.RenderTransform).Y, 60),
|
||||
ModAnimation.AaOpacity(panel, -panel.Opacity, 60)
|
||||
}, "PageDownloadInstall Visibility " + panel.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取实例图标。
|
||||
/// </summary>
|
||||
private string GetSelectLogo()
|
||||
{
|
||||
if (selectedFabric is not null) return "pack://application:,,,/images/Blocks/Fabric.png";
|
||||
|
||||
if (selectedLegacyFabric is not null) return "pack://application:,,,/images/Blocks/Fabric.png";
|
||||
|
||||
if (selectedForge is not null) return "pack://application:,,,/images/Blocks/Anvil.png";
|
||||
|
||||
if (selectedNeoForge is not null) return "pack://application:,,,/images/Blocks/NeoForge.png";
|
||||
|
||||
if (selectedLiteLoader is not null) return "pack://application:,,,/images/Blocks/Egg.png";
|
||||
|
||||
if (selectedOptiFine is not null) return "pack://application:,,,/images/Blocks/GrassPath.png";
|
||||
|
||||
if (selectedCleanroom is not null) return "pack://application:,,,/images/Blocks/Cleanroom.png";
|
||||
|
||||
if (selectedLabyModVersion is not null) return "pack://application:,,,/images/Blocks/LabyMod.png";
|
||||
|
||||
return _vanillaIcon;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取实例描述信息。
|
||||
/// </summary>
|
||||
private string GetSelectInfo()
|
||||
{
|
||||
var parts = new List<string>
|
||||
{
|
||||
_vanillaName
|
||||
};
|
||||
|
||||
var loaderInfos = new (string NameKey, string? Version)[]
|
||||
{
|
||||
("Common.Installation.Fabric", selectedFabric?.Replace("+build", "")),
|
||||
("Common.Installation.LegacyFabric", selectedLegacyFabric),
|
||||
("Common.Installation.Forge", selectedForge?.VersionName),
|
||||
("Common.Installation.NeoForge",
|
||||
selectedNeoForge?.VersionName ?? VersionOrNull(selectedNeoForgeVersion)),
|
||||
("Common.Installation.Cleanroom",
|
||||
selectedCleanroom?.VersionName ?? VersionOrNull(selectedCleanroomVersion)),
|
||||
("Common.Installation.LabyMod", selectedLabyModVersion),
|
||||
("Common.Installation.OptiFine",
|
||||
selectedOptiFine?.DisplayName.Replace(_vanillaName + " ", ""))
|
||||
};
|
||||
|
||||
parts.AddRange(
|
||||
loaderInfos
|
||||
.Where(info => !string.IsNullOrWhiteSpace(info.Version))
|
||||
.Select(info => $"{Lang.Text(info.NameKey)} {info.Version}")
|
||||
);
|
||||
|
||||
if (selectedLiteLoader is not null) parts.Add(Lang.Text("Common.Installation.LiteLoader"));
|
||||
|
||||
if (parts.Count == 1) parts.Add(Lang.Text("Instance.Install.NoExtraInstall"));
|
||||
|
||||
return string.Join(" | ", parts);
|
||||
}
|
||||
|
||||
private static string? VersionOrNull<T>(T version)
|
||||
{
|
||||
return EqualityComparer<T>.Default.Equals(version, default!)
|
||||
? null
|
||||
: version?.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 当前信息获取
|
||||
|
||||
private ModComp.CompFile _currentFabricApi; // 加载完成后直接调用以提高性能
|
||||
private string _currentFabricApiPath;
|
||||
|
||||
private object GetCurrentFabricApi() // 进入页面和联网加载时调用
|
||||
{
|
||||
var loaderOutput = ModDownload.dlFabricApiLoader.output;
|
||||
if (loaderOutput is null)
|
||||
return null; // 确保联网信息已加载
|
||||
var localComp = ModLocalComp.GetModLocalCompByKeywords(PageInstanceLeft.McInstance,
|
||||
new[] { "fabric-api", "fabric" }, "fabric", "api");
|
||||
if (localComp is null)
|
||||
return null;
|
||||
var result = loaderOutput.FirstOrDefault(comp => (comp.Hash ?? "") == (localComp.ModrinthHash ?? ""));
|
||||
if (result is not null)
|
||||
{
|
||||
_currentFabricApi = result;
|
||||
_currentFabricApiPath = localComp.path;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private ModComp.CompFile _currentLegacyFabricApi; // 加载完成后直接调用以提高性能
|
||||
private string _currentLegacyFabricApiPath;
|
||||
|
||||
private object GetCurrentLegacyFabricApi() // 进入页面和联网加载时调用
|
||||
{
|
||||
var loaderOutput = ModDownload.dlLegacyFabricApiLoader.output;
|
||||
if (loaderOutput is null)
|
||||
return null; // 确保联网信息已加载
|
||||
var localComp = ModLocalComp.GetModLocalCompByKeywords(PageInstanceLeft.McInstance,
|
||||
new[] { "legacy-fabric-api", "legacy-fabric" }, "legacy-fabric", "api");
|
||||
if (localComp is null)
|
||||
return null;
|
||||
var result = loaderOutput.FirstOrDefault(comp => (comp.Hash ?? "") == (localComp.ModrinthHash ?? ""));
|
||||
if (result is not null)
|
||||
{
|
||||
_currentLegacyFabricApi = result;
|
||||
_currentLegacyFabricApiPath = localComp.path;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private ModComp.CompFile _currentOptiFabric;
|
||||
private string _currentOptiFabricPath;
|
||||
|
||||
private object GetCurrentOptiFabric()
|
||||
{
|
||||
var loaderOutput = ModDownload.dlOptiFabricLoader.output;
|
||||
if (loaderOutput is null)
|
||||
return null;
|
||||
var localComp =
|
||||
ModLocalComp.GetModLocalCompByKeywords(PageInstanceLeft.McInstance, "optifabric", "optifabric", "opti");
|
||||
if (localComp is null)
|
||||
return null;
|
||||
var result = loaderOutput.FirstOrDefault(comp => (comp.Hash ?? "") == (localComp.ModrinthHash ?? ""));
|
||||
if (result is not null)
|
||||
{
|
||||
_currentOptiFabric = result;
|
||||
_currentOptiFabricPath = localComp.path;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 当前信息获取
|
||||
public void GetCurrentInfo()
|
||||
{
|
||||
ClearSelected();
|
||||
BtnSelectStart.IsEnabled = true;
|
||||
var currentInstance = PageInstanceLeft.McInstance.Info;
|
||||
_vanillaName = currentInstance.VanillaName;
|
||||
if (currentInstance.HasLiteLoader)
|
||||
selectedLiteLoader = new ModDownload.DlLiteLoaderListEntry { Inherit = currentInstance.VanillaName };
|
||||
if (currentInstance.HasOptiFine)
|
||||
selectedOptiFine = new ModDownload.DlOptiFineListEntry
|
||||
{
|
||||
DisplayName = currentInstance.VanillaName + " " + currentInstance.OptiFine.Replace("_", " "),
|
||||
IsPreview = currentInstance.OptiFine.ContainsF("pre"), Inherit = currentInstance.VanillaName,
|
||||
NameVersion = currentInstance.VanillaName + "-OptiFine_HD_U_" + currentInstance.OptiFine
|
||||
};
|
||||
if (currentInstance.HasCleanroom)
|
||||
{
|
||||
selectedLoaderName = "Cleanroom";
|
||||
selectedAPIName = "Cleanroom";
|
||||
selectedCleanroomVersion = currentInstance.Cleanroom;
|
||||
selectedCleanroom = new ModDownload.DlCleanroomListEntry(selectedCleanroomVersion);
|
||||
}
|
||||
else if (currentInstance.HasForge)
|
||||
{
|
||||
selectedLoaderName = "Forge";
|
||||
selectedForge =
|
||||
new ModDownload.DlForgeVersionEntry(currentInstance.Forge, null, currentInstance.VanillaName)
|
||||
{
|
||||
Category = "installer", forgeType = ModDownload.DlForgelikeEntry.ForgelikeType.Forge,
|
||||
Inherit = currentInstance.VanillaName
|
||||
};
|
||||
}
|
||||
else if (currentInstance.HasLegacyFabric)
|
||||
{
|
||||
selectedLoaderName = "LegacyFabric";
|
||||
selectedLegacyFabric = currentInstance.LegacyFabric;
|
||||
selectedLegacyFabricApi = (ModComp.CompFile)GetCurrentLegacyFabricApi();
|
||||
}
|
||||
else if (currentInstance.HasFabric)
|
||||
{
|
||||
selectedLoaderName = "Fabric";
|
||||
selectedFabric = currentInstance.Fabric;
|
||||
selectedFabricApi = (ModComp.CompFile)GetCurrentFabricApi();
|
||||
}
|
||||
else if (currentInstance.HasLabyMod)
|
||||
{
|
||||
selectedLoaderName = "LabyMod";
|
||||
selectedLabyModVersion = currentInstance.LabyMod;
|
||||
}
|
||||
else if (currentInstance.HasNeoForge)
|
||||
{
|
||||
selectedLoaderName = "NeoForge";
|
||||
selectedNeoForgeVersion = currentInstance.NeoForge;
|
||||
selectedNeoForge = new ModDownload.DlNeoForgeListEntry(currentInstance.NeoForge)
|
||||
{
|
||||
VersionName = currentInstance.NeoForge, Inherit = currentInstance.VanillaName,
|
||||
forgeType = ModDownload.DlForgelikeEntry.ForgelikeType.NeoForge
|
||||
};
|
||||
}
|
||||
|
||||
if (currentInstance.HasFabric && currentInstance.HasOptiFine)
|
||||
selectedOptiFabric = (ModComp.CompFile)GetCurrentOptiFabric();
|
||||
_vanillaIcon = "pack://application:,,,/images/Blocks/Grass.png"; // TODO: 需要判断 Icon
|
||||
currentInfo = GetSelectInfo();
|
||||
EnterSelectPage();
|
||||
}
|
||||
|
||||
private string currentInfo;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 加载器
|
||||
|
||||
// 结果数据化
|
||||
private static string GetVersionTypeTitle(string key) => key switch
|
||||
{
|
||||
"正式版" => Lang.Text("Download.Version.Type.Release"),
|
||||
"预览版" => Lang.Text("Download.Version.Type.Development"),
|
||||
"远古版" => Lang.Text("Download.Version.Type.BeforeRelease"),
|
||||
"愚人节版" => Lang.Text("Download.Version.Type.AprilFools"),
|
||||
_ => key
|
||||
};
|
||||
|
||||
private void LoadMinecraft_OnFinish()
|
||||
{
|
||||
ExitSelectPage(); // 返回
|
||||
do
|
||||
{
|
||||
try
|
||||
{
|
||||
var dict = new Dictionary<string, List<JsonObject>>
|
||||
{
|
||||
{ "正式版", new List<JsonObject>() }, { "预览版", new List<JsonObject>() }, { "远古版", new List<JsonObject>() },
|
||||
{ "愚人节版", new List<JsonObject>() }
|
||||
};
|
||||
var versions = (JsonArray)ModDownload.dlClientListLoader.output.Value["versions"];
|
||||
foreach (JsonObject Version in versions)
|
||||
{
|
||||
// 确定分类
|
||||
var type = Version["type"].ToString();
|
||||
var versionId = Version["id"].ToString().ToLower();
|
||||
switch (type ?? "")
|
||||
{
|
||||
case "release":
|
||||
{
|
||||
type = "正式版";
|
||||
break;
|
||||
}
|
||||
case "snapshot":
|
||||
case "pending":
|
||||
{
|
||||
type = "预览版";
|
||||
// Mojang 误分类
|
||||
if (versionId.StartsWith("1.") && !versionId.Contains("combat") &&
|
||||
!versionId.Contains("rc") && !versionId.Contains("experimental") &&
|
||||
!versionId.Equals("1.2") && !versionId.Contains("pre"))
|
||||
{
|
||||
type = "正式版";
|
||||
Version["type"] = "release";
|
||||
}
|
||||
|
||||
// 愚人节版本
|
||||
switch (Version["id"].ToString().ToLower() ?? "")
|
||||
{
|
||||
case "2point0_blue":
|
||||
case "2point0_red":
|
||||
case "2point0_purple":
|
||||
case "2.0_blue":
|
||||
case "2.0_red":
|
||||
case "2.0_purple":
|
||||
case "2.0":
|
||||
{
|
||||
type = "愚人节版";
|
||||
Version["id"] = Version["id"].ToString().Replace("point", ".");
|
||||
Version["type"] = "special";
|
||||
Version.Add("lore", McVersionClassifier.GetMcFoolName((string)Version["id"]));
|
||||
break;
|
||||
}
|
||||
case "20w14infinite":
|
||||
case "20w14∞":
|
||||
{
|
||||
type = "愚人节版";
|
||||
Version["id"] = "20w14∞";
|
||||
Version["type"] = "special";
|
||||
Version.Add("lore", McVersionClassifier.GetMcFoolName((string)Version["id"]));
|
||||
break;
|
||||
}
|
||||
case "3d shareware v1.34":
|
||||
case "1.rv-pre1":
|
||||
case "15w14a":
|
||||
case var @case when @case == "2.0":
|
||||
case "22w13oneblockatatime":
|
||||
case "23w13a_or_b":
|
||||
case "24w14potato":
|
||||
case "25w14craftmine":
|
||||
case "26w14a":
|
||||
{
|
||||
type = "愚人节版";
|
||||
Version["type"] = "special";
|
||||
Version.Add("lore",
|
||||
McVersionClassifier.GetMcFoolName((string)Version["id"])); // 4/1 自动视作愚人节版
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
var releaseDate = McVersionClassifier.GetReleaseTime(Version).ToUniversalTime().AddHours(2d);
|
||||
if (releaseDate.Month == 4 && releaseDate.Day == 1)
|
||||
{
|
||||
type = "愚人节版";
|
||||
Version["type"] = "special";
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "special":
|
||||
{
|
||||
// 已被处理的愚人节版
|
||||
type = "愚人节版";
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
type = "远古版";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 加入辞典
|
||||
dict[type].Add(Version);
|
||||
}
|
||||
|
||||
// 排序
|
||||
foreach (var Pair in dict.ToList())
|
||||
dict[Pair.Key] = Pair.Value.OrderByDescending(McVersionClassifier.GetReleaseTime).ToList();
|
||||
// 清空当前
|
||||
PanMinecraft.Children.Clear();
|
||||
// 添加最新版本
|
||||
var cardInfo = new MyCard { Title = Lang.Text("Download.Version.Latest.Title"), Margin = new Thickness(0d, 15d, 0d, 15d) };
|
||||
var topestVersions = new List<JsonObject>();
|
||||
var release = (JsonObject)dict["正式版"][0].DeepClone();
|
||||
release["lore"] = Lang.Text("Download.Version.Latest.Release", Lang.Date(release["releaseTime"].ToObject<DateTime>(), "g"));
|
||||
topestVersions.Add(release);
|
||||
if (dict["正式版"][0]["releaseTime"].ToObject<DateTime>() < dict["预览版"][0]["releaseTime"].ToObject<DateTime>())
|
||||
{
|
||||
var snapshot = (JsonObject)dict["预览版"][0].DeepClone();
|
||||
snapshot["lore"] = Lang.Text("Download.Version.Latest.Development", Lang.Date(snapshot["releaseTime"].ToObject<DateTime>(), "g"));
|
||||
topestVersions.Add(snapshot);
|
||||
}
|
||||
|
||||
var panInfo = new StackPanel
|
||||
{
|
||||
Margin = new Thickness(20d, MyCard.SwapedHeight, 18d, 0d),
|
||||
VerticalAlignment = VerticalAlignment.Top, RenderTransform = new TranslateTransform(0d, 0d),
|
||||
Tag = topestVersions
|
||||
};
|
||||
|
||||
void StackInstall(StackPanel stack)
|
||||
{
|
||||
foreach (var item in (IEnumerable)stack.Tag)
|
||||
stack.Children.Add(ModDownloadLib.McDownloadListItem((JsonObject)item,
|
||||
(sender, e) => MinecraftSelected((MyListItem)sender, e), false));
|
||||
}
|
||||
|
||||
;
|
||||
MyCard.StackInstall(ref panInfo, StackInstall);
|
||||
cardInfo.Children.Add(panInfo);
|
||||
PanMinecraft.Children.Insert(0, cardInfo);
|
||||
// 添加其他版本
|
||||
foreach (var Pair in dict)
|
||||
{
|
||||
if (!Pair.Value.Any())
|
||||
continue;
|
||||
// 增加卡片
|
||||
var newCard = new MyCard
|
||||
{ Title = GetVersionTypeTitle(Pair.Key) + " (" + Pair.Value.Count + ")", Margin = new Thickness(0d, 0d, 0d, 15d) };
|
||||
var newStack = new StackPanel
|
||||
{
|
||||
Margin = new Thickness(20d, MyCard.SwapedHeight, 18d, 0d),
|
||||
VerticalAlignment = VerticalAlignment.Top, RenderTransform = new TranslateTransform(0d, 0d),
|
||||
Tag = Pair.Value
|
||||
};
|
||||
newCard.Children.Add(newStack);
|
||||
newCard.SwapControl = newStack;
|
||||
// 不能使用 AddressOf,这导致了 #535,原因完全不明,疑似是编译器 Bug
|
||||
newCard.InstallMethod = StackInstall;
|
||||
newCard.IsSwapped = true;
|
||||
PanMinecraft.Children.Add(newCard);
|
||||
}
|
||||
|
||||
// 自动选择版本
|
||||
if (mcVersionWaitingForSelect is null)
|
||||
break;
|
||||
ModBase.Log("[Download] 自动选择 MC 版本:" + mcVersionWaitingForSelect);
|
||||
foreach (JsonObject Version in versions)
|
||||
{
|
||||
if ((Version["id"].ToString() ?? "") != (mcVersionWaitingForSelect ?? ""))
|
||||
continue;
|
||||
var item = ModDownloadLib.McDownloadListItem(Version, (_, _) => { }, false);
|
||||
MinecraftSelected(item, null);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"可视化安装版本列表出错",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Install.Error.OperationFailed"));
|
||||
}
|
||||
} while (false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当 MC 版本列表加载完时,立即自动选择的版本。用于外部调用。
|
||||
/// </summary>
|
||||
public static string mcVersionWaitingForSelect = null;
|
||||
|
||||
#endregion
|
||||
|
||||
#region OptiFine 列表
|
||||
|
||||
/// <summary>
|
||||
/// 获取 OptiFine 的加载异常信息。若正常则返回 Nothing。
|
||||
/// </summary>
|
||||
private string LoadOptiFineGetError()
|
||||
{
|
||||
if (selectedLoaderName == "NeoForge" || selectedLoaderName == "LabyMod" || selectedLoaderName == "Cleanroom")
|
||||
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
|
||||
if (LoadOptiFine is null || LoadOptiFine.State.LoadingState == MyLoading.MyLoadingState.Run)
|
||||
return Lang.Text("Download.Install.State.Loading");
|
||||
if (LoadOptiFine.State.LoadingState == MyLoading.MyLoadingState.Error)
|
||||
return $"{Lang.Text("Download.Install.State.GetVersionListFailed")}{((ModLoader.LoaderBase)LoadOptiFine.State).Error.Message}";
|
||||
// 检查 Forge 1.13 - 1.14.3:全部不兼容
|
||||
if (selectedLoaderName == "Forge" && McVersionComparer.CompareVersion(_vanillaName, "1.13") >= 0 &&
|
||||
McVersionComparer.CompareVersion("1.14.3", _vanillaName) >= 0) return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
|
||||
// 检查 Fabric 1.20.5+: 全部不兼容
|
||||
if (selectedFabric is not null && McVersionComparer.CompareVersion(_vanillaName, "1.20.4") > 0)
|
||||
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
|
||||
// 检查 Loader
|
||||
if (GetLoaderError(LoadOptiFine) is not null)
|
||||
return GetLoaderError(LoadOptiFine);
|
||||
// 检查 Forge 版本
|
||||
var hasAny = false;
|
||||
var hasRequiredVersion = false;
|
||||
foreach (var OptiFineVersion in ModDownload.dlOptiFineListLoader.output.Value)
|
||||
{
|
||||
if (!OptiFineVersion.DisplayName.StartsWith(_vanillaName + " "))
|
||||
continue; // 不是同一个大版本
|
||||
hasAny = true;
|
||||
if (selectedForge is null)
|
||||
return null; // 未选择 Forge
|
||||
if ((bool)IsOptiFineSuitForForge(OptiFineVersion, selectedForge))
|
||||
return null; // 该版本可用
|
||||
if (OptiFineVersion.RequiredForgeVersion is not null)
|
||||
hasRequiredVersion = true;
|
||||
}
|
||||
|
||||
if (!hasAny) return Lang.Text("Download.Install.State.NoVersion");
|
||||
|
||||
if (hasRequiredVersion) return Lang.Text("Download.Install.Compat.CompatForgeSpecificOnly");
|
||||
|
||||
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
|
||||
}
|
||||
|
||||
// 检查某个 OptiFine 是否与某个 Forge 兼容
|
||||
private object IsOptiFineSuitForForge(ModDownload.DlOptiFineListEntry optiFine,
|
||||
ModDownload.DlForgeVersionEntry forge)
|
||||
{
|
||||
if ((forge.Inherit ?? "") != (optiFine.Inherit ?? ""))
|
||||
return false; // 不是同一个大版本
|
||||
if (optiFine.RequiredForgeVersion is null)
|
||||
return false; // 不兼容 Forge
|
||||
if (string.IsNullOrWhiteSpace(optiFine.RequiredForgeVersion))
|
||||
return true; // #4183
|
||||
if (optiFine.RequiredForgeVersion.Contains(".")) // XX.X.XXX
|
||||
return McVersionComparer.CompareVersion(forge.version.ToString(), optiFine.RequiredForgeVersion) == 0;
|
||||
|
||||
// XXXX
|
||||
return forge.version.Revision == Convert.ToDouble(optiFine.RequiredForgeVersion);
|
||||
}
|
||||
|
||||
// 限制展开
|
||||
private void CardOptiFine_PreviewSwap(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
if (LoadOptiFineGetError() is not null)
|
||||
e.handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试重新可视化 OptiFine 版本列表。
|
||||
/// </summary>
|
||||
private void OptiFine_Loaded()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ModDownload.dlOptiFineListLoader.State != ModBase.LoadState.Finished)
|
||||
return;
|
||||
|
||||
// 获取版本列表
|
||||
var versions = new List<ModDownload.DlOptiFineListEntry>();
|
||||
foreach (var Version in ModDownload.dlOptiFineListLoader.output.Value)
|
||||
{
|
||||
if (selectedForge is not null &&
|
||||
!(bool)IsOptiFineSuitForForge(Version, selectedForge))
|
||||
continue;
|
||||
if (Version.DisplayName.StartsWith(_vanillaName + " "))
|
||||
versions.Add(Version);
|
||||
}
|
||||
|
||||
if (!versions.Any())
|
||||
return;
|
||||
// 排序
|
||||
versions.Sort((left, right) =>
|
||||
{
|
||||
if (!left.IsPreview && right.IsPreview)
|
||||
return true;
|
||||
if (left.IsPreview && !right.IsPreview)
|
||||
return false;
|
||||
return McVersionComparer.CompareVersion(left.DisplayName, right.DisplayName) != 0;
|
||||
});
|
||||
// 可视化
|
||||
PanOptiFine.Children.Clear();
|
||||
foreach (var Version in versions)
|
||||
PanOptiFine.Children.Add(
|
||||
ModDownloadLib.OptiFineDownloadListItem(Version, (a, b) =>
|
||||
this.OptiFine_Selected((dynamic)a, b), false));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"可视化 OptiFine 安装版本列表出错",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Install.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 选择与清除
|
||||
private void OptiFine_Selected(MyListItem sender, EventArgs e)
|
||||
{
|
||||
selectedOptiFine = (ModDownload.DlOptiFineListEntry)sender.Tag;
|
||||
if (selectedForge is not null &&
|
||||
!(bool)IsOptiFineSuitForForge(selectedOptiFine, selectedForge))
|
||||
selectedForge = null;
|
||||
OptiFabric_Loaded();
|
||||
Forge_Loaded();
|
||||
NeoForge_Loaded();
|
||||
CardOptiFine.IsSwapped = true;
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
private void OptiFine_Clear(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
selectedOptiFine = null;
|
||||
selectedOptiFabric = null;
|
||||
autoSelectedOptiFabric = false;
|
||||
CardOptiFine.IsSwapped = true;
|
||||
e.Handled = true;
|
||||
Forge_Loaded();
|
||||
NeoForge_Loaded();
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region LiteLoader 列表
|
||||
|
||||
/// <summary>
|
||||
/// 获取 LiteLoader 的加载异常信息。若正常则返回 Nothing。
|
||||
/// </summary>
|
||||
private string LoadLiteLoaderGetError()
|
||||
{
|
||||
// 检查 Loader
|
||||
if (GetLoaderError(LoadLiteLoader) is not null)
|
||||
return GetLoaderError(LoadLiteLoader);
|
||||
if (selectedLoaderName == "NeoForge" || selectedLoaderName == "LegacyFabric" || selectedLoaderName == "LabyMod" || selectedLoaderName == "Cleanroom")
|
||||
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
|
||||
// 检查版本
|
||||
return ModDownload.dlLiteLoaderListLoader.output.Value.Any(v => (v.Inherit ?? "") == (_vanillaName ?? ""))
|
||||
? null
|
||||
: Lang.Text("Download.Install.State.NoVersion");
|
||||
}
|
||||
|
||||
// 限制展开
|
||||
private void CardLiteLoader_PreviewSwap(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
if (LoadLiteLoaderGetError() is not null)
|
||||
e.handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试重新可视化 LiteLoader 版本列表。
|
||||
/// </summary>
|
||||
private void LiteLoader_Loaded()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ModDownload.dlLiteLoaderListLoader.State != ModBase.LoadState.Finished)
|
||||
return;
|
||||
// 获取版本列表
|
||||
var versions = new List<ModDownload.DlLiteLoaderListEntry>();
|
||||
foreach (var Version in ModDownload.dlLiteLoaderListLoader.output.Value)
|
||||
if ((Version.Inherit ?? "") == (_vanillaName ?? ""))
|
||||
versions.Add(Version);
|
||||
if (!versions.Any())
|
||||
return;
|
||||
// 可视化
|
||||
PanLiteLoader.Children.Clear();
|
||||
foreach (var Version in versions)
|
||||
PanLiteLoader.Children.Add(ModDownloadLib.LiteLoaderDownloadListItem(Version,
|
||||
(a, b) => this.LiteLoader_Selected((dynamic)a, b), false));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"可视化 LiteLoader 安装版本列表出错",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Install.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 选择与清除
|
||||
private void LiteLoader_Selected(MyListItem sender, EventArgs e)
|
||||
{
|
||||
selectedLiteLoader = (ModDownload.DlLiteLoaderListEntry)sender.Tag;
|
||||
CardLiteLoader.IsSwapped = true;
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
private void LiteLoader_Clear(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
selectedLiteLoader = null;
|
||||
CardLiteLoader.IsSwapped = true;
|
||||
e.Handled = true;
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Forge 列表
|
||||
|
||||
/// <summary>
|
||||
/// 获取 Forge 的加载异常信息。若正常则返回 Nothing。
|
||||
/// </summary>
|
||||
private string LoadForgeGetError()
|
||||
{
|
||||
if (McVersionComparer.CompareVersionGe("1.5.1", _vanillaName) && McVersionComparer.CompareVersionGe(_vanillaName, "1.1"))
|
||||
return Lang.Text("Download.Install.State.NoVersion");
|
||||
|
||||
if (selectedLoaderName is not null && !ReferenceEquals(selectedLoaderName, "Forge"))
|
||||
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
|
||||
|
||||
// 检查 Loader
|
||||
if (GetLoaderError(LoadForge) is not null)
|
||||
return GetLoaderError(LoadForge);
|
||||
var loader = (ModLoader.LoaderTask<string, List<ModDownload.DlForgeVersionEntry>>)LoadForge.State;
|
||||
if ((_vanillaName ?? "") != (loader.input ?? ""))
|
||||
return Lang.Text("Download.Install.State.Getting");
|
||||
// 检查版本
|
||||
foreach (var Version in loader.output)
|
||||
{
|
||||
if (Version.Category == "universal" || Version.Category == "client")
|
||||
continue; // 跳过无法自动安装的版本
|
||||
if (selectedLoaderName is not null && selectedLoaderName != "Forge")
|
||||
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
|
||||
if (selectedOptiFine is not null && McVersionComparer.CompareVersionGe(_vanillaName, "1.13") &&
|
||||
McVersionComparer.CompareVersionGe("1.14.3", _vanillaName))
|
||||
return Lang.Text("Download.Install.Compat.IncompatibleWithOptiFine"); // 1.13 ~ 1.14.3 OptiFine 检查
|
||||
if (selectedOptiFine is not null && !(bool)IsOptiFineSuitForForge(selectedOptiFine, Version))
|
||||
continue;
|
||||
return null;
|
||||
}
|
||||
|
||||
return Lang.Text("Download.Install.Compat.IncompatibleWithOptiFine");
|
||||
}
|
||||
|
||||
// 限制展开
|
||||
private void CardForge_PreviewSwap(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
if (LoadForgeGetError() is not null)
|
||||
e.handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试重新可视化 Forge 版本列表。
|
||||
/// </summary>
|
||||
private void Forge_Loaded()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!LoadForge.State.IsLoader)
|
||||
return;
|
||||
var loader = (ModLoader.LoaderTask<string, List<ModDownload.DlForgeVersionEntry>>)LoadForge.State;
|
||||
if ((_vanillaName ?? "") != (loader.input ?? ""))
|
||||
return;
|
||||
if (loader.State != ModBase.LoadState.Finished)
|
||||
return;
|
||||
// 获取要显示的版本
|
||||
var versions = loader.output.ToList(); // 复制数组,以免 Output 在实例化后变空
|
||||
if (!loader.output.Any())
|
||||
return;
|
||||
PanForge.Children.Clear();
|
||||
versions = versions.Where(v =>
|
||||
{
|
||||
if (v.Category == "universal" || v.Category == "client")
|
||||
return false; // 跳过无法自动安装的版本
|
||||
if (selectedOptiFine is not null &&
|
||||
!(bool)IsOptiFineSuitForForge(selectedOptiFine, v))
|
||||
return false;
|
||||
return true;
|
||||
}).OrderByDescending(v => v).ToList();
|
||||
ModDownloadLib.ForgeDownloadListItemPreload(PanForge, versions,
|
||||
(a, b) => this.Forge_Selected((dynamic)a, b), false);
|
||||
foreach (var Version in versions)
|
||||
PanForge.Children.Add(
|
||||
ModDownloadLib.ForgeDownloadListItem(Version, (a, b) => this.Forge_Selected((dynamic)a, b), false));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"可视化 Forge 安装版本列表出错",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Install.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 选择与清除
|
||||
private void Forge_Selected(MyListItem sender, EventArgs e)
|
||||
{
|
||||
selectedForge = (ModDownload.DlForgeVersionEntry)sender.Tag;
|
||||
selectedLoaderName = "Forge";
|
||||
CardForge.IsSwapped = true;
|
||||
if (selectedOptiFine is not null &&
|
||||
!(bool)IsOptiFineSuitForForge(selectedOptiFine, selectedForge))
|
||||
selectedOptiFine = null;
|
||||
OptiFine_Loaded();
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
private void Forge_Clear(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
selectedForge = null;
|
||||
selectedLoaderName = null;
|
||||
CardForge.IsSwapped = true;
|
||||
e.Handled = true;
|
||||
OptiFine_Loaded();
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region NeoForge 列表
|
||||
|
||||
/// <summary>
|
||||
/// 获取 NeoForge 的加载异常信息。若正常则返回 Nothing。
|
||||
/// </summary>
|
||||
private string LoadNeoForgeGetError()
|
||||
{
|
||||
if (selectedOptiFine is not null)
|
||||
return Lang.Text("Download.Install.Compat.IncompatibleWithOptiFine");
|
||||
if (selectedLoaderName is not null && !ReferenceEquals(selectedLoaderName, "NeoForge"))
|
||||
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
|
||||
// 检查 Loader
|
||||
if (GetLoaderError(LoadNeoForge) is not null)
|
||||
return GetLoaderError(LoadNeoForge);
|
||||
// 检查版本
|
||||
return ModDownload.dlNeoForgeListLoader.output.Value.Any(v => (v.Inherit ?? "") == (_vanillaName ?? ""))
|
||||
? null
|
||||
: Lang.Text("Download.Install.State.NoVersion");
|
||||
}
|
||||
|
||||
// 限制展开
|
||||
private void CardNeoForge_PreviewSwap(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
if (LoadNeoForgeGetError() is not null)
|
||||
e.handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试重新可视化 NeoForge 版本列表。
|
||||
/// </summary>
|
||||
private void NeoForge_Loaded()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取版本列表
|
||||
if (ModDownload.dlNeoForgeListLoader.State != ModBase.LoadState.Finished)
|
||||
return;
|
||||
var versions = ModDownload.dlNeoForgeListLoader.output.Value
|
||||
.Where(v => (v.Inherit ?? "") == (_vanillaName ?? "")).ToList();
|
||||
if (!versions.Any())
|
||||
return;
|
||||
// 可视化
|
||||
PanNeoForge.Children.Clear();
|
||||
ModDownloadLib.NeoForgeDownloadListItemPreload(PanNeoForge, versions,
|
||||
(a, b) => this.NeoForge_Selected((dynamic)a, b),
|
||||
false);
|
||||
foreach (var Version in versions)
|
||||
PanNeoForge.Children.Add(
|
||||
ModDownloadLib.NeoForgeDownloadListItem(Version, (a, b) => this.NeoForge_Selected((dynamic)a, b),
|
||||
false));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"可视化 NeoForge 安装版本列表出错",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Install.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 选择与清除
|
||||
private void NeoForge_Selected(MyListItem sender, EventArgs e)
|
||||
{
|
||||
selectedNeoForge = (ModDownload.DlNeoForgeListEntry)sender.Tag;
|
||||
selectedLoaderName = "NeoForge";
|
||||
CardNeoForge.IsSwapped = true;
|
||||
OptiFine_Loaded();
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
private void NeoForge_Clear(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
selectedNeoForge = null;
|
||||
selectedLoaderName = null;
|
||||
CardNeoForge.IsSwapped = true;
|
||||
e.Handled = true;
|
||||
OptiFine_Loaded();
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cleanroom 列表
|
||||
|
||||
/// <summary>
|
||||
/// 获取 Cleanroom 的加载异常信息。若正常则返回 Nothing。
|
||||
/// </summary>
|
||||
private string LoadCleanroomGetError()
|
||||
{
|
||||
if (!_vanillaName.StartsWith("1."))
|
||||
return Lang.Text("Download.Install.State.NoAvailableVersion");
|
||||
if (selectedOptiFine is not null)
|
||||
return Lang.Text("Download.Install.Compat.IncompatibleWithOptiFine");
|
||||
if (selectedLoaderName is not null && selectedLoaderName != "Cleanroom")
|
||||
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
|
||||
if (selectedLiteLoader is not null)
|
||||
return Lang.Text("Download.Install.Compat.IncompatibleWithLiteLoader");
|
||||
// 检查 Loader
|
||||
if (GetLoaderError(LoadCleanroom) is not null)
|
||||
return GetLoaderError(LoadCleanroom);
|
||||
// 检查版本
|
||||
return ModDownload.dlCleanroomListLoader.output.Value.Any(v => (v.Inherit ?? "") == (_vanillaName ?? ""))
|
||||
? null
|
||||
: Lang.Text("Download.Install.State.NoVersion");
|
||||
}
|
||||
|
||||
// 限制展开
|
||||
private void CardCleanroom_PreviewSwap(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
if (LoadCleanroomGetError() is not null)
|
||||
e.handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试重新可视化 Cleanroom 版本列表。
|
||||
/// </summary>
|
||||
private void Cleanroom_Loaded()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取版本列表
|
||||
if (ModDownload.dlCleanroomListLoader.State != ModBase.LoadState.Finished)
|
||||
return;
|
||||
var versions = ModDownload.dlCleanroomListLoader.output.Value
|
||||
.Where(v => (v.Inherit ?? "") == (_vanillaName ?? "")).ToList();
|
||||
if (!versions.Any())
|
||||
return;
|
||||
// 可视化
|
||||
PanCleanroom.Children.Clear();
|
||||
ModDownloadLib.CleanroomDownloadListItemPreload(PanCleanroom, versions,
|
||||
(a, b) => this.Cleanroom_Selected((dynamic)a, b), false);
|
||||
foreach (var Version in versions)
|
||||
PanCleanroom.Children.Add(
|
||||
ModDownloadLib.CleanroomDownloadListItem(Version, (a, b) => this.Cleanroom_Selected((dynamic)a, b),
|
||||
false));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"可视化 Cleanroom 安装版本列表出错",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Install.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 选择与清除
|
||||
private void Cleanroom_Selected(MyListItem sender, EventArgs e)
|
||||
{
|
||||
selectedCleanroom = (ModDownload.DlCleanroomListEntry)sender.Tag;
|
||||
selectedLoaderName = "Cleanroom";
|
||||
CardCleanroom.IsSwapped = true;
|
||||
OptiFine_Loaded();
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
private void Cleanroom_Clear(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
selectedCleanroom = null;
|
||||
selectedCleanroomVersion = null;
|
||||
selectedLoaderName = null;
|
||||
selectedAPIName = null;
|
||||
CardCleanroom.IsSwapped = true;
|
||||
e.Handled = true;
|
||||
OptiFine_Loaded();
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fabric 列表
|
||||
|
||||
/// <summary>
|
||||
/// 获取 Fabric 的加载异常信息。若正常则返回 Nothing。
|
||||
/// </summary>
|
||||
private string LoadFabricGetError()
|
||||
{
|
||||
// 检查 OptiFine 1.20.5+:没有 OptiFabric 故全部不兼容
|
||||
if (selectedOptiFine is not null && McVersionComparer.CompareVersionGe(_vanillaName, "1.20.5"))
|
||||
return Lang.Text("Download.Install.Compat.IncompatibleWithOptiFine");
|
||||
// 检查 Loader
|
||||
if (GetLoaderError(LoadFabric) is not null)
|
||||
return GetLoaderError(LoadFabric);
|
||||
// 检查版本
|
||||
foreach (JsonObject version in ModDownload.dlFabricListLoader.output.Value["game"].AsArray())
|
||||
if ((version["version"].ToString() ?? "") ==
|
||||
(_vanillaName.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3") ?? ""))
|
||||
{
|
||||
if (selectedLoaderName is not null && !ReferenceEquals(selectedLoaderName, "Fabric"))
|
||||
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
|
||||
return null;
|
||||
}
|
||||
|
||||
return Lang.Text("Download.Install.State.NoVersion");
|
||||
}
|
||||
|
||||
// 限制展开
|
||||
private void CardFabric_PreviewSwap(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
if (LoadFabricGetError() is not null)
|
||||
e.handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试重新可视化 Fabric 版本列表。
|
||||
/// </summary>
|
||||
private void Fabric_Loaded()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ModDownload.dlFabricListLoader.State != ModBase.LoadState.Finished)
|
||||
return;
|
||||
// 获取版本列表
|
||||
var versions = (JsonArray)ModDownload.dlFabricListLoader.output.Value["loader"];
|
||||
if (!versions.Any())
|
||||
return;
|
||||
// 可视化
|
||||
PanFabric.Children.Clear();
|
||||
PanFabric.Tag = versions;
|
||||
CardFabric.SwapControl = PanFabric;
|
||||
CardFabric.InstallMethod = stack =>
|
||||
{
|
||||
foreach (var item in (IEnumerable)stack.Tag)
|
||||
stack.Children.Add(
|
||||
ModDownloadLib.FabricDownloadListItem((JsonObject)item,
|
||||
(a, b) => this.Fabric_Selected((dynamic)a, b)));
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"可视化 Fabric 安装版本列表出错",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Install.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 选择与清除
|
||||
public void Fabric_Selected(MyListItem sender, EventArgs e)
|
||||
{
|
||||
selectedFabric = ((dynamic)sender.Tag)["version"].ToString();
|
||||
selectedLoaderName = "Fabric";
|
||||
FabricApi_Loaded();
|
||||
OptiFabric_Loaded();
|
||||
CardFabric.IsSwapped = true;
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
private void Fabric_Clear(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
selectedFabric = null;
|
||||
selectedFabricApi = null;
|
||||
autoSelectedFabricApi = false;
|
||||
selectedOptiFabric = null;
|
||||
autoSelectedOptiFabric = false;
|
||||
selectedLoaderName = null;
|
||||
selectedAPIName = null;
|
||||
CardFabric.IsSwapped = true;
|
||||
e.Handled = true;
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fabric API 列表
|
||||
|
||||
/// <summary>
|
||||
/// 判断某 Fabric API 是否适配当前选择的原版版本。
|
||||
/// </summary>
|
||||
public bool IsFabricApiCompatible(ModComp.CompFile fabricApi)
|
||||
{
|
||||
var fabricApiName = fabricApi.DisplayName;
|
||||
try
|
||||
{
|
||||
if (fabricApiName is null || _vanillaName is null)
|
||||
return false;
|
||||
fabricApiName = fabricApiName.ToLower();
|
||||
_vanillaName = _vanillaName.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3").ToLower();
|
||||
if (fabricApiName.StartsWith("[" + _vanillaName + "]"))
|
||||
return true;
|
||||
if (!fabricApiName.Contains("/") || !fabricApiName.Contains("]"))
|
||||
return false;
|
||||
// 直接的判断(例如 1.18.1/22w03a)
|
||||
foreach (var part in fabricApiName.BeforeFirst("]").TrimStart('[').Split("/"))
|
||||
if ((part ?? "") == (_vanillaName ?? ""))
|
||||
return true;
|
||||
// 将版本名分割语素(例如 1.16.4/5)
|
||||
var lefts = fabricApiName.BeforeFirst("]").RegexSearch("[a-z/]+|[0-9/]+");
|
||||
var rights = _vanillaName.BeforeFirst("]").RegexSearch("[a-z/]+|[0-9/]+");
|
||||
// 对每段进行判断
|
||||
var i = 0;
|
||||
while (true)
|
||||
{
|
||||
// 两边均缺失,感觉是一个东西
|
||||
if (lefts.Count - 1 < i && rights.Count - 1 < i)
|
||||
return true;
|
||||
// 确定两边是否一致
|
||||
var leftValue = lefts.Count - 1 < i ? "-1" : lefts[i];
|
||||
var rightValue = rights.Count - 1 < i ? "-1" : rights[i];
|
||||
if (!leftValue.Contains("/"))
|
||||
{
|
||||
if ((leftValue ?? "") != (rightValue ?? ""))
|
||||
return false;
|
||||
}
|
||||
// 左边存在斜杠
|
||||
else if (!leftValue.Contains(rightValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "判断 Fabric API 版本适配性出错(" + fabricApiName + ", " + _vanillaName + ")");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 FabricApi 的加载异常信息。若正常则返回 Nothing。
|
||||
/// </summary>
|
||||
private string LoadFabricApiGetError()
|
||||
{
|
||||
// 检查 Loader
|
||||
if (GetLoaderError(LoadFabricApi) is not null)
|
||||
return GetLoaderError(LoadFabricApi);
|
||||
if (ModDownload.dlFabricApiLoader.output is null)
|
||||
return selectedFabric is null ? Lang.Text("Download.Install.Compat.RequiresFabric") : Lang.Text("Download.Install.State.Getting");
|
||||
// 检查版本
|
||||
if (ModDownload.dlFabricApiLoader.output.Any(f => IsFabricApiCompatible(f)))
|
||||
return selectedFabric is null ? Lang.Text("Download.Install.Compat.RequiresFabric") : null;
|
||||
|
||||
return Lang.Text("Download.Install.State.NoVersion");
|
||||
}
|
||||
|
||||
// 限制展开
|
||||
private void CardFabricApi_PreviewSwap(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
if (LoadFabricApiGetError() is not null)
|
||||
e.handled = true;
|
||||
}
|
||||
|
||||
private bool autoSelectedFabricApi;
|
||||
|
||||
/// <summary>
|
||||
/// 尝试重新可视化 FabricApi 版本列表。
|
||||
/// </summary>
|
||||
private void FabricApi_Loaded()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ModDownload.dlFabricApiLoader.State != ModBase.LoadState.Finished)
|
||||
return;
|
||||
if (_vanillaName is null || selectedFabric is null)
|
||||
return;
|
||||
// 获取版本列表
|
||||
var versions = new List<ModComp.CompFile>();
|
||||
foreach (var version in ModDownload.dlFabricApiLoader.output)
|
||||
if (IsFabricApiCompatible(version))
|
||||
{
|
||||
if (!version.DisplayName.StartsWith("["))
|
||||
{
|
||||
ModBase.Log("[Download] 已特判修改 Fabric API 显示名:" + version.DisplayName, ModBase.LogLevel.Debug);
|
||||
version.DisplayName = "[" + _vanillaName + "] " + version.DisplayName;
|
||||
}
|
||||
|
||||
versions.Add(version);
|
||||
}
|
||||
|
||||
if (!versions.Any())
|
||||
return;
|
||||
versions = versions.OrderByDescending(v => v.ReleaseDate).ToList();
|
||||
// 可视化
|
||||
PanFabricApi.Children.Clear();
|
||||
foreach (var version in versions)
|
||||
{
|
||||
if (!IsFabricApiCompatible(version))
|
||||
continue;
|
||||
PanFabricApi.Children.Add(
|
||||
ModDownloadLib.FabricApiDownloadListItem(version,
|
||||
(a, b) => this.FabricApi_Selected((dynamic)a, b)));
|
||||
}
|
||||
|
||||
// 自动选择 Fabric API
|
||||
if (!autoSelectedFabricApi)
|
||||
{
|
||||
autoSelectedFabricApi = true;
|
||||
ModBase.Log($"[Download] 已自动选择 Fabric API:{((MyListItem)PanFabricApi.Children[0]).Title}");
|
||||
FabricApi_Selected((MyListItem)PanFabricApi.Children[0], null);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"可视化 Fabric API 安装版本列表出错",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Install.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 选择与清除
|
||||
private void FabricApi_Selected(MyListItem sender, EventArgs e)
|
||||
{
|
||||
selectedFabricApi = (ModComp.CompFile)sender.Tag;
|
||||
selectedAPIName = "Fabric API";
|
||||
CardFabricApi.IsSwapped = true;
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
private void FabricApi_Clear(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
selectedFabricApi = null;
|
||||
selectedAPIName = null;
|
||||
CardFabricApi.IsSwapped = true;
|
||||
e.Handled = true;
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region LegacyFabric 列表
|
||||
|
||||
/// <summary>
|
||||
/// 获取 LegacyFabric 的加载异常信息。若正常则返回 Nothing。
|
||||
/// </summary>
|
||||
private string LoadLegacyFabricGetError()
|
||||
{
|
||||
if (LoadLegacyFabric is null || LoadLegacyFabric.State.LoadingState == MyLoading.MyLoadingState.Run)
|
||||
return Lang.Text("Download.Install.State.Loading");
|
||||
if (LoadLegacyFabric.State.LoadingState == MyLoading.MyLoadingState.Error)
|
||||
return Lang.Text("Download.Install.State.GetVersionListFailed", ((ModLoader.LoaderBase)LoadLegacyFabric.State).Error.Message);
|
||||
foreach (JsonObject Version in ModDownload.dlLegacyFabricListLoader.output.Value["game"].AsArray())
|
||||
if ((Version["version"].ToString() ?? "") == (_vanillaName ?? ""))
|
||||
{
|
||||
if (selectedLiteLoader is not null)
|
||||
return Lang.Text("Download.Install.Compat.IncompatibleWithLiteLoader");
|
||||
if (selectedLoaderName is not null && !ReferenceEquals(selectedLoaderName, "LegacyFabric"))
|
||||
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
|
||||
return null;
|
||||
}
|
||||
|
||||
return Lang.Text("Download.Install.State.NoVersion");
|
||||
}
|
||||
|
||||
// 限制展开
|
||||
private void CardLegacyFabric_PreviewSwap(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
if (LoadLegacyFabricGetError() is not null)
|
||||
e.handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试重新可视化 LegacyFabric 版本列表。
|
||||
/// </summary>
|
||||
private void LegacyFabric_Loaded()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ModDownload.dlLegacyFabricListLoader.State != ModBase.LoadState.Finished)
|
||||
return;
|
||||
// 获取版本列表
|
||||
var versions = (JsonArray)ModDownload.dlLegacyFabricListLoader.output.Value["loader"];
|
||||
if (!versions.Any())
|
||||
return;
|
||||
// 可视化
|
||||
PanLegacyFabric.Children.Clear();
|
||||
PanLegacyFabric.Tag = versions;
|
||||
CardLegacyFabric.SwapControl = PanLegacyFabric;
|
||||
CardLegacyFabric.InstallMethod = stack =>
|
||||
{
|
||||
foreach (var item in (IEnumerable)stack.Tag)
|
||||
stack.Children.Add(ModDownloadLib.LegacyFabricDownloadListItem((JsonObject)item,
|
||||
(a, b) => this.LegacyFabric_Selected((dynamic)a, b)));
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"可视化 LegacyFabric 安装版本列表出错",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Install.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 选择与清除
|
||||
public void LegacyFabric_Selected(MyListItem sender, EventArgs e)
|
||||
{
|
||||
selectedLegacyFabric = ((dynamic)sender.Tag)["version"].ToString();
|
||||
selectedLoaderName = "LegacyFabric";
|
||||
LegacyFabricApi_Loaded();
|
||||
CardLegacyFabric.IsSwapped = true;
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
private void LegacyFabric_Clear(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
selectedLegacyFabric = null;
|
||||
selectedLegacyFabricApi = null;
|
||||
autoSelectedLegacyFabricApi = false;
|
||||
selectedLoaderName = null;
|
||||
selectedAPIName = null;
|
||||
CardLegacyFabric.IsSwapped = true;
|
||||
e.Handled = true;
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Legacy Fabric API 列表
|
||||
|
||||
/// <summary>
|
||||
/// 从显示名判断该 API 是否与某版本适配。
|
||||
/// </summary>
|
||||
public static bool IsSuitableLegacyFabricApi(List<string> supportVersions, string minecraftVersion)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (supportVersions.Contains(minecraftVersion)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "判断 Legacy Fabric API 版本适配性出错(" + supportVersions + ", " + minecraftVersion + ")");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 LegacyFabricApi 的加载异常信息。若正常则返回 Nothing。
|
||||
/// </summary>
|
||||
private string LoadLegacyFabricApiGetError()
|
||||
{
|
||||
if (LoadLegacyFabricApi is null || LoadLegacyFabricApi.State.LoadingState == MyLoading.MyLoadingState.Run)
|
||||
return Lang.Text("Download.Install.State.Loading");
|
||||
if (LoadLegacyFabricApi.State.LoadingState == MyLoading.MyLoadingState.Error)
|
||||
return Lang.Text("Download.Install.State.GetVersionListFailed", ((ModLoader.LoaderBase)LoadLegacyFabricApi.State).Error.Message);
|
||||
if (selectedAPIName is not null && !ReferenceEquals(selectedAPIName, "Legacy Fabric API"))
|
||||
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedAPIName);
|
||||
if (ModDownload.dlLegacyFabricApiLoader.output is null)
|
||||
{
|
||||
if (selectedLegacyFabric is null)
|
||||
return Lang.Text("Download.Install.Compat.RequiresLegacyFabric");
|
||||
return Lang.Text("Download.Install.State.Loading");
|
||||
}
|
||||
|
||||
foreach (var Version in ModDownload.dlLegacyFabricApiLoader.output)
|
||||
{
|
||||
if (!IsSuitableLegacyFabricApi(Version.GameVersions, _vanillaName))
|
||||
continue;
|
||||
if (selectedLegacyFabric is null)
|
||||
return Lang.Text("Download.Install.Compat.RequiresLegacyFabric");
|
||||
return null;
|
||||
}
|
||||
|
||||
return Lang.Text("Download.Install.State.NoVersion");
|
||||
}
|
||||
|
||||
// 限制展开
|
||||
private void CardLegacyFabricApi_PreviewSwap(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
if (LoadLegacyFabricApiGetError() is not null)
|
||||
e.handled = true;
|
||||
}
|
||||
|
||||
private bool autoSelectedLegacyFabricApi;
|
||||
|
||||
/// <summary>
|
||||
/// 尝试重新可视化 LegacyFabricApi 版本列表。
|
||||
/// </summary>
|
||||
private void LegacyFabricApi_Loaded()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ModDownload.dlLegacyFabricApiLoader.State != ModBase.LoadState.Finished)
|
||||
return;
|
||||
if (_vanillaName is null || selectedLegacyFabric is null)
|
||||
return;
|
||||
// 获取版本列表
|
||||
var versions = new List<ModComp.CompFile>();
|
||||
foreach (var Version in ModDownload.dlLegacyFabricApiLoader.output)
|
||||
if (IsSuitableLegacyFabricApi(Version.GameVersions, _vanillaName))
|
||||
versions.Add(Version);
|
||||
|
||||
if (!versions.Any())
|
||||
return;
|
||||
versions = versions.OrderByDescending(v => v.ReleaseDate).ToList();
|
||||
// 可视化
|
||||
PanLegacyFabricApi.Children.Clear();
|
||||
foreach (var Version in versions)
|
||||
{
|
||||
if (!IsSuitableLegacyFabricApi(Version.GameVersions, _vanillaName))
|
||||
continue;
|
||||
PanLegacyFabricApi.Children.Add(
|
||||
ModDownloadLib.LegacyFabricApiDownloadListItem(Version,
|
||||
(a, b) => this.LegacyFabricApi_Selected((dynamic)a, b)));
|
||||
}
|
||||
|
||||
// 自动选择 Legacy Fabric API
|
||||
if (!autoSelectedLegacyFabricApi)
|
||||
{
|
||||
autoSelectedLegacyFabricApi = true;
|
||||
ModBase.Log($"[Download] 已自动选择 Legacy Fabric API:{((MyListItem)PanLegacyFabricApi.Children[0]).Title}");
|
||||
LegacyFabricApi_Selected((MyListItem)PanLegacyFabricApi.Children[0], null);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"可视化 Legacy Fabric API 安装版本列表出错",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Install.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 选择与清除
|
||||
private void LegacyFabricApi_Selected(MyListItem sender, EventArgs e)
|
||||
{
|
||||
selectedLegacyFabricApi = (ModComp.CompFile)sender.Tag;
|
||||
selectedAPIName = "Legacy Fabric API";
|
||||
CardLegacyFabricApi.IsSwapped = true;
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
private void LegacyFabricApi_Clear(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
selectedLegacyFabricApi = null;
|
||||
selectedAPIName = null;
|
||||
CardLegacyFabricApi.IsSwapped = true;
|
||||
e.Handled = true;
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region OptiFabric 列表
|
||||
|
||||
/// <summary>
|
||||
/// 判断某 OptiFabric 是否适配当前选择的原版版本。
|
||||
/// </summary>
|
||||
private bool IsOptiFabricCompatible(ModComp.CompFile modFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_vanillaName is null)
|
||||
return false;
|
||||
return modFile.GameVersions.Contains(_vanillaName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "判断 OptiFabric 版本适配性出错(" + _vanillaName + ")");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool autoSelectedOptiFabric;
|
||||
|
||||
/// <summary>
|
||||
/// 获取 OptiFabric 的加载异常信息。若正常则返回 Nothing。
|
||||
/// </summary>
|
||||
private string LoadOptiFabricGetError()
|
||||
{
|
||||
if (VanillaDrop >= 140 && VanillaDrop <= 150)
|
||||
return Lang.Text("Download.Install.Compat.OptiFabricOriginsRequired");
|
||||
// 检查 Loader
|
||||
if (GetLoaderError(LoadOptiFabric) is not null)
|
||||
return GetLoaderError(LoadOptiFabric);
|
||||
// 检查版本
|
||||
if (ModDownload.dlOptiFabricLoader.output is null)
|
||||
{
|
||||
if (selectedFabric is null && selectedOptiFine is null)
|
||||
return Lang.Text("Download.Install.Compat.RequiresOptiFineAndFabric");
|
||||
if (selectedFabric is null)
|
||||
return Lang.Text("Download.Install.Compat.RequiresFabric");
|
||||
if (selectedOptiFine is null)
|
||||
return Lang.Text("Download.Install.Compat.RequiresOptiFine");
|
||||
return Lang.Text("Download.Install.State.Getting");
|
||||
}
|
||||
|
||||
foreach (var version in ModDownload.dlOptiFabricLoader.output)
|
||||
{
|
||||
if (!IsOptiFabricCompatible(version))
|
||||
continue; // 2135#
|
||||
if (selectedFabric is null && selectedOptiFine is null)
|
||||
return Lang.Text("Download.Install.Compat.RequiresOptiFineAndFabric");
|
||||
if (selectedFabric is null)
|
||||
return Lang.Text("Download.Install.Compat.RequiresFabric");
|
||||
if (selectedOptiFine is null)
|
||||
return Lang.Text("Download.Install.Compat.RequiresOptiFine");
|
||||
return null; // 通过检查
|
||||
}
|
||||
|
||||
return Lang.Text("Download.Install.State.NoVersion");
|
||||
}
|
||||
|
||||
// 限制展开
|
||||
private void CardOptiFabric_PreviewSwap(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
if (LoadOptiFabricGetError() is not null)
|
||||
e.handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试重新可视化 OptiFabric 版本列表。
|
||||
/// </summary>
|
||||
private void OptiFabric_Loaded()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ModDownload.dlOptiFabricLoader.State != ModBase.LoadState.Finished)
|
||||
return;
|
||||
if (_vanillaName is null || selectedFabric is null || selectedOptiFine is null)
|
||||
return;
|
||||
// 获取版本列表
|
||||
var versions = new List<ModComp.CompFile>();
|
||||
foreach (var Version in ModDownload.dlOptiFabricLoader.output)
|
||||
if (IsOptiFabricCompatible(Version))
|
||||
versions.Add(Version);
|
||||
if (!versions.Any())
|
||||
return;
|
||||
// 排序
|
||||
versions = versions.OrderByDescending(v => v.ReleaseDate).ToList();
|
||||
// 可视化
|
||||
PanOptiFabric.Children.Clear();
|
||||
foreach (var Version in versions)
|
||||
{
|
||||
if (!IsOptiFabricCompatible(Version))
|
||||
continue;
|
||||
PanOptiFabric.Children.Add(
|
||||
ModDownloadLib.OptiFabricDownloadListItem(Version,
|
||||
(a, b) => this.OptiFabric_Selected((dynamic)a, b)));
|
||||
}
|
||||
|
||||
// 自动选择 OptiFabric
|
||||
if (autoSelectedOptiFabric || (VanillaDrop >= 140 && VanillaDrop <= 150))
|
||||
return; // 1.14~15 不自动选择
|
||||
autoSelectedOptiFabric = true;
|
||||
ModBase.Log($"[Download] 已自动选择 OptiFabric:{((MyListItem)PanOptiFabric.Children[0]).Title}");
|
||||
OptiFabric_Selected((MyListItem)PanOptiFabric.Children[0], null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"可视化 OptiFabric 安装版本列表出错",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Install.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 选择与清除
|
||||
private void OptiFabric_Selected(MyListItem sender, EventArgs e)
|
||||
{
|
||||
selectedOptiFabric = (ModComp.CompFile)sender.Tag;
|
||||
CardOptiFabric.IsSwapped = true;
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
private void OptiFabric_Clear(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
selectedOptiFabric = null;
|
||||
CardOptiFabric.IsSwapped = true;
|
||||
e.Handled = true;
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region LabyMod 列表
|
||||
|
||||
/// <summary>
|
||||
/// 获取 LabyMod 的加载异常信息。若正常则返回 Nothing。
|
||||
/// </summary>
|
||||
private string LoadLabyModGetError()
|
||||
{
|
||||
if (LoadLabyMod is null || LoadLabyMod.State.LoadingState == MyLoading.MyLoadingState.Run)
|
||||
return Lang.Text("Download.Install.State.Loading");
|
||||
if (LoadLabyMod.State.LoadingState == MyLoading.MyLoadingState.Error)
|
||||
return Lang.Text("Download.Install.State.GetVersionListFailed", ((ModLoader.LoaderBase)LoadLabyMod.State).Error.Message);
|
||||
// 检查 Loader
|
||||
if (GetLoaderError(LoadLabyMod) is not null)
|
||||
return GetLoaderError(LoadLabyMod);
|
||||
if (selectedOptiFine is not null)
|
||||
return Lang.Text("Download.Install.Compat.IncompatibleWithOptiFine");
|
||||
if (selectedLoaderName is not null && selectedLoaderName != "LabyMod")
|
||||
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
|
||||
if (selectedLiteLoader is not null)
|
||||
return Lang.Text("Download.Install.Compat.IncompatibleWithLiteLoader");
|
||||
foreach (JsonObject Version in ModDownload.dlLabyModListLoader.output.Value["production"]["minecraftVersions"].AsArray())
|
||||
if ((Version["version"].ToString() ?? "") == (_vanillaName ?? ""))
|
||||
return null;
|
||||
foreach (JsonObject Version in ModDownload.dlLabyModListLoader.output.Value["snapshot"]["minecraftVersions"].AsArray())
|
||||
if ((Version["version"].ToString() ?? "") == (_vanillaName ?? ""))
|
||||
return null;
|
||||
return Lang.Text("Download.Install.State.NoVersion");
|
||||
}
|
||||
|
||||
// 限制展开
|
||||
private void CardLabyMod_PreviewSwap(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
if (LoadLabyModGetError() is not null)
|
||||
e.handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试重新可视化 LabyMod 版本列表。
|
||||
/// </summary>
|
||||
private void LabyMod_Loaded()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (LoadLabyMod.State.LoadingState == MyLoading.MyLoadingState.Run)
|
||||
return;
|
||||
// 获取版本列表
|
||||
var versions = ModDownload.dlLabyModListLoader.output.Value;
|
||||
if (versions is null || versions["production"] is null || versions["snapshot"] is null)
|
||||
return;
|
||||
// 可视化
|
||||
var processedVersions = new JsonArray();
|
||||
foreach (JsonObject Production in versions["production"]["minecraftVersions"].AsArray())
|
||||
if ((Production["version"].ToString() ?? "") == (_vanillaName ?? ""))
|
||||
{
|
||||
var productionVersion = new JsonObject();
|
||||
productionVersion.Add("version", versions["production"]["labyModVersion"].ToString());
|
||||
productionVersion.Add("channel", "production");
|
||||
productionVersion.Add("commitReference", versions["production"]["commitReference"].ToString());
|
||||
processedVersions.Add(productionVersion);
|
||||
}
|
||||
|
||||
foreach (JsonObject Snapshot in versions["snapshot"]["minecraftVersions"].AsArray())
|
||||
if ((Snapshot["version"].ToString() ?? "") == (_vanillaName ?? ""))
|
||||
{
|
||||
var snapshotVersion = new JsonObject();
|
||||
snapshotVersion.Add("version", versions["snapshot"]["labyModVersion"].ToString());
|
||||
snapshotVersion.Add("channel", "snapshot");
|
||||
snapshotVersion.Add("commitReference", versions["snapshot"]["commitReference"].ToString());
|
||||
processedVersions.Add(snapshotVersion);
|
||||
}
|
||||
|
||||
// MyMsgBox(If(ProcessedVersions.ToString, "Nothing"))
|
||||
PanLabyMod.Children.Clear();
|
||||
PanLabyMod.Tag = processedVersions;
|
||||
CardLabyMod.SwapControl = PanLabyMod;
|
||||
CardLabyMod.InstallMethod = stack =>
|
||||
{
|
||||
foreach (JsonObject item in (IEnumerable)stack.Tag)
|
||||
stack.Children.Add(
|
||||
ModDownloadLib.LabyModDownloadListItem(item, (a, b) => this.LabyMod_Selected((dynamic)a, b)));
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"可视化 LabyMod 安装版本列表出错",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Install.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 选择与清除
|
||||
public void LabyMod_Selected(MyListItem sender, EventArgs e)
|
||||
{
|
||||
selectedLabyModChannel = ((dynamic)sender.Tag)("channel").ToString();
|
||||
selectedLabyModCommitRef = ((dynamic)sender.Tag)("commitReference").ToString();
|
||||
selectedLabyModVersion =
|
||||
((dynamic)sender.Tag)("version").ToString() + (selectedLabyModChannel == "snapshot" ? " " + Lang.Text("Download.Version.Type.Snapshot") : " " + Lang.Text("Download.Version.Type.Stable"));
|
||||
selectedLoaderName = "LabyMod";
|
||||
CardLabyMod.IsSwapped = true;
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
private void LabyMod_Clear(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
selectedLabyModCommitRef = null;
|
||||
selectedLabyModVersion = null;
|
||||
selectedLabyModChannel = null;
|
||||
|
||||
if (selectedLoaderName == "LabyMod")
|
||||
{
|
||||
selectedLoaderName = null;
|
||||
}
|
||||
|
||||
selectedAPIName = null;
|
||||
CardLabyMod.IsSwapped = true;
|
||||
e.Handled = true;
|
||||
ReloadSelected();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<local:MyPageLeft x:Class="PCL.PageInstanceLeft"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL" AnimatedControl="{Binding ElementName=PanItem, Mode=OneWay}">
|
||||
<local:MyScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"
|
||||
Style="{StaticResource ScrollViewerFullMargin}">
|
||||
<StackPanel Margin="0,12,0,0" x:Name="PanItem">
|
||||
<TextBlock Text="{DynamicResource Instance.Left.Game}" Margin="13,6,5,4" Opacity="0.6" FontSize="12" />
|
||||
<local:MyListItem x:Name="ItemOverall" IsScaleAnimationEnabled="False" Checked="True" Type="RadioBox"
|
||||
Check="PageCheck"
|
||||
Tag="0" MinPaddingRight="35" Height="36" VerticalAlignment="Top"
|
||||
Title="{DynamicResource Instance.Left.Overview}"
|
||||
SvgIcon="lucide/layout-dashboard" />
|
||||
<local:MyListItem x:Name="ItemSetup" IsScaleAnimationEnabled="False" Tag="1" MinPaddingRight="35"
|
||||
Check="PageCheck"
|
||||
Height="36" VerticalAlignment="Top" Title="{DynamicResource Instance.Left.Settings}"
|
||||
Type="RadioBox"
|
||||
SvgIcon="lucide/settings">
|
||||
<local:MyListItem.Buttons>
|
||||
<x:Array Type="{x:Type local:MyIconButton}">
|
||||
<local:MyIconButton Tag="1" ToolTip="{DynamicResource Common.Action.Initialize}"
|
||||
ToolTipService.Placement="Right"
|
||||
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
|
||||
Click="Reset"
|
||||
SvgIcon="lucide/rotate-ccw"
|
||||
LogoScale="0.9" />
|
||||
</x:Array>
|
||||
</local:MyListItem.Buttons>
|
||||
</local:MyListItem>
|
||||
<local:MyListItem x:Name="ItemInstall" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="10"
|
||||
Check="PageCheck"
|
||||
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
|
||||
Title="{DynamicResource Instance.Left.Install}"
|
||||
LogoScale="0.87"
|
||||
SvgIcon="lucide/wrench">
|
||||
<local:MyListItem.Buttons>
|
||||
<x:Array Type="{x:Type local:MyIconButton}">
|
||||
<local:MyIconButton Tag="10" ToolTip="{DynamicResource Common.Action.Refresh}"
|
||||
ToolTipService.Placement="Right"
|
||||
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
|
||||
LogoScale="0.85" Click="RefreshButton_Click"
|
||||
SvgIcon="lucide/refresh-cw" />
|
||||
</x:Array>
|
||||
</local:MyListItem.Buttons>
|
||||
</local:MyListItem>
|
||||
<local:MyListItem x:Name="ItemExport" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="2"
|
||||
Check="PageCheck"
|
||||
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
|
||||
Title="{DynamicResource Instance.Left.Export}"
|
||||
SvgIcon="lucide/package">
|
||||
<local:MyListItem.Buttons>
|
||||
<x:Array Type="{x:Type local:MyIconButton}">
|
||||
<local:MyIconButton Tag="2" ToolTip="{DynamicResource Common.Action.Refresh}"
|
||||
ToolTipService.Placement="Right"
|
||||
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
|
||||
Click="RefreshButton_Click" LogoScale="0.85"
|
||||
SvgIcon="lucide/refresh-cw" />
|
||||
</x:Array>
|
||||
</local:MyListItem.Buttons>
|
||||
</local:MyListItem>
|
||||
<TextBlock x:Name="TextResource" Text="{DynamicResource Instance.Left.Resources}" Margin="13,6,5,4"
|
||||
Opacity="0.6" FontSize="12" />
|
||||
<local:MyListItem x:Name="ItemWorld" LogoScale="0.95" IsScaleAnimationEnabled="False" Type="RadioBox"
|
||||
Check="PageCheck"
|
||||
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
|
||||
Title="{DynamicResource Instance.Left.Saves}" Tag="3"
|
||||
SvgIcon="lucide/globe">
|
||||
<local:MyListItem.Buttons>
|
||||
<x:Array Type="{x:Type local:MyIconButton}">
|
||||
<local:MyIconButton Tag="3" ToolTip="{DynamicResource Common.Action.Refresh}"
|
||||
ToolTipService.Placement="Right"
|
||||
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
|
||||
Click="RefreshButton_Click" LogoScale="0.85"
|
||||
SvgIcon="lucide/refresh-cw" />
|
||||
</x:Array>
|
||||
</local:MyListItem.Buttons>
|
||||
</local:MyListItem>
|
||||
<local:MyListItem x:Name="ItemScreenshot" LogoScale="0.95" IsScaleAnimationEnabled="False" Type="RadioBox"
|
||||
Check="PageCheck"
|
||||
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
|
||||
Title="{DynamicResource Instance.Left.Screenshots}" Tag="4"
|
||||
SvgIcon="lucide/image">
|
||||
<local:MyListItem.Buttons>
|
||||
<x:Array Type="{x:Type local:MyIconButton}">
|
||||
<local:MyIconButton Tag="4" ToolTip="{DynamicResource Common.Action.Refresh}"
|
||||
ToolTipService.Placement="Right"
|
||||
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
|
||||
Click="RefreshButton_Click" LogoScale="0.85"
|
||||
SvgIcon="lucide/refresh-cw" />
|
||||
</x:Array>
|
||||
</local:MyListItem.Buttons>
|
||||
</local:MyListItem>
|
||||
<local:MyListItem x:Name="ItemMod" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="5"
|
||||
Check="PageCheck"
|
||||
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
|
||||
Title="{DynamicResource Instance.Left.Mod}"
|
||||
SvgIcon="lucide/puzzle">
|
||||
<local:MyListItem.Buttons>
|
||||
<x:Array Type="{x:Type local:MyIconButton}">
|
||||
<local:MyIconButton Tag="5" ToolTip="{DynamicResource Common.Action.Refresh}"
|
||||
ToolTipService.Placement="Right"
|
||||
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
|
||||
Click="RefreshButton_Click" LogoScale="0.85"
|
||||
SvgIcon="lucide/refresh-cw" />
|
||||
</x:Array>
|
||||
</local:MyListItem.Buttons>
|
||||
</local:MyListItem>
|
||||
<local:MyListItem x:Name="ItemModDisabled" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="6"
|
||||
Check="PageCheck"
|
||||
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
|
||||
Title="{DynamicResource Instance.Left.Mod}"
|
||||
SvgIcon="lucide/puzzle" />
|
||||
<local:MyListItem x:Name="ItemResourcePack" LogoScale="0.85" IsScaleAnimationEnabled="False"
|
||||
Check="PageCheck"
|
||||
Type="RadioBox" MinPaddingRight="35" Height="36" VerticalAlignment="Top"
|
||||
Title="{DynamicResource Instance.Left.ResourcePacks}"
|
||||
Tag="7"
|
||||
SvgIcon="lucide/layers">
|
||||
<local:MyListItem.Buttons>
|
||||
<x:Array Type="{x:Type local:MyIconButton}">
|
||||
<local:MyIconButton Tag="7" ToolTip="{DynamicResource Common.Action.Refresh}"
|
||||
ToolTipService.Placement="Right"
|
||||
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
|
||||
Click="RefreshButton_Click" LogoScale="0.85"
|
||||
SvgIcon="lucide/refresh-cw" />
|
||||
</x:Array>
|
||||
</local:MyListItem.Buttons>
|
||||
</local:MyListItem>
|
||||
<local:MyListItem x:Name="ItemShader" LogoScale="1.15" IsScaleAnimationEnabled="False" Type="RadioBox"
|
||||
Check="PageCheck"
|
||||
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
|
||||
Title="{DynamicResource Instance.Left.Shaders}" Tag="8"
|
||||
SvgIcon="lucide/sparkles">
|
||||
<local:MyListItem.Buttons>
|
||||
<x:Array Type="{x:Type local:MyIconButton}">
|
||||
<local:MyIconButton Tag="8" ToolTip="{DynamicResource Common.Action.Refresh}"
|
||||
ToolTipService.Placement="Right"
|
||||
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
|
||||
Click="RefreshButton_Click" LogoScale="0.85"
|
||||
SvgIcon="lucide/refresh-cw" />
|
||||
</x:Array>
|
||||
</local:MyListItem.Buttons>
|
||||
</local:MyListItem>
|
||||
<local:MyListItem x:Name="ItemSchematic" LogoScale="0.85" IsScaleAnimationEnabled="False" Type="RadioBox"
|
||||
Check="PageCheck"
|
||||
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
|
||||
Title="{DynamicResource Instance.Left.Schematics}" Tag="9"
|
||||
SvgIcon="lucide/file-box">
|
||||
<local:MyListItem.Buttons>
|
||||
<x:Array Type="{x:Type local:MyIconButton}">
|
||||
<local:MyIconButton Tag="9" ToolTip="{DynamicResource Common.Action.Refresh}"
|
||||
ToolTipService.Placement="Right"
|
||||
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
|
||||
Click="RefreshButton_Click" LogoScale="0.85"
|
||||
SvgIcon="lucide/refresh-cw" />
|
||||
</x:Array>
|
||||
</local:MyListItem.Buttons>
|
||||
</local:MyListItem>
|
||||
<local:MyListItem x:Name="ItemServer" LogoScale="0.85" IsScaleAnimationEnabled="False" Type="RadioBox"
|
||||
Check="PageCheck"
|
||||
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
|
||||
Title="{DynamicResource Instance.Left.Servers}" Tag="11"
|
||||
SvgIcon="lucide/server">
|
||||
<local:MyListItem.Buttons>
|
||||
<x:Array Type="{x:Type local:MyIconButton}">
|
||||
<local:MyIconButton Tag="11" ToolTip="{DynamicResource Common.Action.Refresh}"
|
||||
ToolTipService.Placement="Right"
|
||||
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
|
||||
Click="RefreshButton_Click" LogoScale="0.85"
|
||||
SvgIcon="lucide/refresh-cw" />
|
||||
</x:Array>
|
||||
</local:MyListItem.Buttons>
|
||||
</local:MyListItem>
|
||||
</StackPanel>
|
||||
</local:MyScrollViewer>
|
||||
</local:MyPageLeft>
|
||||
@@ -0,0 +1,339 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using PCL.Core.App;
|
||||
using PCL.Core.App.Localization;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageInstanceLeft : IRefreshable
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前显示设置的 MC 实例。
|
||||
/// </summary>
|
||||
public static McInstance McInstance = null;
|
||||
|
||||
public PageInstanceLeft()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += (_, _) => RefreshModDisabled();
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
Refresh(ModMain.frmMain.PageCurrentSub);
|
||||
}
|
||||
|
||||
public void RefreshModDisabled()
|
||||
{
|
||||
var hide = Config.Preference.Hide;
|
||||
|
||||
if (McInstance is not null && McInstance.Modable)
|
||||
{
|
||||
ItemMod.Visibility = !PageSetupUI.HiddenForceShow && hide.InstanceMod
|
||||
? Visibility.Collapsed
|
||||
: Visibility.Visible;
|
||||
ItemModDisabled.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemMod.Visibility = Visibility.Collapsed;
|
||||
ItemModDisabled.Visibility = !PageSetupUI.HiddenForceShow && hide.InstanceMod
|
||||
? Visibility.Collapsed
|
||||
: Visibility.Visible;
|
||||
}
|
||||
|
||||
// 功能隐藏
|
||||
if (!PageSetupUI.HiddenForceShow)
|
||||
{
|
||||
var disableCount = 0;
|
||||
if (hide.InstanceSave)
|
||||
disableCount += 1;
|
||||
if (hide.InstanceScreenshot)
|
||||
disableCount += 1;
|
||||
if (hide.InstanceMod)
|
||||
disableCount += 1;
|
||||
if (hide.InstanceResourcePack)
|
||||
disableCount += 1;
|
||||
if (hide.InstanceShader)
|
||||
disableCount += 1;
|
||||
if (hide.InstanceSchematic)
|
||||
disableCount += 1;
|
||||
if (hide.InstanceServer)
|
||||
disableCount += 1;
|
||||
if (disableCount == 7)
|
||||
TextResource.Visibility = Visibility.Collapsed;
|
||||
else
|
||||
TextResource.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
TextResource.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
ItemInstall.Visibility = !PageSetupUI.HiddenForceShow && hide.InstanceEdit
|
||||
? Visibility.Collapsed
|
||||
: Visibility.Visible;
|
||||
ItemExport.Visibility = !PageSetupUI.HiddenForceShow && hide.InstanceExport
|
||||
? Visibility.Collapsed
|
||||
: Visibility.Visible;
|
||||
ItemWorld.Visibility = !PageSetupUI.HiddenForceShow && hide.InstanceSave
|
||||
? Visibility.Collapsed
|
||||
: Visibility.Visible;
|
||||
ItemScreenshot.Visibility = !PageSetupUI.HiddenForceShow && hide.InstanceScreenshot
|
||||
? Visibility.Collapsed
|
||||
: Visibility.Visible;
|
||||
ItemResourcePack.Visibility = !PageSetupUI.HiddenForceShow && hide.InstanceResourcePack
|
||||
? Visibility.Collapsed
|
||||
: Visibility.Visible;
|
||||
ItemShader.Visibility = !PageSetupUI.HiddenForceShow && hide.InstanceShader
|
||||
? Visibility.Collapsed
|
||||
: Visibility.Visible;
|
||||
ItemSchematic.Visibility = !PageSetupUI.HiddenForceShow && hide.InstanceSchematic
|
||||
? Visibility.Collapsed
|
||||
: Visibility.Visible;
|
||||
ItemServer.Visibility = !PageSetupUI.HiddenForceShow && hide.InstanceServer
|
||||
? Visibility.Collapsed
|
||||
: Visibility.Visible;
|
||||
}
|
||||
|
||||
private void RefreshButton_Click(object sender, EventArgs e) // 由边栏按钮匿名调用
|
||||
{
|
||||
Refresh((FormMain.PageSubType)ModBase.Val(((MyIconButton)sender).Tag));
|
||||
}
|
||||
|
||||
public void Refresh(FormMain.PageSubType subType)
|
||||
{
|
||||
switch (subType)
|
||||
{
|
||||
case FormMain.PageSubType.VersionMod:
|
||||
{
|
||||
PageInstanceCompResource.Refresh(ModComp.CompType.Mod);
|
||||
break;
|
||||
}
|
||||
case FormMain.PageSubType.VersionScreenshot:
|
||||
{
|
||||
var ignore= PageInstanceScreenshot.RefreshAsync();
|
||||
break;
|
||||
}
|
||||
case FormMain.PageSubType.VersionWorld:
|
||||
{
|
||||
PageInstanceSaves.Refresh();
|
||||
break;
|
||||
}
|
||||
case FormMain.PageSubType.VersionResourcePack:
|
||||
{
|
||||
PageInstanceCompResource.Refresh(ModComp.CompType.ResourcePack);
|
||||
break;
|
||||
}
|
||||
case FormMain.PageSubType.VersionShader:
|
||||
{
|
||||
PageInstanceCompResource.Refresh(ModComp.CompType.Shader);
|
||||
break;
|
||||
}
|
||||
case FormMain.PageSubType.VersionSchematic:
|
||||
{
|
||||
PageInstanceCompResource.Refresh(ModComp.CompType.Schematic);
|
||||
break;
|
||||
}
|
||||
case FormMain.PageSubType.VersionInstall:
|
||||
{
|
||||
ModDownload.dlClientListLoader.Start(isForceRestart: true);
|
||||
ModDownload.dlOptiFineListLoader.Start(isForceRestart: true);
|
||||
ModDownload.dlForgeListLoader.Start(isForceRestart: true);
|
||||
ModDownload.dlNeoForgeListLoader.Start(isForceRestart: true);
|
||||
ModDownload.dlLiteLoaderListLoader.Start(isForceRestart: true);
|
||||
ModDownload.dlFabricListLoader.Start(isForceRestart: true);
|
||||
ModDownload.dlFabricApiLoader.Start(isForceRestart: true);
|
||||
ModDownload.dlOptiFabricLoader.Start(isForceRestart: true);
|
||||
ModDownload.dlLabyModListLoader.Start(isForceRestart: true);
|
||||
ItemInstall.Checked = true;
|
||||
ModMain.frmInstanceInstall.GetCurrentInfo();
|
||||
break;
|
||||
}
|
||||
case FormMain.PageSubType.VersionExport:
|
||||
{
|
||||
if (ModMain.frmInstanceExport is not null)
|
||||
ModMain.frmInstanceExport.RefreshAll();
|
||||
ItemExport.Checked = true;
|
||||
break;
|
||||
}
|
||||
case FormMain.PageSubType.VersionServer:
|
||||
{
|
||||
if (ModMain.frmInstanceServer is not null)
|
||||
ModMain.frmInstanceServer.RefreshServers();
|
||||
ItemServer.Checked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset(object sender, EventArgs e)
|
||||
{
|
||||
if (ModMain.MyMsgBox(Lang.Text("Instance.Left.InitializeSettings.ConfirmMessage"),
|
||||
Lang.Text("Instance.Left.InitializeSettings.ConfirmTitle"),
|
||||
button2: Lang.Text("Common.Action.Cancel"),
|
||||
isWarn: true)
|
||||
== 1)
|
||||
{
|
||||
if (ModMain.frmInstanceSetup is null)
|
||||
ModMain.frmInstanceSetup = new PageInstanceSetup();
|
||||
ModMain.frmInstanceSetup.Reset();
|
||||
ItemSetup.Checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
#region 页面切换
|
||||
|
||||
/// <summary>
|
||||
/// 当前页面的编号。从 0 开始计算。
|
||||
/// </summary>
|
||||
public FormMain.PageSubType pageID = FormMain.PageSubType.Default;
|
||||
|
||||
/// <summary>
|
||||
/// 勾选事件改变页面。
|
||||
/// </summary>
|
||||
private void PageCheck(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
if (sender is MyListItem item && item.Tag is not null)
|
||||
PageChange((FormMain.PageSubType)ModBase.Val(item.Tag));
|
||||
}
|
||||
|
||||
public object PageGet(FormMain.PageSubType id)
|
||||
{
|
||||
if ((int)id == -1)
|
||||
id = pageID;
|
||||
switch (id)
|
||||
{
|
||||
case FormMain.PageSubType.VersionOverall:
|
||||
{
|
||||
if (ModMain.frmInstanceOverall is null)
|
||||
ModMain.frmInstanceOverall = new PageInstanceOverall();
|
||||
return ModMain.frmInstanceOverall;
|
||||
}
|
||||
case FormMain.PageSubType.VersionMod:
|
||||
{
|
||||
if (ModMain.frmInstanceMod is null)
|
||||
ModMain.frmInstanceMod = new PageInstanceCompResource(ModComp.CompType.Mod);
|
||||
return ModMain.frmInstanceMod;
|
||||
}
|
||||
case FormMain.PageSubType.VersionModDisabled:
|
||||
{
|
||||
if (ModMain.frmInstanceModDisabled is null)
|
||||
ModMain.frmInstanceModDisabled = new PageInstanceModDisabled();
|
||||
return ModMain.frmInstanceModDisabled;
|
||||
}
|
||||
case FormMain.PageSubType.VersionSetup:
|
||||
{
|
||||
if (ModMain.frmInstanceSetup is null)
|
||||
ModMain.frmInstanceSetup = new PageInstanceSetup();
|
||||
return ModMain.frmInstanceSetup;
|
||||
}
|
||||
case FormMain.PageSubType.VersionWorld:
|
||||
{
|
||||
if (ModMain.frmInstanceSaves is null)
|
||||
ModMain.frmInstanceSaves = new PageInstanceSaves();
|
||||
return ModMain.frmInstanceSaves;
|
||||
}
|
||||
case FormMain.PageSubType.VersionScreenshot:
|
||||
{
|
||||
if (ModMain.frmInstanceScreenshot is null)
|
||||
ModMain.frmInstanceScreenshot = new PageInstanceScreenshot();
|
||||
return ModMain.frmInstanceScreenshot;
|
||||
}
|
||||
case FormMain.PageSubType.VersionResourcePack:
|
||||
{
|
||||
if (ModMain.frmInstanceResourcePack is null)
|
||||
ModMain.frmInstanceResourcePack = new PageInstanceCompResource(ModComp.CompType.ResourcePack);
|
||||
return ModMain.frmInstanceResourcePack;
|
||||
}
|
||||
case FormMain.PageSubType.VersionShader:
|
||||
{
|
||||
if (ModMain.frmInstanceShader is null)
|
||||
ModMain.frmInstanceShader = new PageInstanceCompResource(ModComp.CompType.Shader);
|
||||
return ModMain.frmInstanceShader;
|
||||
}
|
||||
case FormMain.PageSubType.VersionSchematic:
|
||||
{
|
||||
if (ModMain.frmInstanceSchematic is null)
|
||||
ModMain.frmInstanceSchematic = new PageInstanceCompResource(ModComp.CompType.Schematic);
|
||||
return ModMain.frmInstanceSchematic;
|
||||
}
|
||||
case FormMain.PageSubType.VersionInstall:
|
||||
{
|
||||
if (ModMain.frmInstanceInstall is null)
|
||||
ModMain.frmInstanceInstall = new PageInstanceInstall();
|
||||
return ModMain.frmInstanceInstall;
|
||||
}
|
||||
case FormMain.PageSubType.VersionExport:
|
||||
{
|
||||
if (ModMain.frmInstanceExport is null)
|
||||
ModMain.frmInstanceExport = new PageInstanceExport();
|
||||
return ModMain.frmInstanceExport;
|
||||
}
|
||||
case FormMain.PageSubType.VersionServer:
|
||||
{
|
||||
if (ModMain.frmInstanceServer is null)
|
||||
ModMain.frmInstanceServer = new PageInstanceServer();
|
||||
return ModMain.frmInstanceServer;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw new Exception("未知的实例设置子页面种类:" + (int)id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换现有页面。
|
||||
/// </summary>
|
||||
public void PageChange(FormMain.PageSubType id)
|
||||
{
|
||||
if (pageID == id)
|
||||
return;
|
||||
ModAnimation.AniControlEnabled += 1;
|
||||
try
|
||||
{
|
||||
PageChangeRun((MyPageRight)PageGet(id));
|
||||
pageID = id;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"切换分页面失败(ID " + (int)id + ")",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Error.OperationFailed"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
ModAnimation.AniControlEnabled -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void PageChangeRun(MyPageRight target)
|
||||
{
|
||||
ModAnimation.AniStop("FrmMain PageChangeRight"); // 停止主页面的右页面切换动画,防止它与本动画一起触发多次 PageOnEnter
|
||||
if (target.Parent is not null)
|
||||
target.SetValue(ContentPresenter.ContentProperty, null);
|
||||
ModMain.frmMain.pageRight = target;
|
||||
((MyPageRight)ModMain.frmMain.PanMainRight.Child).PageOnExit();
|
||||
ModAnimation.AniStart(new[]
|
||||
{
|
||||
ModAnimation.AaCode(() =>
|
||||
{
|
||||
((MyPageRight)ModMain.frmMain.PanMainRight.Child).PageOnForceExit();
|
||||
ModMain.frmMain.PanMainRight.Child = ModMain.frmMain.pageRight;
|
||||
ModMain.frmMain.pageRight.Opacity = 0d;
|
||||
}, 130),
|
||||
ModAnimation.AaCode(() =>
|
||||
{
|
||||
// 延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理
|
||||
ModMain.frmMain.pageRight.Opacity = 1d;
|
||||
ModMain.frmMain.pageRight.PageOnEnter();
|
||||
}, 30, true)
|
||||
}, "PageLeft PageChange");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<local:MyPageRight x:Class="PCL.PageInstanceModDisabled"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:PCL"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" Margin="40" x:Name="PanMain">
|
||||
<Grid Margin="20,17">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="0" Grid.ColumnSpan="4" Margin="0,0,0,9" HorizontalAlignment="Center"
|
||||
Text="{DynamicResource Instance.Resource.Mod.Disabled.Title}" FontSize="19" UseLayoutRounding="True"
|
||||
SnapsToDevicePixels="True"
|
||||
Foreground="{DynamicResource ColorBrush3}" />
|
||||
<Rectangle Grid.Row="1" Grid.ColumnSpan="4" HorizontalAlignment="Stretch" Height="2"
|
||||
Fill="{DynamicResource ColorBrush3}" />
|
||||
<TextBlock Grid.Row="2" Grid.ColumnSpan="4" Margin="10,15,10,5"
|
||||
Text="{DynamicResource Instance.Resource.Mod.Disabled.Message}"
|
||||
TextWrapping="Wrap" />
|
||||
<local:MyButton Grid.Row="3" Grid.Column="1" Height="35" HorizontalAlignment="Center" x:Name="BtnDownload"
|
||||
MinWidth="140" Text="{DynamicResource Instance.Resource.Mod.Disabled.GoDownload}"
|
||||
Margin="10,10,10,0" Padding="13,0" ColorType="Highlight" />
|
||||
<local:MyButton Grid.Row="3" Grid.Column="2" Height="35" HorizontalAlignment="Center" x:Name="BtnVersion"
|
||||
MinWidth="140" Text="{DynamicResource Instance.Resource.Mod.Disabled.SelectInstance}"
|
||||
Margin="10,10,10,0" Padding="13,0" />
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
</local:MyPageRight>
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
using System.Windows;
|
||||
using PCL.Core.App;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageInstanceModDisabled
|
||||
{
|
||||
public PageInstanceModDisabled()
|
||||
{
|
||||
InitializeComponent();
|
||||
BtnDownload.Click += BtnDownload_Click;
|
||||
BtnVersion.Click += BtnVersion_Click;
|
||||
BtnDownload.Loaded += BtnDownload_Loaded;
|
||||
}
|
||||
|
||||
private void BtnDownload_Click(object sender, EventArgs e)
|
||||
{
|
||||
ModMain.frmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadInstall);
|
||||
}
|
||||
|
||||
private void BtnVersion_Click(object sender, EventArgs e)
|
||||
{
|
||||
ModMain.frmMain.PageChange(FormMain.PageType
|
||||
.Launch); // 在实例选择页面选定实例的时候只会返回一层,因此如果不先锚定 Launch,在选择实例后会回退到实例设置的这个页面
|
||||
ModMain.frmMain.PageChange(FormMain.PageType.InstanceSelect);
|
||||
}
|
||||
|
||||
public void BtnDownload_Loaded(object? sender = null, RoutedEventArgs? e = null)
|
||||
{
|
||||
var newVisibility =
|
||||
(Config.Preference.Hide.PageDownload && !PageSetupUI.HiddenForceShow) ||
|
||||
(ModMain.frmSelectRight is not null && ModMain.frmSelectRight.showHidden)
|
||||
? Visibility.Collapsed
|
||||
: Visibility.Visible;
|
||||
if (BtnDownload.Visibility != newVisibility)
|
||||
{
|
||||
BtnDownload.Visibility = newVisibility;
|
||||
PanMain.TriggerForceResize();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<local:MyPageRight
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" x:Class="PCL.PageInstanceOverall"
|
||||
PanScroll="{Binding ElementName=PanBack}" Grid.IsSharedSizeScope="True">
|
||||
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
|
||||
<StackPanel x:Name="PanMain" Margin="25,10">
|
||||
<local:MyCard Margin="0,15" Title="">
|
||||
<Grid Margin="10,7" Name="PanDisplayItem" />
|
||||
</local:MyCard>
|
||||
<local:MyCard Margin="0,0,0,15" Title="{DynamicResource Instance.Overall.Info.Title}">
|
||||
<StackPanel x:Name="PanInfo" Margin="15,40,25,5">
|
||||
<local:MyLoading x:Name="LabInfoLoading" Text="{DynamicResource Instance.Overall.Info.Loading}" Margin="0,0,0,10" />
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
<local:MyCard Margin="0,0,0,15" Title="{DynamicResource Instance.Overall.Personalization.Title}" x:Name="PanDisplay">
|
||||
<StackPanel Margin="25,40,25,15">
|
||||
<Grid x:Name="PanDisplayIcon" HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Name" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="28" />
|
||||
<RowDefinition Height="9" />
|
||||
<RowDefinition Height="28" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Text="{DynamicResource Instance.Overall.Icon.Title}" Margin="0,0,25,0" />
|
||||
<local:MyComboBox Grid.Column="1" x:Name="ComboDisplayLogo">
|
||||
<local:MyComboBoxItem Content="{DynamicResource Common.Option.Auto}" IsSelected="True" Tag="" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Common.Option.Customize}" x:Name="ItemDisplayLogoCustom" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Instance.Overall.Icon.Cobblestone}"
|
||||
Tag="pack://application:,,,/images/Blocks/CobbleStone.png" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Instance.Overall.Icon.CommandBlock}"
|
||||
Tag="pack://application:,,,/images/Blocks/CommandBlock.png" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Instance.Overall.Icon.GoldBlock}" Tag="pack://application:,,,/images/Blocks/GoldBlock.png" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Instance.Overall.Icon.GrassBlock}" Tag="pack://application:,,,/images/Blocks/Grass.png" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Instance.Overall.Icon.DirtPath}" Tag="pack://application:,,,/images/Blocks/GrassPath.png" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Instance.Overall.Icon.Anvil}" Tag="pack://application:,,,/images/Blocks/Anvil.png" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Instance.Overall.Icon.RedstoneBlock}"
|
||||
Tag="pack://application:,,,/images/Blocks/RedstoneBlock.png" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Instance.Overall.Icon.RedstoneLampOn}"
|
||||
Tag="pack://application:,,,/images/Blocks/RedstoneLampOn.png" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Instance.Overall.Icon.RedstoneLampOff}"
|
||||
Tag="pack://application:,,,/images/Blocks/RedstoneLampOff.png" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Instance.Overall.Icon.Egg}" Tag="pack://application:,,,/images/Blocks/Egg.png" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Instance.Overall.Icon.Fabric}"
|
||||
Tag="pack://application:,,,/images/Blocks/Fabric.png" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Instance.Overall.Icon.Quilt}"
|
||||
Tag="pack://application:,,,/images/Blocks/Quilt.png" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Instance.Overall.Icon.NeoForgeFox}"
|
||||
Tag="pack://application:,,,/images/Blocks/NeoForge.png" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Instance.Overall.Icon.Cleanroom}"
|
||||
Tag="pack://application:,,,/images/Blocks/Cleanroom.png" />
|
||||
</local:MyComboBox>
|
||||
<TextBlock VerticalAlignment="Center" Grid.Row="2" HorizontalAlignment="Left" Text="{DynamicResource Instance.Overall.Category.Title}"
|
||||
Margin="0,0,25,0" />
|
||||
<local:MyComboBox Grid.Column="1" Grid.Row="2" x:Name="ComboDisplayType">
|
||||
<local:MyComboBoxItem Content="{DynamicResource Common.Option.Auto}" IsSelected="True" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Instance.Overall.Category.Hidden}"
|
||||
ToolTip="{DynamicResource Instance.Overall.Category.Hidden.ToolTip}" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Instance.Overall.Category.Modable}" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Instance.Overall.Category.Regular}" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Instance.Overall.Category.LessUsed}" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Instance.Overall.Category.AprilFools}" />
|
||||
</local:MyComboBox>
|
||||
</Grid>
|
||||
<Grid Margin="0,15,0,7" Height="35">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<local:MyButton x:Name="BtnDisplayRename" Text="{DynamicResource Instance.Overall.Rename}"
|
||||
MinWidth="140" Padding="13,0"
|
||||
Margin="0,0,20,0" />
|
||||
<local:MyButton x:Name="BtnDisplayDesc" Text="{DynamicResource Instance.Overall.EditDescription}"
|
||||
MinWidth="140" Padding="13,0"
|
||||
Margin="0,0,20,0" Grid.Column="1" />
|
||||
<local:MyButton x:Name="BtnDisplayStar" Text="{DynamicResource Instance.Overall.Favorite}"
|
||||
MinWidth="140" Padding="13,0"
|
||||
Margin="0,0,20,0" Grid.Column="2" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
<local:MyCard Margin="0,0,0,15" Title="{DynamicResource Instance.Overall.Shortcuts.Title}" x:Name="PanFolder">
|
||||
<StackPanel Margin="25,40,25,15">
|
||||
<Grid Margin="0,2,0,7" Height="35">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<local:MyButton x:Name="BtnFolderVersion" Text="{DynamicResource Instance.Overall.Shortcuts.InstanceFolder}" MinWidth="140" Padding="13,0"
|
||||
Margin="0,0,20,0" />
|
||||
<local:MyButton x:Name="BtnFolderSaves" Text="{DynamicResource Instance.Overall.Shortcuts.SavesFolder}" MinWidth="140" Padding="13,0"
|
||||
Margin="0,0,20,0" Grid.Column="1" />
|
||||
<local:MyButton x:Name="BtnFolderMods" Text="{DynamicResource Instance.Overall.Shortcuts.ModsFolder}" MinWidth="140" Padding="13,0"
|
||||
Margin="0,0,20,0" Grid.Column="2" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
<local:MyCard Margin="0,0,0,15" Title="{DynamicResource Instance.Overall.Manage.Title}" x:Name="PanManage">
|
||||
<StackPanel Margin="25,40,25,15">
|
||||
<WrapPanel Margin="0,-5,-20,7">
|
||||
<local:MyButton x:Name="BtnManageScript" Text="{DynamicResource Instance.Overall.Manage.ExportLaunchScript}" MinWidth="140" Padding="13,0"
|
||||
Margin="0,7,20,0" Height="35" />
|
||||
<local:MyButton x:Name="BtnManageTest" Text="{DynamicResource Instance.Overall.Manage.TestGame}" MinWidth="140" Padding="13,0"
|
||||
Margin="0,7,20,0"
|
||||
Height="35" />
|
||||
<local:MyButton x:Name="BtnManageCheck" Text="{DynamicResource Instance.Overall.Manage.RepairFiles}" MinWidth="140" Padding="13,0"
|
||||
Margin="0,7,20,0"
|
||||
ToolTip="{DynamicResource Instance.Overall.Manage.RepairFiles.ToolTip}"
|
||||
Height="35" />
|
||||
<local:MyButton x:Name="BtnManageRestore" Text="{DynamicResource Common.Action.Reset}" MinWidth="140" Padding="13,0"
|
||||
Margin="0,7,20,0"
|
||||
ToolTip="{DynamicResource Instance.Overall.Manage.Reinstall.ToolTip}"
|
||||
Height="35" />
|
||||
<local:MyButton x:Name="BtnManageDelete" Text="{DynamicResource Instance.Overall.Manage.Delete}" MinWidth="140" Padding="13,0"
|
||||
Margin="0,7,20,0"
|
||||
ColorType="Red" Height="35" />
|
||||
<local:MyButton x:Name="BtnManagePatch" Text="{DynamicResource Instance.Overall.Manage.PatchCore}" MinWidth="140" Padding="13,0"
|
||||
Margin="0,7,20,0"
|
||||
Height="35" />
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
</StackPanel>
|
||||
</local:MyScrollViewer>
|
||||
</local:MyPageRight>
|
||||
+842
@@ -0,0 +1,842 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using FluentValidation;
|
||||
using Microsoft.VisualBasic.FileIO;
|
||||
using PCL.Core.App;
|
||||
using PCL.Core.App.Configuration;
|
||||
using PCL.Core.App.Configuration.Storage;
|
||||
using PCL.Core.Minecraft;
|
||||
using PCL.Core.UI;
|
||||
using PCL.Core.Utils.Validate;
|
||||
using FileSystem = Microsoft.VisualBasic.FileIO.FileSystem;
|
||||
using PCL.Core.App.Localization;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageInstanceOverall
|
||||
{
|
||||
private ModLoader.LoaderCombo<int> instanceInfoLoader;
|
||||
|
||||
private bool isLoad;
|
||||
|
||||
public MyListItem itemVersion;
|
||||
private MyCompItem modpackCompItem;
|
||||
|
||||
public PageInstanceOverall()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += PageSetupLaunch_Loaded;
|
||||
LabInfoLoading.Text = Lang.Text("Instance.Overall.Info.Loading");
|
||||
// Handles
|
||||
ComboDisplayType.SelectionChanged += ComboDisplayType_SelectionChanged;
|
||||
BtnDisplayDesc.Click += BtnDisplayDesc_Click;
|
||||
BtnDisplayRename.Click += BtnDisplayRename_Click;
|
||||
ComboDisplayLogo.SelectionChanged += ComboDisplayLogo_SelectionChanged;
|
||||
BtnDisplayStar.Click += BtnDisplayStar_Click;
|
||||
BtnFolderVersion.Click += BtnFolderVersion_Click;
|
||||
BtnFolderSaves.Click += BtnFolderSaves_Click;
|
||||
BtnFolderMods.Click += BtnFolderMods_Click;
|
||||
BtnManageScript.Click += BtnManageScript_Click;
|
||||
BtnManageCheck.Click += BtnManageCheck_Click;
|
||||
BtnManageRestore.Click += BtnManageRestore_Click;
|
||||
BtnManageTest.Click += BtnManageTest_Click;
|
||||
BtnManageDelete.Click += BtnManageDelete_Click;
|
||||
BtnManagePatch.Click += BtnManagePatch_Click;
|
||||
}
|
||||
|
||||
private void PageSetupLaunch_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 重复加载部分
|
||||
PanBack.ScrollToHome();
|
||||
|
||||
// 更新设置
|
||||
ItemDisplayLogoCustom.Tag = @"PCL\Logo.png";
|
||||
Reload();
|
||||
|
||||
// 非重复加载部分
|
||||
if (isLoad)
|
||||
return;
|
||||
isLoad = true;
|
||||
PanDisplay.TriggerForceResize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保当前页面上的信息已正确显示。
|
||||
/// </summary>
|
||||
private void Reload()
|
||||
{
|
||||
ModAnimation.AniControlEnabled += 1;
|
||||
|
||||
var instance = PageInstanceLeft.McInstance;
|
||||
// 刷新设置项目
|
||||
ComboDisplayType.SelectedIndex = States.Instance.CardType[instance.PathInstance];
|
||||
BtnDisplayStar.Text = instance.IsStar ? Lang.Text("Instance.Overall.Unfavorite") : Lang.Text("Instance.Overall.Favorite");
|
||||
BtnFolderMods.Visibility = instance.Modable ? Visibility.Visible : Visibility.Collapsed;
|
||||
// 刷新实例显示
|
||||
PanDisplayItem.Children.Clear();
|
||||
itemVersion = PageSelectRight.McVersionListItem(instance);
|
||||
itemVersion.IsHitTestVisible = false;
|
||||
PanDisplayItem.Children.Add(itemVersion);
|
||||
ModMain.frmMain.PageNameRefresh();
|
||||
// 刷新实例信息
|
||||
GetInstanceInfo();
|
||||
// 刷新实例图标
|
||||
ComboDisplayLogo.SelectedIndex = 0;
|
||||
var logo = States.Instance.LogoPath[instance.PathInstance];
|
||||
var logoCustom = States.Instance.IsLogoCustom[instance.PathInstance];
|
||||
if (logoCustom)
|
||||
foreach (MyComboBoxItem Selection in ComboDisplayLogo.Items)
|
||||
if (Equals(Selection.Tag, logo) ||
|
||||
(Equals(Selection.Tag, @"PCL\Logo.png") &&
|
||||
logo.EndsWith(@"PCL\Logo.png")))
|
||||
{
|
||||
ComboDisplayLogo.SelectedItem = Selection;
|
||||
break;
|
||||
}
|
||||
|
||||
ModAnimation.AniControlEnabled -= 1;
|
||||
}
|
||||
|
||||
private void GetInstanceInfo()
|
||||
{
|
||||
modpackCompItem = null;
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
PanInfo.Children.Clear();
|
||||
PanInfo.Children.Add(new MyLoading { Text = Lang.Text("Instance.Overall.Info.Loading"), Margin = new Thickness(0d, 0d, 0d, 10d) });
|
||||
});
|
||||
var loaders = new List<ModLoader.LoaderBase>();
|
||||
loaders.Add(new ModLoader.LoaderTask<int, int>(Lang.Text("Instance.Overall.Info.LoadModpackInfoTask"), _ =>
|
||||
{
|
||||
var modpackId = States.Instance.ModpackId[PageInstanceLeft.McInstance.PathInstance];
|
||||
if (!string.IsNullOrWhiteSpace(modpackId))
|
||||
{
|
||||
var compProjects = ModComp.CompRequest.GetCompProjectsByIds(new List<string> { modpackId });
|
||||
if (compProjects.Count > 0)
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
modpackCompItem = compProjects.First().ToCompItem(false, false);
|
||||
modpackCompItem.Tag = compProjects.First();
|
||||
});
|
||||
}
|
||||
})
|
||||
{
|
||||
block = true
|
||||
});
|
||||
loaders.Add(new ModLoader.LoaderTask<int, int>(Lang.Text("Instance.Overall.Info.LoadInstanceInfoTask"), _ => ModBase.RunInUi(() =>
|
||||
{
|
||||
var instance = PageInstanceLeft.McInstance;
|
||||
var instanceInfo = instance.Info;
|
||||
List<MyListItem> items = [];
|
||||
var launchCount = States.Instance.LaunchCount[instance.PathInstance];
|
||||
if (launchCount == 0)
|
||||
items.Add(new MyListItem
|
||||
{
|
||||
Title = Lang.Text("Instance.Overall.Info.LaunchCount.Title"), Info = Lang.Text("Instance.Overall.Info.LaunchCount.Never"), Logo = "pack://application:,,,/images/Blocks/RedstoneLampOff.png"
|
||||
});
|
||||
else
|
||||
items.Add(new MyListItem
|
||||
{
|
||||
Title = Lang.Text("Instance.Overall.Info.LaunchCount.Title"),
|
||||
Info = Lang.Text("Instance.Overall.Info.LaunchCount.Count", States.Instance.LaunchCount[instance.PathInstance]),
|
||||
Logo = "pack://application:,,,/images/Blocks/RedstoneLampOn.png"
|
||||
});
|
||||
if (!string.IsNullOrWhiteSpace(States.Instance.ModpackVersion[instance.PathInstance]))
|
||||
items.Add(new MyListItem
|
||||
{
|
||||
Title = Lang.Text("Instance.Overall.Info.ModpackVersion"), Info = States.Instance.ModpackVersion[instance.PathInstance],
|
||||
Logo = "pack://application:,,,/images/Blocks/CommandBlock.png"
|
||||
});
|
||||
items.Add(new MyListItem
|
||||
{
|
||||
Title = "Minecraft", Info = instanceInfo.VanillaName,
|
||||
Logo = "pack://application:,,,/images/Blocks/Grass.png"
|
||||
});
|
||||
if (instanceInfo.HasForge)
|
||||
items.Add(new MyListItem
|
||||
{
|
||||
Title = "Forge", Info = instanceInfo.Forge, Logo = "pack://application:,,,/images/Blocks/Anvil.png"
|
||||
});
|
||||
if (instanceInfo.HasNeoForge)
|
||||
items.Add(new MyListItem
|
||||
{
|
||||
Title = "NeoForge", Info = instanceInfo.NeoForge,
|
||||
Logo = "pack://application:,,,/images/Blocks/NeoForge.png"
|
||||
});
|
||||
if (instanceInfo.HasCleanroom)
|
||||
items.Add(new MyListItem
|
||||
{
|
||||
Title = "Cleanroom", Info = instanceInfo.Cleanroom,
|
||||
Logo = "pack://application:,,,/images/Blocks/Cleanroom.png"
|
||||
});
|
||||
if (instanceInfo.HasFabric)
|
||||
items.Add(new MyListItem
|
||||
{
|
||||
Title = "Fabric", Info = instanceInfo.Fabric,
|
||||
Logo = "pack://application:,,,/images/Blocks/Fabric.png"
|
||||
});
|
||||
if (instanceInfo.HasQuilt)
|
||||
items.Add(new MyListItem
|
||||
{
|
||||
Title = "Quilt", Info = instanceInfo.Quilt, Logo = "pack://application:,,,/images/Blocks/Quilt.png"
|
||||
});
|
||||
if (instanceInfo.HasOptiFine)
|
||||
items.Add(new MyListItem
|
||||
{
|
||||
Title = "OptiFine", Info = instanceInfo.OptiFine,
|
||||
Logo = "pack://application:,,,/images/Blocks/GrassPath.png"
|
||||
});
|
||||
if (instanceInfo.HasLiteLoader)
|
||||
items.Add(new MyListItem
|
||||
{ Title = "LiteLoader", Info = Lang.Text("Instance.Overall.Info.Installed"), Logo = "pack://application:,,,/images/Blocks/Egg.png" });
|
||||
if (instanceInfo.HasLegacyFabric)
|
||||
items.Add(new MyListItem
|
||||
{
|
||||
Title = "Legacy Fabric", Info = instanceInfo.LegacyFabric,
|
||||
Logo = "pack://application:,,,/images/Blocks/Fabric.png"
|
||||
});
|
||||
if (instanceInfo.HasLabyMod)
|
||||
items.Add(new MyListItem
|
||||
{
|
||||
Title = "LabyMod", Info = instanceInfo.LabyMod,
|
||||
Logo = "pack://application:,,,/images/Blocks/LabyMod.png"
|
||||
});
|
||||
var wrapPanel = new WrapPanel { Margin = new Thickness(0, -5, -20, 7) };
|
||||
foreach (var item in items)
|
||||
{
|
||||
wrapPanel.Children.Add(item);
|
||||
wrapPanel.Children.Add(new TextBlock { Width = 2d });
|
||||
}
|
||||
|
||||
PanInfo.Children.Clear();
|
||||
if (modpackCompItem is not null)
|
||||
{
|
||||
PanInfo.Children.Add(modpackCompItem);
|
||||
PanInfo.Children.Add(new TextBlock());
|
||||
}
|
||||
|
||||
PanInfo.Children.Add(wrapPanel);
|
||||
})));
|
||||
instanceInfoLoader = new ModLoader.LoaderCombo<int>("Instance Info Loader", loaders) { show = false };
|
||||
instanceInfoLoader.Start();
|
||||
}
|
||||
|
||||
#region 卡片:个性化
|
||||
|
||||
// 实例分类
|
||||
private void ComboDisplayType_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (!(isLoad && ModAnimation.AniControlEnabled == 0))
|
||||
return;
|
||||
if (ComboDisplayType.SelectedIndex != 1)
|
||||
{
|
||||
// 改为不隐藏
|
||||
try
|
||||
{
|
||||
// 若设置分类为可安装 Mod,则显示正常的 Mod 管理页面
|
||||
States.Instance.CardType[PageInstanceLeft.McInstance.PathInstance] = ComboDisplayType.SelectedIndex;
|
||||
PageInstanceLeft.McInstance.displayType = (McInstanceCardType)States.Instance.CardType[PageInstanceLeft.McInstance.PathInstance];
|
||||
ModMain.frmInstanceLeft.RefreshModDisabled();
|
||||
|
||||
ModBase.WriteIni(ModFolder.mcFolderSelected + "PCL.ini", "InstanceCache", ""); // 要求刷新缓存
|
||||
ModLoader.LoaderFolderRun(ModInstanceList.mcInstanceListLoader, ModFolder.mcFolderSelected,
|
||||
ModLoader.LoaderFolderRunType.ForceRun, 1, @"versions\");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"修改实例分类失败({PageInstanceLeft.McInstance.Name})",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Overall.Error.OperationFailed"));
|
||||
}
|
||||
|
||||
Reload(); // 更新 “打开 Mod 文件夹” 按钮
|
||||
}
|
||||
else
|
||||
{
|
||||
// 改为隐藏
|
||||
try
|
||||
{
|
||||
if (!States.Hint.HideGameInstance)
|
||||
{
|
||||
if (ModMain.MyMsgBox(
|
||||
Lang.Text("Instance.Overall.Hide.ConfirmMessage"), Lang.Text("Instance.Overall.Hide.ConfirmTitle"), button2: Lang.Text("Common.Action.Cancel")) != 1)
|
||||
{
|
||||
ComboDisplayType.SelectedIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
States.Hint.HideGameInstance = true;
|
||||
}
|
||||
|
||||
States.Instance.CardType[PageInstanceLeft.McInstance.PathInstance] =
|
||||
(int)McInstanceCardType.Hidden;
|
||||
ModBase.WriteIni(ModFolder.mcFolderSelected + "PCL.ini", "InstanceCache", ""); // 要求刷新缓存
|
||||
ModLoader.LoaderFolderRun(ModInstanceList.mcInstanceListLoader, ModFolder.mcFolderSelected,
|
||||
ModLoader.LoaderFolderRunType.ForceRun, 1, @"versions\");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"隐藏实例 {PageInstanceLeft.McInstance.Name} 失败",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Overall.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更改描述
|
||||
private void BtnDisplayDesc_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var oldInfo = States.Instance.CustomInfo[PageInstanceLeft.McInstance.PathInstance];
|
||||
var newInfo = ModMain.MyMsgBoxInput(Lang.Text("Instance.Overall.Description.EditTitle"), Lang.Text("Instance.Overall.Description.EditMessage"), oldInfo,
|
||||
[], Lang.Text("Instance.Overall.Description.Default"));
|
||||
if (newInfo is not null && (oldInfo ?? "") != (newInfo ?? ""))
|
||||
States.Instance.CustomInfo[PageInstanceLeft.McInstance.PathInstance] = newInfo;
|
||||
PageInstanceLeft.McInstance = new McInstance(PageInstanceLeft.McInstance.Name).Load();
|
||||
Reload();
|
||||
ModLoader.LoaderFolderRun(ModInstanceList.mcInstanceListLoader, ModFolder.mcFolderSelected,
|
||||
ModLoader.LoaderFolderRunType.ForceRun, 1, @"versions\");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"实例 {PageInstanceLeft.McInstance.Name} 描述更改失败",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Overall.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 重命名实例
|
||||
private void BtnDisplayRename_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确认输入的新名称
|
||||
var oldName = PageInstanceLeft.McInstance.Name;
|
||||
var oldPath = PageInstanceLeft.McInstance.PathInstance;
|
||||
// 修改此部分的同时修改快速安装的实例名检测*
|
||||
var newName = ModMain.MyMsgBoxInput(Lang.Text("Instance.Overall.Name.EditTitle"), "", oldName,
|
||||
[new FolderNameValidator(ModFolder.mcFolderSelected + "versions", ignoreCase: false)]);
|
||||
if (string.IsNullOrWhiteSpace(newName))
|
||||
return;
|
||||
var newPath = Path.Combine(ModFolder.mcFolderSelected, "versions", newName);
|
||||
// 获取临时中间名,以防止仅修改大小写的重命名失败
|
||||
var tempName = newName + "_temp";
|
||||
var tempPath = Path.Combine(ModFolder.mcFolderSelected, "versions", tempName);
|
||||
var isCaseChangedOnly = (newName.ToLower() ?? "") == (oldName.ToLower() ?? "");
|
||||
// 重新加载实例 Json 信息,避免 HMCL 项被合并
|
||||
JsonObject jsonObject;
|
||||
try
|
||||
{
|
||||
jsonObject = (JsonObject)ModBase.GetJson(ModBase.ReadFile(PageInstanceLeft.McInstance.PathInstance +
|
||||
PageInstanceLeft.McInstance.Name + ".json"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "重命名读取 Json 时失败");
|
||||
jsonObject = PageInstanceLeft.McInstance.JsonObject;
|
||||
}
|
||||
|
||||
// 重命名主文件夹
|
||||
FileSystem.RenameDirectory(oldPath, tempName);
|
||||
FileSystem.RenameDirectory(tempPath, newName);
|
||||
// 清理 ini 缓存
|
||||
ModBase.IniClearCache(Path.Combine(PageInstanceLeft.McInstance.PathIndie, "options.txt"));
|
||||
// 重命名 Jar 文件与 natives 文件夹
|
||||
// 不能进行遍历重命名,否则在实例名很短的时候容易误伤其他文件(Meloong-Git/#6443)
|
||||
if (Directory.Exists(Path.Combine(newPath, $"{oldName}-natives")))
|
||||
{
|
||||
if (isCaseChangedOnly)
|
||||
{
|
||||
FileSystem.RenameDirectory(Path.Combine(newPath, $"{oldName}-natives"), $"{oldName}natives_temp");
|
||||
FileSystem.RenameDirectory(Path.Combine(newPath, $"{oldName}-natives_temp"), $"{newName}-natives");
|
||||
}
|
||||
else
|
||||
{
|
||||
ModBase.DeleteDirectory(Path.Combine(newPath, $"{newName}-natives"));
|
||||
FileSystem.RenameDirectory(Path.Combine(newPath, $"{oldName}-natives"), $"{newName}-natives");
|
||||
}
|
||||
}
|
||||
|
||||
if (File.Exists(Path.Combine(newPath, $"{oldName}.jar")))
|
||||
{
|
||||
if (isCaseChangedOnly)
|
||||
{
|
||||
FileSystem.RenameFile(Path.Combine(newPath, $"{oldName}.jar"), $"{oldName}_temp.jar");
|
||||
FileSystem.RenameFile(Path.Combine(newPath, $"{oldName}_temp.jar"), $"{newName}.jar");
|
||||
}
|
||||
else
|
||||
{
|
||||
File.Delete(Path.Combine(newPath, $"{newName}.jar"));
|
||||
FileSystem.RenameFile(Path.Combine(newPath, $"{oldName}.jar"), $"{newName}.jar");
|
||||
}
|
||||
}
|
||||
|
||||
// 替换实例设置文件中的路径
|
||||
if (File.Exists(Path.Combine(newPath, "PCL", "Setup.ini")))
|
||||
ModBase.WriteFile(Path.Combine(newPath, "PCL", "Setup.ini"),
|
||||
ModBase.ReadFile(Path.Combine(newPath, "PCL", "Setup.ini")).Replace(oldPath, newPath));
|
||||
// 更改已选中的实例
|
||||
if ((ModBase.ReadIni(ModFolder.mcFolderSelected + "PCL.ini", "Version") ?? "") == (oldName ?? ""))
|
||||
ModBase.WriteIni(ModFolder.mcFolderSelected + "PCL.ini", "Version", newName);
|
||||
// 写入实例 Json,并删除旧的 Json
|
||||
try
|
||||
{
|
||||
jsonObject["id"] = newName;
|
||||
ModBase.WriteFile(Path.Combine(newPath, $"{newName}.json"), jsonObject.ToString());
|
||||
if (!isCaseChangedOnly)
|
||||
File.Delete(Path.Combine(newPath, $"{oldName}.json"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "重命名实例 Json 失败");
|
||||
}
|
||||
|
||||
// 刷新与提示
|
||||
HintService.Hint(Lang.Text("Instance.Overall.Name.RenameSuccess"), HintType.Success);
|
||||
PageInstanceLeft.McInstance = new McInstance(newName).Load();
|
||||
if (ModInstanceList.McMcInstanceSelected is not null &&
|
||||
ModInstanceList.McMcInstanceSelected.Equals(PageInstanceLeft.McInstance))
|
||||
ModBase.WriteIni(ModFolder.mcFolderSelected + "PCL.ini", "Version", newName);
|
||||
Reload();
|
||||
ModLoader.LoaderFolderRun(ModInstanceList.mcInstanceListLoader, ModFolder.mcFolderSelected,
|
||||
ModLoader.LoaderFolderRunType.ForceRun, 1, @"versions\");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"重命名实例失败",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Overall.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 实例图标
|
||||
private void ComboDisplayLogo_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (!(isLoad && ModAnimation.AniControlEnabled == 0))
|
||||
return;
|
||||
// 选择 自定义 时修改图片
|
||||
try
|
||||
{
|
||||
if (ReferenceEquals(ComboDisplayLogo.SelectedItem, ItemDisplayLogoCustom))
|
||||
{
|
||||
var fileName = SystemDialogs.SelectFile(Lang.Text("Instance.Overall.Icon.SelectFile.Filter"), Lang.Text("Instance.Overall.Icon.SelectFile.Title"));
|
||||
if (string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
Reload(); // 还原选项
|
||||
return;
|
||||
}
|
||||
|
||||
ModBase.CopyFile(fileName, PageInstanceLeft.McInstance.PathInstance + @"PCL\Logo.png");
|
||||
}
|
||||
else
|
||||
{
|
||||
File.Delete(PageInstanceLeft.McInstance.PathInstance + @"PCL\Logo.png");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"更改自定义实例图标失败({PageInstanceLeft.McInstance.Name})",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Overall.Error.OperationFailed"));
|
||||
}
|
||||
|
||||
// 进行更改
|
||||
try
|
||||
{
|
||||
string newLogo = ((MyComboBoxItem)ComboDisplayLogo.SelectedItem).Tag?.ToString();
|
||||
States.Instance.LogoPath[PageInstanceLeft.McInstance.PathInstance] = newLogo;
|
||||
States.Instance.IsLogoCustom[PageInstanceLeft.McInstance.PathInstance] = !string.IsNullOrEmpty(newLogo);
|
||||
// 刷新显示
|
||||
ModBase.WriteIni(ModFolder.mcFolderSelected + "PCL.ini", "InstanceCache", ""); // 要求刷新缓存
|
||||
PageInstanceLeft.McInstance = new McInstance(PageInstanceLeft.McInstance.Name).Load();
|
||||
Reload();
|
||||
ModLoader.LoaderFolderRun(ModInstanceList.mcInstanceListLoader, ModFolder.mcFolderSelected,
|
||||
ModLoader.LoaderFolderRunType.ForceRun, 1, @"versions\");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"更改实例图标失败({PageInstanceLeft.McInstance.Name})",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Overall.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 收藏夹
|
||||
private void BtnDisplayStar_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
States.Instance.Starred[PageInstanceLeft.McInstance.PathInstance] = !PageInstanceLeft.McInstance.IsStar;
|
||||
PageInstanceLeft.McInstance = new McInstance(PageInstanceLeft.McInstance.Name).Load();
|
||||
Reload();
|
||||
ModInstanceList.mcInstanceListForceRefresh = true;
|
||||
ModLoader.LoaderFolderRun(ModInstanceList.mcInstanceListLoader, ModFolder.mcFolderSelected,
|
||||
ModLoader.LoaderFolderRunType.ForceRun, 1, @"versions\");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"实例 {PageInstanceLeft.McInstance.Name} 收藏状态更改失败",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Overall.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 卡片:快捷方式
|
||||
|
||||
// 实例文件夹
|
||||
private void BtnFolderVersion_Click(object sender, MouseButtonEventArgs mouseButtonEventArgs)
|
||||
{
|
||||
OpenVersionFolder(PageInstanceLeft.McInstance);
|
||||
}
|
||||
|
||||
public static void OpenVersionFolder(McInstance version)
|
||||
{
|
||||
ModBase.OpenExplorer(version.PathInstance);
|
||||
}
|
||||
|
||||
// 存档文件夹
|
||||
private void BtnFolderSaves_Click(object sender, MouseButtonEventArgs mouseButtonEventArgs)
|
||||
{
|
||||
var folderPath = PageInstanceLeft.McInstance.PathIndie + @"saves\";
|
||||
Directory.CreateDirectory(folderPath);
|
||||
ModBase.OpenExplorer(folderPath);
|
||||
}
|
||||
|
||||
// Mod 文件夹
|
||||
private void BtnFolderMods_Click(object sender, MouseButtonEventArgs mouseButtonEventArgs)
|
||||
{
|
||||
var folderPath = PageInstanceLeft.McInstance.PathIndie + @"mods\";
|
||||
Directory.CreateDirectory(folderPath);
|
||||
ModBase.OpenExplorer(folderPath);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 卡片:管理
|
||||
|
||||
// 导出启动脚本
|
||||
private void BtnManageScript_Click(object sender, MouseButtonEventArgs mouseButtonEventArgs)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 弹窗要求指定脚本的保存位置
|
||||
var savePath = SystemDialogs.SelectSaveFile(Lang.Text("Instance.Overall.Script.SelectSaveTitle"), "启动 " + PageInstanceLeft.McInstance.Name + ".bat",
|
||||
Lang.Text("Instance.Overall.Script.FileFilter"));
|
||||
if (string.IsNullOrEmpty(savePath))
|
||||
return;
|
||||
// 检查中断(等玩家选完弹窗指不定任务就结束了呢……)
|
||||
if (ModLaunch.mcLaunchLoader.State == ModBase.LoadState.Loading)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Overall.Script.WaitForLaunchTask"), HintType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成脚本
|
||||
if (ModLaunch.McLaunchStart(new ModLaunch.McLaunchOptions
|
||||
{ SaveBatch = savePath, instance = PageInstanceLeft.McInstance }))
|
||||
{
|
||||
if (ModProfile.selectedProfile.Type == ModLaunch.McLoginType.Legacy)
|
||||
HintService.Hint(Lang.Text("Instance.Overall.Script.Exporting"));
|
||||
else
|
||||
HintService.Hint(Lang.Text("Instance.Overall.Script.ExportingWarning"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"导出启动脚本失败({PageInstanceLeft.McInstance.Name})",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Overall.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 补全文件
|
||||
private void BtnManageCheck_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 忽略文件检查提示
|
||||
if ((bool)ModLibrary.ShouldIgnoreFileCheck(PageInstanceLeft.McInstance))
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Overall.Repair.DisableVerificationHint"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 重复任务检查
|
||||
var taskName = PageInstanceLeft.McInstance.Name + " " + Lang.Text("Instance.Overall.Repair.TaskName");
|
||||
foreach (var OngoingLoader in ModLoader.loaderTaskbar)
|
||||
{
|
||||
if ((OngoingLoader.name ?? "") != (taskName ?? ""))
|
||||
continue;
|
||||
HintService.Hint(Lang.Text("Instance.Overall.Repair.Processing"), HintType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 启动
|
||||
var loader = new ModLoader.LoaderCombo<string>(taskName,
|
||||
ModDownload.DlClientFix(PageInstanceLeft.McInstance, true,
|
||||
ModDownload.AssetsIndexExistsBehaviour.AlwaysDownload));
|
||||
loader.OnStateChanged = _ =>
|
||||
{
|
||||
switch (loader.State)
|
||||
{
|
||||
case ModBase.LoadState.Finished:
|
||||
{
|
||||
HintService.Hint(
|
||||
Lang.Text("Instance.Overall.Repair.Success.WithTaskName", taskName), HintType.Success);
|
||||
break;
|
||||
}
|
||||
case ModBase.LoadState.Failed:
|
||||
{
|
||||
HintService.Hint(
|
||||
Lang.Text("Instance.Overall.Repair.Failed.WithDetail", taskName, loader.Error.ToString()),
|
||||
HintType.Error);
|
||||
break;
|
||||
}
|
||||
case ModBase.LoadState.Aborted:
|
||||
{
|
||||
HintService.Hint(
|
||||
Lang.Text("Instance.Overall.Repair.Cancelled.WithTaskName", taskName));
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
loader.Start(PageInstanceLeft.McInstance.Name);
|
||||
ModLoader.LoaderTaskbarAdd(loader);
|
||||
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
|
||||
ModMain.frmMain.BtnExtraDownload.Ribble();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"尝试补全文件失败({PageInstanceLeft.McInstance.Name})",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Overall.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 重置
|
||||
private void BtnManageRestore_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentVersion = PageInstanceLeft.McInstance.Info;
|
||||
if (!(currentVersion.Drop == 99) &&
|
||||
McVersionComparer.CompareVersion(currentVersion.VanillaName, "1.5.2") == -1 && currentVersion.HasForge)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Overall.Reset.NotSupported"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentVersion.HasQuilt)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Overall.Reset.QuiltUnsupported"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 确认操作
|
||||
if (ModMain.MyMsgBox(
|
||||
Lang.Text("Instance.Overall.Reset.ConfirmMessage", PageInstanceLeft.McInstance.Name), Lang.Text("Instance.Overall.Reset.ConfirmTitle"), Lang.Text("Common.Action.Confirm"), Lang.Text("Common.Action.Cancel")) == 2)
|
||||
return;
|
||||
|
||||
// 备份实例核心文件
|
||||
ModBase.CopyFile(PageInstanceLeft.McInstance.PathInstance + PageInstanceLeft.McInstance.Name + ".json",
|
||||
PageInstanceLeft.McInstance.PathInstance + @"PCLInstallBackups\" + PageInstanceLeft.McInstance.Name +
|
||||
".json");
|
||||
ModBase.CopyFile(PageInstanceLeft.McInstance.PathInstance + PageInstanceLeft.McInstance.Name + ".jar",
|
||||
PageInstanceLeft.McInstance.PathInstance + @"PCLInstallBackups\" + PageInstanceLeft.McInstance.Name +
|
||||
".jar");
|
||||
// 提交安装申请
|
||||
var request = new ModDownloadLib.McInstallRequest
|
||||
{
|
||||
targetInstanceName = PageInstanceLeft.McInstance.Name,
|
||||
targetInstanceFolder = $@"{ModFolder.mcFolderSelected}versions\{PageInstanceLeft.McInstance.Name}\",
|
||||
minecraftName = currentVersion.VanillaName,
|
||||
optiFineEntry = currentVersion.HasOptiFine
|
||||
? new ModDownload.DlOptiFineListEntry
|
||||
{
|
||||
Inherit = currentVersion.VanillaName,
|
||||
DisplayName = currentVersion.VanillaName + " " + currentVersion.OptiFine
|
||||
}
|
||||
: null,
|
||||
forgeEntry = currentVersion.HasForge
|
||||
? new ModDownload.DlForgeVersionEntry(currentVersion.Forge, null, currentVersion.VanillaName)
|
||||
{ Category = "installer" }
|
||||
: null,
|
||||
forgeVersion = currentVersion.HasForge ? currentVersion.Forge : null,
|
||||
neoForgeVersion = currentVersion.HasNeoForge ? currentVersion.NeoForge : null,
|
||||
cleanroomVersion = currentVersion.HasCleanroom ? currentVersion.Cleanroom : null,
|
||||
fabricVersion = currentVersion.HasFabric ? currentVersion.Fabric : null,
|
||||
liteLoaderEntry = currentVersion.HasLiteLoader
|
||||
? new ModDownload.DlLiteLoaderListEntry { Inherit = currentVersion.VanillaName }
|
||||
: null,
|
||||
legacyFabricVersion = currentVersion.HasLegacyFabric ? currentVersion.LegacyFabric : null
|
||||
};
|
||||
// .MinecraftJson = CurrentVersion.McName,
|
||||
if (!ModDownloadLib.McInstall(request, Lang.Text("Common.Action.Reset")))
|
||||
return;
|
||||
ModMain.frmMain.PageChange(new FormMain.PageStackData { page = FormMain.PageType.Launch });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"重置实例 {PageInstanceLeft.McInstance.Name} 失败",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Overall.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 测试游戏
|
||||
private void BtnManageTest_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
ModLaunch.McLaunchStart(new ModLaunch.McLaunchOptions
|
||||
{ instance = PageInstanceLeft.McInstance, IsTest = true });
|
||||
ModMain.frmMain.PageChange(FormMain.PageType.Launch);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"测试游戏失败",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Overall.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 删除实例
|
||||
// 修改此代码时,同时修改 PageSelectRight 中的代码
|
||||
private void BtnManageDelete_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var isShiftPressed = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
|
||||
|
||||
var isIsolatedInstance =
|
||||
PageInstanceLeft.McInstance.state != McInstanceState.Error &&
|
||||
!string.Equals(
|
||||
PageInstanceLeft.McInstance.PathIndie,
|
||||
ModFolder.mcFolderSelected,
|
||||
StringComparison.OrdinalIgnoreCase
|
||||
);
|
||||
|
||||
var confirmMessageKey = (isIsolatedInstance, isShiftPressed) switch
|
||||
{
|
||||
(true, true) => "Instance.Overall.Delete.ConfirmMessageIsolatedPermanent",
|
||||
(true, false) => "Instance.Overall.Delete.ConfirmMessageIsolated",
|
||||
(false, true) => "Instance.Overall.Delete.ConfirmMessagePermanent",
|
||||
(false, false) => "Instance.Overall.Delete.ConfirmMessage"
|
||||
};
|
||||
|
||||
var confirmResult = ModMain.MyMsgBox(
|
||||
Lang.Text(confirmMessageKey, PageInstanceLeft.McInstance.Name),
|
||||
Lang.Text("Instance.Overall.Delete.ConfirmTitle"),
|
||||
button2: Lang.Text("Common.Action.Cancel"),
|
||||
isWarn: isIsolatedInstance || isShiftPressed
|
||||
);
|
||||
|
||||
switch (confirmResult)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
var instancePath = PageInstanceLeft.McInstance.PathInstance;
|
||||
var instanceName = PageInstanceLeft.McInstance.Name;
|
||||
ModBase.IniClearCache(Path.Combine(PageInstanceLeft.McInstance.PathIndie, "options.txt"));
|
||||
((DynamicCacheConfigStorage)ConfigService.GetProvider(ConfigSource.GameInstance)).InvalidateCache(
|
||||
instancePath);
|
||||
if (isShiftPressed)
|
||||
{
|
||||
ModBase.DeleteDirectory(instancePath);
|
||||
HintService.Hint(Lang.Text("Instance.Overall.Delete.PermanentSuccess", instanceName),
|
||||
HintType.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
FileSystem.DeleteDirectory(instancePath, UIOption.OnlyErrorDialogs,
|
||||
RecycleOption.SendToRecycleBin);
|
||||
HintService.Hint(Lang.Text("Instance.Overall.Delete.RecycleBinSuccess", instanceName),
|
||||
HintType.Success);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ModLoader.LoaderFolderRun(ModInstanceList.mcInstanceListLoader, ModFolder.mcFolderSelected,
|
||||
ModLoader.LoaderFolderRunType.ForceRun, 1, @"versions\");
|
||||
ModMain.frmMain.PageBack();
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
ModBase.Log(ex, "删除实例 " + PageInstanceLeft.McInstance.Name + " 被主动取消");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"删除实例 {PageInstanceLeft.McInstance.Name} 失败",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Overall.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 修补核心
|
||||
private void BtnManagePatch_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
switch (ModMain.MyMsgBox(
|
||||
Lang.Text("Instance.Overall.Patch.ConfirmMessage", PageInstanceLeft.McInstance.Name),
|
||||
Lang.Text("Instance.Overall.Patch.ConfirmTitle"), button2: Lang.Text("Common.Action.Cancel")))
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
var userInput = SystemDialogs.SelectFile(Lang.Text("Instance.Overall.Patch.SelectFile.Filter"), Lang.Text("Instance.Overall.Patch.SelectFile.Title"));
|
||||
if (userInput is null || string.IsNullOrWhiteSpace(userInput))
|
||||
return;
|
||||
HintService.Hint(Lang.Text("Instance.Overall.Patch.Patching"));
|
||||
ModBase.RunInNewThread(() =>
|
||||
{
|
||||
var core = new GameCore(PageInstanceLeft.McInstance.PathInstance + PageInstanceLeft.McInstance.Name +
|
||||
".jar");
|
||||
core.AddToCore(userInput);
|
||||
HintService.Hint(Lang.Text("Instance.Overall.Patch.Success"), HintType.Success);
|
||||
Config.Instance.DisableAssetVerifyV2[PageInstanceLeft.McInstance.PathInstance] = true;
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<local:MyPageRight
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" x:Class="PCL.PageInstanceSaves"
|
||||
PanScroll="{Binding ElementName=PanBack}" Grid.IsSharedSizeScope="True">
|
||||
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
|
||||
<Grid>
|
||||
<local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" Margin="40" x:Name="PanNoWorld">
|
||||
<Grid Margin="20,17">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="0" Grid.ColumnSpan="4" Margin="0,0,0,9" HorizontalAlignment="Center"
|
||||
Text="{DynamicResource Instance.Saves.Empty.Title}" FontSize="19"
|
||||
UseLayoutRounding="True" SnapsToDevicePixels="True"
|
||||
Foreground="{DynamicResource ColorBrush3}" />
|
||||
<Rectangle Grid.Row="1" Grid.ColumnSpan="4" HorizontalAlignment="Stretch" Height="2"
|
||||
Fill="{DynamicResource ColorBrush3}" />
|
||||
<TextBlock Grid.Row="2" Grid.ColumnSpan="4" Margin="10,15,10,5"
|
||||
Text="{DynamicResource Instance.Saves.Empty.Message}"
|
||||
TextWrapping="Wrap" HorizontalAlignment="Center" />
|
||||
<local:MyButton Grid.Row="3" Grid.Column="1" Height="35" HorizontalAlignment="Center"
|
||||
Click="BtnOpenFolder_Click"
|
||||
MinWidth="140" Text="{DynamicResource Instance.Saves.OpenFolder}"
|
||||
Margin="10,10,10,0"
|
||||
Padding="13,0" ColorType="Highlight" />
|
||||
<local:MyButton Grid.Row="3" Grid.Column="2" Height="35" HorizontalAlignment="Center"
|
||||
Click="BtnDownloadNew_Click"
|
||||
MinWidth="140" Text="{DynamicResource Instance.Resource.DownloadNew}"
|
||||
Margin="10,10,10,0"
|
||||
Padding="13,0" />
|
||||
<local:MyButton Grid.Row="3" Grid.Column="3" Height="35" HorizontalAlignment="Center"
|
||||
Click="BtnPaste_Click"
|
||||
MinWidth="140" Text="{DynamicResource Instance.Saves.PasteFile}"
|
||||
Margin="10,10,10,0"
|
||||
Padding="13,0" />
|
||||
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
<StackPanel Orientation="Vertical" Margin="25,10,25,10" x:Name="PanContent">
|
||||
|
||||
<local:MySearchBox Margin="0,15" HintText="{DynamicResource Instance.Saves.SearchHint}"
|
||||
x:Name="SearchBox" />
|
||||
|
||||
<local:MyCard Margin="0,0,0,15" Title="{DynamicResource Instance.Saves.QuickActions}">
|
||||
<Grid Height="35" Margin="25,40,15,20">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<local:MyButton Grid.Column="0" MinWidth="140"
|
||||
Text="{DynamicResource Instance.Saves.OpenFolder}" Padding="13,0"
|
||||
Margin="0,0,20,0" Click="BtnOpenFolder_Click"
|
||||
HorizontalAlignment="Left" ColorType="Highlight" />
|
||||
<local:MyButton Grid.Column="1" MinWidth="140"
|
||||
Text="{DynamicResource Instance.Resource.DownloadNew}" Padding="13,0"
|
||||
Margin="0,0,20,0" Click="BtnDownloadNew_Click"
|
||||
HorizontalAlignment="Left" />
|
||||
<local:MyButton Grid.Column="2" MinWidth="140"
|
||||
Text="{DynamicResource Instance.Saves.PasteFile}" Padding="13,0"
|
||||
Margin="0,0,20,0" Click="BtnPaste_Click"
|
||||
HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
|
||||
<local:MyCard Margin="0,0,0,15" x:Name="PanListBack" VerticalAlignment="Top"
|
||||
Title="{DynamicResource Instance.Saves.ListTitle}"
|
||||
MinHeight="55">
|
||||
<Grid>
|
||||
<StackPanel Margin="0,13,15,0" Orientation="Horizontal" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right" Height="28">
|
||||
<local:MyIconTextButton x:Name="BtnSort" Text="{DynamicResource Instance.Saves.SortBy}"
|
||||
SvgIcon="lucide/arrow-up-down" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<StackPanel Margin="20,48,18,22" Name="PanList" VerticalAlignment="Top" />
|
||||
</local:MyCard>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</local:MyScrollViewer>
|
||||
</local:MyPageRight>
|
||||
@@ -0,0 +1,573 @@
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
using Microsoft.VisualBasic.FileIO;
|
||||
using PCL.Core.App.Localization;
|
||||
using PCL.Core.UI;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageInstanceSaves : IRefreshable
|
||||
{
|
||||
private readonly DispatcherTimer fileSystemRefreshTimer;
|
||||
private readonly DispatcherTimer searchTimer;
|
||||
private FileSystemWatcher fileSystemWatcher;
|
||||
private bool isLoad;
|
||||
|
||||
private object quickPlayFeature = false;
|
||||
|
||||
private List<string> saveFolders = new();
|
||||
private string worldPath;
|
||||
|
||||
public PageInstanceSaves()
|
||||
{
|
||||
InitializeComponent();
|
||||
fileSystemRefreshTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100d) };
|
||||
searchTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100d) };
|
||||
Loaded += PageSetupLaunch_Loaded;
|
||||
Unloaded += Page_Unloaded;
|
||||
fileSystemRefreshTimer.Tick += FileSystemRefreshTimer_Tick;
|
||||
searchTimer.Tick += SearchTimer_Tick;
|
||||
SearchBox.TextChanged += SearchRun;
|
||||
}
|
||||
|
||||
void IRefreshable.Refresh()
|
||||
{
|
||||
RefreshSelf();
|
||||
}
|
||||
|
||||
private void RefreshSelf()
|
||||
{
|
||||
Refresh();
|
||||
CheckQuickPlay();
|
||||
}
|
||||
|
||||
public static void Refresh()
|
||||
{
|
||||
if (ModMain.frmInstanceSaves is not null)
|
||||
ModMain.frmInstanceSaves.Reload();
|
||||
ModMain.frmInstanceLeft.ItemWorld.Checked = true;
|
||||
HintService.Hint(Lang.Text("Instance.Saves.Status.Refreshing"), log: false);
|
||||
}
|
||||
|
||||
private void PageSetupLaunch_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 重复加载部分
|
||||
PanBack.ScrollToHome();
|
||||
worldPath = PageInstanceLeft.McInstance.PathIndie + @"saves\";
|
||||
if (!Directory.Exists(worldPath))
|
||||
Directory.CreateDirectory(worldPath);
|
||||
Reload();
|
||||
|
||||
// 非重复加载部分
|
||||
if (isLoad)
|
||||
return;
|
||||
isLoad = true;
|
||||
CheckQuickPlay();
|
||||
|
||||
// 初始化文件系统监视器和排序按钮
|
||||
SetupFileSystemWatcher();
|
||||
BtnSort.Click += BtnSortClick;
|
||||
SetSortMethod(_currentSortMethod);
|
||||
}
|
||||
|
||||
private string GetFolderNameFromPath(string fullPath)
|
||||
{
|
||||
return string.IsNullOrEmpty(fullPath) ? "" :
|
||||
fullPath.EndsWith(@"\") ? new DirectoryInfo(fullPath).Parent?.Name : new DirectoryInfo(fullPath).Name;
|
||||
}
|
||||
|
||||
private string GetFileNameFromPath(string fullPath)
|
||||
{
|
||||
return Path.GetFileName(fullPath);
|
||||
}
|
||||
|
||||
private void SetupFileSystemWatcher()
|
||||
{
|
||||
if (fileSystemWatcher is not null) fileSystemWatcher.Dispose();
|
||||
|
||||
// 确保目录存在
|
||||
if (!Directory.Exists(worldPath))
|
||||
Directory.CreateDirectory(worldPath);
|
||||
|
||||
fileSystemWatcher = new FileSystemWatcher();
|
||||
fileSystemWatcher.Path = worldPath;
|
||||
fileSystemWatcher.IncludeSubdirectories = false;
|
||||
fileSystemWatcher.NotifyFilter = NotifyFilters.DirectoryName | NotifyFilters.LastWrite;
|
||||
|
||||
fileSystemWatcher.Created += OnFileSystemChanged;
|
||||
fileSystemWatcher.Deleted += OnFileSystemChanged;
|
||||
fileSystemWatcher.Renamed += OnFileSystemChanged;
|
||||
|
||||
fileSystemWatcher.EnableRaisingEvents = true;
|
||||
}
|
||||
|
||||
private void OnFileSystemChanged(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
fileSystemRefreshTimer.Stop();
|
||||
fileSystemRefreshTimer.Start();
|
||||
}
|
||||
|
||||
private void FileSystemRefreshTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
fileSystemRefreshTimer.Stop();
|
||||
ModBase.RunInUi(() => Reload(), true);
|
||||
}
|
||||
|
||||
private void Page_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (fileSystemWatcher is not null)
|
||||
{
|
||||
fileSystemWatcher.Created -= OnFileSystemChanged;
|
||||
fileSystemWatcher.Deleted -= OnFileSystemChanged;
|
||||
fileSystemWatcher.Renamed -= OnFileSystemChanged;
|
||||
fileSystemWatcher.Dispose();
|
||||
fileSystemWatcher = null;
|
||||
}
|
||||
|
||||
fileSystemRefreshTimer.Stop();
|
||||
searchTimer.Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保当前页面上的信息已正确显示。
|
||||
/// </summary>
|
||||
public void Reload()
|
||||
{
|
||||
ModAnimation.AniControlEnabled += 1;
|
||||
PanBack.ScrollToHome();
|
||||
LoadFileList();
|
||||
ModAnimation.AniControlEnabled -= 1;
|
||||
}
|
||||
|
||||
private void RefreshUI()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsSearching)
|
||||
{
|
||||
var resultCount = _searchResult is null ? 0 : _searchResult.Count;
|
||||
PanListBack.Title = Lang.Text("Instance.Saves.SearchResultTitle", resultCount.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
PanListBack.Title = Lang.Text("Instance.Saves.SaveListTitle", saveFolders.Count.ToString());
|
||||
}
|
||||
|
||||
if (saveFolders.Count == 0)
|
||||
{
|
||||
PanNoWorld.Visibility = Visibility.Visible;
|
||||
PanContent.Visibility = Visibility.Collapsed;
|
||||
PanNoWorld.UpdateLayout();
|
||||
}
|
||||
else
|
||||
{
|
||||
PanNoWorld.Visibility = Visibility.Collapsed;
|
||||
PanContent.Visibility = Visibility.Visible;
|
||||
PanContent.UpdateLayout();
|
||||
|
||||
var showingSaves = (IsSearching ? _searchResult : saveFolders).ToList();
|
||||
|
||||
if (showingSaves.Any())
|
||||
{
|
||||
var sortMethod = GetSortMethod(_currentSortMethod);
|
||||
showingSaves.Sort((a, b) => sortMethod(a, b));
|
||||
}
|
||||
|
||||
ModAnimation.AniControlEnabled += 1;
|
||||
PanList.Children.Clear();
|
||||
|
||||
foreach (var curFolder in showingSaves)
|
||||
{
|
||||
// 检查文件夹是否仍然存在
|
||||
if (!Directory.Exists(curFolder)) continue;
|
||||
|
||||
var saveLogo = Path.Combine(curFolder, "icon.png");
|
||||
var tmpCurFolder = curFolder;
|
||||
if (File.Exists(saveLogo))
|
||||
{
|
||||
var target =
|
||||
$@"{PageInstanceLeft.McInstance.PathInstance}PCL\ImgCache\{ModBase.GetStringMD5(saveLogo)}.png";
|
||||
ModBase.CopyFile(saveLogo, target);
|
||||
saveLogo = target;
|
||||
}
|
||||
else
|
||||
{
|
||||
saveLogo = ModBase.pathImage + "Icons/NoIcon.png";
|
||||
}
|
||||
|
||||
var worldItem = new MyListItem
|
||||
{
|
||||
Logo = saveLogo,
|
||||
Title = GetFolderNameFromPath(curFolder),
|
||||
Info =
|
||||
Lang.Text("Instance.Saves.CreationTime", Lang.Date(Directory.GetCreationTime(curFolder), "d"), Lang.Date(Directory.GetLastWriteTime(curFolder), "d")),
|
||||
Type = MyListItem.CheckType.Clickable
|
||||
};
|
||||
worldItem.Click += (_, _) => ModMain.frmMain.PageChange(new FormMain.PageStackData
|
||||
{ page = FormMain.PageType.VersionSaves, additional = (null, null, null, ModComp.CompLoaderType.Any, ModComp.CompType.Any, tmpCurFolder) });
|
||||
|
||||
var btnOpen = new MyIconButton
|
||||
{
|
||||
SvgIcon = "lucide/folder-open",
|
||||
ToolTip = Lang.Text("Common.Action.Open")
|
||||
};
|
||||
btnOpen.Click += (_, _) => ModBase.OpenExplorer(tmpCurFolder);
|
||||
var btnDelete = new MyIconButton
|
||||
{
|
||||
SvgIcon = "lucide/trash-2",
|
||||
ToolTip = Lang.Text("Common.Action.Delete")
|
||||
};
|
||||
btnDelete.Click += (_, _) =>
|
||||
{
|
||||
worldItem.IsEnabled = false;
|
||||
worldItem.Info = Lang.Text("Instance.Saves.Deleting");
|
||||
ModBase.RunInNewThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
FileSystem.DeleteDirectory(tmpCurFolder, UIOption.OnlyErrorDialogs,
|
||||
RecycleOption.SendToRecycleBin);
|
||||
HintService.Hint(Lang.Text("Instance.Saves.DeletedToRecycleBin"));
|
||||
ModBase.RunInUiWait(() => RemoveItem(worldItem));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
Lang.Text("Instance.Saves.DeleteFailed"),
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Instance.Saves.DeleteFailed"));
|
||||
ModBase.RunInUiWait(() => Reload());
|
||||
}
|
||||
});
|
||||
};
|
||||
var btnCopy = new MyIconButton
|
||||
{
|
||||
SvgIcon = "lucide/copy",
|
||||
ToolTip = Lang.Text("Common.Action.Copy")
|
||||
};
|
||||
btnCopy.Click += (_, _) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(tmpCurFolder))
|
||||
{
|
||||
Clipboard.SetFileDropList(new StringCollection { tmpCurFolder });
|
||||
HintService.Hint(Lang.Text("Instance.Saves.CopiedToClipboard"));
|
||||
HintService.Hint(Lang.Text("Instance.Saves.CopyPasteWarning"));
|
||||
}
|
||||
else
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Saves.FolderNotFound"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
Lang.Text("Instance.Saves.CopyFailed"),
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Instance.Saves.CopyFailed"));
|
||||
}
|
||||
};
|
||||
var btnInfo = new MyIconButton
|
||||
{
|
||||
SvgIcon = "lucide/info",
|
||||
ToolTip = Lang.Text("Instance.Saves.Details")
|
||||
};
|
||||
btnInfo.Click += (_, _) => ModMain.frmMain.PageChange(new FormMain.PageStackData
|
||||
{ page = FormMain.PageType.VersionSaves, additional = (null, null, null, ModComp.CompLoaderType.Any, ModComp.CompType.Any, tmpCurFolder) });
|
||||
|
||||
var btnLaunch = new MyIconButton
|
||||
{
|
||||
SvgIcon = "lucide/play",
|
||||
ToolTip = Lang.Text("Instance.Saves.QuickPlay")
|
||||
};
|
||||
btnLaunch.Click += (_, _) =>
|
||||
{
|
||||
var worldName = GetFileNameFromPath(tmpCurFolder);
|
||||
var launchOptions = new ModLaunch.McLaunchOptions
|
||||
{
|
||||
WorldName = worldName,
|
||||
instance = PageInstanceLeft.McInstance
|
||||
};
|
||||
ModLaunch.McLaunchStart(launchOptions);
|
||||
ModMain.frmMain.PageChange(new FormMain.PageStackData { page = FormMain.PageType.Launch });
|
||||
};
|
||||
if ((bool)quickPlayFeature)
|
||||
worldItem.Buttons = new[] { btnOpen, btnDelete, btnCopy, btnInfo, btnLaunch };
|
||||
else
|
||||
worldItem.Buttons = new[] { btnOpen, btnDelete, btnCopy, btnInfo };
|
||||
|
||||
PanList.Children.Add(worldItem);
|
||||
}
|
||||
|
||||
ModAnimation.AniControlEnabled -= 1;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
Lang.Text("Instance.Saves.RefreshUiFailed"),
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Instance.Saves.RefreshUiFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckQuickPlay()
|
||||
{
|
||||
try
|
||||
{
|
||||
var cur = new ModLaunch.LaunchArgument(PageInstanceLeft.McInstance);
|
||||
quickPlayFeature = cur.HasArguments("--quickPlaySingleplayer");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"检查存档快捷启动失败",
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Instance.Saves.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadFileList()
|
||||
{
|
||||
try
|
||||
{
|
||||
ModBase.Log("[World] 刷新存档文件");
|
||||
saveFolders.Clear();
|
||||
if (Directory.Exists(worldPath))
|
||||
saveFolders = Directory.EnumerateDirectories(worldPath).ToList();
|
||||
else
|
||||
saveFolders = new List<string>();
|
||||
|
||||
if (ModBase.modeDebug)
|
||||
ModBase.Log("[World] 共发现 " + saveFolders.Count + " 个存档文件夹", ModBase.LogLevel.Debug);
|
||||
PanList.Children.Clear();
|
||||
CheckQuickPlay();
|
||||
|
||||
if (ModBase.modeDebug)
|
||||
{
|
||||
if ((bool)quickPlayFeature)
|
||||
ModBase.Log("[World] 该实例支持存档快捷启动", ModBase.LogLevel.Debug);
|
||||
else
|
||||
ModBase.Log("[World] 该实例不支持存档快捷启动", ModBase.LogLevel.Debug);
|
||||
}
|
||||
|
||||
RefreshUI(); // 确保UI刷新
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
Lang.Text("Instance.Saves.LoadListFailed"),
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Instance.Saves.LoadListFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveItem(MyListItem item)
|
||||
{
|
||||
if (PanList.Children.IndexOf(item) == -1)
|
||||
return;
|
||||
PanList.Children.Remove(item);
|
||||
RefreshUI();
|
||||
}
|
||||
|
||||
private void BtnOpenFolder_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
ModBase.OpenExplorer(worldPath);
|
||||
}
|
||||
|
||||
private void BtnPaste_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var files = Clipboard.GetFileDropList();
|
||||
var loaders = new List<ModLoader.LoaderBase>();
|
||||
loaders.Add(new ModLoader.LoaderTask<int, int>("Copy saves", _ =>
|
||||
{
|
||||
var copied = 0;
|
||||
foreach (var i in files)
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(i))
|
||||
{
|
||||
if (Directory.Exists(worldPath + GetFolderNameFromPath(i)))
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Saves.DuplicateFolder", GetFolderNameFromPath(i)));
|
||||
}
|
||||
else
|
||||
{
|
||||
ModBase.CopyDirectory(i, worldPath + GetFolderNameFromPath(i));
|
||||
copied += 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Saves.SourceNotFolder"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
Lang.Text("Instance.Saves.PasteFolderFailed"),
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Instance.Saves.PasteFolderFailed"));
|
||||
}
|
||||
|
||||
if (copied > 0)
|
||||
HintService.Hint(Lang.Text("Instance.Saves.PastedCount", copied.ToString()), HintType.Success);
|
||||
ModBase.RunInUi(() => Reload());
|
||||
}));
|
||||
var loader = new ModLoader.LoaderCombo<int>($"{PageInstanceLeft.McInstance.Name} - {Lang.Text("Instance.Saves.CopySave")}", loaders)
|
||||
{ OnStateChanged = ModDownloadLib.LoaderStateChangedHintOnly };
|
||||
loader.Start(1);
|
||||
ModLoader.LoaderTaskbarAdd(loader);
|
||||
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
|
||||
ModMain.frmMain.BtnExtraDownload.Ribble();
|
||||
}
|
||||
|
||||
private void BtnDownloadNew_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
ModMain.frmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadWorld);
|
||||
PageComp.targetVersion = PageInstanceLeft.McInstance; // 将当前实例设置为筛选器
|
||||
}
|
||||
|
||||
#region 搜索和排序
|
||||
|
||||
private SortMethod _currentSortMethod = SortMethod.FileName;
|
||||
private List<string> _searchResult;
|
||||
|
||||
public bool IsSearching => !string.IsNullOrWhiteSpace(SearchBox.Text);
|
||||
|
||||
private enum SortMethod
|
||||
{
|
||||
FileName,
|
||||
CreateTime,
|
||||
ModifyTime
|
||||
}
|
||||
|
||||
private string GetSortName(SortMethod method)
|
||||
{
|
||||
switch (method)
|
||||
{
|
||||
case SortMethod.FileName:
|
||||
{
|
||||
return Lang.Text("Instance.Saves.SortFileName");
|
||||
}
|
||||
case SortMethod.CreateTime:
|
||||
{
|
||||
return Lang.Text("Instance.Saves.SortCreateTime");
|
||||
}
|
||||
case SortMethod.ModifyTime:
|
||||
{
|
||||
return Lang.Text("Instance.Saves.SortModifyTime");
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return Lang.Text("Instance.Saves.SortFileName");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetSortMethod(SortMethod target)
|
||||
{
|
||||
_currentSortMethod = target;
|
||||
BtnSort.Text = Lang.Text("Instance.Saves.SortBy", GetSortName(target));
|
||||
RefreshUI();
|
||||
}
|
||||
|
||||
private void BtnSortClick(object sender, EventArgs e)
|
||||
{
|
||||
var body = new ContextMenu();
|
||||
foreach (SortMethod i in Enum.GetValues(typeof(SortMethod)))
|
||||
{
|
||||
var item = new MyMenuItem();
|
||||
item.Header = GetSortName(i);
|
||||
item.Click += (_, _) => SetSortMethod(i);
|
||||
body.Items.Add(item);
|
||||
}
|
||||
|
||||
body.PlacementTarget = (UIElement)sender;
|
||||
body.Placement = PlacementMode.Bottom;
|
||||
body.IsOpen = true;
|
||||
}
|
||||
|
||||
private void SearchRun(object sender, EventArgs e)
|
||||
{
|
||||
searchTimer.Stop();
|
||||
searchTimer.Start();
|
||||
}
|
||||
|
||||
private void SearchTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
searchTimer.Stop();
|
||||
PerformSearch();
|
||||
}
|
||||
|
||||
private void PerformSearch()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsSearching)
|
||||
{
|
||||
var queryList = new List<ModBase.SearchEntry<string>>();
|
||||
foreach (var saveFolder in saveFolders)
|
||||
{
|
||||
var folderName = GetFolderNameFromPath(saveFolder);
|
||||
var searchSource = new List<ModBase.SearchSource>();
|
||||
searchSource.Add(new ModBase.SearchSource(folderName, 1d));
|
||||
queryList.Add(new ModBase.SearchEntry<string> { item = saveFolder, searchSource = searchSource });
|
||||
}
|
||||
|
||||
_searchResult = ModBase.Search(queryList, SearchBox.Text, ModBase.MaxLocalSearchDepth, 0.35d).Select(r => r.item).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
_searchResult = null;
|
||||
}
|
||||
|
||||
RefreshUI();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, Lang.Text("Instance.Saves.SearchError"));
|
||||
}
|
||||
}
|
||||
|
||||
private Func<string, string, int> GetSortMethod(SortMethod method)
|
||||
{
|
||||
switch (method)
|
||||
{
|
||||
case SortMethod.FileName:
|
||||
{
|
||||
return (a, b) => string.Compare(GetFolderNameFromPath(a), GetFolderNameFromPath(b),
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
case SortMethod.CreateTime:
|
||||
{
|
||||
return (a, b) => Directory.GetCreationTime(b).CompareTo(Directory.GetCreationTime(a));
|
||||
}
|
||||
case SortMethod.ModifyTime:
|
||||
{
|
||||
return (a, b) => Directory.GetLastWriteTime(b).CompareTo(Directory.GetLastWriteTime(a));
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return (a, b) => string.Compare(GetFolderNameFromPath(a), GetFolderNameFromPath(b),
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
<local:MyPageRight
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" x:Class="PCL.PageInstanceSavesDatapack"
|
||||
PanScroll="{Binding ElementName=PanBack}">
|
||||
<Grid>
|
||||
<Grid x:Name="PanAllBack">
|
||||
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled"
|
||||
x:Name="PanBack">
|
||||
<StackPanel x:Name="PanMain" Margin="25,10,25,10" Grid.IsSharedSizeScope="True">
|
||||
<local:MySearchBox Margin="0,15" HintText="{DynamicResource Instance.Resource.Search.Hint}" x:Name="SearchBox" />
|
||||
<local:MyCard Margin="0,0,0,15" x:Name="PanManage">
|
||||
<WrapPanel Margin="15,8,0,15">
|
||||
<local:MyButton x:Name="BtnManageOpen" MinWidth="110" Text="{DynamicResource Common.Action.OpenFolder}" Padding="13,7"
|
||||
Margin="0,7,15,0" HorizontalAlignment="Left" ColorType="Highlight" />
|
||||
<local:MyButton x:Name="BtnManageInstall" MinWidth="110" Text="{DynamicResource Instance.Resource.InstallFromFiles}" Padding="13,7"
|
||||
Margin="0,7,15,0" HorizontalAlignment="Left" />
|
||||
<local:MyButton x:Name="BtnManageDownload" MinWidth="110" Text="{DynamicResource Instance.Resource.Datapack.DownloadNew}" Padding="13,7"
|
||||
Margin="0,7,15,0" HorizontalAlignment="Left" />
|
||||
<local:MyButton x:Name="BtnManageSelectAll" MinWidth="110" Text="{DynamicResource Instance.Resource.SelectAll}" Padding="13,7"
|
||||
Margin="0,7,15,0" HorizontalAlignment="Left" />
|
||||
<local:MyButton x:Name="BtnManageInfoExport" MinWidth="110" Text="{DynamicResource Instance.Resource.ExportInfo}" Padding="13,7"
|
||||
Margin="0,7,15,0" HorizontalAlignment="Left" ToolTip="{DynamicResource Instance.Resource.Datapack.ExportInfo.ToolTip}" />
|
||||
</WrapPanel>
|
||||
</local:MyCard>
|
||||
<local:MyCard x:Name="PanListBack" VerticalAlignment="Top" Opacity="0" Margin="0,0,0,14" Title=" "
|
||||
MinHeight="55">
|
||||
<StackPanel x:Name="PanFilter" Margin="15,13,0,0" Orientation="Horizontal"
|
||||
VerticalAlignment="Top" HorizontalAlignment="Left" Height="28">
|
||||
<local:MyRadioButton Tag="0" ColorType="Highlight" VerticalAlignment="Center" Margin="2,0"
|
||||
Text="{DynamicResource Instance.Resource.Filter.All}" x:Name="BtnFilterAll" Checked="True" />
|
||||
<local:MyRadioButton Tag="1" ColorType="Highlight" VerticalAlignment="Center" Margin="2,0"
|
||||
Text="{DynamicResource Instance.Resource.Filter.Enabled}" x:Name="BtnFilterEnabled" />
|
||||
<local:MyRadioButton Tag="2" ColorType="Highlight" VerticalAlignment="Center" Margin="2,0"
|
||||
Text="{DynamicResource Instance.Resource.Filter.Disabled}" x:Name="BtnFilterDisabled" />
|
||||
<local:MyRadioButton Tag="3" ColorType="Highlight" VerticalAlignment="Center" Margin="2,0"
|
||||
Text="{DynamicResource Instance.Resource.Filter.Updatable}" x:Name="BtnFilterCanUpdate" />
|
||||
<local:MyRadioButton Tag="4" ColorType="Highlight" VerticalAlignment="Center" Margin="2,0"
|
||||
Text="{DynamicResource Instance.Resource.Filter.Error}" x:Name="BtnFilterError" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,13,15,0" Orientation="Horizontal" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right" Height="28">
|
||||
<local:MyIconTextButton x:Name="BtnSort" Text="{DynamicResource Instance.Resource.Sort.Text}"
|
||||
SvgIcon="lucide/arrow-up-down" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="20,48,18,22" Name="PanList" VerticalAlignment="Top" />
|
||||
</local:MyCard>
|
||||
</StackPanel>
|
||||
</local:MyScrollViewer>
|
||||
<local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" Margin="40" x:Name="PanEmpty">
|
||||
<StackPanel Margin="20,17">
|
||||
<TextBlock x:Name="TxtEmptyTitle" Margin="0,0,0,9" HorizontalAlignment="Center" Text="{DynamicResource Instance.Resource.Datapack.Empty.Title}"
|
||||
FontSize="19" UseLayoutRounding="True" SnapsToDevicePixels="True"
|
||||
Foreground="{DynamicResource ColorBrush3}" />
|
||||
<Rectangle HorizontalAlignment="Stretch" Height="2" Fill="{DynamicResource ColorBrush3}" />
|
||||
<TextBlock x:Name="TxtEmptyDescription" Margin="10,10,10,0"
|
||||
Text="{DynamicResource Instance.Resource.Datapack.Empty.Description}" TextWrapping="Wrap" />
|
||||
<WrapPanel Margin="10,20,10,5" HorizontalAlignment="Center" Orientation="Horizontal">
|
||||
<local:MyButton Height="35" x:Name="BtnHintInstall" MinWidth="130" Text="{DynamicResource Instance.Resource.InstallFromFiles}" Margin="5,0"
|
||||
Padding="12,0" ColorType="Highlight" />
|
||||
<local:MyButton Height="35" x:Name="BtnHintDownload" MinWidth="130" Text="{DynamicResource Instance.Resource.Datapack.DownloadNew}" Margin="5,0"
|
||||
Padding="12,0" />
|
||||
<local:MyButton Height="35" x:Name="BtnHintOpen" MinWidth="130" Text="{DynamicResource Common.Action.OpenFolder}" Margin="5,0"
|
||||
Padding="12,0" />
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
</Grid>
|
||||
<local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" Margin="40,0" SnapsToDevicePixels="True"
|
||||
x:Name="PanLoad" UseAnimation="False">
|
||||
<local:MyLoading Text="{DynamicResource Instance.Resource.Datapack.Loading}" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</local:MyCard>
|
||||
<local:MyCard x:Name="CardSelect" Visibility="Collapsed" Opacity="0"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="25,25,25,0" UseAnimation="False">
|
||||
<local:MyCard.RenderTransform>
|
||||
<TranslateTransform x:Name="TransSelect" Y="-10" />
|
||||
</local:MyCard.RenderTransform>
|
||||
<TextBlock x:Name="LabSelect" Text="" HorizontalAlignment="Center" VerticalAlignment="Top"
|
||||
Margin="9" Foreground="{DynamicResource ColorBrush2}" />
|
||||
<StackPanel Orientation="Horizontal" Margin="5,28,5,5">
|
||||
<local:MyIconTextButton x:Name="BtnSelectUpdate" Text="{DynamicResource Instance.Resource.Update}"
|
||||
LogoScale="1"
|
||||
SvgIcon="lucide/upload" />
|
||||
<local:MyIconTextButton x:Name="BtnSelectEnable" Text="{DynamicResource Instance.Resource.Enable}"
|
||||
LogoScale="1.05"
|
||||
SvgIcon="lucide/circle-check" />
|
||||
<local:MyIconTextButton x:Name="BtnSelectDisable" Text="{DynamicResource Instance.Resource.Disable}"
|
||||
LogoScale="1"
|
||||
SvgIcon="lucide/circle-minus" />
|
||||
<local:MyIconTextButton x:Name="BtnSelectFavorites" Text="{DynamicResource Instance.Resource.Favorite}"
|
||||
LogoScale="1"
|
||||
SvgIcon="lucide/heart" />
|
||||
<local:MyIconTextButton x:Name="BtnSelectShare" Text="{DynamicResource Instance.Resource.ShareSelected}"
|
||||
LogoScale="1"
|
||||
SvgIcon="lucide/share-2" />
|
||||
<local:MyIconTextButton x:Name="BtnSelectDelete" Text="{DynamicResource Common.Action.Delete}"
|
||||
LogoScale="0.96"
|
||||
SvgIcon="lucide/trash-2" />
|
||||
<local:MyIconTextButton x:Name="BtnSelectCancel" Text="{DynamicResource Instance.Resource.CancelSelection}"
|
||||
LogoScale="0.8"
|
||||
SvgIcon="lucide/x" />
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
</Grid>
|
||||
</local:MyPageRight>
|
||||
+1698
@@ -0,0 +1,1698 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
using Microsoft.VisualBasic.FileIO;
|
||||
using PCL.Core.App;
|
||||
using PCL.Core.UI;
|
||||
using PCL.Core.UI.Theme;
|
||||
using PCL.Network;
|
||||
using PCL.Network.Loaders;
|
||||
using FileSystem = Microsoft.VisualBasic.FileSystem;
|
||||
using PCL.Core.App.Localization;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageInstanceSavesDatapack : IRefreshable
|
||||
{
|
||||
#region 数据包信息缓存
|
||||
|
||||
private readonly Dictionary<string, (DateTime CreationTime, long Length)> datapackFileInfoCache = new();
|
||||
|
||||
// 获取数据包信息(带缓存)
|
||||
private (DateTime CreationTime, long Length) GetDatapackFileInfo(string path)
|
||||
{
|
||||
(DateTime CreationTime, long Length) cacheItem;
|
||||
if (datapackFileInfoCache.TryGetValue(path, out cacheItem)) return cacheItem;
|
||||
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
var newItem = (fileInfo.CreationTime, fileInfo.Length);
|
||||
if (!datapackFileInfoCache.ContainsKey(path)) datapackFileInfoCache.Add(path, newItem);
|
||||
return newItem;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "获取数据包信息失败: " + path);
|
||||
return (DateTime.MinValue, 0L);
|
||||
}
|
||||
}
|
||||
|
||||
// 页面关闭时清理缓存
|
||||
private void Page_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
datapackFileInfoCache.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 初始化
|
||||
|
||||
private readonly MyLocalCompItem.SwipeSelect currentSwipSelect;
|
||||
|
||||
public PageInstanceSavesDatapack()
|
||||
{
|
||||
currentSwipSelect = new MyLocalCompItem.SwipeSelect { TargetFrm = this };
|
||||
|
||||
InitializeComponent();
|
||||
Unloaded += Page_Unloaded;
|
||||
Loaded += (_, _) => PageOther_Loaded();
|
||||
LoaderInit();
|
||||
PageExit += UnselectedAllWithAnimation;
|
||||
// Handles
|
||||
Load.Click += Load_Click;
|
||||
BtnManageOpen.Click += BtnManageOpen_Click;
|
||||
BtnHintOpen.Click += BtnManageOpen_Click;
|
||||
BtnManageSelectAll.Click += BtnManageSelectAll_Click;
|
||||
BtnManageInstall.Click += BtnManageInstall_Click;
|
||||
BtnHintInstall.Click += BtnManageInstall_Click;
|
||||
BtnManageDownload.Click += BtnManageDownload_Click;
|
||||
BtnHintDownload.Click += BtnManageDownload_Click;
|
||||
BtnManageInfoExport.Click += BtnManageInfoExport_Click;
|
||||
Load.StateChanged += (_, _, _) => UnselectedAllWithAnimation();
|
||||
SearchBox.PreviewKeyDown += SearchBox_PreviewKeyDown;
|
||||
BtnFilterAll.Check += ChangeFilter;
|
||||
BtnFilterCanUpdate.Check += ChangeFilter;
|
||||
BtnFilterDisabled.Check += ChangeFilter;
|
||||
BtnFilterEnabled.Check += ChangeFilter;
|
||||
BtnFilterError.Check += ChangeFilter;
|
||||
BtnSort.Click += BtnSortClick;
|
||||
BtnSelectEnable.Click += BtnSelectEnable_Click;
|
||||
BtnSelectDisable.Click += BtnSelectDisable_Click;
|
||||
BtnSelectUpdate.Click += BtnSelectUpdate_Click;
|
||||
BtnSelectDelete.Click += BtnSelectDelete_Click;
|
||||
BtnSelectCancel.Click += BtnSelectCancel_Click;
|
||||
BtnSelectFavorites.Click += BtnSelectFavorites_Click;
|
||||
BtnSelectShare.Click += BtnSelectShare_Click;
|
||||
SearchBox.TextChanged += SearchRun;
|
||||
}
|
||||
|
||||
private ModLocalComp.CompLocalLoaderData GetRequireLoaderData()
|
||||
{
|
||||
var res = new ModLocalComp.CompLocalLoaderData();
|
||||
res.gameVersion = PageInstanceLeft.McInstance;
|
||||
res.frm = null;
|
||||
res.loaders = new[] { ModComp.CompLoaderType.Minecraft }.ToList();
|
||||
res.compPath = Path.Combine(PageInstanceSavesLeft.currentSave, "datapacks");
|
||||
res.compType = ModComp.CompType.DataPack;
|
||||
return res;
|
||||
}
|
||||
|
||||
private bool isLoad;
|
||||
|
||||
public void PageOther_Loaded()
|
||||
{
|
||||
if (ModMain.frmMain.pageLast.page != FormMain.PageType.CompDetail)
|
||||
PanBack.ScrollToHome();
|
||||
ModAnimation.AniControlEnabled += 1;
|
||||
selectedDatapacks.Clear();
|
||||
ReloadDatapackFileList();
|
||||
ChangeAllSelected(false);
|
||||
ModAnimation.AniControlEnabled -= 1;
|
||||
|
||||
// 非重复加载部分
|
||||
if (isLoad)
|
||||
return;
|
||||
isLoad = true;
|
||||
|
||||
ModMain.frmMain.KeyDown += FrmMain_KeyDown;
|
||||
// 调整按钮边距(这玩意儿没法从 XAML 改)
|
||||
foreach (MyRadioButton Btn in PanFilter.Children)
|
||||
Btn.LabText.Margin = new Thickness(-2, 0d, 8d, 0d);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新数据包列表。
|
||||
/// </summary>
|
||||
public void ReloadDatapackFileList(bool forceReload = false)
|
||||
{
|
||||
if (LoaderRun(forceReload
|
||||
? ModLoader.LoaderFolderRunType.ForceRun
|
||||
: ModLoader.LoaderFolderRunType.RunOnUpdated))
|
||||
{
|
||||
ModBase.Log("[System] 已刷新数据包列表");
|
||||
datapackFileInfoCache.Clear();
|
||||
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
Filter = FilterType.All;
|
||||
PanBack.ScrollToHome();
|
||||
SearchBox.Text = "";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 强制刷新
|
||||
private void RefreshSelf()
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void IRefreshable.Refresh()
|
||||
{
|
||||
RefreshSelf();
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
ModMain.frmInstanceSavesDatapack.ReloadDatapackFileList(true);
|
||||
ModBase.Log("[Datapack] 刷新数据包列表");
|
||||
}
|
||||
|
||||
private void LoaderInit()
|
||||
{
|
||||
PageLoaderInit(Load, PanLoad, PanAllBack, null, ModLocalComp.compResourceListLoader,
|
||||
_ => LoadUIFromLoaderOutput(), () => ModComp.CompType.DataPack, false);
|
||||
}
|
||||
|
||||
private void Load_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (ModLocalComp.compResourceListLoader.State == ModBase.LoadState.Failed)
|
||||
LoaderRun(ModLoader.LoaderFolderRunType.ForceRun);
|
||||
}
|
||||
|
||||
public bool LoaderRun(ModLoader.LoaderFolderRunType type)
|
||||
{
|
||||
var loadPath = Path.Combine(PageInstanceSavesLeft.currentSave, "datapacks");
|
||||
return ModLoader.LoaderFolderRun(ModLocalComp.compResourceListLoader, loadPath, type,
|
||||
loaderInput: GetRequireLoaderData());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UI 化
|
||||
|
||||
/// <summary>
|
||||
/// 已加载的数据包 UI 缓存。Key 为数据包的 RawPath。
|
||||
/// </summary>
|
||||
public Dictionary<string, MyLocalCompItem> datapackItems = new();
|
||||
|
||||
/// <summary>
|
||||
/// 将加载器结果的数据包列表加载为 UI。
|
||||
/// </summary>
|
||||
private void LoadUIFromLoaderOutput()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 判断应该显示哪一个页面
|
||||
if (ModLocalComp.compResourceListLoader.output.Any())
|
||||
{
|
||||
PanBack.Visibility = Visibility.Visible;
|
||||
PanEmpty.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 根据组件类型设置 PanEmpty 的文本内容
|
||||
TxtEmptyTitle.Text = Lang.Text("Instance.Resource.Datapack.Empty.Title");
|
||||
TxtEmptyDescription.Text = Lang.Text("Instance.Resource.Datapack.Empty.Description");
|
||||
|
||||
PanEmpty.Visibility = Visibility.Visible;
|
||||
PanBack.Visibility = Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
|
||||
// 修改缓存
|
||||
datapackItems.Clear();
|
||||
var itemsToShow = ModLocalComp.compResourceListLoader.output.ToList();
|
||||
|
||||
foreach (var DatapackEntity in itemsToShow)
|
||||
datapackItems[DatapackEntity.RawPath] = BuildLocalCompItem(DatapackEntity);
|
||||
|
||||
// 显示结果
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
Filter = FilterType.All;
|
||||
SearchBox.Text = ""; // 这会触发结果刷新,所以需要在 DatapackItems 更新之后
|
||||
RefreshUI();
|
||||
SetSortMethod(SortMethod.CompName);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"加载数据包列表 UI 失败",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Saves.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
private MyLocalCompItem BuildLocalCompItem(ModLocalComp.LocalCompFile entry)
|
||||
{
|
||||
try
|
||||
{
|
||||
ModAnimation.AniControlEnabled += 1;
|
||||
var newItem = new MyLocalCompItem
|
||||
{
|
||||
SnapsToDevicePixels = true,
|
||||
Entry = entry,
|
||||
buttonHandler = BuildLocalCompItemBtnHandler,
|
||||
Checked = selectedDatapacks.Contains(entry.RawPath)
|
||||
};
|
||||
newItem.CurrentSwipe = currentSwipSelect;
|
||||
newItem.Tags = entry.Tags;
|
||||
entry.OnCompUpdate += _ => newItem.Refresh();
|
||||
newItem.Refresh();
|
||||
ModAnimation.AniControlEnabled -= 1;
|
||||
return newItem;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModAnimation.AniControlEnabled -= 1;
|
||||
ModBase.Log(ex, $"创建 UI 项失败:{entry.RawPath}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildLocalCompItemBtnHandler(MyLocalCompItem sender, EventArgs e)
|
||||
{
|
||||
// 点击事件
|
||||
sender.Changed += (ss, e) => CheckChanged((MyLocalCompItem)ss, e);
|
||||
|
||||
// 文件项的点击事件:切换选中状态
|
||||
sender.Click += (ss, e) =>
|
||||
{
|
||||
var s = (MyLocalCompItem)ss;
|
||||
s.Checked = !s.Checked;
|
||||
};
|
||||
|
||||
// 图标按钮
|
||||
var btnOpen = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/folder-open", Tag = sender };
|
||||
btnOpen.ToolTip = Lang.Text("Instance.Saves.OpenFileLocation");
|
||||
ToolTipService.SetPlacement(btnOpen, PlacementMode.Center);
|
||||
ToolTipService.SetVerticalOffset(btnOpen, 30d);
|
||||
ToolTipService.SetHorizontalOffset(btnOpen, 2d);
|
||||
btnOpen.Click += (sender, e) => Open_Click((MyIconButton)sender, e);
|
||||
|
||||
var btnCont = new MyIconButton { LogoScale = 1d, SvgIcon = "lucide/info", Tag = sender };
|
||||
btnCont.ToolTip = Lang.Text("Instance.Saves.Detail");
|
||||
ToolTipService.SetPlacement(btnCont, PlacementMode.Center);
|
||||
ToolTipService.SetVerticalOffset(btnCont, 30d);
|
||||
ToolTipService.SetHorizontalOffset(btnCont, 2d);
|
||||
btnCont.Click += Info_Click;
|
||||
sender.MouseRightButtonUp += Info_Click;
|
||||
|
||||
var btnDelete = new MyIconButton { LogoScale = 1d, SvgIcon = "lucide/trash-2", Tag = sender };
|
||||
btnDelete.ToolTip = Lang.Text("Common.Action.Delete");
|
||||
ToolTipService.SetPlacement(btnDelete, PlacementMode.Center);
|
||||
ToolTipService.SetVerticalOffset(btnDelete, 30d);
|
||||
ToolTipService.SetHorizontalOffset(btnDelete, 2d);
|
||||
btnDelete.Click += (sender, e) => Delete_Click((MyIconButton)sender, e);
|
||||
|
||||
if (sender.Entry.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine)
|
||||
{
|
||||
var btnDisable = new MyIconButton { LogoScale = 1d, SvgIcon = "lucide/circle-minus", Tag = sender };
|
||||
btnDisable.ToolTip = Lang.Text("Instance.Resource.Disable");
|
||||
ToolTipService.SetPlacement(btnDisable, PlacementMode.Center);
|
||||
ToolTipService.SetVerticalOffset(btnDisable, 30d);
|
||||
ToolTipService.SetHorizontalOffset(btnDisable, 2d);
|
||||
btnDisable.Click += (ss, e) => Disable_Click((MyIconButton)ss, e);
|
||||
sender.Buttons = new[] { btnCont, btnOpen, btnDisable, btnDelete };
|
||||
}
|
||||
else if (sender.Entry.State == ModLocalComp.LocalCompFile.LocalFileStatus.Disabled)
|
||||
{
|
||||
var btnEnable = new MyIconButton { LogoScale = 1d, SvgIcon = "lucide/circle-check", Tag = sender };
|
||||
btnEnable.ToolTip = Lang.Text("Instance.Resource.Enable");
|
||||
ToolTipService.SetPlacement(btnEnable, PlacementMode.Center);
|
||||
ToolTipService.SetVerticalOffset(btnEnable, 30d);
|
||||
ToolTipService.SetHorizontalOffset(btnEnable, 2d);
|
||||
btnEnable.Click += (ss, e) => Enable_Click((MyIconButton)ss, e);
|
||||
sender.Buttons = new[] { btnCont, btnOpen, btnEnable, btnDelete };
|
||||
}
|
||||
else
|
||||
{
|
||||
sender.Buttons = new[] { btnCont, btnOpen, btnDelete };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新整个 UI。
|
||||
/// </summary>
|
||||
public void RefreshUI()
|
||||
{
|
||||
if (PanList is null)
|
||||
return;
|
||||
var showingDatapacks = (IsSearching ? searchResult : datapackItems.Values.Select(i => i.Entry))
|
||||
.Where(m => CanPassFilter(m)).ToList();
|
||||
|
||||
// 对显示的数据包进行排序
|
||||
if (showingDatapacks.Any())
|
||||
{
|
||||
var sortMethod = GetSortMethod(currentSortMethod);
|
||||
showingDatapacks.Sort((a, b) => sortMethod(a, b));
|
||||
}
|
||||
|
||||
// 重新列出列表
|
||||
ModAnimation.AniControlEnabled += 1;
|
||||
if (showingDatapacks.Any())
|
||||
{
|
||||
PanList.Visibility = Visibility.Visible;
|
||||
PanList.Children.Clear();
|
||||
foreach (var TargetDatapack in showingDatapacks)
|
||||
{
|
||||
if (!datapackItems.ContainsKey(TargetDatapack.RawPath))
|
||||
continue;
|
||||
var item = datapackItems[TargetDatapack.RawPath];
|
||||
|
||||
// 确保元素没有父容器,避免重复添加异常
|
||||
if (item.Parent is not null) ((Panel)item.Parent).Children.Remove(item);
|
||||
|
||||
ModStyle.MinecraftFormatter.SetColorfulTextLab(item.LabTitle.Text, item.LabTitle,
|
||||
ThemeService.IsDarkMode);
|
||||
ModStyle.MinecraftFormatter.SetColorfulTextLab(item.LabInfo.Text, item.LabInfo,
|
||||
ThemeService.IsDarkMode);
|
||||
item.Checked = selectedDatapacks.Contains(TargetDatapack.RawPath); // 更新选中状态
|
||||
PanList.Children.Add(item);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PanList.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
ModAnimation.AniControlEnabled -= 1;
|
||||
selectedDatapacks =
|
||||
new HashSet<string>(selectedDatapacks.Where(m =>
|
||||
showingDatapacks.Any(s => (s.RawPath ?? "") == (m ?? ""))));
|
||||
RefreshBars();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新顶栏和底栏显示。
|
||||
/// </summary>
|
||||
public void RefreshBars()
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Func<Task>(async () =>
|
||||
{
|
||||
// -----------------
|
||||
// 顶部栏
|
||||
// -----------------
|
||||
|
||||
// 计数
|
||||
var anyCount = 0;
|
||||
var enabledCount = 0;
|
||||
var disabledCount = 0;
|
||||
var updateCount = 0;
|
||||
var unavalialeCount = 0;
|
||||
var itemSource = (IsSearching ? searchResult : datapackItems.Values.Select(i => i.Entry)).ToArray();
|
||||
await Task.Run(() =>
|
||||
{
|
||||
foreach (var item in itemSource)
|
||||
{
|
||||
anyCount += 1;
|
||||
if (item.CanUpdate) updateCount += 1;
|
||||
if (item.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine) enabledCount += 1;
|
||||
if (item.State == ModLocalComp.LocalCompFile.LocalFileStatus.Disabled) disabledCount += 1;
|
||||
if (item.State == ModLocalComp.LocalCompFile.LocalFileStatus.Unavailable) unavalialeCount += 1;
|
||||
}
|
||||
});
|
||||
// 显示
|
||||
BtnFilterAll.Text = IsSearching ? Lang.Text("Instance.Resource.Filter.SearchResult") : Lang.Text("Instance.Resource.Filter.AllWithCount", anyCount);
|
||||
BtnFilterCanUpdate.Text = Lang.Text("Instance.Resource.Filter.UpdatableWithCount", updateCount);
|
||||
BtnFilterCanUpdate.Visibility = Filter == FilterType.CanUpdate || updateCount > 0
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
BtnFilterEnabled.Text = Lang.Text("Instance.Resource.Filter.EnabledWithCount", enabledCount);
|
||||
BtnFilterEnabled.Visibility = Filter == FilterType.Enabled || (enabledCount > 0 && enabledCount < anyCount)
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
BtnFilterDisabled.Text = Lang.Text("Instance.Resource.Filter.DisabledWithCount", disabledCount);
|
||||
BtnFilterDisabled.Visibility = Filter == FilterType.Disabled || disabledCount > 0
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
BtnFilterError.Text = Lang.Text("Instance.Resource.Filter.ErrorWithCount", unavalialeCount);
|
||||
BtnFilterError.Visibility = Filter == FilterType.Unavailable || unavalialeCount > 0
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
|
||||
// -----------------
|
||||
// 底部栏
|
||||
// -----------------
|
||||
|
||||
// 计数
|
||||
var newCount = selectedDatapacks.Count;
|
||||
var selected = newCount > 0;
|
||||
if (selected)
|
||||
LabSelect.Text = Lang.Text("Instance.Resource.SelectedCount", newCount);
|
||||
|
||||
// 按钮可用性
|
||||
if (selected)
|
||||
{
|
||||
var hasUpdate = false;
|
||||
var hasEnabled = false;
|
||||
var hasDisabled = false;
|
||||
var canFavoriteAndShare = true;
|
||||
|
||||
|
||||
// 检查是否所有选中的数据包都有有效的项目信息
|
||||
await Task.Run(() =>
|
||||
{
|
||||
foreach (var DatapackEntity in ModLocalComp.compResourceListLoader.output)
|
||||
if (selectedDatapacks.Contains(DatapackEntity.RawPath))
|
||||
{
|
||||
if (DatapackEntity.CanUpdate) hasUpdate = true;
|
||||
if (DatapackEntity.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine)
|
||||
hasEnabled = true;
|
||||
else if (DatapackEntity.State == ModLocalComp.LocalCompFile.LocalFileStatus.Disabled)
|
||||
hasDisabled = true;
|
||||
if (DatapackEntity.Comp is null || string.IsNullOrEmpty(DatapackEntity.Comp.Id))
|
||||
canFavoriteAndShare = false;
|
||||
}
|
||||
});
|
||||
|
||||
BtnSelectDisable.IsEnabled = hasEnabled;
|
||||
BtnSelectEnable.IsEnabled = hasDisabled;
|
||||
BtnSelectUpdate.IsEnabled = hasUpdate;
|
||||
BtnSelectFavorites.IsEnabled = canFavoriteAndShare;
|
||||
BtnSelectShare.IsEnabled = canFavoriteAndShare;
|
||||
}
|
||||
|
||||
// 更新显示状态
|
||||
if (ModAnimation.AniControlEnabled == 0)
|
||||
{
|
||||
PanListBack.Margin = new Thickness(0d, 0d, 0d, selected ? 95 : 15);
|
||||
if (selected)
|
||||
{
|
||||
// 仅在数量增加时播放出现/跳跃动画
|
||||
if (bottomBarShownCount >= newCount)
|
||||
{
|
||||
bottomBarShownCount = newCount;
|
||||
return;
|
||||
}
|
||||
|
||||
bottomBarShownCount = newCount;
|
||||
// 出现/跳跃动画
|
||||
CardSelect.Visibility = Visibility.Visible;
|
||||
ModAnimation.AniStart(
|
||||
new[]
|
||||
{
|
||||
ModAnimation.AaOpacity(CardSelect, 1d - CardSelect.Opacity, 60),
|
||||
ModAnimation.AaTranslateY(CardSelect, -27 - TransSelect.Y, 120,
|
||||
ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak)),
|
||||
ModAnimation.AaTranslateY(CardSelect, 3d, 150, 120,
|
||||
new ModAnimation.AniEaseInoutFluent(ModAnimation.AniEasePower.Weak)),
|
||||
ModAnimation.AaTranslateY(CardSelect, -1, 90, 270,
|
||||
new ModAnimation.AniEaseInoutFluent(ModAnimation.AniEasePower.Weak))
|
||||
}, "Datapack Sidebar");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 不重复播放隐藏动画
|
||||
if (bottomBarShownCount == 0)
|
||||
return;
|
||||
bottomBarShownCount = 0;
|
||||
// 隐藏动画
|
||||
ModAnimation.AniStart(
|
||||
new[]
|
||||
{
|
||||
ModAnimation.AaOpacity(CardSelect, -CardSelect.Opacity, 90),
|
||||
ModAnimation.AaTranslateY(CardSelect, -10 - TransSelect.Y, 90,
|
||||
ease: new ModAnimation.AniEaseInFluent(ModAnimation.AniEasePower.Weak)),
|
||||
ModAnimation.AaCode(() => CardSelect.Visibility = Visibility.Collapsed, after: true)
|
||||
}, "Datapack Sidebar");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ModAnimation.AniStop("Datapack Sidebar");
|
||||
bottomBarShownCount = newCount;
|
||||
if (selected)
|
||||
{
|
||||
CardSelect.Visibility = Visibility.Visible;
|
||||
CardSelect.Opacity = 1d;
|
||||
TransSelect.Y = -25;
|
||||
}
|
||||
else
|
||||
{
|
||||
CardSelect.Visibility = Visibility.Collapsed;
|
||||
CardSelect.Opacity = 0d;
|
||||
TransSelect.Y = -10;
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private int bottomBarShownCount;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 管理
|
||||
|
||||
/// <summary>
|
||||
/// 打开 datapacks 文件夹。
|
||||
/// </summary>
|
||||
private void BtnManageOpen_Click(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var datapackPath = Path.Combine(PageInstanceSavesLeft.currentSave, "datapacks");
|
||||
Directory.CreateDirectory(datapackPath);
|
||||
ModBase.OpenExplorer(datapackPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"打开 datapacks 文件夹失败",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Saves.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全选。
|
||||
/// </summary>
|
||||
private void BtnManageSelectAll_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
ChangeAllSelected(selectedDatapacks.Count < PanList.Children.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安装数据包。
|
||||
/// </summary>
|
||||
private void BtnManageInstall_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var fileList = SystemDialogs.SelectFiles(
|
||||
Lang.Text("Instance.Saves.Datapack.Install.FileDialog.Filter"),
|
||||
Lang.Text("Instance.Saves.Datapack.Install.FileDialog.Title"));
|
||||
if (fileList is null || fileList.Length == 0)
|
||||
return;
|
||||
InstallDatapackFiles(fileList);
|
||||
Refresh();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安装数据包文件。
|
||||
/// </summary>
|
||||
public static void InstallDatapackFiles(IEnumerable<string> filePathList)
|
||||
{
|
||||
if (!filePathList.Any())
|
||||
return;
|
||||
|
||||
var extension = filePathList.First().AfterLast(".").ToLower();
|
||||
|
||||
// 检查文件扩展名
|
||||
if (extension != "zip")
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Install.UnsupportedFormat", extension, Lang.Text("Download.Comp.Type.DataPack"), "zip"), HintType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查回收站
|
||||
if (filePathList.First().Contains(@":\$RECYCLE.BIN\"))
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Install.RestoreFromRecycleBin"), HintType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
ModBase.Log($"[System] 文件为 {extension} 格式,尝试作为数据包安装");
|
||||
|
||||
// 确认安装
|
||||
if (!(ModMain.frmMain.pageCurrent == FormMain.PageType.InstanceSetup &&
|
||||
ModMain.frmMain.PageCurrentSub == FormMain.PageSubType.VersionSavesDatapack))
|
||||
if (ModMain.MyMsgBox(Lang.Text("Instance.Saves.Datapack.Install.Message"),
|
||||
Lang.Text("Instance.Saves.Datapack.Install.Title"), Lang.Text("Common.Action.Confirm"),
|
||||
Lang.Text("Common.Action.Cancel")) != 1)
|
||||
return;
|
||||
|
||||
// 执行安装
|
||||
try
|
||||
{
|
||||
var datapackFolder = Path.Combine(PageInstanceSavesLeft.currentSave, "datapacks");
|
||||
Directory.CreateDirectory(datapackFolder);
|
||||
|
||||
foreach (var FilePath in filePathList)
|
||||
{
|
||||
var newFileName = ModBase.GetFileNameFromPath(FilePath);
|
||||
var destFile = datapackFolder + newFileName;
|
||||
|
||||
if (File.Exists(destFile))
|
||||
if (ModMain.MyMsgBox(Lang.Text("Instance.Resource.Install.OverwriteConfirm.Message", newFileName), Lang.Text("Instance.Resource.Install.OverwriteConfirm.Title"), Lang.Text("Common.Action.Overwrite"), Lang.Text("Common.Action.Cancel")) != 1)
|
||||
continue;
|
||||
|
||||
ModBase.CopyFile(FilePath, destFile);
|
||||
}
|
||||
|
||||
if (filePathList.Count() == 1)
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Install.SuccessSingle", ModBase.GetFileNameFromPath(filePathList.First())), HintType.Success);
|
||||
else
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Install.SuccessMultiple", filePathList.Count(), Lang.Text("Download.Comp.Type.DataPack")), HintType.Success);
|
||||
|
||||
// 刷新列表
|
||||
if (ModMain.frmMain.pageCurrent == FormMain.PageType.InstanceSetup &&
|
||||
ModMain.frmMain.PageCurrentSub == FormMain.PageSubType.VersionSavesDatapack)
|
||||
if (ModMain.frmInstanceSavesDatapack is not null)
|
||||
ModMain.frmInstanceSavesDatapack.ReloadDatapackFileList(true);
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"复制数据包文件失败",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Saves.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载数据包。
|
||||
/// </summary>
|
||||
private void BtnManageDownload_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var datapackPath = Path.Combine(PageInstanceSavesLeft.currentSave, "datapacks");
|
||||
Directory.CreateDirectory(datapackPath);
|
||||
PageDownloadCompDetail.cachedFolder[ModComp.CompType.DataPack] = datapackPath;
|
||||
ModMain.frmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadDataPack);
|
||||
PageComp.targetVersion = PageInstanceLeft.McInstance; // 将当前实例设置为筛选器
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出信息。
|
||||
/// </summary>
|
||||
private void BtnManageInfoExport_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var choice =
|
||||
ModMain.MyMsgBox(
|
||||
Lang.Text("Instance.Saves.Datapack.Export.Mode.Message"),
|
||||
Lang.Text("Instance.Resource.Export.Mode.Title"), Lang.Text("Instance.Resource.Export.Mode.Txt"), Lang.Text("Instance.Resource.Export.Mode.Csv"), Lang.Text("Common.Action.Cancel"));
|
||||
|
||||
void ExportText(string content, string fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var savePath =
|
||||
SystemDialogs.SelectSaveFile(Lang.Text("Instance.Resource.Export.SelectSaveLocation"), fileName, Lang.Text("Instance.Resource.Export.FilesFilter"));
|
||||
if (string.IsNullOrWhiteSpace(savePath)) return;
|
||||
File.WriteAllText(savePath, content, Encoding.UTF8);
|
||||
ModBase.OpenExplorer(savePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"导出数据包信息失败",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Saves.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
;
|
||||
switch (choice)
|
||||
{
|
||||
case 1: // TXT
|
||||
{
|
||||
var exportContent = new List<string>();
|
||||
foreach (var DatapackEntity in ModLocalComp.compResourceListLoader.output)
|
||||
exportContent.Add(DatapackEntity.FileName);
|
||||
ExportText(exportContent.Join("\r\n"),
|
||||
ModBase.GetFolderNameFromPath(PageInstanceSavesLeft.currentSave) + "的数据包信息.txt");
|
||||
break;
|
||||
}
|
||||
|
||||
case 2: // CSV
|
||||
{
|
||||
var exportContent = new List<string>();
|
||||
exportContent.Add("文件名,数据包名称,数据包版本,此版本更新时间,工程 ID,文件大小(字节),文件路径");
|
||||
foreach (var DatapackEntity in ModLocalComp.compResourceListLoader.output)
|
||||
exportContent.Add(
|
||||
$"{DatapackEntity.FileName},{DatapackEntity.Comp?.TranslatedName},{DatapackEntity.Version},{DatapackEntity.compFile?.ReleaseDate},{DatapackEntity.Comp?.Id},{GetDatapackFileInfo(DatapackEntity.path).Length},{DatapackEntity.path}");
|
||||
ExportText(exportContent.Join("\r\n"),
|
||||
ModBase.GetFolderNameFromPath(PageInstanceSavesLeft.currentSave) + "的数据包信息.csv");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 选择
|
||||
|
||||
/// <summary>
|
||||
/// 选择的数据包的路径。
|
||||
/// </summary>
|
||||
public HashSet<string> selectedDatapacks = new();
|
||||
|
||||
// 单项切换选择状态
|
||||
public void CheckChanged(MyLocalCompItem sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
if (ModAnimation.AniControlEnabled != 0)
|
||||
return;
|
||||
// 更新选择了的内容
|
||||
var selectedKey = sender.Entry.RawPath;
|
||||
if (sender.Checked)
|
||||
selectedDatapacks.Add(selectedKey);
|
||||
else
|
||||
selectedDatapacks.Remove(selectedKey);
|
||||
RefreshBars();
|
||||
}
|
||||
|
||||
// 切换所有项的选择状态
|
||||
private void ChangeAllSelected(bool value)
|
||||
{
|
||||
ModAnimation.AniControlEnabled += 1;
|
||||
selectedDatapacks.Clear();
|
||||
foreach (var Item in datapackItems.Values)
|
||||
{
|
||||
var shouldSelected = value && PanList.Children.Contains(Item);
|
||||
Item.Checked = shouldSelected;
|
||||
if (shouldSelected)
|
||||
selectedDatapacks.Add(Item.Entry.RawPath);
|
||||
}
|
||||
|
||||
ModAnimation.AniControlEnabled -= 1;
|
||||
RefreshBars();
|
||||
}
|
||||
|
||||
private void UnselectedAllWithAnimation()
|
||||
{
|
||||
var cacheAniControlEnabled = ModAnimation.AniControlEnabled;
|
||||
ModAnimation.AniControlEnabled = 0;
|
||||
ChangeAllSelected(false);
|
||||
ModAnimation.AniControlEnabled += cacheAniControlEnabled;
|
||||
}
|
||||
|
||||
private void FrmMain_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (!ReferenceEquals(ModMain.frmMain.pageRight, this))
|
||||
return;
|
||||
if ((Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) && e.Key == Key.A)
|
||||
ChangeAllSelected(true);
|
||||
}
|
||||
|
||||
private void SearchBox_PreviewKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
// Ctrl + A 会被搜索框捕获,导致无法全选,所以在按下 Ctrl + A 时转移焦点以便捕获
|
||||
if (SearchBox.Text.Any())
|
||||
return;
|
||||
if ((Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) && e.Key == Key.A)
|
||||
PanBack.Focus();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 筛选
|
||||
|
||||
public FilterType Filter
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
if (field == value)
|
||||
return;
|
||||
field = value;
|
||||
switch (value)
|
||||
{
|
||||
case FilterType.All:
|
||||
{
|
||||
BtnFilterAll.Checked = true;
|
||||
break;
|
||||
}
|
||||
case FilterType.Enabled:
|
||||
{
|
||||
BtnFilterEnabled.Checked = true;
|
||||
break;
|
||||
}
|
||||
case FilterType.Disabled:
|
||||
{
|
||||
BtnFilterDisabled.Checked = true;
|
||||
break;
|
||||
}
|
||||
case FilterType.CanUpdate:
|
||||
{
|
||||
BtnFilterCanUpdate.Checked = true;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
BtnFilterError.Checked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
RefreshUI();
|
||||
}
|
||||
} = FilterType.All;
|
||||
|
||||
public enum FilterType
|
||||
{
|
||||
All = 0,
|
||||
Enabled = 1,
|
||||
Disabled = 2,
|
||||
CanUpdate = 3,
|
||||
Unavailable = 4
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查该数据包项是否符合当前筛选的类别。
|
||||
/// </summary>
|
||||
private bool CanPassFilter(ModLocalComp.LocalCompFile checkingDatapack)
|
||||
{
|
||||
switch (Filter)
|
||||
{
|
||||
case FilterType.All:
|
||||
{
|
||||
return true;
|
||||
}
|
||||
case FilterType.Enabled:
|
||||
{
|
||||
return checkingDatapack.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine;
|
||||
}
|
||||
case FilterType.Disabled:
|
||||
{
|
||||
return checkingDatapack.State == ModLocalComp.LocalCompFile.LocalFileStatus.Disabled;
|
||||
}
|
||||
case FilterType.CanUpdate:
|
||||
{
|
||||
return checkingDatapack.CanUpdate;
|
||||
}
|
||||
case FilterType.Unavailable:
|
||||
{
|
||||
return checkingDatapack.State == ModLocalComp.LocalCompFile.LocalFileStatus.Unavailable;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 点击筛选项触发的改变
|
||||
private void ChangeFilter(MyRadioButton sender, bool raiseByMouse)
|
||||
{
|
||||
Filter = (FilterType)Convert.ToInt32(sender.Tag);
|
||||
RefreshUI();
|
||||
DoSort();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 排序
|
||||
|
||||
private SortMethod currentSortMethod = SortMethod.CompName;
|
||||
|
||||
private void SetSortMethod(SortMethod target)
|
||||
{
|
||||
currentSortMethod = target;
|
||||
BtnSort.Text = Lang.Text("Instance.Resource.Sort.Text", GetSortName(target));
|
||||
DoSort();
|
||||
}
|
||||
|
||||
private enum SortMethod
|
||||
{
|
||||
FileName,
|
||||
CompName,
|
||||
CreateTime,
|
||||
DatapackFileSize
|
||||
}
|
||||
|
||||
private string GetSortName(SortMethod method)
|
||||
{
|
||||
switch (method)
|
||||
{
|
||||
case SortMethod.FileName:
|
||||
{
|
||||
return Lang.Text("Instance.Resource.Sort.FileName");
|
||||
}
|
||||
case SortMethod.CompName:
|
||||
{
|
||||
return Lang.Text("Instance.Resource.Sort.ResourceName");
|
||||
}
|
||||
case SortMethod.CreateTime:
|
||||
{
|
||||
return Lang.Text("Instance.Resource.Sort.AddTime");
|
||||
}
|
||||
case SortMethod.DatapackFileSize:
|
||||
{
|
||||
return Lang.Text("Instance.Resource.Sort.FileSize");
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return Lang.Text("Instance.Resource.Sort.ResourceName");
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private void BtnSortClick(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
var body = new ContextMenu();
|
||||
foreach (SortMethod i in Enum.GetValues(typeof(SortMethod)))
|
||||
{
|
||||
var item = new MyMenuItem();
|
||||
item.Header = GetSortName(i);
|
||||
item.Click += (_, _) => SetSortMethod(i);
|
||||
body.Items.Add(item);
|
||||
}
|
||||
|
||||
body.PlacementTarget = (UIElement)sender;
|
||||
body.Placement = PlacementMode.Bottom;
|
||||
body.IsOpen = true;
|
||||
}
|
||||
|
||||
private readonly object sortLock = new();
|
||||
|
||||
private void DoSort()
|
||||
{
|
||||
lock (sortLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (PanList is null || PanList.Children.Count < 2)
|
||||
return;
|
||||
|
||||
// 将子元素转换为可排序的列表
|
||||
var items = PanList.Children.OfType<MyLocalCompItem>().ToList();
|
||||
var method = GetSortMethod(currentSortMethod);
|
||||
|
||||
// 分离有效和无效项(保持原始相对顺序)
|
||||
var invalid = items.Where(i => i.Entry is null).ToList();
|
||||
var valid = items.Except(invalid).ToList();
|
||||
// 仅对有效项进行排序
|
||||
valid.Sort((x, y) => method(x.Entry, y.Entry));
|
||||
// 合并保持无效项的原始顺序
|
||||
items = valid.Concat(invalid).ToList();
|
||||
|
||||
// 批量更新UI元素
|
||||
PanList.Children.Clear();
|
||||
items.ForEach(i => PanList.Children.Add(i));
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"执行排序时出错",
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Instance.Saves.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Func<ModLocalComp.LocalCompFile, ModLocalComp.LocalCompFile, int> GetSortMethod(SortMethod method)
|
||||
{
|
||||
switch (method)
|
||||
{
|
||||
case SortMethod.FileName:
|
||||
{
|
||||
return (a, b) => string.Compare(a.FileName, b.FileName, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
case SortMethod.CompName:
|
||||
{
|
||||
return (a, b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
case SortMethod.CreateTime:
|
||||
{
|
||||
return (a, b) =>
|
||||
{
|
||||
var aDate = GetDatapackFileInfo(a.path).CreationTime;
|
||||
var bDate = GetDatapackFileInfo(b.path).CreationTime;
|
||||
if (aDate == DateTime.MinValue && bDate == DateTime.MinValue)
|
||||
return string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (aDate == DateTime.MinValue) return 1;
|
||||
|
||||
if (bDate == DateTime.MinValue) return -1;
|
||||
return bDate.CompareTo(aDate);
|
||||
};
|
||||
}
|
||||
case SortMethod.DatapackFileSize:
|
||||
{
|
||||
return (a, b) =>
|
||||
{
|
||||
var aSize = GetDatapackFileInfo(a.path).Length;
|
||||
var bSize = GetDatapackFileInfo(b.path).Length;
|
||||
if (aSize == 0L && bSize == 0L)
|
||||
return string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (aSize == 0L) return 1;
|
||||
|
||||
if (bSize == 0L) return -1;
|
||||
return bSize.CompareTo(aSize);
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return (a, b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 下边栏
|
||||
|
||||
// 启用
|
||||
private void BtnSelectEnable_Click(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
ToggleDatapacks(
|
||||
ModLocalComp.compResourceListLoader.output.Where(m => selectedDatapacks.Contains(m.RawPath)).ToList(),
|
||||
true);
|
||||
ChangeAllSelected(false);
|
||||
}
|
||||
|
||||
// 禁用
|
||||
private void BtnSelectDisable_Click(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
ToggleDatapacks(
|
||||
ModLocalComp.compResourceListLoader.output.Where(m => selectedDatapacks.Contains(m.RawPath)).ToList(),
|
||||
false);
|
||||
ChangeAllSelected(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用/禁用数据包(通过重命名文件夹为 .disabled)
|
||||
/// </summary>
|
||||
private void ToggleDatapacks(IEnumerable<ModLocalComp.LocalCompFile> datapackList, bool isEnable)
|
||||
{
|
||||
var isSuccessful = true;
|
||||
foreach (var DatapackE in datapackList)
|
||||
{
|
||||
var datapackEntity = DatapackE;
|
||||
string newPath = null;
|
||||
|
||||
if (datapackEntity.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine && !isEnable)
|
||||
// 禁用 - 添加 .disabled 后缀
|
||||
newPath = datapackEntity.path + ".disabled";
|
||||
else if (datapackEntity.State == ModLocalComp.LocalCompFile.LocalFileStatus.Disabled && isEnable)
|
||||
// 启用 - 移除 .disabled 后缀
|
||||
newPath = datapackEntity.RawPath;
|
||||
else
|
||||
continue;
|
||||
|
||||
// 重命名
|
||||
try
|
||||
{
|
||||
if (File.Exists(newPath))
|
||||
{
|
||||
ModMain.MyMsgBox(Lang.Text("Instance.Saves.Datapack.Replace.FileNameConflict", ModBase.GetFileNameFromPath(newPath)));
|
||||
continue;
|
||||
}
|
||||
|
||||
FileSystem.Rename(datapackEntity.path, newPath);
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"未找到需要重命名的数据包({datapackEntity.path ?? "null"})",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Saves.Error.OperationFailed"));
|
||||
ReloadDatapackFileList(true);
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, $"重命名数据包失败({datapackEntity.path ?? "null"})");
|
||||
isSuccessful = false;
|
||||
}
|
||||
|
||||
// 更改 Loader 中的列表
|
||||
var newDatapackEntity = new ModLocalComp.LocalCompFile(newPath);
|
||||
newDatapackEntity.FromJson(datapackEntity.ToJson());
|
||||
if (ModLocalComp.compResourceListLoader.output.Contains(datapackEntity))
|
||||
{
|
||||
var indexOfLoader = ModLocalComp.compResourceListLoader.output.IndexOf(datapackEntity);
|
||||
ModLocalComp.compResourceListLoader.output.RemoveAt(indexOfLoader);
|
||||
ModLocalComp.compResourceListLoader.output.Insert(indexOfLoader, newDatapackEntity);
|
||||
}
|
||||
|
||||
if (searchResult is not null && searchResult.Contains(datapackEntity))
|
||||
{
|
||||
var indexOfResult = searchResult.IndexOf(datapackEntity);
|
||||
searchResult.Remove(datapackEntity);
|
||||
searchResult.Insert(indexOfResult, newDatapackEntity);
|
||||
}
|
||||
|
||||
// 更改 UI 中的列表
|
||||
try
|
||||
{
|
||||
var newItem = BuildLocalCompItem(newDatapackEntity);
|
||||
datapackItems[datapackEntity.RawPath] = newItem;
|
||||
var indexOfUi = PanList.Children.IndexOf(PanList.Children.OfType<MyLocalCompItem>()
|
||||
.FirstOrDefault(i => ReferenceEquals(i.Entry, datapackEntity)));
|
||||
if (indexOfUi == -1)
|
||||
continue;
|
||||
PanList.Children.RemoveAt(indexOfUi);
|
||||
PanList.Children.Insert(indexOfUi, newItem);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"更新 UI 列表项失败:{datapackEntity.FileName}",
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Instance.Saves.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
Dispatcher.Invoke(() => PanList.UpdateLayout(), DispatcherPriority.Background);
|
||||
|
||||
if (isSuccessful)
|
||||
{
|
||||
RefreshBars();
|
||||
}
|
||||
else
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Saves.Datapack.ToggleWarning"), HintType.Error);
|
||||
ReloadDatapackFileList(true);
|
||||
}
|
||||
|
||||
LoaderRun(ModLoader.LoaderFolderRunType.UpdateOnly);
|
||||
}
|
||||
|
||||
// 更新
|
||||
private void BtnSelectUpdate_Click(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
var updateList = ModLocalComp.compResourceListLoader.output
|
||||
.Where(m => selectedDatapacks.Contains(m.RawPath) && m.CanUpdate).ToList();
|
||||
if (!updateList.Any())
|
||||
return;
|
||||
UpdateResource(updateList);
|
||||
ChangeAllSelected(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录正在进行数据包更新的 datapacks 文件夹路径。
|
||||
/// </summary>
|
||||
public static List<string> updatingVersions = new();
|
||||
|
||||
private static bool TryGetSafeDatapackUpdateFileName(ModComp.CompFile file, out string fileName)
|
||||
{
|
||||
fileName = file.FileName?.Trim() ?? "";
|
||||
if (string.IsNullOrEmpty(fileName))
|
||||
return false;
|
||||
|
||||
if (!fileName.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
|
||||
return false;
|
||||
|
||||
if (fileName.IndexOfAny(new[] { '\\', '/', ':' }) >= 0)
|
||||
return false;
|
||||
|
||||
if (fileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
|
||||
return false;
|
||||
|
||||
return fileName == Path.GetFileName(fileName) && fileName != "." && fileName != "..";
|
||||
}
|
||||
|
||||
private static bool TryBuildDatapackUpdatePath(string rootPath, string fileName, out string fullPath)
|
||||
{
|
||||
var fullRootPath = Path.GetFullPath(rootPath);
|
||||
if (!fullRootPath.EndsWith(Path.DirectorySeparatorChar.ToString()) &&
|
||||
!fullRootPath.EndsWith(Path.AltDirectorySeparatorChar.ToString()))
|
||||
fullRootPath += Path.DirectorySeparatorChar;
|
||||
|
||||
fullPath = Path.GetFullPath(Path.Combine(fullRootPath, fileName));
|
||||
return fullPath.StartsWith(fullRootPath, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public void UpdateResource(IEnumerable<ModLocalComp.LocalCompFile> datapackList)
|
||||
{
|
||||
// 更新前警告
|
||||
if (!States.Hint.FunctionDatapackUpdate || datapackList.Count() >= 15)
|
||||
{
|
||||
if (ModMain.MyMsgBox(
|
||||
Lang.Text("Instance.Saves.Datapack.Update.Warning.Message"),
|
||||
Lang.Text("Instance.Saves.Datapack.Update.Warning.Title"), Lang.Text("Instance.Saves.Datapack.Update.Warning.Confirm"), Lang.Text("Common.Action.Cancel"), isWarn: true) == 1)
|
||||
States.Hint.FunctionDatapackUpdate = true;
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 构造下载信息
|
||||
datapackList = datapackList.ToList(); // 防止刷新影响迭代器
|
||||
var fileList = new List<DownloadFile>();
|
||||
var fileCopyList = new Dictionary<string, string>();
|
||||
var updateEntryList = new List<ModLocalComp.LocalCompFile>();
|
||||
var tempRoot = Path.Combine(ModBase.pathTemp, "DownloadedComp");
|
||||
var datapackRoot = Path.Combine(PageInstanceSavesLeft.currentSave, "datapacks");
|
||||
var skippedUnsafeFileCount = 0;
|
||||
foreach (var Entry in datapackList)
|
||||
{
|
||||
var file = Entry.UpdateFile;
|
||||
if (!file.Available)
|
||||
continue;
|
||||
if (!TryGetSafeDatapackUpdateFileName(file, out var safeFileName) ||
|
||||
!TryBuildDatapackUpdatePath(tempRoot, safeFileName, out var tempAddress) ||
|
||||
!TryBuildDatapackUpdatePath(datapackRoot, safeFileName, out var realAddress))
|
||||
{
|
||||
skippedUnsafeFileCount++;
|
||||
ModBase.Log($"[DatapackUpdate] 已跳过不安全的数据包更新文件名:{file.FileName}", ModBase.LogLevel.Debug);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 添加到下载列表
|
||||
fileList.Add(file.ToNetFile(tempAddress, ModComp.DownloadReason.Update,
|
||||
file.RawGameVersions.FirstOrDefault()));
|
||||
fileCopyList[tempAddress] = realAddress;
|
||||
updateEntryList.Add(Entry);
|
||||
}
|
||||
|
||||
if (skippedUnsafeFileCount > 0)
|
||||
HintService.Hint(
|
||||
Lang.Text("Instance.Saves.Datapack.Update.UnsafeFilesSkipped", skippedUnsafeFileCount),
|
||||
HintType.Error);
|
||||
if (!fileList.Any())
|
||||
return;
|
||||
|
||||
// 构造加载器
|
||||
var installLoaders = new List<ModLoader.LoaderBase>();
|
||||
var finishedFileNames = new List<string>();
|
||||
installLoaders.Add(
|
||||
new LoaderDownload(Lang.Text("Instance.Saves.Datapack.Update.Task.DownloadFiles"), fileList)
|
||||
{ ProgressWeight = updateEntryList.Count * 1.5d });
|
||||
|
||||
installLoaders.Add(new ModLoader.LoaderTask<int, int>(
|
||||
Lang.Text("Instance.Saves.Datapack.Update.Task.ReplaceFiles"), _ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var Entry in updateEntryList)
|
||||
if (File.Exists(Entry.path))
|
||||
Microsoft.VisualBasic.FileIO.FileSystem.DeleteFile(Entry.path, UIOption.AllDialogs,
|
||||
RecycleOption.SendToRecycleBin);
|
||||
else
|
||||
ModBase.Log($"[DatapackUpdate] 未找到更新前的数据包文件,跳过对它的删除:{Entry.path}",
|
||||
ModBase.LogLevel.Debug);
|
||||
|
||||
foreach (var Entry in fileCopyList)
|
||||
{
|
||||
if (File.Exists(Entry.Value))
|
||||
{
|
||||
Microsoft.VisualBasic.FileIO.FileSystem.DeleteFile(Entry.Value, UIOption.AllDialogs,
|
||||
RecycleOption.SendToRecycleBin);
|
||||
ModBase.Log($"[Datapack] 更新后的数据包文件已存在,将会把它放入回收站:{Entry.Value}", ModBase.LogLevel.Debug);
|
||||
}
|
||||
|
||||
if (Directory.Exists(ModBase.GetPathFromFullPath(Entry.Value)))
|
||||
{
|
||||
File.Move(Entry.Key, Entry.Value);
|
||||
finishedFileNames.Add(ModBase.GetFileNameFromPath(Entry.Value));
|
||||
}
|
||||
else
|
||||
{
|
||||
ModBase.Log($"[Datapack] 更新后的目标文件夹已被删除:{Entry.Value}", ModBase.LogLevel.Debug);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
ModBase.Log(ex, "替换旧版数据包文件时被主动取消");
|
||||
}
|
||||
}));
|
||||
|
||||
// 结束处理
|
||||
var loader = new ModLoader.LoaderCombo<IEnumerable<ModLocalComp.LocalCompFile>>(
|
||||
Lang.Text("Instance.Saves.Datapack.Update.Task.Title",
|
||||
ModBase.GetFolderNameFromPath(PageInstanceSavesLeft.currentSave)), installLoaders);
|
||||
var pathDatapacks = Path.Combine(PageInstanceSavesLeft.currentSave, "datapacks");
|
||||
|
||||
loader.OnStateChanged = _ =>
|
||||
{
|
||||
switch (loader.State)
|
||||
{
|
||||
case ModBase.LoadState.Finished:
|
||||
{
|
||||
switch (finishedFileNames.Count)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
ModBase.Log("[DatapackUpdate] 没有数据包被成功更新");
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Update.SuccessSingle", finishedFileNames.Single()), HintType.Success);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Update.SuccessMultiple", finishedFileNames.Count), HintType.Success);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ModBase.LoadState.Failed:
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Update.Failed", loader.Error.Message), HintType.Error);
|
||||
break;
|
||||
}
|
||||
case ModBase.LoadState.Aborted:
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Resource.Update.Aborted"));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ModBase.Log($"[DatapackUpdate] 已从正在进行数据包更新的文件夹列表移除:{pathDatapacks}");
|
||||
updatingVersions.Remove(pathDatapacks);
|
||||
|
||||
// 清理缓存
|
||||
ModBase.RunInNewThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var TempFile in fileCopyList.Keys)
|
||||
if (File.Exists(TempFile))
|
||||
File.Delete(TempFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "清理数据包更新缓存失败");
|
||||
}
|
||||
}, "Clean Datapack Update Cache", ThreadPriority.BelowNormal);
|
||||
};
|
||||
|
||||
// 启动加载器
|
||||
ModBase.Log($"[DatapackUpdate] 开始更新 {datapackList.Count()} 个数据包:{pathDatapacks}");
|
||||
updatingVersions.Add(pathDatapacks);
|
||||
loader.Start();
|
||||
ModLoader.LoaderTaskbarAdd(loader);
|
||||
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
|
||||
ModMain.frmMain.BtnExtraDownload.Ribble();
|
||||
ReloadDatapackFileList(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "初始化数据包更新失败");
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
private void BtnSelectDelete_Click(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
DeleteDatapacks(ModLocalComp.compResourceListLoader.output.Where(m => selectedDatapacks.Contains(m.RawPath)));
|
||||
ChangeAllSelected(false);
|
||||
}
|
||||
|
||||
private void DeleteDatapacks(IEnumerable<ModLocalComp.LocalCompFile> datapackList)
|
||||
{
|
||||
try
|
||||
{
|
||||
var isSuccessful = true;
|
||||
var isShiftPressed = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
|
||||
|
||||
// 确认需要删除的文件
|
||||
datapackList = datapackList.SelectMany(target =>
|
||||
{
|
||||
if (target.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine)
|
||||
return new[] { target.path, target.path + ".disabled" };
|
||||
|
||||
return new[] { target.path, target.RawPath };
|
||||
}).Distinct().Where(m => File.Exists(m)).Select(m => new ModLocalComp.LocalCompFile(m)).ToList();
|
||||
|
||||
// 实际删除文件
|
||||
foreach (var DatapackEntity in datapackList)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (isShiftPressed)
|
||||
File.Delete(DatapackEntity.path);
|
||||
else
|
||||
Microsoft.VisualBasic.FileIO.FileSystem.DeleteFile(DatapackEntity.path,
|
||||
UIOption.OnlyErrorDialogs, RecycleOption.SendToRecycleBin);
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
ModBase.Log(ex, "删除数据包被主动取消");
|
||||
ReloadDatapackFileList(true);
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"删除数据包失败({DatapackEntity.path})",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Saves.Error.OperationFailed"));
|
||||
isSuccessful = false;
|
||||
}
|
||||
|
||||
// 取消选中
|
||||
selectedDatapacks.Remove(DatapackEntity.RawPath);
|
||||
// 更改 Loader 和 UI 中的列表
|
||||
ModLocalComp.compResourceListLoader.output.Remove(DatapackEntity);
|
||||
searchResult?.Remove(DatapackEntity);
|
||||
datapackItems.Remove(DatapackEntity.RawPath);
|
||||
var indexOfUi = PanList.Children.IndexOf(PanList.Children.OfType<MyLocalCompItem>()
|
||||
.FirstOrDefault(i => i.Entry.Equals(DatapackEntity)));
|
||||
if (indexOfUi >= 0)
|
||||
PanList.Children.RemoveAt(indexOfUi);
|
||||
}
|
||||
|
||||
RefreshBars();
|
||||
if (!isSuccessful)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Saves.Datapack.Delete.FileOccupied"), HintType.Error);
|
||||
ReloadDatapackFileList(true);
|
||||
}
|
||||
else if (PanList.Children.Count == 0)
|
||||
{
|
||||
ReloadDatapackFileList(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
RefreshBars();
|
||||
}
|
||||
|
||||
if (!isSuccessful)
|
||||
return;
|
||||
if (isShiftPressed)
|
||||
{
|
||||
if (datapackList.Count() == 1)
|
||||
HintService.Hint(Lang.Text("Instance.Saves.Datapack.Delete.PermanentSingle", datapackList.Single().FileName), HintType.Success);
|
||||
else
|
||||
HintService.Hint(Lang.Text("Instance.Saves.Datapack.Delete.PermanentMultiple", datapackList.Count()), HintType.Success);
|
||||
}
|
||||
else if (datapackList.Count() == 1)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Saves.Datapack.Delete.RecycleSingle", datapackList.Single().FileName), HintType.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Saves.Datapack.Delete.RecycleMultiple", datapackList.Count()), HintType.Success);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
ModBase.Log(ex, "删除数据包被主动取消");
|
||||
ReloadDatapackFileList(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"删除数据包出现未知错误",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Saves.Error.OperationFailed"));
|
||||
ReloadDatapackFileList(true);
|
||||
}
|
||||
|
||||
LoaderRun(ModLoader.LoaderFolderRunType.UpdateOnly);
|
||||
}
|
||||
|
||||
// 取消选择
|
||||
private void BtnSelectCancel_Click(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
ChangeAllSelected(false);
|
||||
}
|
||||
|
||||
// 收藏
|
||||
private void BtnSelectFavorites_Click(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
var selected = ModLocalComp.compResourceListLoader.output
|
||||
.Where(m => selectedDatapacks.Contains(m.RawPath) && m.Comp is not null).Select(i => i.Comp).ToList();
|
||||
ModComp.CompFavorites.ShowMenu(selected, (UIElement)sender);
|
||||
}
|
||||
|
||||
// 分享
|
||||
private void BtnSelectShare_Click(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
var shareList = ModLocalComp.compResourceListLoader.output
|
||||
.Where(m => selectedDatapacks.Contains(m.RawPath) && m.Comp is not null).Select(i => i.Comp.Id).ToHashSet();
|
||||
ModBase.ClipboardSet(ModComp.CompFavorites.GetShareCode(shareList));
|
||||
ChangeAllSelected(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 单个资源项
|
||||
|
||||
// 详情
|
||||
public void Info_Click(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var datapackEntry = ((MyLocalCompItem)(sender is MyIconButton iconBtn ? iconBtn.Tag : sender)).Entry;
|
||||
|
||||
// 加载失败信息
|
||||
if (datapackEntry.State == ModLocalComp.LocalCompFile.LocalFileStatus.Unavailable)
|
||||
{
|
||||
ModMain.MyMsgBox(
|
||||
Lang.Text(
|
||||
"Instance.Saves.Datapack.Info.ReadFailed.WithDetail",
|
||||
datapackEntry.FileUnavailableReason.ToString()),
|
||||
Lang.Text("Instance.Saves.Datapack.Info.ReadFailedTitle"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (datapackEntry.Comp is not null)
|
||||
{
|
||||
// 跳转到数据包下载页面
|
||||
ModMain.frmMain.PageChange(new FormMain.PageStackData
|
||||
{
|
||||
page = FormMain.PageType.CompDetail,
|
||||
additional = (datapackEntry.Comp, new List<string>(), PageInstanceLeft.McInstance.Info.VanillaName,
|
||||
ModComp.CompLoaderType.Minecraft, ModComp.CompType.DataPack, null)
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// 获取信息
|
||||
var contentLines = new List<string>();
|
||||
|
||||
if (datapackEntry.Description is not null)
|
||||
contentLines.Add(datapackEntry.Description + "\r\n");
|
||||
if (datapackEntry.Authors is not null)
|
||||
contentLines.Add(Lang.Text("Instance.Saves.Datapack.Info.Author") + datapackEntry.Authors);
|
||||
contentLines.Add(Lang.Text("Instance.Saves.Datapack.Info.File") + datapackEntry.FileName + "(" +
|
||||
ModBase.GetString(GetDatapackFileInfo(datapackEntry.path).Length) + ")");
|
||||
if (datapackEntry.Version is not null)
|
||||
contentLines.Add(Lang.Text("Instance.Saves.Datapack.Info.Version") + datapackEntry.Version);
|
||||
|
||||
var debugInfo = new List<string>();
|
||||
if (datapackEntry.ModId is not null) debugInfo.Add(Lang.Text("Instance.Saves.Datapack.Info.DatapackId") + datapackEntry.ModId);
|
||||
if (debugInfo.Any())
|
||||
{
|
||||
contentLines.Add("");
|
||||
contentLines.AddRange(debugInfo);
|
||||
}
|
||||
|
||||
// 显示详情信息
|
||||
if (datapackEntry.Url is null)
|
||||
ModMain.MyMsgBox(contentLines.Join("\r\n"), datapackEntry.Name, Lang.Text("Instance.Resource.Item.Info.Return"));
|
||||
else if (ModMain.MyMsgBox(contentLines.Join("\r\n"), datapackEntry.Name, Lang.Text("Instance.Resource.Item.Info.OpenWebsite"), Lang.Text("Instance.Resource.Item.Info.Return")) == 1)
|
||||
ModBase.OpenWebsite(datapackEntry.Url);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"获取数据包详情失败",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Saves.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 打开文件所在的位置
|
||||
public void Open_Click(MyIconButton sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var listItem = (MyLocalCompItem)sender.Tag;
|
||||
ModBase.OpenExplorer(listItem.Entry.path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"打开数据包文件位置失败",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Saves.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
public void Delete_Click(MyIconButton sender, EventArgs e)
|
||||
{
|
||||
var listItem = (MyLocalCompItem)sender.Tag;
|
||||
DeleteDatapacks(new[] { listItem.Entry });
|
||||
}
|
||||
|
||||
// 启用
|
||||
public void Enable_Click(MyIconButton sender, EventArgs e)
|
||||
{
|
||||
var listItem = (MyLocalCompItem)sender.Tag;
|
||||
ToggleDatapacks(new[] { listItem.Entry }, true);
|
||||
}
|
||||
|
||||
// 禁用
|
||||
public void Disable_Click(MyIconButton sender, EventArgs e)
|
||||
{
|
||||
var listItem = (MyLocalCompItem)sender.Tag;
|
||||
ToggleDatapacks(new[] { listItem.Entry }, false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 搜索
|
||||
|
||||
public bool IsSearching => !string.IsNullOrWhiteSpace(SearchBox.Text);
|
||||
private List<ModLocalComp.LocalCompFile> searchResult;
|
||||
|
||||
public void SearchRun(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsSearching)
|
||||
{
|
||||
// 构造请求
|
||||
var queryList = new List<ModBase.SearchEntry<ModLocalComp.LocalCompFile>>();
|
||||
foreach (var Entry in ModLocalComp.compResourceListLoader.output)
|
||||
{
|
||||
var searchSource = new List<ModBase.SearchSource>();
|
||||
searchSource.Add(new ModBase.SearchSource(Entry.Name, 1d));
|
||||
searchSource.Add(new ModBase.SearchSource(Entry.FileName, 1d));
|
||||
if (Entry.Version is not null)
|
||||
searchSource.Add(new ModBase.SearchSource(Entry.Version, 0.2d));
|
||||
if (Entry.Description is not null && !string.IsNullOrEmpty(Entry.Description))
|
||||
searchSource.Add(new ModBase.SearchSource(Entry.Description, 0.4d));
|
||||
if (Entry.Comp is not null)
|
||||
{
|
||||
if ((Entry.Comp.RawName ?? "") != (Entry.Name ?? ""))
|
||||
searchSource.Add(new ModBase.SearchSource(Entry.Comp.RawName, 1d));
|
||||
if ((Entry.Comp.TranslatedName ?? "") != (Entry.Comp.RawName ?? ""))
|
||||
searchSource.Add(new ModBase.SearchSource(Entry.Comp.TranslatedName, 1d));
|
||||
if ((Entry.Comp.Description ?? "") != (Entry.Description ?? ""))
|
||||
searchSource.Add(new ModBase.SearchSource(Entry.Comp.Description, 0.4d));
|
||||
searchSource.Add(new ModBase.SearchSource(string.Join("", Entry.Comp.Tags), 0.2d));
|
||||
}
|
||||
|
||||
queryList.Add(new ModBase.SearchEntry<ModLocalComp.LocalCompFile>
|
||||
{ item = Entry, searchSource = searchSource });
|
||||
}
|
||||
|
||||
// 进行搜索
|
||||
searchResult = ModBase.Search(queryList, SearchBox.Text, ModBase.MaxLocalSearchDepth, 0.35d).Select(r => r.item).ToList();
|
||||
}
|
||||
|
||||
RefreshUI();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "搜索过程中发生异常");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<local:MyPageRight
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" x:Class="PCL.PageInstanceSavesInfo"
|
||||
PanScroll="{Binding ElementName=PanBack}">
|
||||
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
|
||||
<StackPanel Margin="25,10,25,10" x:Name="PanMain">
|
||||
<local:MyHint x:Name="Hintversion1_9" Theme="Yellow" Visibility="Collapsed" Margin="0,5,0,0" />
|
||||
<local:MyHint x:Name="Hintversion1_8" Theme="Yellow" Visibility="Collapsed" Margin="0,5,0,0" />
|
||||
<local:MyHint x:Name="Hintversion1_3" Theme="Yellow" Visibility="Collapsed" Margin="0,5,0,0" />
|
||||
<local:MyCard Margin="0,15,0,0" Title="{DynamicResource Instance.Saves.Info.Details.Title}" x:Name="PanContent">
|
||||
<Grid Margin="15,35,15,15" x:Name="PanList">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition SharedSizeGroup="TextBlock" Width="Auto" />
|
||||
<ColumnDefinition Width="24" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
<local:MyCard Margin="0,15,0,0" Title="{DynamicResource Instance.Saves.Info.Settings.Title}" x:Name="PanSettings" Visibility="Collapsed">
|
||||
<Grid Margin="15,35,15,15" x:Name="PanSettingsList">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition SharedSizeGroup="TextBlock" Width="Auto" />
|
||||
<ColumnDefinition Width="24" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
</StackPanel>
|
||||
</local:MyScrollViewer>
|
||||
</local:MyPageRight>
|
||||
+356
@@ -0,0 +1,356 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Humanizer;
|
||||
using PCL.Core.App.Localization;
|
||||
using PCL.Core.Logging;
|
||||
using PCL.Core.Minecraft.Saves;
|
||||
using PCL.Core.Minecraft.Saves.Editing;
|
||||
using PCL.Core.UI;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageInstanceSavesInfo : IRefreshable
|
||||
{
|
||||
/// <summary>无状态服务,线程安全,所有实例可共享。</summary>
|
||||
private static readonly SaveManager SaveManager = new();
|
||||
|
||||
/// <summary>防并发冲突</summary>
|
||||
private static readonly SemaphoreSlim WriteLock = new(1, 1);
|
||||
|
||||
private CancellationTokenSource? _cts;
|
||||
|
||||
public PageInstanceSavesInfo()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += async (_, _) =>
|
||||
{
|
||||
PanBack.ScrollToHome();
|
||||
await RefreshInfoAsync();
|
||||
};
|
||||
}
|
||||
|
||||
void IRefreshable.Refresh() => Refresh();
|
||||
public void Refresh() => RefreshInfoAsync().ContinueWith(
|
||||
t => LogWrapper.Warn(t.Exception, "Saves", "刷新存档信息异常"), //only 兜底
|
||||
CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
|
||||
|
||||
private async Task RefreshInfoAsync()
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_cts?.Dispose();
|
||||
_cts = new CancellationTokenSource();
|
||||
var ct = _cts.Token;
|
||||
|
||||
try
|
||||
{
|
||||
ClearInfoTable();
|
||||
PanSettingsList.Children.Clear();
|
||||
PanSettingsList.RowDefinitions.Clear();
|
||||
Hintversion1_9.Visibility = Visibility.Collapsed;
|
||||
Hintversion1_8.Visibility = Visibility.Collapsed;
|
||||
Hintversion1_3.Visibility = Visibility.Collapsed;
|
||||
PanSettings.Visibility = Visibility.Collapsed;
|
||||
|
||||
var save = await SaveManager.LoadSaveAsync(PageInstanceSavesLeft.currentSave, ct);
|
||||
|
||||
ModMain.frmInstanceSavesLeft.ItemDatapack.Visibility =
|
||||
save.VersionId is null or < DataVersionBoundaries._17w47a ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
if (save.VersionName is null)
|
||||
{
|
||||
if (save.Difficulty.HasValue)
|
||||
ShowHint(Hintversion1_9, "Instance.Saves.Info.VersionHint.1_9");
|
||||
else if (save.AllowCommands)
|
||||
ShowHint(Hintversion1_8, "Instance.Saves.Info.VersionHint.1_8");
|
||||
else
|
||||
ShowHint(Hintversion1_3, "Instance.Saves.Info.VersionHint.1_3");
|
||||
}
|
||||
else
|
||||
AddInfoRow(Lang.Text("Instance.Saves.Info.Version"), $"{save.VersionName} ({save.VersionId})");
|
||||
|
||||
AddInfoRow(Lang.Text("Instance.Saves.Info.LevelName"), save.LevelName);
|
||||
AddInfoRow(Lang.Text("Instance.Saves.Info.Seed"),
|
||||
save.Seed?.ToString() ?? Lang.Text("Instance.Saves.Info.GetFailed"),
|
||||
isSeed: true, versionName: save.VersionName);
|
||||
AddInfoRow(Lang.Text("Instance.Saves.Info.LastPlayed"),
|
||||
Lang.Date(save.LastPlayedUtc.ToLocalTime(), "g"));
|
||||
|
||||
if (save.Spawn.HasValue)
|
||||
{
|
||||
var s = save.Spawn.Value;
|
||||
AddInfoRow(Lang.Text("Instance.Saves.Info.SpawnPoint"), $"{s.X:F0} / {s.Y:F0} / {s.Z:F0}");
|
||||
}
|
||||
|
||||
AddInfoRow(Lang.Text("Instance.Saves.Info.GameMode"), GameModeName(save.GameMode));
|
||||
|
||||
AddInfoRow(Lang.Text("Instance.Saves.Info.PlayTime"),
|
||||
Lang.TimeSpan(save.PlayTime, 3, false, TimeUnit.Day, TimeUnit.Second));
|
||||
|
||||
if (save.VersionName is not null || save.Difficulty.HasValue)
|
||||
BuildAllowCommandsSetting(save.AllowCommands);
|
||||
|
||||
if (save.Difficulty.HasValue)
|
||||
BuildDifficultySetting(save.IsHardcore, save.IsDifficultyLocked, (int)save.Difficulty.Value);
|
||||
|
||||
PanContent.Visibility = Visibility.Visible;
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
Lang.Text("Instance.Saves.Info.Error.LoadFailed"),
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Saves.Info.Error.LoadFailed"));
|
||||
PanContent.Visibility = Visibility.Collapsed;
|
||||
PanSettings.Visibility = Visibility.Collapsed;
|
||||
PanSettingsList.Children.Clear();
|
||||
PanSettingsList.RowDefinitions.Clear();
|
||||
Hintversion1_9.Visibility = Visibility.Collapsed;
|
||||
Hintversion1_8.Visibility = Visibility.Collapsed;
|
||||
Hintversion1_3.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildAllowCommandsSetting(bool allowCommands)
|
||||
{
|
||||
PanSettings.Visibility = Visibility.Visible;
|
||||
var folder = PageInstanceSavesLeft.currentSave;
|
||||
|
||||
var combo = new MyComboBox
|
||||
{
|
||||
Width = 100d, HorizontalAlignment = HorizontalAlignment.Left,
|
||||
ToolTip = Lang.Text("Instance.Saves.Info.Modify.BeforeSave"),
|
||||
SelectedValuePath = "Value", DisplayMemberPath = "Display",
|
||||
};
|
||||
combo.Items.Add(new { Value = 0, Display = Lang.Text("Instance.Saves.Info.AllowCommands.NotAllowed") });
|
||||
combo.Items.Add(new { Value = 1, Display = Lang.Text("Instance.Saves.Info.AllowCommands.Allowed") });
|
||||
combo.SelectedValue = allowCommands ? 1 : 0;
|
||||
|
||||
combo.SelectionChanged += async (_, _) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (combo.SelectedValue is null) return;
|
||||
await WriteLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
await SaveManager.ApplyChangesAsync(folder, new SaveChanges
|
||||
{
|
||||
AllowCommands = new Editable<bool>((int)combo.SelectedValue == 1),
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
WriteLock.Release();
|
||||
}
|
||||
|
||||
HintService.Hint(Lang.Text("Instance.Saves.Info.Modify.CheatSuccess"), HintType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
Lang.Text("Instance.Saves.Info.Modify.CheatFailed"),
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Instance.Saves.Info.Modify.CheatFailed"));
|
||||
}
|
||||
};
|
||||
|
||||
AddSettingRow(Lang.Text("Instance.Saves.Info.AllowCommands"), combo);
|
||||
}
|
||||
|
||||
private void BuildDifficultySetting(bool isHardcore, bool isLocked, int difficultyValue)
|
||||
{
|
||||
PanSettings.Visibility = Visibility.Visible;
|
||||
var folder = PageInstanceSavesLeft.currentSave;
|
||||
|
||||
var combo = new MyComboBox
|
||||
{
|
||||
Width = 100d, HorizontalAlignment = HorizontalAlignment.Left,
|
||||
ToolTip = Lang.Text("Instance.Saves.Info.Modify.BeforeSave"),
|
||||
SelectedValuePath = "Value", DisplayMemberPath = "Display",
|
||||
};
|
||||
combo.Items.Add(new { Value = 0, Display = Lang.Text("Instance.Saves.Info.Difficulty.Peaceful") });
|
||||
combo.Items.Add(new { Value = 1, Display = Lang.Text("Instance.Saves.Info.Difficulty.Easy") });
|
||||
combo.Items.Add(new { Value = 2, Display = Lang.Text("Instance.Saves.Info.Difficulty.Normal") });
|
||||
combo.Items.Add(new { Value = 3, Display = Lang.Text("Instance.Saves.Info.Difficulty.Hard") });
|
||||
combo.SelectedValue = difficultyValue;
|
||||
|
||||
var lockCheckBox = new MyCheckBox
|
||||
{
|
||||
Text = Lang.Text("Instance.Saves.Info.LockDifficulty"),
|
||||
ToolTip = Lang.Text("Instance.Saves.Info.LockDifficulty.ToolTip"),
|
||||
VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(10d, 0d, 0d, 0d),
|
||||
Checked = isLocked,
|
||||
Visibility = isHardcore ? Visibility.Collapsed : Visibility.Visible,
|
||||
};
|
||||
|
||||
var panel = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Left };
|
||||
panel.Children.Add(combo);
|
||||
panel.Children.Add(lockCheckBox);
|
||||
|
||||
async Task ApplyAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (combo.SelectedValue is null) return;
|
||||
await WriteLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
await SaveManager.ApplyChangesAsync(folder, new SaveChanges
|
||||
{
|
||||
Difficulty = new Editable<Difficulty>((Difficulty)(int)combo.SelectedValue),
|
||||
LockDifficulty = new Editable<bool>(!isHardcore && lockCheckBox.Checked == true),
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
WriteLock.Release();
|
||||
}
|
||||
|
||||
HintService.Hint(Lang.Text("Instance.Saves.Info.Modify.DifficultySuccess"), HintType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
Lang.Text("Instance.Saves.Info.Modify.DifficultyFailed"),
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Instance.Saves.Info.Modify.DifficultyFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
combo.SelectionChanged += async (_, _) => await ApplyAsync();
|
||||
lockCheckBox.Change += async (_, _) => await ApplyAsync();
|
||||
|
||||
AddSettingRow(Lang.Text("Instance.Saves.Info.GameDifficultyLabel"), panel);
|
||||
}
|
||||
|
||||
private static string GameModeName(GameMode mode) => mode switch
|
||||
{
|
||||
GameMode.Hardcore => Lang.Text("Instance.Saves.Info.GameMode.Hardcore"),
|
||||
GameMode.Creative => Lang.Text("Instance.Saves.Info.GameMode.Creative"),
|
||||
GameMode.Adventure => Lang.Text("Instance.Saves.Info.GameMode.Adventure"),
|
||||
GameMode.Spectator => Lang.Text("Instance.Saves.Info.GameMode.Spectator"),
|
||||
_ => Lang.Text("Instance.Saves.Info.GameMode.Survival"),
|
||||
};
|
||||
|
||||
private static void ShowHint(MyHint hint, string langKey)
|
||||
{
|
||||
hint.Text = Lang.Text(langKey);
|
||||
hint.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void ClearInfoTable()
|
||||
{
|
||||
PanList.Children.Clear();
|
||||
PanList.RowDefinitions.Clear();
|
||||
}
|
||||
|
||||
private void AddInfoRow(string head, string content, bool isSeed = false, string? versionName = null)
|
||||
{
|
||||
var headBlock = new TextBlock { Text = head, Margin = new Thickness(0d, 3d, 0d, 3d) };
|
||||
var contentStack = new StackPanel { Orientation = Orientation.Horizontal };
|
||||
|
||||
if (isSeed && content != Lang.Text("Instance.Saves.Info.GetFailed"))
|
||||
{
|
||||
var seedBtn = new MyTextButton { Text = content, Margin = new Thickness(0d, 3d, 0d, 3d) };
|
||||
seedBtn.Click += (_, _) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
ModBase.ClipboardSet(content);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
Lang.Text("Instance.Saves.Info.Error.ClipboardFailed"),
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Instance.Saves.Info.Error.ClipboardFailed"));
|
||||
}
|
||||
};
|
||||
contentStack.Children.Add(seedBtn);
|
||||
|
||||
var chunkbaseBtn = new MyIconButton
|
||||
{
|
||||
SvgIcon = "lucide/external-link",
|
||||
Width = 22d,
|
||||
Height = 22d,
|
||||
ToolTip = Lang.Text("Instance.Saves.Info.Chunkbase.ToolTip"),
|
||||
};
|
||||
chunkbaseBtn.Click += (_, _) => OpenChunkbase(content, versionName);
|
||||
contentStack.Children.Add(chunkbaseBtn);
|
||||
}
|
||||
else
|
||||
{
|
||||
contentStack.Children.Add(new TextBlock { Text = content, Margin = new Thickness(0d, 3d, 0d, 3d) });
|
||||
}
|
||||
|
||||
PanList.Children.Add(headBlock);
|
||||
PanList.Children.Add(contentStack);
|
||||
var rowDef = new RowDefinition();
|
||||
PanList.RowDefinitions.Add(rowDef);
|
||||
var rowIndex = PanList.RowDefinitions.IndexOf(rowDef);
|
||||
Grid.SetRow(headBlock, rowIndex);
|
||||
Grid.SetColumn(headBlock, 0);
|
||||
Grid.SetRow(contentStack, rowIndex);
|
||||
Grid.SetColumn(contentStack, 2);
|
||||
}
|
||||
|
||||
private void AddSettingRow(string head, UIElement control)
|
||||
{
|
||||
var rowIndex = PanSettingsList.RowDefinitions.Count;
|
||||
PanSettingsList.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1d, GridUnitType.Auto) });
|
||||
|
||||
var headBlock = new TextBlock { Text = head, Margin = new Thickness(0d, 3d, 0d, 3d) };
|
||||
Grid.SetRow(headBlock, rowIndex);
|
||||
Grid.SetColumn(headBlock, 0);
|
||||
Grid.SetRow(control, rowIndex);
|
||||
Grid.SetColumn(control, 2);
|
||||
PanSettingsList.Children.Add(headBlock);
|
||||
PanSettingsList.Children.Add(control);
|
||||
PanSettingsList.RowDefinitions.Add(new RowDefinition { Height = new GridLength(8d, GridUnitType.Pixel) });
|
||||
}
|
||||
|
||||
private static void OpenChunkbase(string seed, string? versionName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (versionName is null)
|
||||
{
|
||||
ModBase.Log(
|
||||
Lang.Text("Instance.Saves.Info.Chunkbase.UnknownVersion"),
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Instance.Saves.Info.Chunkbase.UnknownVersion"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (versionName.Any(char.IsLetter))
|
||||
{
|
||||
ModBase.Log(
|
||||
Lang.Text("Instance.Saves.Info.Chunkbase.PreviewVersion", versionName),
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Instance.Saves.Info.Chunkbase.PreviewVersion", versionName));
|
||||
return;
|
||||
}
|
||||
|
||||
var usedVersion = versionName.StartsWith("1.21")
|
||||
? versionName.Replace(".", "_")
|
||||
: versionName.Contains('.')
|
||||
? string.Join("_", versionName.Split('.').Take(2))
|
||||
: versionName.Replace(".", "_");
|
||||
ModBase.OpenWebsite(
|
||||
$"https://www.chunkbase.com/apps/seed-map#seed={seed}&platform=java_{usedVersion}&dimension=overworld");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
Lang.Text("Instance.Saves.Info.Error.ChunkbaseFailed"),
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Instance.Saves.Info.Error.ChunkbaseFailed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<local:MyPageLeft x:Class="PCL.PageInstanceSavesLeft"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:PCL"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" HorizontalAlignment="Left">
|
||||
<StackPanel Margin="0,12,0,0" Name="PanItem">
|
||||
<local:MyListItem x:Name="ItemInfo" IsScaleAnimationEnabled="False" Checked="True" Type="RadioBox" Tag="0"
|
||||
MinPaddingRight="35" Height="36" VerticalAlignment="Top" Title="{DynamicResource Instance.Saves.Left.Info}"
|
||||
LogoScale="1"
|
||||
SvgIcon="lucide/info" />
|
||||
<local:MyListItem x:Name="ItemDatapack" Title="{DynamicResource Instance.Saves.Left.Datapack}" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="1"
|
||||
MinPaddingRight="35" Height="36" VerticalAlignment="Top" LogoScale="0.91"
|
||||
SvgIcon="lucide/file-archive">
|
||||
<local:MyListItem.Buttons>
|
||||
<x:Array Type="{x:Type local:MyIconButton}">
|
||||
<local:MyIconButton Tag="2" ToolTip="{DynamicResource Common.Action.Refresh}" ToolTipService.Placement="Right"
|
||||
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
|
||||
Click="RefreshButton_Click" LogoScale="0.85"
|
||||
SvgIcon="lucide/refresh-cw" />
|
||||
</x:Array>
|
||||
</local:MyListItem.Buttons>
|
||||
</local:MyListItem>
|
||||
<TextBlock Text="{DynamicResource Instance.Saves.Left.QuickActions}" Margin="13,6,5,4" Opacity="0.6" FontSize="12" />
|
||||
<local:MyListItem x:Name="BtnOpenFolder" Title="{DynamicResource Common.Action.OpenFolder}" IsScaleAnimationEnabled="False" Type="RadioBox"
|
||||
MinPaddingRight="35" Height="36" VerticalAlignment="Top" LogoScale="0.8"
|
||||
SvgIcon="lucide/folder-open" />
|
||||
</StackPanel>
|
||||
</local:MyPageLeft>
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using PCL.Core.App.Localization;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageInstanceSavesLeft : IRefreshable
|
||||
{
|
||||
public static string currentSave;
|
||||
|
||||
// 初始化
|
||||
private bool isLoad;
|
||||
|
||||
private void Page_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isLoad)
|
||||
return;
|
||||
isLoad = true;
|
||||
}
|
||||
|
||||
private void BtnOpenFolder_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
e.Handled = true;
|
||||
ModBase.OpenExplorer($@"{currentSave}\");
|
||||
}
|
||||
|
||||
#region 龙猫牌 页面管理
|
||||
|
||||
/// <summary>
|
||||
/// 当前页面的编号。从 0 开始计算。
|
||||
/// </summary>
|
||||
public FormMain.PageSubType pageID = FormMain.PageSubType.Default;
|
||||
|
||||
public PageInstanceSavesLeft()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += Page_Loaded;
|
||||
ItemInfo.Check += PageCheck;
|
||||
ItemDatapack.Check += PageCheck;
|
||||
BtnOpenFolder.Click += BtnOpenFolder_Click;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 勾选事件改变页面。
|
||||
/// </summary>
|
||||
private void PageCheck(object sender, ModBase.RouteEventArgs e)
|
||||
{
|
||||
if (sender is MyListItem item && item.Tag is not null)
|
||||
PageChange((FormMain.PageSubType)ModBase.Val(item.Tag));
|
||||
}
|
||||
|
||||
public object PageGet(FormMain.PageSubType id = FormMain.PageSubType.Default)
|
||||
{
|
||||
if ((int)id == -1)
|
||||
id = pageID;
|
||||
switch (id)
|
||||
{
|
||||
case FormMain.PageSubType.VersionSavesInfo:
|
||||
{
|
||||
if (ModMain.frmInstanceSavesInfo is null)
|
||||
ModMain.frmInstanceSavesInfo = new PageInstanceSavesInfo();
|
||||
return ModMain.frmInstanceSavesInfo;
|
||||
}
|
||||
case FormMain.PageSubType.VersionSavesDatapack:
|
||||
{
|
||||
if (ModMain.frmInstanceSavesDatapack is null)
|
||||
ModMain.frmInstanceSavesDatapack = new PageInstanceSavesDatapack();
|
||||
return ModMain.frmInstanceSavesDatapack;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw new Exception(Lang.Text("Instance.Saves.Left.UnknownSubPage", (int)id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换现有页面。
|
||||
/// </summary>
|
||||
public void PageChange(FormMain.PageSubType id)
|
||||
{
|
||||
if (pageID == id)
|
||||
return;
|
||||
ModAnimation.AniControlEnabled += 1;
|
||||
try
|
||||
{
|
||||
PageChangeRun((MyPageRight)PageGet(id));
|
||||
pageID = id;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
Lang.Text("Instance.Saves.Left.SwitchFailed", (int)id),
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Saves.Left.SwitchFailed", (int)id));
|
||||
}
|
||||
finally
|
||||
{
|
||||
ModAnimation.AniControlEnabled -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void PageChangeRun(MyPageRight target)
|
||||
{
|
||||
ModAnimation.AniStop("FrmMain PageChangeRight"); // 停止主页面的右页面切换动画,防止它与本动画一起触发多次 PageOnEnter
|
||||
if (target.Parent is not null)
|
||||
target.SetValue(ContentPresenter.ContentProperty, null);
|
||||
ModMain.frmMain.pageRight = target;
|
||||
((MyPageRight)ModMain.frmMain.PanMainRight.Child).PageOnExit();
|
||||
ModAnimation.AniStart(new[]
|
||||
{
|
||||
ModAnimation.AaCode(() =>
|
||||
{
|
||||
((MyPageRight)ModMain.frmMain.PanMainRight.Child).PageOnForceExit();
|
||||
ModMain.frmMain.PanMainRight.Child = ModMain.frmMain.pageRight;
|
||||
ModMain.frmMain.pageRight.Opacity = 0d;
|
||||
}, 130),
|
||||
ModAnimation.AaCode(() =>
|
||||
{
|
||||
// 延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理
|
||||
ModMain.frmMain.pageRight.Opacity = 1d;
|
||||
ModMain.frmMain.pageRight.PageOnEnter();
|
||||
}, 30, true)
|
||||
}, "PageLeft PageChange");
|
||||
}
|
||||
|
||||
public void RefreshButton_Click(object sender, EventArgs e) // 由边栏按钮匿名调用
|
||||
{
|
||||
Refresh((FormMain.PageSubType)ModBase.Val(((MyIconButton)sender).Tag));
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
Refresh(ModMain.frmMain.PageCurrentSub);
|
||||
}
|
||||
|
||||
public void Refresh(FormMain.PageSubType subType)
|
||||
{
|
||||
switch (subType)
|
||||
{
|
||||
case FormMain.PageSubType.VersionSavesDatapack:
|
||||
{
|
||||
if (ModMain.frmInstanceSavesDatapack is null)
|
||||
ModMain.frmInstanceSavesDatapack = new PageInstanceSavesDatapack();
|
||||
if (ItemDatapack.Checked)
|
||||
ModMain.frmInstanceSavesDatapack.Refresh();
|
||||
else
|
||||
ItemDatapack.Checked = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
HintService.Hint(Lang.Text("Instance.Saves.Left.Refreshing"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<local:MyPageRight
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" x:Class="PCL.PageInstanceScreenshot"
|
||||
PanScroll="{Binding ElementName=PanBack}" Grid.IsSharedSizeScope="True">
|
||||
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
|
||||
<Grid>
|
||||
<local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" Margin="40" x:Name="PanNoPic">
|
||||
<Grid Margin="20,17">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="0" Grid.ColumnSpan="4" Margin="0,0,0,9" HorizontalAlignment="Center"
|
||||
Text="{DynamicResource Instance.Screenshot.Empty.Title}" FontSize="19"
|
||||
UseLayoutRounding="True" SnapsToDevicePixels="True"
|
||||
Foreground="{DynamicResource ColorBrush3}" />
|
||||
<Rectangle Grid.Row="1" Grid.ColumnSpan="4" HorizontalAlignment="Stretch" Height="2"
|
||||
Fill="{DynamicResource ColorBrush3}" />
|
||||
<TextBlock Grid.Row="2" Grid.ColumnSpan="4" Margin="10,15,10,5"
|
||||
Text="{DynamicResource Instance.Screenshot.Empty.Message}" TextWrapping="Wrap" />
|
||||
<local:MyButton Grid.Row="3" Grid.Column="1" Height="35" HorizontalAlignment="Center"
|
||||
x:Name="BtnOpenFolderTop" MinWidth="140"
|
||||
Text="{DynamicResource Instance.Screenshot.OpenFolder}" Margin="10,10,10,0"
|
||||
Padding="13,0" ColorType="Highlight" />
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
<StackPanel Orientation="Vertical" Margin="10,25,10,10" x:Name="PanContent">
|
||||
<local:MyCard Margin="15,0,15,15" Title="{DynamicResource Instance.Screenshot.QuickActions}">
|
||||
<Grid Height="35" Margin="25,40,15,20">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<local:MyButton Grid.Column="0" MinWidth="140"
|
||||
Text="{DynamicResource Instance.Screenshot.OpenFolder}" Padding="13,0"
|
||||
Margin="0,0,20,0"
|
||||
HorizontalAlignment="Left" x:Name="BtnOpenFolder" ColorType="Highlight" />
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
<WrapPanel Margin="8,0,8,25" Name="PanList" HorizontalAlignment="Center" Orientation="Horizontal">
|
||||
<!--<local:MyCard Height="220" Width="120" Margin="7" Tag="Path">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="180"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Image Grid.Row="0"/>
|
||||
<StackPanel Grid.Row="1">
|
||||
<local:MyIconTextButton x:Name="BtnOpen" Text="{DynamicResource Common.Action.Open}"
|
||||
LogoScale="0.8" SvgIcon="lucide/folder-open" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</local:MyCard>-->
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</local:MyScrollViewer>
|
||||
</local:MyPageRight>
|
||||
+340
@@ -0,0 +1,340 @@
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Microsoft.VisualBasic.FileIO;
|
||||
using PCL.Core.App;
|
||||
using SearchOption = System.IO.SearchOption;
|
||||
using PCL.Core.App.Localization;
|
||||
using PCL.Core.UI;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageInstanceScreenshot : IRefreshable
|
||||
{
|
||||
private bool _AppendLock;
|
||||
private int _Offset;
|
||||
|
||||
private List<string> fileList = new();
|
||||
|
||||
private bool isLoad;
|
||||
private string screenshotPath;
|
||||
|
||||
public PageInstanceScreenshot()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += PageSetupLaunch_Loaded;
|
||||
PanBack.ScrollChanged += RequireAppend;
|
||||
BtnOpenFolder.Click += BtnOpenFolder_Click;
|
||||
BtnOpenFolderTop.Click += BtnOpenFolder_Click;
|
||||
}
|
||||
|
||||
void IRefreshable.Refresh()
|
||||
{
|
||||
RefreshSelf();
|
||||
}
|
||||
|
||||
private void RefreshSelf()
|
||||
{
|
||||
var ignore = RefreshAsync();
|
||||
}
|
||||
|
||||
public static async Task RefreshAsync()
|
||||
{
|
||||
if (ModMain.frmInstanceScreenshot is not null)
|
||||
await ModMain.frmInstanceScreenshot.ReloadAsync();
|
||||
ModMain.frmInstanceLeft.ItemScreenshot.Checked = true;
|
||||
HintService.Hint(Lang.Text("Instance.Saves.Status.Refreshing"), log: false);
|
||||
}
|
||||
|
||||
private void PageSetupLaunch_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 重复加载部分
|
||||
PanBack.ScrollToHome();
|
||||
screenshotPath = PageInstanceLeft.McInstance.PathIndie + @"screenshots\";
|
||||
if (!Directory.Exists(screenshotPath))
|
||||
Directory.CreateDirectory(screenshotPath);
|
||||
Dispatcher.BeginInvoke(new Func<Task>(ReloadAsync));
|
||||
|
||||
// 非重复加载部分
|
||||
if (isLoad)
|
||||
return;
|
||||
isLoad = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保当前页面上的信息已正确显示。
|
||||
/// </summary>
|
||||
public async Task ReloadAsync()
|
||||
{
|
||||
ModAnimation.AniControlEnabled += 1;
|
||||
PanBack.ScrollToHome();
|
||||
await LoadFileListAsync();
|
||||
ModAnimation.AniControlEnabled -= 1;
|
||||
}
|
||||
|
||||
private void RefreshTip()
|
||||
{
|
||||
if (fileList.Count.Equals(0))
|
||||
{
|
||||
PanNoPic.Visibility = Visibility.Visible;
|
||||
PanContent.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
PanNoPic.Visibility = Visibility.Collapsed;
|
||||
PanContent.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
private static string[] allowedSuffix = { "*.png", "*.jpg", "*.jpeg", "*.bmp", "*.webp", "*.tiff" };
|
||||
|
||||
private async Task LoadFileListAsync()
|
||||
{
|
||||
ModBase.Log("[Screenshot] 刷新截图文件");
|
||||
fileList.Clear();
|
||||
if (Directory.Exists(screenshotPath))
|
||||
{
|
||||
fileList = allowedSuffix
|
||||
.SelectMany(suffix => Directory.EnumerateFiles(screenshotPath, suffix, SearchOption.TopDirectoryOnly))
|
||||
.OrderByDescending(f => File.GetCreationTime(f))
|
||||
.ToList();
|
||||
}
|
||||
PanList.Children.Clear();
|
||||
RefreshTip();
|
||||
//FileList = FileList.Where(e => !e.ContainsF(@"\debug\")).ToList(); // 排除资源包调试输出
|
||||
//FileList.Sort((a, b) => new FileInfo(a).CreationTime > new FileInfo(b).CreationTime);
|
||||
ModBase.Log("[Screenshot] 共发现 " + fileList.Count + " 个截图文件");
|
||||
if (fileList.Count == 0)
|
||||
return;
|
||||
await ListAppendAsync(20, 0);
|
||||
}
|
||||
|
||||
private void RequireAppend(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
if (fileList.Count != 0 && !_AppendLock && PanBack.VerticalOffset + PanBack.ViewportHeight >= PanBack.ExtentHeight)
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Func<Task>(async () => await ListAppendAsync()));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ListAppendAsync(int count = 20, int offset = -1)
|
||||
{
|
||||
_AppendLock = true;
|
||||
if (offset == -1)
|
||||
{
|
||||
if (_Offset * count > fileList.Count)
|
||||
return;
|
||||
offset = _Offset + 1;
|
||||
_Offset += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_Offset = offset;
|
||||
}
|
||||
|
||||
if (count * offset > fileList.Count)
|
||||
return;
|
||||
for (int j = count * offset, loopTo = count * (offset + 1) - 1; j <= loopTo; j++)
|
||||
{
|
||||
if (j >= fileList.Count)
|
||||
break;
|
||||
var i = fileList.ElementAt(j);
|
||||
try
|
||||
{
|
||||
if (!File.Exists(i))
|
||||
continue; // 文件在加载途中消失了
|
||||
if (File.GetAttributes(i).HasFlag(FileAttributes.Hidden))
|
||||
continue; // 隐藏文件
|
||||
if (new FileInfo(i).Length == 0L)
|
||||
continue; // 空文件
|
||||
var myCard = new MyCard
|
||||
{
|
||||
Margin = new Thickness(7),
|
||||
Tag = i,
|
||||
ToolTip = i.Replace(screenshotPath, "") // 适配高清截图模组
|
||||
};
|
||||
var grid = new Grid();
|
||||
myCard.Children.Add(grid);
|
||||
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(9d) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(120d) });
|
||||
grid.RowDefinitions.Add(new RowDefinition());
|
||||
|
||||
// 图片
|
||||
var image = new Image();
|
||||
image.Source = await Task.Run(() =>
|
||||
{
|
||||
var bitmapImage = new BitmapImage();
|
||||
var loadSource = i;
|
||||
using (var fs = new FileStream(loadSource, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
bitmapImage.BeginInit();
|
||||
bitmapImage.DecodePixelHeight = 200;
|
||||
bitmapImage.DecodePixelWidth = 400;
|
||||
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapImage.StreamSource = fs;
|
||||
bitmapImage.EndInit();
|
||||
bitmapImage.Freeze();
|
||||
}
|
||||
|
||||
return bitmapImage;
|
||||
});
|
||||
image.Stretch = Stretch.Uniform; // 使图片自适应控件大小
|
||||
image.Cursor = Cursors.Hand;
|
||||
image.MouseLeftButtonDown += (sender, e) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Basics.OpenPath(i);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
Lang.Text("Instance.Screenshot.OpenFailed"),
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Instance.Screenshot.OpenFailed"));
|
||||
}
|
||||
}; // 使用系统默认程序打开
|
||||
Grid.SetRow(image, 1);
|
||||
grid.Children.Add(image);
|
||||
|
||||
// 按钮
|
||||
var stackPanel = new StackPanel();
|
||||
stackPanel.Orientation = Orientation.Horizontal;
|
||||
stackPanel.HorizontalAlignment = HorizontalAlignment.Center;
|
||||
stackPanel.Margin = new Thickness(3d, 5d, 3d, 5d);
|
||||
Grid.SetRow(stackPanel, 2);
|
||||
grid.Children.Add(stackPanel);
|
||||
|
||||
var btnOpen = new MyIconTextButton
|
||||
{
|
||||
Name = "BtnOpen",
|
||||
Text = Lang.Text("Common.Action.Open"),
|
||||
LogoScale = 0.8d,
|
||||
SvgIcon = "lucide/folder-open",
|
||||
Tag = i
|
||||
};
|
||||
btnOpen.Click += (s, ev) => BtnOpen_Click((MyIconTextButton)s, ev);
|
||||
stackPanel.Children.Add(btnOpen);
|
||||
var btnDelete = new MyIconTextButton
|
||||
{
|
||||
Name = "BtnDelete",
|
||||
Text = Lang.Text("Common.Action.Delete"),
|
||||
LogoScale = 0.8d,
|
||||
SvgIcon = "lucide/trash-2",
|
||||
Tag = i
|
||||
};
|
||||
btnDelete.Click += (s, ev) => BtnDelete_Click((MyIconTextButton)s, ev);
|
||||
stackPanel.Children.Add(btnDelete);
|
||||
var btnCopy = new MyIconTextButton
|
||||
{
|
||||
Name = "BtnCopy",
|
||||
Text = Lang.Text("Common.Action.Copy"),
|
||||
LogoScale = 0.8d,
|
||||
SvgIcon = "lucide/copy",
|
||||
Tag = i
|
||||
};
|
||||
btnCopy.Click += (s, ev) => BtnCopy_Click((MyIconTextButton)s, ev);
|
||||
stackPanel.Children.Add(btnCopy);
|
||||
PanList.Children.Add(myCard);
|
||||
myCard.Opacity = 0d;
|
||||
ModAnimation.AniStart(new[] { ModAnimation.AaOpacity(myCard, 1d, 200) });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, $"[Screenshot] 创建 {i} 截图预览失败,图像可能损坏");
|
||||
}
|
||||
}
|
||||
|
||||
_AppendLock = false;
|
||||
}
|
||||
|
||||
private void RemoveItem(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var i in PanList.Children)
|
||||
if (((MyCard)i).Tag.Equals(path))
|
||||
{
|
||||
PanList.Children.Remove((UIElement)i);
|
||||
break;
|
||||
}
|
||||
|
||||
fileList.Remove(path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "未能找到对应 UI");
|
||||
}
|
||||
}
|
||||
|
||||
private string GetPathFromSender(MyIconTextButton sender)
|
||||
{
|
||||
return (string)sender.Tag;
|
||||
}
|
||||
|
||||
private void BtnOpen_Click(MyIconTextButton sender, EventArgs e)
|
||||
{
|
||||
ModBase.OpenExplorer(GetPathFromSender(sender));
|
||||
}
|
||||
|
||||
private void BtnDelete_Click(MyIconTextButton sender, EventArgs e)
|
||||
{
|
||||
var path = GetPathFromSender(sender);
|
||||
try
|
||||
{
|
||||
FileSystem.DeleteFile(path, UIOption.OnlyErrorDialogs, RecycleOption.SendToRecycleBin);
|
||||
RemoveItem(path);
|
||||
RefreshTip();
|
||||
HintService.Hint(Lang.Text("Instance.Screenshot.Deleted"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
Lang.Text("Instance.Screenshot.DeleteFailed"),
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Instance.Screenshot.DeleteFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnCopy_Click(MyIconTextButton sender, EventArgs e)
|
||||
{
|
||||
var imagePath = GetPathFromSender(sender);
|
||||
if (File.Exists(imagePath))
|
||||
{
|
||||
var tryTime = 0;
|
||||
while (tryTime <= 5)
|
||||
try
|
||||
{
|
||||
ModBase.Log("[Screenshot] 尝试复制" + imagePath + "到剪贴板");
|
||||
Clipboard.SetImage(new BitmapImage(new Uri(imagePath)));
|
||||
HintService.Hint(Lang.Text("Instance.Screenshot.CopiedToClipboard"));
|
||||
tryTime = 6;
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
tryTime += 1;
|
||||
ModBase.Log(ex, $"[Screenshot]第 {tryTime} 次复制尝试失败");
|
||||
}
|
||||
|
||||
HintService.Hint(Lang.Text("Instance.Screenshot.CopyFailed"), HintType.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Screenshot.FileNotFound"));
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnOpenFolder_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!Directory.Exists(screenshotPath))
|
||||
Directory.CreateDirectory(screenshotPath);
|
||||
ModBase.OpenExplorer(screenshotPath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<local:MyPageRight x:Class="PCL.PageInstanceServer"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:PCL"
|
||||
mc:Ignorable="d"
|
||||
PanScroll="{Binding ElementName=PanBack}" Grid.IsSharedSizeScope="True">
|
||||
|
||||
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Hidden" x:Name="PanBack">
|
||||
<Grid>
|
||||
<local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" Margin="40" x:Name="PanNoServer">
|
||||
<Grid Margin="20,17">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="0" Grid.ColumnSpan="4" Margin="0,0,0,9" HorizontalAlignment="Center"
|
||||
Text="{DynamicResource Instance.Server.Empty.Title}" FontSize="19"
|
||||
UseLayoutRounding="True" SnapsToDevicePixels="True"
|
||||
Foreground="{DynamicResource ColorBrush3}" />
|
||||
<Rectangle Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="4" HorizontalAlignment="Stretch"
|
||||
Height="2" Fill="{DynamicResource ColorBrush3}" />
|
||||
<TextBlock Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="4" Margin="10,15,10,5"
|
||||
Text="{DynamicResource Instance.Server.Empty.Message}" TextWrapping="Wrap" />
|
||||
<local:MyButton Grid.Row="3" Grid.Column="1" Height="35" MinWidth="140"
|
||||
Text="{DynamicResource Instance.Server.RefreshInfo}"
|
||||
Padding="13,0" Margin="10,10,10,0" HorizontalAlignment="Center"
|
||||
Click="BtnRefresh_Click" ColorType="Highlight" />
|
||||
<local:MyButton Grid.Row="3" Grid.Column="2" Height="35" HorizontalAlignment="Center"
|
||||
Click="BtnAddServer_Click" x:Name="BtnAddServerTop" MinWidth="140"
|
||||
Text="{DynamicResource Instance.Server.AddServer}" Margin="10,10,10,0"
|
||||
Padding="13,0" />
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
<StackPanel Margin="10,25,10,10">
|
||||
<!-- 页面标题 -->
|
||||
<local:MyCard Title="{DynamicResource Instance.Server.QuickActions}" Margin="15,0,15,15"
|
||||
x:Name="PanContent">
|
||||
<Grid Height="35" Margin="25,40,23,15">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<local:MyButton Grid.Column="0" MinWidth="140"
|
||||
Text="{DynamicResource Instance.Server.RefreshAll}" Padding="13,0"
|
||||
Margin="0,0,20,0"
|
||||
HorizontalAlignment="Left" Click="BtnRefresh_Click" ColorType="Highlight" />
|
||||
<local:MyButton Grid.Column="1" MinWidth="140"
|
||||
Text="{DynamicResource Instance.Server.AddServer}" Padding="13,0"
|
||||
Margin="0,0,20,0"
|
||||
HorizontalAlignment="Left" Click="BtnAddServer_Click" />
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
|
||||
<!-- 服务器列表 -->
|
||||
<StackPanel x:Name="PanServers" Margin="15,0,15,15">
|
||||
<!-- 服务器项目将动态添加到这里 -->
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</local:MyScrollViewer>
|
||||
</local:MyPageRight>
|
||||
@@ -0,0 +1,498 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using FluentValidation;
|
||||
using fNbt;
|
||||
using PCL.Core.Link.McPing;
|
||||
using PCL.Core.Link.McPing.Model;
|
||||
using PCL.Core.Minecraft;
|
||||
using PCL.Core.Utils.Validate;
|
||||
using PCL.Core.App.Localization;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageInstanceServer : MyPageRight
|
||||
{
|
||||
private const int debounceInterval = 2000;
|
||||
|
||||
public static readonly List<MinecraftServerInfo> serverList = new();
|
||||
private static readonly List<ServerCard> serverCardList = new();
|
||||
|
||||
private CancellationTokenSource _cts;
|
||||
|
||||
private DateTime _lastRefresh = DateTime.MinValue;
|
||||
|
||||
public PageInstanceServer()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += PageLoaded;
|
||||
IsVisibleChanged += PageInstanceServer_IsVisibleChanged;
|
||||
}
|
||||
|
||||
private async void PageLoaded(object e, RoutedEventArgs sender)
|
||||
{
|
||||
serverList.Clear();
|
||||
serverCardList.Clear();
|
||||
PanServers.Children.Clear();
|
||||
|
||||
await LoadServersFromFileAsync();
|
||||
RefreshTip();
|
||||
|
||||
foreach (var server in serverList)
|
||||
{
|
||||
var serverCard = new ServerCard();
|
||||
serverCard.RemoveServer += RemoveServerEvent;
|
||||
serverCard.EditServer += (a, b) => this.EditServer(a, (ServerCard.ResultEventArgs)b);
|
||||
serverCard.UpdateServerInfo(server);
|
||||
serverCardList.Add(serverCard);
|
||||
PanServers.Children.Add(serverCard);
|
||||
}
|
||||
|
||||
PingAllServers();
|
||||
}
|
||||
|
||||
private void PageInstanceServer_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (!IsVisible)
|
||||
if (_cts is not null)
|
||||
{
|
||||
_cts.Cancel();
|
||||
_cts.Dispose(); // 清理旧的 CancellationTokenSource
|
||||
_cts = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async void RemoveServerEvent(object sender, EventArgs e)
|
||||
{
|
||||
// Get server index
|
||||
var index = PanServers.Children.IndexOf((UIElement)sender);
|
||||
if (index < 0)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Server.IndexNotFound"), HintType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Read NBT file
|
||||
var nbtData =
|
||||
await NbtFileHandler.ReadTagInNbtFileAsync<NbtList>(
|
||||
Path.Combine(PageInstanceLeft.McInstance.PathIndie, "servers.dat"), "servers");
|
||||
if (nbtData is null)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Server.ReadDataFailed"), HintType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove server from NBT data
|
||||
nbtData.RemoveAt(index);
|
||||
var clonedNbtData = (NbtList)nbtData.Clone();
|
||||
|
||||
// Write back to NBT file
|
||||
if (!await NbtFileHandler.WriteTagInNbtFileAsync(clonedNbtData,
|
||||
Path.Combine(PageInstanceLeft.McInstance.PathIndie, "servers.dat")))
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Server.WriteDataFailed"), HintType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove server from list and UI
|
||||
serverList.RemoveAt(index);
|
||||
serverCardList.Remove((ServerCard)sender);
|
||||
if (serverList.Count == 0) RefreshTip();
|
||||
|
||||
// Remove UI element
|
||||
PanServers.Children.Remove((UIElement)sender);
|
||||
|
||||
// Success message
|
||||
HintService.Hint(Lang.Text("Instance.Server.Removed"), HintType.Success);
|
||||
}
|
||||
|
||||
private async void EditServer(object sender, ServerCard.ResultEventArgs e)
|
||||
{
|
||||
// Read NBT file
|
||||
var nbtData =
|
||||
await NbtFileHandler.ReadTagInNbtFileAsync<NbtList>(Path.Combine(PageInstanceLeft.McInstance.PathIndie, "servers.dat"),
|
||||
"servers");
|
||||
if (nbtData is null)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Server.ReadDataFailed"), HintType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get server index
|
||||
var index = PanServers.Children.IndexOf((UIElement)sender);
|
||||
if (index < 0 || index >= nbtData.Count)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Server.IndexNotFound"), HintType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify server data
|
||||
var server = nbtData[index] as NbtCompound;
|
||||
|
||||
// Update server data
|
||||
server["name"] = new NbtString("name", e.Param1);
|
||||
server["ip"] = new NbtString("ip", e.Param2);
|
||||
|
||||
// Write updated NBT data
|
||||
var clonedNbtData = (NbtList)nbtData.Clone();
|
||||
if (!await NbtFileHandler.WriteTagInNbtFileAsync(clonedNbtData,
|
||||
Path.Combine(PageInstanceLeft.McInstance.PathIndie, "servers.dat")))
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Server.WriteDataFailed"), HintType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var serverCard = sender as ServerCard;
|
||||
|
||||
serverCard.server.Name = e.Param1;
|
||||
serverCard.server.Address = e.Param2;
|
||||
|
||||
await serverCard.RefreshServerStatusAsync(true);
|
||||
|
||||
// Success message
|
||||
HintService.Hint(Lang.Text("Instance.Server.Updated"), HintType.Success);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新服务器列表
|
||||
/// </summary>
|
||||
public async void RefreshServers()
|
||||
{
|
||||
ModBase.Log("刷新服务器列表");
|
||||
try
|
||||
{
|
||||
// 读取服务器信息
|
||||
await LoadServersFromFileAsync();
|
||||
|
||||
// 在UI线程中更新界面
|
||||
ModBase.RunInUi(() => UpdateServerUi());
|
||||
|
||||
// 异步ping所有服务器
|
||||
PingAllServers();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
Lang.Text("Instance.Server.RefreshFailed"),
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Server.RefreshFailed"));
|
||||
ModBase.RunInUi(() => HintService.Hint(
|
||||
Lang.Text("Instance.Server.RefreshFailed.WithDetail", ex.ToString()),
|
||||
HintType.Error));
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnRefresh_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if ((DateTime.Now - _lastRefresh).TotalMilliseconds < debounceInterval)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Server.NoFrequentRefresh"));
|
||||
return;
|
||||
}
|
||||
|
||||
_lastRefresh = DateTime.Now;
|
||||
HintService.Hint(Lang.Text("Instance.Server.RefreshingList"));
|
||||
try
|
||||
{
|
||||
RefreshServers();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
Lang.Text("Instance.Server.RefreshFailed"),
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Server.RefreshFailed"));
|
||||
HintService.Hint(
|
||||
Lang.Text("Instance.Server.RefreshFailed.WithDetail", ex.ToString()),
|
||||
HintType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async void BtnAddServer_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var result = GetServerInfo(new MinecraftServerInfo { Name = Lang.Text("Instance.Server.DefaultName"), Address = "" });
|
||||
if (result.Success)
|
||||
{
|
||||
var newServer = new MinecraftServerInfo
|
||||
{
|
||||
Name = result.Name,
|
||||
Address = result.Address,
|
||||
Status = ServerStatus.Unknown
|
||||
};
|
||||
serverList.Add(newServer);
|
||||
|
||||
RefreshTip();
|
||||
|
||||
var serverCard = new ServerCard();
|
||||
serverCard.RemoveServer += RemoveServerEvent;
|
||||
serverCard.EditServer += (a, b) => this.EditServer(a, (ServerCard.ResultEventArgs)b);
|
||||
serverCard.UpdateServerInfo(newServer);
|
||||
serverCardList.Add(serverCard);
|
||||
PanServers.Children.Add(serverCard);
|
||||
|
||||
await serverCard.RefreshServerStatusAsync(false);
|
||||
|
||||
var serversDatPath = Path.Combine(PageInstanceLeft.McInstance.PathIndie, "servers.dat");
|
||||
|
||||
NbtList nbtData;
|
||||
if (!File.Exists(serversDatPath))
|
||||
{
|
||||
nbtData = new NbtList("servers", NbtTagType.Compound);
|
||||
RefreshTip();
|
||||
}
|
||||
else
|
||||
{
|
||||
nbtData = await NbtFileHandler.ReadTagInNbtFileAsync<NbtList>(serversDatPath, "servers");
|
||||
}
|
||||
|
||||
if (nbtData is not null)
|
||||
{
|
||||
var server = new NbtCompound();
|
||||
server["name"] = new NbtString("name", result.Name);
|
||||
server["ip"] = new NbtString("ip", result.Address);
|
||||
if (nbtData.Count == 0) nbtData.ListType = NbtTagType.Compound;
|
||||
nbtData.Add(server);
|
||||
var clonedNbtData = (NbtList)nbtData.Clone();
|
||||
await NbtFileHandler.WriteTagInNbtFileAsync(clonedNbtData, serversDatPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static (string Name, string Address, bool Success) GetServerInfo(MinecraftServerInfo server)
|
||||
{
|
||||
var newName = ModMain.MyMsgBoxInput(Lang.Text("Instance.Server.EditTitle"), Lang.Text("Instance.Server.NamePrompt"), server.Name,
|
||||
[new NullOrWhiteSpaceValidator()]);
|
||||
|
||||
if (string.IsNullOrEmpty(newName)) return (string.Empty, string.Empty, false);
|
||||
|
||||
var newAddress = ModMain.MyMsgBoxInput(Lang.Text("Instance.Server.EditTitle"), Lang.Text("Instance.Server.AddressPrompt"), server.Address,
|
||||
[new NullOrWhiteSpaceValidator()]);
|
||||
if (string.IsNullOrEmpty(newAddress)) return (string.Empty, string.Empty, false);
|
||||
return (newName, newAddress, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从servers.dat文件读取服务器信息
|
||||
/// </summary>
|
||||
private async Task LoadServersFromFileAsync()
|
||||
{
|
||||
serverList.Clear();
|
||||
|
||||
var serversFile = Path.Combine(PageInstanceLeft.McInstance.PathIndie, "servers.dat");
|
||||
if (!File.Exists(serversFile))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// 读取NBT格式的servers.dat文件
|
||||
var nbtData = await NbtFileHandler.ReadTagInNbtFileAsync<NbtList>(serversFile, "servers");
|
||||
ParseServersFromNBT(nbtData);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, Lang.Text("Instance.Server.ReadFileFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析NBT格式的服务器数据
|
||||
/// </summary>
|
||||
private void ParseServersFromNBT(NbtList serversList)
|
||||
{
|
||||
if (serversList is not null)
|
||||
{
|
||||
ModBase.Log($"Found {serversList.Count} servers:");
|
||||
|
||||
// 遍历 servers 列表中的每个服务器
|
||||
for (int i = 0, loopTo = serversList.Count - 1; i <= loopTo; i++)
|
||||
{
|
||||
var server = serversList[i] as NbtCompound;
|
||||
if (server is not null)
|
||||
{
|
||||
// 提取服务器信息
|
||||
// Dim hidden As Byte = If(server.Get(Of NbtByte)("hidden")?.Value, 0)
|
||||
var ip = server.Get<NbtString>("ip")?.Value ?? "Unknown";
|
||||
var name = server.Get<NbtString>("name")?.Value ?? "Unknown";
|
||||
var iconBase64 = server.Get<NbtString>("icon")?.Value;
|
||||
|
||||
ModBase.Log($"服务器 {i + 1}:");
|
||||
ModBase.Log($" 名字: {name}");
|
||||
ModBase.Log($" IP: {ip}");
|
||||
// Log($" Hidden: {If(hidden = 1, "Yes", "No")}")
|
||||
serverList.Add(new MinecraftServerInfo
|
||||
{
|
||||
Name = name,
|
||||
Address = ip,
|
||||
Status = ServerStatus.Unknown,
|
||||
Icon = iconBase64
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ModBase.Log("No 'servers' list found in servers.dat.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新服务器UI显示
|
||||
/// </summary>
|
||||
private void UpdateServerUi()
|
||||
{
|
||||
PanServers.Children.Clear();
|
||||
|
||||
RefreshTip();
|
||||
|
||||
foreach (var server in serverList)
|
||||
{
|
||||
var serverCard = new ServerCard();
|
||||
serverCard.RemoveServer += RemoveServerEvent;
|
||||
serverCard.EditServer += (a, b) => this.EditServer(a, (ServerCard.ResultEventArgs)b);
|
||||
serverCard.UpdateServerInfo(server);
|
||||
serverCardList.Add(serverCard);
|
||||
PanServers.Children.Add(serverCard);
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshTip()
|
||||
{
|
||||
if (serverList.Count == 0)
|
||||
{
|
||||
ModBase.Log(Lang.Text("Instance.Server.NoServersFound"));
|
||||
PanNoServer.Visibility = Visibility.Visible;
|
||||
PanContent.Visibility = Visibility.Collapsed;
|
||||
PanServers.Visibility = Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
|
||||
ModBase.Log(Lang.Text("Instance.Server.FoundServers"));
|
||||
PanNoServer.Visibility = Visibility.Collapsed;
|
||||
PanContent.Visibility = Visibility.Visible;
|
||||
PanServers.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private async void PingAllServers()
|
||||
{
|
||||
if (_cts is not null)
|
||||
{
|
||||
_cts.Cancel();
|
||||
_cts.Dispose();
|
||||
}
|
||||
|
||||
_cts = new CancellationTokenSource();
|
||||
var token = _cts.Token;
|
||||
var semaphore = new SemaphoreSlim(5); // 限制最多 5 个并发任务
|
||||
|
||||
var tasks = new List<Task>();
|
||||
try
|
||||
{
|
||||
var snapshot = serverCardList.ToList();
|
||||
foreach (var server in snapshot)
|
||||
{
|
||||
var currentServer = server;
|
||||
await semaphore.WaitAsync(token);
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await currentServer.RefreshServerStatusAsync(false, token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, $"Ping 服务器失败: {currentServer}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}, token));
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks); // 等待所有任务完成
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
ModBase.Log("PingAllServers 被取消", ModBase.LogLevel.Debug);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "PingAllServers 失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ping单个服务器
|
||||
/// </summary>
|
||||
public static async Task<MinecraftServerInfo> PingServerAsync(MinecraftServerInfo server, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
var addr = await ServerAddressResolver.GetResolvedServerAddressAsync(server.Address, token);
|
||||
using (var query = McPingServiceFactory.CreateService(addr.Host, addr.Ip, addr.Port))
|
||||
{
|
||||
McPingResult? result;
|
||||
ModBase.Log("Pinging server: " + server.Address + ":" + addr.Port);
|
||||
result = await query.PingAsync(token); // 传递 token
|
||||
ModBase.Log("Ping result: " + (result is not null ? "Success" : "Failed"));
|
||||
if (result is not null)
|
||||
{
|
||||
server.Status = ServerStatus.Online;
|
||||
server.PlayerCount = result.Players.Online;
|
||||
server.MaxPlayers = result.Players.Max;
|
||||
server.Description = result.Description ?? string.Empty;
|
||||
server.Version = result.Version.Name;
|
||||
server.Ping = (int)result.Latency;
|
||||
server.Icon = result.Favicon;
|
||||
}
|
||||
else
|
||||
{
|
||||
server.Status = ServerStatus.Offline;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
server.Status = ServerStatus.Offline;
|
||||
ModBase.Log("Ping 服务器被取消: " + server.Address, ModBase.LogLevel.Debug);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
server.Status = ServerStatus.Offline;
|
||||
ModBase.Log(ex, $"Ping 服务器失败: {server.Address}:{server.Port}");
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Minecraft服务器信息类
|
||||
/// </summary>
|
||||
public class MinecraftServerInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Address { get; set; }
|
||||
public int Port { get; set; } = 25565;
|
||||
public ServerStatus Status { get; set; } = ServerStatus.Unknown;
|
||||
public int PlayerCount { get; set; }
|
||||
public int MaxPlayers { get; set; }
|
||||
public string Description { get; set; } = "";
|
||||
public string Version { get; set; } = "";
|
||||
public int Ping { get; set; }
|
||||
public string Icon { get; set; } = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 服务器状态枚举
|
||||
/// </summary>
|
||||
public enum ServerStatus
|
||||
{
|
||||
Unknown,
|
||||
Online,
|
||||
Offline,
|
||||
Pinging
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
<local:MyPageRight
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL"
|
||||
xmlns:val="https://ce.pclc.cc/core/utils/validate"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:System="clr-namespace:System;assembly=mscorlib"
|
||||
mc:Ignorable="d" x:Class="PCL.PageInstanceSetup"
|
||||
PanScroll="{Binding ElementName=PanBack}">
|
||||
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
|
||||
<StackPanel x:Name="PanMain" Margin="25,25,25,25">
|
||||
<local:MyHint Text="{DynamicResource Instance.Setup.Hint}" Theme="Blue" Margin="0,0,0,15"
|
||||
CanClose="True" RelativeSetup="HintIndieSetup" />
|
||||
<local:MyCard x:Name="CardArgument" Margin="0,0,0,15"
|
||||
Title="{DynamicResource Setup.Launch.Options.Title}">
|
||||
<StackPanel Margin="25,40,25,21">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Name" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition Width="60" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="28" />
|
||||
<RowDefinition Height="9" />
|
||||
<RowDefinition Height="28" />
|
||||
<RowDefinition Height="9" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="28" />
|
||||
<RowDefinition Height="9" />
|
||||
<RowDefinition Height="28" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
Text="{DynamicResource Instance.Setup.Options.InstanceIsolation}"
|
||||
Margin="0,0,25,0" />
|
||||
<local:MyComboBox x:Name="ComboArgumentIndieV2" Grid.Row="0" Grid.ColumnSpan="2"
|
||||
Tag="VersionArgumentIndieV2" Grid.Column="1">
|
||||
<local:MyComboBoxItem Content="{DynamicResource Instance.Setup.Options.InstanceIsolation.Enabled}"
|
||||
ToolTipService.InitialShowDelay="0"
|
||||
ToolTipService.BetweenShowDelay="0"
|
||||
ToolTip="{DynamicResource Instance.Setup.Options.InstanceIsolation.Enabled.ToolTip}"
|
||||
ToolTipService.HorizontalOffset="100" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Common.Action.Close}"
|
||||
ToolTipService.InitialShowDelay="0"
|
||||
ToolTipService.BetweenShowDelay="0"
|
||||
ToolTip="{DynamicResource Instance.Setup.Options.InstanceIsolation.Disabled.ToolTip}"
|
||||
ToolTipService.HorizontalOffset="100" />
|
||||
</local:MyComboBox>
|
||||
<TextBlock Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
Text="{DynamicResource Setup.Launch.Options.WindowTitle.Label}"
|
||||
Margin="0,0,25,0" />
|
||||
<local:MyComboBox x:Name="TextArgumentTitle" Grid.Row="2" Grid.ColumnSpan="2"
|
||||
Tag="VersionArgumentTitle" Grid.Column="1" IsEditable="True"
|
||||
IsTextSearchEnabled="False"
|
||||
ToolTip="{DynamicResource Instance.Setup.Options.WindowTitle.ToolTip}">
|
||||
<local:MyComboBoxItem Content="{DynamicResource Setup.Launch.Options.WindowTitle.SampleFormat}" />
|
||||
</local:MyComboBox>
|
||||
<local:MyCheckBox x:Name="CheckArgumentTitleEmpty" Margin="0,0,0,9" Grid.Row="4"
|
||||
Grid.ColumnSpan="3"
|
||||
Text="{DynamicResource Instance.Setup.Options.WindowTitle.Default}"
|
||||
Tag="VersionArgumentTitleEmpty"
|
||||
Grid.Column="0"
|
||||
ToolTip="{DynamicResource Instance.Setup.Options.WindowTitle.Default.ToolTip}" />
|
||||
<TextBlock Grid.Row="6" VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
Text="{DynamicResource Setup.Launch.Options.CustomInfo.Label}"
|
||||
Margin="0,0,25,0" />
|
||||
<local:MyTextBox x:Name="TextArgumentInfo" Grid.Row="6" Grid.ColumnSpan="2"
|
||||
Tag="VersionArgumentInfo" Grid.Column="1"
|
||||
HintText="{DynamicResource Instance.Setup.FollowGlobal}"
|
||||
ToolTip="{DynamicResource Instance.Setup.Options.CustomInfo.ToolTip}">
|
||||
<local:MyTextBox.ValidateRules>
|
||||
<val:BlacklistValidator>
|
||||
<val:BlacklistValidator.Blacklist>
|
||||
<System:String>"</System:String>
|
||||
<System:String>“</System:String>
|
||||
<System:String>”</System:String>
|
||||
</val:BlacklistValidator.Blacklist>
|
||||
</val:BlacklistValidator>
|
||||
</local:MyTextBox.ValidateRules>
|
||||
</local:MyTextBox>
|
||||
<TextBlock VerticalAlignment="Center" Grid.Row="8" HorizontalAlignment="Left"
|
||||
Text="{DynamicResource Instance.Setup.Options.Java}"
|
||||
Margin="0,0,25,0" />
|
||||
<local:MyComboBox x:Name="ComboArgumentJava" Grid.ColumnSpan="2" Grid.Row="8" Grid.Column="1"
|
||||
MaxDropDownHeight="240"
|
||||
ToolTip="{DynamicResource Instance.Setup.Options.Java.ToolTip}">
|
||||
<ComboBoxItem Content="{DynamicResource Instance.Setup.Options.Java.Loading}" IsSelected="True" />
|
||||
</local:MyComboBox>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
<local:MyCard Margin="0,0,0,15" Title="{DynamicResource Setup.Launch.Memory.Title}">
|
||||
<StackPanel Margin="25,40,25,15">
|
||||
<local:MyHint x:Name="LabRamWarn" Text="{DynamicResource Setup.Launch.Memory.Warning.Java32Bit}"
|
||||
Theme="Yellow" Margin="0,0,0,12" />
|
||||
<local:MyHint x:Name="HintRamTooHigh" Visibility="Collapsed" Height="30" Theme="Yellow"
|
||||
Text="{DynamicResource Setup.Launch.Memory.Warning.TooMuch}" Margin="0,0,0,12"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
|
||||
<Grid Margin="0,0,0,3">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="9" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<local:MyRadioBox
|
||||
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
|
||||
Text="{DynamicResource Instance.Setup.FollowGlobal}"
|
||||
Checked="True"
|
||||
x:Name="RadioRamType2"
|
||||
Tag="VersionRamType/2"
|
||||
Margin="0,0,20,9" />
|
||||
|
||||
<local:MyRadioBox
|
||||
Grid.Row="1" Grid.Column="0"
|
||||
Text="{DynamicResource Setup.Launch.Memory.Auto}"
|
||||
Width="110"
|
||||
x:Name="RadioRamType0"
|
||||
Tag="VersionRamType/0"
|
||||
Margin="0,0,20,0"
|
||||
ToolTip="{DynamicResource Setup.Launch.Memory.Auto.ToolTip}"
|
||||
ToolTipService.Placement="Right"
|
||||
ToolTipService.HorizontalOffset="-10"
|
||||
ToolTipService.VerticalOffset="-3" />
|
||||
|
||||
<local:MyRadioBox
|
||||
Grid.Row="3" Grid.Column="0"
|
||||
Text="{DynamicResource Common.Option.Customize}"
|
||||
Width="110"
|
||||
x:Name="RadioRamType1"
|
||||
Tag="VersionRamType/1"
|
||||
Margin="0,0,20,0" />
|
||||
|
||||
<local:MySlider
|
||||
Grid.Row="3" Grid.Column="1"
|
||||
IsEnabled="False"
|
||||
x:Name="SliderRamCustom"
|
||||
Tag="VersionRamCustom"
|
||||
MaxValue="49"
|
||||
Value="13"
|
||||
Margin="0,0,12,0" />
|
||||
</Grid>
|
||||
<Grid x:Name="PanRamDisplay" Margin="0,11,2,0" SnapsToDevicePixels="True">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition x:Name="ColumnRamUsed" Width="4.7*" />
|
||||
<ColumnDefinition x:Name="ColumnRamGame" Width="2.5*" />
|
||||
<ColumnDefinition x:Name="ColumnRamEmpty" Width="0.7*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle x:Name="RectRamUsed" Grid.Row="1" StrokeThickness="0">
|
||||
<Rectangle.Fill>
|
||||
<LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
|
||||
<GradientStop Color="{DynamicResource ColorObject3}" Offset="0" />
|
||||
<GradientStop Color="{DynamicResource ColorObject2}" Offset="0.5" />
|
||||
</LinearGradientBrush>
|
||||
</Rectangle.Fill>
|
||||
</Rectangle>
|
||||
<Rectangle Grid.Row="1" StrokeThickness="0" Grid.Column="1"
|
||||
Fill="{DynamicResource ColorBrush3}" Opacity="0.5" x:Name="RectRamGame" />
|
||||
<Rectangle x:Name="RectRamEmpty" Grid.Row="1" StrokeThickness="0" Grid.Column="2"
|
||||
Fill="{DynamicResource ColorBrush6}" Opacity="0.7" />
|
||||
<TextBlock x:Name="LabRamUsedTitle" Text="{DynamicResource Instance.Setup.Memory.Used}"
|
||||
Grid.ColumnSpan="3" Opacity="0.7"
|
||||
TextTrimming="None" Margin="2,0,0,5" FontSize="11" HorizontalAlignment="Left" />
|
||||
<TextBlock x:Name="LabRamGameTitle" Text="{DynamicResource Setup.Launch.Memory.GameAllocation}"
|
||||
Opacity="0.7" Grid.ColumnSpan="3"
|
||||
TextTrimming="None" Margin="2,0,0,5" FontSize="11" HorizontalAlignment="Left" />
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" Grid.ColumnSpan="3" Margin="2,3,0,0"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock x:Name="LabRamUsed" Text="4.7 GiB" FontSize="16"
|
||||
Foreground="{DynamicResource ColorBrushMemory}" TextTrimming="None" />
|
||||
<TextBlock x:Name="LabRamTotal" Text=" / 7.9 GiB" FontSize="16"
|
||||
Foreground="{DynamicResource ColorBrushMemory}" TextTrimming="None" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Row="2" x:Name="LabRamGame" Text="2.5 GiB" Grid.ColumnSpan="3"
|
||||
TextTrimming="None" Margin="2,3,0,0" FontSize="16"
|
||||
Foreground="{DynamicResource ColorBrushMemory}" HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
<local:MyCard x:Name="CardServer" Margin="0,0,0,15" Title="{DynamicResource Instance.Setup.Server.Title}">
|
||||
<StackPanel Margin="25,42,25,18">
|
||||
<local:MyHint x:Name="HintServerLoginLock"
|
||||
Text="{DynamicResource Instance.Setup.Server.LoginLockHint}" IsWarn="True"
|
||||
Margin="0,0,0,13" Visibility="Collapsed" />
|
||||
<local:MyHint x:Name="LabServerAuthServerSecurityVerify"
|
||||
Text="{DynamicResource Instance.Setup.Server.SslWarning}"
|
||||
IsWarn="False" Visibility="Collapsed" />
|
||||
<local:MyHint x:Name="LabServerAuthServerSecurity"
|
||||
Text="{DynamicResource Instance.Setup.Server.HttpWarning}"
|
||||
Visibility="Collapsed" />
|
||||
<TextBlock Name="LabServerAuthServerSecurityCL" Text="
" Visibility="Collapsed" />
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Name" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="28" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="9" />
|
||||
<RowDefinition Height="28" />
|
||||
<RowDefinition Height="3" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
Text="{DynamicResource Instance.Setup.Server.RestrictLogin}"
|
||||
Margin="0,0,25,0" />
|
||||
<local:MyComboBox x:Name="ComboServerLoginRequire" Grid.Row="0" Grid.ColumnSpan="2"
|
||||
Tag="VersionServerLoginRequire" Grid.Column="1">
|
||||
<local:MyComboBoxItem
|
||||
Content="{DynamicResource Instance.Setup.Server.LoginMethod.Unlimited}"
|
||||
IsSelected="True" />
|
||||
<local:MyComboBoxItem
|
||||
Content="{DynamicResource Instance.Setup.Server.LoginMethod.MicrosoftOnly}" />
|
||||
<local:MyComboBoxItem
|
||||
Content="{DynamicResource Instance.Setup.Server.LoginMethod.AuthOnly}" />
|
||||
<local:MyComboBoxItem
|
||||
Content="{DynamicResource Instance.Setup.Server.LoginMethod.MicrosoftOrAuth}" />
|
||||
</local:MyComboBox>
|
||||
<TextBlock Name="LabServerAuthServer" Grid.Row="1" VerticalAlignment="Center"
|
||||
HorizontalAlignment="Left" Text="{DynamicResource Instance.Setup.Server.AuthServer}"
|
||||
Margin="0,8,25,0" Visibility="Collapsed" />
|
||||
<local:MyTextBox x:Name="TextServerAuthServer" Grid.Row="1" Grid.ColumnSpan="2"
|
||||
ShowValidateResult="False" Tag="VersionServerAuthServer" Grid.Column="1"
|
||||
Margin="0,8,0,0" Visibility="Collapsed"
|
||||
HintText="{DynamicResource Instance.Setup.Server.AuthServer.Hint}">
|
||||
<local:MyTextBox.ValidateRules>
|
||||
<val:HttpValidator />
|
||||
</local:MyTextBox.ValidateRules>
|
||||
</local:MyTextBox>
|
||||
<TextBlock Name="LabServerAuthRegister" Grid.Row="2" VerticalAlignment="Center"
|
||||
HorizontalAlignment="Left" Text="{DynamicResource Instance.Setup.Server.Register}"
|
||||
Margin="0,8,25,0" Visibility="Collapsed" />
|
||||
<local:MyTextBox x:Name="TextServerAuthRegister" Grid.Row="2" Grid.ColumnSpan="2"
|
||||
ShowValidateResult="False" Tag="VersionServerAuthRegister" Grid.Column="1"
|
||||
Margin="0,8,0,0" Visibility="Collapsed"
|
||||
HintText="{DynamicResource Instance.Setup.Server.Register.Hint}">
|
||||
<local:MyTextBox.ValidateRules>
|
||||
<val:HttpValidator AllowsNullOrEmpty="True" />
|
||||
</local:MyTextBox.ValidateRules>
|
||||
</local:MyTextBox>
|
||||
<TextBlock Name="LabServerAuthName" Grid.Row="3" VerticalAlignment="Center"
|
||||
HorizontalAlignment="Left" Text="{DynamicResource Instance.Setup.Server.Name}"
|
||||
Margin="0,8,25,0" Visibility="Collapsed" />
|
||||
<local:MyTextBox x:Name="TextServerAuthName" Grid.Row="3" Grid.ColumnSpan="2"
|
||||
ShowValidateResult="False" Tag="VersionServerAuthName" Grid.Column="1"
|
||||
Margin="0,8,0,0" Visibility="Collapsed"
|
||||
HintText="{DynamicResource Instance.Setup.Server.Name.Hint}" />
|
||||
<TextBlock VerticalAlignment="Center" Grid.Row="5" HorizontalAlignment="Left"
|
||||
Text="{DynamicResource Instance.Setup.Server.AutoJoin}"
|
||||
Margin="0,0,25,0" />
|
||||
<local:MyTextBox x:Name="TextServerEnter" Grid.ColumnSpan="2" Grid.Row="5"
|
||||
Tag="VersionServerEnter" Grid.Column="1"
|
||||
ToolTip="{DynamicResource Instance.Setup.Server.AutoJoin.ToolTip}">
|
||||
<local:MyTextBox.ValidateRules>
|
||||
<val:BlacklistValidator>
|
||||
<val:BlacklistValidator.Blacklist>
|
||||
<System:String>"</System:String>
|
||||
<System:String>“</System:String>
|
||||
<System:String>”</System:String>
|
||||
<System:String>http://</System:String>
|
||||
<System:String>https://</System:String>
|
||||
</val:BlacklistValidator.Blacklist>
|
||||
</val:BlacklistValidator>
|
||||
</local:MyTextBox.ValidateRules>
|
||||
</local:MyTextBox>
|
||||
</Grid>
|
||||
<local:MyButton x:Name="BtnServerAuthLittle" Height="35" MinWidth="150" Margin="0,12,0,0"
|
||||
Text="{DynamicResource Instance.Setup.Server.SetLittleSkin}"
|
||||
HorizontalAlignment="Left" ColorType="Highlight" />
|
||||
<local:MyButton x:Name="BtnServerAuthLock" Height="35" MinWidth="150" Margin="180,-35,0,0"
|
||||
Text="{DynamicResource Instance.Setup.Server.LockLoginMethod}"
|
||||
HorizontalAlignment="Left" IsEnabled="False" />
|
||||
<local:MyButton x:Name="BtnServerNewProfile" Height="35" MinWidth="150" Margin="360,-35,0,0"
|
||||
Text="{DynamicResource Instance.Setup.Server.CreateProfile}"
|
||||
HorizontalAlignment="Left" />
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
<local:MyCard x:Name="CardAdvance" Margin="0,0,0,15"
|
||||
Title="{DynamicResource Setup.Launch.Advanced.Title}" IsSwapped="True" CanSwap="True">
|
||||
<Grid Margin="25,40,25,15">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Name" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="28" />
|
||||
<RowDefinition Height="9" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="9" />
|
||||
<RowDefinition Height="28" />
|
||||
<RowDefinition Height="9" />
|
||||
<RowDefinition Height="28" />
|
||||
<RowDefinition Height="9" />
|
||||
<RowDefinition Height="31" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
Text="{DynamicResource Setup.Launch.Advanced.Renderer.Label}"
|
||||
Margin="0,0,25,0" />
|
||||
<local:MyComboBox x:Name="ComboAdvanceRenderer" Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2"
|
||||
Tag="VersionAdvanceRenderer">
|
||||
<local:MyComboBoxItem Content="{DynamicResource Instance.Setup.FollowGlobal}"
|
||||
IsSelected="True" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Setup.Launch.Advanced.Renderer.GameDefault}" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Setup.Launch.Advanced.Renderer.Software}" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Setup.Launch.Advanced.Renderer.DirectX12}" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Setup.Launch.Advanced.Renderer.Vulkan}" />
|
||||
</local:MyComboBox>
|
||||
|
||||
<TextBlock Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
Text="{DynamicResource Setup.Launch.Advanced.JvmHead}"
|
||||
Margin="0,0,25,0" />
|
||||
<local:MyTextBox x:Name="TextAdvanceJvm" Grid.Row="2" Grid.ColumnSpan="2" Tag="VersionAdvanceJvm"
|
||||
Grid.Column="1" MaxLength="4000"
|
||||
ToolTip="{DynamicResource Instance.Setup.Advanced.JvmArgs.ToolTip}"
|
||||
HintText="{DynamicResource Instance.Setup.FollowGlobal}"
|
||||
Height="100" AcceptsReturn="True" TextWrapping="Wrap"
|
||||
VerticalContentAlignment="Top" Padding="0,5" VerticalScrollBarVisibility="Auto" />
|
||||
|
||||
<TextBlock Grid.Row="4" VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
Text="{DynamicResource Setup.Launch.Advanced.GameTail}"
|
||||
Margin="0,0,25,0" />
|
||||
<local:MyTextBox x:Name="TextAdvanceGame" Grid.Row="4" Grid.ColumnSpan="2" Tag="VersionAdvanceGame"
|
||||
Grid.Column="1"
|
||||
ToolTip="{DynamicResource Instance.Setup.Advanced.GameArgs.ToolTip}"
|
||||
HintText="{DynamicResource Instance.Setup.FollowGlobal}" />
|
||||
|
||||
<TextBlock Grid.Row="6" VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
Text="{DynamicResource Instance.Setup.Advanced.Classpath}"
|
||||
Margin="0,0,25,0" />
|
||||
<local:MyTextBox x:Name="TextAdvanceClasspathHead" Grid.Row="6" Grid.ColumnSpan="2"
|
||||
Tag="VersionAdvanceClasspathHead" Grid.Column="1"
|
||||
ToolTip="{DynamicResource Instance.Setup.Advanced.Classpath.ToolTip}" />
|
||||
|
||||
<TextBlock Grid.Row="8" VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
Text="{DynamicResource Setup.Launch.Advanced.PreLaunchCommand}"
|
||||
Margin="0,0,25,3" />
|
||||
<local:MyTextBox x:Name="TextAdvanceRun" Grid.Row="8" Grid.ColumnSpan="2" Tag="VersionAdvanceRun"
|
||||
Grid.Column="1" HintText="" Margin="0,0,0,3"
|
||||
ToolTip="{DynamicResource Instance.Setup.Advanced.PreLaunchCommand.ToolTip}" />
|
||||
|
||||
<StackPanel Margin="0,8,0,4" Grid.Row="9" Grid.ColumnSpan="2" HorizontalAlignment="Left">
|
||||
<local:MyCheckBox Height="28" x:Name="CheckAdvanceRunWait" Tag="VersionAdvanceRunWait"
|
||||
Visibility="Collapsed"
|
||||
Text="{DynamicResource Setup.Launch.Advanced.PreLaunchCommand.Wait}" />
|
||||
<local:MyCheckBox Height="28"
|
||||
Text="{DynamicResource Instance.Setup.Advanced.IgnoreJavaCompatibility}"
|
||||
x:Name="CheckAdvanceJava"
|
||||
Tag="VersionAdvanceJava"
|
||||
ToolTipService.Placement="Right"
|
||||
ToolTip="{DynamicResource Instance.Setup.Advanced.IgnoreJavaCompatibility.ToolTip}" />
|
||||
<local:MyCheckBox Height="28"
|
||||
Text="{DynamicResource Instance.Setup.Advanced.DisableFileVerification}"
|
||||
x:Name="CheckAdvanceAssetsV2"
|
||||
Tag="VersionAdvanceAssetsV2" Margin="0,0,60,0"
|
||||
ToolTipService.Placement="Right"
|
||||
ToolTip="{DynamicResource Instance.Setup.Advanced.DisableFileVerification.ToolTip}" />
|
||||
<local:MyCheckBox Height="28" Text="{DynamicResource Instance.Setup.Advanced.UseGlobalProxy}"
|
||||
x:Name="CheckAdvanceUseProxyV2"
|
||||
Tag="VersionAdvanceUseProxyV2"
|
||||
ToolTipService.Placement="Right"
|
||||
ToolTip="{DynamicResource Instance.Setup.Advanced.UseGlobalProxy.ToolTip}" />
|
||||
<local:MyCheckBox Height="28"
|
||||
Text="{DynamicResource Setup.Launch.Advanced.DisableJlw}"
|
||||
x:Name="CheckAdvanceDisableJLW"
|
||||
Tag="VersionAdvanceDisableJLW"
|
||||
ToolTipService.Placement="Right"
|
||||
ToolTip="{DynamicResource Setup.Launch.Advanced.DisableJlw.ToolTip}" />
|
||||
<local:MyCheckBox Height="28"
|
||||
Text="{DynamicResource Setup.Launch.Advanced.DisableLF}"
|
||||
x:Name="CheckAdvanceDisableLF"
|
||||
Tag="VersionAdvanceDisableLF"
|
||||
ToolTipService.Placement="Right"
|
||||
ToolTip="{DynamicResource Setup.Launch.Advanced.DisableLF.ToolTip}" />
|
||||
<local:MyCheckBox Height="28" Text="{DynamicResource Instance.Setup.Advanced.UseDebugLog4j}"
|
||||
x:Name="CheckUseDebugLog4j2Config"
|
||||
Tag="VersionUseDebugLog4j2Config"
|
||||
ToolTipService.Placement="Right" />
|
||||
<local:MyCheckBox Height="28"
|
||||
Text="{DynamicResource Setup.Launch.Advanced.DisableLwjglUnsafeAgent}"
|
||||
x:Name="CheckAdvanceDisableLwjglUnsafeAgent"
|
||||
Tag="VersionAdvanceDisableLwjglUnsafeAgent"
|
||||
ToolTipService.Placement="Right"
|
||||
ToolTip="{DynamicResource Setup.Launch.Advanced.DisableLwjglUnsafeAgent.ToolTip}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
<local:MyExtraTextButton HorizontalAlignment="Center" Margin="0,-5,0,0"
|
||||
x:Name="BtnSwitch" Text="{DynamicResource Instance.Setup.OpenGlobalSettings}"
|
||||
LogoScale="0.9"
|
||||
SvgIcon="lucide/arrow-right" />
|
||||
</StackPanel>
|
||||
</local:MyScrollViewer>
|
||||
</local:MyPageRight>
|
||||
@@ -0,0 +1,1053 @@
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
using PCL.Core.App;
|
||||
using PCL.Core.App.Configuration;
|
||||
using PCL.Core.IO;
|
||||
using PCL.Core.Minecraft;
|
||||
using PCL.Core.Minecraft.Java.UserPreference;
|
||||
using PCL.Core.UI;
|
||||
using PCL.Core.Utils.OS;
|
||||
using PCL.Core.App.Localization;
|
||||
using PCL.Core.Utils;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageInstanceSetup
|
||||
{
|
||||
private new bool isLoaded;
|
||||
|
||||
public PageInstanceSetup()
|
||||
{
|
||||
Loaded += PageSetupSystem_Loaded;
|
||||
InitializeComponent();
|
||||
|
||||
ComboArgumentIndieV2.SelectionChanged += ComboArgumentIndieV2_SelectionChanged;
|
||||
TextArgumentTitle.TextChanged += TextArgumentTitle_TextChanged;
|
||||
TextArgumentInfo.TextChanged += TextBoxChange;
|
||||
ComboArgumentJava.SelectionChanged += JavaSelectionUpdate;
|
||||
|
||||
RadioRamType2.Check += RadioBoxChange;
|
||||
RadioRamType0.Check += RadioBoxChange;
|
||||
RadioRamType1.Check += RadioBoxChange;
|
||||
SliderRamCustom.Change += SliderChange;
|
||||
|
||||
ComboServerLoginRequire.SelectionChanged += ComboServerLogin_Changed;
|
||||
TextServerAuthServer.TextChanged += TextBoxChange;
|
||||
TextServerAuthServer.LostFocus += TextServerAuthServer_MouseLeave;
|
||||
TextServerAuthRegister.TextChanged += TextBoxChange;
|
||||
TextServerAuthName.TextChanged += TextBoxChange;
|
||||
TextServerEnter.TextChanged += TextBoxChange;
|
||||
BtnServerAuthLittle.Click += BtnServerAuthLittle_Click;
|
||||
BtnServerAuthLock.Click += BtnServerAuthLock_Click;
|
||||
BtnServerNewProfile.Click += BtnServerNewProfile_Click;
|
||||
|
||||
ComboAdvanceRenderer.SelectionChanged += ComboAdvanceRenderer_SelectionChanged;
|
||||
TextAdvanceJvm.TextChanged += TextBoxChange;
|
||||
TextAdvanceGame.TextChanged += TextBoxChange;
|
||||
TextAdvanceClasspathHead.TextChanged += TextBoxChange;
|
||||
TextAdvanceRun.TextChanged += TextAdvanceRun_TextChanged;
|
||||
CheckAdvanceRunWait.Change += CheckBoxChange;
|
||||
CheckAdvanceJava.Change += CheckBoxChange;
|
||||
CheckAdvanceAssetsV2.Change += CheckBoxChange;
|
||||
CheckAdvanceUseProxyV2.Change += CheckBoxChange;
|
||||
CheckAdvanceDisableJLW.Change += CheckBoxChange;
|
||||
CheckAdvanceDisableLF.Change += CheckBoxChange;
|
||||
CheckUseDebugLog4j2Config.Change += CheckUseDebugLog4j2Config_CheckChanged;
|
||||
CheckAdvanceDisableLwjglUnsafeAgent.Change += CheckBoxChange;
|
||||
|
||||
BtnSwitch.Click += BtnSwitch_Click;
|
||||
|
||||
TextServerEnter.TextChanged += TextServerEnter_Change;
|
||||
ComboArgumentJava.DropDownOpened += ComboArgumentJava_DropDownOpened;
|
||||
CheckArgumentTitleEmpty.Change += CheckArgumentTitleEmpty_Change;
|
||||
}
|
||||
|
||||
private void PageSetupSystem_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 重复加载部分
|
||||
PanBack.ScrollToHome();
|
||||
RefreshRam(false);
|
||||
|
||||
// 由于各个实例不同,每次都需要重新加载
|
||||
ModAnimation.AniControlEnabled += 1;
|
||||
Reload();
|
||||
ModAnimation.AniControlEnabled -= 1;
|
||||
|
||||
// 非重复加载部分
|
||||
if (isLoaded)
|
||||
return;
|
||||
isLoaded = true;
|
||||
|
||||
// 内存自动刷新
|
||||
var timer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 0, 1) };
|
||||
timer.Tick += (_, _) => RefreshRam();
|
||||
timer.Start();
|
||||
RectRamGame.SizeChanged += (s, e) => RefreshRamText();
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 启动参数
|
||||
TextArgumentTitle.Text = Config.Instance.Title[PageInstanceLeft.McInstance.PathInstance];
|
||||
CheckArgumentTitleEmpty.Checked = Config.Instance.UseGlobalTitle[PageInstanceLeft.McInstance.PathInstance];
|
||||
TextArgumentInfo.Text = Config.Instance.TypeInfo[PageInstanceLeft.McInstance.PathInstance];
|
||||
var _unused = PageInstanceLeft.McInstance.PathIndie; // 触发自动判定
|
||||
ComboArgumentIndieV2.SelectedIndex = Config.Instance.IndieV2[PageInstanceLeft.McInstance.PathInstance] ? 0 : 1;
|
||||
CheckArgumentTitleEmpty.Visibility = TextArgumentTitle.Text.Length > 0 ? Visibility.Collapsed : Visibility.Visible;
|
||||
TextArgumentTitle.HintText = CheckArgumentTitleEmpty.Checked == true ? Lang.Text("Common.Option.Default") : Lang.Text("Instance.Setup.FollowGlobal");
|
||||
RefreshJavaComboBox();
|
||||
|
||||
// 游戏内存
|
||||
var ramType = Config.Instance.MemorySolution[PageInstanceLeft.McInstance.PathInstance];
|
||||
((MyRadioBox)FindName("RadioRamType" + ramType)).Checked = true;
|
||||
SliderRamCustom.Value = Config.Instance.CustomMemorySize[PageInstanceLeft.McInstance.PathInstance];
|
||||
RamType(ramType);
|
||||
|
||||
// 服务器
|
||||
TextServerEnter.Text = Config.Instance.ServerToEnter[PageInstanceLeft.McInstance.PathInstance];
|
||||
ComboServerLoginRequire.SelectedIndex = Config.InstanceAuth.LoginRequirementSolution[PageInstanceLeft.McInstance.PathInstance];
|
||||
comboServerLoginLast = ComboServerLoginRequire.SelectedIndex;
|
||||
ServerLogin(ComboServerLoginRequire.SelectedIndex);
|
||||
TextServerAuthServer.Text = Config.InstanceAuth.AuthServerAddress[PageInstanceLeft.McInstance.PathInstance];
|
||||
TextServerAuthName.Text = Config.InstanceAuth.AuthServerDisplayName[PageInstanceLeft.McInstance.PathInstance];
|
||||
TextServerAuthRegister.Text = Config.InstanceAuth.AuthRegisterAddress[PageInstanceLeft.McInstance.PathInstance];
|
||||
|
||||
// 高级设置
|
||||
ComboAdvanceRenderer.SelectedIndex = Config.Instance.Renderer[PageInstanceLeft.McInstance.PathInstance];
|
||||
TextAdvanceClasspathHead.Text = Config.Instance.ClasspathHead[PageInstanceLeft.McInstance.PathInstance];
|
||||
TextAdvanceJvm.Text = Config.Instance.JvmArgs[PageInstanceLeft.McInstance.PathInstance];
|
||||
TextAdvanceGame.Text = Config.Instance.GameArgs[PageInstanceLeft.McInstance.PathInstance];
|
||||
TextAdvanceRun.Text = Config.Instance.PreLaunchCommand[PageInstanceLeft.McInstance.PathInstance];
|
||||
CheckAdvanceRunWait.Checked = Config.Instance.PreLaunchCommandWait[PageInstanceLeft.McInstance.PathInstance];
|
||||
CheckAdvanceDisableLwjglUnsafeAgent.Checked = Config.Instance.DisableLwjglUnsafeAgent[PageInstanceLeft.McInstance.PathInstance];
|
||||
if (Config.Instance.AssetVerifySolutionV1[PageInstanceLeft.McInstance.PathInstance] == 2)
|
||||
{
|
||||
ModBase.Log("[Setup] 已迁移老版本的关闭文件校验设置");
|
||||
Config.Instance.AssetVerifySolutionV1Config.Reset(PageInstanceLeft.McInstance.PathInstance);
|
||||
Config.Instance.DisableAssetVerifyV2[PageInstanceLeft.McInstance.PathInstance] = true;
|
||||
}
|
||||
|
||||
CheckAdvanceAssetsV2.Checked = Config.Instance.DisableAssetVerifyV2[PageInstanceLeft.McInstance.PathInstance];
|
||||
CheckAdvanceUseProxyV2.Checked = Config.Instance.UseProxy[PageInstanceLeft.McInstance.PathInstance];
|
||||
CheckAdvanceJava.Checked = Config.Instance.IgnoreJavaCompatibility[PageInstanceLeft.McInstance.PathInstance];
|
||||
if (SystemInfo.IsArm64System)
|
||||
{
|
||||
CheckAdvanceDisableJLW.Checked = true;
|
||||
CheckAdvanceDisableJLW.IsEnabled = false;
|
||||
CheckAdvanceDisableJLW.ToolTip = Lang.Text("Setup.Launch.Advanced.DisableJlw.Arm64ToolTip");
|
||||
}
|
||||
else
|
||||
{
|
||||
CheckAdvanceDisableJLW.Checked = Config.Instance.DisableJlw[PageInstanceLeft.McInstance.PathInstance];
|
||||
}
|
||||
CheckUseDebugLog4j2Config.Checked = Config.Instance.UseDebugLof4j2Config[PageInstanceLeft.McInstance.PathInstance];
|
||||
CheckAdvanceDisableLF.Checked = Config.Instance.DisableLF[PageInstanceLeft.McInstance.PathInstance];
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"重载实例独立设置时出错",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Setup.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
public void Reset()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Config.InstanceAuth.AuthLocked[PageInstanceLeft.McInstance.PathInstance])
|
||||
Config.InstanceAuth.Reset(PageInstanceLeft.McInstance.PathInstance);
|
||||
|
||||
Config.Instance.Reset(PageInstanceLeft.McInstance.PathInstance);
|
||||
|
||||
ModBase.Log("[Setup] 已初始化实例独立设置");
|
||||
HintService.Hint(Lang.Text("Instance.Setup.Initialize.Success"), HintType.Success, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"初始化实例独立设置失败",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Instance.Setup.Error.OperationFailed"));
|
||||
}
|
||||
|
||||
Reload();
|
||||
}
|
||||
|
||||
// 将控件改变路由到设置改变
|
||||
private static void SetByTag(string tag, object value)
|
||||
=> ConfigService.TrySetValue(tag, value, PageInstanceLeft.McInstance.PathInstance);
|
||||
|
||||
private void RadioBoxChange(object o, ModBase.RouteEventArgs routeEventArgs)
|
||||
{
|
||||
if (ModAnimation.AniControlEnabled != 0)
|
||||
return;
|
||||
if (o is not MyRadioBox { Tag: string tag }) return;
|
||||
|
||||
var slash = tag.IndexOf('/');
|
||||
if (slash < 0) return;
|
||||
|
||||
SetByTag(tag[..slash], int.Parse(tag[(slash + 1)..]));
|
||||
}
|
||||
|
||||
private void TextBoxChange(object o, TextChangedEventArgs textChangedEventArgs)
|
||||
{
|
||||
if (ModAnimation.AniControlEnabled != 0)
|
||||
return;
|
||||
if (o is not MyTextBox textBox) return;
|
||||
|
||||
SetByTag(textBox.Tag?.ToString(), textBox.Text);
|
||||
}
|
||||
|
||||
private void SliderChange(object o, bool user)
|
||||
{
|
||||
if (ModAnimation.AniControlEnabled != 0)
|
||||
return;
|
||||
if (o is not MySlider slider) return;
|
||||
|
||||
SetByTag(slider.Tag?.ToString(), slider.Value);
|
||||
}
|
||||
|
||||
private void ComboChange(MyComboBox sender, object e)
|
||||
{
|
||||
if (ModAnimation.AniControlEnabled != 0)
|
||||
return;
|
||||
|
||||
SetByTag(sender.Tag?.ToString(), sender.SelectedIndex);
|
||||
}
|
||||
|
||||
private void CheckBoxChange(object sender, bool user)
|
||||
{
|
||||
if (ModAnimation.AniControlEnabled != 0)
|
||||
return;
|
||||
if (sender is not MyCheckBox checkBox) return;
|
||||
|
||||
SetByTag(checkBox.Tag?.ToString(), checkBox.Checked.GetValueOrDefault());
|
||||
}
|
||||
|
||||
// 切换到全局设置
|
||||
private void BtnSwitch_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
ModMain.frmMain.PageChange(FormMain.PageType.Setup);
|
||||
}
|
||||
|
||||
#region 游戏内存
|
||||
public void RamType(int type)
|
||||
{
|
||||
if (SliderRamCustom is null)
|
||||
return;
|
||||
SliderRamCustom.IsEnabled = type == 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新 UI 上的 RAM 显示。
|
||||
/// </summary>
|
||||
public void RefreshRam(bool showAnim)
|
||||
{
|
||||
if (LabRamGame is null || LabRamUsed is null ||
|
||||
ModMain.frmMain.pageCurrent != FormMain.PageType.InstanceSetup ||
|
||||
ModMain.frmInstanceLeft.pageID != FormMain.PageSubType.VersionSetup)
|
||||
return;
|
||||
// 获取内存情况
|
||||
var ramGame = Math.Round(GetRam(PageInstanceLeft.McInstance), 5);
|
||||
var phyRam = KernelInterop.GetPhysicalMemoryBytes();
|
||||
var ramTotal = Math.Round((double)(phyRam.Total / 1024 / 1024 / 1024), 1);
|
||||
var ramAvailable = Math.Round((double)(phyRam.Available / 1024 / 1024 / 1024), 1);
|
||||
var ramGameActual = Math.Round(Math.Min(ramGame, ramAvailable), 5);
|
||||
var ramUsed = Math.Round(ramTotal - ramAvailable, 5);
|
||||
var ramEmpty = Math.Round(ModBase.MathClamp(ramTotal - ramUsed - ramGame, 0d, 1000d), 1);
|
||||
// 设置最大可用内存
|
||||
if (ramTotal <= 1.5d)
|
||||
SliderRamCustom.MaxValue = (int)Math.Round(Math.Max(Math.Floor((ramTotal - 0.3d) / 0.1d), 1d));
|
||||
else if (ramTotal <= 8d)
|
||||
SliderRamCustom.MaxValue = (int)Math.Round(Math.Floor((ramTotal - 1.5d) / 0.5d) + 12d);
|
||||
else if (ramTotal <= 16d)
|
||||
SliderRamCustom.MaxValue = (int)Math.Round(Math.Floor((ramTotal - 8d) / 1d) + 25d);
|
||||
else
|
||||
SliderRamCustom.MaxValue = (int)Math.Round(Math.Floor((ramTotal - 16d) / 2d) + 33d);
|
||||
// 设置文本
|
||||
LabRamGame.Text = $"{Lang.Number(ramGame, "N1")} GiB{(ramGame != ramGameActual ? $" ({Lang.Text("Setup.Launch.Memory.AvailableSuffix", Lang.Number(ramGameActual, "N1"))})" : "")}";
|
||||
LabRamUsed.Text = $"{Lang.Number(ramUsed, "N1")} GiB";
|
||||
LabRamTotal.Text = $" / {Lang.Number(ramTotal, "N1")} GiB";
|
||||
LabRamWarn.Visibility =
|
||||
ramGame == 1d && !ModJava.IsGameSet64BitJava(PageInstanceLeft.McInstance) && !SystemInfo.Is32BitSystem &&
|
||||
ModJava.Javas.ExistAnyJava()
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
HintRamTooHigh.Visibility = ramGame / ramTotal > 0.75d ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (showAnim)
|
||||
{
|
||||
// 宽度动画
|
||||
ModAnimation.AniStart(
|
||||
new[]
|
||||
{
|
||||
ModAnimation.AaGridLengthWidth(ColumnRamUsed, ramUsed - ColumnRamUsed.Width.Value, 800,
|
||||
ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Strong)),
|
||||
ModAnimation.AaGridLengthWidth(ColumnRamGame, ramGameActual - ColumnRamGame.Width.Value, 800,
|
||||
ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Strong)),
|
||||
ModAnimation.AaGridLengthWidth(ColumnRamEmpty, ramEmpty - ColumnRamEmpty.Width.Value, 800,
|
||||
ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Strong))
|
||||
}, "VersionSetup Ram Grid");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 宽度设置
|
||||
ColumnRamUsed.Width = new GridLength(ramUsed, GridUnitType.Star);
|
||||
ColumnRamGame.Width = new GridLength(ramGameActual, GridUnitType.Star);
|
||||
ColumnRamEmpty.Width = new GridLength(ramEmpty, GridUnitType.Star);
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshRam()
|
||||
{
|
||||
RefreshRam(true);
|
||||
}
|
||||
|
||||
private int ramTextLeft = 2;
|
||||
private int ramTextRight = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 刷新 UI 上的文本位置。
|
||||
/// </summary>
|
||||
private void RefreshRamText()
|
||||
{
|
||||
// 获取宽度信息
|
||||
var rectUsedWidth = RectRamUsed.ActualWidth;
|
||||
var totalWidth = PanRamDisplay.ActualWidth;
|
||||
var labGameWidth = LabRamGame.ActualWidth;
|
||||
var labUsedWidth = LabRamUsed.ActualWidth;
|
||||
var labTotalWidth = LabRamTotal.ActualWidth;
|
||||
var labGameTitleWidth = LabRamGameTitle.ActualWidth;
|
||||
var labUsedTitleWidth = LabRamUsedTitle.ActualWidth;
|
||||
// 左侧
|
||||
int left;
|
||||
if (rectUsedWidth - 30d < labUsedWidth || rectUsedWidth - 30d < labUsedTitleWidth)
|
||||
// 全写不下了
|
||||
left = 0;
|
||||
else if (rectUsedWidth - 25d < labUsedWidth + labTotalWidth)
|
||||
// 显示不下完整数据
|
||||
left = 1;
|
||||
else
|
||||
// 正常
|
||||
left = 2;
|
||||
if (ramTextLeft != left)
|
||||
{
|
||||
ramTextLeft = left;
|
||||
switch (left)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
ModAnimation.AniStart(
|
||||
new[]
|
||||
{
|
||||
ModAnimation.AaOpacity(LabRamUsed, -LabRamUsed.Opacity, 100),
|
||||
ModAnimation.AaOpacity(LabRamTotal, -LabRamTotal.Opacity, 100),
|
||||
ModAnimation.AaOpacity(LabRamUsedTitle, -LabRamUsedTitle.Opacity, 100)
|
||||
}, "VersionSetup Ram TextLeft");
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
ModAnimation.AniStart(
|
||||
new[]
|
||||
{
|
||||
ModAnimation.AaOpacity(LabRamUsed, 1d - LabRamUsed.Opacity, 100),
|
||||
ModAnimation.AaOpacity(LabRamTotal, -LabRamTotal.Opacity, 100),
|
||||
ModAnimation.AaOpacity(LabRamUsedTitle, 0.7d - LabRamUsedTitle.Opacity, 100)
|
||||
}, "VersionSetup Ram TextLeft");
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
ModAnimation.AniStart(
|
||||
new[]
|
||||
{
|
||||
ModAnimation.AaOpacity(LabRamUsed, 1d - LabRamUsed.Opacity, 100),
|
||||
ModAnimation.AaOpacity(LabRamTotal, 1d - LabRamTotal.Opacity, 100),
|
||||
ModAnimation.AaOpacity(LabRamUsedTitle, 0.7d - LabRamUsedTitle.Opacity, 100)
|
||||
}, "VersionSetup Ram TextLeft");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 右侧
|
||||
int right;
|
||||
if (totalWidth < labGameWidth + 2d + rectUsedWidth || totalWidth < labGameTitleWidth + 2d + rectUsedWidth)
|
||||
// 挤到最右边
|
||||
right = 0;
|
||||
else
|
||||
// 正常情况
|
||||
right = 1;
|
||||
if (right == 0)
|
||||
{
|
||||
if (ModAnimation.AniControlEnabled == 0 &&
|
||||
(ramTextRight != right || ModAnimation.AniIsRun("VersionSetup Ram TextRight")))
|
||||
{
|
||||
// 需要动画
|
||||
ModAnimation.AniStart(
|
||||
new[]
|
||||
{
|
||||
ModAnimation.AaX(LabRamGame, totalWidth - labGameWidth - LabRamGame.Margin.Left, 100,
|
||||
ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak)),
|
||||
ModAnimation.AaX(LabRamGameTitle, totalWidth - labGameTitleWidth - LabRamGameTitle.Margin.Left,
|
||||
100, ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak))
|
||||
}, "VersionSetup Ram TextRight");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 不需要动画
|
||||
LabRamGame.Margin = new Thickness(totalWidth - labGameWidth, 3d, 0d, 0d);
|
||||
LabRamGameTitle.Margin = new Thickness(totalWidth - labGameTitleWidth, 0d, 0d, 5d);
|
||||
}
|
||||
}
|
||||
else if (ModAnimation.AniControlEnabled == 0 &&
|
||||
(ramTextRight != right || ModAnimation.AniIsRun("VersionSetup Ram TextRight")))
|
||||
{
|
||||
// 需要动画
|
||||
ModAnimation.AniStart(
|
||||
new[]
|
||||
{
|
||||
ModAnimation.AaX(LabRamGame, 2d + rectUsedWidth - LabRamGame.Margin.Left, 100,
|
||||
ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak)),
|
||||
ModAnimation.AaX(LabRamGameTitle, 2d + rectUsedWidth - LabRamGameTitle.Margin.Left, 100,
|
||||
ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak))
|
||||
}, "VersionSetup Ram TextRight");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 不需要动画
|
||||
LabRamGame.Margin = new Thickness(2d + rectUsedWidth, 3d, 0d, 0d);
|
||||
LabRamGameTitle.Margin = new Thickness(2d + rectUsedWidth, 0d, 0d, 5d);
|
||||
}
|
||||
|
||||
ramTextRight = right;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前设置的 RAM 值。单位为 GB。
|
||||
/// </summary>
|
||||
public static double GetRam(McInstance version, bool? is32BitJava = default)
|
||||
{
|
||||
var instancePath = version?.PathInstance;
|
||||
// 跟随全局设置
|
||||
if (Config.Instance.MemorySolution[instancePath] == 2)
|
||||
return PageSetupLaunch.GetRam(version, true, is32BitJava);
|
||||
|
||||
// ------------------------------------------
|
||||
// 修改下方代码时需要一并修改 PageSetupLaunch
|
||||
// ------------------------------------------
|
||||
|
||||
// 使用当前实例的设置
|
||||
var ramGive = default(double);
|
||||
if (Config.Instance.MemorySolution[instancePath] == 0)
|
||||
{
|
||||
// 自动配置
|
||||
var ramAvailable =
|
||||
Math.Round((double)(KernelInterop.GetAvailablePhysicalMemoryBytes() / 1024 / 1024 / 1024 * 10)) / 10;
|
||||
// 确定需求的内存值
|
||||
double ramMininum; // 无论如何也需要保证的最低限度内存
|
||||
double ramTarget1; // 估计能勉强带动了的内存
|
||||
double ramTarget2; // 估计没啥问题了的内存
|
||||
double ramTarget3; // 安装过多附加组件需要的内存
|
||||
if (version is not null && !version.IsLoaded)
|
||||
version.Load();
|
||||
if (version is not null && version.Modable)
|
||||
{
|
||||
// 可安装 Mod 的实例
|
||||
var modDir = new DirectoryInfo(version.PathIndie + @"mods\");
|
||||
var modCount = modDir.Exists ? modDir.GetFiles().Length : 0;
|
||||
ramMininum = 0.5d + modCount / 150d;
|
||||
ramTarget1 = 1.5d + modCount / 90d;
|
||||
ramTarget2 = 2.7d + modCount / 50d;
|
||||
ramTarget3 = 4.5d + modCount / 25d;
|
||||
}
|
||||
else if (version is not null && version.Info.HasOptiFine)
|
||||
{
|
||||
// OptiFine 实例
|
||||
ramMininum = 0.5d;
|
||||
ramTarget1 = 1.5d;
|
||||
ramTarget2 = 3d;
|
||||
ramTarget3 = 5d;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 普通实例
|
||||
ramMininum = 0.5d;
|
||||
ramTarget1 = 1.5d;
|
||||
ramTarget2 = 2.5d;
|
||||
ramTarget3 = 4d;
|
||||
}
|
||||
|
||||
double ramDelta;
|
||||
// 预分配内存,阶段一,0 ~ T1,100%
|
||||
ramDelta = ramTarget1;
|
||||
ramGive += Math.Min(ramAvailable, ramDelta);
|
||||
ramAvailable -= ramDelta;
|
||||
if (ramAvailable >= 0.1d)
|
||||
{
|
||||
// 预分配内存,阶段二,T1 ~ T2,70%
|
||||
ramDelta = ramTarget2 - ramTarget1;
|
||||
ramGive += Math.Min(ramAvailable * 0.7d, ramDelta);
|
||||
ramAvailable -= ramDelta / 0.7d;
|
||||
if (ramAvailable >= 0.1d)
|
||||
{
|
||||
// 预分配内存,阶段三,T2 ~ T3,40%
|
||||
ramDelta = ramTarget3 - ramTarget2;
|
||||
ramGive += Math.Min(ramAvailable * 0.4d, ramDelta);
|
||||
ramAvailable -= ramDelta / 0.4d;
|
||||
if (ramAvailable >= 0.1d)
|
||||
{
|
||||
// 预分配内存,阶段四,T3 ~ T3 * 2,15%
|
||||
ramDelta = ramTarget3;
|
||||
ramGive += Math.Min(ramAvailable * 0.15d, ramDelta);
|
||||
ramAvailable -= ramDelta / 0.15d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 不低于最低值
|
||||
ramGive = Math.Round(Math.Max(ramGive, ramMininum), 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 手动配置
|
||||
var value = Config.Instance.CustomMemorySize[instancePath];
|
||||
if (value <= 12)
|
||||
ramGive = value * 0.1d + 0.3d;
|
||||
else if (value <= 25)
|
||||
ramGive = (value - 12) * 0.5d + 1.5d;
|
||||
else if (value <= 33)
|
||||
ramGive = (value - 25) * 1 + 8;
|
||||
else
|
||||
ramGive = (value - 33) * 2 + 16;
|
||||
}
|
||||
|
||||
// 若使用 32 位 Java,则限制为 1G
|
||||
if (is32BitJava ?? !ModJava.IsGameSet64BitJava(PageInstanceLeft.McInstance))
|
||||
ramGive = Math.Min(1d, ramGive);
|
||||
return ramGive;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 服务器
|
||||
|
||||
// 全局
|
||||
private int comboServerLoginLast;
|
||||
|
||||
private void ComboServerLogin_Changed(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (ModAnimation.AniControlEnabled != 0)
|
||||
return;
|
||||
ServerLogin(ComboServerLoginRequire.SelectedIndex);
|
||||
if (TextServerAuthServer.IsValidated)
|
||||
BtnServerAuthLock.IsEnabled = true;
|
||||
else
|
||||
BtnServerAuthLock.IsEnabled = false;
|
||||
if ((ComboServerLoginRequire.SelectedIndex == 2 || ComboServerLoginRequire.SelectedIndex == 3) &&
|
||||
!TextServerAuthServer.IsValidated)
|
||||
return;
|
||||
if (comboServerLoginLast == ComboServerLoginRequire.SelectedIndex)
|
||||
return;
|
||||
comboServerLoginLast = ComboServerLoginRequire.SelectedIndex;
|
||||
Config.InstanceAuth.LoginRequirementSolution[PageInstanceLeft.McInstance.PathInstance] = ComboServerLoginRequire.SelectedIndex;
|
||||
}
|
||||
|
||||
private void TextServerAuthServer_MouseLeave(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(TextServerAuthServer.Text))
|
||||
return;
|
||||
if (!(TextServerAuthServer.Text.EndsWithF("/api/yggdrasil/") ||
|
||||
TextServerAuthServer.Text.EndsWithF("/api/yggdrasil")))
|
||||
{
|
||||
if (TextServerAuthServer.Text.EndsWithF("/"))
|
||||
{
|
||||
TextServerAuthServer.Text = $"{TextServerAuthServer.Text}api/yggdrasil";
|
||||
HintService.Hint(Lang.Text("Instance.Setup.Server.AuthServer.AutoFormatted"));
|
||||
}
|
||||
else
|
||||
{
|
||||
TextServerAuthServer.Text = $"{TextServerAuthServer.Text}/api/yggdrasil";
|
||||
HintService.Hint(Lang.Text("Instance.Setup.Server.AuthServer.AutoFormatted"));
|
||||
}
|
||||
}
|
||||
|
||||
if (TextServerAuthServer.Text.EndsWithF("/api/yggdrasil/"))
|
||||
{
|
||||
TextServerAuthServer.Text = TextServerAuthServer.Text.BeforeLast("/");
|
||||
HintService.Hint(Lang.Text("Instance.Setup.Server.AuthServer.AutoFormatted"));
|
||||
}
|
||||
|
||||
comboServerLoginLast = ComboServerLoginRequire.SelectedIndex;
|
||||
ComboChange(ComboServerLoginRequire, null);
|
||||
}
|
||||
|
||||
public void ServerLogin(int type)
|
||||
{
|
||||
LabServerAuthName.Visibility = type == 2 || type == 3 ? Visibility.Visible : Visibility.Collapsed;
|
||||
TextServerAuthName.Visibility = type == 2 || type == 3 ? Visibility.Visible : Visibility.Collapsed;
|
||||
LabServerAuthRegister.Visibility = type == 2 || type == 3 ? Visibility.Visible : Visibility.Collapsed;
|
||||
TextServerAuthRegister.Visibility = type == 2 || type == 3 ? Visibility.Visible : Visibility.Collapsed;
|
||||
LabServerAuthServer.Visibility = type == 2 || type == 3 ? Visibility.Visible : Visibility.Collapsed;
|
||||
TextServerAuthServer.Visibility = type == 2 || type == 3 ? Visibility.Visible : Visibility.Collapsed;
|
||||
BtnServerAuthLittle.Visibility = type == 2 || type == 3 ? Visibility.Visible : Visibility.Collapsed;
|
||||
BtnServerNewProfile.Visibility = type == 2 || type == 3 ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (type == 0 || type == 1)
|
||||
BtnServerAuthLock.Visibility = Visibility.Collapsed;
|
||||
else
|
||||
BtnServerAuthLock.Visibility = Visibility.Visible;
|
||||
if (Config.InstanceAuth.AuthLocked[PageInstanceLeft.McInstance.PathInstance])
|
||||
{
|
||||
HintServerLoginLock.Visibility = Visibility.Visible;
|
||||
ComboServerLoginRequire.IsEnabled = false;
|
||||
TextServerAuthServer.IsEnabled = false;
|
||||
TextServerAuthName.IsEnabled = false;
|
||||
TextServerAuthRegister.IsEnabled = false;
|
||||
BtnServerAuthLittle.IsEnabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
HintServerLoginLock.Visibility = Visibility.Collapsed;
|
||||
ComboServerLoginRequire.IsEnabled = true;
|
||||
TextServerAuthServer.IsEnabled = true;
|
||||
TextServerAuthName.IsEnabled = true;
|
||||
TextServerAuthRegister.IsEnabled = true;
|
||||
BtnServerAuthLittle.IsEnabled = true;
|
||||
}
|
||||
|
||||
CardServer.TriggerForceResize();
|
||||
// 避免正版验证和离线验证出现此提示
|
||||
if (type != 2 && type != 3)
|
||||
{
|
||||
LabServerAuthServerSecurity.Visibility = Visibility.Collapsed;
|
||||
LabServerAuthServerSecurityCL.Visibility = Visibility.Collapsed;
|
||||
LabServerAuthServerSecurityVerify.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
// 如果开头为 http:// 给予警告
|
||||
else if (TextServerAuthServer.Text.StartsWithF("https://"))
|
||||
{
|
||||
LabServerAuthServerSecurity.Visibility = Visibility.Collapsed;
|
||||
LabServerAuthServerSecurityVerify.Visibility = Visibility.Visible;
|
||||
LabServerAuthServerSecurityCL.Visibility = Visibility.Visible;
|
||||
}
|
||||
else if (TextServerAuthServer.Text.StartsWithF("http://"))
|
||||
{
|
||||
LabServerAuthServerSecurity.Visibility = Visibility.Visible;
|
||||
LabServerAuthServerSecurityCL.Visibility = Visibility.Visible;
|
||||
LabServerAuthServerSecurityVerify.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
LabServerAuthServerSecurity.Visibility = Visibility.Collapsed;
|
||||
LabServerAuthServerSecurityVerify.Visibility = Visibility.Collapsed;
|
||||
LabServerAuthServerSecurityCL.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
// LittleSkin
|
||||
private void BtnServerAuthLittle_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(TextServerAuthServer.Text) &&
|
||||
TextServerAuthServer.Text != "https://littleskin.cn/api/yggdrasil" && ModMain.MyMsgBox(
|
||||
Lang.Text("Instance.Setup.Server.LittleSkin.Override.Message"),
|
||||
Lang.Text("Instance.Setup.Server.LittleSkin.Override.Title"), Lang.Text("Instance.Setup.Server.LittleSkin.Override.Continue"), Lang.Text("Common.Action.Cancel")) == 2)
|
||||
return;
|
||||
TextServerAuthServer.Text = "https://littleskin.cn/api/yggdrasil";
|
||||
TextServerAuthRegister.Text = "https://littleskin.cn/auth/register";
|
||||
TextServerAuthName.Text = Lang.Text("Instance.Setup.Server.LittleSkin.Name");
|
||||
}
|
||||
|
||||
// 锁定设置
|
||||
private void BtnServerAuthLock_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (ModMain.MyMsgBox(
|
||||
Lang.Text("Instance.Setup.Server.LockLoginMethod.Message"),
|
||||
Lang.Text("Instance.Setup.Server.LockLoginMethod.Title"), Lang.Text("Common.Action.Confirm"), Lang.Text("Common.Action.Cancel"), isWarn: true) == 1)
|
||||
{
|
||||
Config.InstanceAuth.AuthLocked[PageInstanceLeft.McInstance.PathInstance] = true;
|
||||
Reload();
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转新建档案
|
||||
private void BtnServerNewProfile_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
ModMain.frmMain.PageChange(new FormMain.PageStackData { page = FormMain.PageType.Launch });
|
||||
PageLoginAuth.draggedAuthServer = TextServerAuthServer.Text;
|
||||
ModBase.RunInNewThread(() =>
|
||||
{
|
||||
Thread.Sleep(150);
|
||||
ModBase.RunInUi(() => ModMain.frmLaunchLeft.RefreshPage(true, ModLaunch.McLoginType.Auth));
|
||||
});
|
||||
}
|
||||
|
||||
private static void TextServerEnter_Change(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (sender is MyTextBox textBox) textBox.Text = textBox.Text.Replace(":", ":");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Java 选择
|
||||
|
||||
// 刷新 Java 下拉框显示
|
||||
public void RefreshJavaComboBox()
|
||||
{
|
||||
if (ComboArgumentJava is null)
|
||||
return;
|
||||
|
||||
// 获取实例的 Java 偏好(已兼容新旧格式)
|
||||
var preference = ModJava.GetInstanceJavaPreference(PageInstanceLeft.McInstance);
|
||||
|
||||
// === 1. 初始化固定选项(使用类型安全的 Tag) ===
|
||||
ComboArgumentJava.Items.Clear();
|
||||
|
||||
// 选项 0: 跟随全局设置
|
||||
ComboArgumentJava.Items.Add(new MyComboBoxItem
|
||||
{
|
||||
Content = Lang.Text("Instance.Setup.FollowGlobal"),
|
||||
Tag = new UseGlobalPreference()
|
||||
});
|
||||
|
||||
// 选项 1: 自动选择
|
||||
ComboArgumentJava.Items.Add(new MyComboBoxItem
|
||||
{
|
||||
Content = Lang.Text("Instance.Setup.Options.Java.AutoSelect"),
|
||||
Tag = new AutoSelect() // Nothing 表示自动选择
|
||||
});
|
||||
|
||||
// 选项 2: 相对路径选项
|
||||
MyComboBoxItem relativePathItem;
|
||||
if (preference is UseRelativePath)
|
||||
{
|
||||
var relPref = (UseRelativePath)preference;
|
||||
var absPath = Path.GetFullPath(Path.Combine(Basics.ExecutableDirectory, relPref.RelativePath));
|
||||
var javaEntry = ModJava.Javas.Get(absPath);
|
||||
|
||||
if (Files.IsPathWithinDirectory(absPath, Basics.ExecutableDirectory) && javaEntry is not null &&
|
||||
javaEntry.IsEnabled)
|
||||
// 有效路径:显示具体 Java 信息
|
||||
relativePathItem = new MyComboBoxItem
|
||||
{
|
||||
Content = Lang.Text("Instance.Setup.Options.Java.SelectRelative.WithJava", javaEntry.ToString()),
|
||||
Tag = new UseRelativePath(relPref.RelativePath),
|
||||
ToolTip = Lang.Text("Instance.Setup.Options.Java.RelativePathToolTip", relPref.RelativePath, absPath)
|
||||
};
|
||||
else
|
||||
// 无效路径:提示用户重新选择
|
||||
relativePathItem = new MyComboBoxItem
|
||||
{
|
||||
Content = Lang.Text("Instance.Setup.Options.Java.SelectRelative.Invalid"),
|
||||
Tag = new UseRelativePath(relPref.RelativePath),
|
||||
ToolTip = Lang.Text("Instance.Setup.Options.Java.InvalidPathToolTip", absPath)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// 未配置相对路径:使用默认模板
|
||||
relativePathItem = new MyComboBoxItem
|
||||
{
|
||||
Content = Lang.Text("Instance.Setup.Options.Java.SelectRelative"),
|
||||
Tag = new UseRelativePath(@"jre\bin\java.exe"),
|
||||
ToolTip = Lang.Text("Instance.Setup.Options.Java.SelectRelativeToolTip")
|
||||
};
|
||||
}
|
||||
|
||||
ComboArgumentJava.Items.Add(relativePathItem);
|
||||
|
||||
// === 2. 添加所有可用 Java 运行时 ===
|
||||
MyComboBoxItem selectedItem = null;
|
||||
try
|
||||
{
|
||||
foreach (var curJava in ModJava.Javas.GetSortedJavaList())
|
||||
{
|
||||
var item = new MyComboBoxItem
|
||||
{
|
||||
Content = curJava.ToString(),
|
||||
ToolTip =
|
||||
Lang.Text("Instance.Setup.Options.Java.Details.ToolTip", curJava.Installation.JavaExePath, curJava.Installation.Version, curJava.Source),
|
||||
Tag = curJava
|
||||
};
|
||||
ToolTipService.SetInitialShowDelay(item, 300);
|
||||
ToolTipService.SetBetweenShowDelay(item, 100);
|
||||
ComboArgumentJava.Items.Add(item);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Config.Instance.SelectedJava[PageInstanceLeft.McInstance.PathInstance] = "使用全局设置";
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"更新实例设置 Java 下拉框失败",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Setup.Error.OperationFailed"));
|
||||
ComboArgumentJava.Items.Clear();
|
||||
ComboArgumentJava.Items.Add(new MyComboBoxItem
|
||||
{
|
||||
Content = Lang.Text("Instance.Setup.Options.Java.LoadFailed"),
|
||||
IsEnabled = false
|
||||
});
|
||||
ComboArgumentJava.SelectedIndex = 0;
|
||||
RefreshRam(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// === 3. 根据当前偏好设置选中项(优先使用新格式 preference) ===
|
||||
if (preference is null)
|
||||
{
|
||||
// 自动选择
|
||||
selectedItem = ComboArgumentJava.Items[1] as MyComboBoxItem;
|
||||
}
|
||||
else if (preference is UseGlobalPreference)
|
||||
{
|
||||
selectedItem = ComboArgumentJava.Items[0] as MyComboBoxItem;
|
||||
}
|
||||
else if (preference is UseRelativePath)
|
||||
{
|
||||
selectedItem = ComboArgumentJava.Items[2] as MyComboBoxItem;
|
||||
}
|
||||
else if (preference is ExistingJava)
|
||||
{
|
||||
var existPref = (ExistingJava)preference;
|
||||
// 在 Java 列表中查找匹配项(从索引 3 开始)
|
||||
for (int i = 3, loopTo = ComboArgumentJava.Items.Count - 1; i <= loopTo; i++)
|
||||
{
|
||||
var item = ComboArgumentJava.Items[i] as MyComboBoxItem;
|
||||
if (item is not null && item.Tag is JavaEntry)
|
||||
{
|
||||
var javaEntry = (JavaEntry)item.Tag;
|
||||
if (string.Equals(javaEntry.Installation.JavaExePath, existPref.JavaExePath,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
selectedItem = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 降级处理:无匹配项时回退到自动选择
|
||||
if (selectedItem is null && ComboArgumentJava.Items.Count > 1)
|
||||
selectedItem = ComboArgumentJava.Items[1] as MyComboBoxItem;
|
||||
|
||||
// 设置选中项
|
||||
if (selectedItem is not null) ComboArgumentJava.SelectedItem = selectedItem;
|
||||
|
||||
// === 4. 无可用 Java 时的降级处理 ===
|
||||
if (!ModJava.Javas.ExistAnyJava() && ComboArgumentJava.Items.Count <= 3)
|
||||
{
|
||||
ComboArgumentJava.Items.Clear();
|
||||
var noJavaItem = new MyComboBoxItem
|
||||
{
|
||||
Content = Lang.Text("Instance.Setup.Options.Java.NoRuntime"),
|
||||
ToolTip = Lang.Text("Instance.Setup.Options.Java.NoRuntime.ToolTip"),
|
||||
IsEnabled = false
|
||||
};
|
||||
ComboArgumentJava.Items.Add(noJavaItem);
|
||||
ComboArgumentJava.SelectedItem = noJavaItem;
|
||||
}
|
||||
|
||||
// === 5. 刷新关联控件 ===
|
||||
RefreshRam(true);
|
||||
}
|
||||
|
||||
// 阻止在无效状态下展开下拉框
|
||||
private void ComboArgumentJava_DropDownOpened(object? sender, EventArgs e)
|
||||
{
|
||||
if (ComboArgumentJava.SelectedItem is null)
|
||||
{
|
||||
ComboArgumentJava.IsDropDownOpen = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var firstItem = ComboArgumentJava.Items[0] as MyComboBoxItem;
|
||||
if (firstItem is not null &&
|
||||
((string)firstItem.Content == Lang.Text("Instance.Setup.Options.Java.NoRuntime") ||
|
||||
(string)firstItem.Content == Lang.Text("Instance.Setup.Options.Java.LoadFailed")))
|
||||
ComboArgumentJava.IsDropDownOpen = false;
|
||||
}
|
||||
|
||||
// 下拉框选择更改处理(保存新格式配置)
|
||||
private void JavaSelectionUpdate(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (ModAnimation.AniControlEnabled != 0)
|
||||
return;
|
||||
if (ComboArgumentJava.SelectedItem is null)
|
||||
return;
|
||||
|
||||
var selectedItem = ComboArgumentJava.SelectedItem as MyComboBoxItem;
|
||||
if (selectedItem is null || (selectedItem.Tag is null &&
|
||||
(string)selectedItem.Content != Lang.Text("Instance.Setup.Options.Java.AutoSelect")))
|
||||
return;
|
||||
|
||||
JavaPreference preference = default;
|
||||
var logMessage = "";
|
||||
|
||||
// 根据 Tag 类型生成偏好对象
|
||||
if (selectedItem.Tag is null or AutoSelect)
|
||||
{
|
||||
// 自动选择:存储空字符串
|
||||
preference = new AutoSelect();
|
||||
logMessage = "[Java] 修改实例 Java 选择设置:自动选择";
|
||||
}
|
||||
else if (selectedItem.Tag is UseGlobalPreference)
|
||||
{
|
||||
preference = new UseGlobalPreference();
|
||||
logMessage = "[Java] 修改实例 Java 选择设置:跟随全局设置";
|
||||
}
|
||||
else if (selectedItem.Tag is UseRelativePath)
|
||||
{
|
||||
// 相对路径:需要用户选择实际文件
|
||||
var ret = SystemDialogs.SelectFile(Lang.Text("Setup.Java.SelectFile.Filter"), Lang.Text("Setup.Java.SelectFile.Title"), Basics.ExecutableDirectory);
|
||||
if (string.IsNullOrWhiteSpace(ret))
|
||||
// 用户取消,不保存配置,保持原选择
|
||||
return;
|
||||
|
||||
ret = Path.GetFullPath(ret);
|
||||
var relativePath = Path.GetRelativePath(Basics.ExecutableDirectory, ret);
|
||||
|
||||
// 验证路径是否在启动器目录内
|
||||
if (!Files.IsPathWithinDirectory(relativePath, Basics.ExecutableDirectory))
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Setup.Options.Java.PathOutOfRange"), HintType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
preference = new UseRelativePath(relativePath);
|
||||
logMessage = $"[Java] 修改实例 Java 选择设置:相对路径 | {relativePath}";
|
||||
}
|
||||
else if (selectedItem.Tag is JavaEntry)
|
||||
{
|
||||
var javaEntry = (JavaEntry)selectedItem.Tag;
|
||||
preference = new ExistingJava(javaEntry.Installation.JavaExePath);
|
||||
logMessage = $"[Java] 修改实例 Java 选择设置:{javaEntry}";
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
var json = JsonSerializer.Serialize(preference, JsonCompat.SerializerOptions);
|
||||
Config.Instance.SelectedJava[PageInstanceLeft.McInstance.PathInstance] = json;
|
||||
|
||||
|
||||
ModBase.Log(logMessage);
|
||||
RefreshRam(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 其他设置
|
||||
|
||||
// 版本隔离警告
|
||||
private bool isReverting;
|
||||
|
||||
private void ComboArgumentIndieV2_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (ModAnimation.AniControlEnabled != 0)
|
||||
return;
|
||||
if (isReverting)
|
||||
return;
|
||||
if (ModMain.MyMsgBox(
|
||||
Lang.Text("Instance.Setup.Options.InstanceIsolation.Message"),
|
||||
Lang.Text("Common.Dialog.Warning"), Lang.Text("Setup.Launch.Advanced.Renderer.Warning.Confirm"), Lang.Text("Common.Action.Cancel"), isWarn: true) == 2)
|
||||
{
|
||||
isReverting = true;
|
||||
ComboArgumentIndieV2.SelectedItem = e.RemovedItems[0];
|
||||
isReverting = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool newValue = ComboArgumentIndieV2.SelectedIndex == 0;
|
||||
Config.Instance.IndieV2[PageInstanceLeft.McInstance.PathInstance] = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
// 游戏窗口
|
||||
private void CheckArgumentTitleEmpty_Change(object sender, bool e)
|
||||
{
|
||||
TextArgumentTitle.HintText = CheckArgumentTitleEmpty.Checked == true ? Lang.Text("Common.Option.Default") : Lang.Text("Instance.Setup.FollowGlobal");
|
||||
CheckBoxChange(sender,e);
|
||||
}
|
||||
|
||||
private void TextArgumentTitle_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
CheckArgumentTitleEmpty.Visibility = TextArgumentTitle.Text.Length > 0 ? Visibility.Collapsed : Visibility.Visible;
|
||||
TextBoxChange(sender,e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 高级设置
|
||||
|
||||
private void TextAdvanceRun_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
CheckAdvanceRunWait.Visibility = string.IsNullOrEmpty(TextAdvanceRun.Text) ? Visibility.Collapsed : Visibility.Visible;
|
||||
TextBoxChange(sender,e);
|
||||
}
|
||||
|
||||
private void ComboAdvanceRenderer_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (ModAnimation.AniControlEnabled != 0)
|
||||
return;
|
||||
|
||||
var args = e; // 转换事件参数
|
||||
|
||||
if (!States.Hint.Renderer && ComboAdvanceRenderer.SelectedIndex != 0)
|
||||
{
|
||||
if (ModMain.MyMsgBox(Lang.Text("Setup.Launch.Advanced.Renderer.Warning.Message"),
|
||||
Lang.Text("Common.Dialog.Warning"),
|
||||
Lang.Text("Setup.Launch.Advanced.Renderer.Warning.Confirm"), Lang.Text("Common.Action.Cancel"), isWarn: true) == 2)
|
||||
{
|
||||
ComboAdvanceRenderer.SelectedItem = args.RemovedItems[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
ComboChange(ComboAdvanceRenderer, e);
|
||||
States.Hint.Renderer = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ComboChange(ComboAdvanceRenderer, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckUseDebugLog4j2Config_CheckChanged(object sender, bool e)
|
||||
{
|
||||
if (ModAnimation.AniControlEnabled != 0)
|
||||
return;
|
||||
var checkBox = sender as MyCheckBox;
|
||||
if (checkBox is null) return;
|
||||
|
||||
if (checkBox.Checked.GetValueOrDefault() && !States.Hint.DebugLog4j2Config)
|
||||
{
|
||||
if (ModMain.MyMsgBox(
|
||||
Lang.Text("Instance.Setup.Advanced.UseDebugLog4j.Message"),
|
||||
Lang.Text("Common.Dialog.Warning"), Lang.Text("Setup.Launch.Advanced.Renderer.Warning.Confirm"), Lang.Text("Common.Action.Cancel"), isWarn: true) == 2)
|
||||
{
|
||||
checkBox.Checked = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
CheckBoxChange(sender, e);
|
||||
States.Hint.DebugLog4j2Config = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CheckBoxChange(sender, e);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<local:MyCard x:Class="PCL.ServerCard"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL;assembly="
|
||||
xmlns:controls="clr-namespace:PCL.Core.UI.Controls;assembly=PCL.Core"
|
||||
Margin="0,8">
|
||||
<Grid
|
||||
x:Name="PanBack" Height="44"
|
||||
RenderTransformOrigin="0.5,0.5" Background="{StaticResource ColorBrushSemiTransparent}"
|
||||
SnapsToDevicePixels="True">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="6" />
|
||||
<ColumnDefinition Width="34" />
|
||||
<ColumnDefinition Width="7" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="350" />
|
||||
<ColumnDefinition Width="60" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="1*" />
|
||||
<RowDefinition Height="17" />
|
||||
<RowDefinition Height="18" />
|
||||
<RowDefinition Height="1*" />
|
||||
</Grid.RowDefinitions>
|
||||
<!-- Icon -->
|
||||
<Border Grid.Column="1" Grid.Row="1" Grid.RowSpan="2" IsHitTestVisible="False" SnapsToDevicePixels="True"
|
||||
UseLayoutRounding="True"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center" Width="34" Height="34">
|
||||
<Border.Clip>
|
||||
<RectangleGeometry Rect="0,0,34,34" RadiusX="6" RadiusY="6" />
|
||||
</Border.Clip>
|
||||
<Image x:Name="ServerIcon" RenderOptions.BitmapScalingMode="HighQuality" />
|
||||
</Border>
|
||||
<!-- 标题 -->
|
||||
<TextBlock Grid.Column="3" Grid.Row="1" VerticalAlignment="Bottom" x:Name="ServerName"
|
||||
TextTrimming="CharacterEllipsis" FontSize="14"
|
||||
HorizontalAlignment="Left" ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Text}"
|
||||
ToolTipService.Placement="Top" MaxWidth="180" />
|
||||
<!-- ping 值与玩家人数 -->
|
||||
<StackPanel Grid.Row="2" Grid.Column="3" Orientation="Horizontal" VerticalAlignment="Bottom">
|
||||
<ContentPresenter x:Name="Signal" Content="{Binding SelectedIcon.XamlContent}"
|
||||
Width="20" Height="20" />
|
||||
<TextBlock x:Name="ServerPlayer" HorizontalAlignment="Left"
|
||||
TextTrimming="CharacterEllipsis" FontSize="12"
|
||||
Foreground="{DynamicResource ColorBrushGray3}" Opacity="0.4" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- MotD -->
|
||||
<controls:MotdRenderer x:Name="MotdRenderer" Grid.Row="1" Grid.RowSpan="2" Grid.Column="4" Margin="0,0,3,1"
|
||||
IsHitTestVisible="False" HorizontalAlignment="Center" Width="350" Height="34" />
|
||||
<TextBlock Grid.Row="1" x:Name="ServerMotD" Grid.RowSpan="2" Grid.Column="4" VerticalAlignment="Center"
|
||||
Margin="0,0,3,1"
|
||||
TextTrimming="CharacterEllipsis" FontSize="12" Foreground="{DynamicResource ColorBrushGray4}"
|
||||
IsHitTestVisible="False" HorizontalAlignment="Center" Visibility="Visible" />
|
||||
<!--<Canvas x:Name="MotdCanvas" Grid.Row="1" Grid.RowSpan="2" Grid.Column="4" Margin="0,0,3,1"
|
||||
IsHitTestVisible="False" HorizontalAlignment="Center" Width="300" Height="34"/>-->
|
||||
|
||||
<StackPanel Grid.Row="1" Grid.Column="5" Grid.RowSpan="2" Orientation="Horizontal" VerticalAlignment="Center"
|
||||
Margin="0,0,4,0">
|
||||
<local:MyIconButton
|
||||
Width="24"
|
||||
SvgIcon="lucide/play"
|
||||
Click="BtnConnect_Click" />
|
||||
<local:MyIconButton
|
||||
x:Name="BtnSetting"
|
||||
Click="BtnSkin_Click"
|
||||
Width="24"
|
||||
LogoScale="1.2"
|
||||
SvgIcon="lucide/settings">
|
||||
<local:MyIconButton.ContextMenu>
|
||||
<ContextMenu>
|
||||
<local:MyMenuItem Click="BtnRefresh_Click" Header="{DynamicResource Common.Action.Refresh}"
|
||||
Icon="M849.493333 661.76h-193.28a42.666667 42.666667 0 0 0 0 85.333333h102.4A341.333333 341.333333 0 0 1 170.666667 512a42.666667 42.666667 0 0 0-85.333334 0 426.666667 426.666667 0 0 0 720.213334 308.48V896a42.666667 42.666667 0 0 0 85.333333 0v-192a42.666667 42.666667 0 0 0-41.386667-42.24zM640 512a128 128 0 1 0-128 128 128 128 0 0 0 128-128z m-170.666667 0a42.666667 42.666667 0 1 1 42.666667 42.666667 42.666667 42.666667 0 0 1-42.666667-42.666667z m42.666667-426.666667a426.666667 426.666667 0 0 0-293.546667 118.186667V128a42.666667 42.666667 0 0 0-85.333333 0v192a42.666667 42.666667 0 0 0 42.666667 42.666667h192a42.666667 42.666667 0 0 0 0-85.333334h-102.4A341.333333 341.333333 0 0 1 853.333333 512a42.666667 42.666667 0 0 0 85.333334 0A426.666667 426.666667 0 0 0 512 85.333333z" />
|
||||
<local:MyMenuItem Click="BtnCopy_Click"
|
||||
Header="{DynamicResource Instance.Server.Card.CopyAddress}"
|
||||
Icon="M931.882 131.882l-103.764-103.764A96 96 0 0 0 760.236 0H416c-53.02 0-96 42.98-96 96v96H160c-53.02 0-96 42.98-96 96v640c0 53.02 42.98 96 96 96h448c53.02 0 96-42.98 96-96v-96h160c53.02 0 96-42.98 96-96V199.764a96 96 0 0 0-28.118-67.882zM596 928H172a12 12 0 0 1-12-12V300a12 12 0 0 1 12-12h148v448c0 53.02 42.98 96 96 96h192v84a12 12 0 0 1-12 12z m256-192H428a12 12 0 0 1-12-12V108a12 12 0 0 1 12-12h212v176c0 26.51 21.49 48 48 48h176v404a12 12 0 0 1-12 12z m12-512h-128V96h19.264c3.182 0 6.234 1.264 8.486 3.514l96.736 96.736a12 12 0 0 1 3.514 8.486V224z" />
|
||||
<local:MyMenuItem Click="BtnEdit_Click" Header="{DynamicResource Instance.Server.Card.Edit}"
|
||||
Icon="M934.4 981.333333H89.6A46.08 46.08 0 0 1 42.666667 934.485333V156.672a46.08 46.08 0 0 1 46.933333-46.848h367.658667c26.581333 0 46.933333 20.309333 46.933333 46.848a46.08 46.08 0 0 1-46.933333 46.848H136.533333v684.117333h750.933334v-371.712a46.08 46.08 0 0 1 46.933333-46.890666c26.581333 0 46.933333 20.309333 46.933333 46.848v418.56c0 26.581333-21.888 46.890667-46.933333 46.890666z M330.538667 739.242667c-12.544 0-25.045333-4.693333-32.853334-14.08-9.386667-9.344-14.08-20.266667-14.08-32.768v-187.434667c0-12.501333 4.693333-24.96 14.08-32.768l416.128-415.488c7.808-9.386667 20.352-14.037333 32.853334-14.037333 12.501333 0 25.045333 4.693333 32.853333 14.08l187.733333 187.392c18.773333 18.773333 18.773333 48.426667 0 65.621333l-416.128 415.445333c-9.386667 9.386667-20.352 14.08-32.853333 14.08h-187.733333z m46.933333-215.552v120.32h120.448l369.194667-367.061334L746.666667 155.136l-369.194667 368.597333z" />
|
||||
<Separator />
|
||||
<local:MyMenuItem Click="BtnRemove_Click"
|
||||
Header="{DynamicResource Instance.Server.Card.Remove}"
|
||||
Icon="M683.514192 1023.999519H340.485808A154.527713 154.527713 0 0 1 186.336081 884.785983L129.317406 325.755552a39.082648 39.082648 0 0 1 77.762111-7.937715l57.053038 559.030431a76.559427 76.559427 0 0 0 76.376161 68.976791h343.005476a76.559427 76.559427 0 0 0 76.376161-68.976791l57.053038-559.030431a39.082648 39.082648 0 1 1 77.762112 7.937715l-57.053039 559.030431A154.516259 154.516259 0 0 1 683.514192 1023.999519zM888.85244 191.513109h-753.681972a39.092957 39.092957 0 0 1 0-78.17446h753.681972a39.092957 39.092957 0 0 1 0 78.17446z M409.199153 704.887365a39.092957 39.092957 0 0 1-39.081502-39.081503V451.991555a39.092957 39.092957 0 0 1 78.174459 0v213.814307a39.092957 39.092957 0 0 1-39.092957 39.081503zM614.789392 704.887365a39.092957 39.092957 0 0 1-39.081502-39.081503V451.991555a39.092957 39.092957 0 0 1 78.174459 0v213.814307a39.092957 39.092957 0 0 1-39.092957 39.081503zM667.844938 171.960904a39.081503 39.081503 0 0 1-39.127319-39.081503 54.773666 54.773666 0 0 0-54.716395-54.716395h-124.048264a54.773666 54.773666 0 0 0-54.716395 54.716395 39.081503 39.081503 0 1 1-78.163006 0 133.028304 133.028304 0 0 1 132.867947-132.867947H574.04704a133.028304 133.028304 0 0 1 132.867947 132.867947 39.070049 39.070049 0 0 1-39.070049 39.081503z" />
|
||||
</ContextMenu>
|
||||
</local:MyIconButton.ContextMenu>
|
||||
</local:MyIconButton>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
@@ -0,0 +1,238 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using PCL.Core.UI;
|
||||
using PCL.Core.UI.Theme;
|
||||
using PCL.Core.App.Localization;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
public partial class ServerCard
|
||||
{
|
||||
private readonly IconManager _manager;
|
||||
public MinecraftServerInfo server;
|
||||
|
||||
public ServerCard()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
DataContext = new IconManager();
|
||||
|
||||
// 示例:可在代码中切换图标
|
||||
_manager = DataContext as IconManager;
|
||||
_manager.AddIconFromXaml("signal_1",
|
||||
"<Viewbox xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" Width=\"20\" Height=\"20\"><Canvas UseLayoutRounding=\"False\" Width=\"1024.0\" Height=\"1024.0\"><Canvas.Clip><RectangleGeometry Rect=\"0.0,0.0,1024.0,1024.0\"/></Canvas.Clip><Canvas UseLayoutRounding=\"False\"><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"234.666667\" Canvas.Top=\"610.56\" Width=\"80.853333\" Height=\"127.04\" Fill=\"#ff00ff21\"/></Canvas><Canvas UseLayoutRounding=\"False\"><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"353.066667\" Canvas.Top=\"541.226667\" Width=\"80.853333\" Height=\"196.373333\" Fill=\"#ff888888\"/><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"471.445333\" Canvas.Top=\"460.373333\" Width=\"80.896\" Height=\"277.226667\" Fill=\"#ff888888\"/><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"589.866667\" Canvas.Top=\"379.52\" Width=\"80.853333\" Height=\"358.08\" Fill=\"#ff888888\"/><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"708.266667\" Canvas.Top=\"298.666667\" Width=\"80.853333\" Height=\"438.933333\" Fill=\"#ff888888\"/></Canvas></Canvas></Viewbox>");
|
||||
_manager.AddIconFromXaml("signal_2",
|
||||
"<Viewbox xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" Width=\"20\" Height=\"20\"><Canvas UseLayoutRounding=\"False\" Width=\"1024.0\" Height=\"1024.0\"><Canvas.Clip><RectangleGeometry Rect=\"0.0,0.0,1024.0,1024.0\"/></Canvas.Clip><Canvas UseLayoutRounding=\"False\"><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"234.666667\" Canvas.Top=\"610.56\" Width=\"80.853333\" Height=\"127.04\" Fill=\"#ff00ff21\"/><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"353.066667\" Canvas.Top=\"541.226667\" Width=\"80.853333\" Height=\"196.373333\" Fill=\"#ff00ff21\"/></Canvas><Canvas UseLayoutRounding=\"False\"><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"471.445333\" Canvas.Top=\"460.373333\" Width=\"80.896\" Height=\"277.226667\" Fill=\"#ff888888\"/><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"589.866667\" Canvas.Top=\"379.52\" Width=\"80.853333\" Height=\"358.08\" Fill=\"#ff888888\"/><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"708.266667\" Canvas.Top=\"298.666667\" Width=\"80.853333\" Height=\"438.933333\" Fill=\"#ff888888\"/></Canvas></Canvas></Viewbox>");
|
||||
_manager.AddIconFromXaml("signal_3",
|
||||
"<Viewbox Width=\"20\" Height=\"20\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"><Canvas UseLayoutRounding=\"False\" Width=\"1024.0\" Height=\"1024.0\"><Canvas.Clip><RectangleGeometry Rect=\"0.0,0.0,1024.0,1024.0\"/></Canvas.Clip><Canvas UseLayoutRounding=\"False\"><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"234.666667\" Canvas.Top=\"610.56\" Width=\"80.853333\" Height=\"127.04\" Fill=\"#ff00ff21\"/><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"353.066667\" Canvas.Top=\"541.226667\" Width=\"80.853333\" Height=\"196.373333\" Fill=\"#ff00ff21\"/><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"471.445333\" Canvas.Top=\"460.373333\" Width=\"80.896\" Height=\"277.226667\" Fill=\"#ff00ff21\"/></Canvas><Canvas UseLayoutRounding=\"False\"><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"589.866667\" Canvas.Top=\"379.52\" Width=\"80.853333\" Height=\"358.08\" Fill=\"#ff888888\"/><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"708.266667\" Canvas.Top=\"298.666667\" Width=\"80.853333\" Height=\"438.933333\" Fill=\"#ff888888\"/></Canvas></Canvas></Viewbox>");
|
||||
_manager.AddIconFromXaml("signal_4",
|
||||
"<Viewbox xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" Width=\"20\" Height=\"20\"><Canvas UseLayoutRounding=\"False\" Width=\"1024.0\" Height=\"1024.0\"><Canvas.Clip><RectangleGeometry Rect=\"0.0,0.0,1024.0,1024.0\"/></Canvas.Clip><Canvas UseLayoutRounding=\"False\"><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"234.666667\" Canvas.Top=\"610.56\" Width=\"80.853333\" Height=\"127.04\" Fill=\"#ff00ff21\"/><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"353.066667\" Canvas.Top=\"541.226667\" Width=\"80.853333\" Height=\"196.373333\" Fill=\"#ff00ff21\"/><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"471.445333\" Canvas.Top=\"460.373333\" Width=\"80.896\" Height=\"277.226667\" Fill=\"#ff00ff21\"/><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"589.866667\" Canvas.Top=\"379.52\" Width=\"80.853333\" Height=\"358.08\" Fill=\"#ff00ff21\"/></Canvas><Canvas UseLayoutRounding=\"False\"><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"708.266667\" Canvas.Top=\"298.666667\" Width=\"80.853333\" Height=\"438.933333\" Fill=\"#ff888888\"/></Canvas></Canvas></Viewbox>");
|
||||
_manager.AddIconFromXaml("signal_5",
|
||||
"<Viewbox Width=\"20\" Height=\"20\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"><Canvas UseLayoutRounding=\"False\" Width=\"1024.0\" Height=\"1024.0\"><Canvas.Clip><RectangleGeometry Rect=\"0.0,0.0,1024.0,1024.0\"/></Canvas.Clip><Canvas UseLayoutRounding=\"False\"><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"234.666667\" Canvas.Top=\"610.56\" Width=\"80.853333\" Height=\"127.04\" Fill=\"#ff00ff21\"/><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"353.066667\" Canvas.Top=\"541.226667\" Width=\"80.853333\" Height=\"196.373333\" Fill=\"#ff00ff21\"/><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"471.445333\" Canvas.Top=\"460.373333\" Width=\"80.896\" Height=\"277.226667\" Fill=\"#ff00ff21\"/><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"589.866667\" Canvas.Top=\"379.52\" Width=\"80.853333\" Height=\"358.08\" Fill=\"#ff00ff21\"/><Rectangle RadiusX=\"0.0\" RadiusY=\"0.0\" Canvas.Left=\"708.266667\" Canvas.Top=\"298.666667\" Width=\"80.853333\" Height=\"438.933333\" Fill=\"#ff00ff21\"/></Canvas></Canvas></Viewbox>");
|
||||
_manager.AddIconFromXaml("signal_offline",
|
||||
"<Viewbox xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" Width=\"14\" Height=\"14\" Margin=\"3\"><Canvas UseLayoutRounding=\"False\" Width=\"1280.0\" Height=\"1024.0\"><Canvas.Clip><RectangleGeometry Rect=\"0.0,0.0,1280.0,1024.0\"/></Canvas.Clip><Path Fill=\"#ff000000\"><Path.Data><PathGeometry Figures=\"M 317.63 349.235 l -67.951 -67.951 l -67.95 67.95 c -18.964 18.964 -48.988 18.964 -67.951 0 c -18.963 -18.962 -18.963 -48.987 0 -67.95 l 67.95 -67.95 l -66.37 -67.951 c -18.963 -18.963 -18.963 -48.988 0 -67.95 c 18.963 -18.964 48.988 -18.964 67.95 0 l 67.951 67.95 l 67.95 -67.95 c 18.964 -18.964 48.989 -18.964 67.951 0 c 18.963 18.962 18.963 48.987 0 67.95 l -67.95 67.95 l 67.95 67.951 c 18.963 18.963 18.963 48.988 0 67.95 c -9.481 9.482 -20.543 14.223 -33.185 14.223 c -14.222 0 -26.864 -6.321 -36.345 -14.222 z M 216.494 752.198 h -48.988 c -26.864 0 -48.987 26.864 -48.987 60.049 v 120.099 c 0 33.185 22.123 60.05 48.987 60.05 h 48.988 c 26.864 0 48.987 -26.865 48.987 -60.05 v -120.1 c 0 -33.184 -22.123 -60.048 -48.987 -60.048 z M 516.74 512 h -48.988 c -26.864 0 -48.988 26.864 -48.988 60.05 v 360.296 c 0 33.185 22.124 60.05 48.988 60.05 h 48.988 c 26.864 0 48.987 -26.865 48.987 -60.05 V 572.049 c 0 -33.185 -22.123 -60.049 -48.987 -60.049 z m 300.247 -240.198 H 768 c -26.864 0 -48.988 26.865 -48.988 60.05 v 600.494 c 0 33.185 22.124 60.05 48.988 60.05 h 48.988 c 26.864 0 48.987 -26.865 48.987 -60.05 V 331.852 c 0 -33.185 -22.123 -60.05 -48.987 -60.05 z m 300.247 -240.197 h -48.988 c -26.864 0 -48.988 26.864 -48.988 60.05 v 840.69 c 0 33.186 22.124 60.05 48.988 60.05 h 48.988 c 26.864 0 48.987 -26.864 48.987 -60.05 V 91.656 c -1.58 -33.186 -22.123 -60.05 -48.987 -60.05 z\" FillRule=\"Nonzero\"/></Path.Data></Path></Canvas></Viewbox>");
|
||||
_manager.AddIconFromXaml("loading",
|
||||
"<Viewbox xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" Width=\"20\" Height=\"20\"><Canvas UseLayoutRounding=\"False\" Width=\"1024.0\" Height=\"1024.0\"><Canvas.Clip><RectangleGeometry Rect=\"0.0,0.0,1024.0,1024.0\"/></Canvas.Clip><Path Fill=\"#ff000000\"><Path.Data><PathGeometry Figures=\"M 256 490.667 a 64 64 0 1 1 -128 0 a 64 64 0 0 1 128 0 z m -42.6667 0 a 21.3333 21.3333 0 1 0 -42.6667 0 a 21.3333 21.3333 0 0 0 42.6667 0 z m 384 0 a 106.667 106.667 0 1 1 -213.376 -0.042667 A 106.667 106.667 0 0 1 597.333 490.667 z m -42.6667 0 a 64 64 0 1 0 -128.043 0.042666 A 64 64 0 0 0 554.667 490.667 z m 298.667 0 a 64 64 0 1 1 -128 0 a 64 64 0 0 1 128 0 z m -42.6667 0 a 21.3333 21.3333 0 1 0 -42.6667 0 a 21.3333 21.3333 0 0 0 42.6667 0 z\" FillRule=\"Nonzero\"/></Path.Data></Path></Canvas></Viewbox>");
|
||||
}
|
||||
|
||||
public event EventHandler? RemoveServer;
|
||||
public event EventHandler? EditServer;
|
||||
|
||||
private void BtnSkin_Click(object sender, EventArgs eventArgs)
|
||||
{
|
||||
BtnSetting.ContextMenu.IsOpen = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化服务器卡片
|
||||
/// </summary>
|
||||
public void UpdateServerInfo(MinecraftServerInfo serverInfo)
|
||||
{
|
||||
server = serverInfo;
|
||||
ModBase.RunInUi(() => UpdateServerUi());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新服务器UI
|
||||
/// </summary>
|
||||
private async void UpdateServerUi()
|
||||
{
|
||||
if (server is null)
|
||||
return;
|
||||
|
||||
// 更新服务器名称
|
||||
ServerName.Text = server.Name;
|
||||
await ImageLoaderHelper.SetServerLogoAsync(server.Icon, ServerIcon);
|
||||
if (server.Status == ServerStatus.Online)
|
||||
{
|
||||
_manager.SetSelectedIconByName(GetSignalIcon(server.Ping));
|
||||
Signal.ToolTip = $"{server.Ping}ms";
|
||||
ToolTipService.SetInitialShowDelay(Signal, 0);
|
||||
ToolTipService.SetBetweenShowDelay(Signal, 50);
|
||||
ToolTipService.SetPlacement(Signal, PlacementMode.Top);
|
||||
|
||||
if (server.PlayerCount != default && server.MaxPlayers != default)
|
||||
ServerPlayer.Text = $"{server.PlayerCount} / {server.MaxPlayers}";
|
||||
else
|
||||
ServerPlayer.Text = "???";
|
||||
|
||||
ServerMotD.Visibility = Visibility.Collapsed;
|
||||
MotdRenderer.RenderMotd(server.Description, ThemeService.IsDarkMode, 2);
|
||||
MotdRenderer.RenderCanvas();
|
||||
}
|
||||
else if (server.Status == ServerStatus.Pinging)
|
||||
{
|
||||
_manager.SetSelectedIconByName("loading");
|
||||
MotdRenderer.ClearCanvas();
|
||||
ServerPlayer.Text = Lang.Text("Instance.Server.Card.Connecting");
|
||||
ServerMotD.Text = Lang.Text("Instance.Server.Card.ConnectingDots");
|
||||
ServerMotD.Visibility = Visibility.Visible;
|
||||
}
|
||||
else if (server.Status == ServerStatus.Offline)
|
||||
{
|
||||
_manager.SetSelectedIconByName("signal_offline");
|
||||
MotdRenderer.ClearCanvas();
|
||||
ServerPlayer.Text = Lang.Text("Instance.Server.Card.Offline");
|
||||
ServerMotD.Text = Lang.Text("Instance.Server.Card.ServerOffline");
|
||||
ServerMotD.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetSignalIcon(int ping)
|
||||
{
|
||||
switch (ping)
|
||||
{
|
||||
case var @case when 0 <= @case && @case <= 99:
|
||||
{
|
||||
return "signal_5"; // 5 条信号
|
||||
}
|
||||
case var case1 when 100 <= case1 && case1 <= 299:
|
||||
{
|
||||
return "signal_4"; // 4 条信号
|
||||
}
|
||||
case var case2 when 300 <= case2 && case2 <= 599:
|
||||
{
|
||||
return "signal_3"; // 3 条信号
|
||||
}
|
||||
case var case3 when 600 <= case3 && case3 <= 999:
|
||||
{
|
||||
return "signal_2"; // 2 条信号
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return "signal_1"; // 1 条信号
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新服务器状态
|
||||
/// </summary>
|
||||
public async Task RefreshServerStatusAsync(bool withHint, CancellationToken token = default)
|
||||
{
|
||||
if (withHint) HintService.Hint(Lang.Text("Instance.Server.Card.RefreshingStatus", server.Name));
|
||||
server.Status = ServerStatus.Pinging;
|
||||
await Dispatcher.InvokeAsync(() => UpdateServerUi());
|
||||
var serverInfo = await PageInstanceServer.PingServerAsync(server, token);
|
||||
UpdateServerInfo(serverInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接到服务器
|
||||
/// </summary>
|
||||
private void BtnConnect_Click(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var launchOptions = new ModLaunch.McLaunchOptions
|
||||
{
|
||||
ServerIp = server.Address,
|
||||
instance = PageInstanceLeft.McInstance
|
||||
};
|
||||
ModLaunch.McLaunchStart(launchOptions);
|
||||
ModMain.frmMain.PageChange(new FormMain.PageStackData { page = FormMain.PageType.Launch });
|
||||
HintService.Hint(Lang.Text("Instance.Server.Card.ConnectingTo", server.Name));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
Lang.Text("Instance.Server.Card.LaunchFailed"),
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Instance.Server.Card.LaunchFailed"));
|
||||
HintService.Hint(Lang.Text("Instance.Server.Card.LaunchFailedMsg", ex.Message), HintType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复制服务器地址
|
||||
/// </summary>
|
||||
private void BtnCopy_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(server.Address);
|
||||
HintService.Hint(Lang.Text("Instance.Server.Card.AddressCopied", server.Address), HintType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, Lang.Text("Instance.Server.Card.CopyAddressFailed"));
|
||||
HintService.Hint(Lang.Text("Instance.Server.Card.CopyAddressFailed"), HintType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新服务器状态
|
||||
/// </summary>
|
||||
private async void BtnRefresh_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await Task.Run(async () => await RefreshServerStatusAsync(true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 编辑服务器信息
|
||||
/// </summary>
|
||||
private void BtnEdit_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get server information
|
||||
var result = PageInstanceServer.GetServerInfo(server);
|
||||
if (!result.Success) return;
|
||||
|
||||
EditServer?.Invoke(this, new ResultEventArgs(result.Name, result.Address));
|
||||
}
|
||||
|
||||
// Update server object
|
||||
// _server.Name = result.Name
|
||||
// _server.Address = result.Address
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Instance.Server.Card.EditFailed", ex.Message), HintType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnRemove_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ModMain.MyMsgBox(
|
||||
Lang.Text("Instance.Server.Card.RemoveConfirmMessage", server.Name, server.Address),
|
||||
Lang.Text("Instance.Server.Card.RemoveConfirmTitle"), Lang.Text("Common.Action.Confirm"),
|
||||
Lang.Text("Common.Action.Cancel")
|
||||
) == 1) RemoveServer?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public class ResultEventArgs : EventArgs
|
||||
{
|
||||
public ResultEventArgs(string param1, string param2)
|
||||
{
|
||||
Param1 = param1;
|
||||
Param2 = param2;
|
||||
}
|
||||
|
||||
public string Param1 { get; set; }
|
||||
public string Param2 { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user