初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
<Grid x:Class="PCL.MyCompItem"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL"
|
||||
xmlns:svgIcon="clr-namespace:PCL.Core.UI.Controls.SvgIcon;assembly=PCL.Core"
|
||||
x:Name="PanBack" Height="64"
|
||||
RenderTransformOrigin="0.5,0.5" Background="{StaticResource ColorBrushSemiTransparent}"
|
||||
SnapsToDevicePixels="True">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="7" />
|
||||
<ColumnDefinition Width="50" />
|
||||
<ColumnDefinition Width="8" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="1*" />
|
||||
<RowDefinition Height="20" />
|
||||
<RowDefinition Height="18" />
|
||||
<RowDefinition Height="16" />
|
||||
<RowDefinition Height="1.1*" />
|
||||
</Grid.RowDefinitions>
|
||||
<!-- Logo -->
|
||||
<Border Grid.Column="1" Grid.Row="1" Grid.RowSpan="3" IsHitTestVisible="False" SnapsToDevicePixels="True"
|
||||
UseLayoutRounding="True"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center" Width="50" Height="50">
|
||||
<Border.Clip>
|
||||
<RectangleGeometry Rect="0,0,50,50" RadiusX="6" RadiusY="6" />
|
||||
</Border.Clip>
|
||||
<local:MyImage x:Name="PathLogo" RenderOptions.BitmapScalingMode="HighQuality"
|
||||
FallbackSource="pack://application:,,,/images/Icons/NoIcon.png" />
|
||||
</Border>
|
||||
<!-- 标题 -->
|
||||
<StackPanel Grid.Column="3" Grid.ColumnSpan="2" Grid.Row="1" VerticalAlignment="Center" Orientation="Horizontal"
|
||||
IsHitTestVisible="False" SnapsToDevicePixels="False" UseLayoutRounding="False">
|
||||
<TextBlock x:Name="LabTitle" TextTrimming="WordEllipsis" FontSize="14"
|
||||
Foreground="{DynamicResource ColorBrush1}" />
|
||||
<TextBlock x:Name="LabTitleRaw" TextTrimming="WordEllipsis" FontSize="12"
|
||||
Foreground="{StaticResource ColorBrushGray1}" Opacity="0.4" VerticalAlignment="Center"
|
||||
Visibility="Collapsed" />
|
||||
</StackPanel>
|
||||
<!-- Tag 与 详情 -->
|
||||
<StackPanel x:Name="PanTags" Grid.Row="2" Grid.Column="3" Orientation="Horizontal" VerticalAlignment="Center"
|
||||
Margin="-1,0,1,0" Visibility="Collapsed">
|
||||
<!--<corelocal:BlurBorder Background="{DynamicResource ColorBrush6}" Padding="3,1" CornerRadius="3" Margin="0,0,4,0" SnapsToDevicePixels="True" UseLayoutRounding="False">
|
||||
<TextBlock Text="科技" Foreground="{DynamicResource ColorBrush2}" FontSize="11" />
|
||||
</corelocal:BlurBorder>-->
|
||||
</StackPanel>
|
||||
<TextBlock x:Name="LabInfo" Grid.Row="2" Grid.Column="4" VerticalAlignment="Center" Margin="0,0,3,0.5"
|
||||
TextTrimming="CharacterEllipsis" FontSize="12" Foreground="{StaticResource ColorBrushGray3}"
|
||||
IsHitTestVisible="True" SnapsToDevicePixels="False" UseLayoutRounding="False"
|
||||
ToolTipService.InitialShowDelay="100" ToolTipService.BetweenShowDelay="100"
|
||||
ToolTipService.Placement="Relative" ToolTipService.PlacementRectangle="-16,-10,500,10000">
|
||||
<TextBlock.ToolTip>
|
||||
<ToolTip x:Name="ToolTipInfo" />
|
||||
</TextBlock.ToolTip>
|
||||
</TextBlock>
|
||||
<!-- 下边栏 -->
|
||||
<Grid Grid.Row="3" Grid.Column="3" Grid.ColumnSpan="2" VerticalAlignment="Center"
|
||||
IsHitTestVisible="False" SnapsToDevicePixels="False" UseLayoutRounding="False">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition x:Name="ColumnVersion1" Width="Auto" />
|
||||
<ColumnDefinition x:Name="ColumnVersion2" Width="Auto" SharedSizeGroup="CompItemVersion" />
|
||||
<ColumnDefinition x:Name="ColumnVersion3" Width="0.25*" />
|
||||
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="LabDownload" />
|
||||
<ColumnDefinition Width="0.25*" />
|
||||
|
||||
<ColumnDefinition x:Name="ColumnTime1" Width="Auto" />
|
||||
<ColumnDefinition x:Name="ColumnTime2" Width="Auto" MinWidth="57" SharedSizeGroup="CompItemTime" />
|
||||
<ColumnDefinition x:Name="ColumnTime3" Width="0.25*" />
|
||||
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="CompItemSource" />
|
||||
<ColumnDefinition Width="0.25*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<svgIcon:SvgIcon x:Name="SvgIconVersion"
|
||||
Grid.Column="0"
|
||||
IconBrush="{StaticResource ColorBrushGray3}"
|
||||
Stretch="Uniform"
|
||||
Height="12"
|
||||
Icon="lucide/layout-list" />
|
||||
<TextBlock x:Name="LabVersion"
|
||||
Grid.Column="1"
|
||||
Margin="4,0,2,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{StaticResource ColorBrushGray3}"
|
||||
FontSize="12"
|
||||
TextTrimming="WordEllipsis" />
|
||||
<svgIcon:SvgIcon x:Name="SvgIconDownload"
|
||||
Grid.Column="3"
|
||||
IconBrush="{StaticResource ColorBrushGray4}"
|
||||
Stretch="Uniform"
|
||||
Height="12"
|
||||
Icon="lucide/download" />
|
||||
<TextBlock x:Name="LabDownload"
|
||||
Grid.Column="4"
|
||||
Margin="5,0,2,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{StaticResource ColorBrushGray4}"
|
||||
FontSize="12" />
|
||||
<svgIcon:SvgIcon x:Name="SvgIconTime"
|
||||
Grid.Column="6"
|
||||
IconBrush="{StaticResource ColorBrushGray4}"
|
||||
Stretch="Uniform"
|
||||
Height="12"
|
||||
Margin="0,0,1,0"
|
||||
Icon="lucide/arrow-up-from-line" />
|
||||
<TextBlock x:Name="LabTime"
|
||||
Grid.Column="7"
|
||||
Margin="5,0,2,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{StaticResource ColorBrushGray4}"
|
||||
FontSize="12" />
|
||||
<svgIcon:SvgIcon x:Name="SvgIconSource"
|
||||
Grid.Column="9"
|
||||
IconBrush="{StaticResource ColorBrushGray4}"
|
||||
Stretch="Uniform"
|
||||
Height="12"
|
||||
Icon="lucide/globe" />
|
||||
<TextBlock x:Name="LabSource"
|
||||
Grid.Column="10"
|
||||
Margin="5,0,2,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{StaticResource ColorBrushGray4}"
|
||||
FontSize="12" />
|
||||
</Grid>
|
||||
|
||||
<!-- 右侧按钮组 -->
|
||||
<StackPanel x:Name="PanButtons" Grid.Column="5" Grid.Row="0" Grid.RowSpan="5"
|
||||
Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,5,0" SnapsToDevicePixels="False" UseLayoutRounding="False" Opacity="0">
|
||||
|
||||
<!-- 快速下载按钮 -->
|
||||
<local:MyIconButton x:Name="BtnDownload" Height="25" Width="25" SvgIcon="lucide/download"
|
||||
Margin="0,0,4,0" Visibility="Collapsed"
|
||||
ToolTip="{DynamicResource Download.Comp.QuickDownload.Btn.ToolTip}" />
|
||||
|
||||
<!-- 删除收藏按钮 -->
|
||||
<local:MyIconButton x:Name="BtnDelete" Height="25" Width="25" SvgIcon="lucide/heart" />
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
@@ -0,0 +1,467 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
public partial class MyCompItem
|
||||
{
|
||||
private string stateLast;
|
||||
|
||||
/// <summary>
|
||||
/// 是否允许交互。目前仅用于 PageDownloadCompDetail 的顶部栏展示:若关闭碰撞检测,则无法展开 Tooltip。
|
||||
/// </summary>
|
||||
public bool CanInteraction { get; set; } = true;
|
||||
|
||||
public void RefreshColor(object sender, EventArgs e)
|
||||
{
|
||||
if (!CanInteraction)
|
||||
return;
|
||||
// 判断当前颜色
|
||||
string stateNew;
|
||||
int time;
|
||||
if (IsMouseOver)
|
||||
{
|
||||
if (isMouseDown)
|
||||
{
|
||||
stateNew = "MouseDown";
|
||||
time = 120;
|
||||
}
|
||||
else
|
||||
{
|
||||
stateNew = "MouseOver";
|
||||
time = 120;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stateNew = "Idle";
|
||||
time = 180;
|
||||
}
|
||||
|
||||
if ((stateLast ?? "") == (stateNew ?? ""))
|
||||
return;
|
||||
stateLast = stateNew;
|
||||
// 触发颜色动画
|
||||
if (IsLoaded && ModAnimation.AniControlEnabled == 0) // 防止默认属性变更触发动画
|
||||
{
|
||||
// 有动画
|
||||
var ani = new List<ModAnimation.AniData>();
|
||||
if (IsMouseOver)
|
||||
{
|
||||
if (PanButtons is not null && _HasActionButtons)
|
||||
ani.Add(ModAnimation.AaOpacity(PanButtons, 1d - PanButtons.Opacity, (int)Math.Round(time * 0.35d),
|
||||
(int)Math.Round(time * 0.15d)));
|
||||
ani.AddRange(new[]
|
||||
{
|
||||
ModAnimation.AaColor(RectBack, Border.BackgroundProperty,
|
||||
isMouseDown ? "ColorBrush6" : "ColorBrushBg1", time),
|
||||
ModAnimation.AaOpacity(RectBack, 1d - RectBack.Opacity, time,
|
||||
ease: new ModAnimation.AniEaseOutFluent())
|
||||
});
|
||||
if (isMouseDown)
|
||||
ani.Add(ModAnimation.AaScaleTransform(RectBack,
|
||||
0.996d - ((ScaleTransform)RectBack.RenderTransform).ScaleX, (int)Math.Round(time * 1.2d),
|
||||
ease: new ModAnimation.AniEaseOutFluent()));
|
||||
else
|
||||
ani.Add(ModAnimation.AaScaleTransform(RectBack,
|
||||
1d - ((ScaleTransform)RectBack.RenderTransform).ScaleX, (int)Math.Round(time * 1.2d),
|
||||
ease: new ModAnimation.AniEaseOutFluent()));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (PanButtons is not null && _HasActionButtons)
|
||||
ani.Add(ModAnimation.AaOpacity(PanButtons, -PanButtons.Opacity, (int)Math.Round(time * 0.4d)));
|
||||
ani.AddRange(new[]
|
||||
{
|
||||
ModAnimation.AaOpacity(RectBack, -RectBack.Opacity, time),
|
||||
ModAnimation.AaColor(RectBack, Border.BackgroundProperty,
|
||||
isMouseDown ? "ColorBrush6" : "ColorBrush7", time),
|
||||
ModAnimation.AaScaleTransform(RectBack, 0.996d - ((ScaleTransform)RectBack.RenderTransform).ScaleX,
|
||||
time, ease: new ModAnimation.AniEaseOutFluent()),
|
||||
ModAnimation.AaScaleTransform(RectBack, -0.196d, 1, after: true)
|
||||
});
|
||||
}
|
||||
|
||||
ModAnimation.AniStart(ani, "CompItem Color " + Uuid);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 无动画
|
||||
ModAnimation.AniStop("CompItem Color " + Uuid);
|
||||
if (_RectBack is not null)
|
||||
RectBack.Opacity = 0d;
|
||||
if (PanButtons is not null)
|
||||
PanButtons.Opacity = 0d;
|
||||
}
|
||||
}
|
||||
|
||||
#region 基础属性
|
||||
|
||||
public int Uuid = ModBase.GetUuid();
|
||||
|
||||
// Logo
|
||||
public string Logo
|
||||
{
|
||||
get => PathLogo.Source;
|
||||
set => PathLogo.Source = value;
|
||||
}
|
||||
|
||||
// 标题
|
||||
public string Title
|
||||
{
|
||||
get => LabTitle.Text;
|
||||
set
|
||||
{
|
||||
if ((LabTitle.Text ?? "") == (value ?? ""))
|
||||
return;
|
||||
LabTitle.Text = value;
|
||||
}
|
||||
}
|
||||
|
||||
// 副标题
|
||||
public string SubTitle
|
||||
{
|
||||
get => LabTitleRaw?.Text ?? "";
|
||||
set
|
||||
{
|
||||
if ((LabTitleRaw.Text ?? "") == (value ?? ""))
|
||||
return;
|
||||
LabTitleRaw.Text = value;
|
||||
LabTitleRaw.Visibility = string.IsNullOrEmpty(value) ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
// 描述
|
||||
public string Description
|
||||
{
|
||||
get => LabInfo.Text;
|
||||
set
|
||||
{
|
||||
if ((LabInfo.Text ?? "") == (value ?? ""))
|
||||
return;
|
||||
LabInfo.Text = value;
|
||||
}
|
||||
}
|
||||
|
||||
public MyCompItem()
|
||||
{
|
||||
InitializeComponent();
|
||||
Click += (sender, e) => MyCompItem_Click((MyCompItem)sender, e);
|
||||
PreviewMouseLeftButtonUp += Button_MouseUp;
|
||||
PreviewMouseLeftButtonDown += Button_MouseDown;
|
||||
MouseLeave += Button_MouseLeave;
|
||||
PreviewMouseLeftButtonUp += Button_MouseLeave;
|
||||
MouseEnter += RefreshColor;
|
||||
MouseLeave += RefreshColor;
|
||||
MouseLeftButtonDown += RefreshColor;
|
||||
MouseLeftButtonUp += RefreshColor;
|
||||
// Handles
|
||||
LabInfo.MouseEnter += LabInfo_MouseEnter;
|
||||
BtnDelete.Click += BtnDelete_Click;
|
||||
BtnDownload.Click += _BtnDownload_Click;
|
||||
}
|
||||
|
||||
// 指向时扩展描述
|
||||
private void LabInfo_MouseEnter(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (IsTextTrimmed(LabInfo))
|
||||
{
|
||||
ToolTipInfo.Content = LabInfo.Text;
|
||||
ToolTipInfo.Width = LabInfo.ActualWidth + 25d;
|
||||
LabInfo.ToolTip = ToolTipInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
LabInfo.ToolTip = null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsTextTrimmed(TextBlock textBlock)
|
||||
{
|
||||
var typeface = new Typeface(textBlock.FontFamily, textBlock.FontStyle, textBlock.FontWeight,
|
||||
textBlock.FontStretch);
|
||||
var formattedText = new FormattedText(textBlock.Text, Thread.CurrentThread.CurrentCulture,
|
||||
textBlock.FlowDirection, typeface, textBlock.FontSize, textBlock.Foreground, ModBase.dpi);
|
||||
return formattedText.Width > textBlock.ActualWidth;
|
||||
}
|
||||
|
||||
// Tag
|
||||
public List<string> Tags
|
||||
{
|
||||
set
|
||||
{
|
||||
PanTags.Children.Clear();
|
||||
PanTags.Visibility = value.Any() ? Visibility.Visible : Visibility.Collapsed;
|
||||
foreach (var tagText in value)
|
||||
{
|
||||
var newTag = new Border
|
||||
{
|
||||
Background = new SolidColorBrush(Color.FromArgb(17, 0, 0, 0)),
|
||||
Padding = new Thickness(3d, 1d, 3d, 1d),
|
||||
CornerRadius = new CornerRadius(3d),
|
||||
Margin = new Thickness(0d, 0d, 3d, 0d),
|
||||
SnapsToDevicePixels = true,
|
||||
UseLayoutRounding = false
|
||||
};
|
||||
var tagTextBlock = new TextBlock
|
||||
{
|
||||
Text = tagText,
|
||||
Foreground = new SolidColorBrush(Color.FromRgb(134, 134, 134)),
|
||||
FontSize = 11d
|
||||
};
|
||||
newTag.Child = tagTextBlock;
|
||||
PanTags.Children.Add(newTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ‘收藏按钮
|
||||
public bool ShowFavoriteBtn
|
||||
{
|
||||
get => BtnDelete.Visibility == Visibility.Visible;
|
||||
set
|
||||
{
|
||||
BtnDelete.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
|
||||
_UpdatePanButtons();
|
||||
}
|
||||
}
|
||||
|
||||
// 快速下载按钮
|
||||
public bool ShowDownloadBtn
|
||||
{
|
||||
get => BtnDownload.Visibility == Visibility.Visible;
|
||||
set
|
||||
{
|
||||
BtnDownload.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
|
||||
_UpdatePanButtons();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>右侧是否存在任意可见的操作按钮(收藏 / 下载),用于决定悬停时是否淡入按钮区。</summary>
|
||||
private bool _HasActionButtons => ShowFavoriteBtn || ShowDownloadBtn;
|
||||
|
||||
private void _UpdatePanButtons()
|
||||
{
|
||||
if (PanButtons is null) return;
|
||||
PanButtons.Visibility = _HasActionButtons ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新收藏状态(仅更新图标,不影响 hover 展示逻辑)
|
||||
/// </summary>
|
||||
public void RefreshFavoriteStatus()
|
||||
{
|
||||
if (Tag is not ModComp.CompProject project) return;
|
||||
|
||||
var isFavourite = ModComp.CompFavorites.IsFavourite(project.Id);
|
||||
BtnDelete.SvgIcon = isFavourite ? "lucide/heart-filled" : "lucide/heart";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 点击
|
||||
|
||||
// 触发点击事件
|
||||
public event ClickEventHandler? Click;
|
||||
|
||||
public delegate void ClickEventHandler(object sender, MouseButtonEventArgs e);
|
||||
|
||||
private void BtnDelete_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (PanButtons.Opacity > 0d && Tag is ModComp.CompProject)
|
||||
{
|
||||
var project = (ModComp.CompProject)Tag;
|
||||
ModComp.CompFavorites.ShowMenu(project, (UIElement)sender, () => RefreshFavoriteStatus());
|
||||
}
|
||||
}
|
||||
|
||||
private void _BtnDownload_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (PanButtons.Opacity > 0d && Tag is ModComp.CompProject project)
|
||||
ModComp.QuickDownload(project);
|
||||
}
|
||||
|
||||
private void MyCompItem_Click(MyCompItem sender, EventArgs e)
|
||||
{
|
||||
// 记录当前展开的卡片标题(#2712)
|
||||
var titles = new List<string>();
|
||||
if (ModMain.frmMain.pageCurrent.page == FormMain.PageType.CompDetail)
|
||||
{
|
||||
foreach (MyCard Card in ModMain.frmDownloadCompDetail.PanResults.Children)
|
||||
if (!string.IsNullOrEmpty(Card.Title) && !Card.IsSwapped)
|
||||
titles.Add(Card.Title);
|
||||
ModBase.Log("[Comp] 记录当前已展开的卡片:" + string.Join("、", titles));
|
||||
var additional = ModMain.frmMain.pageCurrent.additional.Value;
|
||||
ModMain.frmMain.pageCurrent.additional = additional with { ExpandedTitles = titles };
|
||||
}
|
||||
|
||||
// 打开详情页
|
||||
var targetType = default(ModComp.CompType);
|
||||
string targetVersion = null;
|
||||
var targetLoader = ModComp.CompLoaderType.Any;
|
||||
if (ModMain.frmMain.pageCurrent.page == FormMain.PageType.Download)
|
||||
{
|
||||
if (ModMain.frmMain.PageCurrentSub == FormMain.PageSubType.DownloadCompFavorites)
|
||||
{
|
||||
targetVersion = "";
|
||||
targetLoader = ModComp.CompLoaderType.Any;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 从下载页进入
|
||||
switch (ModMain.frmMain.PageCurrentSub)
|
||||
{
|
||||
case FormMain.PageSubType.DownloadMod:
|
||||
{
|
||||
targetType = ModComp.CompType.Mod;
|
||||
targetVersion = ModMain.frmDownloadMod.Content.loader.input.gameVersion;
|
||||
targetLoader = ModMain.frmDownloadMod.Content.loader.input.modLoader;
|
||||
break;
|
||||
}
|
||||
case FormMain.PageSubType.DownloadPack:
|
||||
{
|
||||
targetType = ModComp.CompType.ModPack;
|
||||
targetVersion = ModMain.frmDownloadPack.Content.loader.input.gameVersion;
|
||||
break;
|
||||
}
|
||||
case FormMain.PageSubType.DownloadDataPack:
|
||||
{
|
||||
targetType = ModComp.CompType.DataPack;
|
||||
targetVersion = ModMain.frmDownloadDataPack.Content.loader.input.gameVersion;
|
||||
break;
|
||||
}
|
||||
case FormMain.PageSubType.DownloadResourcePack:
|
||||
{
|
||||
targetType = ModComp.CompType.ResourcePack;
|
||||
targetVersion = ModMain.frmDownloadResourcePack.Content.loader.input.gameVersion;
|
||||
break;
|
||||
}
|
||||
case FormMain.PageSubType.DownloadShader:
|
||||
{
|
||||
targetType = ModComp.CompType.Shader;
|
||||
targetVersion = ModMain.frmDownloadShader.Content.loader.input.gameVersion;
|
||||
break;
|
||||
}
|
||||
case FormMain.PageSubType.DownloadWorld:
|
||||
{
|
||||
targetType = ModComp.CompType.World;
|
||||
targetVersion = ModMain.frmDownloadWorld.Content.loader.input.gameVersion;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ModMain.frmMain.pageCurrent.page == FormMain.PageType.InstanceSetup)
|
||||
{
|
||||
// 从实例设置页进入(查看整合包信息)
|
||||
targetType = ModComp.CompType.ModPack;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 从详情页进入(查看前置)
|
||||
targetType = ModComp.CompType.Any; // 允许任意类别
|
||||
var additional = ModMain.frmMain.pageCurrent.additional.Value;
|
||||
targetVersion = additional.TargetVersion;
|
||||
targetLoader = additional.TargetLoader;
|
||||
}
|
||||
|
||||
ModMain.frmMain.PageChange(new FormMain.PageStackData
|
||||
{
|
||||
page = FormMain.PageType.CompDetail,
|
||||
additional = ((ModComp.CompProject)sender.Tag, new List<string>(), targetVersion, targetLoader, targetType, null)
|
||||
});
|
||||
}
|
||||
|
||||
// 鼠标点击判定
|
||||
private bool isMouseDown;
|
||||
|
||||
// 触发点击事件
|
||||
private void Button_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!isMouseDown)
|
||||
return;
|
||||
Click?.Invoke(sender, e);
|
||||
}
|
||||
|
||||
private void Button_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!CanInteraction)
|
||||
return;
|
||||
// 检查点击位置是否在按钮区域内
|
||||
var clickPosition = e.GetPosition(this);
|
||||
var isClickOnButton = false;
|
||||
|
||||
if (PanButtons.Visibility == Visibility.Visible)
|
||||
isClickOnButton = _IsClickOnActionButton(BtnDelete, clickPosition) ||
|
||||
_IsClickOnActionButton(BtnDownload, clickPosition);
|
||||
|
||||
// 如果点击在按钮上,不处理主项目点击事件
|
||||
if (isClickOnButton) return;
|
||||
|
||||
// 如果点击在其他区域,按原逻辑处理
|
||||
// 也要检查是否点击在LabInfo区域(支持ToolTip点击)
|
||||
var isClickOnLabInfo = false;
|
||||
if (LabInfo.Visibility == Visibility.Visible)
|
||||
{
|
||||
var labInfoBounds = new Rect(LabInfo.TranslatePoint(new Point(0d, 0d), this), LabInfo.RenderSize);
|
||||
isClickOnLabInfo = labInfoBounds.Contains(clickPosition);
|
||||
}
|
||||
|
||||
if (IsMouseDirectlyOver || isClickOnLabInfo) isMouseDown = true;
|
||||
}
|
||||
|
||||
private void Button_MouseLeave(object sender, object e)
|
||||
{
|
||||
isMouseDown = false;
|
||||
}
|
||||
|
||||
// 判断点击是否落在某个操作按钮(收藏 / 下载)上
|
||||
private bool _IsClickOnActionButton(FrameworkElement button, Point clickPosition)
|
||||
{
|
||||
if (button is null || button.Visibility != Visibility.Visible) return false;
|
||||
var bounds = new Rect(button.TranslatePoint(new Point(0d, 0d), this), button.RenderSize);
|
||||
return bounds.Contains(clickPosition);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 后加载指向背景
|
||||
|
||||
private Border _RectBack;
|
||||
|
||||
public Border RectBack
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_RectBack is null)
|
||||
{
|
||||
var rect = new Border
|
||||
{
|
||||
Name = "RectBack",
|
||||
CornerRadius = new CornerRadius(3d),
|
||||
RenderTransform = new ScaleTransform(0.8d, 0.8d),
|
||||
RenderTransformOrigin = new Point(0.5d, 0.5d),
|
||||
BorderThickness = new Thickness(ModBase.GetWPFSize(1d)),
|
||||
SnapsToDevicePixels = true,
|
||||
IsHitTestVisible = false,
|
||||
Opacity = 0d
|
||||
};
|
||||
rect.SetResourceReference(Border.BackgroundProperty, "ColorBrush7");
|
||||
rect.SetResourceReference(Border.BorderBrushProperty, "ColorBrush6");
|
||||
SetColumnSpan(rect, 999);
|
||||
SetRowSpan(rect, 999);
|
||||
Children.Insert(0, rect);
|
||||
_RectBack = rect;
|
||||
// <!--<corelocal:BlurBorder x:Name = "RectBack" CornerRadius="3" RenderTransformOrigin="0.5,0.5" SnapsToDevicePixels="True"
|
||||
// IsHitTestVisible = "False" Opacity="0" BorderThickness="1"
|
||||
// Grid.ColumnSpan = "4" Background="{DynamicResource ColorBrush7}" BorderBrush="{DynamicResource ColorBrush6}"/>-->
|
||||
}
|
||||
|
||||
return _RectBack;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<local:MyScrollViewer x:Class="PCL.PageComp"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:PCL"
|
||||
mc:Ignorable="d"
|
||||
DataContext="{Binding RelativeSource={RelativeSource Self}}"
|
||||
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<local:MyScrollViewer.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="VisibilityConverter" />
|
||||
</local:MyScrollViewer.Resources>
|
||||
<StackPanel Orientation="Vertical" Margin="25">
|
||||
<StackPanel x:Name="PanAlways">
|
||||
<local:MySearchBox x:Name="PanSearchBox" Margin="0,0,0,15" SearchButtonVisibility="Visible" HintText="{DynamicResource Download.Comp.Search.Hint}" />
|
||||
<local:MyCard Margin="0,0,0,15">
|
||||
<Grid Margin="7" HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1.0*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1.3*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1.0*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="28" />
|
||||
<RowDefinition Height="8" />
|
||||
<RowDefinition Height="28" />
|
||||
</Grid.RowDefinitions>
|
||||
<!--来源-->
|
||||
<TextBlock x:Name="LabSource" VerticalAlignment="Center" Grid.Row="0" Grid.Column="0"
|
||||
HorizontalAlignment="Left" Text="{DynamicResource Download.Comp.Filter.Source}"
|
||||
Margin="5,0,5,0" />
|
||||
<local:MyComboBox x:Name="ComboSearchSource" Grid.Row="0" Grid.Column="1">
|
||||
<local:MyComboBoxItem Content="{DynamicResource Common.Option.All}" Tag="3" IsSelected="True" />
|
||||
<local:MyComboBoxItem Content="CurseForge" Tag="1"
|
||||
Visibility="{Binding SupportCurseForge, Converter={StaticResource VisibilityConverter}}" />
|
||||
<local:MyComboBoxItem Content="Modrinth" Tag="2"
|
||||
Visibility="{Binding SupportModrinth, Converter={StaticResource VisibilityConverter}}" />
|
||||
</local:MyComboBox>
|
||||
<!--Tags-->
|
||||
<TextBlock x:Name="LabTag" VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Left"
|
||||
Text="{DynamicResource Download.Comp.Filter.Tag}" Margin="5,0,5,0" />
|
||||
<local:MyComboBox x:Name="ComboSearchTag" Grid.Column="3" MaxDropDownHeight="320" />
|
||||
<!--排序方式-->
|
||||
<TextBlock x:Name="LabSort" VerticalAlignment="Center" Grid.Row="0" Grid.Column="4"
|
||||
HorizontalAlignment="Left" Text="{DynamicResource Download.Comp.Filter.Sort}"
|
||||
Margin="5,0,5,0" />
|
||||
<local:MyComboBox x:Name="ComboSearchSort" Grid.Row="0" Grid.Column="5" MaxDropDownHeight="320">
|
||||
<local:MyComboBoxItem Content="{DynamicResource Common.Option.Default}" Tag="1"
|
||||
IsSelected="True" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Sort.Relevance}" Tag="2" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Sort.Downloads}" Tag="3" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Sort.Follows}" Tag="4" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Sort.Latest}" Tag="5" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Sort.Updated}" Tag="6" />
|
||||
</local:MyComboBox>
|
||||
<!--版本-->
|
||||
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
Text="{DynamicResource Download.Comp.Filter.Version}" Margin="5,0,5,0"
|
||||
Grid.Row="2" />
|
||||
<local:MyComboBox x:Name="TextSearchVersion" Grid.Row="2" Grid.Column="1" IsEditable="True"
|
||||
MaxDropDownHeight="320">
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Filter.Version.Any}"
|
||||
IsSelected="True" />
|
||||
<local:MyComboBoxItem Content="1.21.11" />
|
||||
<local:MyComboBoxItem Content="1.21.1" />
|
||||
<local:MyComboBoxItem Content="1.20.6" />
|
||||
<local:MyComboBoxItem Content="1.20.1" />
|
||||
<local:MyComboBoxItem Content="1.19.4" />
|
||||
<local:MyComboBoxItem Content="1.19.2" />
|
||||
<local:MyComboBoxItem Content="1.18.2" />
|
||||
<local:MyComboBoxItem Content="1.16.5" />
|
||||
<local:MyComboBoxItem Content="1.12.2" />
|
||||
<local:MyComboBoxItem Content="1.10.2" />
|
||||
<local:MyComboBoxItem Content="1.8.9" />
|
||||
<local:MyComboBoxItem Content="1.7.10" />
|
||||
</local:MyComboBox>
|
||||
<!--加载器-->
|
||||
<TextBlock x:Name="LabLoader" VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
Text="{DynamicResource Download.Comp.Filter.Loader}"
|
||||
Margin="5,0,5,0" Grid.Row="2" Grid.Column="2" />
|
||||
<local:MyComboBox x:Name="ComboSearchLoader" Grid.Row="2" Grid.Column="3" MaxDropDownHeight="320">
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Filter.Loader.Any}"
|
||||
IsSelected="True" Tag="0" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Common.Installation.Forge}" Tag="1" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Common.Installation.NeoForge}" Tag="6" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Common.Installation.Fabric}" Tag="4" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Common.Installation.Quilt}" Tag="5" />
|
||||
</local:MyComboBox>
|
||||
<local:MyComboBox x:Name="ComboSearchShaderLoader" Visibility="Collapsed" Grid.Row="2"
|
||||
Grid.Column="3" MaxDropDownHeight="320">
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Filter.Loader.AnyShader}" Tag=""
|
||||
IsSelected="True" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Filter.Loader.VanillaAvailable}"
|
||||
Tag="/vanilla"
|
||||
ToolTip="{DynamicResource Download.Comp.Filter.Loader.VanillaAvailable.ToolTip}" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Common.Installation.Iris}" Tag="/iris" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Common.Installation.OptiFine}" Tag="/optifine" />
|
||||
</local:MyComboBox>
|
||||
<!--按钮-->
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Column="6">
|
||||
<local:MyIconButton x:Name="BtnSearchReset"
|
||||
SvgIcon="lucide/rotate-ccw"
|
||||
ToolTip="{DynamicResource Download.Comp.Filter.Reset}" LogoScale="0.9"
|
||||
Margin="3,0,0,0" />
|
||||
<local:MyIconButton x:Name="BtnSearchInstallModPack"
|
||||
SvgIcon="lucide/download"
|
||||
Visibility="Collapsed"
|
||||
ToolTip="{DynamicResource Download.Comp.Filter.InstallModpack.ToolTip}"
|
||||
LogoScale="0.9"
|
||||
ToolTipService.HorizontalOffset="-55" ToolTipService.VerticalOffset="5" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
</StackPanel>
|
||||
<StackPanel x:Name="PanContent">
|
||||
<local:MyHint x:Name="HintError" HasBorder="True"
|
||||
Text="{DynamicResource Download.Comp.Hint.ModrinthUnreachable}"
|
||||
Theme="Yellow" Margin="0,0,0,15" />
|
||||
<local:MyCard x:Name="CardProjects" UseAnimation="False">
|
||||
<StackPanel x:Name="PanProjects" Margin="12" />
|
||||
</local:MyCard>
|
||||
<local:MyCard Margin="0,15,0,0" x:Name="CardPages" HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal" Height="23" Margin="10,7">
|
||||
<local:MyIconButton x:Name="BtnPageFirst" IsEnabled="False" Opacity="0.2" Margin="0,0.5,5,0.5"
|
||||
SvgIcon="lucide/chevrons-left" />
|
||||
<local:MyIconButton x:Name="BtnPageLeft" IsEnabled="False" Opacity="0.2" Margin="0,0,5,0"
|
||||
SvgIcon="lucide/chevron-left" />
|
||||
<TextBlock x:Name="LabPage" Text="1"
|
||||
Margin="8,0,13,0" FontSize="15" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource ColorBrush3}" />
|
||||
<local:MyIconButton x:Name="BtnPageRight" Margin="0,0,30,0"
|
||||
SvgIcon="lucide/chevron-right" />
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
</StackPanel>
|
||||
<local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="True"
|
||||
x:Name="PanLoad" UseAnimation="False" Margin="40,50">
|
||||
<local:MyLoading Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</local:MyCard>
|
||||
</StackPanel>
|
||||
</local:MyScrollViewer>
|
||||
@@ -0,0 +1,389 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Markup;
|
||||
using PCL.Core.App.Localization;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
[ContentProperty("SearchTags")]
|
||||
public partial class PageComp
|
||||
{
|
||||
/// <summary>
|
||||
/// 每页展示的结果数量。
|
||||
/// </summary>
|
||||
public const int pageSize = 40;
|
||||
|
||||
public int page;
|
||||
|
||||
public ModComp.CompProjectStorage storage = new();
|
||||
|
||||
// 结果 UI 化
|
||||
private void Load_OnFinish()
|
||||
{
|
||||
try
|
||||
{
|
||||
ModBase.Log($"[Comp] 开始可视化{TypeNameSpaced}列表,已储藏 {storage.results.Count} 个结果,当前在第 {page + 1} 页");
|
||||
// 列表项
|
||||
PanProjects.Children.Clear();
|
||||
var index = Math.Min(page * pageSize, storage.results.Count - 1);
|
||||
foreach (var result in storage.results.GetRange(index, Math.Min(storage.results.Count - index, pageSize)))
|
||||
{
|
||||
var showQuickDownload = result.Type != ModComp.CompType.ModPack &&
|
||||
result.Type != ModComp.CompType.DataPack;
|
||||
PanProjects.Children.Add(result.ToCompItem(loader.input.gameVersion is null,
|
||||
loader.input.modLoader == ModComp.CompLoaderType.Any &&
|
||||
(PageType == ModComp.CompType.Mod || PageType == ModComp.CompType.ModPack),
|
||||
showQuickDownload));
|
||||
}
|
||||
// 页码
|
||||
CardPages.Visibility =
|
||||
storage.results.Count > 40 || storage.curseForgeOffset < storage.curseForgeTotal ||
|
||||
storage.modrinthOffset < storage.modrinthTotal
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
LabPage.Text = Lang.Number(page + 1, "N0");
|
||||
BtnPageFirst.IsEnabled = page > 1;
|
||||
BtnPageFirst.Opacity = page > 1 ? 1d : 0.2d;
|
||||
BtnPageLeft.IsEnabled = page > 0;
|
||||
BtnPageLeft.Opacity = page > 0 ? 1d : 0.2d;
|
||||
var isRightEnabled = storage.results.Count > pageSize * (page + 1) ||
|
||||
storage.curseForgeOffset < storage.curseForgeTotal ||
|
||||
storage.modrinthOffset <
|
||||
storage.modrinthTotal; // 由于 WPF 的未知 bug,读取到的 IsEnabled 可能是错误的值(#3319)
|
||||
BtnPageRight.IsEnabled = isRightEnabled;
|
||||
BtnPageRight.Opacity = isRightEnabled ? 1d : 0.2d;
|
||||
// 错误信息
|
||||
if (storage.errorMessage is null)
|
||||
{
|
||||
HintError.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
HintError.Visibility = Visibility.Visible;
|
||||
HintError.Text = storage.errorMessage;
|
||||
}
|
||||
|
||||
// 强制返回顶部
|
||||
ScrollToTop();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"可视化{TypeNameSpaced}列表出错",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Download.Comp.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 自动重试
|
||||
private void Load_State(object sender, MyLoading.MyLoadingState state, MyLoading.MyLoadingState oldState)
|
||||
{
|
||||
switch (loader.State)
|
||||
{
|
||||
case ModBase.LoadState.Failed:
|
||||
{
|
||||
var errorMessage = "";
|
||||
if (loader.Error is not null)
|
||||
errorMessage = loader.Error.Message;
|
||||
if (errorMessage.Contains(Lang.Text("Common.Error.InvalidJson")))
|
||||
{
|
||||
ModBase.Log($"[Download] 下载的{TypeNameSpaced}列表 json 文件损坏,已自动重试", ModBase.LogLevel.Debug);
|
||||
((MyPageRight)Parent).PageLoaderRestart();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 切换页码
|
||||
private void BtnPageFirst_Click(object sender, EventArgs e)
|
||||
{
|
||||
ChangePage(0);
|
||||
}
|
||||
|
||||
private void BtnPageLeft_Click(object sender, EventArgs e)
|
||||
{
|
||||
ChangePage(page - 1);
|
||||
}
|
||||
|
||||
private void BtnPageRight_Click(object sender, EventArgs e)
|
||||
{
|
||||
ChangePage(page + 1);
|
||||
}
|
||||
|
||||
private void ChangePage(int newPage)
|
||||
{
|
||||
CardPages.IsEnabled = false;
|
||||
page = newPage;
|
||||
ModMain.frmMain.BackToTop();
|
||||
ModBase.Log($"[Download] {TypeName}:切换到第 {page + 1} 页");
|
||||
ModBase.RunInThread(() =>
|
||||
{
|
||||
Thread.Sleep(100); // 等待向上滚的动画结束
|
||||
ModBase.RunInUi(() => CardPages.IsEnabled = true);
|
||||
loader.Start();
|
||||
});
|
||||
}
|
||||
|
||||
// 安装已有整合包按钮
|
||||
private void BtnSearchInstallModPack_Click(object sender, EventArgs e)
|
||||
{
|
||||
ModModpack.ModpackInstall();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新所有已显示项目的收藏状态
|
||||
/// </summary>
|
||||
public void RefreshAllFavoriteStatus()
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var item in PanProjects.Children)
|
||||
if (item is MyCompItem)
|
||||
((MyCompItem)item).RefreshFavoriteStatus();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "刷新收藏状态时出错");
|
||||
}
|
||||
}
|
||||
|
||||
#region 属性
|
||||
|
||||
/// <summary>
|
||||
/// 用于 XAML 快速设置的 Tag 下拉框列表。
|
||||
/// </summary>
|
||||
public ItemCollection SearchTags => ComboSearchTag.Items;
|
||||
|
||||
public static readonly DependencyProperty SupportCurseForgeProperty =
|
||||
DependencyProperty.Register("SupportCurseForge", typeof(bool), typeof(PageComp), new PropertyMetadata(true));
|
||||
|
||||
public bool SupportCurseForge
|
||||
{
|
||||
get => (bool)GetValue(SupportCurseForgeProperty);
|
||||
set => SetValue(SupportCurseForgeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty SupportModrinthProperty =
|
||||
DependencyProperty.Register("SupportModrinth", typeof(bool), typeof(PageComp), new PropertyMetadata(true));
|
||||
|
||||
public bool SupportModrinth
|
||||
{
|
||||
get => (bool)GetValue(SupportModrinthProperty);
|
||||
set => SetValue(SupportModrinthProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 英文前后不含空格的可读资源类型名,例如 "Mod"、"整合包"。
|
||||
/// </summary>
|
||||
public string TypeName => ModComp.GetCompTypeName(PageType);
|
||||
|
||||
/// <summary>
|
||||
/// 英文前后含一个空格的可读资源类型名,例如 " Mod "、"整合包"。
|
||||
/// </summary>
|
||||
public string TypeNameSpaced => TypeName;
|
||||
|
||||
/// <summary>
|
||||
/// 该页面对应的资源类型。
|
||||
/// </summary>
|
||||
public ModComp.CompType PageType
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
if (field == value)
|
||||
return;
|
||||
field = value;
|
||||
BtnSearchInstallModPack.Visibility =
|
||||
value == ModComp.CompType.ModPack ? Visibility.Visible : Visibility.Collapsed;
|
||||
loader.name = Lang.Text("Download.Comp.List.Source.ResourceFetch", TypeName);
|
||||
PanSearchBox.HintText = ModComp.GetCompSearchName(value);
|
||||
Load.Text = ModComp.GetCompLoadingName(value);
|
||||
}
|
||||
} = (ModComp.CompType)(-1);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 加载
|
||||
|
||||
/// <summary>
|
||||
/// 在切换到页面时,应自动将筛选项设置为与该目标 MC 版本和加载器相同。
|
||||
/// </summary>
|
||||
public static McInstance targetVersion;
|
||||
|
||||
// 在点击 MyCompItem 时会获取 Loader 的输入,以使资源详情页面可以应用相同的筛选项
|
||||
public ModLoader.LoaderTask<ModComp.CompProjectRequest, int> loader;
|
||||
|
||||
private bool isLoaderInited;
|
||||
|
||||
public PageComp()
|
||||
{
|
||||
loader = new ModLoader.LoaderTask<ModComp.CompProjectRequest, int>(Lang.Text("Download.Comp.List.Source.ResourceFetch", "XXX"), ModComp.CompProjectsGet,
|
||||
LoaderInput) { reloadTimeout = 60 * 1000 };
|
||||
Loaded += PageCompControls_Inited;
|
||||
IsVisibleChanged += PageComp_IsVisibleChanged;
|
||||
InitializeComponent();
|
||||
Load.StateChanged += Load_State;
|
||||
BtnPageFirst.Click += BtnPageFirst_Click;
|
||||
BtnPageLeft.Click += BtnPageLeft_Click;
|
||||
BtnPageRight.Click += BtnPageRight_Click;
|
||||
PanSearchBox.Search += (_, _) => StartNewSearch();
|
||||
PanSearchBox.KeyDown += EnterTrigger;
|
||||
TextSearchVersion.KeyDown += EnterTrigger;
|
||||
BtnSearchReset.Click += (_, _) => ResetFilter();
|
||||
BtnSearchInstallModPack.Click += BtnSearchInstallModPack_Click;
|
||||
}
|
||||
|
||||
private void PageCompControls_Inited(object sender, EventArgs e)
|
||||
{
|
||||
// 不知道从 Initialized 改成 Loaded 会不会有问题,但用 Initialized 会导致初始的筛选器修改被覆盖回默认值
|
||||
if (targetVersion is not null)
|
||||
{
|
||||
// 设置目标
|
||||
ResetFilter(); // 重置筛选器
|
||||
TextSearchVersion.Text = targetVersion.Info.VanillaName;
|
||||
|
||||
MyComboBoxItem GetTargetItemByName(string name)
|
||||
{
|
||||
foreach (MyComboBoxItem Item in ComboSearchLoader.Items)
|
||||
if (string.Equals(Item.Content?.ToString(), name, StringComparison.OrdinalIgnoreCase))
|
||||
return Item;
|
||||
return (MyComboBoxItem)ComboSearchLoader.Items[0];
|
||||
}
|
||||
|
||||
;
|
||||
if (targetVersion.Info.HasForge)
|
||||
ComboSearchLoader.SelectedItem = GetTargetItemByName("Forge");
|
||||
else if (targetVersion.Info.HasFabric)
|
||||
ComboSearchLoader.SelectedItem = GetTargetItemByName("Fabric");
|
||||
else if (targetVersion.Info.HasNeoForge)
|
||||
ComboSearchLoader.SelectedItem = GetTargetItemByName("NeoForge");
|
||||
else if (targetVersion.Info.HasQuilt) ComboSearchLoader.SelectedItem = GetTargetItemByName("Quilt");
|
||||
targetVersion = null;
|
||||
// 如果已经完成请求,则重新开始
|
||||
if (isLoaderInited)
|
||||
StartNewSearch();
|
||||
ScrollToHome();
|
||||
}
|
||||
|
||||
// 加载器初始化
|
||||
if (isLoaderInited)
|
||||
return;
|
||||
isLoaderInited = true;
|
||||
((MyPageRight)Parent).PageLoaderInit(Load, PanLoad, PanContent, PanAlways, loader, _ => Load_OnFinish(),
|
||||
LoaderInput);
|
||||
// 将最高 Drop 加入筛选
|
||||
if (ModDownload.AllDrops is not null && ModDownload.AllDrops.Count != 0 && ModDownload.AllDrops.First() > 250)
|
||||
{
|
||||
var highestVersion = McInstanceInfo.DropToVersion(ModDownload.AllDrops.First());
|
||||
if ((((MyComboBoxItem)TextSearchVersion.Items[1]).Content.ToString() ?? "") !=
|
||||
(highestVersion ?? "")) // 0 是全部
|
||||
TextSearchVersion.Items.Insert(1, new MyComboBoxItem { Content = highestVersion });
|
||||
}
|
||||
|
||||
// 根据页面类型控制加载器选择的显示
|
||||
if (PageType == ModComp.CompType.Shader)
|
||||
{
|
||||
LabLoader.Visibility = Visibility.Visible;
|
||||
ComboSearchLoader.Visibility = Visibility.Collapsed;
|
||||
ComboSearchShaderLoader.Visibility = Visibility.Visible;
|
||||
}
|
||||
else if (PageType == ModComp.CompType.Mod || PageType == ModComp.CompType.ModPack)
|
||||
{
|
||||
LabLoader.Visibility = Visibility.Visible;
|
||||
ComboSearchLoader.Visibility = Visibility.Visible;
|
||||
ComboSearchShaderLoader.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
LabLoader.Visibility = Visibility.Collapsed;
|
||||
ComboSearchLoader.Visibility = Visibility.Collapsed;
|
||||
ComboSearchShaderLoader.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void PageComp_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
// 当页面变为可见时刷新收藏按钮状态
|
||||
if (IsVisible) RefreshAllFavoriteStatus();
|
||||
}
|
||||
|
||||
private ModComp.CompProjectRequest LoaderInput()
|
||||
{
|
||||
var request = new ModComp.CompProjectRequest(PageType, storage, (page + 1) * pageSize);
|
||||
var gameVersion = TextSearchVersion.Text == Lang.Text("Download.Comp.Filter.Version.AllInputAvailable") ? null :
|
||||
TextSearchVersion.Text.Contains(".") || TextSearchVersion.Text.Contains("w") ? TextSearchVersion.Text :
|
||||
null;
|
||||
var modLoader = ModComp.CompLoaderType.Any;
|
||||
if (PageType == ModComp.CompType.Mod || PageType == ModComp.CompType.ModPack) // 只有 Mod 考虑加载器
|
||||
{
|
||||
modLoader = (ModComp.CompLoaderType)ModBase.Val(((MyComboBoxItem)ComboSearchLoader.SelectedItem).Tag);
|
||||
if (gameVersion is not null && gameVersion.Contains(".") && ModBase.Val(gameVersion.Split(".")[1]) < 14d &&
|
||||
modLoader == ModComp.CompLoaderType.Forge) // 1.14-
|
||||
// 选择了 Forge
|
||||
modLoader = ModComp.CompLoaderType.Any; // 此时,视作没有筛选 Mod Loader(因为部分老 Mod 没有设置自己支持的加载器)
|
||||
}
|
||||
|
||||
request.searchText = PanSearchBox.Text;
|
||||
request.gameVersion = gameVersion;
|
||||
var selectedTag = (ComboSearchTag.SelectedItem as FrameworkElement)?.Tag?.ToString();
|
||||
var loaderTag = (ComboSearchShaderLoader.SelectedItem as FrameworkElement)?.Tag?.ToString();
|
||||
|
||||
request.tag = PageType == ModComp.CompType.Shader
|
||||
? string.IsNullOrEmpty(loaderTag)
|
||||
? selectedTag
|
||||
: selectedTag + loaderTag
|
||||
: selectedTag;
|
||||
request.modLoader =
|
||||
(ModComp.CompLoaderType)(PageType == ModComp.CompType.Mod || PageType == ModComp.CompType.ModPack
|
||||
? ModBase.Val(((MyComboBoxItem)ComboSearchLoader.SelectedItem).Tag)
|
||||
: (double)ModComp.CompLoaderType.Any);
|
||||
request.source = (ModComp.CompSourceType)ModBase.Val(((MyComboBoxItem)ComboSearchSource.SelectedItem).Tag);
|
||||
request.sort = (ModComp.CompSortType)ModBase.Val(((MyComboBoxItem)ComboSearchSort.SelectedItem).Tag);
|
||||
return request;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 搜索
|
||||
|
||||
// 搜索按钮
|
||||
private void StartNewSearch()
|
||||
{
|
||||
page = 0;
|
||||
object argInput = LoaderInput();
|
||||
if (loader.ShouldStart(ref argInput))
|
||||
storage = new ModComp.CompProjectStorage(); // 避免连续搜索两次使得 CompProjectStorage 引用丢失(#1311)
|
||||
loader.Start();
|
||||
}
|
||||
|
||||
private void EnterTrigger(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter)
|
||||
StartNewSearch();
|
||||
}
|
||||
|
||||
// 重置按钮
|
||||
private void ResetFilter()
|
||||
{
|
||||
PanSearchBox.Text = "";
|
||||
TextSearchVersion.Text = Lang.Text("Download.Comp.Filter.Version.AllInputAvailable");
|
||||
TextSearchVersion.SelectedIndex = 0;
|
||||
ComboSearchSource.SelectedIndex = 0;
|
||||
ComboSearchTag.SelectedIndex = 0;
|
||||
ComboSearchLoader.SelectedIndex = 0;
|
||||
ComboSearchShaderLoader.SelectedIndex = 0;
|
||||
ComboSearchSort.SelectedIndex = 0;
|
||||
loader.lastFinishedTime = 0L; // 要求强制重新开始
|
||||
}
|
||||
|
||||
private void BtnSearchReset_Click(object sender, EventArgs e)
|
||||
{
|
||||
ResetFilter();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<local:MyPageRight
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" x:Class="PCL.PageDownloadCompDetail"
|
||||
PanScroll="{Binding ElementName=PanBack}">
|
||||
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
|
||||
<Grid Margin="25,25,25,10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="1*" />
|
||||
</Grid.RowDefinitions>
|
||||
<local:MyCard Margin="0,0,0,25" x:Name="CardIntro">
|
||||
<StackPanel Margin="22,20,2,20" x:Name="PanIntro">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<local:MyIconTextButton x:Name="BtnIntroWeb" Text="CurseForge"
|
||||
BorderBrush="{DynamicResource ColorBrush6}" BorderThickness="1"
|
||||
SvgIcon="lucide/globe"
|
||||
Margin="0,0,3,0" HorizontalAlignment="Left" />
|
||||
<local:MyIconTextButton x:Name="BtnIntroWiki"
|
||||
Text="{DynamicResource Download.Comp.Detail.McMod}"
|
||||
BorderBrush="{DynamicResource ColorBrush6}" BorderThickness="1"
|
||||
SvgIcon="lucide/globe"
|
||||
Margin="0,0,3,0" HorizontalAlignment="Left" />
|
||||
<local:MyIconTextButton x:Name="BtnIntroCopy"
|
||||
Text="{DynamicResource Download.Comp.Detail.CopyName}"
|
||||
BorderBrush="{DynamicResource ColorBrush6}" BorderThickness="1"
|
||||
SvgIcon="lucide/copy"
|
||||
Margin="0,0,3,0" HorizontalAlignment="Left" />
|
||||
<local:MyIconTextButton x:Name="BtnIntroLinkCopy"
|
||||
Text="{DynamicResource Download.Comp.Detail.CopyLink}"
|
||||
BorderBrush="{DynamicResource ColorBrush6}" BorderThickness="1"
|
||||
SvgIcon="lucide/copy"
|
||||
Margin="0,0,3,0" HorizontalAlignment="Left" />
|
||||
<local:MyIconTextButton x:Name="BtnTranslate"
|
||||
Text="{DynamicResource Download.Comp.Detail.TranslateDescription}"
|
||||
BorderBrush="{DynamicResource ColorBrush6}" BorderThickness="1"
|
||||
SvgIcon="lucide/earth"
|
||||
Margin="0,0,3,0" HorizontalAlignment="Left" />
|
||||
<local:MyIconTextButton x:Name="BtnFavorites"
|
||||
Text="{DynamicResource Download.Comp.Detail.Favorite}"
|
||||
BorderBrush="{DynamicResource ColorBrush6}" BorderThickness="1"
|
||||
SvgIcon="lucide/heart"
|
||||
Margin="0,0,2,0" HorizontalAlignment="Left" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
<StackPanel Grid.Row="1" Grid.RowSpan="2" Name="PanMain" Grid.IsSharedSizeScope="True">
|
||||
<local:MyCard Margin="0,0,0,15" x:Name="CardFilter">
|
||||
<StackPanel>
|
||||
<StackPanel x:Name="PanInstanceFilter" Margin="10,10,0,5" Orientation="Horizontal"
|
||||
VerticalAlignment="Top" HorizontalAlignment="Left" />
|
||||
<StackPanel x:Name="PanModLoaderFilter" Margin="10,5,0,10" Orientation="Horizontal"
|
||||
VerticalAlignment="Top" HorizontalAlignment="Left" />
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
<StackPanel Name="PanResults" />
|
||||
</StackPanel>
|
||||
<local:MyCard Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False" Margin="0,0,0,8">
|
||||
<local:MyLoading Text="{DynamicResource Download.Version.LoadingList}" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</local:MyCard>
|
||||
</Grid>
|
||||
</local:MyScrollViewer>
|
||||
</local:MyPageRight>
|
||||
+1183
@@ -0,0 +1,1183 @@
|
||||
using System.Collections;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.Eventing.Reader;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using FluentValidation;
|
||||
using PCL.Core.App;
|
||||
using PCL.Core.App.Localization;
|
||||
using PCL.Core.Logging;
|
||||
using PCL.Core.Minecraft.ResourceProject;
|
||||
using PCL.Core.UI;
|
||||
using PCL.Core.Utils;
|
||||
using PCL.Core.Utils.Validate;
|
||||
using PCL.Network;
|
||||
using PCL.Network.Loaders;
|
||||
using Control = System.Windows.Forms.Control;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageDownloadCompDetail
|
||||
{
|
||||
// 资源下载;整合包另存为
|
||||
public static Dictionary<ModComp.CompType, string> cachedFolder = new(); // 仅在本次缓存的下载文件夹
|
||||
private MyCompItem _compItem;
|
||||
private bool _isFirstInit = true;
|
||||
|
||||
private void Init()
|
||||
{
|
||||
ModAnimation.AniControlEnabled += 1;
|
||||
_project = ModMain.frmMain.pageCurrent.additional.Value.CompProject;
|
||||
PanBack.ScrollToHome();
|
||||
// 重启加载器
|
||||
if (_isFirstInit)
|
||||
// 在 Me.Initialized 已经初始化了加载器,不再重复初始化
|
||||
_isFirstInit = false;
|
||||
else
|
||||
PageLoaderRestart(isForceRestart: true);
|
||||
// 放置当前工程
|
||||
if (_compItem is not null)
|
||||
PanIntro.Children.Remove(_compItem);
|
||||
_compItem = _project.ToCompItem(true, true);
|
||||
_compItem.CanInteraction = false;
|
||||
_compItem.ShowFavoriteBtn = false;
|
||||
_compItem.Margin = new Thickness(-7, -7, 0d, 8d);
|
||||
PanIntro.Children.Insert(0, _compItem);
|
||||
|
||||
// 决定按钮显示
|
||||
BtnIntroWeb.Text = _project.FromCurseForge ? "CurseForge" : "Modrinth";
|
||||
BtnIntroWiki.Visibility = Lang.IsChineseMainland && _project.WikiId != 0
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
BtnTranslate.Visibility = Lang.IsChineseMainland
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
RefreshFavoriteButton();
|
||||
|
||||
ModAnimation.AniControlEnabled -= 1;
|
||||
}
|
||||
|
||||
// 整合包安装
|
||||
public void Install_Click(MyListItem sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取基本信息
|
||||
var file = (ModComp.CompFile)sender.Tag;
|
||||
var loaderName =
|
||||
$"{(_project.FromCurseForge ? "CurseForge" : "Modrinth")} {Lang.Text("Download.Comp.Detail.ModpackDownload")}:{_project.TranslatedName} ";
|
||||
|
||||
// 获取实例名
|
||||
var packName = _project.TranslatedName.Replace(".zip", "").Replace(".rar", "").Replace(".mrpack", "")
|
||||
.Replace(@"\", "\").Replace("/", "/").Replace("|", "|").Replace(":", ":").Replace("<", "<")
|
||||
.Replace(">", ">").Replace("*", "*").Replace("?", "?").Replace("\"", "").Replace(": ", ":");
|
||||
var validate = new FolderNameValidator(ModFolder.mcFolderSelected + "versions");
|
||||
if (!validate.Validate(packName).IsValid)
|
||||
packName = "";
|
||||
var instanceName = ModMain.MyMsgBoxInput(Lang.Text("Download.Comp.Detail.InputInstanceName"), "", packName, [validate]);
|
||||
if (string.IsNullOrEmpty(instanceName))
|
||||
return;
|
||||
|
||||
// 构造步骤加载器
|
||||
var loaders = new List<ModLoader.LoaderBase>();
|
||||
var target =
|
||||
$@"{ModFolder.mcFolderSelected}versions\{instanceName}\原始整合包.{(_project.FromCurseForge ? "zip" : "mrpack")}";
|
||||
var logoFileAddress = MyImage.GetTempPath(_compItem.Logo);
|
||||
loaders.Add(new LoaderDownload(Lang.Text("Download.Comp.Detail.DownloadModpackFile"),
|
||||
new List<DownloadFile> { file.ToNetFile(target, ModComp.DownloadReason.ModPack) })
|
||||
{ ProgressWeight = 10d, block = true });
|
||||
loaders.Add(new ModLoader.LoaderTask<int, int>(Lang.Text("Download.Comp.Detail.PrepareModpackInstall"),
|
||||
_ => ModModpack.ModpackInstall(target, instanceName,
|
||||
System.IO.File.Exists(logoFileAddress) ? logoFileAddress : null, file.ProjectId,
|
||||
true)) { ProgressWeight = 0.1d });
|
||||
|
||||
// 启动
|
||||
var loader = new ModLoader.LoaderCombo<string>(loaderName, loaders)
|
||||
{
|
||||
OnStateChanged = myLoader =>
|
||||
{
|
||||
switch (myLoader.State)
|
||||
{
|
||||
case ModBase.LoadState.Failed:
|
||||
{
|
||||
HintService.Hint(
|
||||
Lang.Text("Download.Comp.Detail.Task.Failed", myLoader.name,
|
||||
myLoader.Error.ToString()), HintType.Error);
|
||||
break;
|
||||
}
|
||||
case ModBase.LoadState.Aborted:
|
||||
{
|
||||
HintService.Hint(Lang.Text("Download.Comp.Detail.Task.Cancelled", myLoader.name));
|
||||
break;
|
||||
}
|
||||
case ModBase.LoadState.Loading:
|
||||
{
|
||||
return; // 不重新加载版本列表
|
||||
}
|
||||
}
|
||||
|
||||
ModDownloadLib.McInstallFailedClearFolder(myLoader);
|
||||
}
|
||||
};
|
||||
loader.Start(Path.Combine(ModFolder.mcFolderSelected, "versions", instanceName));
|
||||
ModLoader.LoaderTaskbarAdd(loader);
|
||||
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
|
||||
ModMain.frmMain.BtnExtraDownload.Ribble();
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"下载资源整合包失败",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Download.Comp.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// 世界下载
|
||||
public void InstallWorld_Click(MyListItem sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取基本信息
|
||||
var file = (ModComp.CompFile)sender.Tag;
|
||||
var loaderName = $"{(_project.FromCurseForge ? "CurseForge" : "Modrinth")} {Lang.Text("Download.Comp.Detail.WorldDownload")}:{_project.TranslatedName} ";
|
||||
|
||||
// 确认默认保存位置
|
||||
string defaultFolder = null;
|
||||
var subFolder = @"saves\";
|
||||
Func<McInstance, bool> isVersionSuitable = null;
|
||||
// 获取资源所需的加载器
|
||||
var allowedLoaders = new List<ModComp.CompLoaderType>();
|
||||
if (file.ModLoaders.Any())
|
||||
allowedLoaders = file.ModLoaders;
|
||||
else if (_project.ModLoaders.Any()) allowedLoaders = _project.ModLoaders;
|
||||
ModBase.Log("[Comp] 世界要求的加载器种类:" + (allowedLoaders.Any() ? allowedLoaders.Join(" / ") : "无要求"));
|
||||
// 判断某个版本是否符合资源要求
|
||||
isVersionSuitable = version =>
|
||||
{
|
||||
if (version is null)
|
||||
return false;
|
||||
if (!version.IsLoaded)
|
||||
version.Load();
|
||||
if (file.GameVersions.Any(v => v.Contains(".")) && !file.GameVersions.Any(v =>
|
||||
v.Contains(".") && (v ?? "") == (version.Info.VanillaName ?? "")))
|
||||
return false;
|
||||
// 加载器
|
||||
if (!allowedLoaders.Any())
|
||||
return true; // 无要求
|
||||
return false;
|
||||
};
|
||||
// 获取常规资源默认下载位置
|
||||
if (cachedFolder.ContainsKey(file.Type) && !string.IsNullOrEmpty(cachedFolder[file.Type]))
|
||||
{
|
||||
defaultFolder = cachedFolder.GetOrDefault(file.Type,
|
||||
ModInstanceList.McMcInstanceSelected?.PathIndie ?? ModBase.exePath);
|
||||
ModBase.Log($"[Comp] 使用上次下载时的文件夹作为默认下载位置:{defaultFolder}");
|
||||
}
|
||||
else if (ModInstanceList.McMcInstanceSelected is not null && isVersionSuitable(ModInstanceList.McMcInstanceSelected))
|
||||
{
|
||||
defaultFolder = $"{ModInstanceList.McMcInstanceSelected.PathIndie}{subFolder}";
|
||||
Directory.CreateDirectory(defaultFolder);
|
||||
ModBase.Log($"[Comp] 使用当前实例作为默认下载位置:{defaultFolder}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 查找所有可能的实例
|
||||
var needLoad = ModInstanceList.mcInstanceListLoader.State != ModBase.LoadState.Finished;
|
||||
if (needLoad)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Download.Comp.Detail.FindingApplicableInstance"));
|
||||
ModLoader.LoaderFolderRun(ModInstanceList.mcInstanceListLoader, ModFolder.mcFolderSelected,
|
||||
ModLoader.LoaderFolderRunType.ForceRun, 1, @"versions\", true);
|
||||
}
|
||||
|
||||
var suitableVersions = ModInstanceList.mcInstanceList.Values.SelectMany(l => l)
|
||||
.Where(v => isVersionSuitable(v)).Select(v => new DirectoryInfo($"{v.PathIndie}{subFolder}"));
|
||||
if (suitableVersions.Any())
|
||||
{
|
||||
var selectedVersion = suitableVersions
|
||||
.OrderByDescending(dir => dir.Exists ? dir.LastWriteTimeUtc : DateTime.MinValue)
|
||||
.ThenByDescending(dir => dir.Exists ? dir.GetFiles().Length : -1).First(); // 先按文件夹更改时间降序
|
||||
// 再按文件夹中的文件数量降序
|
||||
defaultFolder = selectedVersion.FullName;
|
||||
Directory.CreateDirectory(defaultFolder);
|
||||
ModBase.Log($"[Comp] 使用适合的游戏实例作为默认下载位置:{defaultFolder}");
|
||||
}
|
||||
else
|
||||
{
|
||||
defaultFolder = ModFolder.mcFolderSelected;
|
||||
if (needLoad)
|
||||
HintService.Hint(Lang.Text("Download.Comp.Detail.NoApplicableInstance"));
|
||||
else
|
||||
ModBase.Log("[Comp] 由于当前实例不兼容,使用当前的 MC 文件夹作为默认下载位置");
|
||||
}
|
||||
}
|
||||
|
||||
var target = SystemDialogs.SelectSaveFile(Lang.Text("Download.Comp.Detail.SelectWorldInstallLocation"), file.FileName, Lang.Text("Download.Comp.Detail.WorldFile.Filter"),
|
||||
defaultFolder);
|
||||
if (string.IsNullOrEmpty(target))
|
||||
return;
|
||||
|
||||
// 构造步骤加载器
|
||||
var loaders = new List<ModLoader.LoaderBase>();
|
||||
var targetPath = target.BeforeLast(@"\");
|
||||
var logoFileAddress = MyImage.GetTempPath(_compItem.Logo);
|
||||
loaders.Add(new LoaderDownload(Lang.Text("Download.Comp.Detail.DownloadWorldFile"),
|
||||
new List<DownloadFile>
|
||||
{
|
||||
file.ToNetFile(target, ModComp.DownloadReason.Standalone, file.RawGameVersions.FirstOrDefault())
|
||||
})
|
||||
{ ProgressWeight = 10d, block = true });
|
||||
loaders.Add(new ModLoader.LoaderTask<int, int>(Lang.Text("Download.Comp.Detail.InstallWorld"),
|
||||
_ => ModBase.ExtractFile(target, targetPath, Encoding.UTF8)) { ProgressWeight = 0.1d, block = true });
|
||||
loaders.Add(new ModLoader.LoaderTask<int, int>(Lang.Text("Download.Comp.Detail.CleanCache"),
|
||||
_ => System.IO.File.Delete(target)));
|
||||
|
||||
// 启动
|
||||
var loader = new ModLoader.LoaderCombo<int>(loaderName, loaders)
|
||||
{ OnStateChanged = ModDownloadLib.LoaderStateChangedHintOnly };
|
||||
loader.Start();
|
||||
ModLoader.LoaderTaskbarAdd(loader);
|
||||
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
|
||||
ModMain.frmMain.BtnExtraDownload.Ribble();
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"下载世界资源失败",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Download.Comp.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
public void Save_Click(object sender, EventArgs e)
|
||||
{
|
||||
// 获取点击项关联的文件对象
|
||||
var file = sender switch
|
||||
{
|
||||
FrameworkElement Element when Element.Tag is ModComp.CompFile CompFile => CompFile,
|
||||
FrameworkElement Element when Element.Parent is FrameworkElement Parent && Parent.Tag is ModComp.CompFile CompFile => CompFile,
|
||||
FrameworkElement Element when Element.Parent is FrameworkElement Parent && Parent.Parent is FrameworkElement GrandParent && GrandParent.Tag is ModComp.CompFile CompFile => CompFile,
|
||||
_ => null
|
||||
};
|
||||
|
||||
ModBase.RunInNewThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var desc = file.Type switch
|
||||
{
|
||||
ModComp.CompType.ModPack => Lang.Text("Download.Comp.Type.Modpack"),
|
||||
ModComp.CompType.Mod => Lang.Text("Download.Comp.Type.Mod"),
|
||||
ModComp.CompType.ResourcePack => Lang.Text("Download.Comp.Type.ResourcePack"),
|
||||
ModComp.CompType.Shader => Lang.Text("Download.Comp.Type.Shader"),
|
||||
ModComp.CompType.DataPack => Lang.Text("Download.Comp.Type.DataPack"),
|
||||
ModComp.CompType.World => Lang.Text("Download.Comp.Type.World"),
|
||||
_ => ""
|
||||
};
|
||||
|
||||
// 确认默认保存位置
|
||||
string defaultFolder = null;
|
||||
var allowedLoaders = new List<ModComp.CompLoaderType>();
|
||||
if (file.Type != ModComp.CompType.ModPack)
|
||||
{
|
||||
var subFolder = "";
|
||||
switch (file.Type)
|
||||
{
|
||||
case ModComp.CompType.Mod: subFolder = "mods\\"; break;
|
||||
case ModComp.CompType.ResourcePack: subFolder = "resourcepacks\\"; break;
|
||||
case ModComp.CompType.Shader: subFolder = "shaderpacks\\"; break;
|
||||
case ModComp.CompType.World: subFolder = "saves\\"; break;
|
||||
case ModComp.CompType.DataPack: subFolder = ""; break; // 导航到版本根目录
|
||||
}
|
||||
|
||||
// 获取资源所需的加载器
|
||||
if (file.ModLoaders.Any())
|
||||
allowedLoaders = file.ModLoaders;
|
||||
else if (_project.ModLoaders.Any()) allowedLoaders = _project.ModLoaders;
|
||||
ModBase.Log(
|
||||
$"[Comp] {desc}要求的加载器种类:{(allowedLoaders.Any() ? string.Join(" / ", allowedLoaders) : "无要求")}");
|
||||
|
||||
// 判断某个版本是否符合资源要求 (局部函数)
|
||||
Func<McInstance, bool> isVersionSuitable = version =>
|
||||
{
|
||||
if (version is null) return false;
|
||||
if (!version.IsLoaded) version.Load();
|
||||
|
||||
// 只对 Mod 和数据包进行版本检测
|
||||
if (file.Type == ModComp.CompType.Mod || file.Type == ModComp.CompType.DataPack)
|
||||
if (file.GameVersions.Any(v => v.Contains(".")) &&
|
||||
!file.GameVersions.Any(v => v.Contains(".") && v == version.Info.VanillaName))
|
||||
return false;
|
||||
|
||||
// 加载器判定
|
||||
if (!allowedLoaders.Any()) return true; // 无要求
|
||||
if (allowedLoaders.Contains(ModComp.CompLoaderType.Forge) && version.Info.HasForge) return true;
|
||||
if (allowedLoaders.Contains(ModComp.CompLoaderType.Forge) && version.Info.HasCleanroom)
|
||||
return true;
|
||||
if (allowedLoaders.Contains(ModComp.CompLoaderType.Fabric) &&
|
||||
(version.Info.HasFabric || version.Info.HasLegacyFabric)) return true;
|
||||
if (allowedLoaders.Contains(ModComp.CompLoaderType.NeoForge) && version.Info.HasNeoForge)
|
||||
return true;
|
||||
if (allowedLoaders.Contains(ModComp.CompLoaderType.LiteLoader) && version.Info.HasLiteLoader)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
// 获取常规资源默认下载位置逻辑
|
||||
if (cachedFolder.ContainsKey(file.Type) && !string.IsNullOrEmpty(cachedFolder[file.Type]))
|
||||
{
|
||||
defaultFolder = cachedFolder.GetOrDefault(file.Type,
|
||||
ModInstanceList.McMcInstanceSelected?.PathIndie ?? ModBase.exePath);
|
||||
ModBase.Log($"[Comp] 使用上次下载时的文件夹作为默认下载位置:{defaultFolder}");
|
||||
}
|
||||
else if (ModInstanceList.McMcInstanceSelected is not null &&
|
||||
isVersionSuitable(ModInstanceList.McMcInstanceSelected))
|
||||
{
|
||||
defaultFolder = $"{ModInstanceList.McMcInstanceSelected.PathIndie}{subFolder}";
|
||||
Directory.CreateDirectory(defaultFolder);
|
||||
ModBase.Log($"[Comp] 使用当前实例作为默认下载位置:{defaultFolder}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 查找所有可能的实例
|
||||
var needLoad = ModInstanceList.mcInstanceListLoader.State != ModBase.LoadState.Finished;
|
||||
if (needLoad)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Download.Comp.Detail.FindingApplicableInstance"));
|
||||
ModLoader.LoaderFolderRun(ModInstanceList.mcInstanceListLoader, ModFolder.mcFolderSelected,
|
||||
ModLoader.LoaderFolderRunType.ForceRun, 1, "versions\\", true);
|
||||
}
|
||||
|
||||
var suitableVersions = ModInstanceList.mcInstanceList.Values.SelectMany(l => l)
|
||||
.Where(v => isVersionSuitable(v))
|
||||
.Select(v => new DirectoryInfo($"{v.PathIndie}{subFolder}"));
|
||||
|
||||
if (suitableVersions.Any())
|
||||
{
|
||||
var selectedVersion = suitableVersions
|
||||
.OrderByDescending(dir => dir.Exists ? dir.LastWriteTimeUtc : DateTime.MinValue)
|
||||
.ThenByDescending(dir => dir.Exists ? dir.GetFiles().Length : -1)
|
||||
.First();
|
||||
defaultFolder = selectedVersion.FullName;
|
||||
Directory.CreateDirectory(defaultFolder);
|
||||
ModBase.Log($"[Comp] 使用适合的游戏实例作为默认下载位置:{defaultFolder}");
|
||||
}
|
||||
else
|
||||
{
|
||||
defaultFolder = ModFolder.mcFolderSelected;
|
||||
if (needLoad)
|
||||
HintService.Hint(Lang.Text("Download.Comp.Detail.NoApplicableInstance"));
|
||||
else
|
||||
ModBase.Log("[Comp] 由于当前实例不兼容,使用当前的 MC 文件夹作为默认下载位置");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取文件名并弹窗
|
||||
var fileName = ModComp.CompFileNameGet(_project, file);
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
var target = SystemDialogs.SelectSaveFile(Lang.Text("Download.Comp.Detail.SelectSaveLocation"),
|
||||
fileName, Lang.Text("Download.Comp.Detail.ResourceFile.Filter", desc) + "|" +
|
||||
(file.Type == ModComp.CompType.Mod
|
||||
? file.FileName.EndsWith(".litemod") ? "*.litemod" : "*.jar"
|
||||
: file.FileName.EndsWith(".mrpack")
|
||||
? "*.mrpack"
|
||||
: "*.zip"),
|
||||
defaultFolder);
|
||||
|
||||
if (!target.Contains("\\")) return;
|
||||
|
||||
// 记录缓存路径
|
||||
var targetDir = ModBase.GetPathFromFullPath(target);
|
||||
if (target != defaultFolder)
|
||||
{
|
||||
if (cachedFolder.ContainsKey(file.Type))
|
||||
cachedFolder[file.Type] = targetDir;
|
||||
else
|
||||
cachedFolder.Add(file.Type, targetDir);
|
||||
}
|
||||
|
||||
if (file.Type == ModComp.CompType.Mod && Config.Download.Comp.AutoInstallDependencies &&
|
||||
file.Dependencies.Any())
|
||||
{
|
||||
try
|
||||
{
|
||||
McInstance? targetInstance = null;
|
||||
var knownInstances = new List<McInstance>();
|
||||
if (ModInstanceList.McMcInstanceSelected is not null)
|
||||
{
|
||||
knownInstances.Add(ModInstanceList.McMcInstanceSelected);
|
||||
}
|
||||
|
||||
knownInstances.AddRange(ModInstanceList.mcInstanceList.Values.SelectMany(list => list)
|
||||
.Where(instance => instance is not null));
|
||||
targetInstance = knownInstances
|
||||
.Distinct()
|
||||
.FirstOrDefault(instance =>
|
||||
targetDir.StartsWith(instance.PathIndie, StringComparison.OrdinalIgnoreCase));
|
||||
if (targetInstance is not null && !targetInstance.IsLoaded)
|
||||
{
|
||||
targetInstance.Load();
|
||||
}
|
||||
|
||||
var mcVersion = targetInstance?.Info?.VanillaName
|
||||
?? file.GameVersions.FirstOrDefault(version => version.Contains("."))
|
||||
?? string.Empty;
|
||||
var targetLoaders = new List<ModComp.CompLoaderType>();
|
||||
if (targetInstance is not null)
|
||||
{
|
||||
if (targetInstance.Info.HasForge || targetInstance.Info.HasCleanroom)
|
||||
targetLoaders.Add(ModComp.CompLoaderType.Forge);
|
||||
if (targetInstance.Info.HasFabric || targetInstance.Info.HasLegacyFabric)
|
||||
targetLoaders.Add(ModComp.CompLoaderType.Fabric);
|
||||
if (targetInstance.Info.HasQuilt)
|
||||
targetLoaders.Add(ModComp.CompLoaderType.Quilt);
|
||||
if (targetInstance.Info.HasNeoForge)
|
||||
targetLoaders.Add(ModComp.CompLoaderType.NeoForge);
|
||||
if (targetInstance.Info.HasLiteLoader)
|
||||
targetLoaders.Add(ModComp.CompLoaderType.LiteLoader);
|
||||
}
|
||||
|
||||
if (!targetLoaders.Any())
|
||||
{
|
||||
targetLoaders = allowedLoaders.ToList();
|
||||
}
|
||||
|
||||
ModBase.Log($"[CompDeps] 开始解析必需前置: {file.Dependencies.Count} 个依赖");
|
||||
var request = ModCompDependency.BuildRequest(file, _project, mcVersion, targetLoaders,
|
||||
targetDir);
|
||||
var resolver = new ModDependencyResolver();
|
||||
var result = resolver.Resolve(request);
|
||||
|
||||
void DownloadDependencies()
|
||||
{
|
||||
if (!result.ToInstall.Any())
|
||||
{
|
||||
ModBase.Log("[CompDeps] 所有前置均无法解析,仅下载 Mod 本体");
|
||||
return;
|
||||
}
|
||||
|
||||
ModBase.Log($"[CompDeps] 准备下载: {result.ToInstall.Count} 个前置");
|
||||
var depDownloads = ModCompDependency.BuildDependencyDownloads(result, targetDir);
|
||||
foreach (var (depFilename, downloadFile) in depDownloads)
|
||||
{
|
||||
var depLoaderName = Lang.Text("Download.Comp.Detail.DownloadResource", desc,
|
||||
ModBase.GetFileNameWithoutExtentionFromPath(depFilename));
|
||||
var depLoaders = new List<ModLoader.LoaderBase>
|
||||
{
|
||||
new LoaderDownload(Lang.Text("Download.Comp.Detail.DownloadFile"),
|
||||
new List<DownloadFile> { downloadFile })
|
||||
{
|
||||
ProgressWeight = 6,
|
||||
block = true
|
||||
}
|
||||
};
|
||||
|
||||
// 启动加载器
|
||||
var depLoader = new ModLoader.LoaderCombo<int>(depLoaderName, depLoaders);
|
||||
depLoader.OnStateChanged = ModDownloadLib.LoaderStateChangedHintOnly;
|
||||
depLoader.Start(1);
|
||||
ModLoader.LoaderTaskbarAdd(depLoader);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.Unresolved.Any() || result.ToInstall.Any())
|
||||
{
|
||||
var installChoice = ModCompDependency.ConfirmDependencyInstall(result);
|
||||
|
||||
switch (installChoice)
|
||||
{
|
||||
case ModComp.CompDepsInstallTypes.Unresolved:
|
||||
ModBase.Log("[CompDeps] 发现无法解析的前置");
|
||||
DownloadDependencies();
|
||||
break;
|
||||
|
||||
case ModComp.CompDepsInstallTypes.WithDeps:
|
||||
DownloadDependencies();
|
||||
break;
|
||||
|
||||
case ModComp.CompDepsInstallTypes.WithoutDeps:
|
||||
ModBase.Log("[CompDeps] 用户选择仅下载 Mod 本体,跳过前置下载");
|
||||
break;
|
||||
|
||||
case ModComp.CompDepsInstallTypes.Cancel:
|
||||
ModBase.Log("[CompDeps] 用户取消安装");
|
||||
return;
|
||||
|
||||
default:
|
||||
ModBase.Log($"[CompDeps] 未知返回值: {installChoice} ,终止下载");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ModBase.Log("[CompDeps] 已满足: 所有必需前置已安装");
|
||||
}
|
||||
}
|
||||
catch (Exception depEx)
|
||||
{
|
||||
ModBase.Log(depEx, "[CompDeps] 依赖解析失败,跳过前置安装");
|
||||
var message = ExceptionDetails.Compose(
|
||||
Lang.Text("Download.Comp.Dependency.ResolveFailed.Message"),
|
||||
depEx);
|
||||
ModMain.MyMsgBox(
|
||||
message,
|
||||
Lang.Text("Download.Comp.Dependency.ResolveFailed.Title"),
|
||||
Lang.Text("Download.Comp.Dependency.ResolveFailed.Continue"),
|
||||
isWarn: true,
|
||||
forceWait: true);
|
||||
}
|
||||
}
|
||||
|
||||
// 构造下载任务
|
||||
var loaderName = Lang.Text("Download.Comp.Detail.DownloadResource", desc,
|
||||
ModBase.GetFileNameWithoutExtentionFromPath(target));
|
||||
var loaders = new List<ModLoader.LoaderBase>
|
||||
{
|
||||
new LoaderDownload(Lang.Text("Download.Comp.Detail.DownloadFile"),
|
||||
new List<DownloadFile>
|
||||
{
|
||||
file.Type == ModComp.CompType.Mod
|
||||
? file.ToNetFile(target)
|
||||
: file.ToNetFile(target, ModComp.DownloadReason.Standalone, null)
|
||||
})
|
||||
{
|
||||
ProgressWeight = 6,
|
||||
block = true
|
||||
}
|
||||
};
|
||||
|
||||
// 启动加载器
|
||||
var loader = new ModLoader.LoaderCombo<int>(loaderName, loaders);
|
||||
loader.OnStateChanged = ModDownloadLib.LoaderStateChangedHintOnly;
|
||||
loader.Start(1);
|
||||
ModLoader.LoaderTaskbarAdd(loader);
|
||||
|
||||
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
|
||||
ModMain.frmMain.BtnExtraDownload.Ribble();
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"保存资源文件失败",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Download.Comp.Error.OperationFailed"));
|
||||
}
|
||||
}, "Download CompDetail Save");
|
||||
}
|
||||
|
||||
private void BtnIntroWeb_Click(object sender, EventArgs e)
|
||||
{
|
||||
ModBase.OpenWebsite(_project.Website);
|
||||
}
|
||||
|
||||
private void BtnIntroWiki_Click(object sender, EventArgs e)
|
||||
{
|
||||
ModBase.OpenWebsite("https://www.mcmod.cn/class/" + _project.WikiId + ".html");
|
||||
}
|
||||
|
||||
private void BtnIntroCopy_Click(object sender, EventArgs e)
|
||||
{
|
||||
ModBase.ClipboardSet(_compItem.LabTitle.Text + _compItem.LabTitleRaw.Text);
|
||||
}
|
||||
|
||||
private void BtnFavorites_Click(object sender, EventArgs e)
|
||||
{
|
||||
ModComp.CompFavorites.ShowMenu(_project, (UIElement)sender, RefreshFavoriteButton);
|
||||
}
|
||||
|
||||
private void BtnIntroLinkCopy_Click(object sender, EventArgs e)
|
||||
{
|
||||
ModComp.CompClipboard.currentText = _project.Website;
|
||||
ModBase.ClipboardSet(_project.Website);
|
||||
}
|
||||
|
||||
// 翻译简介
|
||||
private async void BtnTranslate_Click(object sender, EventArgs e)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Download.Comp.Detail.DescriptionTranslating", _project.TranslatedName));
|
||||
var chineseDescription = await _project.ChineseDescription;
|
||||
if (chineseDescription is null)
|
||||
return;
|
||||
ModMain.MyMsgBox(Lang.Text("Download.Comp.Detail.DescriptionTranslationResult", _project.Description,
|
||||
chineseDescription));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新收藏按钮的显示状态
|
||||
/// </summary>
|
||||
public void RefreshFavoriteButton()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_project is null) return;
|
||||
|
||||
var isFavourite = ModComp.CompFavorites.IsFavourite(_project.Id);
|
||||
BtnFavorites.SvgIcon = isFavourite ? "lucide/heart-filled" : "lucide/heart";
|
||||
|
||||
// 刷新顶部的项目卡片收藏状态
|
||||
if (_compItem is not null)
|
||||
_compItem.RefreshFavoriteStatus();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "刷新收藏按钮状态时出错");
|
||||
}
|
||||
}
|
||||
|
||||
#region 加载器
|
||||
|
||||
private readonly ModLoader.LoaderTask<int, List<ModComp.CompFile>> _compFileLoader;
|
||||
|
||||
public PageDownloadCompDetail()
|
||||
{
|
||||
_compFileLoader = new ModLoader.LoaderTask<int, List<ModComp.CompFile>>("Comp File", task =>
|
||||
{
|
||||
LoadTargetFromAdditional();
|
||||
var result = ModComp.CompFilesGet(_project.Id, _project.FromCurseForge);
|
||||
if (task.IsAborted)
|
||||
return;
|
||||
task.output = result;
|
||||
});
|
||||
Initialized += PageDownloadCompDetail_Inited;
|
||||
Loaded += (_, _) => LoadTargetFromAdditional();
|
||||
PageEnter += Init;
|
||||
InitializeComponent();
|
||||
Load.StateChanged += Load_State;
|
||||
BtnIntroWeb.Click += BtnIntroWeb_Click;
|
||||
BtnIntroWiki.Click += BtnIntroWiki_Click;
|
||||
BtnIntroCopy.Click += BtnIntroCopy_Click;
|
||||
BtnFavorites.Click += BtnFavorites_Click;
|
||||
BtnIntroLinkCopy.Click += BtnIntroLinkCopy_Click;
|
||||
BtnTranslate.Click += BtnTranslate_Click;
|
||||
}
|
||||
|
||||
// 初始化加载器信息
|
||||
private void PageDownloadCompDetail_Inited(object sender, EventArgs e)
|
||||
{
|
||||
LoadTargetFromAdditional();
|
||||
PageLoaderInit(Load, PanLoad, PanMain, CardIntro, _compFileLoader, _ => Load_OnFinish());
|
||||
}
|
||||
|
||||
public void LoadTargetFromAdditional()
|
||||
{
|
||||
var additional = ModMain.frmMain.pageCurrent.additional.Value;
|
||||
_project = additional.CompProject;
|
||||
_targetInstance = additional.TargetVersion;
|
||||
_targetLoader = additional.TargetLoader;
|
||||
_pageType = additional.ResourceType;
|
||||
}
|
||||
|
||||
private ModComp.CompProject _project;
|
||||
private string _targetInstance;
|
||||
private ModComp.CompLoaderType _targetLoader;
|
||||
|
||||
/// <summary>
|
||||
/// 当前页面应展示的内容类别。可能为 Any。
|
||||
/// </summary>
|
||||
private ModComp.CompType _pageType;
|
||||
|
||||
// 自动重试
|
||||
private void Load_State(object sender, MyLoading.MyLoadingState state, MyLoading.MyLoadingState oldState)
|
||||
{
|
||||
switch (_compFileLoader.State)
|
||||
{
|
||||
case ModBase.LoadState.Failed:
|
||||
{
|
||||
var errorMessage = "";
|
||||
if (_compFileLoader.Error is not null)
|
||||
errorMessage = _compFileLoader.Error.Message;
|
||||
if (errorMessage.Contains(Lang.Text("Common.Error.InvalidJson")))
|
||||
{
|
||||
ModBase.Log("[Comp] 下载的文件 Json 列表损坏,已自动重试", ModBase.LogLevel.Debug);
|
||||
PageLoaderRestart();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 结果 UI 化
|
||||
private class CardSorter : IComparer<string>
|
||||
{
|
||||
public readonly string topmost = "";
|
||||
|
||||
public CardSorter(string topmost = "")
|
||||
{
|
||||
this.topmost = topmost ?? "";
|
||||
}
|
||||
|
||||
public int Compare(string x, string y)
|
||||
{
|
||||
// 相同
|
||||
if ((x ?? "") == (y ?? ""))
|
||||
return 0;
|
||||
// 置顶
|
||||
if ((x ?? "") == (topmost ?? ""))
|
||||
return -1;
|
||||
if ((y ?? "") == (topmost ?? ""))
|
||||
return 1;
|
||||
// 特殊版本
|
||||
var isXSpecial = !x.Contains(".");
|
||||
var isYSpecial = !y.Contains(".");
|
||||
if (isXSpecial && isYSpecial)
|
||||
return string.Compare(x, y, StringComparison.Ordinal);
|
||||
if (isXSpecial)
|
||||
return 1;
|
||||
if (isYSpecial)
|
||||
return -1;
|
||||
// 比较版本号
|
||||
var versionCodeSort = -McVersionComparer.CompareVersion(x.Replace(x.BeforeFirst(" ") + " ", ""),
|
||||
y.Replace(y.BeforeFirst(" ") + " ", ""));
|
||||
if (versionCodeSort != 0)
|
||||
return versionCodeSort;
|
||||
// 比较全部
|
||||
return -McVersionComparer.CompareVersion(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
private string? _instanceFilter;
|
||||
private string? _modLoaderFilter;
|
||||
private bool groupedDrop; // 是否按 Drop 筛选(1.21 / 1.20 / 1.19 / ...)而非小版本号(1.21.1 / 1.21 / 1.20.4 / ...)
|
||||
|
||||
private bool groupedOld; // 是否折叠远古版本为一个选项
|
||||
|
||||
// 筛选类型相同的结果(Modrinth 会返回 Mod、服务端插件、数据包混合的列表)
|
||||
private List<ModComp.CompFile> GetResults()
|
||||
{
|
||||
var results = _compFileLoader.output;
|
||||
if (_pageType == ModComp.CompType.Any)
|
||||
{
|
||||
results = results.Where(r => r.Type != ModComp.CompType.Plugin).ToList();
|
||||
}
|
||||
else if (_pageType == ModComp.CompType.Shader || _pageType == ModComp.CompType.ResourcePack)
|
||||
{
|
||||
}
|
||||
// 不筛选光影和资源包,否则原版光影会因为是资源包格式而被过滤(Meloong-Git/#6473)
|
||||
else
|
||||
{
|
||||
results = results.Where(r => r.Type == _pageType).ToList();
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private void Load_OnFinish()
|
||||
{
|
||||
var results = GetResults();
|
||||
|
||||
// 初始化筛选器
|
||||
List<string> instanceFilters = null;
|
||||
List<string> modLoaderFilters = null;
|
||||
|
||||
void updateFilters()
|
||||
{
|
||||
instanceFilters = results.SelectMany(v => v.GameVersions)
|
||||
.Select(v => GetGroupedVersionName(v, groupedDrop, groupedOld)).Distinct()
|
||||
.OrderByDescending(s => s, new McVersionComparer.VersionComparer()).ToList();
|
||||
modLoaderFilters = results.SelectMany(v => v.ModLoaders).Select(l => l.ToString()).Distinct()
|
||||
.OrderByDescending(s => s).ToList();
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
// 确定分组方式
|
||||
groupedDrop = false;
|
||||
groupedOld = false;
|
||||
updateFilters();
|
||||
if (instanceFilters.Count >= 9)
|
||||
{
|
||||
groupedDrop = true;
|
||||
groupedOld = false;
|
||||
updateFilters();
|
||||
if (instanceFilters.Count >= 9)
|
||||
{
|
||||
groupedDrop = false;
|
||||
groupedOld = true;
|
||||
updateFilters();
|
||||
if (instanceFilters.Count >= 9)
|
||||
{
|
||||
groupedDrop = true;
|
||||
groupedOld = true;
|
||||
updateFilters();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// UI 化筛选器
|
||||
PanInstanceFilter.Children.Clear();
|
||||
PanModLoaderFilter.Children.Clear();
|
||||
if (_pageType == ModComp.CompType.Mod)
|
||||
{
|
||||
PanInstanceFilter.Margin = new Thickness(10d, 10d, 0d, 5d);
|
||||
PanModLoaderFilter.Margin = new Thickness(10d, 5d, 0d, 10d);
|
||||
}
|
||||
else
|
||||
{
|
||||
PanInstanceFilter.Margin = new Thickness(10d, 10d, 0d, 10d);
|
||||
PanModLoaderFilter.Margin = new Thickness(0d);
|
||||
}
|
||||
|
||||
if (instanceFilters.Count < 2)
|
||||
{
|
||||
CardFilter.Visibility = Visibility.Collapsed;
|
||||
_instanceFilter = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
CardFilter.Visibility = Visibility.Visible;
|
||||
// 插入标签
|
||||
if (_pageType == ModComp.CompType.Mod)
|
||||
{
|
||||
var instanceTextBlock = new TextBlock
|
||||
{
|
||||
Text = Lang.Text("Download.Comp.Detail.InstanceFilter"),
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(2d, 0d, 0d, 0d)
|
||||
};
|
||||
PanInstanceFilter.Children.Add(instanceTextBlock);
|
||||
var modLoaderTextBlock = new TextBlock
|
||||
{
|
||||
Text = Lang.Text("Download.Comp.Detail.LoaderFilter"),
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(2d, 0d, 0d, 0d)
|
||||
};
|
||||
PanModLoaderFilter.Children.Add(modLoaderTextBlock);
|
||||
}
|
||||
|
||||
instanceFilters.Insert(0, Lang.Text("Common.Option.All"));
|
||||
modLoaderFilters.Insert(0, Lang.Text("Common.Option.All"));
|
||||
// 转化为按钮
|
||||
foreach (var version in instanceFilters)
|
||||
{
|
||||
var newButton = new MyRadioButton
|
||||
{
|
||||
Text = version, Margin = new Thickness(2d, 0d, 2d, 0d),
|
||||
ColorType = MyRadioButton.ColorState.Highlight
|
||||
};
|
||||
newButton.LabText.Margin = new Thickness(-2, 0d, 10d, 0d);
|
||||
newButton.Check += (sender, raiseByMouse) =>
|
||||
{
|
||||
_instanceFilter = sender.Text == Lang.Text("Common.Option.All") ? null : sender.Text;
|
||||
UpdateFilterResult();
|
||||
};
|
||||
PanInstanceFilter.Children.Add(newButton);
|
||||
}
|
||||
|
||||
if (_pageType == ModComp.CompType.Mod)
|
||||
foreach (var loader in modLoaderFilters)
|
||||
{
|
||||
var newButton = new MyRadioButton
|
||||
{
|
||||
Text = loader,
|
||||
Margin = new Thickness(2d, 0d, 2d, 0d),
|
||||
ColorType = MyRadioButton.ColorState.Highlight
|
||||
};
|
||||
newButton.LabText.Margin = new Thickness(-2, 0d, 10d, 0d);
|
||||
newButton.Check += (sender, raiseByMouse) =>
|
||||
{
|
||||
_modLoaderFilter = sender.Text == Lang.Text("Common.Option.All") ? null : sender.Text;
|
||||
UpdateFilterResult();
|
||||
};
|
||||
PanModLoaderFilter.Children.Add(newButton);
|
||||
}
|
||||
|
||||
// 自动选择
|
||||
MyRadioButton instanceToCheck = null;
|
||||
MyRadioButton modLoaderToCheck = null;
|
||||
if (!string.IsNullOrEmpty(_targetInstance))
|
||||
{
|
||||
var targetFile = results.FirstOrDefault(v => v.GameVersions.Contains(_targetInstance));
|
||||
if (targetFile is not null)
|
||||
{
|
||||
var targetGroup = GetGroupedVersionName(_targetInstance, groupedDrop, groupedOld);
|
||||
var children = _pageType == ModComp.CompType.Mod
|
||||
? PanInstanceFilter.Children.Cast<UIElement>().Skip(1)
|
||||
: PanInstanceFilter.Children.Cast<UIElement>();
|
||||
foreach (MyRadioButton button in (IEnumerable)children)
|
||||
{
|
||||
if ((button.Text ?? "") != (targetGroup ?? ""))
|
||||
continue;
|
||||
instanceToCheck = button;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_pageType == ModComp.CompType.Mod)
|
||||
if (_targetLoader != ModComp.CompLoaderType.Any)
|
||||
{
|
||||
var targetFile = results.FirstOrDefault(v => v.ModLoaders.Contains(_targetLoader));
|
||||
if (targetFile is not null)
|
||||
{
|
||||
var children = _pageType == ModComp.CompType.Mod
|
||||
? PanInstanceFilter.Children.Cast<UIElement>().Skip(1)
|
||||
: PanInstanceFilter.Children.Cast<UIElement>();
|
||||
foreach (MyRadioButton button in (IEnumerable)children)
|
||||
{
|
||||
if ((button.Text ?? "") != (_targetLoader.ToString() ?? ""))
|
||||
continue;
|
||||
modLoaderToCheck = button;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 注意:在 Mod 下 index 0 是 TextBlock
|
||||
var index = _pageType == ModComp.CompType.Mod ? 1 : 0;
|
||||
if (instanceToCheck is null)
|
||||
instanceToCheck = (MyRadioButton)PanInstanceFilter.Children[index];
|
||||
if (modLoaderToCheck is null && (_pageType == ModComp.CompType.Mod))
|
||||
modLoaderToCheck = (MyRadioButton)PanModLoaderFilter.Children[index];
|
||||
instanceToCheck.Checked = true;
|
||||
if (_pageType == ModComp.CompType.Mod)
|
||||
modLoaderToCheck.Checked = true;
|
||||
}
|
||||
|
||||
// 更新筛选结果(文件列表 UI 化)
|
||||
UpdateFilterResult();
|
||||
}
|
||||
|
||||
private void UpdateFilterResult()
|
||||
{
|
||||
var results = GetResults();
|
||||
if (results is null)
|
||||
return;
|
||||
|
||||
// 1. 预处理基础变量
|
||||
var targetVersionText = _targetLoader != ModComp.CompLoaderType.Any ? _targetLoader + " " : "";
|
||||
var targetCardName = !string.IsNullOrEmpty(_targetInstance) || _targetLoader != ModComp.CompLoaderType.Any
|
||||
? Lang.Text("Download.Comp.Detail.SelectedVersion", targetVersionText, _targetInstance)
|
||||
: "";
|
||||
|
||||
// 使用 HashSet 提高查询性能 O(1)
|
||||
var supportedLoaders =
|
||||
new HashSet<ModComp.CompLoaderType>(Enum.GetValues(typeof(ModComp.CompLoaderType))
|
||||
.Cast<ModComp.CompLoaderType>());
|
||||
var ignoreQuilt = Config.Download.Comp.IgnoreQuilt;
|
||||
var hasMultipleLoaders = _project.ModLoaders.Count > 1;
|
||||
|
||||
// 2. 核心数据归类 (使用 Dictionary 配合 HashSet 去重)
|
||||
var dict = new SortedDictionary<string, List<ModComp.CompFile>>(new CardSorter(targetCardName));
|
||||
dict.Add(Lang.Text("Download.Comp.Detail.VersionGroup.Other"), new List<ModComp.CompFile>());
|
||||
|
||||
// 用于记录每个卡片内已存在的 version,防止 Contains(version) 的 O(n) 消耗
|
||||
var versionDuplicateChecker = new Dictionary<string, HashSet<ModComp.CompFile>>();
|
||||
|
||||
foreach (var version in results)
|
||||
{
|
||||
// 处理普通卡片归类
|
||||
foreach (var gameVersion in version.GameVersions)
|
||||
{
|
||||
// 筛选器预检查
|
||||
var currentGroupedName = GetGroupedVersionName(gameVersion, groupedDrop, groupedOld);
|
||||
if (_instanceFilter is not null && (currentGroupedName ?? "") != (_instanceFilter ?? ""))
|
||||
continue;
|
||||
var verName = GetGroupedVersionName(gameVersion, false, false);
|
||||
var loaders = new List<string>();
|
||||
|
||||
// 判定 Loader 逻辑
|
||||
if (hasMultipleLoaders && version.Type == ModComp.CompType.Mod &&
|
||||
McInstanceInfo.IsFormatFit(verName))
|
||||
{
|
||||
foreach (var loader in version.ModLoaders)
|
||||
{
|
||||
if (loader == ModComp.CompLoaderType.Quilt && ignoreQuilt)
|
||||
continue;
|
||||
if (!supportedLoaders.Contains(loader))
|
||||
continue;
|
||||
|
||||
// 模组加载器筛选器
|
||||
if (_modLoaderFilter is not null && (loader.ToString() ?? "") != (_modLoaderFilter ?? ""))
|
||||
continue;
|
||||
|
||||
loaders.Add(loader + " ");
|
||||
}
|
||||
|
||||
if (loaders.Count == 0 && _modLoaderFilter is not null) continue;
|
||||
}
|
||||
|
||||
if (loaders.Count == 0)
|
||||
loaders.Add("");
|
||||
|
||||
// 填充数据
|
||||
foreach (var loaderPrefix in loaders)
|
||||
{
|
||||
var targetKey = loaderPrefix + verName;
|
||||
AddVersionToDict(dict, versionDuplicateChecker, targetKey, version);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理“所选版本”卡片 (逻辑合并,减少二次循环)
|
||||
if (!string.IsNullOrEmpty(targetCardName))
|
||||
{
|
||||
var isMatchFilter = _instanceFilter is null ||
|
||||
GetGroupedVersionName(_targetInstance, groupedDrop, groupedOld)
|
||||
.StartsWithF(_instanceFilter);
|
||||
|
||||
if (isMatchFilter && version.GameVersions.Contains(_targetInstance))
|
||||
if (_targetLoader == ModComp.CompLoaderType.Any || version.ModLoaders.Contains(_targetLoader))
|
||||
// 再次检查 version 是否符合筛选器(针对该文件的所有游戏版本)
|
||||
if (_instanceFilter is null || version.GameVersions.Any(v =>
|
||||
(GetGroupedVersionName(v, groupedDrop, groupedOld) ?? "") == (_instanceFilter ?? "")))
|
||||
AddVersionToDict(dict, versionDuplicateChecker, targetCardName, version);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 渲染 UI
|
||||
try
|
||||
{
|
||||
PanResults.Children.Clear();
|
||||
var additional = ModMain.frmMain.pageCurrent.additional;
|
||||
var additionalTitles = additional is not null
|
||||
? additional.Value.ExpandedTitles
|
||||
: new List<string>();
|
||||
|
||||
foreach (var pair in dict)
|
||||
{
|
||||
if (pair.Value.Count == 0)
|
||||
continue;
|
||||
|
||||
// 创建卡片组件
|
||||
var newCard = new MyCard
|
||||
{
|
||||
Title = pair.Key,
|
||||
Margin = new Thickness(0d, 0d, 0d, 15d)
|
||||
};
|
||||
|
||||
// 闭包引用:避免在 Sub 内做高耗时操作
|
||||
var files = pair.Value;
|
||||
var currentKey = pair.Key;
|
||||
|
||||
var newStack = new StackPanel
|
||||
{
|
||||
Margin = new Thickness(20d, MyCard.SwapedHeight, 18d, 0d),
|
||||
VerticalAlignment = VerticalAlignment.Top,
|
||||
Tag = files
|
||||
};
|
||||
|
||||
newCard.Children.Add(newStack);
|
||||
newCard.SwapControl = newStack;
|
||||
|
||||
// 延迟加载安装项的逻辑
|
||||
newCard.InstallMethod = stack =>
|
||||
{
|
||||
var list = (List<ModComp.CompFile>)stack.Tag;
|
||||
// 排序和去重检查
|
||||
list.Sort((a, b) => b.ReleaseDate.CompareTo(a.ReleaseDate));
|
||||
var distinctCount = list.Select(f => f.DisplayName).Distinct().Count();
|
||||
var badDisplayName = distinctCount != list.Count;
|
||||
|
||||
// 批量添加子项
|
||||
switch (_project.Type)
|
||||
{
|
||||
case ModComp.CompType.ModPack:
|
||||
{
|
||||
foreach (var item in list)
|
||||
stack.Children.Add(item.ToListItem(
|
||||
(sender, e) => ModMain.frmDownloadCompDetail.Install_Click((MyListItem)sender, e),
|
||||
ModMain.frmDownloadCompDetail.Save_Click, badDisplayName));
|
||||
break;
|
||||
}
|
||||
case ModComp.CompType.World:
|
||||
{
|
||||
foreach (var item in list)
|
||||
stack.Children.Add(item.ToListItem(
|
||||
(sender, e) =>
|
||||
ModMain.frmDownloadCompDetail.InstallWorld_Click((MyListItem)sender, e),
|
||||
ModMain.frmDownloadCompDetail.Save_Click, badDisplayName));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
ModComp.CompFilesCardPreload(stack, list);
|
||||
foreach (var item in list)
|
||||
stack.Children.Add(item.ToListItem(ModMain.frmDownloadCompDetail.Save_Click,
|
||||
badDisplayName: badDisplayName));
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
PanResults.Children.Add(newCard);
|
||||
|
||||
// 展开逻辑
|
||||
if ((currentKey ?? "") == (targetCardName ?? "") || additionalTitles.Contains(newCard.Title))
|
||||
newCard.StackInstall();
|
||||
else
|
||||
newCard.IsSwapped = true;
|
||||
|
||||
// 特殊提示
|
||||
if (currentKey == Lang.Text("Download.Comp.Detail.VersionGroup.Other"))
|
||||
newStack.Children.Add(new MyHint
|
||||
{
|
||||
Text = Lang.Text("Download.Comp.Detail.VersionRecognitionDelayHint"),
|
||||
Theme = MyHint.Themes.Yellow,
|
||||
Margin = new Thickness(5d, 0d, 0d, 8d)
|
||||
});
|
||||
}
|
||||
|
||||
// 单卡片自动展开
|
||||
if (PanResults.Children.Count == 1) ((MyCard)PanResults.Children[0]).IsSwapped = false;
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"可视化工程下载列表出错",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Download.Comp.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 辅助方法:向字典添加数据并处理去重
|
||||
/// </summary>
|
||||
private void AddVersionToDict(SortedDictionary<string, List<ModComp.CompFile>> dict,
|
||||
Dictionary<string, HashSet<ModComp.CompFile>> checker, string key, ModComp.CompFile version)
|
||||
{
|
||||
if (!dict.ContainsKey(key))
|
||||
{
|
||||
dict.Add(key, new List<ModComp.CompFile>());
|
||||
checker.Add(key, new HashSet<ModComp.CompFile>());
|
||||
}
|
||||
|
||||
// 使用 HashSet.Add 判断是否重复,比 List.Contains 快得多
|
||||
if (checker[key].Add(version)) dict[key].Add(version);
|
||||
}
|
||||
|
||||
private string GetGroupedVersionName(string name, bool groupedByDrop, bool foldOld)
|
||||
{
|
||||
if (name is null)
|
||||
return Lang.Text("Download.Comp.Detail.VersionGroup.Other");
|
||||
if (name.Contains('w'))
|
||||
return Lang.Text("Download.Comp.Detail.VersionGroup.Snapshot");
|
||||
if (foldOld && McInstanceInfo.VersionToDrop(name, true) < 120)
|
||||
return Lang.Text("Download.Comp.Detail.VersionGroup.Old");
|
||||
if (groupedByDrop)
|
||||
{
|
||||
var drop = McInstanceInfo.VersionToDrop(name, true);
|
||||
if (drop >= 0)
|
||||
return McInstanceInfo.DropToVersion(drop);
|
||||
return name;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<local:MyPageRight
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" x:Class="PCL.PageDownloadDataPack"
|
||||
PanScroll="{Binding ElementName=Content}">
|
||||
<local:PageComp x:Name="Content" PageType="DataPack">
|
||||
<local:MyComboBoxItem Tag="" Content="{DynamicResource Common.Option.All}" IsSelected="True" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.WorldGen}" Tag="/worldgen" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Technology}" Tag="6951/technology" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.GameMechanics}" Tag="/game-mechanics" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Transportation}" Tag="/transportation" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Storage}" Tag="/storage" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Magic}" Tag="6952/magic" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Adventure}" Tag="6948/adventure" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.DataPack.Fantasy}" Tag="6949/" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Decoration}" Tag="/decoration" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Mobs}" Tag="/mobs" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Utility}" Tag="6953/utility" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Equipment}" Tag="/equipment" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Optimization}" Tag="/optimization" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Server}" Tag="/social" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Library}" Tag="6950/library" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Mod.ModRelated}" Tag="6946/" />
|
||||
</local:PageComp>
|
||||
</local:MyPageRight>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageDownloadDataPack
|
||||
{
|
||||
public PageDownloadDataPack()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<local:MyPageRight
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" x:Class="PCL.PageDownloadMod"
|
||||
PanScroll="{Binding ElementName=Content}">
|
||||
<local:PageComp x:Name="Content" PageType="Mod">
|
||||
<local:MyComboBoxItem Content="{DynamicResource Common.Option.All}" Tag="" IsSelected="True" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.WorldGen}" Tag="406/worldgen" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Biomes}" Tag="407/" Margin="15,0,0,0" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Dimensions}" Tag="410/" Margin="15,0,0,0" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.OresResources}" Tag="408/" Margin="15,0,0,0" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Structures}" Tag="409/" Margin="15,0,0,0" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Technology}" Tag="412/technology" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.PipesLogistics}" Tag="415/" Margin="15,0,0,0" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Automation}" Tag="4843/" Margin="15,0,0,0" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Energy}" Tag="417/" Margin="15,0,0,0" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Redstone}" Tag="4558/" Margin="15,0,0,0" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.FoodCooking}" Tag="436/food" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Farming}" Tag="416/" Margin="15,0,0,0" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.GameMechanics}" Tag="/game-mechanics" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Transportation}" Tag="414/transportation" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Storage}" Tag="420/storage" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Magic}" Tag="419/magic" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Adventure}" Tag="422/adventure" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Decoration}" Tag="424/decoration" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Mobs}" Tag="411/mobs" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Utility}" Tag="5191/utility" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Equipment}" Tag="434/equipment" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Creative}" Tag="9026/" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Optimization}" Tag="6814/optimization" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Display}" Tag="423/" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Server}" Tag="435/social" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Library}" Tag="421/library" />
|
||||
</local:PageComp>
|
||||
</local:MyPageRight>
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageDownloadMod
|
||||
{
|
||||
public PageDownloadMod()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<local:MyPageRight
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" x:Class="PCL.PageDownloadPack"
|
||||
PanScroll="{Binding ElementName=Content}">
|
||||
<local:PageComp x:Name="Content" PageType="ModPack">
|
||||
<local:MyComboBoxItem Tag="" Content="{DynamicResource Common.Option.All}" IsSelected="True" />
|
||||
<local:MyComboBoxItem Tag="4484/" Content="{DynamicResource Download.Comp.Category.Multiplayer}" />
|
||||
<local:MyComboBoxItem Tag="/optimization" Content="{DynamicResource Download.Comp.Category.Optimization}" />
|
||||
<local:MyComboBoxItem Tag="4479/challenging" Content="{DynamicResource Download.Comp.Category.Modpack.Hardcore}" />
|
||||
<local:MyComboBoxItem Tag="4483/combat" Content="{DynamicResource Download.Comp.Category.Combat}" />
|
||||
<local:MyComboBoxItem Tag="4478/quests" Content="{DynamicResource Download.Comp.Category.Modpack.Quests}" />
|
||||
<local:MyComboBoxItem Tag="4472/technology" Content="{DynamicResource Download.Comp.Category.Technology}" />
|
||||
<local:MyComboBoxItem Tag="4473/magic" Content="{DynamicResource Download.Comp.Category.Magic}" />
|
||||
<local:MyComboBoxItem Tag="4475/adventure" Content="{DynamicResource Download.Comp.Category.Adventure}" />
|
||||
<local:MyComboBoxItem Tag="/kitchen-sink" Content="{DynamicResource Download.Comp.Category.Modpack.KitchenSink}" />
|
||||
<local:MyComboBoxItem Tag="4476/" Content="{DynamicResource Download.Comp.Category.Modpack.Exploration}" />
|
||||
<local:MyComboBoxItem Tag="4477/" Content="{DynamicResource Download.Comp.Category.Modpack.MiniGame}" />
|
||||
<local:MyComboBoxItem Tag="4471/" Content="{DynamicResource Download.Comp.Category.Modpack.SciFi}" />
|
||||
<local:MyComboBoxItem Tag="4736/" Content="{DynamicResource Download.Comp.Category.Modpack.Skyblock}" />
|
||||
<local:MyComboBoxItem Tag="5128/" Content="{DynamicResource Download.Comp.Category.Modpack.VanillaPlus}" />
|
||||
<local:MyComboBoxItem Tag="4487/" Content="{DynamicResource Download.Comp.Category.Modpack.Ftb}" ToolTip="{DynamicResource Download.Comp.Category.Modpack.Ftb.ToolTip}" ToolTipService.HorizontalOffset="70" />
|
||||
<local:MyComboBoxItem Tag="4480/" Content="{DynamicResource Download.Comp.Category.Modpack.MapBased}" />
|
||||
<local:MyComboBoxItem Tag="4481/lightweight" Content="{DynamicResource Download.Comp.Category.Modpack.SmallLight}" />
|
||||
<local:MyComboBoxItem Tag="4482/" Content="{DynamicResource Download.Comp.Category.Modpack.ExtraLarge}" />
|
||||
</local:PageComp>
|
||||
</local:MyPageRight>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageDownloadPack
|
||||
{
|
||||
public PageDownloadPack()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
<local:MyPageRight
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" x:Class="PCL.PageDownloadResourcePack"
|
||||
PanScroll="{Binding ElementName=Content}">
|
||||
<local:PageComp x:Name="Content" PageType="ResourcePack">
|
||||
<local:MyComboBoxItem Tag="" Content="{DynamicResource Common.Option.All}" IsSelected="True" />
|
||||
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Style}" IsEnabled="False" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.VanillaLike}" Tag="403/vanilla-like" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Realistic}" Tag="400/realistic" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Modern}" Tag="401/" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Medieval}" Tag="402/" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Steampunk}" Tag="399/" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Themed}" Tag="/themed" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Simplistic}" Tag="/simplistic" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Decoration}" Tag="/decoration" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Combat}" Tag="/combat" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Utility}" Tag="/utility" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Tweaks}" Tag="/tweaks" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Cursed}" Tag="/cursed" />
|
||||
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Feature}" IsEnabled="False" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Entities}" Tag="/entities" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Audio}" Tag="/audio" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Fonts}" Tag="5244/fonts" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Models}" Tag="/models" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Locale}" Tag="/locale" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Gui}" Tag="/gui" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.CoreShaders}" Tag="/core-shaders" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Animated}" Tag="404/" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ModSupport}" Tag="4465/modded" />
|
||||
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Resolution}" IsEnabled="False" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution8xOrLower}" Tag="/8x-" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution16x}" Tag="393/16x" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution32x}" Tag="394/32x" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution48x}" Tag="/48x" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution64x}" Tag="395/64x" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution128x}" Tag="396/128x" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution256x}" Tag="397/256x" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution512xOrHigher}" Tag="398/512x+" />
|
||||
</local:PageComp>
|
||||
</local:MyPageRight>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageDownloadResourcePack
|
||||
{
|
||||
public PageDownloadResourcePack()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<local:MyPageRight
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" x:Class="PCL.PageDownloadShader"
|
||||
PanScroll="{Binding ElementName=Content}">
|
||||
<local:PageComp x:Name="Content" PageType="Shader">
|
||||
<local:MyComboBoxItem Tag="" Content="{DynamicResource Common.Option.All}" IsSelected="True" />
|
||||
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Style}" IsEnabled="False" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.VanillaLike}" Tag="6555/vanilla-like" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Fantasy}" Tag="6554/fantasy" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Realistic}" Tag="6553/realistic" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.SemiRealistic}" Tag="/semi-realistic" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Cartoon}" Tag="/cartoon" />
|
||||
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Feature}" IsEnabled="False" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ColoredLighting}" Tag="/colored-lighting" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.PathTracing}" Tag="/path-tracing" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Pbr}" Tag="/pbr" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Reflections}" Tag="/reflections" />
|
||||
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Performance}" IsEnabled="False" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.PerformancePotato}" Tag="/potato" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.PerformanceLow}" Tag="/low" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.PerformanceMedium}" Tag="/medium" />
|
||||
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.PerformanceHigh}" Tag="/high" />
|
||||
</local:PageComp>
|
||||
</local:MyPageRight>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageDownloadShader
|
||||
{
|
||||
public PageDownloadShader()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<local:MyPageRight
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" x:Class="PCL.PageDownloadWorld"
|
||||
PanScroll="{Binding ElementName=Content}">
|
||||
<local:PageComp x:Name="Content" PageType="World" SupportModrinth="False">
|
||||
<local:MyComboBoxItem Tag="" Content="{DynamicResource Common.Option.All}" IsSelected="True" />
|
||||
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.World.Adventure}" Tag="248/" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.World.Creative}" Tag="249/" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Modpack.MiniGame}" Tag="250/" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.World.Parkour}" Tag="251/" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.World.Puzzle}" Tag="252/" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.World.Survival}" Tag="253/" />
|
||||
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.World.ModWorld}" Tag="4464/" />
|
||||
</local:PageComp>
|
||||
</local:MyPageRight>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageDownloadWorld
|
||||
{
|
||||
public PageDownloadWorld()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user