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:
parent
3f5cfb13a6
commit
a95987b3ce
|
@ -17,12 +17,13 @@
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.3" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.3" />
|
||||||
<PackageReference Include="MongoDB.Driver.GridFS" Version="2.25.0" />
|
<PackageReference Include="MongoDB.Driver.GridFS" Version="2.25.0" />
|
||||||
<PackageReference Include="MongoDB.EntityFrameworkCore" Version="7.0.0-preview.1" />
|
<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" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Canon.Core\Canon.Core.csproj" />
|
<ProjectReference Include="..\Canon.Core\Canon.Core.csproj" />
|
||||||
<ProjectReference Include="..\Canon.Visualization\Canon.Visualization.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
|
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Canon.Visualization.Models;
|
namespace Canon.Server.Models;
|
||||||
|
|
||||||
public sealed class Brush(SKCanvas canvas) : IDisposable
|
public sealed class Brush(SKCanvas canvas) : IDisposable
|
||||||
{
|
{
|
|
@ -1,7 +1,7 @@
|
||||||
using Canon.Core.SyntaxNodes;
|
using Canon.Core.SyntaxNodes;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Canon.Visualization.Models;
|
namespace Canon.Server.Models;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 展示树节点
|
/// 展示树节点
|
||||||
|
@ -9,7 +9,7 @@ namespace Canon.Visualization.Models;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PresentableTreeNode
|
public class PresentableTreeNode
|
||||||
{
|
{
|
||||||
public float X { get; set; } = -1;
|
public float X { get; set; }
|
||||||
public float Y { get; set; }
|
public float Y { get; set; }
|
||||||
|
|
||||||
public SKPoint Position => new(X, Y);
|
public SKPoint Position => new(X, Y);
|
|
@ -2,7 +2,6 @@ using Canon.Core.Abstractions;
|
||||||
using Canon.Core.LexicalParser;
|
using Canon.Core.LexicalParser;
|
||||||
using Canon.Server.Extensions;
|
using Canon.Server.Extensions;
|
||||||
using Canon.Server.Services;
|
using Canon.Server.Services;
|
||||||
using Canon.Visualization.Services;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
using Canon.Core.LexicalParser;
|
using Canon.Core.LexicalParser;
|
||||||
using Canon.Core.SyntaxNodes;
|
using Canon.Core.SyntaxNodes;
|
||||||
using Canon.Server.Models;
|
using Canon.Server.Models;
|
||||||
using Canon.Visualization.Services;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace Canon.Server.Services;
|
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",
|
"@fontsource/roboto": "^5.0.12",
|
||||||
"@mui/icons-material": "^5.15.14",
|
"@mui/icons-material": "^5.15.14",
|
||||||
"@mui/material": "^5.15.14",
|
"@mui/material": "^5.15.14",
|
||||||
|
"notistack": "^3.0.1",
|
||||||
"openapi-fetch": "^0.9.3",
|
"openapi-fetch": "^0.9.3",
|
||||||
"openapi-typescript": "^6.7.5",
|
"openapi-typescript": "^6.7.5",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {CSSProperties, useState} from "react";
|
||||||
import {OutputField} from "./OutputField.tsx";
|
import {OutputField} from "./OutputField.tsx";
|
||||||
import createClient from "openapi-fetch";
|
import createClient from "openapi-fetch";
|
||||||
import * as openapi from '../openapi';
|
import * as openapi from '../openapi';
|
||||||
|
import {enqueueSnackbar} from "notistack";
|
||||||
|
|
||||||
const client = createClient<openapi.paths>();
|
const client = createClient<openapi.paths>();
|
||||||
|
|
||||||
|
@ -23,6 +24,7 @@ export function Index() {
|
||||||
id: "",
|
id: "",
|
||||||
imageAddress: "pic/uncompiled.png"
|
imageAddress: "pic/uncompiled.png"
|
||||||
});
|
});
|
||||||
|
//const {enqueueSnackbar} = useSnackbar();
|
||||||
const handleValueChange = (value: string) => {
|
const handleValueChange = (value: string) => {
|
||||||
setInputValue(value);
|
setInputValue(value);
|
||||||
};
|
};
|
||||||
|
@ -43,6 +45,10 @@ export function Index() {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
imageAddress: data.imageAddress
|
imageAddress: data.imageAddress
|
||||||
})
|
})
|
||||||
|
enqueueSnackbar("编译成功", {variant: "success", anchorOrigin: {vertical: 'top', horizontal: 'right'}});
|
||||||
|
} else {
|
||||||
|
// error
|
||||||
|
enqueueSnackbar("编译失败", {variant: "error", anchorOrigin: {vertical: 'top', horizontal: 'right'}});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,12 +78,12 @@ export function Index() {
|
||||||
|
|
||||||
<div className={"content"}
|
<div className={"content"}
|
||||||
style={contentClassCss}>
|
style={contentClassCss}>
|
||||||
<Grid container spacing={2} style = {{width: "100%",height: "100%"}}>
|
<Grid container spacing={2} style={{width: "100%", height: "100%"}}>
|
||||||
<Grid item xs={12} sm={6} style = {{width: "100%",height: "100%"}}>
|
<Grid item xs={12} sm={6} style={{width: "100%", height: "100%"}}>
|
||||||
<InputField onValueChange={handleValueChange}>
|
<InputField onValueChange={handleValueChange}>
|
||||||
</InputField>
|
</InputField>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sm={6} style = {{width: "100%",height: "100%"}}>
|
<Grid item xs={12} sm={6} style={{width: "100%", height: "100%"}}>
|
||||||
<OutputField imgPath={outputValue.imageAddress}>
|
<OutputField imgPath={outputValue.imageAddress}>
|
||||||
</OutputField>
|
</OutputField>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from 'react-dom/client'
|
||||||
import { App } from './App.tsx'
|
import {App} from './App.tsx'
|
||||||
import '@fontsource/roboto/300.css';
|
import '@fontsource/roboto/300.css';
|
||||||
import '@fontsource/roboto/400.css';
|
import '@fontsource/roboto/400.css';
|
||||||
import '@fontsource/roboto/500.css';
|
import '@fontsource/roboto/500.css';
|
||||||
import '@fontsource/roboto/700.css';
|
import '@fontsource/roboto/700.css';
|
||||||
import { CssBaseline } from '@mui/material';
|
import {CssBaseline} from '@mui/material';
|
||||||
|
import {SnackbarProvider} from "notistack";
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<CssBaseline />
|
<CssBaseline/>
|
||||||
<App />
|
<SnackbarProvider/>
|
||||||
|
<App/>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
)
|
)
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Canon.Core\Canon.Core.csproj" />
|
<ProjectReference Include="..\Canon.Core\Canon.Core.csproj" />
|
||||||
<ProjectReference Include="..\Canon.Visualization\Canon.Visualization.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -84,8 +84,9 @@ public class PascalGrammarTests
|
||||||
{
|
{
|
||||||
const string program = """
|
const string program = """
|
||||||
program varTest;
|
program varTest;
|
||||||
var a : char;
|
var a : integer;
|
||||||
begin
|
begin
|
||||||
|
a := 9 div 1;
|
||||||
end.
|
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
|
.gitea\workflows\build.yaml = .gitea\workflows\build.yaml
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
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}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Canon.Server", "Canon.Server\Canon.Server.csproj", "{401112EA-1A87-4D1C-9B6D-085309F4137E}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Canon.Generator", "Canon.Generator\Canon.Generator.csproj", "{32C103C4-589C-4DC2-B173-55B1799B62CE}"
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{E5F2B97B-3766-466D-9309-BA361F0CE15E}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
|
||||||
{401112EA-1A87-4D1C-9B6D-085309F4137E}.Debug|Any CPU.Build.0 = 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
|
{401112EA-1A87-4D1C-9B6D-085309F4137E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
|
Loading…
Reference in New Issue
Block a user