using System.IO; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Threading; using Microsoft.VisualBasic.FileIO; using PCL.Core.App; using PCL.Core.UI; using PCL.Core.UI.Theme; using PCL.Network; using PCL.Network.Loaders; using FileSystem = Microsoft.VisualBasic.FileSystem; using PCL.Core.App.Localization; namespace PCL; public partial class PageInstanceSavesDatapack : IRefreshable { #region 数据包信息缓存 private readonly Dictionary datapackFileInfoCache = new(); // 获取数据包信息(带缓存) private (DateTime CreationTime, long Length) GetDatapackFileInfo(string path) { (DateTime CreationTime, long Length) cacheItem; if (datapackFileInfoCache.TryGetValue(path, out cacheItem)) return cacheItem; try { var fileInfo = new FileInfo(path); var newItem = (fileInfo.CreationTime, fileInfo.Length); if (!datapackFileInfoCache.ContainsKey(path)) datapackFileInfoCache.Add(path, newItem); return newItem; } catch (Exception ex) { ModBase.Log(ex, "获取数据包信息失败: " + path); return (DateTime.MinValue, 0L); } } // 页面关闭时清理缓存 private void Page_Unloaded(object sender, RoutedEventArgs e) { datapackFileInfoCache.Clear(); } #endregion #region 初始化 private readonly MyLocalCompItem.SwipeSelect currentSwipSelect; public PageInstanceSavesDatapack() { currentSwipSelect = new MyLocalCompItem.SwipeSelect { TargetFrm = this }; InitializeComponent(); Unloaded += Page_Unloaded; Loaded += (_, _) => PageOther_Loaded(); LoaderInit(); PageExit += UnselectedAllWithAnimation; // Handles Load.Click += Load_Click; BtnManageOpen.Click += BtnManageOpen_Click; BtnHintOpen.Click += BtnManageOpen_Click; BtnManageSelectAll.Click += BtnManageSelectAll_Click; BtnManageInstall.Click += BtnManageInstall_Click; BtnHintInstall.Click += BtnManageInstall_Click; BtnManageDownload.Click += BtnManageDownload_Click; BtnHintDownload.Click += BtnManageDownload_Click; BtnManageInfoExport.Click += BtnManageInfoExport_Click; Load.StateChanged += (_, _, _) => UnselectedAllWithAnimation(); SearchBox.PreviewKeyDown += SearchBox_PreviewKeyDown; BtnFilterAll.Check += ChangeFilter; BtnFilterCanUpdate.Check += ChangeFilter; BtnFilterDisabled.Check += ChangeFilter; BtnFilterEnabled.Check += ChangeFilter; BtnFilterError.Check += ChangeFilter; BtnSort.Click += BtnSortClick; BtnSelectEnable.Click += BtnSelectEnable_Click; BtnSelectDisable.Click += BtnSelectDisable_Click; BtnSelectUpdate.Click += BtnSelectUpdate_Click; BtnSelectDelete.Click += BtnSelectDelete_Click; BtnSelectCancel.Click += BtnSelectCancel_Click; BtnSelectFavorites.Click += BtnSelectFavorites_Click; BtnSelectShare.Click += BtnSelectShare_Click; SearchBox.TextChanged += SearchRun; } private ModLocalComp.CompLocalLoaderData GetRequireLoaderData() { var res = new ModLocalComp.CompLocalLoaderData(); res.gameVersion = PageInstanceLeft.McInstance; res.frm = null; res.loaders = new[] { ModComp.CompLoaderType.Minecraft }.ToList(); res.compPath = Path.Combine(PageInstanceSavesLeft.currentSave, "datapacks"); res.compType = ModComp.CompType.DataPack; return res; } private bool isLoad; public void PageOther_Loaded() { if (ModMain.frmMain.pageLast.page != FormMain.PageType.CompDetail) PanBack.ScrollToHome(); ModAnimation.AniControlEnabled += 1; selectedDatapacks.Clear(); ReloadDatapackFileList(); ChangeAllSelected(false); ModAnimation.AniControlEnabled -= 1; // 非重复加载部分 if (isLoad) return; isLoad = true; ModMain.frmMain.KeyDown += FrmMain_KeyDown; // 调整按钮边距(这玩意儿没法从 XAML 改) foreach (MyRadioButton Btn in PanFilter.Children) Btn.LabText.Margin = new Thickness(-2, 0d, 8d, 0d); } /// /// 刷新数据包列表。 /// public void ReloadDatapackFileList(bool forceReload = false) { if (LoaderRun(forceReload ? ModLoader.LoaderFolderRunType.ForceRun : ModLoader.LoaderFolderRunType.RunOnUpdated)) { ModBase.Log("[System] 已刷新数据包列表"); datapackFileInfoCache.Clear(); ModBase.RunInUi(() => { Filter = FilterType.All; PanBack.ScrollToHome(); SearchBox.Text = ""; }); } } // 强制刷新 private void RefreshSelf() { Refresh(); } void IRefreshable.Refresh() { RefreshSelf(); } public void Refresh() { ModMain.frmInstanceSavesDatapack.ReloadDatapackFileList(true); ModBase.Log("[Datapack] 刷新数据包列表"); } private void LoaderInit() { PageLoaderInit(Load, PanLoad, PanAllBack, null, ModLocalComp.compResourceListLoader, _ => LoadUIFromLoaderOutput(), () => ModComp.CompType.DataPack, false); } private void Load_Click(object sender, MouseButtonEventArgs e) { if (ModLocalComp.compResourceListLoader.State == ModBase.LoadState.Failed) LoaderRun(ModLoader.LoaderFolderRunType.ForceRun); } public bool LoaderRun(ModLoader.LoaderFolderRunType type) { var loadPath = Path.Combine(PageInstanceSavesLeft.currentSave, "datapacks"); return ModLoader.LoaderFolderRun(ModLocalComp.compResourceListLoader, loadPath, type, loaderInput: GetRequireLoaderData()); } #endregion #region UI 化 /// /// 已加载的数据包 UI 缓存。Key 为数据包的 RawPath。 /// public Dictionary datapackItems = new(); /// /// 将加载器结果的数据包列表加载为 UI。 /// private void LoadUIFromLoaderOutput() { try { // 判断应该显示哪一个页面 if (ModLocalComp.compResourceListLoader.output.Any()) { PanBack.Visibility = Visibility.Visible; PanEmpty.Visibility = Visibility.Collapsed; } else { // 根据组件类型设置 PanEmpty 的文本内容 TxtEmptyTitle.Text = Lang.Text("Instance.Resource.Datapack.Empty.Title"); TxtEmptyDescription.Text = Lang.Text("Instance.Resource.Datapack.Empty.Description"); PanEmpty.Visibility = Visibility.Visible; PanBack.Visibility = Visibility.Collapsed; return; } // 修改缓存 datapackItems.Clear(); var itemsToShow = ModLocalComp.compResourceListLoader.output.ToList(); foreach (var DatapackEntity in itemsToShow) datapackItems[DatapackEntity.RawPath] = BuildLocalCompItem(DatapackEntity); // 显示结果 ModBase.RunInUi(() => { Filter = FilterType.All; SearchBox.Text = ""; // 这会触发结果刷新,所以需要在 DatapackItems 更新之后 RefreshUI(); SetSortMethod(SortMethod.CompName); }); } catch (Exception ex) { ModBase.Log( ex, "加载数据包列表 UI 失败", ModBase.LogLevel.Feedback, userSummary: Lang.Text("Instance.Saves.Error.OperationFailed")); } } private MyLocalCompItem BuildLocalCompItem(ModLocalComp.LocalCompFile entry) { try { ModAnimation.AniControlEnabled += 1; var newItem = new MyLocalCompItem { SnapsToDevicePixels = true, Entry = entry, buttonHandler = BuildLocalCompItemBtnHandler, Checked = selectedDatapacks.Contains(entry.RawPath) }; newItem.CurrentSwipe = currentSwipSelect; newItem.Tags = entry.Tags; entry.OnCompUpdate += _ => newItem.Refresh(); newItem.Refresh(); ModAnimation.AniControlEnabled -= 1; return newItem; } catch (Exception ex) { ModAnimation.AniControlEnabled -= 1; ModBase.Log(ex, $"创建 UI 项失败:{entry.RawPath}"); throw; } } private void BuildLocalCompItemBtnHandler(MyLocalCompItem sender, EventArgs e) { // 点击事件 sender.Changed += (ss, e) => CheckChanged((MyLocalCompItem)ss, e); // 文件项的点击事件:切换选中状态 sender.Click += (ss, e) => { var s = (MyLocalCompItem)ss; s.Checked = !s.Checked; }; // 图标按钮 var btnOpen = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/folder-open", Tag = sender }; btnOpen.ToolTip = Lang.Text("Instance.Saves.OpenFileLocation"); ToolTipService.SetPlacement(btnOpen, PlacementMode.Center); ToolTipService.SetVerticalOffset(btnOpen, 30d); ToolTipService.SetHorizontalOffset(btnOpen, 2d); btnOpen.Click += (sender, e) => Open_Click((MyIconButton)sender, e); var btnCont = new MyIconButton { LogoScale = 1d, SvgIcon = "lucide/info", Tag = sender }; btnCont.ToolTip = Lang.Text("Instance.Saves.Detail"); ToolTipService.SetPlacement(btnCont, PlacementMode.Center); ToolTipService.SetVerticalOffset(btnCont, 30d); ToolTipService.SetHorizontalOffset(btnCont, 2d); btnCont.Click += Info_Click; sender.MouseRightButtonUp += Info_Click; var btnDelete = new MyIconButton { LogoScale = 1d, SvgIcon = "lucide/trash-2", Tag = sender }; btnDelete.ToolTip = Lang.Text("Common.Action.Delete"); ToolTipService.SetPlacement(btnDelete, PlacementMode.Center); ToolTipService.SetVerticalOffset(btnDelete, 30d); ToolTipService.SetHorizontalOffset(btnDelete, 2d); btnDelete.Click += (sender, e) => Delete_Click((MyIconButton)sender, e); if (sender.Entry.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine) { var btnDisable = new MyIconButton { LogoScale = 1d, SvgIcon = "lucide/circle-minus", Tag = sender }; btnDisable.ToolTip = Lang.Text("Instance.Resource.Disable"); ToolTipService.SetPlacement(btnDisable, PlacementMode.Center); ToolTipService.SetVerticalOffset(btnDisable, 30d); ToolTipService.SetHorizontalOffset(btnDisable, 2d); btnDisable.Click += (ss, e) => Disable_Click((MyIconButton)ss, e); sender.Buttons = new[] { btnCont, btnOpen, btnDisable, btnDelete }; } else if (sender.Entry.State == ModLocalComp.LocalCompFile.LocalFileStatus.Disabled) { var btnEnable = new MyIconButton { LogoScale = 1d, SvgIcon = "lucide/circle-check", Tag = sender }; btnEnable.ToolTip = Lang.Text("Instance.Resource.Enable"); ToolTipService.SetPlacement(btnEnable, PlacementMode.Center); ToolTipService.SetVerticalOffset(btnEnable, 30d); ToolTipService.SetHorizontalOffset(btnEnable, 2d); btnEnable.Click += (ss, e) => Enable_Click((MyIconButton)ss, e); sender.Buttons = new[] { btnCont, btnOpen, btnEnable, btnDelete }; } else { sender.Buttons = new[] { btnCont, btnOpen, btnDelete }; } } /// /// 刷新整个 UI。 /// public void RefreshUI() { if (PanList is null) return; var showingDatapacks = (IsSearching ? searchResult : datapackItems.Values.Select(i => i.Entry)) .Where(m => CanPassFilter(m)).ToList(); // 对显示的数据包进行排序 if (showingDatapacks.Any()) { var sortMethod = GetSortMethod(currentSortMethod); showingDatapacks.Sort((a, b) => sortMethod(a, b)); } // 重新列出列表 ModAnimation.AniControlEnabled += 1; if (showingDatapacks.Any()) { PanList.Visibility = Visibility.Visible; PanList.Children.Clear(); foreach (var TargetDatapack in showingDatapacks) { if (!datapackItems.ContainsKey(TargetDatapack.RawPath)) continue; var item = datapackItems[TargetDatapack.RawPath]; // 确保元素没有父容器,避免重复添加异常 if (item.Parent is not null) ((Panel)item.Parent).Children.Remove(item); ModStyle.MinecraftFormatter.SetColorfulTextLab(item.LabTitle.Text, item.LabTitle, ThemeService.IsDarkMode); ModStyle.MinecraftFormatter.SetColorfulTextLab(item.LabInfo.Text, item.LabInfo, ThemeService.IsDarkMode); item.Checked = selectedDatapacks.Contains(TargetDatapack.RawPath); // 更新选中状态 PanList.Children.Add(item); } } else { PanList.Visibility = Visibility.Collapsed; } ModAnimation.AniControlEnabled -= 1; selectedDatapacks = new HashSet(selectedDatapacks.Where(m => showingDatapacks.Any(s => (s.RawPath ?? "") == (m ?? "")))); RefreshBars(); } /// /// 刷新顶栏和底栏显示。 /// public void RefreshBars() { Dispatcher.BeginInvoke(new Func(async () => { // ----------------- // 顶部栏 // ----------------- // 计数 var anyCount = 0; var enabledCount = 0; var disabledCount = 0; var updateCount = 0; var unavalialeCount = 0; var itemSource = (IsSearching ? searchResult : datapackItems.Values.Select(i => i.Entry)).ToArray(); await Task.Run(() => { foreach (var item in itemSource) { anyCount += 1; if (item.CanUpdate) updateCount += 1; if (item.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine) enabledCount += 1; if (item.State == ModLocalComp.LocalCompFile.LocalFileStatus.Disabled) disabledCount += 1; if (item.State == ModLocalComp.LocalCompFile.LocalFileStatus.Unavailable) unavalialeCount += 1; } }); // 显示 BtnFilterAll.Text = IsSearching ? Lang.Text("Instance.Resource.Filter.SearchResult") : Lang.Text("Instance.Resource.Filter.AllWithCount", anyCount); BtnFilterCanUpdate.Text = Lang.Text("Instance.Resource.Filter.UpdatableWithCount", updateCount); BtnFilterCanUpdate.Visibility = Filter == FilterType.CanUpdate || updateCount > 0 ? Visibility.Visible : Visibility.Collapsed; BtnFilterEnabled.Text = Lang.Text("Instance.Resource.Filter.EnabledWithCount", enabledCount); BtnFilterEnabled.Visibility = Filter == FilterType.Enabled || (enabledCount > 0 && enabledCount < anyCount) ? Visibility.Visible : Visibility.Collapsed; BtnFilterDisabled.Text = Lang.Text("Instance.Resource.Filter.DisabledWithCount", disabledCount); BtnFilterDisabled.Visibility = Filter == FilterType.Disabled || disabledCount > 0 ? Visibility.Visible : Visibility.Collapsed; BtnFilterError.Text = Lang.Text("Instance.Resource.Filter.ErrorWithCount", unavalialeCount); BtnFilterError.Visibility = Filter == FilterType.Unavailable || unavalialeCount > 0 ? Visibility.Visible : Visibility.Collapsed; // ----------------- // 底部栏 // ----------------- // 计数 var newCount = selectedDatapacks.Count; var selected = newCount > 0; if (selected) LabSelect.Text = Lang.Text("Instance.Resource.SelectedCount", newCount); // 按钮可用性 if (selected) { var hasUpdate = false; var hasEnabled = false; var hasDisabled = false; var canFavoriteAndShare = true; // 检查是否所有选中的数据包都有有效的项目信息 await Task.Run(() => { foreach (var DatapackEntity in ModLocalComp.compResourceListLoader.output) if (selectedDatapacks.Contains(DatapackEntity.RawPath)) { if (DatapackEntity.CanUpdate) hasUpdate = true; if (DatapackEntity.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine) hasEnabled = true; else if (DatapackEntity.State == ModLocalComp.LocalCompFile.LocalFileStatus.Disabled) hasDisabled = true; if (DatapackEntity.Comp is null || string.IsNullOrEmpty(DatapackEntity.Comp.Id)) canFavoriteAndShare = false; } }); BtnSelectDisable.IsEnabled = hasEnabled; BtnSelectEnable.IsEnabled = hasDisabled; BtnSelectUpdate.IsEnabled = hasUpdate; BtnSelectFavorites.IsEnabled = canFavoriteAndShare; BtnSelectShare.IsEnabled = canFavoriteAndShare; } // 更新显示状态 if (ModAnimation.AniControlEnabled == 0) { PanListBack.Margin = new Thickness(0d, 0d, 0d, selected ? 95 : 15); if (selected) { // 仅在数量增加时播放出现/跳跃动画 if (bottomBarShownCount >= newCount) { bottomBarShownCount = newCount; return; } bottomBarShownCount = newCount; // 出现/跳跃动画 CardSelect.Visibility = Visibility.Visible; ModAnimation.AniStart( new[] { ModAnimation.AaOpacity(CardSelect, 1d - CardSelect.Opacity, 60), ModAnimation.AaTranslateY(CardSelect, -27 - TransSelect.Y, 120, ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak)), ModAnimation.AaTranslateY(CardSelect, 3d, 150, 120, new ModAnimation.AniEaseInoutFluent(ModAnimation.AniEasePower.Weak)), ModAnimation.AaTranslateY(CardSelect, -1, 90, 270, new ModAnimation.AniEaseInoutFluent(ModAnimation.AniEasePower.Weak)) }, "Datapack Sidebar"); } else { // 不重复播放隐藏动画 if (bottomBarShownCount == 0) return; bottomBarShownCount = 0; // 隐藏动画 ModAnimation.AniStart( new[] { ModAnimation.AaOpacity(CardSelect, -CardSelect.Opacity, 90), ModAnimation.AaTranslateY(CardSelect, -10 - TransSelect.Y, 90, ease: new ModAnimation.AniEaseInFluent(ModAnimation.AniEasePower.Weak)), ModAnimation.AaCode(() => CardSelect.Visibility = Visibility.Collapsed, after: true) }, "Datapack Sidebar"); } } else { ModAnimation.AniStop("Datapack Sidebar"); bottomBarShownCount = newCount; if (selected) { CardSelect.Visibility = Visibility.Visible; CardSelect.Opacity = 1d; TransSelect.Y = -25; } else { CardSelect.Visibility = Visibility.Collapsed; CardSelect.Opacity = 0d; TransSelect.Y = -10; } } })); } private int bottomBarShownCount; #endregion #region 管理 /// /// 打开 datapacks 文件夹。 /// private void BtnManageOpen_Click(object sender, EventArgs e) { try { var datapackPath = Path.Combine(PageInstanceSavesLeft.currentSave, "datapacks"); Directory.CreateDirectory(datapackPath); ModBase.OpenExplorer(datapackPath); } catch (Exception ex) { ModBase.Log( ex, "打开 datapacks 文件夹失败", ModBase.LogLevel.Msgbox, userSummary: Lang.Text("Instance.Saves.Error.OperationFailed")); } } /// /// 全选。 /// private void BtnManageSelectAll_Click(object sender, MouseButtonEventArgs e) { ChangeAllSelected(selectedDatapacks.Count < PanList.Children.Count); } /// /// 安装数据包。 /// private void BtnManageInstall_Click(object sender, MouseButtonEventArgs e) { var fileList = SystemDialogs.SelectFiles( Lang.Text("Instance.Saves.Datapack.Install.FileDialog.Filter"), Lang.Text("Instance.Saves.Datapack.Install.FileDialog.Title")); if (fileList is null || fileList.Length == 0) return; InstallDatapackFiles(fileList); Refresh(); } /// /// 安装数据包文件。 /// public static void InstallDatapackFiles(IEnumerable filePathList) { if (!filePathList.Any()) return; var extension = filePathList.First().AfterLast(".").ToLower(); // 检查文件扩展名 if (extension != "zip") { HintService.Hint(Lang.Text("Instance.Resource.Install.UnsupportedFormat", extension, Lang.Text("Download.Comp.Type.DataPack"), "zip"), HintType.Error); return; } // 检查回收站 if (filePathList.First().Contains(@":\$RECYCLE.BIN\")) { HintService.Hint(Lang.Text("Instance.Resource.Install.RestoreFromRecycleBin"), HintType.Error); return; } ModBase.Log($"[System] 文件为 {extension} 格式,尝试作为数据包安装"); // 确认安装 if (!(ModMain.frmMain.pageCurrent == FormMain.PageType.InstanceSetup && ModMain.frmMain.PageCurrentSub == FormMain.PageSubType.VersionSavesDatapack)) if (ModMain.MyMsgBox(Lang.Text("Instance.Saves.Datapack.Install.Message"), Lang.Text("Instance.Saves.Datapack.Install.Title"), Lang.Text("Common.Action.Confirm"), Lang.Text("Common.Action.Cancel")) != 1) return; // 执行安装 try { var datapackFolder = Path.Combine(PageInstanceSavesLeft.currentSave, "datapacks"); Directory.CreateDirectory(datapackFolder); foreach (var FilePath in filePathList) { var newFileName = ModBase.GetFileNameFromPath(FilePath); var destFile = datapackFolder + newFileName; if (File.Exists(destFile)) if (ModMain.MyMsgBox(Lang.Text("Instance.Resource.Install.OverwriteConfirm.Message", newFileName), Lang.Text("Instance.Resource.Install.OverwriteConfirm.Title"), Lang.Text("Common.Action.Overwrite"), Lang.Text("Common.Action.Cancel")) != 1) continue; ModBase.CopyFile(FilePath, destFile); } if (filePathList.Count() == 1) HintService.Hint(Lang.Text("Instance.Resource.Install.SuccessSingle", ModBase.GetFileNameFromPath(filePathList.First())), HintType.Success); else HintService.Hint(Lang.Text("Instance.Resource.Install.SuccessMultiple", filePathList.Count(), Lang.Text("Download.Comp.Type.DataPack")), HintType.Success); // 刷新列表 if (ModMain.frmMain.pageCurrent == FormMain.PageType.InstanceSetup && ModMain.frmMain.PageCurrentSub == FormMain.PageSubType.VersionSavesDatapack) if (ModMain.frmInstanceSavesDatapack is not null) ModMain.frmInstanceSavesDatapack.ReloadDatapackFileList(true); } catch (Exception ex) { ModBase.Log( ex, "复制数据包文件失败", ModBase.LogLevel.Msgbox, userSummary: Lang.Text("Instance.Saves.Error.OperationFailed")); } } /// /// 下载数据包。 /// private void BtnManageDownload_Click(object sender, MouseButtonEventArgs e) { var datapackPath = Path.Combine(PageInstanceSavesLeft.currentSave, "datapacks"); Directory.CreateDirectory(datapackPath); PageDownloadCompDetail.cachedFolder[ModComp.CompType.DataPack] = datapackPath; ModMain.frmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadDataPack); PageComp.targetVersion = PageInstanceLeft.McInstance; // 将当前实例设置为筛选器 } /// /// 导出信息。 /// private void BtnManageInfoExport_Click(object sender, MouseButtonEventArgs e) { var choice = ModMain.MyMsgBox( Lang.Text("Instance.Saves.Datapack.Export.Mode.Message"), Lang.Text("Instance.Resource.Export.Mode.Title"), Lang.Text("Instance.Resource.Export.Mode.Txt"), Lang.Text("Instance.Resource.Export.Mode.Csv"), Lang.Text("Common.Action.Cancel")); void ExportText(string content, string fileName) { try { var savePath = SystemDialogs.SelectSaveFile(Lang.Text("Instance.Resource.Export.SelectSaveLocation"), fileName, Lang.Text("Instance.Resource.Export.FilesFilter")); if (string.IsNullOrWhiteSpace(savePath)) return; File.WriteAllText(savePath, content, Encoding.UTF8); ModBase.OpenExplorer(savePath); } catch (Exception ex) { ModBase.Log( ex, "导出数据包信息失败", ModBase.LogLevel.Msgbox, userSummary: Lang.Text("Instance.Saves.Error.OperationFailed")); } } ; switch (choice) { case 1: // TXT { var exportContent = new List(); foreach (var DatapackEntity in ModLocalComp.compResourceListLoader.output) exportContent.Add(DatapackEntity.FileName); ExportText(exportContent.Join("\r\n"), ModBase.GetFolderNameFromPath(PageInstanceSavesLeft.currentSave) + "的数据包信息.txt"); break; } case 2: // CSV { var exportContent = new List(); exportContent.Add("文件名,数据包名称,数据包版本,此版本更新时间,工程 ID,文件大小(字节),文件路径"); foreach (var DatapackEntity in ModLocalComp.compResourceListLoader.output) exportContent.Add( $"{DatapackEntity.FileName},{DatapackEntity.Comp?.TranslatedName},{DatapackEntity.Version},{DatapackEntity.compFile?.ReleaseDate},{DatapackEntity.Comp?.Id},{GetDatapackFileInfo(DatapackEntity.path).Length},{DatapackEntity.path}"); ExportText(exportContent.Join("\r\n"), ModBase.GetFolderNameFromPath(PageInstanceSavesLeft.currentSave) + "的数据包信息.csv"); break; } } } #endregion #region 选择 /// /// 选择的数据包的路径。 /// public HashSet selectedDatapacks = new(); // 单项切换选择状态 public void CheckChanged(MyLocalCompItem sender, ModBase.RouteEventArgs e) { if (ModAnimation.AniControlEnabled != 0) return; // 更新选择了的内容 var selectedKey = sender.Entry.RawPath; if (sender.Checked) selectedDatapacks.Add(selectedKey); else selectedDatapacks.Remove(selectedKey); RefreshBars(); } // 切换所有项的选择状态 private void ChangeAllSelected(bool value) { ModAnimation.AniControlEnabled += 1; selectedDatapacks.Clear(); foreach (var Item in datapackItems.Values) { var shouldSelected = value && PanList.Children.Contains(Item); Item.Checked = shouldSelected; if (shouldSelected) selectedDatapacks.Add(Item.Entry.RawPath); } ModAnimation.AniControlEnabled -= 1; RefreshBars(); } private void UnselectedAllWithAnimation() { var cacheAniControlEnabled = ModAnimation.AniControlEnabled; ModAnimation.AniControlEnabled = 0; ChangeAllSelected(false); ModAnimation.AniControlEnabled += cacheAniControlEnabled; } private void FrmMain_KeyDown(object sender, KeyEventArgs e) { if (!ReferenceEquals(ModMain.frmMain.pageRight, this)) return; if ((Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) && e.Key == Key.A) ChangeAllSelected(true); } private void SearchBox_PreviewKeyDown(object sender, KeyEventArgs e) { // Ctrl + A 会被搜索框捕获,导致无法全选,所以在按下 Ctrl + A 时转移焦点以便捕获 if (SearchBox.Text.Any()) return; if ((Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) && e.Key == Key.A) PanBack.Focus(); } #endregion #region 筛选 public FilterType Filter { get => field; set { if (field == value) return; field = value; switch (value) { case FilterType.All: { BtnFilterAll.Checked = true; break; } case FilterType.Enabled: { BtnFilterEnabled.Checked = true; break; } case FilterType.Disabled: { BtnFilterDisabled.Checked = true; break; } case FilterType.CanUpdate: { BtnFilterCanUpdate.Checked = true; break; } default: { BtnFilterError.Checked = true; break; } } RefreshUI(); } } = FilterType.All; public enum FilterType { All = 0, Enabled = 1, Disabled = 2, CanUpdate = 3, Unavailable = 4 } /// /// 检查该数据包项是否符合当前筛选的类别。 /// private bool CanPassFilter(ModLocalComp.LocalCompFile checkingDatapack) { switch (Filter) { case FilterType.All: { return true; } case FilterType.Enabled: { return checkingDatapack.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine; } case FilterType.Disabled: { return checkingDatapack.State == ModLocalComp.LocalCompFile.LocalFileStatus.Disabled; } case FilterType.CanUpdate: { return checkingDatapack.CanUpdate; } case FilterType.Unavailable: { return checkingDatapack.State == ModLocalComp.LocalCompFile.LocalFileStatus.Unavailable; } default: { return false; } } } // 点击筛选项触发的改变 private void ChangeFilter(MyRadioButton sender, bool raiseByMouse) { Filter = (FilterType)Convert.ToInt32(sender.Tag); RefreshUI(); DoSort(); } #endregion #region 排序 private SortMethod currentSortMethod = SortMethod.CompName; private void SetSortMethod(SortMethod target) { currentSortMethod = target; BtnSort.Text = Lang.Text("Instance.Resource.Sort.Text", GetSortName(target)); DoSort(); } private enum SortMethod { FileName, CompName, CreateTime, DatapackFileSize } private string GetSortName(SortMethod method) { switch (method) { case SortMethod.FileName: { return Lang.Text("Instance.Resource.Sort.FileName"); } case SortMethod.CompName: { return Lang.Text("Instance.Resource.Sort.ResourceName"); } case SortMethod.CreateTime: { return Lang.Text("Instance.Resource.Sort.AddTime"); } case SortMethod.DatapackFileSize: { return Lang.Text("Instance.Resource.Sort.FileSize"); } default: { return Lang.Text("Instance.Resource.Sort.ResourceName"); } } return ""; } private void BtnSortClick(object sender, ModBase.RouteEventArgs e) { var body = new ContextMenu(); foreach (SortMethod i in Enum.GetValues(typeof(SortMethod))) { var item = new MyMenuItem(); item.Header = GetSortName(i); item.Click += (_, _) => SetSortMethod(i); body.Items.Add(item); } body.PlacementTarget = (UIElement)sender; body.Placement = PlacementMode.Bottom; body.IsOpen = true; } private readonly object sortLock = new(); private void DoSort() { lock (sortLock) { try { if (PanList is null || PanList.Children.Count < 2) return; // 将子元素转换为可排序的列表 var items = PanList.Children.OfType().ToList(); var method = GetSortMethod(currentSortMethod); // 分离有效和无效项(保持原始相对顺序) var invalid = items.Where(i => i.Entry is null).ToList(); var valid = items.Except(invalid).ToList(); // 仅对有效项进行排序 valid.Sort((x, y) => method(x.Entry, y.Entry)); // 合并保持无效项的原始顺序 items = valid.Concat(invalid).ToList(); // 批量更新UI元素 PanList.Children.Clear(); items.ForEach(i => PanList.Children.Add(i)); } catch (Exception ex) { ModBase.Log( ex, "执行排序时出错", ModBase.LogLevel.Hint, userSummary: Lang.Text("Instance.Saves.Error.OperationFailed")); } } } private Func GetSortMethod(SortMethod method) { switch (method) { case SortMethod.FileName: { return (a, b) => string.Compare(a.FileName, b.FileName, StringComparison.OrdinalIgnoreCase); } case SortMethod.CompName: { return (a, b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase); } case SortMethod.CreateTime: { return (a, b) => { var aDate = GetDatapackFileInfo(a.path).CreationTime; var bDate = GetDatapackFileInfo(b.path).CreationTime; if (aDate == DateTime.MinValue && bDate == DateTime.MinValue) return string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase); if (aDate == DateTime.MinValue) return 1; if (bDate == DateTime.MinValue) return -1; return bDate.CompareTo(aDate); }; } case SortMethod.DatapackFileSize: { return (a, b) => { var aSize = GetDatapackFileInfo(a.path).Length; var bSize = GetDatapackFileInfo(b.path).Length; if (aSize == 0L && bSize == 0L) return string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase); if (aSize == 0L) return 1; if (bSize == 0L) return -1; return bSize.CompareTo(aSize); }; } default: { return (a, b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase); } } } #endregion #region 下边栏 // 启用 private void BtnSelectEnable_Click(object sender, ModBase.RouteEventArgs e) { ToggleDatapacks( ModLocalComp.compResourceListLoader.output.Where(m => selectedDatapacks.Contains(m.RawPath)).ToList(), true); ChangeAllSelected(false); } // 禁用 private void BtnSelectDisable_Click(object sender, ModBase.RouteEventArgs e) { ToggleDatapacks( ModLocalComp.compResourceListLoader.output.Where(m => selectedDatapacks.Contains(m.RawPath)).ToList(), false); ChangeAllSelected(false); } /// /// 启用/禁用数据包(通过重命名文件夹为 .disabled) /// private void ToggleDatapacks(IEnumerable datapackList, bool isEnable) { var isSuccessful = true; foreach (var DatapackE in datapackList) { var datapackEntity = DatapackE; string newPath = null; if (datapackEntity.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine && !isEnable) // 禁用 - 添加 .disabled 后缀 newPath = datapackEntity.path + ".disabled"; else if (datapackEntity.State == ModLocalComp.LocalCompFile.LocalFileStatus.Disabled && isEnable) // 启用 - 移除 .disabled 后缀 newPath = datapackEntity.RawPath; else continue; // 重命名 try { if (File.Exists(newPath)) { ModMain.MyMsgBox(Lang.Text("Instance.Saves.Datapack.Replace.FileNameConflict", ModBase.GetFileNameFromPath(newPath))); continue; } FileSystem.Rename(datapackEntity.path, newPath); } catch (FileNotFoundException ex) { ModBase.Log( ex, $"未找到需要重命名的数据包({datapackEntity.path ?? "null"})", ModBase.LogLevel.Feedback, userSummary: Lang.Text("Instance.Saves.Error.OperationFailed")); ReloadDatapackFileList(true); return; } catch (Exception ex) { ModBase.Log(ex, $"重命名数据包失败({datapackEntity.path ?? "null"})"); isSuccessful = false; } // 更改 Loader 中的列表 var newDatapackEntity = new ModLocalComp.LocalCompFile(newPath); newDatapackEntity.FromJson(datapackEntity.ToJson()); if (ModLocalComp.compResourceListLoader.output.Contains(datapackEntity)) { var indexOfLoader = ModLocalComp.compResourceListLoader.output.IndexOf(datapackEntity); ModLocalComp.compResourceListLoader.output.RemoveAt(indexOfLoader); ModLocalComp.compResourceListLoader.output.Insert(indexOfLoader, newDatapackEntity); } if (searchResult is not null && searchResult.Contains(datapackEntity)) { var indexOfResult = searchResult.IndexOf(datapackEntity); searchResult.Remove(datapackEntity); searchResult.Insert(indexOfResult, newDatapackEntity); } // 更改 UI 中的列表 try { var newItem = BuildLocalCompItem(newDatapackEntity); datapackItems[datapackEntity.RawPath] = newItem; var indexOfUi = PanList.Children.IndexOf(PanList.Children.OfType() .FirstOrDefault(i => ReferenceEquals(i.Entry, datapackEntity))); if (indexOfUi == -1) continue; PanList.Children.RemoveAt(indexOfUi); PanList.Children.Insert(indexOfUi, newItem); } catch (Exception ex) { ModBase.Log( ex, $"更新 UI 列表项失败:{datapackEntity.FileName}", ModBase.LogLevel.Hint, userSummary: Lang.Text("Instance.Saves.Error.OperationFailed")); } } Dispatcher.Invoke(() => PanList.UpdateLayout(), DispatcherPriority.Background); if (isSuccessful) { RefreshBars(); } else { HintService.Hint(Lang.Text("Instance.Saves.Datapack.ToggleWarning"), HintType.Error); ReloadDatapackFileList(true); } LoaderRun(ModLoader.LoaderFolderRunType.UpdateOnly); } // 更新 private void BtnSelectUpdate_Click(object sender, ModBase.RouteEventArgs e) { var updateList = ModLocalComp.compResourceListLoader.output .Where(m => selectedDatapacks.Contains(m.RawPath) && m.CanUpdate).ToList(); if (!updateList.Any()) return; UpdateResource(updateList); ChangeAllSelected(false); } /// /// 记录正在进行数据包更新的 datapacks 文件夹路径。 /// public static List updatingVersions = new(); private static bool TryGetSafeDatapackUpdateFileName(ModComp.CompFile file, out string fileName) { fileName = file.FileName?.Trim() ?? ""; if (string.IsNullOrEmpty(fileName)) return false; if (!fileName.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)) return false; if (fileName.IndexOfAny(new[] { '\\', '/', ':' }) >= 0) return false; if (fileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) return false; return fileName == Path.GetFileName(fileName) && fileName != "." && fileName != ".."; } private static bool TryBuildDatapackUpdatePath(string rootPath, string fileName, out string fullPath) { var fullRootPath = Path.GetFullPath(rootPath); if (!fullRootPath.EndsWith(Path.DirectorySeparatorChar.ToString()) && !fullRootPath.EndsWith(Path.AltDirectorySeparatorChar.ToString())) fullRootPath += Path.DirectorySeparatorChar; fullPath = Path.GetFullPath(Path.Combine(fullRootPath, fileName)); return fullPath.StartsWith(fullRootPath, StringComparison.OrdinalIgnoreCase); } public void UpdateResource(IEnumerable datapackList) { // 更新前警告 if (!States.Hint.FunctionDatapackUpdate || datapackList.Count() >= 15) { if (ModMain.MyMsgBox( Lang.Text("Instance.Saves.Datapack.Update.Warning.Message"), Lang.Text("Instance.Saves.Datapack.Update.Warning.Title"), Lang.Text("Instance.Saves.Datapack.Update.Warning.Confirm"), Lang.Text("Common.Action.Cancel"), isWarn: true) == 1) States.Hint.FunctionDatapackUpdate = true; else return; } try { // 构造下载信息 datapackList = datapackList.ToList(); // 防止刷新影响迭代器 var fileList = new List(); var fileCopyList = new Dictionary(); var updateEntryList = new List(); var tempRoot = Path.Combine(ModBase.pathTemp, "DownloadedComp"); var datapackRoot = Path.Combine(PageInstanceSavesLeft.currentSave, "datapacks"); var skippedUnsafeFileCount = 0; foreach (var Entry in datapackList) { var file = Entry.UpdateFile; if (!file.Available) continue; if (!TryGetSafeDatapackUpdateFileName(file, out var safeFileName) || !TryBuildDatapackUpdatePath(tempRoot, safeFileName, out var tempAddress) || !TryBuildDatapackUpdatePath(datapackRoot, safeFileName, out var realAddress)) { skippedUnsafeFileCount++; ModBase.Log($"[DatapackUpdate] 已跳过不安全的数据包更新文件名:{file.FileName}", ModBase.LogLevel.Debug); continue; } // 添加到下载列表 fileList.Add(file.ToNetFile(tempAddress, ModComp.DownloadReason.Update, file.RawGameVersions.FirstOrDefault())); fileCopyList[tempAddress] = realAddress; updateEntryList.Add(Entry); } if (skippedUnsafeFileCount > 0) HintService.Hint( Lang.Text("Instance.Saves.Datapack.Update.UnsafeFilesSkipped", skippedUnsafeFileCount), HintType.Error); if (!fileList.Any()) return; // 构造加载器 var installLoaders = new List(); var finishedFileNames = new List(); installLoaders.Add( new LoaderDownload(Lang.Text("Instance.Saves.Datapack.Update.Task.DownloadFiles"), fileList) { ProgressWeight = updateEntryList.Count * 1.5d }); installLoaders.Add(new ModLoader.LoaderTask( Lang.Text("Instance.Saves.Datapack.Update.Task.ReplaceFiles"), _ => { try { foreach (var Entry in updateEntryList) if (File.Exists(Entry.path)) Microsoft.VisualBasic.FileIO.FileSystem.DeleteFile(Entry.path, UIOption.AllDialogs, RecycleOption.SendToRecycleBin); else ModBase.Log($"[DatapackUpdate] 未找到更新前的数据包文件,跳过对它的删除:{Entry.path}", ModBase.LogLevel.Debug); foreach (var Entry in fileCopyList) { if (File.Exists(Entry.Value)) { Microsoft.VisualBasic.FileIO.FileSystem.DeleteFile(Entry.Value, UIOption.AllDialogs, RecycleOption.SendToRecycleBin); ModBase.Log($"[Datapack] 更新后的数据包文件已存在,将会把它放入回收站:{Entry.Value}", ModBase.LogLevel.Debug); } if (Directory.Exists(ModBase.GetPathFromFullPath(Entry.Value))) { File.Move(Entry.Key, Entry.Value); finishedFileNames.Add(ModBase.GetFileNameFromPath(Entry.Value)); } else { ModBase.Log($"[Datapack] 更新后的目标文件夹已被删除:{Entry.Value}", ModBase.LogLevel.Debug); } } } catch (OperationCanceledException ex) { ModBase.Log(ex, "替换旧版数据包文件时被主动取消"); } })); // 结束处理 var loader = new ModLoader.LoaderCombo>( Lang.Text("Instance.Saves.Datapack.Update.Task.Title", ModBase.GetFolderNameFromPath(PageInstanceSavesLeft.currentSave)), installLoaders); var pathDatapacks = Path.Combine(PageInstanceSavesLeft.currentSave, "datapacks"); loader.OnStateChanged = _ => { switch (loader.State) { case ModBase.LoadState.Finished: { switch (finishedFileNames.Count) { case 0: { ModBase.Log("[DatapackUpdate] 没有数据包被成功更新"); break; } case 1: { HintService.Hint(Lang.Text("Instance.Resource.Update.SuccessSingle", finishedFileNames.Single()), HintType.Success); break; } default: { HintService.Hint(Lang.Text("Instance.Resource.Update.SuccessMultiple", finishedFileNames.Count), HintType.Success); break; } } break; } case ModBase.LoadState.Failed: { HintService.Hint(Lang.Text("Instance.Resource.Update.Failed", loader.Error.Message), HintType.Error); break; } case ModBase.LoadState.Aborted: { HintService.Hint(Lang.Text("Instance.Resource.Update.Aborted")); break; } default: { return; } } ModBase.Log($"[DatapackUpdate] 已从正在进行数据包更新的文件夹列表移除:{pathDatapacks}"); updatingVersions.Remove(pathDatapacks); // 清理缓存 ModBase.RunInNewThread(() => { try { foreach (var TempFile in fileCopyList.Keys) if (File.Exists(TempFile)) File.Delete(TempFile); } catch (Exception ex) { ModBase.Log(ex, "清理数据包更新缓存失败"); } }, "Clean Datapack Update Cache", ThreadPriority.BelowNormal); }; // 启动加载器 ModBase.Log($"[DatapackUpdate] 开始更新 {datapackList.Count()} 个数据包:{pathDatapacks}"); updatingVersions.Add(pathDatapacks); loader.Start(); ModLoader.LoaderTaskbarAdd(loader); ModMain.frmMain.BtnExtraDownload.ShowRefresh(); ModMain.frmMain.BtnExtraDownload.Ribble(); ReloadDatapackFileList(true); } catch (Exception ex) { ModBase.Log(ex, "初始化数据包更新失败"); } } // 删除 private void BtnSelectDelete_Click(object sender, ModBase.RouteEventArgs e) { DeleteDatapacks(ModLocalComp.compResourceListLoader.output.Where(m => selectedDatapacks.Contains(m.RawPath))); ChangeAllSelected(false); } private void DeleteDatapacks(IEnumerable datapackList) { try { var isSuccessful = true; var isShiftPressed = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift); // 确认需要删除的文件 datapackList = datapackList.SelectMany(target => { if (target.State == ModLocalComp.LocalCompFile.LocalFileStatus.Fine) return new[] { target.path, target.path + ".disabled" }; return new[] { target.path, target.RawPath }; }).Distinct().Where(m => File.Exists(m)).Select(m => new ModLocalComp.LocalCompFile(m)).ToList(); // 实际删除文件 foreach (var DatapackEntity in datapackList) { try { if (isShiftPressed) File.Delete(DatapackEntity.path); else Microsoft.VisualBasic.FileIO.FileSystem.DeleteFile(DatapackEntity.path, UIOption.OnlyErrorDialogs, RecycleOption.SendToRecycleBin); } catch (OperationCanceledException ex) { ModBase.Log(ex, "删除数据包被主动取消"); ReloadDatapackFileList(true); return; } catch (Exception ex) { ModBase.Log( ex, $"删除数据包失败({DatapackEntity.path})", ModBase.LogLevel.Msgbox, userSummary: Lang.Text("Instance.Saves.Error.OperationFailed")); isSuccessful = false; } // 取消选中 selectedDatapacks.Remove(DatapackEntity.RawPath); // 更改 Loader 和 UI 中的列表 ModLocalComp.compResourceListLoader.output.Remove(DatapackEntity); searchResult?.Remove(DatapackEntity); datapackItems.Remove(DatapackEntity.RawPath); var indexOfUi = PanList.Children.IndexOf(PanList.Children.OfType() .FirstOrDefault(i => i.Entry.Equals(DatapackEntity))); if (indexOfUi >= 0) PanList.Children.RemoveAt(indexOfUi); } RefreshBars(); if (!isSuccessful) { HintService.Hint(Lang.Text("Instance.Saves.Datapack.Delete.FileOccupied"), HintType.Error); ReloadDatapackFileList(true); } else if (PanList.Children.Count == 0) { ReloadDatapackFileList(true); } else { RefreshBars(); } if (!isSuccessful) return; if (isShiftPressed) { if (datapackList.Count() == 1) HintService.Hint(Lang.Text("Instance.Saves.Datapack.Delete.PermanentSingle", datapackList.Single().FileName), HintType.Success); else HintService.Hint(Lang.Text("Instance.Saves.Datapack.Delete.PermanentMultiple", datapackList.Count()), HintType.Success); } else if (datapackList.Count() == 1) { HintService.Hint(Lang.Text("Instance.Saves.Datapack.Delete.RecycleSingle", datapackList.Single().FileName), HintType.Success); } else { HintService.Hint(Lang.Text("Instance.Saves.Datapack.Delete.RecycleMultiple", datapackList.Count()), HintType.Success); } } catch (OperationCanceledException ex) { ModBase.Log(ex, "删除数据包被主动取消"); ReloadDatapackFileList(true); } catch (Exception ex) { ModBase.Log( ex, "删除数据包出现未知错误", ModBase.LogLevel.Feedback, userSummary: Lang.Text("Instance.Saves.Error.OperationFailed")); ReloadDatapackFileList(true); } LoaderRun(ModLoader.LoaderFolderRunType.UpdateOnly); } // 取消选择 private void BtnSelectCancel_Click(object sender, ModBase.RouteEventArgs e) { ChangeAllSelected(false); } // 收藏 private void BtnSelectFavorites_Click(object sender, ModBase.RouteEventArgs e) { var selected = ModLocalComp.compResourceListLoader.output .Where(m => selectedDatapacks.Contains(m.RawPath) && m.Comp is not null).Select(i => i.Comp).ToList(); ModComp.CompFavorites.ShowMenu(selected, (UIElement)sender); } // 分享 private void BtnSelectShare_Click(object sender, ModBase.RouteEventArgs e) { var shareList = ModLocalComp.compResourceListLoader.output .Where(m => selectedDatapacks.Contains(m.RawPath) && m.Comp is not null).Select(i => i.Comp.Id).ToHashSet(); ModBase.ClipboardSet(ModComp.CompFavorites.GetShareCode(shareList)); ChangeAllSelected(false); } #endregion #region 单个资源项 // 详情 public void Info_Click(object sender, EventArgs e) { try { var datapackEntry = ((MyLocalCompItem)(sender is MyIconButton iconBtn ? iconBtn.Tag : sender)).Entry; // 加载失败信息 if (datapackEntry.State == ModLocalComp.LocalCompFile.LocalFileStatus.Unavailable) { ModMain.MyMsgBox( Lang.Text( "Instance.Saves.Datapack.Info.ReadFailed.WithDetail", datapackEntry.FileUnavailableReason.ToString()), Lang.Text("Instance.Saves.Datapack.Info.ReadFailedTitle")); return; } if (datapackEntry.Comp is not null) { // 跳转到数据包下载页面 ModMain.frmMain.PageChange(new FormMain.PageStackData { page = FormMain.PageType.CompDetail, additional = (datapackEntry.Comp, new List(), PageInstanceLeft.McInstance.Info.VanillaName, ModComp.CompLoaderType.Minecraft, ModComp.CompType.DataPack, null) }); } else { // 获取信息 var contentLines = new List(); if (datapackEntry.Description is not null) contentLines.Add(datapackEntry.Description + "\r\n"); if (datapackEntry.Authors is not null) contentLines.Add(Lang.Text("Instance.Saves.Datapack.Info.Author") + datapackEntry.Authors); contentLines.Add(Lang.Text("Instance.Saves.Datapack.Info.File") + datapackEntry.FileName + "(" + ModBase.GetString(GetDatapackFileInfo(datapackEntry.path).Length) + ")"); if (datapackEntry.Version is not null) contentLines.Add(Lang.Text("Instance.Saves.Datapack.Info.Version") + datapackEntry.Version); var debugInfo = new List(); if (datapackEntry.ModId is not null) debugInfo.Add(Lang.Text("Instance.Saves.Datapack.Info.DatapackId") + datapackEntry.ModId); if (debugInfo.Any()) { contentLines.Add(""); contentLines.AddRange(debugInfo); } // 显示详情信息 if (datapackEntry.Url is null) ModMain.MyMsgBox(contentLines.Join("\r\n"), datapackEntry.Name, Lang.Text("Instance.Resource.Item.Info.Return")); else if (ModMain.MyMsgBox(contentLines.Join("\r\n"), datapackEntry.Name, Lang.Text("Instance.Resource.Item.Info.OpenWebsite"), Lang.Text("Instance.Resource.Item.Info.Return")) == 1) ModBase.OpenWebsite(datapackEntry.Url); } } catch (Exception ex) { ModBase.Log( ex, "获取数据包详情失败", ModBase.LogLevel.Feedback, userSummary: Lang.Text("Instance.Saves.Error.OperationFailed")); } } // 打开文件所在的位置 public void Open_Click(MyIconButton sender, EventArgs e) { try { var listItem = (MyLocalCompItem)sender.Tag; ModBase.OpenExplorer(listItem.Entry.path); } catch (Exception ex) { ModBase.Log( ex, "打开数据包文件位置失败", ModBase.LogLevel.Feedback, userSummary: Lang.Text("Instance.Saves.Error.OperationFailed")); } } // 删除 public void Delete_Click(MyIconButton sender, EventArgs e) { var listItem = (MyLocalCompItem)sender.Tag; DeleteDatapacks(new[] { listItem.Entry }); } // 启用 public void Enable_Click(MyIconButton sender, EventArgs e) { var listItem = (MyLocalCompItem)sender.Tag; ToggleDatapacks(new[] { listItem.Entry }, true); } // 禁用 public void Disable_Click(MyIconButton sender, EventArgs e) { var listItem = (MyLocalCompItem)sender.Tag; ToggleDatapacks(new[] { listItem.Entry }, false); } #endregion #region 搜索 public bool IsSearching => !string.IsNullOrWhiteSpace(SearchBox.Text); private List searchResult; public void SearchRun(object sender, EventArgs e) { try { if (IsSearching) { // 构造请求 var queryList = new List>(); foreach (var Entry in ModLocalComp.compResourceListLoader.output) { var searchSource = new List(); 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 { item = Entry, searchSource = searchSource }); } // 进行搜索 searchResult = ModBase.Search(queryList, SearchBox.Text, ModBase.MaxLocalSearchDepth, 0.35d).Select(r => r.item).ToList(); } RefreshUI(); } catch (Exception ex) { ModBase.Log(ex, "搜索过程中发生异常"); } } #endregion }