From cc6eb8c3f93f1f6652ca6403f27ca0e1b65c9324 Mon Sep 17 00:00:00 2001 From: Alexander Bentkamp Date: Tue, 29 Nov 2022 17:06:20 +0100 Subject: [PATCH] use redux --- client/src/App.tsx | 22 +-- client/src/components/Level.tsx | 6 +- client/src/components/Welcome.tsx | 69 ++++---- client/src/connection.ts | 15 ++ client/src/game/gameSlice.ts | 59 +++++++ client/src/hooks.ts | 6 + client/src/index.tsx | 14 +- client/src/store.ts | 24 +++ package-lock.json | 270 ++++++++++++++++++++++++++---- package.json | 4 +- 10 files changed, 399 insertions(+), 90 deletions(-) create mode 100644 client/src/connection.ts create mode 100644 client/src/game/gameSlice.ts create mode 100644 client/src/hooks.ts create mode 100644 client/src/store.ts diff --git a/client/src/App.tsx b/client/src/App.tsx index 5217e60..bbdbc11 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -14,29 +14,15 @@ import { AppBar, CssBaseline, Toolbar, Typography } from '@mui/material'; import Welcome from './components/Welcome'; import Level from './components/Level'; import GoodBye from './components/GoodBye'; -import useWebSocket from 'react-use-websocket'; -import { LeanClient } from 'lean4web/client/src/editor/leanclient'; import { monacoSetup } from 'lean4web/client/src/monacoSetup' - -const socketUrl = ((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + '/websocket/' - -monacoSetup() +import { useAppSelector } from './hooks'; function App() { - const [title, setTitle] = useState("") const [conclusion, setConclusion] = useState("") const [levelTitle, setLevelTitle] = useState("") const [nbLevels, setNbLevels] = useState(0) const [curLevel, setCurLevel] = useState(0) const [finished, setFinished] = useState(false) - const [leanClient, setLeanClient] = useState(null) - - useEffect(() => { - const uri = monaco.Uri.parse('file:///') - const leanClient = new LeanClient(socketUrl, undefined, uri, () => {}) - setLeanClient(leanClient) - }, []) - const mathJaxConfig = { loader: { @@ -58,11 +44,13 @@ function App() { if (finished) { mainComponent = } else if (curLevel > 0) { - mainComponent = + mainComponent = } else { - mainComponent = + mainComponent = } + const title = useAppSelector(state => state.game.title) + return (
diff --git a/client/src/components/Level.tsx b/client/src/components/Level.tsx index d38dfb5..57e734a 100644 --- a/client/src/components/Level.tsx +++ b/client/src/components/Level.tsx @@ -22,9 +22,9 @@ import 'lean4web/client/src/editor/infoview.css' import { renderInfoview } from '@leanprover/infoview' import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js' import './level.css' +import { ConnectionContext } from '../connection'; interface LevelProps { - leanClient: null|LeanClient; nbLevels: any; level: any; setCurLevel: any; @@ -32,7 +32,7 @@ interface LevelProps { setFinished: any } -function Level({ leanClient, nbLevels, level, setCurLevel, setLevelTitle, setFinished }: LevelProps) { +function Level({ nbLevels, level, setCurLevel, setLevelTitle, setFinished }: LevelProps) { const [index, setIndex] = useState(level) // Level number const [tacticDocs, setTacticDocs] = useState([]) const [lemmaDocs, setLemmaDocs] = useState([]) @@ -72,6 +72,8 @@ function Level({ leanClient, nbLevels, level, setCurLevel, setLevelTitle, setFin messagePanelRef.current!.scrollTo(0,0) }, [level]) + const leanClient = React.useContext(ConnectionContext) + useEffect(() => { const editor = monaco.editor.create(codeviewRef.current!, { glyphMargin: true, diff --git a/client/src/components/Welcome.tsx b/client/src/components/Welcome.tsx index 633fd7f..1ab8ef8 100644 --- a/client/src/components/Welcome.tsx +++ b/client/src/components/Welcome.tsx @@ -9,30 +9,25 @@ import '@fontsource/roboto/700.css'; import * as rpc from 'vscode-ws-jsonrpc'; import cytoscape from 'cytoscape' import klay from 'cytoscape-klay'; +import { useSelector, useDispatch } from 'react-redux' +import { fetchGame } from '../game/gameSlice' cytoscape.use( klay ); import { Box, Typography, Button, CircularProgress, Grid } from '@mui/material'; import { LeanClient } from 'lean4web/client/src/editor/leanclient'; +import { ConnectionContext } from '../connection'; +import { useAppDispatch, useAppSelector } from '../hooks'; interface WelcomeProps { - leanClient: null|LeanClient; setNbLevels: any; - setTitle: any; startGame: any; setConclusion: any; } -type infoResultType = { - title: string, - nb_levels: any[], - conclusion: string - worlds: {edges: string[][], nodes: string[]} -} - -function Welcome({ leanClient, setNbLevels, setTitle, startGame, setConclusion }: WelcomeProps) { +function Welcome({ setNbLevels, startGame, setConclusion }: WelcomeProps) { + const dispatch = useAppDispatch() - const [leanData, setLeanData] = useState(null) const worldsRef = useRef(null) const drawWorlds = (worlds) => { @@ -78,42 +73,34 @@ function Welcome({ leanClient, setNbLevels, setTitle, startGame, setConclusion } }) } - useEffect(() => { - if (!leanClient) return; + useEffect(() => { dispatch(fetchGame); }, []) - const getInfo = async () => { - await leanClient.start() // TODO: need a way to wait for start without restarting - leanClient.sendRequest("info", {}).then((res: infoResultType) =>{ - console.log(res) - setLeanData(res) - setNbLevels(res.nb_levels) - setTitle(res.title) - document.title = res.title - setConclusion(res.conclusion) - drawWorlds(res.worlds) - }); - } - getInfo() - }, [leanClient]) + const worlds = useAppSelector(state => state.game.worlds) + useEffect(() => { if (worlds) { drawWorlds(worlds); } }, [worlds]) + + const title = useAppSelector(state => state.game.title) + useEffect(() => { window.document.title = title }, [title]) + + const introduction = useAppSelector(state => state.game.introduction) - let content - if (leanData) { - content = ( - - - {leanData["introduction"]} - - + return
+ { introduction?// TODO: find a better way to mark loading state? +
+ + + + {introduction} + + + - ) - } else { - content = +
+
+ : } - return
-
{content}
-
+
} diff --git a/client/src/connection.ts b/client/src/connection.ts new file mode 100644 index 0000000..c212769 --- /dev/null +++ b/client/src/connection.ts @@ -0,0 +1,15 @@ + + +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js' +import { LeanClient } from 'lean4web/client/src/editor/leanclient'; +import * as React from 'react'; +import { monacoSetup } from 'lean4web/client/src/monacoSetup'; + +monacoSetup() + +const socketUrl = ((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + '/websocket/' + +const uri = monaco.Uri.parse('file:///') +export const leanClient = new LeanClient(socketUrl, undefined, uri, () => {}) + +export const ConnectionContext = React.createContext(null); diff --git a/client/src/game/gameSlice.ts b/client/src/game/gameSlice.ts new file mode 100644 index 0000000..9210755 --- /dev/null +++ b/client/src/game/gameSlice.ts @@ -0,0 +1,59 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit' +import { LeanClient } from 'lean4web/client/src/editor/leanclient' +import type { RootState } from '../store' + +interface GameState { + title: null|string, + introduction: null|string, + worlds: null|{nodes: string[], edges: string[][2]}, + authors: null|string[], + conclusion: null|string, +} + +const initialState : GameState = { + title: null, + introduction: null, + worlds: null, + authors: null, + conclusion: null, +} + +export const gameSlice = createSlice({ + name: 'game', + initialState, + reducers: { + loadedGame: (state, action: PayloadAction) => { + state.title = action.payload.title + state.introduction = action.payload.introduction + state.worlds = action.payload.worlds + state.authors = action.payload.authors + state.conclusion = action.payload.conclusion + }, + }, +}) + +export const { loadedGame } = gameSlice.actions + +// TODO: Move this into LeanClient? +/** Call `callback` when the leanClient has started. If not already started, start it. */ +const whenLeanClientStarted = (leanClient, callback) => { + if (leanClient.isRunning()) { + callback() + } else { + if (!leanClient.isStarted()) { + leanClient.start() + } + leanClient.restarted(callback) + } +} + +export const fetchGame = (dispatch, getState, extraArgument) => { + const leanClient : LeanClient = extraArgument.leanClient + whenLeanClientStarted(leanClient, () => { + leanClient.sendRequest("info", {}).then((res) =>{ + dispatch(loadedGame(res)) + }) + }) +} + +export default gameSlice.reducer diff --git a/client/src/hooks.ts b/client/src/hooks.ts new file mode 100644 index 0000000..60a4163 --- /dev/null +++ b/client/src/hooks.ts @@ -0,0 +1,6 @@ +import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux' +import type { RootState, AppDispatch } from './store' + +// Use throughout your app instead of plain `useDispatch` and `useSelector` +export const useAppDispatch: () => AppDispatch = useDispatch +export const useAppSelector: TypedUseSelectorHook = useSelector diff --git a/client/src/index.tsx b/client/src/index.tsx index a0fc92e..749feb9 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -2,7 +2,19 @@ import * as React from 'react'; import { createRoot } from 'react-dom/client'; import './index.css'; import App from './App'; +import { store } from './store'; +import { Provider } from 'react-redux'; +import { ConnectionContext, leanClient } from './connection' + const container = document.getElementById('root'); const root = createRoot(container!); -root.render(); +root.render( + + + + + + + +); diff --git a/client/src/store.ts b/client/src/store.ts new file mode 100644 index 0000000..08c3899 --- /dev/null +++ b/client/src/store.ts @@ -0,0 +1,24 @@ +import { configureStore } from '@reduxjs/toolkit'; +import gameReducer from './game/gameSlice'; +import { leanClient } from './connection' +import thunkMiddleware from 'redux-thunk' + + +const thunkMiddlewareWithArg = thunkMiddleware.withExtraArgument({ leanClient }) + +export const store = configureStore({ + reducer: { + game: gameReducer, + }, + middleware: getDefaultMiddleware => + getDefaultMiddleware({ + thunk: { + extraArgument: { leanClient } + } + }) +}); + +// Infer the `RootState` and `AppDispatch` types from the store itself +export type RootState = ReturnType +// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} +export type AppDispatch = typeof store.dispatch diff --git a/package-lock.json b/package-lock.json index 6a1c1dc..11ff251 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@fontsource/roboto-mono": "^4.5.8", "@mui/icons-material": "^5.10.6", "@mui/material": "^5.10.9", + "@reduxjs/toolkit": "^1.9.0", "@types/cytoscape": "^3.19.9", "better-react-mathjax": "^2.0.2", "cytoscape": "^3.23.0", @@ -22,7 +23,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-markdown": "^8.0.3", - "react-use-websocket": "^4.2.0", + "react-redux": "^8.0.5", "vscode-ws-jsonrpc": "^2.0.0", "ws": "^8.9.0" }, @@ -33,6 +34,7 @@ "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.18.6", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.8", + "@redux-devtools/core": "^3.13.1", "@testing-library/react": "^13.4.0", "babel-loader": "^8.2.5", "concurrently": "^7.4.0", @@ -2009,6 +2011,60 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@redux-devtools/core": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/@redux-devtools/core/-/core-3.13.1.tgz", + "integrity": "sha512-VZbma4b28D7dLn6rKTxx4r1KJrgiT2EQNF4vjkpTlXTu0cQcHkEcAO9ixMBj6rZGrT/jinCHq8gBy2bWgnDvcA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.18.3", + "@redux-devtools/instrument": "^2.1.0", + "@types/prop-types": "^15.7.5", + "lodash": "^4.17.21", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^0.14.9 || ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-redux": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "redux": "^3.5.2 || ^4.0.0" + } + }, + "node_modules/@redux-devtools/instrument": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@redux-devtools/instrument/-/instrument-2.1.0.tgz", + "integrity": "sha512-e8fo88kuq/zWqfNf6S/GNfaQMjF4WSPpucmYfRhzZyyXHC3PCLd/xgz7zooPErDh9QwUXK6sTVYvrkq7hPbsFA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.16.7", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "redux": "^3.4.0 || ^4.0.0" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.0.tgz", + "integrity": "sha512-ak11IrjYcUXRqlhNPwnz6AcvA2ynJTu8PzDbbqQw4a3xR4KZtgiqbNblQD+10CRbfK4+5C79SOyxnT9dhBqFnA==", + "dependencies": { + "immer": "^9.0.16", + "redux": "^4.2.0", + "redux-thunk": "^2.4.2", + "reselect": "^4.1.7" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.0.2" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@testing-library/dom": { "version": "8.19.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.19.0.tgz", @@ -2230,6 +2286,15 @@ "@types/unist": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/http-proxy": { "version": "1.17.9", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", @@ -2308,7 +2373,7 @@ "version": "18.0.6", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", "integrity": "sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==", - "dev": true, + "devOptional": true, "dependencies": { "@types/react": "*" } @@ -2373,6 +2438,11 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -4501,8 +4571,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "optional": true, - "peer": true, "dependencies": { "react-is": "^16.7.0" } @@ -4510,9 +4578,7 @@ "node_modules/hoist-non-react-statics/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "optional": true, - "peer": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/hpack.js": { "version": "2.1.6", @@ -4665,6 +4731,15 @@ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "dev": true }, + "node_modules/immer": { + "version": "9.0.16", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.16.tgz", + "integrity": "sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -6694,6 +6769,44 @@ "react": ">=16" } }, + "node_modules/react-redux": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", + "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", + "dependencies": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -6730,15 +6843,6 @@ "react-dom": ">=16.6.0" } }, - "node_modules/react-use-websocket": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/react-use-websocket/-/react-use-websocket-4.2.0.tgz", - "integrity": "sha512-ZovaTlc/tWX6a590fi3kMWImhyoWj46BWJWvO5oucZJzRnVVhYtes2D9g+5MKXjSdR7Es3456hB89v4/1pcBKg==", - "peerDependencies": { - "react": ">= 18.0.0", - "react-dom": ">= 18.0.0" - } - }, "node_modules/readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -6793,6 +6897,22 @@ "node": ">= 0.10" } }, + "node_modules/redux": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz", + "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/redux-thunk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "peerDependencies": { + "redux": "^4" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -6922,6 +7042,11 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, + "node_modules/reselect": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", + "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -8015,6 +8140,14 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -10014,6 +10147,40 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" }, + "@redux-devtools/core": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/@redux-devtools/core/-/core-3.13.1.tgz", + "integrity": "sha512-VZbma4b28D7dLn6rKTxx4r1KJrgiT2EQNF4vjkpTlXTu0cQcHkEcAO9ixMBj6rZGrT/jinCHq8gBy2bWgnDvcA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.18.3", + "@redux-devtools/instrument": "^2.1.0", + "@types/prop-types": "^15.7.5", + "lodash": "^4.17.21", + "prop-types": "^15.8.1" + } + }, + "@redux-devtools/instrument": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@redux-devtools/instrument/-/instrument-2.1.0.tgz", + "integrity": "sha512-e8fo88kuq/zWqfNf6S/GNfaQMjF4WSPpucmYfRhzZyyXHC3PCLd/xgz7zooPErDh9QwUXK6sTVYvrkq7hPbsFA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.16.7", + "lodash": "^4.17.21" + } + }, + "@reduxjs/toolkit": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.0.tgz", + "integrity": "sha512-ak11IrjYcUXRqlhNPwnz6AcvA2ynJTu8PzDbbqQw4a3xR4KZtgiqbNblQD+10CRbfK4+5C79SOyxnT9dhBqFnA==", + "requires": { + "immer": "^9.0.16", + "redux": "^4.2.0", + "redux-thunk": "^2.4.2", + "reselect": "^4.1.7" + } + }, "@testing-library/dom": { "version": "8.19.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.19.0.tgz", @@ -10206,6 +10373,15 @@ "@types/unist": "*" } }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "@types/http-proxy": { "version": "1.17.9", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", @@ -10284,7 +10460,7 @@ "version": "18.0.6", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", "integrity": "sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==", - "dev": true, + "devOptional": true, "requires": { "@types/react": "*" } @@ -10349,6 +10525,11 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" }, + "@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -11976,8 +12157,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "optional": true, - "peer": true, "requires": { "react-is": "^16.7.0" }, @@ -11985,9 +12164,7 @@ "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "optional": true, - "peer": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" } } }, @@ -12103,6 +12280,11 @@ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "dev": true }, + "immer": { + "version": "9.0.16", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.16.tgz", + "integrity": "sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ==" + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -13486,6 +13668,19 @@ "vfile": "^5.0.0" } }, + "react-redux": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", + "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", + "requires": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + } + }, "react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -13512,12 +13707,6 @@ "prop-types": "^15.6.2" } }, - "react-use-websocket": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/react-use-websocket/-/react-use-websocket-4.2.0.tgz", - "integrity": "sha512-ZovaTlc/tWX6a590fi3kMWImhyoWj46BWJWvO5oucZJzRnVVhYtes2D9g+5MKXjSdR7Es3456hB89v4/1pcBKg==", - "requires": {} - }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -13568,6 +13757,20 @@ "resolve": "^1.9.0" } }, + "redux": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz", + "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", + "requires": { + "@babel/runtime": "^7.9.2" + } + }, + "redux-thunk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "requires": {} + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -13673,6 +13876,11 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, + "reselect": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", + "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" + }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -14482,6 +14690,12 @@ "punycode": "^2.1.0" } }, + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index e30428a..d1a64ec 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "@fontsource/roboto-mono": "^4.5.8", "@mui/icons-material": "^5.10.6", "@mui/material": "^5.10.9", + "@reduxjs/toolkit": "^1.9.0", "@types/cytoscape": "^3.19.9", "better-react-mathjax": "^2.0.2", "cytoscape": "^3.23.0", @@ -18,7 +19,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-markdown": "^8.0.3", - "react-use-websocket": "^4.2.0", + "react-redux": "^8.0.5", "vscode-ws-jsonrpc": "^2.0.0", "ws": "^8.9.0" }, @@ -29,6 +30,7 @@ "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.18.6", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.8", + "@redux-devtools/core": "^3.13.1", "@testing-library/react": "^13.4.0", "babel-loader": "^8.2.5", "concurrently": "^7.4.0",