diff --git a/DotNetSolutionTools.Core/Models/Project.cs b/DotNetSolutionTools.Core/Models/Project.cs new file mode 100644 index 0000000..7f8f27b --- /dev/null +++ b/DotNetSolutionTools.Core/Models/Project.cs @@ -0,0 +1,8 @@ +namespace DotNetSolutionTools.Core.Models; + +public class Project +{ + public required string FullPath { get; set; } + public required string Name { get; set; } + public List DependsOn { get; set; } = []; +} diff --git a/DotNetSolutionTools.Core/SolutionBuildOrder.cs b/DotNetSolutionTools.Core/SolutionBuildOrder.cs new file mode 100644 index 0000000..8507f29 --- /dev/null +++ b/DotNetSolutionTools.Core/SolutionBuildOrder.cs @@ -0,0 +1,52 @@ +using DotNetSolutionTools.Core.Common; +using DotNetSolutionTools.Core.Models; +using Microsoft.Build.Construction; + +namespace DotNetSolutionTools.Core; + +public static class SolutionBuildOrder +{ + public static List Projects { get; set; } = []; + + public static List GetBuildOrder(string solutionFilePath) + { + var solutionFile = SlnHelper.ParseSolutionFileFromPath(solutionFilePath); + ArgumentNullException.ThrowIfNull(solutionFile); + var projects = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile); + Projects = projects; + + List projects2 = []; + foreach (var project in projects) + { + var projectName = Path.GetFileNameWithoutExtension(project.FullPath); + var project2 = new Project { FullPath = project.FullPath, Name = projectName }; + project2.DependsOn = GetDependencies(project2); + projects2.Add(project2); + } + + return projects2; + } + + public static List GetDependencies(Project project) + { + var projectReferences = Projects + .Single(s => s.FullPath == project.FullPath) + .AllChildren.OfType() + .Where(x => x.ElementName == "ProjectReference") + .ToList(); + + List dependencies = []; + foreach (var projectReference in projectReferences) + { + var fullPath = Path.Combine(Path.GetDirectoryName(project.FullPath)!, projectReference.Include); + fullPath = Path.GetFullPath(fullPath); + var dependency = Projects.Single(s => s.FullPath == fullPath); + var projectName = Path.GetFileNameWithoutExtension(dependency.FullPath); + var subProject = new Project { FullPath = dependency.FullPath, Name = projectName }; + subProject.DependsOn = GetDependencies(subProject); + dependencies.Add(subProject); + } + + return dependencies; + } +} diff --git a/DotNetSolutionTools.Photino/App.razor b/DotNetSolutionTools.Photino/App.razor new file mode 100644 index 0000000..d832b2f --- /dev/null +++ b/DotNetSolutionTools.Photino/App.razor @@ -0,0 +1,17 @@ + + + + + + + Not found + +

Sorry, there's nothing at this address.

+
+
+
+ +@code +{ + +} diff --git a/DotNetSolutionTools.Photino/AppThemeProvider.cs b/DotNetSolutionTools.Photino/AppThemeProvider.cs new file mode 100644 index 0000000..f8bd1d5 --- /dev/null +++ b/DotNetSolutionTools.Photino/AppThemeProvider.cs @@ -0,0 +1,15 @@ +using MudBlazor; + +namespace DotNetSolutionTools.Photino; + +public static class AppThemeProvider +{ + public static MudTheme GetTheme() + { + var theme = new MudTheme(); + theme.Typography.H5.FontSize = "1.4rem"; + theme.Typography.H5.FontWeight = 500; + theme.Typography.H6.FontSize = "1.1rem"; + return theme; + } +} diff --git a/DotNetSolutionTools.Photino/Components/BuildOrderDiagram.razor b/DotNetSolutionTools.Photino/Components/BuildOrderDiagram.razor new file mode 100644 index 0000000..0ecc108 --- /dev/null +++ b/DotNetSolutionTools.Photino/Components/BuildOrderDiagram.razor @@ -0,0 +1,61 @@ +@using Blazor.Diagrams +@using Blazor.Diagrams.Core.Anchors +@using Blazor.Diagrams.Core.Geometry +@using Blazor.Diagrams.Core.Models +@using DotNetSolutionTools.Core.Models + +

BuildOrderDiagram

+
+ + + +
+ +@code { + [Parameter, EditorRequired] + public List Projects { get; set; } + + private BlazorDiagram Diagram { get; set; } = null!; + + protected override void OnInitialized() + { + Diagram = new BlazorDiagram(); + foreach (var project in Projects) + { + var node = Diagram.Nodes.Add(new NodeModel() + { + Title = project.Name, + + }); + foreach (var dependency in project.DependsOn) + { + var dependencyNode = Diagram.Nodes.Add(new NodeModel() + { + Title = dependency.Name + }); + Diagram.Links.Add(new LinkModel(node, dependencyNode)); + } + } + } + + protected void Example() + { + Diagram = new BlazorDiagram(); + var firstNode = Diagram.Nodes.Add(new NodeModel(position: new Point(50, 50)) + { + Title = "Node 1" + }); + var secondNode = Diagram.Nodes.Add(new NodeModel(position: new Point(200, 100)) + { + Title = "Node 2" + }); + var leftPort = secondNode.AddPort(PortAlignment.Left); + var rightPort = secondNode.AddPort(PortAlignment.Right); + // The connection point will be the intersection of + // a line going from the target to the center of the source + var sourceAnchor = new ShapeIntersectionAnchor(firstNode); + // The connection point will be the port's position + var targetAnchor = new SinglePortAnchor(leftPort); + var link = Diagram.Links.Add(new LinkModel(sourceAnchor, targetAnchor)); + } +} \ No newline at end of file diff --git a/DotNetSolutionTools.Photino/DotNetSolutionTools.Photino.csproj b/DotNetSolutionTools.Photino/DotNetSolutionTools.Photino.csproj new file mode 100644 index 0000000..5cdb209 --- /dev/null +++ b/DotNetSolutionTools.Photino/DotNetSolutionTools.Photino.csproj @@ -0,0 +1,47 @@ + + + + WinExe + net9.0 + favicon.ico + enable + enable + + true + false + true + + + + + + + + + + + + + + + PreserveNewest + + + + + + Always + + + + + + + + + + + + + + diff --git a/DotNetSolutionTools.Photino/Layout/MainLayout.razor b/DotNetSolutionTools.Photino/Layout/MainLayout.razor new file mode 100644 index 0000000..7d423e7 --- /dev/null +++ b/DotNetSolutionTools.Photino/Layout/MainLayout.razor @@ -0,0 +1,34 @@ +@inherits LayoutComponentBase + + + + + + + + + + + + + + DotNetSolutionTools.Photino + + + + + + + @Body + + + + +@code { + bool _drawerOpen = true; + + void DrawerToggle() + { + _drawerOpen = !_drawerOpen; + } +} diff --git a/DotNetSolutionTools.Photino/Layout/NavMenu.razor b/DotNetSolutionTools.Photino/Layout/NavMenu.razor new file mode 100644 index 0000000..8b2eaf4 --- /dev/null +++ b/DotNetSolutionTools.Photino/Layout/NavMenu.razor @@ -0,0 +1,3 @@ + + Home + diff --git a/DotNetSolutionTools.Photino/Models/AppState.cs b/DotNetSolutionTools.Photino/Models/AppState.cs new file mode 100644 index 0000000..0faee31 --- /dev/null +++ b/DotNetSolutionTools.Photino/Models/AppState.cs @@ -0,0 +1,8 @@ +namespace DotNetSolutionTools.Photino.Models; + +public class AppState +{ + public required string SolutionFolderPath { get; set; } + public required string SolutionFilePath { get; set; } + public required string CsprojFilePath { get; set; } +} diff --git a/DotNetSolutionTools.Photino/Pages/Home.razor b/DotNetSolutionTools.Photino/Pages/Home.razor new file mode 100644 index 0000000..7df5955 --- /dev/null +++ b/DotNetSolutionTools.Photino/Pages/Home.razor @@ -0,0 +1,41 @@ +@page "/" +@using DotNetSolutionTools.Core +@using DotNetSolutionTools.Core.Models +@using DotNetSolutionTools.Photino.Models +@inject AppState AppState + + + + Populate project dependencies + + +@if (_projects.Count > 0) +{ + +} +@foreach(var project in _projects) +{ + @project.Name +} + +@code { + private string? _solutionFilePath; + private List _projects = []; + + protected override void OnInitialized() + { + _solutionFilePath = AppState.SolutionFilePath; + } + + private void Populate() + { + var result = SolutionBuildOrder.GetBuildOrder(_solutionFilePath); + _projects = result; + } + + private void SetToAppState() + { + AppState.SolutionFilePath = _solutionFilePath; + } + +} diff --git a/DotNetSolutionTools.Photino/Program.cs b/DotNetSolutionTools.Photino/Program.cs new file mode 100644 index 0000000..249e5e2 --- /dev/null +++ b/DotNetSolutionTools.Photino/Program.cs @@ -0,0 +1,91 @@ +using System.Text.Json; +using DotNetSolutionTools.Photino.Models; +using Microsoft.Build.Locator; +using Microsoft.Extensions.DependencyInjection; +using MudBlazor.Services; +using Photino.Blazor; + +namespace DotNetSolutionTools.Photino; + +public class Program +{ + [STAThread] + public static void Main(string[] args) + { + var appBuilder = PhotinoBlazorAppBuilder.CreateDefault(args); + + appBuilder.Services.AddLogging(); + appBuilder.Services.AddMudServices(); + appBuilder.Services.AddSingleton(); + + appBuilder.RootComponents.Add("app"); + + var app = appBuilder.Build(); + + app.MainWindow.SetSize(1400, 800) + .SetDevToolsEnabled(true) + .SetLogVerbosity(0) + //.SetIconFile("favicon.ico") + .SetTitle("DotNetSolutionTools.Photino"); + + AppDomain.CurrentDomain.UnhandledException += (sender, error) => + { + app.MainWindow.ShowMessage("Fatal exception", error.ExceptionObject.ToString()); + }; + + var instance = MSBuildLocator + .QueryVisualStudioInstances() + .OrderByDescending(instance => instance.Version) + .First(); + MSBuildLocator.RegisterInstance(instance); + + var configFilePath = GetConfigFilePath(); + + using var scope = app.Services.CreateScope(); + var appState = scope.ServiceProvider.GetRequiredService(); + + LoadAppStateFromConfigFile(appState, configFilePath); + + app.MainWindow.RegisterWindowClosingHandler( + (sender, eventArgs) => + { + using var stream = File.Create(configFilePath); + JsonSerializer.Serialize(stream, appState); + stream.Flush(); + return false; + } + ); + + app.Run(); + } + + private static string GetConfigFilePath() + { + var folder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + var configFolder = Path.Combine(folder, "DotNetSolutionTools.Photino"); + Directory.CreateDirectory(configFolder); + var configFilePath = Path.Combine(configFolder, "config.json"); + return configFilePath; + } + + private static void LoadAppStateFromConfigFile(AppState appState, string configFilePath) + { + if (File.Exists(configFilePath) is false) + { + File.WriteAllText(configFilePath, string.Empty); + } + + using var stream = File.OpenRead(configFilePath); + if (stream.Length is 0) + { + return; + } + var deserializedAppState = JsonSerializer.Deserialize(stream); + if (deserializedAppState is not null) + { + appState.SolutionFolderPath = deserializedAppState.SolutionFolderPath; + appState.SolutionFilePath = deserializedAppState.SolutionFilePath; + appState.CsprojFilePath = deserializedAppState.CsprojFilePath; + } + } +} diff --git a/DotNetSolutionTools.Photino/Properties/launchSettings.json b/DotNetSolutionTools.Photino/Properties/launchSettings.json new file mode 100644 index 0000000..98ea0ab --- /dev/null +++ b/DotNetSolutionTools.Photino/Properties/launchSettings.json @@ -0,0 +1,22 @@ +{ +"$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "Run": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + }, + "(Watch)": { + "commandName": "Executable", + "executablePath": "dotnet", + "workingDirectory": "$(ProjectDir)", + "commandLineArgs": "watch run", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_WATCH_RESTART_ON_RUDE_EDIT": "true" + } + } + } +} diff --git a/DotNetSolutionTools.Photino/_Imports.razor b/DotNetSolutionTools.Photino/_Imports.razor new file mode 100644 index 0000000..8cac136 --- /dev/null +++ b/DotNetSolutionTools.Photino/_Imports.razor @@ -0,0 +1,15 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Authorization +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using Microsoft.Extensions.Logging +@using MudBlazor +@using DotNetSolutionTools.Photino.Layout +@using DotNetSolutionTools.Photino +@using DotNetSolutionTools.Photino.Models +@using DotNetSolutionTools.Photino.Components +@using Blazor.Diagrams.Components \ No newline at end of file diff --git a/DotNetSolutionTools.Photino/favicon.ico b/DotNetSolutionTools.Photino/favicon.ico new file mode 100644 index 0000000..7f6f31e Binary files /dev/null and b/DotNetSolutionTools.Photino/favicon.ico differ diff --git a/DotNetSolutionTools.Photino/wwwroot/css/app.css b/DotNetSolutionTools.Photino/wwwroot/css/app.css new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/DotNetSolutionTools.Photino/wwwroot/css/app.css @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/DotNetSolutionTools.Photino/wwwroot/index.html b/DotNetSolutionTools.Photino/wwwroot/index.html new file mode 100644 index 0000000..aaf5776 --- /dev/null +++ b/DotNetSolutionTools.Photino/wwwroot/index.html @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + Loading... + +
+ An unhandled error has occurred. + Reload + 🗙 +
+ + + + + + diff --git a/DotNetSolutionTools.sln b/DotNetSolutionTools.sln index 3b3ad0b..9ae3d87 100644 --- a/DotNetSolutionTools.sln +++ b/DotNetSolutionTools.sln @@ -19,6 +19,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{B38F .github\dependabot.yaml = .github\dependabot.yaml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetSolutionTools.Photino", "DotNetSolutionTools.Photino\DotNetSolutionTools.Photino.csproj", "{6030845C-0B67-4F55-ABC2-47ACD18AE216}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -37,5 +39,9 @@ Global {A7F46873-C8EC-4C2E-86F5-B9F2472C8036}.Debug|Any CPU.Build.0 = Debug|Any CPU {A7F46873-C8EC-4C2E-86F5-B9F2472C8036}.Release|Any CPU.ActiveCfg = Release|Any CPU {A7F46873-C8EC-4C2E-86F5-B9F2472C8036}.Release|Any CPU.Build.0 = Release|Any CPU + {6030845C-0B67-4F55-ABC2-47ACD18AE216}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6030845C-0B67-4F55-ABC2-47ACD18AE216}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6030845C-0B67-4F55-ABC2-47ACD18AE216}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6030845C-0B67-4F55-ABC2-47ACD18AE216}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal