From 5277a7eb708800b114d98ec8265adf71c2b938bf Mon Sep 17 00:00:00 2001 From: "Matthew Parker [SSW]" <61717342+MattParkerDev@users.noreply.github.com> Date: Thu, 21 Nov 2024 20:48:11 +1000 Subject: [PATCH] Add Photino project --- DotNetSolutionTools.Core/Models/Project.cs | 8 ++ .../SolutionBuildOrder.cs | 52 ++++++++++ DotNetSolutionTools.Photino/App.razor | 17 ++++ .../AppThemeProvider.cs | 15 +++ .../Components/BuildOrderDiagram.razor | 61 ++++++++++++ .../DotNetSolutionTools.Photino.csproj | 47 +++++++++ .../Layout/MainLayout.razor | 34 +++++++ .../Layout/NavMenu.razor | 3 + .../Models/AppState.cs | 8 ++ DotNetSolutionTools.Photino/Pages/Home.razor | 41 ++++++++ DotNetSolutionTools.Photino/Program.cs | 91 ++++++++++++++++++ .../Properties/launchSettings.json | 22 +++++ DotNetSolutionTools.Photino/_Imports.razor | 15 +++ DotNetSolutionTools.Photino/favicon.ico | Bin 0 -> 183198 bytes .../wwwroot/css/app.css | 1 + .../wwwroot/index.html | 28 ++++++ DotNetSolutionTools.sln | 6 ++ 17 files changed, 449 insertions(+) create mode 100644 DotNetSolutionTools.Core/Models/Project.cs create mode 100644 DotNetSolutionTools.Core/SolutionBuildOrder.cs create mode 100644 DotNetSolutionTools.Photino/App.razor create mode 100644 DotNetSolutionTools.Photino/AppThemeProvider.cs create mode 100644 DotNetSolutionTools.Photino/Components/BuildOrderDiagram.razor create mode 100644 DotNetSolutionTools.Photino/DotNetSolutionTools.Photino.csproj create mode 100644 DotNetSolutionTools.Photino/Layout/MainLayout.razor create mode 100644 DotNetSolutionTools.Photino/Layout/NavMenu.razor create mode 100644 DotNetSolutionTools.Photino/Models/AppState.cs create mode 100644 DotNetSolutionTools.Photino/Pages/Home.razor create mode 100644 DotNetSolutionTools.Photino/Program.cs create mode 100644 DotNetSolutionTools.Photino/Properties/launchSettings.json create mode 100644 DotNetSolutionTools.Photino/_Imports.razor create mode 100644 DotNetSolutionTools.Photino/favicon.ico create mode 100644 DotNetSolutionTools.Photino/wwwroot/css/app.css create mode 100644 DotNetSolutionTools.Photino/wwwroot/index.html 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 0000000000000000000000000000000000000000..7f6f31eb3ca5e32471e099399fe3bd7a737e1b2a GIT binary patch literal 183198 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 literal 0 HcmV?d00001 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