Files
MRCC/launcher/PCL-CE/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceCompResource.xaml.cs
T
xyou f70b061d1a
CI / Go Backend (push) Canceled after 0s
feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块

Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
2026-08-08 14:07:56 +08:00

2962 lines
120 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}