mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-03-19 08:19:48 +08:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -386,7 +386,7 @@ namespace LogParse
|
||||
return "Invalid input time format. Please use 'yyyy-MM-dd HH:mm:ss'.";
|
||||
}
|
||||
}
|
||||
public static string GenerHtmlByConfigGroupEntity(List<ConfigGroupEntity> configGroups, GameInfo gameInfo,LogParseConfig.ScriptGroupLogParseConfig scriptGroupLogParseConfig)
|
||||
public static string GenerHtmlByConfigGroupEntity(List<ConfigGroupEntity> configGroups, GameInfo? gameInfo,LogParseConfig.ScriptGroupLogParseConfig scriptGroupLogParseConfig)
|
||||
{
|
||||
(string name, Func<ConfigTask, string> value)[] colConfigs =
|
||||
[
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using BetterGenshinImpact.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using BetterGenshinImpact.Model;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
@@ -54,4 +57,103 @@ public class FileTreeNodeHelper
|
||||
parentNode.Children.Add(fileNode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据路径过滤树形结构,排除指定路径的节点
|
||||
/// </summary>
|
||||
/// <typeparam name="T">节点数据类型</typeparam>
|
||||
/// <param name="nodes">树形结构的根节点集合</param>
|
||||
/// <param name="folderNames">要排除的路径集合</param>
|
||||
/// <returns>过滤后的树形结构</returns>
|
||||
public static ObservableCollection<FileTreeNode<T>> FilterTree<T>(
|
||||
ObservableCollection<FileTreeNode<T>> nodes,
|
||||
List<string> folderNames)
|
||||
{
|
||||
// 递归过滤节点
|
||||
ObservableCollection<FileTreeNode<T>> FilterNodes(ObservableCollection<FileTreeNode<T>> inputNodes, List<string> paths)
|
||||
{
|
||||
var filteredNodes = new ObservableCollection<FileTreeNode<T>>();
|
||||
|
||||
foreach (var node in inputNodes)
|
||||
{
|
||||
// 获取当前层需要排除的路径
|
||||
var matchedPaths = paths
|
||||
.Where(path => IsPathMatch(node.FileName ??"", path))
|
||||
.Select(path => GetRemainingPath(node.FileName ?? "" , path))
|
||||
.Where(remainingPath => remainingPath != null)
|
||||
.ToList();
|
||||
|
||||
// 如果当前路径完全匹配,跳过当前节点
|
||||
if (matchedPaths.Any(path => path == ""))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 递归对子节点过滤
|
||||
node.Children = FilterNodes(node.Children, matchedPaths);
|
||||
|
||||
// 添加过滤后的节点
|
||||
filteredNodes.Add(node);
|
||||
}
|
||||
|
||||
return filteredNodes;
|
||||
}
|
||||
|
||||
return FilterNodes(nodes, folderNames);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断文件路径是否匹配多级路径的前缀
|
||||
/// </summary>
|
||||
/// <param name="fileName">当前节点路径</param>
|
||||
/// <param name="path">目标路径</param>
|
||||
/// <returns>是否匹配</returns>
|
||||
private static bool IsPathMatch(string fileName, string path)
|
||||
{
|
||||
// 匹配路径是否以指定前缀开始,路径分隔符对齐
|
||||
return path.StartsWith(fileName, StringComparison.OrdinalIgnoreCase)
|
||||
&& (path.Length == fileName.Length || path[fileName.Length] == '\\');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取路径中去掉当前节点后的剩余路径
|
||||
/// </summary>
|
||||
/// <param name="fileName">当前节点路径</param>
|
||||
/// <param name="path">完整路径</param>
|
||||
/// <returns>剩余路径,如果不匹配返回 null</returns>
|
||||
private static string? GetRemainingPath(string fileName, string path)
|
||||
{
|
||||
if (IsPathMatch(fileName, path))
|
||||
{
|
||||
return path.Length > fileName.Length ? path.Substring(fileName.Length + 1) : "";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public static ObservableCollection<FileTreeNode<T>> FilterEmptyNodes<T>(ObservableCollection<FileTreeNode<T>> nodes)
|
||||
{
|
||||
// 递归过滤节点
|
||||
ObservableCollection<FileTreeNode<T>> Filter(ObservableCollection<FileTreeNode<T>> inputNodes)
|
||||
{
|
||||
var filteredNodes = new ObservableCollection<FileTreeNode<T>>();
|
||||
|
||||
foreach (var node in inputNodes)
|
||||
{
|
||||
// 递归处理子节点
|
||||
node.Children = Filter(node.Children);
|
||||
|
||||
// 如果是目录并且没有子节点,跳过当前节点
|
||||
if (node.IsDirectory && !node.Children.Any())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 其他情况保留节点
|
||||
filteredNodes.Add(node);
|
||||
}
|
||||
|
||||
return filteredNodes;
|
||||
}
|
||||
|
||||
return Filter(nodes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,6 +331,22 @@
|
||||
<MenuItem Command="{Binding DeleteScriptCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem}"
|
||||
Header="移除" />
|
||||
<MenuItem Command="{Binding DeleteScriptByFolderCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem}"
|
||||
Header="根据文件夹移除">
|
||||
<MenuItem.Style>
|
||||
<Style TargetType="MenuItem">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger
|
||||
Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem.Type}"
|
||||
Value="Pathing">
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</MenuItem.Style>
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
</ListView.ContextMenu>
|
||||
<ListView.Style>
|
||||
|
||||
@@ -11,7 +11,6 @@ using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using BetterGenshinImpact.Core.Config;
|
||||
using BetterGenshinImpact.Core.Script;
|
||||
using BetterGenshinImpact.Core.Script.Group;
|
||||
@@ -30,6 +29,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using LogParse;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharpCompress;
|
||||
using Wpf.Ui;
|
||||
using Wpf.Ui.Controls;
|
||||
using Wpf.Ui.Violeta.Controls;
|
||||
@@ -107,25 +107,32 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
|
||||
[RelayCommand]
|
||||
private void ClearTasks()
|
||||
{
|
||||
if (SelectedScriptGroup == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
SelectedScriptGroup.Projects.Clear();
|
||||
WriteScriptGroup(SelectedScriptGroup);
|
||||
}
|
||||
[RelayCommand]
|
||||
private async Task OpenLogParse()
|
||||
{
|
||||
|
||||
GameInfo gameInfo = null;
|
||||
if (SelectedScriptGroup == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
GameInfo? gameInfo = null;
|
||||
var config = LogParse.LogParse.LoadConfig();
|
||||
if (!string.IsNullOrEmpty(config.Cookie))
|
||||
{
|
||||
config.CookieDictionary.TryGetValue(config.Cookie, out gameInfo);
|
||||
}
|
||||
LogParseConfig.ScriptGroupLogParseConfig sgpc;
|
||||
if (!config.ScriptGroupLogDictionary.TryGetValue(_selectedScriptGroup.Name,out sgpc))
|
||||
LogParseConfig.ScriptGroupLogParseConfig? sgpc;
|
||||
if (!config.ScriptGroupLogDictionary.TryGetValue(SelectedScriptGroup.Name,out sgpc))
|
||||
{
|
||||
sgpc=new LogParseConfig.ScriptGroupLogParseConfig();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 创建 StackPanel
|
||||
var stackPanel = new StackPanel
|
||||
@@ -265,9 +272,9 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
|
||||
PrimaryButtonText = "确定",
|
||||
Owner = Application.Current.MainWindow,
|
||||
};
|
||||
questionButton.Click += (sender, args) =>
|
||||
{
|
||||
|
||||
void OnQuestionButtonOnClick(object sender, RoutedEventArgs args)
|
||||
{
|
||||
WebpageWindow cookieWin = new()
|
||||
{
|
||||
Title = "日志分析",
|
||||
@@ -278,8 +285,9 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
|
||||
};
|
||||
cookieWin.NavigateToHtml(TravelsDiaryDetailManager.generHtmlMessage());
|
||||
cookieWin.Show();
|
||||
}
|
||||
|
||||
};
|
||||
questionButton.Click += OnQuestionButtonOnClick;
|
||||
|
||||
//对象赋值
|
||||
rangeComboBox.SelectedValue = sgpc.RangeValue;
|
||||
@@ -305,7 +313,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
|
||||
sgpc.HoeingDelay = hoeingDelayTextBox.Text;
|
||||
|
||||
config.Cookie = cookieValue;
|
||||
config.ScriptGroupLogDictionary[_selectedScriptGroup.Name]=sgpc;
|
||||
config.ScriptGroupLogDictionary[SelectedScriptGroup.Name]=sgpc;
|
||||
|
||||
LogParse.LogParse.WriteConfigFile(config);
|
||||
|
||||
@@ -339,7 +347,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
|
||||
Toast.Warning("未填写cookie,此次将不启用锄地统计!");
|
||||
}
|
||||
//真正存储的gameinfo
|
||||
GameInfo realGameInfo = gameInfo;
|
||||
GameInfo? realGameInfo = gameInfo;
|
||||
//统计锄地开关打开,并且不为cookie不为空
|
||||
if ((hoeingStatsSwitch.IsChecked ?? false) && !string.IsNullOrEmpty(cookieValue))
|
||||
{
|
||||
@@ -350,7 +358,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
|
||||
Toast.Success($"米游社数据获取成功,开始进行解析,请耐心等待!");
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception)
|
||||
{
|
||||
if (realGameInfo!=null)
|
||||
{
|
||||
@@ -378,7 +386,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
|
||||
var configGroupEntities = LogParse.LogParse.ParseFile(fs);
|
||||
if (rangeValue == "CurrentConfig") {
|
||||
//Toast.Success(_selectedScriptGroup.Name);
|
||||
configGroupEntities =configGroupEntities.Where(item => _selectedScriptGroup.Name == item.Name).ToList();
|
||||
configGroupEntities =configGroupEntities.Where(item => SelectedScriptGroup.Name == item.Name).ToList();
|
||||
}
|
||||
if (configGroupEntities.Count == 0)
|
||||
{
|
||||
@@ -414,11 +422,11 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
|
||||
ScriptRepoUpdater.Instance.OpenLocalRepoInWebView();
|
||||
}
|
||||
[RelayCommand]
|
||||
private async Task UpdateTasks()
|
||||
private void UpdateTasks()
|
||||
{
|
||||
List<ScriptGroupProject> projects = new();
|
||||
List<ScriptGroupProject> oldProjects = new();
|
||||
oldProjects.AddRange(SelectedScriptGroup?.Projects);
|
||||
oldProjects.AddRange(SelectedScriptGroup?.Projects ?? []);
|
||||
var oldcount = oldProjects.Count;
|
||||
List<string> folderNames = new();
|
||||
foreach (var project in oldProjects)
|
||||
@@ -451,25 +459,25 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
|
||||
}
|
||||
}
|
||||
|
||||
SelectedScriptGroup.Projects.Clear();
|
||||
SelectedScriptGroup?.Projects.Clear();
|
||||
foreach (var scriptGroupProject in projects)
|
||||
{
|
||||
SelectedScriptGroup?.AddProject(scriptGroupProject);
|
||||
}
|
||||
|
||||
Toast.Success($"增加了{projects.Count - oldcount}个路径追踪任务");
|
||||
WriteScriptGroup(SelectedScriptGroup);
|
||||
|
||||
if (SelectedScriptGroup != null) WriteScriptGroup(SelectedScriptGroup);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ReverseTaskOrder()
|
||||
{
|
||||
|
||||
List<ScriptGroupProject> projects = new();
|
||||
projects.AddRange(SelectedScriptGroup?.Projects.Reverse());
|
||||
SelectedScriptGroup.Projects.Clear();
|
||||
projects.ForEach(item=>SelectedScriptGroup.Projects.Add(item));
|
||||
WriteScriptGroup(SelectedScriptGroup);
|
||||
projects.AddRange(SelectedScriptGroup?.Projects.Reverse() ?? []);
|
||||
SelectedScriptGroup?.Projects.Clear();
|
||||
projects.ForEach(item=>SelectedScriptGroup?.Projects.Add(item));
|
||||
if (SelectedScriptGroup != null) WriteScriptGroup(SelectedScriptGroup);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -498,8 +506,12 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
|
||||
else
|
||||
{
|
||||
var newScriptGroup =JsonSerializer.Deserialize<ScriptGroup>(JsonSerializer.Serialize(item)) ;
|
||||
newScriptGroup.Name = str;
|
||||
ScriptGroups.Add(newScriptGroup);
|
||||
if (newScriptGroup != null)
|
||||
{
|
||||
newScriptGroup.Name = str;
|
||||
ScriptGroups.Add(newScriptGroup);
|
||||
}
|
||||
|
||||
//WriteScriptGroup(newScriptGroup);
|
||||
}
|
||||
}
|
||||
@@ -628,12 +640,26 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
|
||||
private ScrollViewer CreatePathingScriptSelectionPanel(IEnumerable<FileTreeNode<PathingTask>> list)
|
||||
{
|
||||
var stackPanel = new StackPanel();
|
||||
CheckBox excludeCheckBox = new CheckBox
|
||||
{
|
||||
Content = "排除已选择过的目录",
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
};
|
||||
stackPanel.Children.Add(excludeCheckBox);
|
||||
|
||||
var filterTextBox = new TextBox
|
||||
{
|
||||
Margin = new Thickness(0, 0, 0, 10),
|
||||
PlaceholderText = "输入筛选条件..."
|
||||
PlaceholderText = "输入筛选条件...",
|
||||
};
|
||||
filterTextBox.TextChanged += delegate
|
||||
{
|
||||
ApplyFilter(stackPanel, list, filterTextBox.Text, excludeCheckBox.IsChecked);
|
||||
};
|
||||
excludeCheckBox.Click += delegate
|
||||
{
|
||||
ApplyFilter(stackPanel, list, filterTextBox.Text, excludeCheckBox.IsChecked);
|
||||
};
|
||||
filterTextBox.TextChanged += (s, e) => ApplyFilter(stackPanel, list, filterTextBox.Text);
|
||||
stackPanel.Children.Add(filterTextBox);
|
||||
AddNodesToPanel(stackPanel, list, 0, filterTextBox.Text);
|
||||
|
||||
@@ -647,14 +673,50 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
|
||||
return scrollViewer;
|
||||
}
|
||||
|
||||
private void ApplyFilter(StackPanel parentPanel, IEnumerable<FileTreeNode<PathingTask>> nodes, string filter)
|
||||
private void ApplyFilter(StackPanel parentPanel, IEnumerable<FileTreeNode<PathingTask>> nodes, string filter,bool? excludeSelectedFolder = false)
|
||||
{
|
||||
if (parentPanel.Children.Count > 0 && parentPanel.Children[0] is TextBox filterTextBox)
|
||||
if (parentPanel.Children.Count > 0)
|
||||
{
|
||||
List<UIElement> removeElements = new List<UIElement>();
|
||||
foreach (UIElement parentPanelChild in parentPanel.Children)
|
||||
{
|
||||
if (parentPanelChild is FrameworkElement frameworkElement && frameworkElement.Name.StartsWith("dynamic_"))
|
||||
{
|
||||
removeElements.Add(frameworkElement);
|
||||
}
|
||||
|
||||
}
|
||||
removeElements.ForEach(parentPanel.Children.Remove);
|
||||
}
|
||||
|
||||
if (excludeSelectedFolder ?? false)
|
||||
{
|
||||
|
||||
List<string> skipFolderNames = SelectedScriptGroup?.Projects.ToList().Select(item=>item.FolderName).Distinct().ToList() ?? [];
|
||||
//复制Nodes
|
||||
string jsonString = JsonSerializer.Serialize(nodes);
|
||||
var copiedNodes = JsonSerializer.Deserialize<ObservableCollection<FileTreeNode<PathingTask>>>(jsonString);
|
||||
if (copiedNodes!=null)
|
||||
{
|
||||
//路径过滤
|
||||
copiedNodes = FileTreeNodeHelper.FilterTree(copiedNodes, skipFolderNames);
|
||||
copiedNodes = FileTreeNodeHelper.FilterEmptyNodes(copiedNodes);
|
||||
AddNodesToPanel(parentPanel, copiedNodes, 0,filter);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
AddNodesToPanel(parentPanel, nodes, 0,filter);
|
||||
}
|
||||
|
||||
|
||||
/*if (parentPanel.Children.Count > 0 && parentPanel.Children[1] is TextBox filterTextBox)
|
||||
{
|
||||
parentPanel.Children.Clear();
|
||||
parentPanel.Children.Add(filterTextBox); // 保留筛选框
|
||||
AddNodesToPanel(parentPanel, nodes, 0, filter);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
private void AddNodesToPanel(StackPanel parentPanel, IEnumerable<FileTreeNode<PathingTask>> nodes, int depth, string filter)
|
||||
@@ -671,6 +733,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
|
||||
Content = node.FileName,
|
||||
Tag = node.FilePath,
|
||||
Margin = new Thickness(depth * 30, 0, 0, 0) // 根据深度计算Margin
|
||||
,Name = "dynamic_"+Guid.NewGuid().ToString().Replace("-","_")
|
||||
};
|
||||
|
||||
if (node.IsDirectory)
|
||||
@@ -683,6 +746,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
|
||||
Header = checkBox,
|
||||
Content = childPanel,
|
||||
IsExpanded = false // 默认不展开
|
||||
,Name = "dynamic_"+Guid.NewGuid().ToString().Replace("-","_")
|
||||
};
|
||||
|
||||
checkBox.Checked += (s, e) => SetChildCheckBoxesState(childPanel, true);
|
||||
@@ -793,7 +857,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
|
||||
[RelayCommand]
|
||||
private void AddNextFlag(ScriptGroupProject? item)
|
||||
{
|
||||
if (item == null)
|
||||
if (item == null || SelectedScriptGroup == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -805,8 +869,8 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
|
||||
nextScheduledTask.Remove(nst);
|
||||
}
|
||||
|
||||
nextScheduledTask.Add((SelectedScriptGroup?.Name, item.Index, item.FolderName, item.Name));
|
||||
foreach (var item1 in SelectedScriptGroup.Projects)
|
||||
nextScheduledTask.Add((SelectedScriptGroup?.Name ?? "", item.Index, item.FolderName, item.Name));
|
||||
foreach (var item1 in SelectedScriptGroup?.Projects ?? [])
|
||||
{
|
||||
item1.NextFlag = false;
|
||||
}
|
||||
@@ -878,7 +942,15 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
|
||||
Toast.Warning("只有JS脚本才有自定义配置");
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void OnDeleteScriptByFolder(ScriptGroupProject? item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
SelectedScriptGroup?.Projects.ToList().Where(item2=>item2.FolderName == item.FolderName).ForEach(OnDeleteScript);
|
||||
}
|
||||
[RelayCommand]
|
||||
public void OnDeleteScript(ScriptGroupProject? item)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user