Files
MRCC/launcher/PCL-CE/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceCompResource.xaml.cs
T

2962 lines
120 KiB
C#
Raw Normal View History

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
}