feat: 编译历史记录和代码生成结果(#51)
地址已绑定编译结果,支持历史记录切换功能 Co-authored-by: jackfiled <xcrenchangjun@outlook.com> Reviewed-on: PostGuard/Canon#51 Co-authored-by: Ichirinko <1621543655@qq.com> Co-committed-by: Ichirinko <1621543655@qq.com>
This commit is contained in:
@@ -1,12 +1,19 @@
|
||||
import { RouterProvider, createBrowserRouter} from 'react-router-dom'
|
||||
import { Index } from "./Pages/Index";
|
||||
import {Index} from "./Pages/Index";
|
||||
import 'react-photo-view/dist/react-photo-view.css';
|
||||
import {loader} from "./Pages/Loader.tsx";
|
||||
|
||||
|
||||
const routers = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <Index/>
|
||||
}
|
||||
element: <Index/>,
|
||||
},
|
||||
{
|
||||
path: "/:compileId",
|
||||
element: <Index/>,
|
||||
loader : loader
|
||||
},
|
||||
])
|
||||
|
||||
export function App() {
|
||||
|
||||
9
Canon.Server/client-app/src/Interfaces/OutputIntf.ts
Normal file
9
Canon.Server/client-app/src/Interfaces/OutputIntf.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export interface OutputIntf {
|
||||
|
||||
compiledCode: string,
|
||||
id: string,
|
||||
imageAddress: string,
|
||||
sourceCode: string,
|
||||
compileTime: string
|
||||
|
||||
}
|
||||
91
Canon.Server/client-app/src/Pages/HistoryPage.tsx
Normal file
91
Canon.Server/client-app/src/Pages/HistoryPage.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import {Box, Button, Card, CardActionArea, Drawer, Stack, Typography} from "@mui/material";
|
||||
import createClient from "openapi-fetch";
|
||||
import * as openapi from "../openapi";
|
||||
import {useEffect, useState} from "react";
|
||||
import {OutputIntf} from "../Interfaces/OutputIntf.ts";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import {enqueueSnackbar} from "notistack";
|
||||
|
||||
const client = createClient<openapi.paths>();
|
||||
|
||||
|
||||
// @ts-expect-error ...
|
||||
export function HistoryPage({state, setState}) {
|
||||
const [data, setData] = useState<OutputIntf[]>([]);
|
||||
const navigate = useNavigate();
|
||||
useEffect(() => {
|
||||
|
||||
const getResponse = async () => {
|
||||
await client.GET("/api/Compiler", {
|
||||
params:
|
||||
{
|
||||
query: {
|
||||
start: 1,
|
||||
end: 20
|
||||
}
|
||||
}
|
||||
}).then((response) => {
|
||||
if (response !== undefined) {
|
||||
// @ts-expect-error ...
|
||||
setData(response.data)
|
||||
}
|
||||
});
|
||||
}
|
||||
getResponse();
|
||||
|
||||
}, [data])
|
||||
|
||||
const toggleDrawerClose = () => {
|
||||
setState(false);
|
||||
}
|
||||
|
||||
const deleteHistory = async () => {
|
||||
await client.DELETE("/api/Compiler")
|
||||
.then((res) => {
|
||||
if(res.response.status === 204) {
|
||||
enqueueSnackbar("删除缓存成功", {variant: "success", anchorOrigin: {vertical: 'bottom', horizontal: 'right'}});
|
||||
navigate('/');
|
||||
} else {
|
||||
enqueueSnackbar("删除缓存失败", {variant: "error", anchorOrigin: {vertical: 'bottom', horizontal: 'right'}});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
return <>
|
||||
<Drawer
|
||||
anchor={"right"}
|
||||
open={state}
|
||||
onClose={toggleDrawerClose}
|
||||
>
|
||||
|
||||
<Stack spacing={2}
|
||||
sx={{height: "100%", overflowY: "scroll", padding: "5%"}}
|
||||
direction="column">
|
||||
{
|
||||
data.map((item, index) => {
|
||||
return <Card key={index}
|
||||
sx={{width: "100%", height: "auto"}}>
|
||||
<CardActionArea onClick={() => {
|
||||
console.log(item.id)
|
||||
navigate(`/${item.id}`)
|
||||
}}>
|
||||
<Box sx={{padding: "5%"}}>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
{item.compileTime}
|
||||
</Typography>
|
||||
{item.id}
|
||||
</Box>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
}
|
||||
)
|
||||
}
|
||||
</Stack>
|
||||
<Button sx={{width: "20rem"}} onClick={deleteHistory} size={"large"}>
|
||||
清除缓存
|
||||
</Button>
|
||||
</Drawer>
|
||||
</>
|
||||
}
|
||||
@@ -1,64 +1,111 @@
|
||||
import {AppBar, Button, Grid, Toolbar, Typography} from "@mui/material";
|
||||
import {InputField} from "./InputField.tsx";
|
||||
import {CSSProperties, useState} from "react";
|
||||
import {CSSProperties, useEffect, useState} from "react";
|
||||
import {OutputField} from "./OutputField.tsx";
|
||||
import createClient from "openapi-fetch";
|
||||
import * as openapi from '../openapi';
|
||||
import {enqueueSnackbar} from "notistack";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import {HistoryPage} from "./HistoryPage.tsx";
|
||||
import {OutputIntf} from "../Interfaces/OutputIntf.ts";
|
||||
|
||||
|
||||
const client = createClient<openapi.paths>();
|
||||
|
||||
interface outputData {
|
||||
compiledCode: string,
|
||||
id: string,
|
||||
imageAddress: string,
|
||||
sourceCode: string
|
||||
}
|
||||
|
||||
export function Index() {
|
||||
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [outputValue, setOutputValue] = useState<outputData>({
|
||||
const [outputValue, setOutputValue] = useState<OutputIntf>({
|
||||
compiledCode: "",
|
||||
sourceCode: "",
|
||||
id: "",
|
||||
imageAddress: "pic/uncompiled.png"
|
||||
imageAddress: "",
|
||||
compileTime: ""
|
||||
});
|
||||
//const {enqueueSnackbar} = useSnackbar();
|
||||
const [historyPageState,setHistoryPageState] = useState(false);
|
||||
const navigate = useNavigate(); // 跳转hook
|
||||
|
||||
useEffect(() => {
|
||||
// 进入页面的初始化
|
||||
const path = location.pathname.substring(1);
|
||||
if (path === "") {
|
||||
setInputValue("");
|
||||
setOutputValue({
|
||||
compiledCode: "",
|
||||
sourceCode: "",
|
||||
id: "",
|
||||
imageAddress: "pic/uncompiled.png",
|
||||
compileTime: ""
|
||||
})
|
||||
return;
|
||||
}
|
||||
const getCompileInstance = async () => {
|
||||
const {data} = await client.GET("/api/Compiler/{compileId}", {
|
||||
params:
|
||||
{
|
||||
path:
|
||||
{
|
||||
compileId: path
|
||||
}
|
||||
}
|
||||
})
|
||||
if (data !== undefined) {
|
||||
setInputValue(data.sourceCode);
|
||||
setOutputValue({
|
||||
compiledCode: data.compiledCode,
|
||||
sourceCode: data.sourceCode,
|
||||
id: data.id,
|
||||
imageAddress: data.imageAddress,
|
||||
compileTime: data.compileTime
|
||||
})
|
||||
}
|
||||
}
|
||||
getCompileInstance();
|
||||
}, [location.pathname]);
|
||||
|
||||
|
||||
const handleValueChange = (value: string) => {
|
||||
setInputValue(value);
|
||||
};
|
||||
|
||||
|
||||
async function compilerButtonClick() {
|
||||
console.log(inputValue)
|
||||
|
||||
const {data} = await client.POST("/api/Compiler", {
|
||||
body: {
|
||||
code: inputValue
|
||||
}
|
||||
})
|
||||
console.log(data)
|
||||
if (data != undefined) {
|
||||
|
||||
if (data !== undefined) {
|
||||
setOutputValue({
|
||||
compiledCode: data.compiledCode,
|
||||
sourceCode: data.sourceCode,
|
||||
id: data.id,
|
||||
imageAddress: data.imageAddress
|
||||
imageAddress: data.imageAddress,
|
||||
compileTime: data.compileTime
|
||||
})
|
||||
enqueueSnackbar("编译成功", {variant: "success", anchorOrigin: {vertical: 'top', horizontal: 'right'}});
|
||||
enqueueSnackbar("编译成功", {variant: "success", anchorOrigin: {vertical: 'bottom', horizontal: 'right'}});
|
||||
navigate(`/${data.id}`, {})
|
||||
|
||||
} else {
|
||||
// error
|
||||
enqueueSnackbar("编译失败", {variant: "error", anchorOrigin: {vertical: 'top', horizontal: 'right'}});
|
||||
enqueueSnackbar("编译失败", {variant: "error", anchorOrigin: {vertical: 'bottom', horizontal: 'right'}});
|
||||
}
|
||||
}
|
||||
|
||||
function historyButtonClick() {
|
||||
setHistoryPageState(true);
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className={"title"}
|
||||
style={titleClassCss}>
|
||||
<AppBar style={{zIndex: 0}}>
|
||||
<Toolbar style={{width: '100%'}}>
|
||||
<Typography variant="h6">
|
||||
任昌骏组编译器
|
||||
Canon
|
||||
</Typography>
|
||||
<Button style={{
|
||||
position: "absolute",
|
||||
@@ -72,6 +119,17 @@ export function Index() {
|
||||
>
|
||||
编译
|
||||
</Button>
|
||||
|
||||
<Button style={{
|
||||
position: "absolute",
|
||||
right: "10%",
|
||||
fontSize: "medium",
|
||||
}}
|
||||
variant="text"
|
||||
color="inherit"
|
||||
onClick={historyButtonClick}>
|
||||
历史记录
|
||||
</Button>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
</div>
|
||||
@@ -80,15 +138,18 @@ export function Index() {
|
||||
style={contentClassCss}>
|
||||
<Grid container spacing={2} style={{width: "100%", height: "100%"}}>
|
||||
<Grid item xs={12} sm={6} style={{width: "100%", height: "100%"}}>
|
||||
<InputField onValueChange={handleValueChange}>
|
||||
<InputField defaultValue={inputValue} onValueChange={handleValueChange}>
|
||||
</InputField>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} style={{width: "100%", height: "100%"}}>
|
||||
<OutputField imgPath={outputValue.imageAddress}>
|
||||
<OutputField data={outputValue}>
|
||||
</OutputField>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
<HistoryPage state = {historyPageState} setState={setHistoryPageState}>
|
||||
|
||||
</HistoryPage>
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import {CSSProperties, useState} from "react";
|
||||
import {CSSProperties, useEffect, useState} from "react";
|
||||
import MonacoEditor from "react-monaco-editor";
|
||||
|
||||
|
||||
// @ts-expect-error ...
|
||||
export function InputField({onValueChange}) {
|
||||
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
// @ts-expect-error ...
|
||||
export function InputField(props) {
|
||||
const {defaultValue, onValueChange} = props;
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
|
||||
// @ts-expect-error ...
|
||||
const handleChange = (newValue) => {
|
||||
@@ -13,6 +14,9 @@ export function InputField({onValueChange}) {
|
||||
onValueChange(newValue);
|
||||
};
|
||||
|
||||
useEffect(()=>{
|
||||
setInputValue(defaultValue);
|
||||
}, [defaultValue])
|
||||
|
||||
return <>
|
||||
<div className={"input-field"} style={inputFieldClassCss}>
|
||||
|
||||
26
Canon.Server/client-app/src/Pages/Loader.tsx
Normal file
26
Canon.Server/client-app/src/Pages/Loader.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import {redirect} from "react-router-dom";
|
||||
import createClient from "openapi-fetch";
|
||||
import * as openapi from "../openapi";
|
||||
|
||||
export async function loader() {
|
||||
const client = createClient<openapi.paths>();
|
||||
const compileId = location.pathname.substring(1);
|
||||
console.log("hello")
|
||||
|
||||
const compileInstance = await client.GET("/api/Compiler/{compileId}", {
|
||||
params:
|
||||
{
|
||||
path:
|
||||
{
|
||||
compileId: compileId
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (compileInstance.response.status !== 200) {
|
||||
// 不存在的id
|
||||
console.log("redirect")
|
||||
return redirect("/");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1,32 +1,77 @@
|
||||
import {CSSProperties} from "react";
|
||||
import {CSSProperties, useState} from "react";
|
||||
import {Box, ToggleButton, ToggleButtonGroup} from "@mui/material";
|
||||
import {PhotoProvider, PhotoView} from "react-photo-view";
|
||||
import MonacoEditor from "react-monaco-editor";
|
||||
|
||||
|
||||
// @ts-expect-error ...
|
||||
export function OutputField({imgPath}) {
|
||||
|
||||
export function OutputField({data}) {
|
||||
const [state, setState] = useState('tree')
|
||||
const {imageAddress, compiledCode} = data;
|
||||
return <>
|
||||
<div className={"output-field"} style={outputFieldClassCss}>
|
||||
<PhotoProvider>
|
||||
<PhotoView key={1} src={imgPath}>
|
||||
{imgPath == "pic/uncompiled.png" ?
|
||||
<img src={imgPath}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "auto"
|
||||
}}
|
||||
alt=""/> :
|
||||
<img src={imgPath}
|
||||
style={{
|
||||
objectFit: 'cover',
|
||||
width: "100%",
|
||||
height: "100%"
|
||||
}}
|
||||
alt=""/>
|
||||
}
|
||||
<ToggleButtonGroup
|
||||
color="primary"
|
||||
value={state}
|
||||
sx={{
|
||||
position: "relative",
|
||||
top: "0",
|
||||
left: "50%",
|
||||
height : "10%",
|
||||
paddingBottom: "5%",
|
||||
transform: "translateX(-50%)"
|
||||
}}
|
||||
exclusive
|
||||
onChange={(_event, value) => {
|
||||
setState(value + "");
|
||||
}}
|
||||
aria-label="Platform"
|
||||
>
|
||||
<ToggleButton value="code"
|
||||
aria-label="code"
|
||||
size={"small"}>
|
||||
Code
|
||||
</ToggleButton>
|
||||
<ToggleButton value="tree"
|
||||
aria-label="tree"
|
||||
size={"small"}>
|
||||
Tree
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
<Box sx = {{
|
||||
height: "90%",
|
||||
}}>
|
||||
{
|
||||
|
||||
</PhotoView>
|
||||
</PhotoProvider>
|
||||
state === 'tree' ?
|
||||
<PhotoProvider>
|
||||
<PhotoView key={1} src={imageAddress}>
|
||||
{imageAddress == "pic/uncompiled.png" ?
|
||||
<img src={imageAddress}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "auto"
|
||||
}}
|
||||
alt=""/> :
|
||||
<img src={imageAddress}
|
||||
style={{
|
||||
objectFit: 'cover',
|
||||
width: "100%",
|
||||
height: "100%"
|
||||
}}
|
||||
alt=""/>
|
||||
}
|
||||
|
||||
</PhotoView>
|
||||
</PhotoProvider>
|
||||
: <MonacoEditor
|
||||
language="javascript"
|
||||
theme="twilight"
|
||||
value={compiledCode === "" ? "也就是说,还没编译啊还没编译" : compiledCode}
|
||||
options={{readOnly:true}}
|
||||
/>
|
||||
}
|
||||
</Box>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
83
Canon.Server/client-app/src/openapi.d.ts
vendored
83
Canon.Server/client-app/src/openapi.d.ts
vendored
@@ -5,6 +5,53 @@
|
||||
|
||||
|
||||
export interface paths {
|
||||
"/api/Compiler": {
|
||||
get: {
|
||||
parameters: {
|
||||
query?: {
|
||||
start?: number;
|
||||
end?: number;
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Success */
|
||||
200: {
|
||||
content: {
|
||||
"text/plain": components["schemas"]["CompileResponse"][];
|
||||
"application/json": components["schemas"]["CompileResponse"][];
|
||||
"text/json": components["schemas"]["CompileResponse"][];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
post: {
|
||||
requestBody?: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["SourceCode"];
|
||||
"text/json": components["schemas"]["SourceCode"];
|
||||
"application/*+json": components["schemas"]["SourceCode"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Success */
|
||||
200: {
|
||||
content: {
|
||||
"text/plain": components["schemas"]["CompileResponse"];
|
||||
"application/json": components["schemas"]["CompileResponse"];
|
||||
"text/json": components["schemas"]["CompileResponse"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
delete: {
|
||||
responses: {
|
||||
/** @description No Content */
|
||||
204: {
|
||||
content: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
"/api/Compiler/{compileId}": {
|
||||
get: {
|
||||
parameters: {
|
||||
@@ -23,23 +70,23 @@ export interface paths {
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
"/api/Compiler": {
|
||||
post: {
|
||||
requestBody?: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["SourceCode"];
|
||||
"text/json": components["schemas"]["SourceCode"];
|
||||
"application/*+json": components["schemas"]["SourceCode"];
|
||||
delete: {
|
||||
parameters: {
|
||||
path: {
|
||||
compileId: string;
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Success */
|
||||
200: {
|
||||
/** @description No Content */
|
||||
204: {
|
||||
content: never;
|
||||
};
|
||||
/** @description Not Found */
|
||||
404: {
|
||||
content: {
|
||||
"text/plain": components["schemas"]["CompileResponse"];
|
||||
"application/json": components["schemas"]["CompileResponse"];
|
||||
"text/json": components["schemas"]["CompileResponse"];
|
||||
"text/plain": components["schemas"]["ProblemDetails"];
|
||||
"application/json": components["schemas"]["ProblemDetails"];
|
||||
"text/json": components["schemas"]["ProblemDetails"];
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -71,6 +118,16 @@ export interface components {
|
||||
sourceCode: string;
|
||||
compiledCode: string;
|
||||
imageAddress: string;
|
||||
compileTime: string;
|
||||
};
|
||||
ProblemDetails: {
|
||||
type?: string | null;
|
||||
title?: string | null;
|
||||
/** Format: int32 */
|
||||
status?: number | null;
|
||||
detail?: string | null;
|
||||
instance?: string | null;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
SourceCode: {
|
||||
code: string;
|
||||
|
||||
Reference in New Issue
Block a user