PageVersionMod.xaml.vb 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. Imports System.Security.Principal
  2. Public Class PageVersionMod
  3. #Region "初始化"
  4. Private IsLoad As Boolean = False
  5. Public Sub PageOther_Loaded() Handles Me.Loaded
  6. If FrmMain.PageLast.Page <> FormMain.PageType.CompDetail Then PanBack.ScrollToHome()
  7. AniControlEnabled += 1
  8. SelectedMods.Clear()
  9. RefreshList()
  10. ChangeAllSelected(False)
  11. AniControlEnabled -= 1
  12. '非重复加载部分
  13. If IsLoad Then Exit Sub
  14. IsLoad = True
  15. #If DEBUG Then
  16. BtnManageCheck.Visibility = Visibility.Visible
  17. #End If
  18. End Sub
  19. ''' <summary>
  20. ''' 刷新 Mod 列表。
  21. ''' </summary>
  22. Public Sub RefreshList(Optional ForceReload As Boolean = False)
  23. If McModLoader.State = LoadState.Loading Then Exit Sub
  24. If LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", If(ForceReload, LoaderFolderRunType.ForceRun, LoaderFolderRunType.RunOnUpdated)) Then
  25. Log("[System] 已刷新 Mod 列表")
  26. PanBack.ScrollToHome()
  27. SearchBox.Text = ""
  28. End If
  29. End Sub
  30. Private Sub LoaderInit() Handles Me.Initialized
  31. PageLoaderInit(Load, PanLoad, PanAllBack, Nothing, McModLoader, AddressOf LoadUIFromLoaderOutput, AutoRun:=False)
  32. End Sub
  33. Private Sub Load_Click(sender As Object, e As MouseButtonEventArgs) Handles Load.Click
  34. If McModLoader.State = LoadState.Failed Then
  35. LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", LoaderFolderRunType.ForceRun)
  36. End If
  37. End Sub
  38. #End Region
  39. #Region "UI 化"
  40. ''' <summary>
  41. ''' 已加载的 Mod UI 缓存,不确保按显示顺序排列。Key 为 Mod 的 RawFileName。
  42. ''' </summary>
  43. Public ModItems As New Dictionary(Of String, MyLocalModItem)
  44. ''' <summary>
  45. ''' 将加载器结果的 Mod 列表加载为 UI。
  46. ''' </summary>
  47. Private Sub LoadUIFromLoaderOutput()
  48. Dim Mods As List(Of McMod) = McModLoader.Output
  49. Try
  50. '判断应该显示哪一个页面
  51. If Mods.Any() Then
  52. PanBack.Visibility = Visibility.Visible
  53. PanEmpty.Visibility = Visibility.Collapsed
  54. Else
  55. PanEmpty.Visibility = Visibility.Visible
  56. PanBack.Visibility = Visibility.Collapsed
  57. Exit Sub
  58. End If
  59. '输出结果
  60. ModItems.Clear()
  61. For Each ModEntity As McMod In Mods
  62. ModItems(ModEntity.RawFileName) = McModListItem(ModEntity)
  63. Next
  64. SearchBox.Text = "" '这会触发结果刷新,所以需要在 ModItems 更新之后,详见 #3124 的视频
  65. RefreshResult(Mods)
  66. Catch ex As Exception
  67. Log(ex, "加载 Mod 列表 UI 失败", LogLevel.Feedback)
  68. End Try
  69. End Sub
  70. Private Function McModListItem(Entry As McMod) As MyLocalModItem
  71. AniControlEnabled += 1
  72. Dim NewItem As New MyLocalModItem With {.SnapsToDevicePixels = True, .Entry = Entry,
  73. .ButtonHandler = AddressOf McModContent, .Checked = SelectedMods.Contains(Entry.RawFileName)}
  74. AddHandler Entry.OnGetCompProject, AddressOf NewItem.Refresh
  75. NewItem.Refresh()
  76. AniControlEnabled -= 1
  77. Return NewItem
  78. End Function
  79. Private Sub McModContent(sender As MyLocalModItem, e As EventArgs)
  80. '点击事件
  81. AddHandler sender.Changed, AddressOf CheckChanged
  82. AddHandler sender.Click, Sub(ss As MyLocalModItem, ee As EventArgs) ss.Checked = Not ss.Checked
  83. '图标按钮
  84. Dim BtnOpen As New MyIconButton With {.LogoScale = 1.05, .Logo = Logo.IconButtonOpen, .Tag = sender}
  85. BtnOpen.ToolTip = "打开文件位置"
  86. ToolTipService.SetPlacement(BtnOpen, Primitives.PlacementMode.Center)
  87. ToolTipService.SetVerticalOffset(BtnOpen, 30)
  88. ToolTipService.SetHorizontalOffset(BtnOpen, 2)
  89. AddHandler BtnOpen.Click, AddressOf Open_Click
  90. Dim BtnCont As New MyIconButton With {.LogoScale = 1, .Logo = Logo.IconButtonInfo, .Tag = sender}
  91. BtnCont.ToolTip = "详情"
  92. ToolTipService.SetPlacement(BtnCont, Primitives.PlacementMode.Center)
  93. ToolTipService.SetVerticalOffset(BtnCont, 30)
  94. ToolTipService.SetHorizontalOffset(BtnCont, 2)
  95. AddHandler BtnCont.Click, AddressOf Info_Click
  96. AddHandler sender.MouseRightButtonDown, AddressOf Info_Click
  97. Dim BtnDelete As New MyIconButton With {.LogoScale = 1, .Logo = Logo.IconButtonDelete, .Tag = sender}
  98. BtnDelete.ToolTip = "删除"
  99. ToolTipService.SetPlacement(BtnDelete, Primitives.PlacementMode.Center)
  100. ToolTipService.SetVerticalOffset(BtnDelete, 30)
  101. ToolTipService.SetHorizontalOffset(BtnDelete, 2)
  102. AddHandler BtnDelete.Click, AddressOf Delete_Click
  103. Dim BtnED As New MyIconButton With {.LogoScale = 1, .Logo = If(sender.Entry.State = McMod.McModState.Fine,
  104. "M508 990.4c-261.6 0-474.4-212-474.4-474.4S246.4 41.6 508 41.6s474.4 212 474.4 474.4S769.6 990.4 508 990.4zM508 136.8c-209.6 0-379.2 169.6-379.2 379.2 0 209.6 169.6 379.2 379.2 379.2s379.2-169.6 379.2-379.2C887.2 306.4 717.6 136.8 508 136.8zM697.6 563.2 318.4 563.2c-26.4 0-47.2-21.6-47.2-47.2 0-26.4 21.6-47.2 47.2-47.2l379.2 0c26.4 0 47.2 21.6 47.2 47.2C744.8 542.4 724 563.2 697.6 563.2z",
  105. "M512 0a512 512 0 1 0 512 512A512 512 0 0 0 512 0z m0 921.6a409.6 409.6 0 1 1 409.6-409.6 409.6 409.6 0 0 1-409.6 409.6z M716.8 339.968l-256 253.44L328.192 460.8A51.2 51.2 0 0 0 256 532.992l168.448 168.96a51.2 51.2 0 0 0 72.704 0l289.28-289.792A51.2 51.2 0 0 0 716.8 339.968z"),
  106. .Tag = sender}
  107. BtnED.ToolTip = If(sender.Entry.State = McMod.McModState.Fine, "禁用", "启用")
  108. ToolTipService.SetPlacement(BtnED, Primitives.PlacementMode.Center)
  109. ToolTipService.SetVerticalOffset(BtnED, 30)
  110. ToolTipService.SetHorizontalOffset(BtnED, 2)
  111. AddHandler BtnED.Click, AddressOf ED_Click
  112. If sender.Entry.State = McMod.McModState.Unavaliable Then
  113. sender.Buttons = {BtnCont, BtnOpen, BtnDelete}
  114. Else
  115. sender.Buttons = {BtnCont, BtnOpen, BtnED, BtnDelete}
  116. End If
  117. End Sub
  118. ''' <summary>
  119. ''' 刷新结果显示。
  120. ''' </summary>
  121. Private Sub RefreshResult(Mods As List(Of McMod))
  122. PanList.Children.Clear()
  123. For Each TargetMod In Mods
  124. PanList.Children.Add(ModItems(TargetMod.RawFileName))
  125. Next
  126. RefreshTitle()
  127. End Sub
  128. ''' <summary>
  129. ''' 刷新卡片标题。
  130. ''' </summary>
  131. Private Sub RefreshTitle()
  132. Dim Mods = PanList.Children.Cast(Of MyLocalModItem).Select(Function(i) i.Entry).ToList
  133. Dim Counter = {0, 0, 0}
  134. For Each ModEntity As McMod In Mods
  135. Counter(ModEntity.State) += 1
  136. Next
  137. Dim TypeList As New List(Of String)
  138. If Counter(McMod.McModState.Disabled) > 0 Then TypeList.Add("禁用 " & Counter(McMod.McModState.Disabled))
  139. If Counter(McMod.McModState.Unavaliable) > 0 Then TypeList.Add("错误 " & Counter(McMod.McModState.Unavaliable))
  140. If Counter(McMod.McModState.Fine) > 0 Then TypeList.Insert(0, If(TypeList.Any, "启用 ", "") & Counter(McMod.McModState.Fine))
  141. If Not IsSearching Then
  142. PanListBack.Title = "Mod 列表 (" & Join(TypeList, ",") & ")"
  143. ElseIf TypeList.Any() Then
  144. PanListBack.Title = "搜索结果 (" & Join(TypeList, ",") & ")"
  145. Else
  146. PanListBack.Title = "无搜索结果"
  147. End If
  148. PanList.Visibility = If(Mods.Any(), Visibility.Visible, Visibility.Collapsed)
  149. End Sub
  150. #End Region
  151. #Region "管理"
  152. ''' <summary>
  153. ''' 打开 Mods 文件夹。
  154. ''' </summary>
  155. Private Sub BtnManageOpen_Click(sender As Object, e As EventArgs) Handles BtnManageOpen.Click, BtnHintOpen.Click
  156. Try
  157. Directory.CreateDirectory(PageVersionLeft.Version.PathIndie & "mods\")
  158. OpenExplorer("""" & PageVersionLeft.Version.PathIndie & "mods\""")
  159. Catch ex As Exception
  160. Log(ex, "打开 Mods 文件夹失败", LogLevel.Msgbox)
  161. End Try
  162. End Sub
  163. #If DEBUG Then
  164. ''' <summary>
  165. ''' 检查 Mod。
  166. ''' </summary>
  167. Private Sub BtnManageCheck_Click(sender As Object, e As EventArgs) Handles BtnManageCheck.Click
  168. Try
  169. Dim Result = McModCheck(PageVersionLeft.Version, McModLoader.Output)
  170. If Result.Count > 0 Then
  171. MyMsgBox(Join(Result, vbCrLf & vbCrLf), "Mod 检查结果")
  172. Else
  173. Hint("Mod 检查完成,未发现任何问题!", HintType.Finish)
  174. End If
  175. Catch ex As Exception
  176. Log(ex, "进行 Mod 检查时出错", LogLevel.Feedback)
  177. End Try
  178. End Sub
  179. #End If
  180. ''' <summary>
  181. ''' 全选。
  182. ''' </summary>
  183. Private Sub BtnManageSelectAll_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnManageSelectAll.Click
  184. If SelectedMods.Count < PanList.Children.Count Then
  185. ChangeAllSelected(True)
  186. Else
  187. ChangeAllSelected(False)
  188. End If
  189. End Sub
  190. ''' <summary>
  191. ''' 安装 Mod。
  192. ''' </summary>
  193. Private Sub BtnManageInstall_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnManageInstall.Click
  194. Hint("将 Mod 文件直接拖入 PCL 窗口即可安装!")
  195. End Sub
  196. #End Region
  197. #Region "选择"
  198. '选择的 Mod 的路径(不含 .disabled)
  199. Public SelectedMods As New List(Of String)
  200. '单项切换选择状态
  201. Public Sub CheckChanged(sender As MyLocalModItem, e As RouteEventArgs)
  202. If AniControlEnabled <> 0 Then Return
  203. '更新选择了的内容
  204. Dim SelectedKey As String = sender.Entry.RawFileName
  205. If sender.Checked Then
  206. If Not SelectedMods.Contains(SelectedKey) Then SelectedMods.Add(SelectedKey)
  207. Else
  208. SelectedMods.Remove(SelectedKey)
  209. End If
  210. '更新下边栏 UI
  211. RefreshBottomBar()
  212. End Sub
  213. '改变下边栏状态
  214. Private ShownCount As Integer = 0
  215. Private Sub RefreshBottomBar()
  216. '计数
  217. Dim NewCount As Integer = SelectedMods.Count
  218. Dim Selected = NewCount > 0
  219. If Selected Then LabSelect.Text = $"已选择 {NewCount} 个文件" '取消所有选择时不更新数字
  220. '按钮可用性
  221. If Selected Then
  222. Dim HasEnabled As Boolean = False
  223. Dim HasDisabled As Boolean = False
  224. For Each ModEntity In McModLoader.Output
  225. If SelectedMods.Contains(ModEntity.RawFileName) Then
  226. If ModEntity.State = McMod.McModState.Fine Then
  227. HasEnabled = True
  228. ElseIf ModEntity.State = McMod.McModState.Disabled Then
  229. HasDisabled = True
  230. End If
  231. End If
  232. Next
  233. BtnSelectDisable.IsEnabled = HasEnabled
  234. BtnSelectEnable.IsEnabled = HasDisabled
  235. End If
  236. '更新显示状态
  237. CardSelect.IsHitTestVisible = Selected
  238. If AniControlEnabled = 0 Then
  239. If Selected Then
  240. '仅在数量增加时播放出现/跳跃动画
  241. If ShownCount >= NewCount Then
  242. ShownCount = NewCount
  243. Return
  244. Else
  245. ShownCount = NewCount
  246. End If
  247. '出现/跳跃动画
  248. CardSelect.Visibility = Visibility.Visible
  249. AniStart({
  250. AaOpacity(CardSelect, 1 - CardSelect.Opacity, 60),
  251. AaTranslateY(CardSelect, -27 - TransSelect.Y, 120, Ease:=New AniEaseOutFluent(AniEasePower.Weak)),
  252. AaTranslateY(CardSelect, 3, 150, 120, Ease:=New AniEaseInoutFluent(AniEasePower.Weak)),
  253. AaTranslateY(CardSelect, -1, 90, 270, Ease:=New AniEaseInoutFluent(AniEasePower.Weak))
  254. }, "Mod Sidebar")
  255. Else
  256. '不重复播放隐藏动画
  257. If ShownCount = 0 Then Return
  258. ShownCount = 0
  259. '隐藏动画
  260. AniStart({
  261. AaOpacity(CardSelect, -CardSelect.Opacity, 90),
  262. AaTranslateY(CardSelect, -10 - TransSelect.Y, 90, Ease:=New AniEaseInFluent(AniEasePower.Weak)),
  263. AaCode(Sub() CardSelect.Visibility = Visibility.Collapsed, After:=True)
  264. }, "Mod Sidebar")
  265. End If
  266. Else
  267. AniStop("Mod Sidebar")
  268. ShownCount = NewCount
  269. If Selected Then
  270. CardSelect.Visibility = Visibility.Visible
  271. CardSelect.Opacity = 1
  272. TransSelect.Y = -25
  273. Else
  274. CardSelect.Visibility = Visibility.Collapsed
  275. CardSelect.Opacity = 0
  276. TransSelect.Y = -10
  277. End If
  278. End If
  279. End Sub
  280. '切换所有项的选择状态
  281. Private Sub ChangeAllSelected(Value As Boolean)
  282. AniControlEnabled += 1
  283. SelectedMods.Clear()
  284. For Each Item As MyLocalModItem In PanList.Children
  285. Item.Checked = Value
  286. If Value Then SelectedMods.Add(Item.Entry.RawFileName)
  287. Next
  288. AniControlEnabled -= 1
  289. '更新下边栏 UI
  290. RefreshBottomBar()
  291. End Sub
  292. Private Sub UnselectedAllWithAnimation() Handles Load.StateChanged, Me.PageExit
  293. Dim CacheAniControlEnabled = AniControlEnabled
  294. AniControlEnabled = 0
  295. ChangeAllSelected(False)
  296. AniControlEnabled += CacheAniControlEnabled
  297. End Sub
  298. Private Sub PageVersionMod_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
  299. If My.Computer.Keyboard.CtrlKeyDown AndAlso e.Key = Key.A Then ChangeAllSelected(True)
  300. End Sub
  301. #End Region
  302. #Region "下边栏"
  303. '启用 / 禁用
  304. Private Sub BtnSelectED_Click(sender As MyIconTextButton, e As RouteEventArgs) Handles BtnSelectEnable.Click, BtnSelectDisable.Click
  305. EDMods(McModLoader.Output.Where(Function(m) SelectedMods.Contains(m.RawFileName)),
  306. Not sender.Equals(BtnSelectDisable))
  307. ChangeAllSelected(False)
  308. End Sub
  309. Private Sub EDMods(ModList As IEnumerable(Of McMod), IsEnable As Boolean)
  310. Dim IsSuccessful As Boolean = True
  311. For Each ModE In ModList.ToList
  312. Dim ModEntity = ModE '仅用于去除迭代变量无法修改的限制
  313. Dim NewPath As String = Nothing
  314. If ModEntity.State = McMod.McModState.Fine AndAlso Not IsEnable Then
  315. '禁用
  316. NewPath = ModEntity.Path & If(File.Exists(ModEntity.Path & ".old"), ".old", ".disabled")
  317. ElseIf ModEntity.State = McMod.McModState.Disabled AndAlso IsEnable Then
  318. '启用
  319. NewPath = ModEntity.RawPath
  320. Else
  321. Continue For
  322. End If
  323. '重命名
  324. Try
  325. If File.Exists(NewPath) Then
  326. If File.Exists(ModEntity.Path) Then
  327. '同时存在两个名称的 Mod
  328. If GetFileMD5(ModEntity.Path) <> GetFileMD5(NewPath) Then
  329. MyMsgBox($"目前同时存在启用和禁用的两个 Mod 文件:{vbCrLf} - {NewPath}{vbCrLf} - {ModEntity.Path}{vbCrLf}{vbCrLf}注意,这两个文件的内容并不相同。{vbCrLf}在手动删除或重命名其中一个文件后,才能继续操作。", "存在文件冲突")
  330. Continue For
  331. End If
  332. Else
  333. '已经重命名过了
  334. Log("[Mod] Mod 的状态已被切换", LogLevel.Debug)
  335. Continue For
  336. End If
  337. End If
  338. File.Delete(NewPath)
  339. FileSystem.Rename(ModEntity.Path, NewPath)
  340. Catch ex As FileNotFoundException
  341. Log(ex, $"未找到需要重命名的 Mod({If(ModEntity.Path, "null")})", LogLevel.Feedback)
  342. RefreshList(True)
  343. Return
  344. Catch ex As Exception
  345. Log(ex, $"重命名 Mod 失败({If(ModEntity.Path, "null")})")
  346. IsSuccessful = False
  347. End Try
  348. '更改 Loader 中的列表
  349. Dim NewModEntity As New McMod(NewPath)
  350. NewModEntity.Comp = ModEntity.Comp
  351. Dim IndexOfLoader As Integer = McModLoader.Output.IndexOf(ModEntity)
  352. McModLoader.Output.RemoveAt(IndexOfLoader)
  353. McModLoader.Output.Insert(IndexOfLoader, NewModEntity)
  354. '更改 UI 中的列表
  355. Dim NewItem As MyLocalModItem = McModListItem(NewModEntity)
  356. ModItems(ModEntity.RawFileName) = NewItem
  357. Dim IndexOfUi As Integer = PanList.Children.IndexOf(PanList.Children.OfType(Of MyLocalModItem).FirstOrDefault(Function(i) i.Entry Is ModEntity))
  358. If IndexOfUi = -1 Then Continue For '因为未知原因 Mod 的状态已经切换完了
  359. PanList.Children.RemoveAt(IndexOfUi)
  360. PanList.Children.Insert(IndexOfUi, NewItem)
  361. Next
  362. RefreshTitle() '改变数量显示
  363. If Not IsSuccessful Then
  364. Hint("由于文件被占用,Mod 的状态切换失败,请尝试关闭正在运行的游戏后再试!", HintType.Critical)
  365. RefreshList(True)
  366. Else
  367. RefreshBottomBar()
  368. End If
  369. End Sub
  370. '删除
  371. Private Sub BtnSelectDelete_Click() Handles BtnSelectDelete.Click
  372. Dim DeleteList As List(Of McMod) = McModLoader.Output.Where(Function(m) SelectedMods.Contains(m.RawFileName)).ToList()
  373. DeleteMods(DeleteList)
  374. End Sub
  375. Private Sub DeleteMods(ModList As IEnumerable(Of McMod))
  376. Try
  377. Dim IsSuccessful As Boolean = True
  378. Dim IsShiftPressed As Boolean = My.Computer.Keyboard.ShiftKeyDown
  379. '确认需要删除的文件
  380. ModList = ModList.SelectMany(
  381. Function(Target As McMod)
  382. If Target.State = McMod.McModState.Fine Then
  383. Return {Target.Path, Target.Path & If(File.Exists(Target.Path & ".old"), ".old", ".disabled")}
  384. Else
  385. Return {Target.Path, Target.RawPath}
  386. End If
  387. End Function).Distinct.Where(Function(m) File.Exists(m)).Select(Function(m) New McMod(m)).ToList()
  388. '实际删除文件
  389. For Each ModEntity In ModList
  390. '删除
  391. Try
  392. If IsShiftPressed Then
  393. File.Delete(ModEntity.Path)
  394. Else
  395. My.Computer.FileSystem.DeleteFile(ModEntity.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
  396. End If
  397. Catch ex As OperationCanceledException
  398. Log(ex, "删除 Mod 被主动取消")
  399. RefreshList(True)
  400. Return
  401. Catch ex As Exception
  402. Log(ex, $"删除 Mod 失败({ModEntity.Path})", LogLevel.Msgbox)
  403. IsSuccessful = False
  404. End Try
  405. '取消选中
  406. SelectedMods.Remove(ModEntity.RawFileName)
  407. '更改 Loader 和 UI 中的列表
  408. McModLoader.Output.Remove(ModEntity)
  409. ModItems.Remove(ModEntity.RawFileName)
  410. Dim IndexOfUi As Integer = PanList.Children.IndexOf(PanList.Children.OfType(Of MyLocalModItem).FirstOrDefault(Function(i) i.Entry.Equals(ModEntity)))
  411. If IndexOfUi >= 0 Then PanList.Children.RemoveAt(IndexOfUi)
  412. Next
  413. RefreshTitle()
  414. If Not IsSuccessful Then
  415. Hint("由于文件被占用,Mod 删除失败,请尝试关闭正在运行的游戏后再试!", HintType.Critical)
  416. RefreshList(True)
  417. ElseIf PanList.Children.Count = 0 Then
  418. RefreshList(True) '删除了全部文件
  419. Else
  420. RefreshBottomBar()
  421. End If
  422. '显示结果提示
  423. If Not IsSuccessful Then Exit Sub
  424. If IsShiftPressed Then
  425. If ModList.Count = 1 Then
  426. Hint($"已彻底删除 {ModList.Single.FileName}!", HintType.Finish)
  427. Else
  428. Hint($"已彻底删除 {ModList.Count} 个文件!", HintType.Finish)
  429. End If
  430. Else
  431. If ModList.Count = 1 Then
  432. Hint($"已将 {ModList.Single.FileName} 删除到回收站!", HintType.Finish)
  433. Else
  434. Hint($"已将 {ModList.Count} 个文件删除到回收站!", HintType.Finish)
  435. End If
  436. End If
  437. Catch ex As OperationCanceledException
  438. Log(ex, "删除 Mod 被主动取消")
  439. RefreshList(True)
  440. Catch ex As Exception
  441. Log(ex, "删除 Mod 出现未知错误", LogLevel.Feedback)
  442. RefreshList(True)
  443. End Try
  444. End Sub
  445. '取消选择
  446. Private Sub BtnSelectCancel_Click() Handles BtnSelectCancel.Click
  447. ChangeAllSelected(False)
  448. End Sub
  449. #End Region
  450. #Region "单个 Mod 项"
  451. '详情
  452. Public Sub Info_Click(sender As Object, e As EventArgs)
  453. Try
  454. Dim ModEntry As McMod = CType(If(TypeOf sender Is MyIconButton, sender.Tag, sender), MyLocalModItem).Entry
  455. '加载失败信息
  456. If ModEntry.State = McMod.McModState.Unavaliable Then
  457. MyMsgBox("无法读取此 Mod 的信息。" & vbCrLf & vbCrLf & "详细的错误信息:" & GetExceptionDetail(ModEntry.FileUnavailableReason), "Mod 读取失败")
  458. Return
  459. End If
  460. If ModEntry.Comp IsNot Nothing Then
  461. '跳转到 Mod 下载页面
  462. FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.CompDetail,
  463. .Additional = {ModEntry.Comp, New List(Of String), PageVersionLeft.Version.Version.McName,
  464. If(PageVersionLeft.Version.Version.HasForge, CompModLoaderType.Forge, If(PageVersionLeft.Version.Version.HasFabric, CompModLoaderType.Fabric, CompModLoaderType.Any))}})
  465. Else
  466. '获取信息
  467. Dim ContentLines As New List(Of String)
  468. If ModEntry.Description IsNot Nothing Then ContentLines.Add(ModEntry.Description & vbCrLf)
  469. If ModEntry.Authors IsNot Nothing Then ContentLines.Add("作者:" & ModEntry.Authors)
  470. ContentLines.Add("文件:" & ModEntry.FileName & "(" & GetString(New FileInfo(ModEntry.Path).Length) & ")")
  471. If ModEntry.Version IsNot Nothing Then ContentLines.Add("版本:" & ModEntry.Version)
  472. Dim DebugInfo As New List(Of String)
  473. If ModEntry.ModId IsNot Nothing Then
  474. DebugInfo.Add("Mod ID:" & ModEntry.ModId)
  475. End If
  476. If ModEntry.Dependencies.Count > 0 Then
  477. DebugInfo.Add("依赖于:")
  478. For Each Dep In ModEntry.Dependencies
  479. DebugInfo.Add(" - " & Dep.Key & If(Dep.Value Is Nothing, "", ",版本:" & Dep.Value))
  480. Next
  481. End If
  482. If DebugInfo.Count > 0 Then
  483. ContentLines.Add("")
  484. ContentLines.AddRange(DebugInfo)
  485. End If
  486. '获取用于搜索的 Mod 名称
  487. Dim ModOriginalName As String = ModEntry.Name.Replace(" ", "+")
  488. Dim ModSearchName As String = ModOriginalName.Substring(0, 1)
  489. For i = 1 To ModOriginalName.Count - 1
  490. Dim IsLastLower As Boolean = ModOriginalName(i - 1).ToString.ToLower.Equals(ModOriginalName(i - 1).ToString)
  491. Dim IsCurrentLower As Boolean = ModOriginalName(i).ToString.ToLower.Equals(ModOriginalName(i).ToString)
  492. If IsLastLower AndAlso Not IsCurrentLower Then
  493. '上一个字母为小写,这一个字母为大写
  494. ModSearchName += "+"
  495. End If
  496. ModSearchName += ModOriginalName(i)
  497. Next
  498. ModSearchName = ModSearchName.Replace("++", "+").Replace("pti+Fine", "ptiFine")
  499. '显示
  500. If ModEntry.Url Is Nothing Then
  501. If MyMsgBox(Join(ContentLines, vbCrLf), ModEntry.Name, "百科搜索", "返回") = 1 Then
  502. OpenWebsite("https://www.mcmod.cn/s?key=" & ModSearchName & "&site=all&filter=0")
  503. End If
  504. Else
  505. Select Case MyMsgBox(Join(ContentLines, vbCrLf), ModEntry.Name, "打开官网", "百科搜索", "返回")
  506. Case 1
  507. OpenWebsite(ModEntry.Url)
  508. Case 2
  509. OpenWebsite("https://www.mcmod.cn/s?key=" & ModSearchName & "&site=all&filter=0")
  510. End Select
  511. End If
  512. End If
  513. Catch ex As Exception
  514. Log(ex, "获取 Mod 详情失败", LogLevel.Feedback)
  515. End Try
  516. End Sub
  517. '打开文件所在的位置
  518. Public Sub Open_Click(sender As MyIconButton, e As EventArgs)
  519. Try
  520. Dim ListItem As MyLocalModItem = sender.Tag
  521. OpenExplorer("/select,""" & ListItem.Entry.Path & """")
  522. Catch ex As Exception
  523. Log(ex, "打开 Mod 文件位置失败", LogLevel.Feedback)
  524. End Try
  525. End Sub
  526. '删除
  527. Public Sub Delete_Click(sender As MyIconButton, e As EventArgs)
  528. Dim ListItem As MyLocalModItem = sender.Tag
  529. DeleteMods({ListItem.Entry})
  530. End Sub
  531. '启用 / 禁用
  532. Public Sub ED_Click(sender As MyIconButton, e As EventArgs)
  533. Dim ListItem As MyLocalModItem = sender.Tag
  534. EDMods({ListItem.Entry}, ListItem.Entry.State = McMod.McModState.Disabled)
  535. End Sub
  536. #End Region
  537. #Region "搜索"
  538. Public ReadOnly Property IsSearching As Boolean
  539. Get
  540. Return Not String.IsNullOrWhiteSpace(SearchBox.Text)
  541. End Get
  542. End Property
  543. Public Sub SearchRun() Handles SearchBox.TextChanged
  544. ChangeAllSelected(False)
  545. If IsSearching Then
  546. '构造请求
  547. Dim QueryList As New List(Of SearchEntry(Of McMod))
  548. For Each Entry As McMod In McModLoader.Output
  549. Dim SearchSource As New List(Of KeyValuePair(Of String, Double))
  550. SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Name, 1))
  551. SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.FileName, 1))
  552. If Entry.Version IsNot Nothing Then
  553. SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Version, 0.2))
  554. End If
  555. If Entry.Description IsNot Nothing AndAlso Entry.Description <> "" Then
  556. SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Description, 0.4))
  557. End If
  558. If Entry.Comp IsNot Nothing Then
  559. If Entry.Comp.RawName <> Entry.Name Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Comp.RawName, 1))
  560. If Entry.Comp.TranslatedName <> Entry.Comp.RawName Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Comp.TranslatedName, 1))
  561. If Entry.Comp.Description <> Entry.Description Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Comp.Description, 0.4))
  562. SearchSource.Add(New KeyValuePair(Of String, Double)(String.Join("", Entry.Comp.Tags), 0.2))
  563. End If
  564. QueryList.Add(New SearchEntry(Of McMod) With {.Item = Entry, .SearchSource = SearchSource})
  565. Next
  566. '进行搜索
  567. Dim SearchResult = Search(QueryList, SearchBox.Text, MaxBlurCount:=6, MinBlurSimilarity:=0.35)
  568. RefreshResult(SearchResult.Select(Function(r) r.Item).ToList)
  569. Else
  570. '退出搜索状态
  571. RefreshResult(McModLoader.Output)
  572. End If
  573. End Sub
  574. #End Region
  575. End Class