Skip to content

Commit dbb90ac

Browse files
More reliable loading with react query
1 parent 360d92a commit dbb90ac

17 files changed

+6426
-4342
lines changed

.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,9 @@ dist-ssr
2222
*.njsproj
2323
*.sln
2424
*.sw?
25+
.yarn
26+
27+
.pnp.cjs
28+
.pnp.loader.mjs
29+
.yarnrc
30+
*.tsbuildinfo

.vscode/extensions.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"recommendations": [
3+
"arcanis.vscode-zipfs",
4+
"dbaeumer.vscode-eslint"
5+
]
6+
}

eslint.config.js

+2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import globals from 'globals'
33
import reactHooks from 'eslint-plugin-react-hooks'
44
import reactRefresh from 'eslint-plugin-react-refresh'
55
import tseslint from 'typescript-eslint'
6+
import pluginQuery from '@tanstack/eslint-plugin-query'
67

78
export default tseslint.config(
9+
...pluginQuery.configs['flat/recommended'],
810
{ ignores: ['dist'] },
911
{
1012
extends: [js.configs.recommended, ...tseslint.configs.recommended],

package.json

+27-25
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "egraph-visualizer",
3-
"version": "1.3.0",
3+
"version": "1.4.0",
44
"repository": {
55
"type": "git",
66
"url": "git+https://github.com/saulshanabrook/egraph-visualizer.git"
@@ -16,40 +16,42 @@
1616
"@anywidget/types": "0.2.0",
1717
"@heroicons/react": "2.1.5",
1818
"@tailwindcss/container-queries": "0.1.1",
19-
"@xyflow/react": "12.1.1",
19+
"@tanstack/eslint-plugin-query": "5.59.7",
20+
"@tanstack/react-query": "5.59.11",
21+
"@tanstack/react-query-devtools": "5.59.11",
22+
"@xyflow/react": "12.3.2",
2023
"elkjs": "0.9.3",
21-
"monaco-editor": "0.50.0",
22-
"react": "19.0.0-rc-77f43893-20241010",
23-
"react-aria-components": "1.3.3",
24-
"react-dom": "19.0.0-rc-77f43893-20241010",
25-
"react-error-boundary": "4.0.13",
26-
"react-monaco-editor": "0.56.1",
27-
"tailwind-merge": "2.5.2",
24+
"monaco-editor": "0.52.0",
25+
"react": "18.3.1",
26+
"react-aria-components": "^1.4.0",
27+
"react-dom": "18.3.1",
28+
"react-monaco-editor": "0.56.2",
29+
"react-stately": "3.33.0",
30+
"tailwind-merge": "^2.5.3",
2831
"tailwindcss-animate": "1.0.7",
29-
"tailwindcss-react-aria-components": "1.1.5",
30-
"use-debounce": "10.0.3",
32+
"tailwindcss-react-aria-components": "1.1.6",
3133
"vega-scale": "7.4.1"
3234
},
3335
"devDependencies": {
3436
"@eslint/js": "^9.9.0",
35-
"@types/node": "22.5.0",
36-
"@types/react": "^18.3.3",
37-
"@types/react-dom": "^18.3.0",
38-
"@vitejs/plugin-react": "^4.3.1",
37+
"@types/node": "22.7.5",
38+
"@types/react": "^18.3.11",
39+
"@types/react-dom": "^18.3.1",
40+
"@vitejs/plugin-react": "^4.3.2",
3941
"autoprefixer": "10.4.20",
40-
"eslint": "^9.9.0",
41-
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
42-
"eslint-plugin-react-refresh": "^0.4.9",
43-
"globals": "^15.9.0",
44-
"postcss": "8.4.41",
45-
"tailwindcss": "3.4.10",
46-
"typescript": "^5.5.3",
47-
"typescript-eslint": "^8.0.1",
48-
"vite": "^5.4.1"
42+
"eslint": "^9.12.0",
43+
"eslint-plugin-react-hooks": "^5.1.0-rc-fb9a90fa48-20240614",
44+
"eslint-plugin-react-refresh": "^0.4.12",
45+
"globals": "^15.11.0",
46+
"postcss": "8.4.47",
47+
"tailwindcss": "3.4.13",
48+
"typescript": "^5.6.3",
49+
"typescript-eslint": "^8.8.1",
50+
"vite": "^5.4.8"
4951
},
5052
"files": [
5153
"dist"
5254
],
5355
"main": "dist/index.js",
54-
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
56+
"packageManager": "yarn@4.5.0"
5557
}

postcss.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export default {
22
plugins: {
3+
'tailwindcss/nesting': {},
34
tailwindcss: {},
45
autoprefixer: {},
56
},

src/App.css

Whitespace-only changes.

src/App.tsx

+15-16
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,29 @@
1-
import { use, useCallback, useState, useTransition } from "react";
2-
import "./App.css";
1+
import { useState } from "react";
32
import Monaco from "./Monaco";
43
import { Visualizer } from "./Visualizer";
5-
import DefaultCode from "/examples/manual/homepage.json?raw";
4+
import { defaultCode, defaultExample, fetchExample } from "./examples";
5+
import { useQuery } from "@tanstack/react-query";
66

77
function App() {
8-
const [isPending, startTransition] = useTransition();
9-
const [egraphPromise, setEGraphPromise] = useState<Promise<string> | string>(DefaultCode);
10-
const egraph = typeof egraphPromise === "string" ? egraphPromise : use(egraphPromise);
11-
// const setCode = useCallback(
12-
// (code: Promise<string>) => {
13-
// // startTransition(() => {
14-
// setEGraphPromise(code);
15-
// // });
16-
// },
17-
// [startTransition, setEGraphPromise]
18-
// );
8+
const [example, setExample] = useState<string>(defaultExample);
9+
const exampleQuery = useQuery({
10+
queryKey: ["example", example],
11+
queryFn: () => fetchExample(example),
12+
staleTime: Infinity,
13+
retry: false,
14+
retryOnMount: false,
15+
});
16+
const [modifiedCode, setModifiedCode] = useState<string | null>(null);
17+
const currentCode = modifiedCode ?? exampleQuery.data;
1918
return (
2019
<>
2120
<div className="flex min-h-screen">
2221
<div className="flex w-1/3 resize-x overflow-auto">
23-
<Monaco code={egraph} setCode={setEGraphPromise} startTransition={startTransition} />
22+
<Monaco setModifiedCode={setModifiedCode} exampleQuery={exampleQuery} example={example} setExample={setExample} />
2423
</div>
2524

2625
<div className="flex w-2/3">
27-
<Visualizer egraph={egraph} startTransition={startTransition} isPending={isPending} />
26+
<Visualizer egraph={currentCode || defaultCode} />
2827
</div>
2928
</div>
3029
<footer className="p-2 fixed bottom-0 min-w-full text-xs text-gray-500 text-right dark:text-gray-400">

src/Loading.tsx

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// https://tailwindflex.com/@prajwal/loading-overlay
2+
export function Loading() {
3+
return (
4+
<div className="absolute bg-white bg-opacity-60 z-10 h-full w-full flex items-center justify-center">
5+
<div className="flex items-center">
6+
<span className="text-3xl mr-4">Loading</span>
7+
<svg className="animate-spin h-8 w-8 text-gray-800" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
8+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
9+
<path
10+
className="opacity-75"
11+
fill="currentColor"
12+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
13+
></path>
14+
</svg>
15+
</div>
16+
</div>
17+
);
18+
}

src/Monaco.tsx

+69-53
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,84 @@
1-
/// <reference types="react/canary" />
2-
3-
import { startTransition, TransitionStartFunction, useCallback, useState } from "react";
1+
import { useCallback, useState } from "react";
42
import MonacoEditor from "react-monaco-editor";
5-
import { useDebouncedCallback } from "use-debounce";
6-
7-
const examples = {
8-
...import.meta.glob("/examples/manual/*.json", { query: "?raw" }),
9-
...import.meta.glob("/examples/egraph-serialize/tests/*.json", { query: "?raw" }),
10-
...import.meta.glob("/examples/extraction-gym/data/*/*.json", { query: "?raw" }),
11-
};
12-
13-
const defaultExample = "/examples/manual/homepage.json";
3+
import { exampleNames } from "./examples";
4+
import { UseQueryResult } from "@tanstack/react-query";
5+
import { Select, SelectListItem, SelectListBox, SelectButton, SelectPopover } from "./react-aria-components-tailwind-starter/src/select";
6+
import { Key } from "react-aria-components";
7+
import { Button } from "./react-aria-components-tailwind-starter/src/button";
8+
import { Loading } from "./Loading";
149

1510
function Monaco({
16-
code,
17-
setCode,
18-
startTransition,
11+
exampleQuery,
12+
setModifiedCode,
13+
example,
14+
setExample,
1915
}: {
20-
code: string;
21-
setCode: (code: Promise<string> | string) => void;
22-
startTransition: TransitionStartFunction;
16+
exampleQuery: UseQueryResult<string, Error>;
17+
setModifiedCode: (code: string | null) => void;
18+
example: string;
19+
setExample: (example: string) => void;
2320
}) {
24-
const [selectedPreset, setSelectedPreset] = useState(defaultExample);
21+
const [code, setCode] = useState<string | null>(null);
2522
const handlePresetChange = useCallback(
26-
(event: React.ChangeEvent<HTMLSelectElement>) => {
27-
const preset = event.target.value;
28-
setSelectedPreset(preset);
29-
setCode(examples[preset]().then((loaded) => (loaded as { default: string }).default));
23+
(preset: Key) => {
24+
setExample(preset as string);
25+
setCode(null);
26+
setModifiedCode(null);
3027
},
31-
[setCode, setSelectedPreset]
28+
[setExample, setModifiedCode]
3229
);
3330

34-
const setCodeString = useDebouncedCallback((code: string) => {
35-
startTransition(() => setCode(code));
36-
// setCode(code);
37-
}, 500);
31+
const currentValue = code || exampleQuery.data;
32+
33+
const handleUpdate = useCallback(() => {
34+
setModifiedCode(currentValue || null);
35+
}, [currentValue, setModifiedCode]);
3836

3937
return (
4038
<div className="flex flex-col h-full w-full">
41-
<select value={selectedPreset} onChange={handlePresetChange} className="m-1 p-2 border border-gray-300 rounded">
42-
<option value="" disabled>
43-
Select a preset
44-
</option>
45-
{Object.keys(examples).map((preset) => (
46-
<option key={preset} value={preset}>
47-
{preset}
48-
</option>
49-
))}
50-
</select>
51-
<MonacoEditor
52-
language="json"
53-
theme="vs-dark"
54-
value={code}
55-
onChange={setCodeString}
56-
width="100%"
57-
height="100%"
58-
defaultValue=""
59-
options={{}}
60-
overrideServices={{}}
61-
editorWillMount={() => {}}
62-
editorDidMount={() => {}}
63-
editorWillUnmount={() => {}}
64-
className={null}
65-
/>
39+
<div className="flex p-2">
40+
<Select
41+
placeholder="Select a preset"
42+
selectedKey={example}
43+
onSelectionChange={handlePresetChange}
44+
aria-label="Select a preset file to load"
45+
>
46+
<SelectButton />
47+
48+
<SelectPopover>
49+
<SelectListBox>
50+
{exampleNames.map((preset) => (
51+
<SelectListItem key={preset} id={preset}>
52+
{preset}
53+
</SelectListItem>
54+
))}
55+
</SelectListBox>
56+
</SelectPopover>
57+
</Select>
58+
<Button className="ml-2" onPress={handleUpdate} variant="outline">
59+
Update
60+
</Button>
61+
</div>
62+
{exampleQuery.isFetching && <Loading />}
63+
{exampleQuery.status == "error" ? (
64+
<div className="p-4">Error loading example: {exampleQuery.error.message}</div>
65+
) : (
66+
<MonacoEditor
67+
language="json"
68+
theme="vs-dark"
69+
value={currentValue}
70+
onChange={setCode}
71+
width="100%"
72+
height="100%"
73+
defaultValue=""
74+
options={{}}
75+
overrideServices={{}}
76+
editorWillMount={() => {}}
77+
editorDidMount={() => {}}
78+
editorWillUnmount={() => {}}
79+
className={null}
80+
/>
81+
)}
6682
</div>
6783
);
6884
}

0 commit comments

Comments
 (0)