From c5f54834ed0c6cb9724cd8333d1dc9f5eb60634f Mon Sep 17 00:00:00 2001 From: Jon Eugster Date: Wed, 5 Jul 2023 14:54:06 +0200 Subject: [PATCH 1/5] refactor --- client/src/{App.test.js => app.test.js} | 2 +- client/src/{App.tsx => app.tsx} | 4 +- .../src/components/{Button.tsx => button.tsx} | 0 .../error_page.tsx} | 0 .../{GameMenu.tsx => game_menu.tsx} | 10 +-- .../src/components/{infoview => }/hints.tsx | 5 +- .../{CommandLine.tsx => command_line.tsx} | 16 ++-- client/src/components/infoview/context.ts | 77 ++++++++++++++++++ client/src/components/infoview/goals.tsx | 8 +- client/src/components/infoview/info.tsx | 23 +++--- client/src/components/infoview/infoview.css | 13 ++- client/src/components/infoview/main.tsx | 41 +++++++--- client/src/components/infoview/messages.tsx | 26 +++--- .../infoview/{rpcApi.ts => rpc_api.ts} | 7 +- .../{Inventory.tsx => inventory.tsx} | 4 +- .../{LandingPage.css => landing_page.css} | 0 .../{LandingPage.tsx => landing_page.tsx} | 11 ++- .../src/components/{Level.tsx => level.tsx} | 81 ++++--------------- .../components/{Markdown.tsx => markdown.tsx} | 0 .../components/{Message.tsx => message.tsx} | 2 +- .../{PrivacyPolicy.tsx => privacy_policy.tsx} | 0 .../components/{Welcome.tsx => welcome.tsx} | 27 +++---- client/src/index.tsx | 12 +-- client/src/state/api.ts | 2 +- client/src/{ => state}/connection.ts | 9 ++- client/src/{ => state}/hooks.ts | 2 +- .../{localStorage.ts => local_storage.ts} | 0 client/src/state/progress.ts | 2 +- client/src/state/store.ts | 9 ++- package-lock.json | 7 +- package.json | 1 + 31 files changed, 226 insertions(+), 175 deletions(-) rename client/src/{App.test.js => app.test.js} (89%) rename client/src/{App.tsx => app.tsx} (82%) rename client/src/components/{Button.tsx => button.tsx} (100%) rename client/src/{ErrorPage.tsx => components/error_page.tsx} (100%) rename client/src/components/{GameMenu.tsx => game_menu.tsx} (94%) rename client/src/components/{infoview => }/hints.tsx (78%) rename client/src/components/infoview/{CommandLine.tsx => command_line.tsx} (96%) create mode 100644 client/src/components/infoview/context.ts rename client/src/components/infoview/{rpcApi.ts => rpc_api.ts} (88%) rename client/src/components/{Inventory.tsx => inventory.tsx} (97%) rename client/src/components/{LandingPage.css => landing_page.css} (100%) rename client/src/components/{LandingPage.tsx => landing_page.tsx} (96%) rename client/src/components/{Level.tsx => level.tsx} (90%) rename client/src/components/{Markdown.tsx => markdown.tsx} (100%) rename client/src/components/{Message.tsx => message.tsx} (95%) rename client/src/components/{PrivacyPolicy.tsx => privacy_policy.tsx} (100%) rename client/src/components/{Welcome.tsx => welcome.tsx} (93%) rename client/src/{ => state}/connection.ts (94%) rename client/src/{ => state}/hooks.ts (82%) rename client/src/state/{localStorage.ts => local_storage.ts} (100%) diff --git a/client/src/App.test.js b/client/src/app.test.js similarity index 89% rename from client/src/App.test.js rename to client/src/app.test.js index 1f03afe..888a926 100644 --- a/client/src/App.test.js +++ b/client/src/app.test.js @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react'; -import App from './App'; +import App from './app'; test('renders learn react link', () => { render(); diff --git a/client/src/App.tsx b/client/src/app.tsx similarity index 82% rename from client/src/App.tsx rename to client/src/app.tsx index cf2787b..e995fac 100644 --- a/client/src/App.tsx +++ b/client/src/app.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import { useState, useEffect } from 'react'; import { Outlet, useParams } from "react-router-dom"; import '@fontsource/roboto/300.css'; @@ -9,8 +8,7 @@ import '@fontsource/roboto/700.css'; import './reset.css'; import './app.css'; - -export const GameIdContext = React.createContext(undefined); +import { GameIdContext } from './components/infoview/context'; function App() { const params = useParams(); diff --git a/client/src/components/Button.tsx b/client/src/components/button.tsx similarity index 100% rename from client/src/components/Button.tsx rename to client/src/components/button.tsx diff --git a/client/src/ErrorPage.tsx b/client/src/components/error_page.tsx similarity index 100% rename from client/src/ErrorPage.tsx rename to client/src/components/error_page.tsx diff --git a/client/src/components/GameMenu.tsx b/client/src/components/game_menu.tsx similarity index 94% rename from client/src/components/GameMenu.tsx rename to client/src/components/game_menu.tsx index ab0d41b..87c10b1 100644 --- a/client/src/components/GameMenu.tsx +++ b/client/src/components/game_menu.tsx @@ -1,13 +1,11 @@ import * as React from 'react' -import { Button } from './Button' -import { GameIdContext } from '../App'; -import { useStore } from 'react-redux'; -import { useAppDispatch, useAppSelector } from '../hooks'; -import { useSelector } from 'react-redux'; - +import { useStore, useSelector } from 'react-redux'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faDownload, faUpload, faEraser } from '@fortawesome/free-solid-svg-icons' +import { Button } from './button' +import { GameIdContext } from './infoview/context'; +import { useAppDispatch, useAppSelector } from '../state/hooks'; import { deleteProgress, selectProgress, loadProgress, GameProgressState } from '../state/progress'; const downloadFile = ({ data, fileName, fileType }) => { diff --git a/client/src/components/infoview/hints.tsx b/client/src/components/hints.tsx similarity index 78% rename from client/src/components/infoview/hints.tsx rename to client/src/components/hints.tsx index e97080c..90918f1 100644 --- a/client/src/components/infoview/hints.tsx +++ b/client/src/components/hints.tsx @@ -1,7 +1,6 @@ -import { GameHint } from "./rpcApi"; +import { GameHint } from "./infoview/rpc_api"; import * as React from 'react'; -import { Alert, FormControlLabel, Switch } from '@mui/material'; -import Markdown from '../Markdown'; +import Markdown from './markdown'; export function Hint({hint} : {hint: GameHint}) { return
{hint.text}
diff --git a/client/src/components/infoview/CommandLine.tsx b/client/src/components/infoview/command_line.tsx similarity index 96% rename from client/src/components/infoview/CommandLine.tsx rename to client/src/components/infoview/command_line.tsx index 72fa488..1c88a9b 100644 --- a/client/src/components/infoview/CommandLine.tsx +++ b/client/src/components/infoview/command_line.tsx @@ -1,24 +1,20 @@ import * as React from 'react' import { useRef, useState, useEffect } from 'react' -import { LspDiagnosticsContext } from '../../../../node_modules/lean4-infoview/src/infoview/contexts'; -import { useServerNotificationEffect } from '../../../../node_modules/lean4-infoview/src/infoview/util'; -import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js' -import { DiagnosticSeverity, PublishDiagnosticsParams } from 'vscode-languageserver-protocol'; -import { InputModeContext, MonacoEditorContext } from '../Level' - import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faWandMagicSparkles } from '@fortawesome/free-solid-svg-icons' -import { AbbreviationRewriter } from 'lean4web/client/src/editor/abbreviation/rewriter/AbbreviationRewriter'; -import { AbbreviationProvider } from 'lean4web/client/src/editor/abbreviation/AbbreviationProvider'; - +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js' import { Registry } from 'monaco-textmate' // peer dependency import { wireTmGrammars } from 'monaco-editor-textmate' -import * as lightPlusTheme from 'lean4web/client/src/lightPlus.json' +import { DiagnosticSeverity, PublishDiagnosticsParams } from 'vscode-languageserver-protocol'; +import { useServerNotificationEffect } from '../../../../node_modules/lean4-infoview/src/infoview/util'; +import { AbbreviationRewriter } from 'lean4web/client/src/editor/abbreviation/rewriter/AbbreviationRewriter'; +import { AbbreviationProvider } from 'lean4web/client/src/editor/abbreviation/AbbreviationProvider'; import * as leanSyntax from 'lean4web/client/src/syntaxes/lean.json' import * as leanMarkdownSyntax from 'lean4web/client/src/syntaxes/lean-markdown.json' import * as codeblockSyntax from 'lean4web/client/src/syntaxes/codeblock.json' import languageConfig from 'lean4/language-configuration.json'; +import { InputModeContext, MonacoEditorContext } from './context' /* We register a new language `leancmd` that looks like lean4, but does not use the lsp server. */ diff --git a/client/src/components/infoview/context.ts b/client/src/components/infoview/context.ts new file mode 100644 index 0000000..ccfd453 --- /dev/null +++ b/client/src/components/infoview/context.ts @@ -0,0 +1,77 @@ +/** + * @fileOverview This file contains the the react contexts used in the project. + */ +import * as React from 'react'; +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js' +import { InteractiveDiagnostic, InteractiveTermGoal } from '@leanprover/infoview-api'; +import { InteractiveGoals } from './rpc_api'; + +export const GameIdContext = React.createContext(undefined); + +export const MonacoEditorContext = React.createContext( + null as any) + +// TODO: Is this still used? +export const HintContext = React.createContext<{ + showHiddenHints : boolean, + setShowHiddenHints: React.Dispatch> +}>({ + showHiddenHints: true, + setShowHiddenHints: () => {}, +}); + +export type InfoStatus = 'updating' | 'error' | 'ready'; + +export type ProofStep = { + // TODO: Add correct types + command : string + goals: string + hints: string + errors: string +} + +export const ProofContext = React.createContext<{ + // The first entry will always have an empty/undefined command + proof: ProofStep[], + setProof: React.Dispatch>> +}>({ + proof: [], + setProof: () => {} +}) + +export interface ProofStateProps { + // pos: DocumentPosition; + status: InfoStatus; + messages: InteractiveDiagnostic[]; + goals?: InteractiveGoals; + termGoal?: InteractiveTermGoal; + error?: string; + // userWidgets: UserWidgetInstance[]; + // rpcSess: RpcSessionAtPos; + // triggerUpdate: () => Promise; +} + +export const ProofStateContext = React.createContext<{ + proofState : ProofStateProps, + setProofState: React.Dispatch> +}>({ + proofState : { + status: 'updating', + messages: [], + goals: undefined, + termGoal: undefined, + error: undefined}, + setProofState: () => {}, +}); + +export const InputModeContext = React.createContext<{ + commandLineMode: boolean, + setCommandLineMode: React.Dispatch>, + commandLineInput: string, + setCommandLineInput: React.Dispatch> +}>({ + commandLineMode: true, + setCommandLineMode: () => {}, + commandLineInput: "", + setCommandLineInput: () => {}, +}); diff --git a/client/src/components/infoview/goals.tsx b/client/src/components/infoview/goals.tsx index e83888c..a288a15 100644 --- a/client/src/components/infoview/goals.tsx +++ b/client/src/components/infoview/goals.tsx @@ -6,10 +6,9 @@ import { InteractiveHypothesisBundle_nonAnonymousNames, MVarId, TaggedText_strip import { WithTooltipOnHover } from '../../../../node_modules/lean4-infoview/src/infoview/tooltips'; import { EditorContext } from '../../../../node_modules/lean4-infoview/src/infoview/contexts'; import { Locations, LocationsContext, SelectableLocation } from '../../../../node_modules/lean4-infoview/src/infoview/goalLocation'; -import { InteractiveGoal, InteractiveGoals, InteractiveHypothesisBundle } from './rpcApi'; -import { Hints } from './hints'; -import { CommandLine } from './CommandLine'; -import { InputModeContext } from '../Level'; +import { InteractiveGoal, InteractiveGoals, InteractiveHypothesisBundle } from './rpc_api'; +import { InputModeContext } from './context'; + /** Returns true if `h` is inaccessible according to Lean's default name rendering. */ function isInaccessibleName(h: string): boolean { @@ -231,6 +230,7 @@ export const OtherGoals = React.memo((props: GoalProps2) => { }) + export const ProofDisplay = React.memo((props : ProofDisplayProps) => { const { proof } = props const steps = proof.match(/.+/g) diff --git a/client/src/components/infoview/info.tsx b/client/src/components/infoview/info.tsx index c57621b..1b87255 100644 --- a/client/src/components/infoview/info.tsx +++ b/client/src/components/infoview/info.tsx @@ -1,24 +1,25 @@ /* Mostly copied from https://github.com/leanprover/vscode-lean4/blob/master/lean4-infoview/src/infoview/info.tsx */ import * as React from 'react'; +import { CircularProgress } from '@mui/material'; + + import type { Location, Diagnostic } from 'vscode-languageserver-protocol'; -import { goalsToString, Goal, MainAssumptions, OtherGoals, FilteredGoals, ProofDisplay } from './goals' -import { basename, DocumentPosition, RangeHelpers, useEvent, usePausableState, discardMethodNotFound, - mapRpcError, useAsyncWithTrigger, PausableProps } from '../../../../node_modules/lean4-infoview/src/infoview/util'; -import { ConfigContext, EditorContext, LspDiagnosticsContext, ProgressContext } from '../../../../node_modules/lean4-infoview/src/infoview/contexts'; -import { AllMessages, lspDiagToInteractive, MessagesList } from './messages'; import { getInteractiveTermGoal, InteractiveDiagnostic, UserWidgetInstance, Widget_getWidgets, RpcSessionAtPos, isRpcError, RpcErrorCode, getInteractiveDiagnostics, InteractiveTermGoal } from '@leanprover/infoview-api'; -import { InteractiveGoal, InteractiveGoals } from './rpcApi'; +import { basename, DocumentPosition, RangeHelpers, useEvent, usePausableState, discardMethodNotFound, + mapRpcError, useAsyncWithTrigger, PausableProps } from '../../../../node_modules/lean4-infoview/src/infoview/util'; +import { ConfigContext, EditorContext, LspDiagnosticsContext, ProgressContext } from '../../../../node_modules/lean4-infoview/src/infoview/contexts'; import { PanelWidgetDisplay } from '../../../../node_modules/lean4-infoview/src/infoview/userWidget' import { RpcContext, useRpcSessionAtPos } from '../../../../node_modules/lean4-infoview/src/infoview/rpcSessions'; import { GoalsLocation, Locations, LocationsContext } from '../../../../node_modules/lean4-infoview/src/infoview/goalLocation'; -import { InteractiveCode } from '../../../../node_modules/lean4-infoview/src/infoview/interactiveCode' -import { CircularProgress } from '@mui/material'; -import { InputModeContext, MonacoEditorContext, HintContext, ProofStateProps, InfoStatus, ProofStateContext } from '../Level' -import { Hint } from './hints'; + +import { AllMessages, lspDiagToInteractive, MessagesList } from './messages'; +import { goalsToString, Goal, MainAssumptions, OtherGoals, FilteredGoals, ProofDisplay } from './goals' +import { InteractiveGoal, InteractiveGoals } from './rpc_api'; +import { InputModeContext, MonacoEditorContext, HintContext, ProofStateProps, InfoStatus, ProofStateContext } from './context' type InfoKind = 'cursor' | 'pin'; @@ -127,7 +128,7 @@ const InfoDisplayContent = React.memo((props: InfoDisplayContentProps) => {
{ goals && goals.goals.length > 0 && <> - {/* */} + }
diff --git a/client/src/components/infoview/infoview.css b/client/src/components/infoview/infoview.css index 1024033..9aa5fd2 100644 --- a/client/src/components/infoview/infoview.css +++ b/client/src/components/infoview/infoview.css @@ -64,7 +64,7 @@ flex-direction: column; } -.command-line .command-line-input{ +.command-line .command-line-input { flex: 1; } @@ -128,3 +128,14 @@ border-radius: 1em; padding: 0.6em; } + + +/* Push the goals to the bottom for now, until we insert the proof history above. */ +.commandline-interface .content { + display: flex; + flex-direction: column; +} + +.commandline-interface .content .tmp-pusher { + flex: 1; +} diff --git a/client/src/components/infoview/main.tsx b/client/src/components/infoview/main.tsx index 38e520b..d385ff5 100644 --- a/client/src/components/infoview/main.tsx +++ b/client/src/components/infoview/main.tsx @@ -4,26 +4,28 @@ import * as React from 'react'; import type { DidCloseTextDocumentParams, DidChangeTextDocumentParams, Location, DocumentUri } from 'vscode-languageserver-protocol'; import 'tachyons/css/tachyons.css'; -// import '@vscode/codicons/dist/codicon.ttf'; import '@vscode/codicons/dist/codicon.css'; import '../../../../node_modules/lean4-infoview/src/infoview/index.css'; import './infoview.css' import { LeanFileProgressParams, LeanFileProgressProcessingInfo, defaultInfoviewConfig, EditorApi, InfoviewApi } from '@leanprover/infoview-api'; - -import { Infos } from './infos'; -import { AllMessages, WithLspDiagnosticsContext } from './messages'; import { useClientNotificationEffect, useServerNotificationEffect, useEventResult, useServerNotificationState } from '../../../../node_modules/lean4-infoview/src/infoview/util'; import { EditorContext, ConfigContext, ProgressContext, VersionContext } from '../../../../node_modules/lean4-infoview/src/infoview/contexts'; import { WithRpcSessions } from '../../../../node_modules/lean4-infoview/src/infoview/rpcSessions'; import { ServerVersion } from '../../../../node_modules/lean4-infoview/src/infoview/serverVersion'; -import { useAppDispatch, useAppSelector } from '../../hooks'; -import { levelCompleted, selectCompleted } from '../../state/progress'; -import { GameIdContext } from '../../App'; -import { InputModeContext } from '../Level'; -import { CommandLine } from './CommandLine'; -import Markdown from '../Markdown'; + +import { GameIdContext } from './context'; +import { useAppDispatch, useAppSelector } from '../../state/hooks'; import { LevelInfo } from '../../state/api'; +import { levelCompleted, selectCompleted } from '../../state/progress'; +import Markdown from '../markdown'; + +import { Infos } from './infos'; +import { AllMessages, WithLspDiagnosticsContext } from './messages'; +import { Goal } from './goals'; +import { InputModeContext, ProofStateContext } from './context'; +import { CommandLine } from './command_line'; + // The mathematical formulation of the statement, supporting e.g. Latex // It takes three forms, depending on the precence of name and description: @@ -138,6 +140,10 @@ export function CommandLineInterface(props: {world: string, level: number, data: const ec = React.useContext(EditorContext); const gameId = React.useContext(GameIdContext) + const proofStateContext = React.useContext(ProofStateContext) + + const [selectedGoal, setSelectedGoal] = React.useState(0) + const dispatch = useAppDispatch() // Mark level as completed when server gives notification @@ -181,6 +187,8 @@ export function CommandLineInterface(props: {world: string, level: number, data: [] ); + const goalFilter = { reverse: false, showType: true, showInstance: true, showHiddenAssumption: true, showLetValue: true } + const serverVersion = useEventResult(ec.events.serverRestarted, result => new ServerVersion(result.serverInfo?.version ?? '')) const serverStoppedResult = useEventResult(ec.events.serverStopped); @@ -199,6 +207,19 @@ export function CommandLineInterface(props: {world: string, level: number, data:
+
+
+ {proofStateContext.proofState.goals?.goals.map((goal, i) => +
{ setSelectedGoal(i) }}> + {i ? `Goal ${i+1}` : "Active Goal"} +
)} +
+
+ {proofStateContext.proofState.goals?.goals?.length && + + } +
diff --git a/client/src/components/infoview/messages.tsx b/client/src/components/infoview/messages.tsx index 60a0229..46550f7 100644 --- a/client/src/components/infoview/messages.tsx +++ b/client/src/components/infoview/messages.tsx @@ -1,16 +1,16 @@ -import * as React from 'react'; -import fastIsEqual from 'react-fast-compare'; -import { Location, DocumentUri, Diagnostic, DiagnosticSeverity, PublishDiagnosticsParams } from 'vscode-languageserver-protocol'; - -import { LeanDiagnostic, RpcErrorCode } from '@leanprover/infoview-api'; - -import { basename, escapeHtml, usePausableState, useEvent, addUniqueKeys, DocumentPosition, useServerNotificationState, useEventResult } from '../../../../node_modules/lean4-infoview/src/infoview/util'; -import { ConfigContext, EditorContext, LspDiagnosticsContext, VersionContext } from '../../../../node_modules/lean4-infoview/src/infoview/contexts'; -import { Details } from '../../../../node_modules/lean4-infoview/src/infoview/collapsing'; -import { InteractiveMessage } from '../../../../node_modules/lean4-infoview/src/infoview/traceExplorer'; -import { getInteractiveDiagnostics, InteractiveDiagnostic, TaggedText_stripTags } from '@leanprover/infoview-api'; -import { RpcContext, useRpcSessionAtPos } from '../../../../node_modules/lean4-infoview/src/infoview/rpcSessions'; -import { InputModeContext } from '../Level'; +import * as React from 'react' +import fastIsEqual from 'react-fast-compare' +import { Location, DocumentUri, Diagnostic, DiagnosticSeverity, PublishDiagnosticsParams } from 'vscode-languageserver-protocol' + +import { LeanDiagnostic, RpcErrorCode, getInteractiveDiagnostics, InteractiveDiagnostic, TaggedText_stripTags } from '@leanprover/infoview-api' + +import { basename, escapeHtml, usePausableState, useEvent, addUniqueKeys, DocumentPosition, useServerNotificationState, useEventResult } from '../../../../node_modules/lean4-infoview/src/infoview/util' +import { ConfigContext, EditorContext, LspDiagnosticsContext, VersionContext } from '../../../../node_modules/lean4-infoview/src/infoview/contexts' +import { Details } from '../../../../node_modules/lean4-infoview/src/infoview/collapsing' +import { InteractiveMessage } from '../../../../node_modules/lean4-infoview/src/infoview/traceExplorer' +import { RpcContext, useRpcSessionAtPos } from '../../../../node_modules/lean4-infoview/src/infoview/rpcSessions' + +import { InputModeContext } from './context' interface MessageViewProps { uri: DocumentUri; diff --git a/client/src/components/infoview/rpcApi.ts b/client/src/components/infoview/rpc_api.ts similarity index 88% rename from client/src/components/infoview/rpcApi.ts rename to client/src/components/infoview/rpc_api.ts index 8cfc91e..3833c61 100644 --- a/client/src/components/infoview/rpcApi.ts +++ b/client/src/components/infoview/rpc_api.ts @@ -1,5 +1,8 @@ -/* This file is based on `vscode-lean4/vscode-lean4/src/rpcApi.ts ` */ - +/** + * @fileOverview Defines the interface for the communication with the server. + * + * This file is based on `vscode-lean4/vscode-lean4/src/rpcApi.ts` + */ import { ContextInfo, FVarId, CodeWithInfos, MVarId } from '@leanprover/infoview-api'; export interface GameHint { diff --git a/client/src/components/Inventory.tsx b/client/src/components/inventory.tsx similarity index 97% rename from client/src/components/Inventory.tsx rename to client/src/components/inventory.tsx index 02d660f..f713f64 100644 --- a/client/src/components/Inventory.tsx +++ b/client/src/components/inventory.tsx @@ -3,9 +3,9 @@ import { useState, useEffect } from 'react'; import './inventory.css' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faLock, faLockOpen, faBook, faHammer, faBan } from '@fortawesome/free-solid-svg-icons' -import Markdown from './Markdown'; +import { GameIdContext } from './infoview/context'; +import Markdown from './markdown'; import { useLoadDocQuery, InventoryTile, LevelInfo } from '../state/api'; -import { GameIdContext } from '../App'; export function Inventory({levelInfo, openDoc } : { diff --git a/client/src/components/LandingPage.css b/client/src/components/landing_page.css similarity index 100% rename from client/src/components/LandingPage.css rename to client/src/components/landing_page.css diff --git a/client/src/components/LandingPage.tsx b/client/src/components/landing_page.tsx similarity index 96% rename from client/src/components/LandingPage.tsx rename to client/src/components/landing_page.tsx index 7cdf947..e1727bf 100644 --- a/client/src/components/LandingPage.tsx +++ b/client/src/components/landing_page.tsx @@ -1,19 +1,18 @@ import * as React from 'react'; -import { useNavigate } from "react-router-dom"; -import { Link } from 'react-router-dom'; -import Markdown from './Markdown'; +import { useNavigate, Link } from "react-router-dom"; import '@fontsource/roboto/300.css'; import '@fontsource/roboto/400.css'; import '@fontsource/roboto/500.css'; import '@fontsource/roboto/700.css'; -import './LandingPage.css' -import {PrivacyPolicyPopup} from './PrivacyPolicy' - +import './landing_page.css' import coverRobo from '../assets/covers/formaloversum.png' import bgImage from '../assets/bg.jpg' +import Markdown from './markdown'; +import {PrivacyPolicyPopup} from './privacy_policy' + const flag = { 'Dutch': '🇳🇱', 'English': '🇬🇧', diff --git a/client/src/components/Level.tsx b/client/src/components/level.tsx similarity index 90% rename from client/src/components/Level.tsx rename to client/src/components/level.tsx index ee37bca..19afc8f 100644 --- a/client/src/components/Level.tsx +++ b/client/src/components/level.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; import { useEffect, useState, useRef } from 'react'; +import Split from 'react-split' +import { Alert } from '@mui/material'; import '@fontsource/roboto/300.css'; import '@fontsource/roboto/400.css'; import '@fontsource/roboto/500.css'; @@ -26,74 +28,19 @@ import type { Location } from 'vscode-languageserver-protocol'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faHome, faArrowRight, faArrowLeft, faRotateLeft } from '@fortawesome/free-solid-svg-icons' import { styled, useTheme, Theme, CSSObject } from '@mui/material/styles'; -import Split from 'react-split' -import { Alert } from '@mui/material'; +import { DocumentPosition } from '../../../node_modules/lean4-infoview/src/infoview/util'; -import { Button } from './Button' -import {Inventory, Documentation} from './Inventory'; -import Markdown from './Markdown'; -import { EditorInterface, CommandLineInterface } from './infoview/main' -import { Hints } from './infoview/hints'; -import { GameHint, InteractiveGoals } from './infoview/rpcApi'; -import { GameIdContext } from '../App'; -import { ConnectionContext, useLeanClient } from '../connection'; -import { useAppDispatch, useAppSelector } from '../hooks'; +import { GameIdContext } from './infoview/context'; +import { ConnectionContext, useLeanClient } from '../state/connection'; +import { useAppDispatch, useAppSelector } from '../state/hooks'; +import { Button } from './button' +import Markdown from './markdown'; +import {Inventory, Documentation} from './inventory'; import { useGetGameInfoQuery, useLoadLevelQuery } from '../state/api'; import { changedSelection, codeEdited, selectCode, selectSelections, progressSlice, selectCompleted } from '../state/progress'; -import { DocumentPosition } from '../../../node_modules/lean4-infoview/src/infoview/util'; -import { getInteractiveTermGoal, InteractiveDiagnostic, - UserWidgetInstance, Widget_getWidgets, RpcSessionAtPos, isRpcError, - RpcErrorCode, getInteractiveDiagnostics, InteractiveTermGoal } from '@leanprover/infoview-api'; - -export const MonacoEditorContext = React.createContext(null as any); - -export const HintContext = React.createContext<{ - showHiddenHints : boolean, - setShowHiddenHints: React.Dispatch> -}>({ - showHiddenHints: true, - setShowHiddenHints: () => {}, -}); - -export type InfoStatus = 'updating' | 'error' | 'ready'; - -export interface ProofStateProps { - // pos: DocumentPosition; - status: InfoStatus; - messages: InteractiveDiagnostic[]; - goals?: InteractiveGoals; - termGoal?: InteractiveTermGoal; - error?: string; - // userWidgets: UserWidgetInstance[]; - // rpcSess: RpcSessionAtPos; - // triggerUpdate: () => Promise; -} - -export const ProofStateContext = React.createContext<{ - proofState : ProofStateProps, - setProofState: React.Dispatch> -}>({ - proofState : { - status: 'updating', - messages: [], - goals: undefined, - termGoal: undefined, - error: undefined}, - setProofState: () => {}, -}); - - -export const InputModeContext = React.createContext<{ - commandLineMode: boolean, - setCommandLineMode: React.Dispatch>, - commandLineInput: string, - setCommandLineInput: React.Dispatch> -}>({ - commandLineMode: true, - setCommandLineMode: () => {}, - commandLineInput: "", - setCommandLineInput: () => {}, -}); +import { EditorInterface, CommandLineInterface } from './infoview/main' +import { Hints } from './hints'; +import { HintContext, InputModeContext, MonacoEditorContext, ProofContext, ProofStateContext, ProofStateProps, ProofStep } from './infoview/context'; function Level() { @@ -124,6 +71,8 @@ function PlayableLevel({worldId, levelId}) { const [showHiddenHints, setShowHiddenHints] = useState(false) + const [proof, setProof] = useState>([]) + const [proofState, setProofState] = React.useState({ status: 'updating', messages: [], @@ -238,6 +187,7 @@ function PlayableLevel({worldId, levelId}) {
+ @@ -307,6 +257,7 @@ function PlayableLevel({worldId, levelId}) { + diff --git a/client/src/components/Markdown.tsx b/client/src/components/markdown.tsx similarity index 100% rename from client/src/components/Markdown.tsx rename to client/src/components/markdown.tsx diff --git a/client/src/components/Message.tsx b/client/src/components/message.tsx similarity index 95% rename from client/src/components/Message.tsx rename to client/src/components/message.tsx index 1a745a5..3883eb0 100644 --- a/client/src/components/Message.tsx +++ b/client/src/components/message.tsx @@ -5,7 +5,7 @@ import '@fontsource/roboto/500.css'; import '@fontsource/roboto/700.css'; import { Button, Dialog, DialogContent, DialogContentText, DialogActions } from '@mui/material'; -import Markdown from './Markdown'; +import Markdown from './markdown'; function Message({ isOpen, content, close }) { diff --git a/client/src/components/PrivacyPolicy.tsx b/client/src/components/privacy_policy.tsx similarity index 100% rename from client/src/components/PrivacyPolicy.tsx rename to client/src/components/privacy_policy.tsx diff --git a/client/src/components/Welcome.tsx b/client/src/components/welcome.tsx similarity index 93% rename from client/src/components/Welcome.tsx rename to client/src/components/welcome.tsx index 0382b6b..a69771a 100644 --- a/client/src/components/Welcome.tsx +++ b/client/src/components/welcome.tsx @@ -1,28 +1,21 @@ import * as React from 'react'; import { useState, useEffect, useRef } from 'react'; -import './welcome.css' -import cytoscape, { LayoutOptions } from 'cytoscape' -import klay from 'cytoscape-klay'; +import { Link } from 'react-router-dom'; import { useNavigate } from 'react-router-dom'; import { useSelector } from 'react-redux'; import Split from 'react-split' - -import GameMenu from './GameMenu'; -import {PrivacyPolicy} from './PrivacyPolicy'; - -cytoscape.use( klay ); - import { Box, Typography, CircularProgress } from '@mui/material'; -import { useGetGameInfoQuery } from '../state/api'; -import { Link } from 'react-router-dom'; -import Markdown from './Markdown'; +import cytoscape, { LayoutOptions } from 'cytoscape' +import klay from 'cytoscape-klay'; +import './welcome.css' +import { GameIdContext } from './infoview/context'; import { selectCompleted } from '../state/progress'; -import { GameIdContext } from '../App'; -import { Button } from './Button'; - -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faDownload, faUpload, faEraser } from '@fortawesome/free-solid-svg-icons' +import { useGetGameInfoQuery } from '../state/api'; +import Markdown from './markdown'; +import GameMenu from './game_menu'; +import {PrivacyPolicy} from './privacy_policy'; +cytoscape.use( klay ); const N = 24 // max number of levels per world const R = 800 // radius of a world diff --git a/client/src/index.tsx b/client/src/index.tsx index 814407f..4146aaf 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { createRoot } from 'react-dom/client'; -import App from './App'; -import { ConnectionContext, connection } from './connection' +import App from './app'; +import { ConnectionContext, connection } from './state/connection' import { store } from './state/store'; import { Provider } from 'react-redux'; import { @@ -9,10 +9,10 @@ import { RouterProvider, Route, } from "react-router-dom"; -import ErrorPage from './ErrorPage'; -import Welcome from './components/Welcome'; -import LandingPage from './components/LandingPage'; -import Level from './components/Level'; +import ErrorPage from './components/error_page'; +import Welcome from './components/welcome'; +import LandingPage from './components/landing_page'; +import Level from './components/level'; import { monacoSetup } from 'lean4web/client/src/monacoSetup'; import { redirect } from 'react-router-dom'; diff --git a/client/src/state/api.ts b/client/src/state/api.ts index 349b905..19c4c5e 100644 --- a/client/src/state/api.ts +++ b/client/src/state/api.ts @@ -1,5 +1,5 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' -import { Connection } from '../connection' +import { Connection } from './connection' interface GameInfo { title: null|string, diff --git a/client/src/connection.ts b/client/src/state/connection.ts similarity index 94% rename from client/src/connection.ts rename to client/src/state/connection.ts index 2d630ac..3a132a8 100644 --- a/client/src/connection.ts +++ b/client/src/state/connection.ts @@ -1,9 +1,10 @@ +/** + * @fileOverview todo + */ +import * as React from 'react'; 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 { useState } from 'react'; - export class Connection { private game: string = undefined // We only keep a connection to a single game at a time @@ -54,7 +55,7 @@ export const ConnectionContext = React.createContext(null); export const useLeanClient = (gameId) => { const leanClient = connection.getLeanClient(gameId) - const [leanClientStarted, setLeanClientStarted] = useState(leanClient.isStarted()) + const [leanClientStarted, setLeanClientStarted] = React.useState(leanClient.isStarted()) React.useEffect(() => { const t1 = leanClient.restarted(() => { console.log("START"); setLeanClientStarted(true) }) diff --git a/client/src/hooks.ts b/client/src/state/hooks.ts similarity index 82% rename from client/src/hooks.ts rename to client/src/state/hooks.ts index afdce0a..60a4163 100644 --- a/client/src/hooks.ts +++ b/client/src/state/hooks.ts @@ -1,5 +1,5 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux' -import type { RootState, AppDispatch } from './state/store' +import type { RootState, AppDispatch } from './store' // Use throughout your app instead of plain `useDispatch` and `useSelector` export const useAppDispatch: () => AppDispatch = useDispatch diff --git a/client/src/state/localStorage.ts b/client/src/state/local_storage.ts similarity index 100% rename from client/src/state/localStorage.ts rename to client/src/state/local_storage.ts diff --git a/client/src/state/progress.ts b/client/src/state/progress.ts index 72816ff..cf1a656 100644 --- a/client/src/state/progress.ts +++ b/client/src/state/progress.ts @@ -1,6 +1,6 @@ import { createSlice } from '@reduxjs/toolkit' import type { PayloadAction } from '@reduxjs/toolkit' -import { loadState } from "./localStorage"; +import { loadState } from "./local_storage"; export interface GameProgressState { [world: string] : {[level: number]: LevelProgressState} diff --git a/client/src/state/store.ts b/client/src/state/store.ts index d0c28a6..f6bfd4c 100644 --- a/client/src/state/store.ts +++ b/client/src/state/store.ts @@ -1,10 +1,11 @@ import { configureStore } from '@reduxjs/toolkit'; -import { connection } from '../connection' -import thunkMiddleware from 'redux-thunk' +import { debounce } from "debounce"; + +import { connection } from './connection' import { apiSlice } from './api' import { progressSlice } from './progress' -import { saveState } from "./localStorage"; -import { debounce } from "debounce"; +import { saveState } from "./local_storage"; + export const store = configureStore({ reducer: { diff --git a/package-lock.json b/package-lock.json index 3b70d6c..8ae9f0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@emotion/styled": "^11.10.5", "@fontsource/roboto": "^4.5.8", "@fontsource/roboto-mono": "^4.5.8", + "@leanprover/infoview": "^0.4.3", "@mui/icons-material": "^5.11.0", "@mui/material": "^5.11.1", "@reduxjs/toolkit": "^1.9.1", @@ -2095,9 +2096,9 @@ } }, "node_modules/@leanprover/infoview": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@leanprover/infoview/-/infoview-0.4.2.tgz", - "integrity": "sha512-Bj+q/7n1xlnpRd4xsJ75uikZPwW5id/G4G1JPqhbbfMacTwAgzLNFaqJhzBrIUuF5nxJYGKRqRIeFtkalGxJ0g==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@leanprover/infoview/-/infoview-0.4.3.tgz", + "integrity": "sha512-SufdOr2myHAbZNUmobfQdAhsEC5H9ddi3KS0z1v/8riWSMm+yJk3u4LxVuzCmmSmV2QxFqtFzn5z+HQqj1Vo7g==", "dependencies": { "@leanprover/infoview-api": "~0.2.1", "@vscode/codicons": "^0.0.32", diff --git a/package.json b/package.json index 89f53cc..4636050 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "@emotion/styled": "^11.10.5", "@fontsource/roboto": "^4.5.8", "@fontsource/roboto-mono": "^4.5.8", + "@leanprover/infoview": "^0.4.3", "@mui/icons-material": "^5.11.0", "@mui/material": "^5.11.1", "@reduxjs/toolkit": "^1.9.1", From 5e728fc21aa9d50eb34239858e039b0161efe5f7 Mon Sep 17 00:00:00 2001 From: Jon Eugster Date: Wed, 5 Jul 2023 17:34:46 +0200 Subject: [PATCH 2/5] rename files --- client/src/components/game_menu.tsx | 2 +- client/src/components/infoview/main.tsx | 2 +- client/src/components/level.tsx | 4 ++-- client/src/{state => }/connection.ts | 0 client/src/{state => }/hooks.ts | 2 +- client/src/index.tsx | 2 +- client/src/state/api.ts | 2 +- client/src/state/store.ts | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) rename client/src/{state => }/connection.ts (100%) rename client/src/{state => }/hooks.ts (82%) diff --git a/client/src/components/game_menu.tsx b/client/src/components/game_menu.tsx index 87c10b1..2dec139 100644 --- a/client/src/components/game_menu.tsx +++ b/client/src/components/game_menu.tsx @@ -5,7 +5,7 @@ import { faDownload, faUpload, faEraser } from '@fortawesome/free-solid-svg-icon import { Button } from './button' import { GameIdContext } from './infoview/context'; -import { useAppDispatch, useAppSelector } from '../state/hooks'; +import { useAppDispatch, useAppSelector } from '../hooks'; import { deleteProgress, selectProgress, loadProgress, GameProgressState } from '../state/progress'; const downloadFile = ({ data, fileName, fileType }) => { diff --git a/client/src/components/infoview/main.tsx b/client/src/components/infoview/main.tsx index d385ff5..1bb0048 100644 --- a/client/src/components/infoview/main.tsx +++ b/client/src/components/infoview/main.tsx @@ -15,7 +15,7 @@ import { WithRpcSessions } from '../../../../node_modules/lean4-infoview/src/inf import { ServerVersion } from '../../../../node_modules/lean4-infoview/src/infoview/serverVersion'; import { GameIdContext } from './context'; -import { useAppDispatch, useAppSelector } from '../../state/hooks'; +import { useAppDispatch, useAppSelector } from '../../hooks'; import { LevelInfo } from '../../state/api'; import { levelCompleted, selectCompleted } from '../../state/progress'; import Markdown from '../markdown'; diff --git a/client/src/components/level.tsx b/client/src/components/level.tsx index 19afc8f..6a5590f 100644 --- a/client/src/components/level.tsx +++ b/client/src/components/level.tsx @@ -31,8 +31,8 @@ import { styled, useTheme, Theme, CSSObject } from '@mui/material/styles'; import { DocumentPosition } from '../../../node_modules/lean4-infoview/src/infoview/util'; import { GameIdContext } from './infoview/context'; -import { ConnectionContext, useLeanClient } from '../state/connection'; -import { useAppDispatch, useAppSelector } from '../state/hooks'; +import { ConnectionContext, useLeanClient } from '../connection'; +import { useAppDispatch, useAppSelector } from '../hooks'; import { Button } from './button' import Markdown from './markdown'; import {Inventory, Documentation} from './inventory'; diff --git a/client/src/state/connection.ts b/client/src/connection.ts similarity index 100% rename from client/src/state/connection.ts rename to client/src/connection.ts diff --git a/client/src/state/hooks.ts b/client/src/hooks.ts similarity index 82% rename from client/src/state/hooks.ts rename to client/src/hooks.ts index 60a4163..afdce0a 100644 --- a/client/src/state/hooks.ts +++ b/client/src/hooks.ts @@ -1,5 +1,5 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux' -import type { RootState, AppDispatch } from './store' +import type { RootState, AppDispatch } from './state/store' // Use throughout your app instead of plain `useDispatch` and `useSelector` export const useAppDispatch: () => AppDispatch = useDispatch diff --git a/client/src/index.tsx b/client/src/index.tsx index 4146aaf..2b593c6 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { createRoot } from 'react-dom/client'; import App from './app'; -import { ConnectionContext, connection } from './state/connection' +import { ConnectionContext, connection } from './connection' import { store } from './state/store'; import { Provider } from 'react-redux'; import { diff --git a/client/src/state/api.ts b/client/src/state/api.ts index 19c4c5e..349b905 100644 --- a/client/src/state/api.ts +++ b/client/src/state/api.ts @@ -1,5 +1,5 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' -import { Connection } from './connection' +import { Connection } from '../connection' interface GameInfo { title: null|string, diff --git a/client/src/state/store.ts b/client/src/state/store.ts index f6bfd4c..94746ce 100644 --- a/client/src/state/store.ts +++ b/client/src/state/store.ts @@ -1,7 +1,7 @@ import { configureStore } from '@reduxjs/toolkit'; import { debounce } from "debounce"; -import { connection } from './connection' +import { connection } from '../connection' import { apiSlice } from './api' import { progressSlice } from './progress' import { saveState } from "./local_storage"; From 13c78ba4208269557434cf22e29ac14baf4bc1c4 Mon Sep 17 00:00:00 2001 From: Jon Eugster Date: Wed, 5 Jul 2023 17:40:55 +0200 Subject: [PATCH 3/5] . --- client/src/app.tsx | 3 ++- client/src/components/game_menu.tsx | 2 +- client/src/components/infoview/context.ts | 2 -- client/src/components/infoview/main.tsx | 2 +- client/src/components/inventory.tsx | 2 +- client/src/components/level.tsx | 2 +- client/src/components/welcome.tsx | 2 +- 7 files changed, 7 insertions(+), 8 deletions(-) diff --git a/client/src/app.tsx b/client/src/app.tsx index e995fac..ce40e0e 100644 --- a/client/src/app.tsx +++ b/client/src/app.tsx @@ -8,7 +8,8 @@ import '@fontsource/roboto/700.css'; import './reset.css'; import './app.css'; -import { GameIdContext } from './components/infoview/context'; + +export const GameIdContext = React.createContext(undefined); function App() { const params = useParams(); diff --git a/client/src/components/game_menu.tsx b/client/src/components/game_menu.tsx index 2dec139..6b8cb24 100644 --- a/client/src/components/game_menu.tsx +++ b/client/src/components/game_menu.tsx @@ -4,7 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faDownload, faUpload, faEraser } from '@fortawesome/free-solid-svg-icons' import { Button } from './button' -import { GameIdContext } from './infoview/context'; +import { GameIdContext } from '../app'; import { useAppDispatch, useAppSelector } from '../hooks'; import { deleteProgress, selectProgress, loadProgress, GameProgressState } from '../state/progress'; diff --git a/client/src/components/infoview/context.ts b/client/src/components/infoview/context.ts index ccfd453..9332a35 100644 --- a/client/src/components/infoview/context.ts +++ b/client/src/components/infoview/context.ts @@ -6,8 +6,6 @@ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js' import { InteractiveDiagnostic, InteractiveTermGoal } from '@leanprover/infoview-api'; import { InteractiveGoals } from './rpc_api'; -export const GameIdContext = React.createContext(undefined); - export const MonacoEditorContext = React.createContext( null as any) diff --git a/client/src/components/infoview/main.tsx b/client/src/components/infoview/main.tsx index 1bb0048..a03a0c2 100644 --- a/client/src/components/infoview/main.tsx +++ b/client/src/components/infoview/main.tsx @@ -14,7 +14,7 @@ import { EditorContext, ConfigContext, ProgressContext, VersionContext } from '. import { WithRpcSessions } from '../../../../node_modules/lean4-infoview/src/infoview/rpcSessions'; import { ServerVersion } from '../../../../node_modules/lean4-infoview/src/infoview/serverVersion'; -import { GameIdContext } from './context'; +import { GameIdContext } from '../../app'; import { useAppDispatch, useAppSelector } from '../../hooks'; import { LevelInfo } from '../../state/api'; import { levelCompleted, selectCompleted } from '../../state/progress'; diff --git a/client/src/components/inventory.tsx b/client/src/components/inventory.tsx index f713f64..e51e07a 100644 --- a/client/src/components/inventory.tsx +++ b/client/src/components/inventory.tsx @@ -3,7 +3,7 @@ import { useState, useEffect } from 'react'; import './inventory.css' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faLock, faLockOpen, faBook, faHammer, faBan } from '@fortawesome/free-solid-svg-icons' -import { GameIdContext } from './infoview/context'; +import { GameIdContext } from '../app'; import Markdown from './markdown'; import { useLoadDocQuery, InventoryTile, LevelInfo } from '../state/api'; diff --git a/client/src/components/level.tsx b/client/src/components/level.tsx index 6a5590f..b00fc67 100644 --- a/client/src/components/level.tsx +++ b/client/src/components/level.tsx @@ -30,7 +30,7 @@ import { faHome, faArrowRight, faArrowLeft, faRotateLeft } from '@fortawesome/fr import { styled, useTheme, Theme, CSSObject } from '@mui/material/styles'; import { DocumentPosition } from '../../../node_modules/lean4-infoview/src/infoview/util'; -import { GameIdContext } from './infoview/context'; +import { GameIdContext } from '../app'; import { ConnectionContext, useLeanClient } from '../connection'; import { useAppDispatch, useAppSelector } from '../hooks'; import { Button } from './button' diff --git a/client/src/components/welcome.tsx b/client/src/components/welcome.tsx index a69771a..85fb1fb 100644 --- a/client/src/components/welcome.tsx +++ b/client/src/components/welcome.tsx @@ -8,7 +8,7 @@ import { Box, Typography, CircularProgress } from '@mui/material'; import cytoscape, { LayoutOptions } from 'cytoscape' import klay from 'cytoscape-klay'; import './welcome.css' -import { GameIdContext } from './infoview/context'; +import { GameIdContext } from '../app'; import { selectCompleted } from '../state/progress'; import { useGetGameInfoQuery } from '../state/api'; import Markdown from './markdown'; From 4039fa9467baae1fbcb1f0b88a81796469f2cae0 Mon Sep 17 00:00:00 2001 From: Jon Eugster Date: Wed, 5 Jul 2023 18:00:50 +0200 Subject: [PATCH 4/5] currently its working --- client/src/state/api.ts | 3 + client/src/state/local_storage.ts | 16 +++++- client/src/state/progress.ts | 91 +++++++++++++++++++------------ package-lock.json | 7 +++ package.json | 1 + 5 files changed, 83 insertions(+), 35 deletions(-) diff --git a/client/src/state/api.ts b/client/src/state/api.ts index 349b905..84d083f 100644 --- a/client/src/state/api.ts +++ b/client/src/state/api.ts @@ -1,3 +1,6 @@ +/** + * @fileOverview +*/ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' import { Connection } from '../connection' diff --git a/client/src/state/local_storage.ts b/client/src/state/local_storage.ts index 729d84d..194709e 100644 --- a/client/src/state/local_storage.ts +++ b/client/src/state/local_storage.ts @@ -1,14 +1,28 @@ +/** + * @fileOverview Load/save the state to the local browser store + */ + const KEY = "game_progress"; + +/** Load from browser storage */ export function loadState() { try { const serializedState = localStorage.getItem(KEY); if (!serializedState) return undefined; - return JSON.parse(serializedState); + let x = JSON.parse(serializedState); + // Complatibilty because `state.level` has been renamed to `x.games`. + // TODO: Does this work? + if (x.level) { + x.games = x.level + x.level = undefined + } + return x } catch (e) { return undefined; } } +/** Save to browser storage */ export async function saveState(state: any) { try { const serializedState = JSON.stringify(state); diff --git a/client/src/state/progress.ts b/client/src/state/progress.ts index cf1a656..137c571 100644 --- a/client/src/state/progress.ts +++ b/client/src/state/progress.ts @@ -1,15 +1,10 @@ +/** + * @fileOverview Defines the user progress which is loaded from the browser store and kept + */ import { createSlice } from '@reduxjs/toolkit' import type { PayloadAction } from '@reduxjs/toolkit' import { loadState } from "./local_storage"; -export interface GameProgressState { - [world: string] : {[level: number]: LevelProgressState} - -} - -interface ProgressState { - level: {[game: string]: GameProgressState} -} interface Selection { selectionStartLineNumber: number, selectionStartColumn: number, @@ -22,18 +17,29 @@ interface LevelProgressState { completed: boolean } -const initialProgressState = loadState() ?? { level: {} } as ProgressState -const initalLevelProgressState = {code: "", completed: false} as LevelProgressState +export interface GameProgressState { + [world: string] : {[level: number]: LevelProgressState} +} + +/** The progress made on all lean4-games */ +interface ProgressState { + games: {[game: string]: GameProgressState} +} -function addLevelProgress(state, action: PayloadAction<{game: string, world: string, level: number}>) { - if (!state.level[action.payload.game]) { - state.level[action.payload.game] = {} +const initialProgressState: ProgressState = loadState() ?? { games: {} } + +const initalLevelProgressState: LevelProgressState = {code: "", completed: false, selections: []} + +/** Add an empty skeleton with progress for the current level */ +function addLevelProgress(state: ProgressState, action: PayloadAction<{game: string, world: string, level: number}>) { + if (!state.games[action.payload.game]) { + state.games[action.payload.game] = {} } - if (!state.level[action.payload.game][action.payload.world]) { - state.level[action.payload.game][action.payload.world] = {} + if (!state.games[action.payload.game][action.payload.world]) { + state.games[action.payload.game][action.payload.world] = {} } - if (!state.level[action.payload.game][action.payload.world][action.payload.level]) { - state.level[action.payload.game][action.payload.world][action.payload.level] = {...initalLevelProgressState} + if (!state.games[action.payload.game][action.payload.world][action.payload.level]) { + state.games[action.payload.game][action.payload.world][action.payload.level] = {...initalLevelProgressState} } } @@ -41,60 +47,77 @@ export const progressSlice = createSlice({ name: 'progress', initialState: initialProgressState, reducers: { - codeEdited(state, action: PayloadAction<{game: string, world: string, level: number, code: string}>) { + /** put edited code in the state and set completed to false */ + codeEdited(state: ProgressState, action: PayloadAction<{game: string, world: string, level: number, code: string}>) { addLevelProgress(state, action) - state.level[action.payload.game][action.payload.world][action.payload.level].code = action.payload.code - state.level[action.payload.game][action.payload.world][action.payload.level].completed = false + state.games[action.payload.game][action.payload.world][action.payload.level].code = action.payload.code + state.games[action.payload.game][action.payload.world][action.payload.level].completed = false }, - changedSelection(state, action: PayloadAction<{game: string, world: string, level: number, selections: Selection[]}>) { + /** TODO: ? */ + changedSelection(state: ProgressState, action: PayloadAction<{game: string, world: string, level: number, selections: Selection[]}>) { addLevelProgress(state, action) - state.level[action.payload.game][action.payload.world][action.payload.level].selections = action.payload.selections + state.games[action.payload.game][action.payload.world][action.payload.level].selections = action.payload.selections }, - levelCompleted(state, action: PayloadAction<{game: string, world: string, level: number}>) { + /** mark level as completed */ + levelCompleted(state: ProgressState, action: PayloadAction<{game: string, world: string, level: number}>) { addLevelProgress(state, action) - state.level[action.payload.game][action.payload.world][action.payload.level].completed = true + state.games[action.payload.game][action.payload.world][action.payload.level].completed = true }, - deleteProgress(state, action: PayloadAction<{game: string}>) { - state.level[action.payload.game] = {} + /** delete all progress for this game */ + deleteProgress(state: ProgressState, action: PayloadAction<{game: string}>) { + state.games[action.payload.game] = {} + }, + /** delete progress for this level */ + deleteLevelProgress(state: ProgressState, action: PayloadAction<{game: string, world: string, level: number}>) { + addLevelProgress(state, action) + state.games[action.payload.game][action.payload.world][action.payload.level] = initalLevelProgressState }, - loadProgress(state, action: PayloadAction<{game: string, data:GameProgressState}>) { + /** load progress, e.g. from external import */ + loadProgress(state: ProgressState, action: PayloadAction<{game: string, data:GameProgressState}>) { console.debug(`setting data to:\n ${action.payload.data}`) - state.level[action.payload.game] = action.payload.data + state.games[action.payload.game] = action.payload.data }, } }) +/** if the level does not exist, return default values */ export function selectLevel(game: string, world: string, level: number) { return (state) =>{ - if (!state.progress.level[game]) { return initalLevelProgressState } - if (!state.progress.level[game][world]) { return initalLevelProgressState } - if (!state.progress.level[game][world][level]) { return initalLevelProgressState } - return state.progress.level[game][world][level] + if (!state.progress.games[game]) { return initalLevelProgressState } + if (!state.progress.games[game][world]) { return initalLevelProgressState } + if (!state.progress.games[game][world][level]) { return initalLevelProgressState } + return state.progress.games[game][world][level] } } +/** return the code of the current level */ export function selectCode(game: string, world: string, level: number) { return (state) => { return selectLevel(game, world, level)(state).code } } +/** return the selections made in the current level */ export function selectSelections(game: string, world: string, level: number) { return (state) => { return selectLevel(game, world, level)(state).selections } } +/** return whether the current level is clompleted */ export function selectCompleted(game: string, world: string, level: number) { return (state) => { return selectLevel(game, world, level)(state).completed } } +/** return progress for the current game if it exists */ export function selectProgress(game: string) { return (state) => { - return state.progress.level[game] ?? null + return state.progress.games[game] ?? null } } -export const { changedSelection, codeEdited, levelCompleted, deleteProgress, loadProgress } = progressSlice.actions +/** Export actions to modify the progress */ +export const { changedSelection, codeEdited, levelCompleted, deleteProgress, + deleteLevelProgress, loadProgress } = progressSlice.actions diff --git a/package-lock.json b/package-lock.json index 8ae9f0a..b5b034a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,6 +52,7 @@ "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10", "@redux-devtools/core": "^3.13.1", "@testing-library/react": "^13.4.0", + "@types/debounce": "^1.2.1", "babel-loader": "^8.3.0", "concurrently": "^7.6.0", "css-loader": "^6.7.3", @@ -3016,6 +3017,12 @@ "resolved": "https://registry.npmjs.org/@types/cytoscape/-/cytoscape-3.19.9.tgz", "integrity": "sha512-oqCx0ZGiBO0UESbjgq052vjDAy2X53lZpMrWqiweMpvVwKw/2IiYDdzPFK6+f4tMfdv9YKEM9raO5bAZc3UYBg==" }, + "node_modules/@types/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-epMsEE85fi4lfmJUH/89/iV/LI+F5CvNIvmgs5g5jYFPfhO2S/ae8WSsLOKWdwtoaZw9Q2IhJ4tQ5tFCcS/4HA==", + "dev": true + }, "node_modules/@types/debug": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", diff --git a/package.json b/package.json index 4636050..a1af0f5 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10", "@redux-devtools/core": "^3.13.1", "@testing-library/react": "^13.4.0", + "@types/debounce": "^1.2.1", "babel-loader": "^8.3.0", "concurrently": "^7.6.0", "css-loader": "^6.7.3", From 042bf37f1d07682a3d3254185dc7b86211931505 Mon Sep 17 00:00:00 2001 From: Jon Eugster Date: Wed, 5 Jul 2023 18:05:53 +0200 Subject: [PATCH 5/5] comments --- client/src/state/api.ts | 2 +- client/src/state/progress.ts | 1 + client/src/state/store.ts | 10 +++++++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/client/src/state/api.ts b/client/src/state/api.ts index 84d083f..022bf0e 100644 --- a/client/src/state/api.ts +++ b/client/src/state/api.ts @@ -1,5 +1,5 @@ /** - * @fileOverview + * @fileOverview Define API of the server-client communication */ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' import { Connection } from '../connection' diff --git a/client/src/state/progress.ts b/client/src/state/progress.ts index 137c571..b82fd5b 100644 --- a/client/src/state/progress.ts +++ b/client/src/state/progress.ts @@ -28,6 +28,7 @@ interface ProgressState { const initialProgressState: ProgressState = loadState() ?? { games: {} } +// TODO: There was some weird unreproducible bug with removing `as LevelProgressState` here... const initalLevelProgressState: LevelProgressState = {code: "", completed: false, selections: []} /** Add an empty skeleton with progress for the current level */ diff --git a/client/src/state/store.ts b/client/src/state/store.ts index 94746ce..4079ebd 100644 --- a/client/src/state/store.ts +++ b/client/src/state/store.ts @@ -1,3 +1,6 @@ +/** + * @fileOverview configure the store and save the state periodically to the browser storage +*/ import { configureStore } from '@reduxjs/toolkit'; import { debounce } from "debounce"; @@ -21,10 +24,11 @@ export const store = configureStore({ }).concat(apiSlice.middleware), }); -// Save progress in local storage +/** + * Save progress in local storage once each 800ms. + * This is for better performance when multiple changes occur in a short time + */ store.subscribe( - // we use debounce to save the state once each 800ms - // for better performances in case multiple changes occur in a short time debounce(() => { saveState(store.getState()[progressSlice.name]); }, 800)