feat: 前端界面优化 (#48)
Co-authored-by: jackfiled <xcrenchangjun@outlook.com> Reviewed-on: PostGuard/Canon#48 Co-authored-by: Ichirinko <1621543655@qq.com> Co-committed-by: Ichirinko <1621543655@qq.com>
This commit is contained in:
		@@ -17,12 +17,13 @@
 | 
			
		||||
    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.3" />
 | 
			
		||||
    <PackageReference Include="MongoDB.Driver.GridFS" Version="2.25.0" />
 | 
			
		||||
    <PackageReference Include="MongoDB.EntityFrameworkCore" Version="7.0.0-preview.1" />
 | 
			
		||||
    <PackageReference Include="SkiaSharp" Version="2.88.8" />
 | 
			
		||||
    <PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.8" />
 | 
			
		||||
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ProjectReference Include="..\Canon.Core\Canon.Core.csproj" />
 | 
			
		||||
    <ProjectReference Include="..\Canon.Visualization\Canon.Visualization.csproj" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
  <Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
using SkiaSharp;
 | 
			
		||||
 | 
			
		||||
namespace Canon.Visualization.Models;
 | 
			
		||||
namespace Canon.Server.Models;
 | 
			
		||||
 | 
			
		||||
public sealed class Brush(SKCanvas canvas) : IDisposable
 | 
			
		||||
{
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
using Canon.Core.SyntaxNodes;
 | 
			
		||||
using SkiaSharp;
 | 
			
		||||
 | 
			
		||||
namespace Canon.Visualization.Models;
 | 
			
		||||
namespace Canon.Server.Models;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// 展示树节点
 | 
			
		||||
@@ -9,7 +9,7 @@ namespace Canon.Visualization.Models;
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class PresentableTreeNode
 | 
			
		||||
{
 | 
			
		||||
    public float X { get; set; } = -1;
 | 
			
		||||
    public float X { get; set; }
 | 
			
		||||
    public float Y { get; set; }
 | 
			
		||||
 | 
			
		||||
    public SKPoint Position => new(X, Y);
 | 
			
		||||
@@ -2,7 +2,6 @@ using Canon.Core.Abstractions;
 | 
			
		||||
using Canon.Core.LexicalParser;
 | 
			
		||||
using Canon.Server.Extensions;
 | 
			
		||||
using Canon.Server.Services;
 | 
			
		||||
using Canon.Visualization.Services;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
 | 
			
		||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@
 | 
			
		||||
using Canon.Core.LexicalParser;
 | 
			
		||||
using Canon.Core.SyntaxNodes;
 | 
			
		||||
using Canon.Server.Models;
 | 
			
		||||
using Canon.Visualization.Services;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
 | 
			
		||||
namespace Canon.Server.Services;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										89
									
								
								Canon.Server/Services/SyntaxTreePresentationService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								Canon.Server/Services/SyntaxTreePresentationService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
using Canon.Core.SyntaxNodes;
 | 
			
		||||
using Canon.Server.Models;
 | 
			
		||||
using SkiaSharp;
 | 
			
		||||
 | 
			
		||||
namespace Canon.Server.Services;
 | 
			
		||||
 | 
			
		||||
public class SyntaxTreePresentationService
 | 
			
		||||
{
 | 
			
		||||
    private const float Scale = 150;
 | 
			
		||||
 | 
			
		||||
    public Stream Present(ProgramStruct root)
 | 
			
		||||
    {
 | 
			
		||||
        PresentableTreeNode presentableTreeRoot = PresentableTreeNode.Build(root);
 | 
			
		||||
        ScaleTree(presentableTreeRoot);
 | 
			
		||||
 | 
			
		||||
        (float height, float width) = presentableTreeRoot.CalculateImageSize();
 | 
			
		||||
        using SKSurface surface = SKSurface.Create(
 | 
			
		||||
            new SKImageInfo((int)(width + 2 * Scale), (int)(height * Scale)));
 | 
			
		||||
 | 
			
		||||
        surface.Canvas.Clear(SKColors.White);
 | 
			
		||||
 | 
			
		||||
        using Brush brush = new(surface.Canvas);
 | 
			
		||||
        DrawNode(presentableTreeRoot, brush);
 | 
			
		||||
 | 
			
		||||
        using SKImage image = surface.Snapshot();
 | 
			
		||||
        SKData data = image.Encode();
 | 
			
		||||
 | 
			
		||||
        return data.AsStream();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void DrawNode(PresentableTreeNode node, Brush brush)
 | 
			
		||||
    {
 | 
			
		||||
        foreach (PresentableTreeNode child in node.Children)
 | 
			
		||||
        {
 | 
			
		||||
            brush.DrawLine(node.Position, child.Position);
 | 
			
		||||
            DrawNode(child, brush);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        brush.DrawText(node.Position, node.DisplayText);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void ScaleTree(PresentableTreeNode root)
 | 
			
		||||
    {
 | 
			
		||||
        Queue<PresentableTreeNode> queue = [];
 | 
			
		||||
        queue.Enqueue(root);
 | 
			
		||||
        float minX = float.MaxValue;
 | 
			
		||||
 | 
			
		||||
        // 第一次遍历
 | 
			
		||||
        // 放大坐标并获得最左侧的节点X坐标
 | 
			
		||||
        while (queue.Count != 0)
 | 
			
		||||
        {
 | 
			
		||||
            PresentableTreeNode node = queue.Dequeue();
 | 
			
		||||
 | 
			
		||||
            node.X *= Scale;
 | 
			
		||||
            node.X += Scale;
 | 
			
		||||
            node.Y *= Scale;
 | 
			
		||||
            node.Y += Scale;
 | 
			
		||||
 | 
			
		||||
            minX = float.Min(minX, node.X);
 | 
			
		||||
 | 
			
		||||
            foreach (PresentableTreeNode child in node.Children)
 | 
			
		||||
            {
 | 
			
		||||
                queue.Enqueue(child);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (minX >= Scale)
 | 
			
		||||
        {
 | 
			
		||||
            // 判断最左侧的节点位置是否正确
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        float delta = Scale - minX;
 | 
			
		||||
 | 
			
		||||
        // 第二次遍历调整位置
 | 
			
		||||
        queue.Enqueue(root);
 | 
			
		||||
        while (queue.Count != 0)
 | 
			
		||||
        {
 | 
			
		||||
            PresentableTreeNode node = queue.Dequeue();
 | 
			
		||||
 | 
			
		||||
            node.X += delta;
 | 
			
		||||
 | 
			
		||||
            foreach (PresentableTreeNode child in node.Children)
 | 
			
		||||
            {
 | 
			
		||||
                queue.Enqueue(child);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -15,6 +15,7 @@
 | 
			
		||||
    "@fontsource/roboto": "^5.0.12",
 | 
			
		||||
    "@mui/icons-material": "^5.15.14",
 | 
			
		||||
    "@mui/material": "^5.15.14",
 | 
			
		||||
    "notistack": "^3.0.1",
 | 
			
		||||
    "openapi-fetch": "^0.9.3",
 | 
			
		||||
    "openapi-typescript": "^6.7.5",
 | 
			
		||||
    "react": "^18.2.0",
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import {CSSProperties, useState} from "react";
 | 
			
		||||
import {OutputField} from "./OutputField.tsx";
 | 
			
		||||
import createClient from "openapi-fetch";
 | 
			
		||||
import * as openapi from '../openapi';
 | 
			
		||||
import {enqueueSnackbar} from "notistack";
 | 
			
		||||
 | 
			
		||||
const client = createClient<openapi.paths>();
 | 
			
		||||
 | 
			
		||||
@@ -23,6 +24,7 @@ export function Index() {
 | 
			
		||||
        id: "",
 | 
			
		||||
        imageAddress: "pic/uncompiled.png"
 | 
			
		||||
    });
 | 
			
		||||
    //const {enqueueSnackbar} = useSnackbar();
 | 
			
		||||
    const handleValueChange = (value: string) => {
 | 
			
		||||
        setInputValue(value);
 | 
			
		||||
    };
 | 
			
		||||
@@ -43,6 +45,10 @@ export function Index() {
 | 
			
		||||
                id: data.id,
 | 
			
		||||
                imageAddress: data.imageAddress
 | 
			
		||||
            })
 | 
			
		||||
            enqueueSnackbar("编译成功", {variant: "success", anchorOrigin: {vertical: 'top', horizontal: 'right'}});
 | 
			
		||||
        } else {
 | 
			
		||||
            // error
 | 
			
		||||
            enqueueSnackbar("编译失败", {variant: "error", anchorOrigin: {vertical: 'top', horizontal: 'right'}});
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,10 +6,12 @@ import '@fontsource/roboto/400.css';
 | 
			
		||||
import '@fontsource/roboto/500.css';
 | 
			
		||||
import '@fontsource/roboto/700.css';
 | 
			
		||||
import {CssBaseline} from '@mui/material';
 | 
			
		||||
import {SnackbarProvider} from "notistack";
 | 
			
		||||
 | 
			
		||||
ReactDOM.createRoot(document.getElementById('root')!).render(
 | 
			
		||||
    <React.StrictMode>
 | 
			
		||||
        <CssBaseline/>
 | 
			
		||||
        <SnackbarProvider/>
 | 
			
		||||
            <App/>
 | 
			
		||||
    </React.StrictMode>,
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,6 @@
 | 
			
		||||
 | 
			
		||||
    <ItemGroup>
 | 
			
		||||
      <ProjectReference Include="..\Canon.Core\Canon.Core.csproj" />
 | 
			
		||||
      <ProjectReference Include="..\Canon.Visualization\Canon.Visualization.csproj" />
 | 
			
		||||
    </ItemGroup>
 | 
			
		||||
 | 
			
		||||
</Project>
 | 
			
		||||
 
 | 
			
		||||
@@ -84,8 +84,9 @@ public class PascalGrammarTests
 | 
			
		||||
    {
 | 
			
		||||
        const string program = """
 | 
			
		||||
                               program varTest;
 | 
			
		||||
                               var a : char;
 | 
			
		||||
                               var a : integer;
 | 
			
		||||
                               begin
 | 
			
		||||
                               a := 9 div 1;
 | 
			
		||||
                               end.
 | 
			
		||||
                               """;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +0,0 @@
 | 
			
		||||
<Project Sdk="Microsoft.NET.Sdk">
 | 
			
		||||
 | 
			
		||||
    <PropertyGroup>
 | 
			
		||||
        <TargetFramework>net8.0</TargetFramework>
 | 
			
		||||
        <ImplicitUsings>enable</ImplicitUsings>
 | 
			
		||||
        <Nullable>enable</Nullable>
 | 
			
		||||
    </PropertyGroup>
 | 
			
		||||
 | 
			
		||||
    <ItemGroup>
 | 
			
		||||
      <ProjectReference Include="..\Canon.Core\Canon.Core.csproj" />
 | 
			
		||||
    </ItemGroup>
 | 
			
		||||
 | 
			
		||||
    <ItemGroup>
 | 
			
		||||
      <PackageReference Include="SkiaSharp" Version="2.88.7" />
 | 
			
		||||
      <PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.7"/>
 | 
			
		||||
    </ItemGroup>
 | 
			
		||||
 | 
			
		||||
</Project>
 | 
			
		||||
@@ -1,54 +0,0 @@
 | 
			
		||||
using Canon.Core.SyntaxNodes;
 | 
			
		||||
using Canon.Visualization.Models;
 | 
			
		||||
using SkiaSharp;
 | 
			
		||||
 | 
			
		||||
namespace Canon.Visualization.Services;
 | 
			
		||||
 | 
			
		||||
public class SyntaxTreePresentationService
 | 
			
		||||
{
 | 
			
		||||
    private const float Scale = 150;
 | 
			
		||||
 | 
			
		||||
    public Stream Present(ProgramStruct root)
 | 
			
		||||
    {
 | 
			
		||||
        PresentableTreeNode presentableTreeRoot = PresentableTreeNode.Build(root);
 | 
			
		||||
        ScaleTree(presentableTreeRoot);
 | 
			
		||||
 | 
			
		||||
        (float height, float width) = presentableTreeRoot.CalculateImageSize();
 | 
			
		||||
        using SKSurface surface = SKSurface.Create(
 | 
			
		||||
            new SKImageInfo((int)(width + 2 * Scale), (int)(height * Scale)));
 | 
			
		||||
 | 
			
		||||
        surface.Canvas.Clear(SKColors.White);
 | 
			
		||||
 | 
			
		||||
        using Brush brush = new(surface.Canvas);
 | 
			
		||||
        DrawNode(presentableTreeRoot, brush);
 | 
			
		||||
 | 
			
		||||
        using SKImage image = surface.Snapshot();
 | 
			
		||||
        SKData data = image.Encode();
 | 
			
		||||
 | 
			
		||||
        return data.AsStream();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void DrawNode(PresentableTreeNode node, Brush brush)
 | 
			
		||||
    {
 | 
			
		||||
        foreach (PresentableTreeNode child in node.Children)
 | 
			
		||||
        {
 | 
			
		||||
            brush.DrawLine(node.Position, child.Position);
 | 
			
		||||
            DrawNode(child, brush);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        brush.DrawText(node.Position, node.DisplayText);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void ScaleTree(PresentableTreeNode node)
 | 
			
		||||
    {
 | 
			
		||||
        node.X *= Scale;
 | 
			
		||||
        node.X += Scale;
 | 
			
		||||
        node.Y *= Scale;
 | 
			
		||||
        node.Y += Scale;
 | 
			
		||||
 | 
			
		||||
        foreach (PresentableTreeNode child in node.Children)
 | 
			
		||||
        {
 | 
			
		||||
            ScaleTree(child);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -26,8 +26,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
 | 
			
		||||
		.gitea\workflows\build.yaml = .gitea\workflows\build.yaml
 | 
			
		||||
	EndProjectSection
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Canon.Visualization", "Canon.Visualization\Canon.Visualization.csproj", "{23644467-2BCB-422D-8942-C20AF4A7F429}"
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Canon.Server", "Canon.Server\Canon.Server.csproj", "{401112EA-1A87-4D1C-9B6D-085309F4137E}"
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Canon.Generator", "Canon.Generator\Canon.Generator.csproj", "{32C103C4-589C-4DC2-B173-55B1799B62CE}"
 | 
			
		||||
@@ -53,10 +51,6 @@ Global
 | 
			
		||||
		{E5F2B97B-3766-466D-9309-BA361F0CE15E}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
			
		||||
		{E5F2B97B-3766-466D-9309-BA361F0CE15E}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
			
		||||
		{E5F2B97B-3766-466D-9309-BA361F0CE15E}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
			
		||||
		{23644467-2BCB-422D-8942-C20AF4A7F429}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 | 
			
		||||
		{23644467-2BCB-422D-8942-C20AF4A7F429}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
			
		||||
		{23644467-2BCB-422D-8942-C20AF4A7F429}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
			
		||||
		{23644467-2BCB-422D-8942-C20AF4A7F429}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
			
		||||
		{401112EA-1A87-4D1C-9B6D-085309F4137E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 | 
			
		||||
		{401112EA-1A87-4D1C-9B6D-085309F4137E}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
			
		||||
		{401112EA-1A87-4D1C-9B6D-085309F4137E}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user