Compare commits
2 Commits
8416c3f6f0
...
d6bac4bff6
| Author | SHA1 | Date | |
|---|---|---|---|
| d6bac4bff6 | |||
| 5b76abb0ae |
@@ -1,8 +1,8 @@
|
|||||||
using System.Windows;
|
using CodeContextGenerator.Interfaces;
|
||||||
using CodeContextGenerator.Interfaces;
|
|
||||||
using CodeContextGenerator.Services;
|
using CodeContextGenerator.Services;
|
||||||
using CodeContextGenerator.ViewModels;
|
using CodeContextGenerator.ViewModels;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
namespace CodeContextGenerator;
|
namespace CodeContextGenerator;
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,26 @@
|
|||||||
using System;
|
using System.Globalization;
|
||||||
using System.Globalization;
|
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
|
|
||||||
namespace CodeContextGenerator.Converters
|
namespace CodeContextGenerator.Converters;
|
||||||
|
|
||||||
|
public class BooleanToVisibilityConverter : IValueConverter
|
||||||
{
|
{
|
||||||
public class BooleanToVisibilityConverter : IValueConverter
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
{
|
{
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
bool boolValue = value is true;
|
||||||
|
|
||||||
|
// Проверяем параметр для инвертирования
|
||||||
|
if (parameter is string paramStr && paramStr.Equals("Inverted", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (value is bool boolValue)
|
boolValue = !boolValue;
|
||||||
{
|
|
||||||
return boolValue ? Visibility.Visible : Visibility.Collapsed;
|
|
||||||
}
|
|
||||||
return Visibility.Collapsed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
return boolValue ? Visibility.Visible : Visibility.Collapsed;
|
||||||
{
|
}
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,6 +15,7 @@ public partial class FileItem : ObservableObject
|
|||||||
private bool isDirectory;
|
private bool isDirectory;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
|
[NotifyPropertyChangedFor(nameof(HasSelectedChildren))]
|
||||||
private bool? isSelected;
|
private bool? isSelected;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
@@ -22,6 +23,11 @@ public partial class FileItem : ObservableObject
|
|||||||
|
|
||||||
public ObservableCollection<FileItem> Children { get; } = new ObservableCollection<FileItem>();
|
public ObservableCollection<FileItem> Children { get; } = new ObservableCollection<FileItem>();
|
||||||
|
|
||||||
|
// Событие, вызываемое при изменении состояния выбора
|
||||||
|
public event EventHandler SelectionChanged;
|
||||||
|
|
||||||
|
public bool HasSelectedChildren => Children.Any(c => c.IsSelected == true || c.HasSelectedChildren);
|
||||||
|
|
||||||
partial void OnIsSelectedChanged(bool? oldValue, bool? newValue)
|
partial void OnIsSelectedChanged(bool? oldValue, bool? newValue)
|
||||||
{
|
{
|
||||||
if (newValue.HasValue)
|
if (newValue.HasValue)
|
||||||
@@ -29,6 +35,7 @@ public partial class FileItem : ObservableObject
|
|||||||
UpdateChildrenSelection(newValue.Value);
|
UpdateChildrenSelection(newValue.Value);
|
||||||
}
|
}
|
||||||
UpdateParentSelection();
|
UpdateParentSelection();
|
||||||
|
NotifySelectionChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateChildrenSelection(bool value)
|
private void UpdateChildrenSelection(bool value)
|
||||||
@@ -37,7 +44,13 @@ public partial class FileItem : ObservableObject
|
|||||||
|
|
||||||
foreach (var child in Children)
|
foreach (var child in Children)
|
||||||
{
|
{
|
||||||
|
// Гарантируем вызов события для каждого ребенка
|
||||||
|
var oldValue = child.IsSelected;
|
||||||
child.IsSelected = value;
|
child.IsSelected = value;
|
||||||
|
if (oldValue != child.IsSelected)
|
||||||
|
{
|
||||||
|
child.RaiseSelectionChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +63,41 @@ public partial class FileItem : ObservableObject
|
|||||||
var noneSelected = children.All(c => c.IsSelected == false);
|
var noneSelected = children.All(c => c.IsSelected == false);
|
||||||
var hasIndeterminate = children.Any(c => c.IsSelected == null);
|
var hasIndeterminate = children.Any(c => c.IsSelected == null);
|
||||||
|
|
||||||
Parent.IsSelected = hasIndeterminate ? null : (allSelected ? true : (noneSelected ? false : null));
|
bool? newParentState = hasIndeterminate ? null : (allSelected ? true : (noneSelected ? false : null));
|
||||||
Parent.UpdateParentSelection();
|
/*bool? newParentState;
|
||||||
|
if (hasIndeterminate)
|
||||||
|
{
|
||||||
|
newParentState = null;
|
||||||
|
}
|
||||||
|
else if (allSelected)
|
||||||
|
{
|
||||||
|
newParentState = true;
|
||||||
|
}
|
||||||
|
else if (noneSelected)
|
||||||
|
{
|
||||||
|
newParentState = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newParentState = null;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
if (Parent.IsSelected != newParentState)
|
||||||
|
{
|
||||||
|
Parent.IsSelected = newParentState;
|
||||||
|
Parent.RaiseSelectionChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NotifySelectionChanged()
|
||||||
|
{
|
||||||
|
RaiseSelectionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Публичный метод для гарантии вызова события
|
||||||
|
public void RaiseSelectionChanged()
|
||||||
|
{
|
||||||
|
// Вызываем событие для текущего элемента
|
||||||
|
SelectionChanged?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ public class FileScannerService : IFileScannerService
|
|||||||
private static readonly string[] ExcludedDirectories = {
|
private static readonly string[] ExcludedDirectories = {
|
||||||
"bin", "obj", ".git", "packages", ".vs", "Properties",
|
"bin", "obj", ".git", "packages", ".vs", "Properties",
|
||||||
"node_modules", ".vscode", ".idea", "Debug", "Release",
|
"node_modules", ".vscode", ".idea", "Debug", "Release",
|
||||||
"wwwroot", "dist", "build", "node_modules"
|
"wwwroot", "dist", "build", ".gitignore", ".dockerignore"
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly string[] IncludedExtensions = { ".cs", ".xaml" };
|
private static readonly string[] IncludedExtensions = { ".cs", ".xaml" };
|
||||||
@@ -25,13 +25,13 @@ public class FileScannerService : IFileScannerService
|
|||||||
|
|
||||||
var directories = Directory.GetDirectories(path)
|
var directories = Directory.GetDirectories(path)
|
||||||
.Where(d => !ExcludedDirectories.Any(ex =>
|
.Where(d => !ExcludedDirectories.Any(ex =>
|
||||||
d.EndsWith(ex, System.StringComparison.OrdinalIgnoreCase) ||
|
d.EndsWith(ex, StringComparison.OrdinalIgnoreCase) ||
|
||||||
Path.GetFileName(d).Equals(ex, System.StringComparison.OrdinalIgnoreCase)))
|
Path.GetFileName(d).Equals(ex, StringComparison.OrdinalIgnoreCase)))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var files = Directory.GetFiles(path)
|
var files = Directory.GetFiles(path)
|
||||||
.Where(f => IncludedExtensions.Any(ext =>
|
.Where(f => IncludedExtensions.Any(ext =>
|
||||||
f.EndsWith(ext, System.StringComparison.OrdinalIgnoreCase)))
|
f.EndsWith(ext, StringComparison.OrdinalIgnoreCase)))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
int totalItems = directories.Count + files.Count;
|
int totalItems = directories.Count + files.Count;
|
||||||
@@ -55,7 +55,7 @@ public class FileScannerService : IFileScannerService
|
|||||||
await BuildDirectoryTreeAsync(dir, dirItem, progress, cancellationToken);
|
await BuildDirectoryTreeAsync(dir, dirItem, progress, cancellationToken);
|
||||||
|
|
||||||
// Добавляем директорию только если в ней есть файлы или поддиректории с файлами
|
// Добавляем директорию только если в ней есть файлы или поддиректории с файлами
|
||||||
if (dirItem.Children.Any())
|
if (dirItem.Children.Count > 0 || files.Count > 0)
|
||||||
{
|
{
|
||||||
parentItem.Children.Add(dirItem);
|
parentItem.Children.Add(dirItem);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using CodeContextGenerator.Interfaces;
|
using CodeContextGenerator.Interfaces;
|
||||||
using CodeContextGenerator.Models;
|
using CodeContextGenerator.Models;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
|
||||||
namespace CodeContextGenerator.Services;
|
namespace CodeContextGenerator.Services;
|
||||||
@@ -63,7 +64,7 @@ public class ProjectLoaderService : IProjectLoaderService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var projectPaths = ParseSolutionProjects(solutionPath);
|
var projectPaths = ParseSolutionProjects(solutionPath, solutionDir);
|
||||||
int totalProjects = projectPaths.Count;
|
int totalProjects = projectPaths.Count;
|
||||||
int processedProjects = 0;
|
int processedProjects = 0;
|
||||||
|
|
||||||
@@ -94,7 +95,10 @@ public class ProjectLoaderService : IProjectLoaderService
|
|||||||
}
|
}
|
||||||
|
|
||||||
processedProjects++;
|
processedProjects++;
|
||||||
progress?.Report((int)((processedProjects * 100.0) / totalProjects));
|
if (totalProjects > 0 && progress != null)
|
||||||
|
{
|
||||||
|
progress.Report((int)((processedProjects * 100.0) / totalProjects));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -105,46 +109,36 @@ public class ProjectLoaderService : IProjectLoaderService
|
|||||||
return solutionItem;
|
return solutionItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<FileItem> LoadCsprojAsync(string csprojPath, IProgress<int> progress, CancellationToken cancellationToken)
|
// Улучшенный парсинг .sln файлов
|
||||||
{
|
private List<string> ParseSolutionProjects(string solutionPath, string solutionDir)
|
||||||
string projectDir = Path.GetDirectoryName(csprojPath);
|
|
||||||
string projectName = Path.GetFileNameWithoutExtension(csprojPath);
|
|
||||||
|
|
||||||
var projectItem = new FileItem
|
|
||||||
{
|
|
||||||
Name = projectName,
|
|
||||||
FullName = projectDir,
|
|
||||||
IsDirectory = true,
|
|
||||||
IsSelected = false
|
|
||||||
};
|
|
||||||
|
|
||||||
await _fileScannerService.BuildDirectoryTreeAsync(projectDir, projectItem, progress, cancellationToken);
|
|
||||||
return projectItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<string> ParseSolutionProjects(string solutionPath)
|
|
||||||
{
|
{
|
||||||
var projects = new List<string>();
|
var projects = new List<string>();
|
||||||
string solutionDir = Path.GetDirectoryName(solutionPath);
|
var projectRegex = new Regex(@"Project\(""[^""]*""\)\s*=\s*""([^""]*)"",\s*""([^""]*)""", RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach (string line in File.ReadAllLines(solutionPath))
|
foreach (string line in File.ReadAllLines(solutionPath))
|
||||||
{
|
{
|
||||||
if (line.Trim().StartsWith("Project(", StringComparison.OrdinalIgnoreCase))
|
var match = projectRegex.Match(line);
|
||||||
|
if (match.Success && match.Groups.Count >= 3)
|
||||||
{
|
{
|
||||||
var parts = line.Split(new[] { '"' }, StringSplitOptions.RemoveEmptyEntries);
|
string projectName = match.Groups[1].Value;
|
||||||
if (parts.Length >= 3)
|
string relativePath = match.Groups[2].Value;
|
||||||
|
|
||||||
|
// Пропускаем служебные проекты
|
||||||
|
if (projectName.Contains("Solution Items") ||
|
||||||
|
relativePath.EndsWith(".sln", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
relativePath.EndsWith(".suo", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
string relativePath = parts[2].Trim();
|
continue;
|
||||||
if (relativePath.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase))
|
}
|
||||||
{
|
|
||||||
string absolutePath = Path.GetFullPath(Path.Combine(solutionDir, relativePath));
|
string absolutePath = Path.GetFullPath(Path.Combine(solutionDir, relativePath));
|
||||||
if (File.Exists(absolutePath))
|
|
||||||
{
|
// Проверяем, что это .csproj файл
|
||||||
projects.Add(absolutePath);
|
if (absolutePath.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase) && File.Exists(absolutePath))
|
||||||
}
|
{
|
||||||
}
|
projects.Add(absolutePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,6 +151,23 @@ public class ProjectLoaderService : IProjectLoaderService
|
|||||||
return projects;
|
return projects;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<FileItem> LoadCsprojAsync(string csprojPath, IProgress<int> progress, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
string projectDir = Path.GetDirectoryName(csprojPath);
|
||||||
|
string projectName = Path.GetFileNameWithoutExtension(csprojPath);
|
||||||
|
|
||||||
|
var projectItem = new FileItem
|
||||||
|
{
|
||||||
|
Name = $"{projectName} (Проект)",
|
||||||
|
FullName = projectDir,
|
||||||
|
IsDirectory = true,
|
||||||
|
IsSelected = false
|
||||||
|
};
|
||||||
|
|
||||||
|
await _fileScannerService.BuildDirectoryTreeAsync(projectDir, projectItem, progress, cancellationToken);
|
||||||
|
return projectItem;
|
||||||
|
}
|
||||||
|
|
||||||
public string GetDefaultOutputFileName(string projectPath)
|
public string GetDefaultOutputFileName(string projectPath)
|
||||||
{
|
{
|
||||||
if (Directory.Exists(projectPath))
|
if (Directory.Exists(projectPath))
|
||||||
|
|||||||
@@ -7,20 +7,32 @@ namespace CodeContextGenerator.Services;
|
|||||||
|
|
||||||
public class UIService : IUIService
|
public class UIService : IUIService
|
||||||
{
|
{
|
||||||
|
private readonly ISettingsService _settingsService;
|
||||||
|
|
||||||
|
public UIService(ISettingsService settingsService)
|
||||||
|
{
|
||||||
|
_settingsService = settingsService;
|
||||||
|
}
|
||||||
|
|
||||||
public string ShowFolderBrowserDialog(string initialDirectory = null)
|
public string ShowFolderBrowserDialog(string initialDirectory = null)
|
||||||
{
|
{
|
||||||
// Используем OpenFileDialog для выбора папки - это стандартный способ в WPF
|
// Используем специальный трюк для выбора папки в WPF
|
||||||
var dialog = new OpenFileDialog
|
var dialog = new OpenFileDialog
|
||||||
{
|
{
|
||||||
Title = "Выберите папку с проектом",
|
Title = "Выберите папку с проектом",
|
||||||
Filter = "Папки|*.folder", // Фильтр для отображения только папок
|
Filter = "Папки|*.dummy", // Фильтр для отображения только папок
|
||||||
FileName = "select_folder", // Имя файла для обхода проверки существования файла
|
FileName = "выберите_папку", // Специальное имя файла
|
||||||
CheckFileExists = false,
|
CheckFileExists = false,
|
||||||
CheckPathExists = true,
|
CheckPathExists = true,
|
||||||
ValidateNames = false, // Отключаем валидацию имен для выбора папок
|
ValidateNames = false,
|
||||||
DereferenceLinks = true
|
DereferenceLinks = true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(initialDirectory) || !Directory.Exists(initialDirectory))
|
||||||
|
{
|
||||||
|
initialDirectory = _settingsService.GetLastProjectPath();
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(initialDirectory) && Directory.Exists(initialDirectory))
|
if (!string.IsNullOrEmpty(initialDirectory) && Directory.Exists(initialDirectory))
|
||||||
{
|
{
|
||||||
dialog.InitialDirectory = initialDirectory;
|
dialog.InitialDirectory = initialDirectory;
|
||||||
@@ -29,8 +41,9 @@ public class UIService : IUIService
|
|||||||
var result = dialog.ShowDialog();
|
var result = dialog.ShowDialog();
|
||||||
if (result == true)
|
if (result == true)
|
||||||
{
|
{
|
||||||
// Возвращаем папку, а не файл
|
// Возвращаем директорию, а не путь к файлу
|
||||||
return Path.GetDirectoryName(dialog.FileName);
|
string selectedFolder = Path.GetDirectoryName(dialog.FileName);
|
||||||
|
return selectedFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -42,11 +55,17 @@ public class UIService : IUIService
|
|||||||
|
|
||||||
var dialog = new OpenFileDialog
|
var dialog = new OpenFileDialog
|
||||||
{
|
{
|
||||||
Title = "Выберите файл решения, проекта или папку",
|
Title = "Выберите файл решения или проекта",
|
||||||
Filter = "Все поддерживаемые файлы (*.sln;*.csproj;*.razor)|*.sln;*.csproj;*.razor|Файлы решений (*.sln)|*.sln|Файлы проектов (*.csproj)|*.csproj|Файлы Razor (*.razor)|*.razor|Все файлы (*.*)|*.*",
|
Filter = "Все поддерживаемые файлы (*.sln;*.csproj)|*.sln;*.csproj|Файлы решений Visual Studio (*.sln)|*.sln|Файлы проектов C# (*.csproj)|*.csproj|Все файлы (*.*)|*.*",
|
||||||
Multiselect = false
|
Multiselect = false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var lastPath = _settingsService.GetLastProjectPath();
|
||||||
|
if (!string.IsNullOrEmpty(lastPath) && Directory.Exists(lastPath))
|
||||||
|
{
|
||||||
|
dialog.InitialDirectory = lastPath;
|
||||||
|
}
|
||||||
|
|
||||||
bool? result = dialog.ShowDialog();
|
bool? result = dialog.ShowDialog();
|
||||||
if (result == true)
|
if (result == true)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,208 +1,282 @@
|
|||||||
using CodeContextGenerator.Interfaces;
|
using CodeContextGenerator.Interfaces;
|
||||||
using CodeContextGenerator.Models;
|
using CodeContextGenerator.Models;
|
||||||
|
using CodeContextGenerator.Services;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
|
||||||
namespace CodeContextGenerator.ViewModels;
|
namespace CodeContextGenerator.ViewModels
|
||||||
|
|
||||||
public partial class MainViewModel : ObservableObject
|
|
||||||
{
|
{
|
||||||
private readonly IProjectLoaderService _projectLoaderService;
|
public partial class MainViewModel : ObservableObject
|
||||||
private readonly IFileScannerService _fileScannerService;
|
|
||||||
private readonly IContextFileGenerator _contextFileGenerator;
|
|
||||||
private readonly IUIService _uiService;
|
|
||||||
private readonly ISettingsService _settingsService;
|
|
||||||
|
|
||||||
private CancellationTokenSource _cancellationTokenSource;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private FileItem rootDirectory;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private string selectedProjectPath;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private bool isProcessing;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private int progressValue;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private string progressText;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private bool isProjectLoaded;
|
|
||||||
|
|
||||||
public MainViewModel(
|
|
||||||
IProjectLoaderService projectLoaderService,
|
|
||||||
IFileScannerService fileScannerService,
|
|
||||||
IContextFileGenerator contextFileGenerator,
|
|
||||||
IUIService uiService,
|
|
||||||
ISettingsService settingsService)
|
|
||||||
{
|
{
|
||||||
_projectLoaderService = projectLoaderService;
|
private readonly IProjectLoaderService _projectLoaderService;
|
||||||
_fileScannerService = fileScannerService;
|
private readonly IFileScannerService _fileScannerService;
|
||||||
_contextFileGenerator = contextFileGenerator;
|
private readonly IContextFileGenerator _contextFileGenerator;
|
||||||
_uiService = uiService;
|
private readonly IUIService _uiService;
|
||||||
_settingsService = settingsService;
|
private readonly ISettingsService _settingsService;
|
||||||
}
|
|
||||||
|
|
||||||
public bool CanSelectProject => !IsProcessing;
|
private CancellationTokenSource _cancellationTokenSource;
|
||||||
public bool CanGenerate => !IsProcessing && IsProjectLoaded && HasSelectedFiles(RootDirectory);
|
|
||||||
|
|
||||||
[RelayCommand(CanExecute = nameof(CanSelectProject))]
|
[ObservableProperty]
|
||||||
private void SelectProject()
|
private FileItem rootDirectory;
|
||||||
{
|
|
||||||
var initialDir = _settingsService.GetLastProjectPath();
|
|
||||||
|
|
||||||
// Сначала пробуем выбрать файл проекта/решения
|
[ObservableProperty]
|
||||||
if (_uiService.ShowOpenProjectFileDialog(out var filePath))
|
private string selectedProjectPath;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool isProcessing;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private int progressValue;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string progressText;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool isProjectLoaded;
|
||||||
|
|
||||||
|
// Явное объявление команд для гарантии их создания
|
||||||
|
public IRelayCommand SelectProjectCommand { get; }
|
||||||
|
public IAsyncRelayCommand GenerateContextFileCommand { get; }
|
||||||
|
public IRelayCommand CancelProcessingCommand { get; }
|
||||||
|
public IRelayCommand ExitApplicationCommand { get; }
|
||||||
|
|
||||||
|
public MainViewModel(
|
||||||
|
IProjectLoaderService projectLoaderService,
|
||||||
|
IFileScannerService fileScannerService,
|
||||||
|
IContextFileGenerator contextFileGenerator,
|
||||||
|
IUIService uiService,
|
||||||
|
ISettingsService settingsService)
|
||||||
{
|
{
|
||||||
LoadProject(filePath);
|
_projectLoaderService = projectLoaderService;
|
||||||
return;
|
_fileScannerService = fileScannerService;
|
||||||
|
_contextFileGenerator = contextFileGenerator;
|
||||||
|
_uiService = uiService;
|
||||||
|
_settingsService = settingsService;
|
||||||
|
|
||||||
|
// Явная инициализация команд
|
||||||
|
SelectProjectCommand = new RelayCommand(SelectProject, CanSelectProject);
|
||||||
|
GenerateContextFileCommand = new AsyncRelayCommand(GenerateContextFileAsync, CanGenerate);
|
||||||
|
CancelProcessingCommand = new RelayCommand(CancelProcessing);
|
||||||
|
ExitApplicationCommand = new RelayCommand(ExitApplication);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если отменили выбор файла - предлагаем выбрать папку
|
private bool CanSelectProject() => !IsProcessing;
|
||||||
var folderPath = _uiService.ShowFolderBrowserDialog(initialDir);
|
|
||||||
if (!string.IsNullOrEmpty(folderPath))
|
|
||||||
{
|
|
||||||
LoadProject(folderPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand(CanExecute = nameof(CanGenerate))]
|
private bool CanGenerate()
|
||||||
private async Task GenerateContextFileAsync()
|
|
||||||
{
|
|
||||||
var selectedFiles = _fileScannerService.GetSelectedFiles(RootDirectory);
|
|
||||||
if (selectedFiles.Count == 0)
|
|
||||||
{
|
{
|
||||||
_uiService.ShowMessage("Пожалуйста, выберите хотя бы один файл для обработки.", "Предупреждение", MessageBoxImage.Warning);
|
bool result = !IsProcessing &&
|
||||||
return;
|
IsProjectLoaded &&
|
||||||
|
RootDirectory != null &&
|
||||||
|
HasSelectedFiles(RootDirectory);
|
||||||
|
|
||||||
|
// Отладочная информация
|
||||||
|
System.Diagnostics.Debug.WriteLine($"CanGenerate: {result}, IsProcessing: {IsProcessing}, IsProjectLoaded: {IsProjectLoaded}, RootDirectory: {(RootDirectory != null)}, HasSelectedFiles: {HasSelectedFiles(RootDirectory)}");
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultFileName = _projectLoaderService.GetDefaultOutputFileName(SelectedProjectPath);
|
private void SelectProject()
|
||||||
var initialDir = _settingsService.GetLastProjectPath();
|
|
||||||
|
|
||||||
if (!_uiService.ShowSaveFileDialog(defaultFileName, initialDir, out var savePath))
|
|
||||||
return;
|
|
||||||
|
|
||||||
IsProcessing = true;
|
|
||||||
ProgressText = "Генерация контекстного файла...";
|
|
||||||
ProgressValue = 0;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
_cancellationTokenSource = new CancellationTokenSource();
|
var initialDir = !string.IsNullOrEmpty(SelectedProjectPath) && Directory.Exists(SelectedProjectPath)
|
||||||
var progress = new Progress<int>(value =>
|
? Path.GetDirectoryName(SelectedProjectPath)
|
||||||
|
: _settingsService.GetLastProjectPath();
|
||||||
|
|
||||||
|
if (_uiService.ShowOpenProjectFileDialog(out var filePath))
|
||||||
{
|
{
|
||||||
ProgressValue = value;
|
LoadProject(filePath);
|
||||||
ProgressText = $"Генерация: {value}%";
|
return;
|
||||||
});
|
}
|
||||||
|
|
||||||
await _contextFileGenerator.GenerateContextFileAsync(
|
var folderPath = _uiService.ShowFolderBrowserDialog(initialDir);
|
||||||
selectedFiles,
|
if (!string.IsNullOrEmpty(folderPath))
|
||||||
savePath,
|
|
||||||
Path.GetDirectoryName(SelectedProjectPath),
|
|
||||||
progress,
|
|
||||||
_cancellationTokenSource.Token);
|
|
||||||
|
|
||||||
_uiService.ShowMessage($"Файл успешно создан:\n{savePath}", "Успех");
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
ProgressText = "Генерация отменена";
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
_uiService.ShowMessage($"Ошибка доступа к файлу: {ex.Message}\nПожалуйста, закройте файлы или предоставьте необходимые права доступа.", "Ошибка доступа", MessageBoxImage.Error);
|
|
||||||
}
|
|
||||||
catch (System.Exception ex)
|
|
||||||
{
|
|
||||||
_uiService.ShowMessage($"Ошибка при генерации файла: {ex.Message}", "Ошибка", MessageBoxImage.Error);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
IsProcessing = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private void CancelProcessing()
|
|
||||||
{
|
|
||||||
_cancellationTokenSource?.Cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private void ExitApplication()
|
|
||||||
{
|
|
||||||
Application.Current.Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadProject(string projectPath)
|
|
||||||
{
|
|
||||||
SelectedProjectPath = projectPath;
|
|
||||||
_settingsService.SaveLastProjectPath(Path.GetDirectoryName(projectPath));
|
|
||||||
|
|
||||||
IsProjectLoaded = false;
|
|
||||||
LoadProjectAsync(projectPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void LoadProjectAsync(string projectPath)
|
|
||||||
{
|
|
||||||
IsProcessing = true;
|
|
||||||
ProgressText = "Загрузка проекта...";
|
|
||||||
ProgressValue = 0;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var progress = new Progress<int>(value =>
|
|
||||||
{
|
{
|
||||||
ProgressValue = value;
|
LoadProject(folderPath);
|
||||||
ProgressText = $"Загрузка: {value}%";
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
_cancellationTokenSource = new CancellationTokenSource();
|
private async Task GenerateContextFileAsync()
|
||||||
RootDirectory = await _projectLoaderService.LoadProjectFromPathAsync(projectPath, progress, _cancellationTokenSource.Token);
|
{
|
||||||
|
var selectedFiles = _fileScannerService.GetSelectedFiles(RootDirectory);
|
||||||
|
if (selectedFiles.Count == 0)
|
||||||
|
{
|
||||||
|
_uiService.ShowMessage("Пожалуйста, выберите хотя бы один файл для обработки.", "Предупреждение", MessageBoxImage.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
IsProjectLoaded = true;
|
var defaultFileName = _projectLoaderService.GetDefaultOutputFileName(SelectedProjectPath);
|
||||||
ProgressText = "Проект загружен успешно";
|
var initialDir = _settingsService.GetLastProjectPath() ?? Path.GetDirectoryName(SelectedProjectPath);
|
||||||
|
|
||||||
// Сбрасываем выделение после загрузки
|
if (!_uiService.ShowSaveFileDialog(defaultFileName, initialDir, out var savePath))
|
||||||
ClearSelections(RootDirectory);
|
return;
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
ProgressText = "Загрузка отменена";
|
|
||||||
}
|
|
||||||
catch (System.Exception ex)
|
|
||||||
{
|
|
||||||
_uiService.ShowMessage($"Ошибка при загрузке проекта: {ex.Message}", "Ошибка", MessageBoxImage.Error);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
IsProcessing = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ClearSelections(FileItem item)
|
IsProcessing = true;
|
||||||
{
|
ProgressText = "Генерация контекстного файла...";
|
||||||
item.IsSelected = false;
|
ProgressValue = 0;
|
||||||
foreach (var child in item.Children)
|
|
||||||
{
|
|
||||||
ClearSelections(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool HasSelectedFiles(FileItem item)
|
try
|
||||||
{
|
{
|
||||||
if (item == null) return false;
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
if (item.IsSelected == true && !item.IsDirectory) return true;
|
var progress = new Progress<int>(value =>
|
||||||
foreach (var child in item.Children)
|
{
|
||||||
{
|
ProgressValue = value;
|
||||||
if (HasSelectedFiles(child)) return true;
|
ProgressText = $"Генерация: {value}%";
|
||||||
|
});
|
||||||
|
|
||||||
|
await _contextFileGenerator.GenerateContextFileAsync(
|
||||||
|
selectedFiles,
|
||||||
|
savePath,
|
||||||
|
Path.GetDirectoryName(SelectedProjectPath),
|
||||||
|
progress,
|
||||||
|
_cancellationTokenSource.Token);
|
||||||
|
|
||||||
|
_uiService.ShowMessage($"Файл успешно создан:\n{savePath}", "Успех");
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
ProgressText = "Генерация отменена";
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
_uiService.ShowMessage($"Ошибка доступа к файлу: {ex.Message}\nПожалуйста, закройте файлы или предоставьте необходимые права доступа.", "Ошибка доступа", MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
_uiService.ShowMessage($"Ошибка при генерации файла: {ex.Message}", "Ошибка", MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsProcessing = false;
|
||||||
|
// Принудительно обновляем команды после завершения операции
|
||||||
|
UpdateCommandsCanExecute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelProcessing()
|
||||||
|
{
|
||||||
|
_cancellationTokenSource?.Cancel();
|
||||||
|
ProgressText = "Операция отменена";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExitApplication()
|
||||||
|
{
|
||||||
|
Application.Current.Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadProject(string projectPath)
|
||||||
|
{
|
||||||
|
SelectedProjectPath = projectPath;
|
||||||
|
_settingsService.SaveLastProjectPath(Path.GetDirectoryName(projectPath));
|
||||||
|
|
||||||
|
IsProjectLoaded = false;
|
||||||
|
RootDirectory = null;
|
||||||
|
UpdateCommandsCanExecute();
|
||||||
|
|
||||||
|
LoadProjectAsync(projectPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void LoadProjectAsync(string projectPath)
|
||||||
|
{
|
||||||
|
ProgressText = "Загрузка проекта...";
|
||||||
|
ProgressValue = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var progress = new Progress<int>(value =>
|
||||||
|
{
|
||||||
|
ProgressValue = value;
|
||||||
|
ProgressText = $"Загрузка: {value}%";
|
||||||
|
});
|
||||||
|
|
||||||
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
RootDirectory = await _projectLoaderService.LoadProjectFromPathAsync(projectPath, progress, _cancellationTokenSource.Token);
|
||||||
|
|
||||||
|
IsProjectLoaded = true;
|
||||||
|
ProgressText = "Проект загружен успешно";
|
||||||
|
|
||||||
|
// Подписываемся на события изменения выбора
|
||||||
|
SubscribeToSelectionChanges(RootDirectory);
|
||||||
|
|
||||||
|
// Сбрасываем выделение после загрузки
|
||||||
|
if (RootDirectory != null)
|
||||||
|
{
|
||||||
|
ClearSelections(RootDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Принудительно обновляем команды
|
||||||
|
UpdateCommandsCanExecute();
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
ProgressText = "Загрузка отменена";
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
_uiService.ShowMessage($"Ошибка при загрузке проекта: {ex.Message}", "Ошибка", MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsProcessing = false;
|
||||||
|
UpdateCommandsCanExecute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Рекурсивная подписка на события изменения выбора
|
||||||
|
private void SubscribeToSelectionChanges(FileItem item)
|
||||||
|
{
|
||||||
|
if (item == null) return;
|
||||||
|
|
||||||
|
item.SelectionChanged += (sender, args) =>
|
||||||
|
{
|
||||||
|
UpdateCommandsCanExecute();
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var child in item.Children)
|
||||||
|
{
|
||||||
|
SubscribeToSelectionChanges(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearSelections(FileItem item)
|
||||||
|
{
|
||||||
|
if (item == null) return;
|
||||||
|
|
||||||
|
item.IsSelected = false;
|
||||||
|
foreach (var child in item.Children)
|
||||||
|
{
|
||||||
|
ClearSelections(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasSelectedFiles(FileItem item)
|
||||||
|
{
|
||||||
|
if (item == null) return false;
|
||||||
|
|
||||||
|
if (!item.IsDirectory && item.IsSelected == true)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (item.IsDirectory)
|
||||||
|
{
|
||||||
|
foreach (var child in item.Children)
|
||||||
|
{
|
||||||
|
if (HasSelectedFiles(child))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Метод для принудительного обновления всех команд
|
||||||
|
private void UpdateCommandsCanExecute()
|
||||||
|
{
|
||||||
|
(SelectProjectCommand as RelayCommand)?.NotifyCanExecuteChanged();
|
||||||
|
(GenerateContextFileCommand as AsyncRelayCommand)?.NotifyCanExecuteChanged();
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,50 +59,51 @@
|
|||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Border
|
<!-- Контейнер для дерева и сообщения -->
|
||||||
Grid.Row="2"
|
<Grid Grid.Row="2">
|
||||||
Padding="5"
|
<Border
|
||||||
BorderBrush="Gray"
|
Padding="5"
|
||||||
BorderThickness="1"
|
BorderBrush="Gray"
|
||||||
CornerRadius="4"
|
BorderThickness="1"
|
||||||
Visibility="{Binding IsProjectLoaded, Converter={StaticResource BooleanToVisibilityConverter}}">
|
CornerRadius="4"
|
||||||
<ScrollViewer
|
Visibility="{Binding IsProjectLoaded, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||||
HorizontalScrollBarVisibility="Auto"
|
<ScrollViewer
|
||||||
PreviewMouseWheel="ScrollViewer_PreviewMouseWheel"
|
HorizontalScrollBarVisibility="Auto"
|
||||||
VerticalScrollBarVisibility="Auto">
|
PreviewMouseWheel="ScrollViewer_PreviewMouseWheel"
|
||||||
<TreeView
|
VerticalScrollBarVisibility="Auto">
|
||||||
x:Name="ProjectTree"
|
<TreeView
|
||||||
ItemsSource="{Binding RootDirectory.Children}"
|
x:Name="ProjectTree"
|
||||||
VirtualizingPanel.IsVirtualizing="True"
|
ItemsSource="{Binding RootDirectory.Children}"
|
||||||
VirtualizingPanel.VirtualizationMode="Recycling">
|
VirtualizingPanel.IsVirtualizing="True"
|
||||||
<TreeView.ItemTemplate>
|
VirtualizingPanel.VirtualizationMode="Recycling">
|
||||||
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
|
<TreeView.ItemTemplate>
|
||||||
<StackPanel MinHeight="24" Orientation="Horizontal">
|
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
|
||||||
<CheckBox
|
<StackPanel MinHeight="24" Orientation="Horizontal">
|
||||||
Margin="2,0,5,0"
|
<CheckBox
|
||||||
VerticalAlignment="Center"
|
Margin="2,0,5,0"
|
||||||
Click="CheckBox_Click"
|
VerticalAlignment="Center"
|
||||||
IsChecked="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged}"
|
Click="CheckBox_Click"
|
||||||
IsThreeState="True" />
|
IsChecked="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged}"
|
||||||
<TextBlock
|
IsThreeState="True" />
|
||||||
VerticalAlignment="Center"
|
<TextBlock
|
||||||
Text="{Binding Name}"
|
VerticalAlignment="Center"
|
||||||
ToolTip="{Binding FullName}" />
|
Text="{Binding Name}"
|
||||||
</StackPanel>
|
ToolTip="{Binding FullName}" />
|
||||||
</HierarchicalDataTemplate>
|
</StackPanel>
|
||||||
</TreeView.ItemTemplate>
|
</HierarchicalDataTemplate>
|
||||||
</TreeView>
|
</TreeView.ItemTemplate>
|
||||||
</ScrollViewer>
|
</TreeView>
|
||||||
</Border>
|
</ScrollViewer>
|
||||||
|
</Border>
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="2"
|
HorizontalAlignment="Center"
|
||||||
HorizontalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
FontSize="14"
|
||||||
FontSize="14"
|
Foreground="Gray"
|
||||||
Foreground="Gray"
|
Text="Проект еще не загружен. Выберите файл решения или проекта."
|
||||||
Text="Проект еще не загружен. Выберите файл решения или проекта."
|
Visibility="{Binding IsProjectLoaded, Converter={StaticResource BooleanToVisibilityConverter}, ConverterParameter=Inverted}" />
|
||||||
Visibility="{Binding IsProjectLoaded, Converter={StaticResource BooleanToVisibilityConverter}, ConverterParameter=Collapsed}" />
|
</Grid>
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
Grid.Row="3"
|
Grid.Row="3"
|
||||||
@@ -130,14 +131,12 @@
|
|||||||
Content="Отмена"
|
Content="Отмена"
|
||||||
Foreground="White"
|
Foreground="White"
|
||||||
Visibility="{Binding IsProcessing, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
Visibility="{Binding IsProcessing, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||||
<Button Command="{Binding ExitApplicationCommand}" Content="Закрыть" />
|
<Button Command="{Binding ExitApplicationCommand}" Content="В главное меню" />
|
||||||
<Button
|
<Button
|
||||||
Background="#FF28A745"
|
Background="#FF28A745"
|
||||||
Command="{Binding GenerateContextFileCommand}"
|
Command="{Binding GenerateContextFileCommand}"
|
||||||
Content="Сформировать"
|
Content="Сформировать"
|
||||||
Foreground="White"
|
Foreground="White" />
|
||||||
IsEnabled="{Binding CanGenerate}"
|
|
||||||
Visibility="{Binding IsProjectLoaded, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Window>
|
</Window>
|
||||||
Reference in New Issue
Block a user