App layout

This commit is contained in:
Matthew Parker
2023-08-31 23:38:32 +10:00
parent 701218a28b
commit fc55b195d6
11 changed files with 284 additions and 102 deletions

View File

@@ -6,6 +6,7 @@
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
@@ -28,6 +29,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DotNetSolutionTools.Core\DotNetSolutionTools.Core.csproj" />
<ProjectReference Include="..\DotNetSolutionTools.Core\DotNetSolutionTools.Core.csproj"/>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,40 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform.Storage;
namespace DotNetSolutionTools.App.Services;
public class FileService
{
public async Task<IStorageFile?> DoOpenFilePickerAsync()
{
if (
Application.Current?.ApplicationLifetime
is not IClassicDesktopStyleApplicationLifetime desktop
|| desktop.MainWindow?.StorageProvider is not { } provider
)
throw new NullReferenceException("Missing StorageProvider instance.");
var files = await provider.OpenFilePickerAsync(
new FilePickerOpenOptions() { Title = "Open Text File", AllowMultiple = false }
);
return files?.Count >= 1 ? files[0] : null;
}
public async Task<IStorageFolder?> DoOpenFolderPickerAsync()
{
if (
Application.Current?.ApplicationLifetime
is not IClassicDesktopStyleApplicationLifetime desktop
|| desktop.MainWindow?.StorageProvider is not { } provider
)
throw new NullReferenceException("Missing StorageProvider instance.");
var folder = await provider.OpenFolderPickerAsync(
new FolderPickerOpenOptions() { Title = "Open Text File", AllowMultiple = false }
);
return folder?.Count >= 1 ? folder[0] : null;
}
}

View File

@@ -1,21 +1,15 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform.Storage;
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DotNetSolutionTools.App.Services;
using DotNetSolutionTools.Core;
namespace DotNetSolutionTools.App.ViewModels;
public partial class MainWindowViewModel : ViewModelBase
{
private readonly FileService _fileService = new();
[ObservableProperty]
private string _solutionFolderPath;
@@ -23,33 +17,57 @@ public partial class MainWindowViewModel : ViewModelBase
private string _solutionFilePath;
[ObservableProperty]
private string? _fileText;
private string _csprojFilePath;
[ObservableProperty]
private ObservableCollection<string> _parityResults = new ObservableCollection<string>()
{
"Test"
};
private ObservableCollection<string> _parityResults = new() { };
[RelayCommand]
private async Task ExecuteParityChecker(CancellationToken token)
{
var results =
DotNetSolutionTools.Core.SolutionParityChecker.CompareSolutionAndCSharpProjects(
SolutionFolderPath,
SolutionFilePath
);
var results = SolutionProjectParity.CompareSolutionAndCSharpProjects(
SolutionFolderPath,
SolutionFilePath
);
ParityResults.Clear();
foreach (var result in results)
{
ParityResults.Add(result);
}
}
[RelayCommand]
private async Task FormatCsProjFile(CancellationToken token)
{
FormatCsproj.FormatCsprojFile(SolutionFilePath);
FormatCsproj.FormatCsprojFile(CsprojFilePath);
}
[RelayCommand]
private async Task FormatAllCsprojFilesInSolutionFile(CancellationToken token)
{
var csprojList = SolutionProjectParity.RetrieveAllCSharpProjectFullPathsFromFolder(
SolutionFolderPath
);
foreach (var csproj in csprojList)
{
FormatCsproj.FormatCsprojFile(csproj);
}
}
[RelayCommand]
private async Task FormatAllCsprojFilesInSolutionFolder(CancellationToken token)
{
var csprojList = SolutionProjectParity.RetrieveAllCSharpProjectFullPathsFromFolder(
SolutionFolderPath
);
foreach (var csproj in csprojList)
{
FormatCsproj.FormatCsprojFile(csproj);
}
}
[RelayCommand]
private async Task CheckForMissingImplicitUsingsInSolutionFile(CancellationToken token)
{
ImplicitUsings.FindCSharpProjectsMissingImplicitUsings(SolutionFilePath);
}
[RelayCommand]
@@ -58,7 +76,7 @@ public partial class MainWindowViewModel : ViewModelBase
ErrorMessages?.Clear();
try
{
var file = await DoOpenFilePickerAsync();
var file = await _fileService.DoOpenFilePickerAsync();
if (file is null)
return;
@@ -70,13 +88,19 @@ public partial class MainWindowViewModel : ViewModelBase
}
}
[RelayCommand]
private async Task ClearSolutionFile(CancellationToken token)
{
SolutionFilePath = string.Empty;
}
[RelayCommand]
private async Task LoadSolutionFolder(CancellationToken token)
{
ErrorMessages?.Clear();
try
{
var folder = await DoOpenFolderPickerAsync();
var folder = await _fileService.DoOpenFolderPickerAsync();
if (folder is null)
return;
@@ -88,49 +112,33 @@ public partial class MainWindowViewModel : ViewModelBase
}
}
private async Task<IStorageFile?> DoOpenFilePickerAsync()
[RelayCommand]
private async Task ClearSolutionFolder(CancellationToken token)
{
// For learning purposes, we opted to directly get the reference
// for StorageProvider APIs here inside the ViewModel.
// For your real-world apps, you should follow the MVVM principles
// by making service classes and locating them with DI/IoC.
// See IoCFileOps project for an example of how to accomplish this.
if (
Application.Current?.ApplicationLifetime
is not IClassicDesktopStyleApplicationLifetime desktop
|| desktop.MainWindow?.StorageProvider is not { } provider
)
throw new NullReferenceException("Missing StorageProvider instance.");
var files = await provider.OpenFilePickerAsync(
new FilePickerOpenOptions() { Title = "Open Text File", AllowMultiple = false }
);
return files?.Count >= 1 ? files[0] : null;
SolutionFolderPath = string.Empty;
}
private async Task<IStorageFolder?> DoOpenFolderPickerAsync()
[RelayCommand]
private async Task LoadCsprojFile(CancellationToken token)
{
// For learning purposes, we opted to directly get the reference
// for StorageProvider APIs here inside the ViewModel.
ErrorMessages?.Clear();
try
{
var folder = await _fileService.DoOpenFilePickerAsync();
if (folder is null)
return;
// For your real-world apps, you should follow the MVVM principles
// by making service classes and locating them with DI/IoC.
CsprojFilePath = folder.Path.AbsolutePath;
}
catch (Exception e)
{
ErrorMessages?.Add(e.Message);
}
}
// See IoCFileOps project for an example of how to accomplish this.
if (
Application.Current?.ApplicationLifetime
is not IClassicDesktopStyleApplicationLifetime desktop
|| desktop.MainWindow?.StorageProvider is not { } provider
)
throw new NullReferenceException("Missing StorageProvider instance.");
var folder = await provider.OpenFolderPickerAsync(
new FolderPickerOpenOptions() { Title = "Open Text File", AllowMultiple = false }
);
return folder?.Count >= 1 ? folder[0] : null;
[RelayCommand]
private async Task ClearCsprojFile(CancellationToken token)
{
CsprojFilePath = string.Empty;
}
}

View File

@@ -0,0 +1,53 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace DotNetSolutionTools.App.ViewModels;
public partial class PreviewMainWindowViewModel : ViewModelBase
{
[ObservableProperty]
private string _solutionFolderPath =
"C:\\Users\\matt\\source\\repos\\DotNetSolutionTools\\DotNetSolutionTools.App";
[ObservableProperty]
private string _solutionFilePath =
"C:\\Users\\matt\\source\\repos\\DotNetSolutionTools\\DotNetSolutionTools.App.sln";
[ObservableProperty]
private string _csprojFilePath =
"C:\\Users\\matt\\source\\repos\\DotNetSolutionTools\\DotNetSolutionTools.App.csproj";
[ObservableProperty]
private ObservableCollection<string> _parityResults = new() { "Error Message" };
[RelayCommand]
private async Task ExecuteParityChecker(CancellationToken token)
{
throw new NotImplementedException();
}
[RelayCommand]
private async Task FormatCsProjFile(CancellationToken token)
{
throw new NotImplementedException();
}
[RelayCommand]
private async Task LoadSolutionFile(CancellationToken token)
{
throw new NotImplementedException();
}
[RelayCommand]
private async Task LoadSolutionFolder(CancellationToken token)
{
throw new NotImplementedException();
}
[RelayCommand]
private async Task LoadCsprojFile(CancellationToken token)
{
throw new NotImplementedException();
}
}

View File

@@ -4,27 +4,86 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:DotNetSolutionTools.App.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
Width="800" Height="450"
x:Class="DotNetSolutionTools.App.Views.MainWindow"
x:DataType="viewModels:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="FileOps">
Title="DotNetSolutionTools">
<Design.DataContext>
<!-- This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
<viewModels:MainWindowViewModel />
</Design.DataContext>
<Panel>
<StackPanel>
<TextBlock>Welcome to Avalonia!</TextBlock>
<Button Command="{Binding LoadSolutionFolderCommand}" >Select Solution Folder</Button>
<Button Command="{Binding LoadSolutionFileCommand}">Select Solution File</Button>
<Button Command="{Binding ExecuteParityCheckerCommand}">Check Solution Parity</Button>
<Button Command="{Binding FormatCsProjFileCommand}">Format CSharp Project File</Button>
<TextBlock Name="SolutionFilePath" Text="{Binding SolutionFilePath}" />
<TextBlock Name="SolutionFolderPath" Text="{Binding SolutionFolderPath}" />
<ListBox Name="Results" ItemsSource="{Binding ParityResults}"/>
<ListBox ItemsSource="{Binding ErrorMessages}"/>
<StackPanel VerticalAlignment="Stretch" Margin="10">
<StackPanel Orientation="Horizontal">
<Button Width="200" HorizontalContentAlignment="Center" Command="{Binding LoadSolutionFolderCommand}">Select Solution Folder</Button>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Background="Gray" Name="SolutionFolderPath" Text="{Binding SolutionFolderPath}" />
<Button Width="60" HorizontalContentAlignment="Center" Command="{Binding ClearSolutionFolderCommand}">Clear</Button>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button Width="200" HorizontalContentAlignment="Center" Command="{Binding LoadSolutionFileCommand}">Select Solution File</Button>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Name="SolutionFilePath" Text="{Binding SolutionFilePath}" />
<Button Width="60" HorizontalContentAlignment="Center" Command="{Binding ClearSolutionFileCommand}">Clear</Button>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button Width="200" HorizontalContentAlignment="Center" Command="{Binding LoadCsprojFileCommand}">Select CSharp Project File</Button>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Background="Gray" Name="CsprojFilePath" Text="{Binding CsprojFilePath}" />
<Button Width="60" HorizontalContentAlignment="Center" Command="{Binding ClearCsprojFileCommand}">Clear</Button>
</StackPanel>
<Separator Margin="0 10 0 0" />
<Grid VerticalAlignment="Stretch" ShowGridLines="False" RowDefinitions="*,*" ColumnDefinitions="*,*,*">
<Button Grid.Row="0" Grid.Column="0" MinHeight="130" Padding="10" Margin="10"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
IsEnabled="{Binding SolutionFolderPath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Command="{Binding ExecuteParityCheckerCommand}">
<TextBlock TextWrapping="Wrap">
Check Solution Parity
</TextBlock>
</Button>
<Button Grid.Row="0" Grid.Column="1" MinHeight="130" Padding="10" Margin="10"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
IsEnabled="{Binding CsprojFilePath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Command="{Binding FormatCsProjFileCommand}">
<TextBlock TextWrapping="Wrap">
Format CSharp Project File
</TextBlock>
</Button>
<Button Grid.Row="0" Grid.Column="2" MinHeight="130" Padding="10" Margin="10"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
IsEnabled="{Binding SolutionFilePath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Command="{Binding FormatAllCsprojFilesInSolutionFileCommand}">
<TextBlock TextWrapping="Wrap">
Format All CSharp Project Files in Solution
</TextBlock>
</Button>
<Button Grid.Row="1" Grid.Column="0" MinHeight="130" Padding="10" Margin="10"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
IsEnabled="{Binding SolutionFolderPath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Command="{Binding FormatAllCsprojFilesInSolutionFolderCommand}">
<TextBlock TextWrapping="Wrap">
Format All CSharp Project Files in Folder
</TextBlock>
</Button>
<Button Grid.Row="1" Grid.Column="1" MinHeight="130" Padding="10" Margin="10"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
IsEnabled="{Binding SolutionFilePath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Command="{Binding CheckForMissingImplicitUsingsInSolutionFileCommand}">
<TextBlock TextWrapping="Wrap">
Check For Missing Implicit Usings
</TextBlock>
</Button>
</Grid>
<ListBox Name="Results" ItemsSource="{Binding ParityResults}" />
<ListBox ItemsSource="{Binding ErrorMessages}" />
</StackPanel>
</Panel>
</Window>

View File

@@ -28,7 +28,7 @@ public class CompareCommand : Command<CompareCommand.Settings>
var pathToSolutionFile = settings.SolutionFilePath;
Console.WriteLine($"Retrieving C# Projects from {folderDirectory}");
var csprojList = SolutionParityChecker.RetrieveAllCSharpProjectNamesFromFolder(
var csprojList = SolutionProjectParity.RetrieveAllCSharpProjectNamesFromFolder(
folderDirectory
);
@@ -45,7 +45,7 @@ public class CompareCommand : Command<CompareCommand.Settings>
Console.WriteLine($"Parsing Solution File: {pathToSolutionFile}");
// Load the solution file
var solutionFile = SolutionParityChecker.ParseSolutionFileFromPath(pathToSolutionFile);
var solutionFile = SolutionProjectParity.ParseSolutionFileFromPath(pathToSolutionFile);
if (solutionFile == null)
{
Console.WriteLine(
@@ -55,7 +55,7 @@ public class CompareCommand : Command<CompareCommand.Settings>
}
// Get the list of projects
var projectsMissingFromSolution = SolutionParityChecker.FindProjectsMissingFromSolution(
var projectsMissingFromSolution = SolutionProjectParity.FindProjectsMissingFromSolution(
csprojList,
solutionFile
);

View File

@@ -29,7 +29,7 @@ public class FormatCsprojCommand : Command<FormatCsprojCommand.Settings>
}
else if (!string.IsNullOrWhiteSpace(settings.SolutionFilePath))
{
var test = SolutionParityChecker.ParseSolutionFileFromPath(settings.SolutionFilePath);
var test = SolutionProjectParity.ParseSolutionFileFromPath(settings.SolutionFilePath);
if (test == null)
{
Console.WriteLine(
@@ -37,7 +37,7 @@ public class FormatCsprojCommand : Command<FormatCsprojCommand.Settings>
);
return 1;
}
var cSharpProjects = SolutionParityChecker.GetCSharpProjectObjectsFromSolutionFile(
var cSharpProjects = SolutionProjectParity.GetCSharpProjectObjectsFromSolutionFile(
test
);
Console.WriteLine($"Found {cSharpProjects.Count} C# Projects");
@@ -54,7 +54,7 @@ public class FormatCsprojCommand : Command<FormatCsprojCommand.Settings>
var folderDirectory = settings.SolutionFolderPath; // Include the trailing slash
Console.WriteLine($"Retrieving C# Projects from {folderDirectory}");
var csprojList = SolutionParityChecker.RetrieveAllCSharpProjectFullPathsFromFolder(
var csprojList = SolutionProjectParity.RetrieveAllCSharpProjectFullPathsFromFolder(
folderDirectory
);

View File

@@ -34,7 +34,7 @@ public class ImplicitUsingsCommand : Command<ImplicitUsingsCommand.Settings>
var pathToSolutionFile = settings.SolutionFilePath;
Console.WriteLine($"Retrieving Solution from {pathToSolutionFile}");
var solutionFile = SolutionParityChecker.ParseSolutionFileFromPath(pathToSolutionFile);
var solutionFile = SolutionProjectParity.ParseSolutionFileFromPath(pathToSolutionFile);
if (solutionFile == null)
{
Console.WriteLine(
@@ -42,7 +42,7 @@ public class ImplicitUsingsCommand : Command<ImplicitUsingsCommand.Settings>
);
return 1;
}
var cSharpProjects = SolutionParityChecker.GetCSharpProjectObjectsFromSolutionFile(
var cSharpProjects = SolutionProjectParity.GetCSharpProjectObjectsFromSolutionFile(
solutionFile
);
Console.WriteLine($"Found {cSharpProjects.Count} C# Projects");
@@ -67,7 +67,7 @@ public class ImplicitUsingsCommand : Command<ImplicitUsingsCommand.Settings>
Console.WriteLine("==================================================");
Console.WriteLine("Adding missing implicit usings");
ImplicitUsings.AddMissingImplicitUsings(projectsMissingImplicitUsings);
var updatedProjects = SolutionParityChecker.GetCSharpProjectObjectsFromSolutionFile(
var updatedProjects = SolutionProjectParity.GetCSharpProjectObjectsFromSolutionFile(
solutionFile
);
var projectsWithMissing = ImplicitUsings.FindCSharpProjectsMissingImplicitUsings(

View File

@@ -19,14 +19,13 @@ public static class FormatCsproj
using var wr = new StreamWriter(csprojFilePath, false, Encoding.Default);
var settings =
new XmlWriterSettings
{
Indent = true,
IndentChars = "\t",
NewLineOnAttributes = false,
OmitXmlDeclaration = true
};
var settings = new XmlWriterSettings
{
Indent = true,
IndentChars = "\t",
NewLineOnAttributes = false,
OmitXmlDeclaration = true
};
using (var writer = XmlWriter.Create(wr, settings))
{
@@ -34,4 +33,4 @@ public static class FormatCsproj
writer.Close();
}
}
}
}

View File

@@ -4,10 +4,26 @@ namespace DotNetSolutionTools.Core;
public static class ImplicitUsings
{
public static List<ProjectRootElement> FindCSharpProjectsMissingImplicitUsings(List<ProjectRootElement> projectList)
public static List<string> FindCSharpProjectsMissingImplicitUsings(string solutionFilePath)
{
var solutionFile = SolutionFile.Parse(solutionFilePath);
var csprojList = SolutionProjectParity.GetCSharpProjectObjectsFromSolutionFile(
solutionFile
);
var projectsMissingImplicitUsings = FindCSharpProjectsMissingImplicitUsings(csprojList);
var projectsMissingImplicitUsingsStringList = projectsMissingImplicitUsings
.Select(x => x.FullPath)
.ToList();
return projectsMissingImplicitUsingsStringList;
}
public static List<ProjectRootElement> FindCSharpProjectsMissingImplicitUsings(
List<ProjectRootElement> projectList
)
{
var projectsMissingImplicitUsings = new List<ProjectRootElement>();
foreach (var project in projectList)
{
var implicitUsings = project.PropertyGroups
@@ -22,7 +38,9 @@ public static class ImplicitUsings
return projectsMissingImplicitUsings;
}
public static void AddMissingImplicitUsings(List<ProjectRootElement> projectsMissingImplicitUsings)
public static void AddMissingImplicitUsings(
List<ProjectRootElement> projectsMissingImplicitUsings
)
{
foreach (var project in projectsMissingImplicitUsings)
{
@@ -35,16 +53,20 @@ public static class ImplicitUsings
}
}
public static void EnableDisabledImplicitUsings(List<ProjectRootElement> projectsMissingImplicitUsings)
public static void EnableDisabledImplicitUsings(
List<ProjectRootElement> projectsMissingImplicitUsings
)
{
throw new NotImplementedException();
}
public static void EnableAllImplicitUsings(List<ProjectRootElement> projectsMissingImplicitUsings)
public static void EnableAllImplicitUsings(
List<ProjectRootElement> projectsMissingImplicitUsings
)
{
throw new NotImplementedException();
}
public static bool ProjectIsMissingImplicitUsings(ProjectRootElement project)
{
var implicitUsings = project.PropertyGroups
@@ -57,4 +79,4 @@ public static class ImplicitUsings
return false;
}
}
}

View File

@@ -2,7 +2,7 @@
namespace DotNetSolutionTools.Core;
public static class SolutionParityChecker
public static class SolutionProjectParity
{
public static List<string> CompareSolutionAndCSharpProjects(
string solutionFolderPath,