Add Photino project

This commit is contained in:
Matthew Parker [SSW]
2024-11-21 20:48:11 +10:00
parent dbb5609500
commit 5277a7eb70
17 changed files with 449 additions and 0 deletions

View File

@@ -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<Project> DependsOn { get; set; } = [];
}

View File

@@ -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<ProjectRootElement> Projects { get; set; } = [];
public static List<Project> GetBuildOrder(string solutionFilePath)
{
var solutionFile = SlnHelper.ParseSolutionFileFromPath(solutionFilePath);
ArgumentNullException.ThrowIfNull(solutionFile);
var projects = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
Projects = projects;
List<Project> 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<Project> GetDependencies(Project project)
{
var projectReferences = Projects
.Single(s => s.FullPath == project.FullPath)
.AllChildren.OfType<ProjectItemElement>()
.Where(x => x.ElementName == "ProjectReference")
.ToList();
List<Project> 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;
}
}

View File

@@ -0,0 +1,17 @@
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
@code
{
}

View File

@@ -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;
}
}

View File

@@ -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
<h3>BuildOrderDiagram</h3>
<div style="width: 100%; height: 400px; border: black">
<CascadingValue Value="Diagram" IsFixed="true">
<DiagramCanvas />
</CascadingValue>
</div>
@code {
[Parameter, EditorRequired]
public List<Project> 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));
}
}

View File

@@ -0,0 +1,47 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<OutputType>WinExe</OutputType> <!-- Doesn't actually mean Windows Exe, 'Exe' = console entrypoint, 'WinExe' = application entry point -->
<TargetFramework>net9.0</TargetFramework>
<ApplicationIcon>favicon.ico</ApplicationIcon>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>false</SelfContained>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
</PropertyGroup>
<ItemGroup>
<SupportedPlatform Include="browser" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MudBlazor" Version="7.15.0" />
<PackageReference Include="Photino.Blazor" Version="3.2.0" />
<PackageReference Include="Z.Blazor.Diagrams" Version="3.0.2" />
</ItemGroup>
<ItemGroup>
<Content Update="wwwroot\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Update="favicon.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DotNetSolutionTools.Core\DotNetSolutionTools.Core.csproj" />
</ItemGroup>
<Target Name="RemoveConflictingAssets" BeforeTargets="ResolveStaticWebAssetsConfiguration">
<ItemGroup>
<StaticWebAsset Remove="@(StaticWebAsset)" Condition="'%(Extension)' == '.gz'" />
</ItemGroup>
</Target>
</Project>

View File

@@ -0,0 +1,34 @@
@inherits LayoutComponentBase
<MudThemeProvider Theme="@AppThemeProvider.GetTheme()" />
<MudPopoverProvider/>
<MudDialogProvider/>
<MudSnackbarProvider/>
<MudLayout>
<MudAppBar Dense="true">
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@((e) => DrawerToggle())" />
</MudAppBar>
<MudDrawer @bind-Open="@_drawerOpen">
<MudDrawerHeader>
<MudStack Row="false" Justify="Justify.Center">
<MudText Typo="Typo.h5">DotNetSolutionTools.Photino</MudText>
</MudStack>
</MudDrawerHeader>
<NavMenu/>
</MudDrawer>
<MudMainContent>
<MudContainer Class="px-8 py-6">
@Body
</MudContainer>
</MudMainContent>
</MudLayout>
@code {
bool _drawerOpen = true;
void DrawerToggle()
{
_drawerOpen = !_drawerOpen;
}
}

View File

@@ -0,0 +1,3 @@
<MudNavMenu>
<MudNavLink Href="/" Icon="@Icons.Material.Filled.Home" Match="NavLinkMatch.All">Home</MudNavLink>
</MudNavMenu>

View File

@@ -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; }
}

View File

@@ -0,0 +1,41 @@
@page "/"
@using DotNetSolutionTools.Core
@using DotNetSolutionTools.Core.Models
@using DotNetSolutionTools.Photino.Models
@inject AppState AppState
<MudStack>
<MudInput T="string" Label="Solution File Path" @bind-Value="_solutionFilePath" @bind-Value:after="SetToAppState" />
<MudButton OnClick="@Populate">Populate project dependencies</MudButton>
</MudStack>
@if (_projects.Count > 0)
{
<BuildOrderDiagram Projects="_projects"/>
}
@foreach(var project in _projects)
{
<MudText>@project.Name</MudText>
}
@code {
private string? _solutionFilePath;
private List<Project> _projects = [];
protected override void OnInitialized()
{
_solutionFilePath = AppState.SolutionFilePath;
}
private void Populate()
{
var result = SolutionBuildOrder.GetBuildOrder(_solutionFilePath);
_projects = result;
}
private void SetToAppState()
{
AppState.SolutionFilePath = _solutionFilePath;
}
}

View File

@@ -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<AppState>();
appBuilder.RootComponents.Add<App>("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<AppState>();
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<AppState>(stream);
if (deserializedAppState is not null)
{
appState.SolutionFolderPath = deserializedAppState.SolutionFolderPath;
appState.SolutionFilePath = deserializedAppState.SolutionFilePath;
appState.CsprojFilePath = deserializedAppState.CsprojFilePath;
}
}
}

View File

@@ -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"
}
}
}
}

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<base href="/" />
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" />
<link href="DotNetSolutionTools.Photino.styles.css" rel="stylesheet" />
<link href="_content/Z.Blazor.Diagrams/style.min.css" rel="stylesheet" />
<link href="_content/Z.Blazor.Diagrams/default.styles.min.css" rel="stylesheet" />
</head>
<body>
<app>Loading...</app>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webview.js"></script>
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
<script src="_content/Z.Blazor.Diagrams/script.min.js"></script>
</body>
</html>

View File

@@ -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