mirror of
https://github.com/ellmau/adf-obdd.git
synced 2025-12-20 09:39:38 +01:00
Add basic UI for user endpoints
This commit is contained in:
parent
e353e01c1d
commit
257bd5cdfc
@ -5,7 +5,8 @@ module.exports = {
|
||||
},
|
||||
"extends": [
|
||||
"plugin:react/recommended",
|
||||
"airbnb"
|
||||
"airbnb",
|
||||
"airbnb-typescript",
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
@ -13,7 +14,8 @@ module.exports = {
|
||||
"jsx": true
|
||||
},
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
"sourceType": "module",
|
||||
"project": "tsconfig.json"
|
||||
},
|
||||
"plugins": [
|
||||
"react",
|
||||
|
||||
@ -4,16 +4,19 @@
|
||||
"source": "src/index.html",
|
||||
"browserslist": "> 0.5%, last 2 versions, not dead",
|
||||
"scripts": {
|
||||
"check": "tsc --noEmit && eslint ./src",
|
||||
"start": "parcel",
|
||||
"build": "parcel build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.15.11",
|
||||
"@types/react": "^18.0.26",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.1",
|
||||
"@typescript-eslint/parser": "^5.48.1",
|
||||
"eslint": "^8.31.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||
"eslint-plugin-import": "^2.27.4",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"eslint-plugin-react": "^7.32.0",
|
||||
|
||||
@ -6,8 +6,8 @@ import '@fontsource/roboto/400.css';
|
||||
import '@fontsource/roboto/500.css';
|
||||
import '@fontsource/roboto/700.css';
|
||||
|
||||
import App from './components/app.tsx';
|
||||
import App from './components/app';
|
||||
|
||||
const container = document.getElementById('app');
|
||||
const root = createRoot(container);
|
||||
const root = createRoot(container!);
|
||||
root.render(<App />);
|
||||
|
||||
@ -19,9 +19,11 @@ import {
|
||||
TextField,
|
||||
} from '@mui/material';
|
||||
|
||||
import GraphG6 from './graph-g6.tsx';
|
||||
import LoadingContext from './loading-context';
|
||||
import GraphG6, { GraphProps } from './graph-g6';
|
||||
import Footer from './footer';
|
||||
|
||||
const { useState, useCallback } = React;
|
||||
const { useState, useCallback, useMemo } = React;
|
||||
|
||||
const darkTheme = createTheme({
|
||||
palette: {
|
||||
@ -57,7 +59,7 @@ function App() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [code, setCode] = useState(placeholder);
|
||||
const [parsing, setParsing] = useState(Parsing.Naive);
|
||||
const [graphs, setGraphs] = useState();
|
||||
const [graphs, setGraphs] = useState<GraphProps[]>();
|
||||
const [graphIndex, setGraphIndex] = useState(0);
|
||||
|
||||
const submitHandler = useCallback(
|
||||
@ -82,93 +84,98 @@ function App() {
|
||||
[code, parsing],
|
||||
);
|
||||
|
||||
const loadingContext = useMemo(() => ({ loading, setLoading }), [loading, setLoading]);
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<CssBaseline />
|
||||
<main>
|
||||
<Typography variant="h2" component="h1" align="center" gutterBottom>
|
||||
Solve your ADF Problem with OBDDs!
|
||||
</Typography>
|
||||
<LoadingContext.Provider value={loadingContext}>
|
||||
<CssBaseline />
|
||||
<main>
|
||||
<Typography variant="h2" component="h1" align="center" gutterBottom>
|
||||
Solve your ADF Problem with OBDDs!
|
||||
</Typography>
|
||||
|
||||
<Container>
|
||||
<TextField
|
||||
name="code"
|
||||
label="Put your code here:"
|
||||
helperText={(
|
||||
<Container>
|
||||
<TextField
|
||||
name="code"
|
||||
label="Put your code here:"
|
||||
helperText={(
|
||||
<>
|
||||
For more info on the syntax, have a
|
||||
look
|
||||
{' '}
|
||||
<Link href="https://github.com/ellmau/adf-obdd" target="_blank" rel="noreferrer">here</Link>
|
||||
.
|
||||
</>
|
||||
)}
|
||||
multiline
|
||||
fullWidth
|
||||
variant="filled"
|
||||
value={code}
|
||||
onChange={(event) => { setCode(event.target.value); }}
|
||||
/>
|
||||
</Container>
|
||||
<Container sx={{ marginTop: 2, marginBottom: 2 }}>
|
||||
<FormControl>
|
||||
<FormLabel id="parsing-radio-group">Parsing Strategy</FormLabel>
|
||||
<RadioGroup
|
||||
row
|
||||
aria-labelledby="parsing-radio-group"
|
||||
name="parsing"
|
||||
value={parsing}
|
||||
onChange={(e) => setParsing(((e.target as HTMLInputElement).value) as Parsing)}
|
||||
>
|
||||
<FormControlLabel value={Parsing.Naive} control={<Radio />} label="Naive" />
|
||||
<FormControlLabel value={Parsing.Hybrid} control={<Radio />} label="Hybrid" />
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<br />
|
||||
<br />
|
||||
<Button variant="outlined" onClick={() => submitHandler(Strategy.ParseOnly)}>Parse only</Button>
|
||||
{' '}
|
||||
<Button variant="outlined" onClick={() => submitHandler(Strategy.Ground)}>Grounded Model</Button>
|
||||
{' '}
|
||||
<Button variant="outlined" onClick={() => submitHandler(Strategy.Complete)}>Complete Models</Button>
|
||||
{' '}
|
||||
<Button variant="outlined" onClick={() => submitHandler(Strategy.Stable)}>Stable Models (naive heuristics)</Button>
|
||||
{' '}
|
||||
<Button disabled={parsing !== Parsing.Hybrid} variant="outlined" onClick={() => submitHandler(Strategy.StableCountingA)}>Stable Models (counting heuristic A)</Button>
|
||||
{' '}
|
||||
<Button disabled={parsing !== Parsing.Hybrid} variant="outlined" onClick={() => submitHandler(Strategy.StableCountingB)}>Stable Models (counting heuristic B)</Button>
|
||||
{' '}
|
||||
<Button variant="outlined" onClick={() => submitHandler(Strategy.StableNogood)}>Stable Models using nogoods (Simple Heuristic)</Button>
|
||||
</Container>
|
||||
|
||||
{graphs
|
||||
&& (
|
||||
<Container sx={{ marginTop: 4, marginBottom: 4 }}>
|
||||
{graphs.length > 1
|
||||
&& (
|
||||
<>
|
||||
For more info on the syntax, have a
|
||||
look
|
||||
{' '}
|
||||
<Link href="https://github.com/ellmau/adf-obdd" target="_blank" rel="noreferrer">here</Link>
|
||||
.
|
||||
Models:
|
||||
<br />
|
||||
<Pagination variant="outlined" shape="rounded" count={graphs.length} page={graphIndex + 1} onChange={(e, value) => setGraphIndex(value - 1)} />
|
||||
</>
|
||||
)}
|
||||
multiline
|
||||
fullWidth
|
||||
variant="filled"
|
||||
value={code}
|
||||
onChange={(event) => { setCode(event.target.value); }}
|
||||
/>
|
||||
</Container>
|
||||
<Container sx={{ marginTop: 2, marginBottom: 2 }}>
|
||||
<FormControl>
|
||||
<FormLabel id="parsing-radio-group">Parsing Strategy</FormLabel>
|
||||
<RadioGroup
|
||||
row
|
||||
aria-labelledby="parsing-radio-group"
|
||||
name="parsing"
|
||||
value={parsing}
|
||||
onChange={(e) => setParsing((e.target as HTMLInputElement).value)}
|
||||
>
|
||||
<FormControlLabel value={Parsing.Naive} control={<Radio />} label="Naive" />
|
||||
<FormControlLabel value={Parsing.Hybrid} control={<Radio />} label="Hybrid" />
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<br />
|
||||
<br />
|
||||
<Button variant="outlined" onClick={() => submitHandler(Strategy.ParseOnly)}>Parse only</Button>
|
||||
{' '}
|
||||
<Button variant="outlined" onClick={() => submitHandler(Strategy.Ground)}>Grounded Model</Button>
|
||||
{' '}
|
||||
<Button variant="outlined" onClick={() => submitHandler(Strategy.Complete)}>Complete Models</Button>
|
||||
{' '}
|
||||
<Button variant="outlined" onClick={() => submitHandler(Strategy.Stable)}>Stable Models (naive heuristics)</Button>
|
||||
{' '}
|
||||
<Button disabled={parsing !== Parsing.Hybrid} variant="outlined" onClick={() => submitHandler(Strategy.StableCountingA)}>Stable Models (counting heuristic A)</Button>
|
||||
{' '}
|
||||
<Button disabled={parsing !== Parsing.Hybrid} variant="outlined" onClick={() => submitHandler(Strategy.StableCountingB)}>Stable Models (counting heuristic B)</Button>
|
||||
{' '}
|
||||
<Button variant="outlined" onClick={() => submitHandler(Strategy.StableNogood)}>Stable Models using nogoods (Simple Heuristic)</Button>
|
||||
</Container>
|
||||
)}
|
||||
{graphs.length > 0
|
||||
&& (
|
||||
<Paper elevation={3} square sx={{ marginTop: 4, marginBottom: 4 }}>
|
||||
<GraphG6 graph={graphs[graphIndex]} />
|
||||
</Paper>
|
||||
)}
|
||||
{graphs.length === 0
|
||||
&& <>No models!</>}
|
||||
</Container>
|
||||
)}
|
||||
</main>
|
||||
<Footer />
|
||||
|
||||
{graphs
|
||||
&& (
|
||||
<Container sx={{ marginTop: 4, marginBottom: 4 }}>
|
||||
{graphs.length > 1
|
||||
&& (
|
||||
<>
|
||||
Models:
|
||||
<br />
|
||||
<Pagination variant="outlined" shape="rounded" count={graphs.length} page={graphIndex + 1} onChange={(e, value) => setGraphIndex(value - 1)} />
|
||||
</>
|
||||
)}
|
||||
{graphs.length > 0
|
||||
&& (
|
||||
<Paper elevation={3} square sx={{ marginTop: 4, marginBottom: 4 }}>
|
||||
<GraphG6 graph={graphs[graphIndex]} />
|
||||
</Paper>
|
||||
)}
|
||||
{graphs.length === 0
|
||||
&& <>No models!</>}
|
||||
</Container>
|
||||
)}
|
||||
</main>
|
||||
|
||||
<Backdrop
|
||||
open={loading}
|
||||
>
|
||||
<CircularProgress color="inherit" />
|
||||
</Backdrop>
|
||||
<Backdrop
|
||||
open={loading}
|
||||
>
|
||||
<CircularProgress color="inherit" />
|
||||
</Backdrop>
|
||||
</LoadingContext.Provider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
220
frontend/src/components/footer.tsx
Normal file
220
frontend/src/components/footer.tsx
Normal file
@ -0,0 +1,220 @@
|
||||
import React, {
|
||||
useState, useCallback, useContext, useEffect,
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
Alert,
|
||||
AppBar,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Snackbar,
|
||||
TextField,
|
||||
Toolbar,
|
||||
} from '@mui/material';
|
||||
|
||||
import LoadingContext from './loading-context';
|
||||
|
||||
enum UserFormType {
|
||||
Login = 'Login',
|
||||
Register = 'Register',
|
||||
Update = 'Update',
|
||||
}
|
||||
|
||||
interface UserFormProps {
|
||||
formType: UserFormType | null;
|
||||
close: (message?: string) => void;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
function UserForm({ username: propUsername, formType, close }: UserFormProps) {
|
||||
const { setLoading } = useContext(LoadingContext);
|
||||
const [username, setUsername] = useState<string>(propUsername || '');
|
||||
const [password, setPassword] = useState<string>('');
|
||||
const [errorOccurred, setError] = useState<boolean>(false);
|
||||
|
||||
const submitHandler = useCallback(
|
||||
(del: boolean) => {
|
||||
setLoading(true);
|
||||
setError(false);
|
||||
|
||||
let method; let
|
||||
endpoint;
|
||||
if (del) {
|
||||
method = 'DELETE';
|
||||
endpoint = '/users/delete';
|
||||
} else {
|
||||
switch (formType) {
|
||||
case UserFormType.Login:
|
||||
method = 'POST';
|
||||
endpoint = '/users/login';
|
||||
break;
|
||||
case UserFormType.Register:
|
||||
method = 'POST';
|
||||
endpoint = '/users/register';
|
||||
break;
|
||||
case UserFormType.Update:
|
||||
method = 'PUT';
|
||||
endpoint = '/users/update';
|
||||
break;
|
||||
default:
|
||||
// NOTE: the value is not null when the dialog is open
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fetch(`${process.env.NODE_ENV === 'development' ? '//localhost:8080' : ''}${endpoint}`, {
|
||||
method,
|
||||
credentials: process.env.NODE_ENV === 'development' ? 'include' : 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: !del ? JSON.stringify({ username, password }) : undefined,
|
||||
})
|
||||
.then((res) => {
|
||||
switch (res.status) {
|
||||
case 200:
|
||||
close(`Action '${formType}' successful!`);
|
||||
break;
|
||||
default:
|
||||
setError(true);
|
||||
break;
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
},
|
||||
[username, password, formType],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogTitle>{formType}</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField variant="standard" type="text" label="Username" value={username} onChange={(event) => { setUsername(event.target.value); }} />
|
||||
<br />
|
||||
<TextField variant="standard" type="password" label="Password" value={password} onChange={(event) => { setPassword(event.target.value); }} />
|
||||
{errorOccurred
|
||||
&& <Alert severity="error">Check your inputs!</Alert>}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => close()}>Cancel</Button>
|
||||
<Button onClick={() => submitHandler(false)}>{formType}</Button>
|
||||
{formType === UserFormType.Update
|
||||
// TODO: add another confirm dialog here
|
||||
&& (
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
// eslint-disable-next-line no-alert
|
||||
if (window.confirm('Are you sure that you want to delete your account?')) {
|
||||
submitHandler(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Delete Account
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
UserForm.defaultProps = { username: undefined };
|
||||
|
||||
function Footer() {
|
||||
const [username, setUsername] = useState<string>();
|
||||
const [tempUser, setTempUser] = useState<boolean>();
|
||||
const [dialogTypeOpen, setDialogTypeOpen] = useState<UserFormType | null>(null);
|
||||
const [snackbarMessage, setSnackbarMessage] = useState<string | undefined>();
|
||||
|
||||
const logout = useCallback(() => {
|
||||
fetch(`${process.env.NODE_ENV === 'development' ? '//localhost:8080' : ''}/users/logout`, {
|
||||
method: 'DELETE',
|
||||
credentials: process.env.NODE_ENV === 'development' ? 'include' : 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
switch (res.status) {
|
||||
case 200:
|
||||
setSnackbarMessage('Logout successful!');
|
||||
setUsername(undefined);
|
||||
break;
|
||||
default:
|
||||
setSnackbarMessage('An error occurred while trying to log out.');
|
||||
break;
|
||||
}
|
||||
});
|
||||
}, [setSnackbarMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
// Intuition: If the dialog was just closed (or on first render).
|
||||
if (!dialogTypeOpen) {
|
||||
fetch(`${process.env.NODE_ENV === 'development' ? '//localhost:8080' : ''}/users/info`, {
|
||||
method: 'GET',
|
||||
credentials: process.env.NODE_ENV === 'development' ? 'include' : 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
switch (res.status) {
|
||||
case 200:
|
||||
res.json().then(({ username: user, temp }) => {
|
||||
setUsername(user);
|
||||
setTempUser(temp);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
setUsername(undefined);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [dialogTypeOpen]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppBar position="fixed" sx={{ top: 'auto', bottom: 0 }}>
|
||||
<Toolbar sx={{ justifyContent: 'center' }}>
|
||||
{username ? (
|
||||
<>
|
||||
<span>
|
||||
Logged in as:
|
||||
{' '}
|
||||
{username}
|
||||
{' '}
|
||||
{tempUser ? '(Temporary User. Edit to set a password!)' : undefined}
|
||||
</span>
|
||||
<Button color="inherit" onClick={() => setDialogTypeOpen(UserFormType.Update)}>Edit</Button>
|
||||
{!tempUser && <Button color="inherit" onClick={() => logout()}>Logout</Button>}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button color="inherit" onClick={() => setDialogTypeOpen(UserFormType.Login)}>Login</Button>
|
||||
<Button color="inherit" onClick={() => setDialogTypeOpen(UserFormType.Register)}>Register</Button>
|
||||
</>
|
||||
)}
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<Dialog open={!!dialogTypeOpen} onClose={() => setDialogTypeOpen(null)}>
|
||||
<UserForm
|
||||
formType={dialogTypeOpen}
|
||||
close={(message) => { setDialogTypeOpen(null); setSnackbarMessage(message); }}
|
||||
username={dialogTypeOpen === UserFormType.Update ? username : undefined}
|
||||
/>
|
||||
</Dialog>
|
||||
<Snackbar
|
||||
open={!!snackbarMessage}
|
||||
autoHideDuration={5000}
|
||||
message={snackbarMessage}
|
||||
onClose={() => setSnackbarMessage(undefined)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Footer;
|
||||
@ -1,13 +1,13 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
import G6 from '@antv/g6';
|
||||
import G6, { Graph } from '@antv/g6';
|
||||
|
||||
G6.registerNode('nodeWithFlag', {
|
||||
draw(cfg, group) {
|
||||
const mainWidth = Math.max(30, 5 * cfg.mainLabel.length + 10);
|
||||
const mainWidth = Math.max(30, 5 * (cfg!.mainLabel as string).length + 10);
|
||||
const mainHeight = 30;
|
||||
|
||||
const keyShape = group.addShape('rect', {
|
||||
const keyShape = group!.addShape('rect', {
|
||||
attrs: {
|
||||
width: mainWidth,
|
||||
height: mainHeight,
|
||||
@ -20,13 +20,13 @@ G6.registerNode('nodeWithFlag', {
|
||||
draggable: true,
|
||||
});
|
||||
|
||||
group.addShape('text', {
|
||||
group!.addShape('text', {
|
||||
attrs: {
|
||||
x: mainWidth / 2,
|
||||
y: mainHeight / 2,
|
||||
textAlign: 'center',
|
||||
textBaseline: 'middle',
|
||||
text: cfg.mainLabel,
|
||||
text: cfg!.mainLabel,
|
||||
fill: '#212121',
|
||||
fontFamily: 'Roboto',
|
||||
cursor: 'pointer',
|
||||
@ -37,14 +37,14 @@ G6.registerNode('nodeWithFlag', {
|
||||
draggable: true,
|
||||
});
|
||||
|
||||
if (cfg.subLabel) {
|
||||
const subWidth = 5 * cfg.subLabel.length + 4;
|
||||
if (cfg!.subLabel) {
|
||||
const subWidth = 5 * (cfg!.subLabel as string).length + 4;
|
||||
const subHeight = 20;
|
||||
|
||||
const subRectX = mainWidth - 4;
|
||||
const subRectY = -subHeight + 4;
|
||||
|
||||
group.addShape('rect', {
|
||||
group!.addShape('rect', {
|
||||
attrs: {
|
||||
x: subRectX,
|
||||
y: subRectY,
|
||||
@ -59,13 +59,13 @@ G6.registerNode('nodeWithFlag', {
|
||||
draggable: true,
|
||||
});
|
||||
|
||||
group.addShape('text', {
|
||||
group!.addShape('text', {
|
||||
attrs: {
|
||||
x: subRectX + subWidth / 2,
|
||||
y: subRectY + subHeight / 2,
|
||||
textAlign: 'center',
|
||||
textBaseline: 'middle',
|
||||
text: cfg.subLabel,
|
||||
text: cfg!.subLabel,
|
||||
fill: '#212121',
|
||||
fontFamily: 'Roboto',
|
||||
fontSize: 10,
|
||||
@ -95,6 +95,7 @@ G6.registerNode('nodeWithFlag', {
|
||||
// },
|
||||
// },
|
||||
setState(name, value, item) {
|
||||
if (!item) { return; }
|
||||
const group = item.getContainer();
|
||||
const mainShape = group.get('children')[0]; // Find the first graphics shape of the node. It is determined by the order of being added
|
||||
const subShape = group.get('children')[2];
|
||||
@ -131,11 +132,11 @@ G6.registerNode('nodeWithFlag', {
|
||||
},
|
||||
});
|
||||
|
||||
interface GraphProps {
|
||||
lo_edges: [number, number][],
|
||||
hi_edges: [number, number][],
|
||||
node_labels: { [key: number]: string },
|
||||
tree_root_labels: { [key: number]: string[] },
|
||||
export interface GraphProps {
|
||||
lo_edges: [string, string][],
|
||||
hi_edges: [string, string][],
|
||||
node_labels: { [key: string]: string },
|
||||
tree_root_labels: { [key: string]: string[] },
|
||||
}
|
||||
|
||||
function nodesAndEdgesFromGraphProps(graphProps: GraphProps) {
|
||||
@ -174,24 +175,26 @@ function GraphG6(props: Props) {
|
||||
|
||||
const ref = useRef(null);
|
||||
|
||||
const graphRef = useRef();
|
||||
const graphRef = useRef<Graph>();
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (!graphRef.current) {
|
||||
graphRef.current = new G6.Graph({
|
||||
container: ref.current,
|
||||
graphRef.current = new Graph({
|
||||
container: ref.current!,
|
||||
width: 1200,
|
||||
height: 600,
|
||||
fitView: true,
|
||||
rankdir: 'TB',
|
||||
align: 'DR',
|
||||
nodesep: 100,
|
||||
ranksep: 100,
|
||||
modes: {
|
||||
default: ['drag-canvas', 'zoom-canvas', 'drag-node'],
|
||||
},
|
||||
layout: { type: 'dagre' },
|
||||
layout: {
|
||||
type: 'dagre',
|
||||
rankdir: 'TB',
|
||||
align: 'DR',
|
||||
nodesep: 100,
|
||||
ranksep: 100,
|
||||
},
|
||||
// defaultNode: {
|
||||
// anchorPoints: [[0.5, 0], [0, 0.5], [1, 0.5], [0.5, 1]],
|
||||
// type: 'rect',
|
||||
@ -239,13 +242,13 @@ function GraphG6(props: Props) {
|
||||
|
||||
// Mouse enter a node
|
||||
graph.on('node:mouseenter', (e) => {
|
||||
const nodeItem = e.item; // Get the target item
|
||||
const nodeItem = e.item!; // Get the target item
|
||||
graph.setItemState(nodeItem, 'hover', true); // Set the state 'hover' of the item to be true
|
||||
});
|
||||
|
||||
// Mouse leave a node
|
||||
graph.on('node:mouseleave', (e) => {
|
||||
const nodeItem = e.item; // Get the target item
|
||||
const nodeItem = e.item!; // Get the target item
|
||||
graph.setItemState(nodeItem, 'hover', false); // Set the state 'hover' of the item to be false
|
||||
});
|
||||
},
|
||||
@ -254,11 +257,11 @@ function GraphG6(props: Props) {
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
const graph = graphRef.current;
|
||||
const graph = graphRef.current!;
|
||||
|
||||
// Click a node
|
||||
graph.on('node:click', (e) => {
|
||||
const nodeItem = e.item; // et the clicked item
|
||||
const nodeItem = e.item!; // et the clicked item
|
||||
|
||||
let onlyRemoveStates = false;
|
||||
if (nodeItem.hasState('highlight')) {
|
||||
@ -290,12 +293,12 @@ function GraphG6(props: Props) {
|
||||
graph.setItemState(edge, 'lowlight', true);
|
||||
});
|
||||
|
||||
const relevantNodeIds = [];
|
||||
const relevantLoEdges = [];
|
||||
const relevantHiEdges = [];
|
||||
let newNodeIds = [nodeItem.getModel().id];
|
||||
let newLoEdges = [];
|
||||
let newHiEdges = [];
|
||||
const relevantNodeIds: string[] = [];
|
||||
const relevantLoEdges: [string, string][] = [];
|
||||
const relevantHiEdges: [string, string][] = [];
|
||||
let newNodeIds: string[] = [nodeItem.getModel().id!];
|
||||
let newLoEdges: [string, string][] = [];
|
||||
let newHiEdges: [string, string][] = [];
|
||||
|
||||
while (newNodeIds.length > 0 || newLoEdges.length > 0 || newHiEdges.length > 0) {
|
||||
relevantNodeIds.push(...newNodeIds);
|
||||
@ -347,7 +350,7 @@ function GraphG6(props: Props) {
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
const graph = graphRef.current;
|
||||
const graph = graphRef.current!;
|
||||
|
||||
const { nodes, edges } = nodesAndEdgesFromGraphProps(graphProps);
|
||||
|
||||
|
||||
13
frontend/src/components/loading-context.ts
Normal file
13
frontend/src/components/loading-context.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
interface ILoadingContext {
|
||||
loading: boolean;
|
||||
setLoading: (loading: boolean) => void;
|
||||
}
|
||||
|
||||
const LoadingContext = createContext<ILoadingContext>({
|
||||
loading: false,
|
||||
setLoading: () => {},
|
||||
});
|
||||
|
||||
export default LoadingContext;
|
||||
103
frontend/tsconfig.json
Normal file
103
frontend/tsconfig.json
Normal file
@ -0,0 +1,103 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
"jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
||||
@ -1323,6 +1323,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
|
||||
|
||||
"@types/node@^18.15.11":
|
||||
version "18.15.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f"
|
||||
integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==
|
||||
|
||||
"@types/parse-json@^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||
@ -2204,6 +2209,13 @@ eslint-config-airbnb-base@^15.0.0:
|
||||
object.entries "^1.1.5"
|
||||
semver "^6.3.0"
|
||||
|
||||
eslint-config-airbnb-typescript@^17.0.0:
|
||||
version "17.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.0.0.tgz#360dbcf810b26bbcf2ff716198465775f1c49a07"
|
||||
integrity sha512-elNiuzD0kPAPTXjFWg+lE24nMdHMtuxgYoD30OyMD6yrW1AhFZPAg27VX7d3tzOErw+dgJTNWfRSDqEcXb4V0g==
|
||||
dependencies:
|
||||
eslint-config-airbnb-base "^15.0.0"
|
||||
|
||||
eslint-config-airbnb@^19.0.4:
|
||||
version "19.0.4"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz#84d4c3490ad70a0ffa571138ebcdea6ab085fdc3"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user