diff --git a/client/src/components/Infoview.tsx b/client/src/components/Infoview.tsx index 0ae02fa..a1d8d2c 100644 --- a/client/src/components/Infoview.tsx +++ b/client/src/components/Infoview.tsx @@ -6,13 +6,16 @@ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js' import * as ls from 'vscode-languageserver-protocol' import TacticState from './TacticState'; import { useLeanClient } from '../connection'; +import { useAppDispatch } from '../hooks'; +import { levelCompleted, selectCompleted } from '../state/progress'; // TODO: move into Lean 4 web function toLanguageServerPosition (pos: monaco.Position): ls.Position { return {line : pos.lineNumber - 1, character: pos.column - 1} } -function Infoview({ editor, editorApi } : {editor: monaco.editor.IStandaloneCodeEditor, editorApi: EditorApi}) { +function Infoview({ worldId, levelId, editor, editorApi } : {worldId: string, levelId: number, editor: monaco.editor.IStandaloneCodeEditor, editorApi: EditorApi}) { + const dispatch = useAppDispatch() const [rpcSession, setRpcSession] = useState() const [goals, setGoals] = useState(null) const [completed, setCompleted] = useState(false) @@ -74,7 +77,11 @@ function Infoview({ editor, editorApi } : {editor: monaco.editor.IStandaloneCode "position": pos }).then((res) => { // Check that there are no errors and no warnings - setCompleted(!res.some(({severity}) => severity <= 2)) + const completed = !res.some(({severity}) => severity <= 2) + if (completed) { + dispatch(levelCompleted({world: worldId, level: levelId})) + } + setCompleted(completed) setGlobalDiagnostics(res) }).catch((err) => { console.error(err) diff --git a/client/src/components/Level.tsx b/client/src/components/Level.tsx index 915aebb..144fc33 100644 --- a/client/src/components/Level.tsx +++ b/client/src/components/Level.tsx @@ -176,7 +176,7 @@ function Level() { - + diff --git a/client/src/components/Welcome.tsx b/client/src/components/Welcome.tsx index 01dd626..2caafd5 100644 --- a/client/src/components/Welcome.tsx +++ b/client/src/components/Welcome.tsx @@ -7,16 +7,28 @@ import '@fontsource/roboto/700.css'; import cytoscape, { LayoutOptions } from 'cytoscape' import klay from 'cytoscape-klay'; import { Link as RouterLink, useNavigate } from 'react-router-dom'; +import { useSelector } from 'react-redux'; cytoscape.use( klay ); -import { Box, Typography, Button, CircularProgress, Grid } from '@mui/material'; +import { Box, Typography, Button, CircularProgress, Grid, selectClasses } from '@mui/material'; import { useGetGameInfoQuery } from '../state/api'; import { Link } from 'react-router-dom'; import Markdown from './Markdown'; +import { selectCompleted } from '../state/progress'; +function LevelIcon({ worldId, levelId, position }) { + const completed = useSelector(selectCompleted(worldId,levelId)) + // TODO: relative positioning? + return ( + + + + ) +} + function Welcome() { const navigate = useNavigate(); @@ -51,9 +63,7 @@ function Welcome() { for (let i = 1; i <= gameInfo.data.worldSize[id]; i++) { svgElements.push( - - - + ) } } diff --git a/client/src/state/progress.ts b/client/src/state/progress.ts index fae1127..c91b8e6 100644 --- a/client/src/state/progress.ts +++ b/client/src/state/progress.ts @@ -2,29 +2,66 @@ import { createSlice } from '@reduxjs/toolkit' import type { PayloadAction } from '@reduxjs/toolkit' interface ProgressState { - code: {[world: string]: {[level: number]: string}} + level: {[world: string]: {[level: number]: LevelProgressState}} } -const initialState = { code: {} } as ProgressState +interface LevelProgressState { + code: string, + completed: boolean +} + +const initialProgressState = { level: {} } as ProgressState +const initalLevelProgressState = {code: "", completed: false} as LevelProgressState + +function addLevelProgress(state, action: PayloadAction<{world: string, level: number}>) { + if (!state.level[action.payload.world]) { + state.level[action.payload.world] = {} + } + if (!state.level[action.payload.world][action.payload.level]) { + state.level[action.payload.world][action.payload.level] = {...initalLevelProgressState} + } +} export const progressSlice = createSlice({ name: 'progress', - initialState, + initialState: initialProgressState, reducers: { codeEdited(state, action: PayloadAction<{world: string, level: number, code: string}>) { - if (!state.code[action.payload.world]) { - state.code[action.payload.world] = {} - } - state.code[action.payload.world][action.payload.level] = action.payload.code + addLevelProgress(state, action) + state.level[action.payload.world][action.payload.level].code = action.payload.code + state.level[action.payload.world][action.payload.level].completed = false + }, + levelCompleted(state, action: PayloadAction<{world: string, level: number}>) { + addLevelProgress(state, action) + state.level[action.payload.world][action.payload.level].completed = true }, } }) +export function selectLevel(world: string, level: number) { + return (state) =>{ + if (!state.progress.level[world]) { return initalLevelProgressState } + if (!state.progress.level[world][level]) { return initalLevelProgressState } + return state.progress.level[world][level] + } +} + export function selectCode(world: string, level: number) { return (state) => { - if (!state.progress.code[world]) { return undefined } - return state.progress.code[world][level]; + return selectLevel(world, level)(state).code + } +} + +export function selectCompleted(world: string, level: number) { + return (state) => { + return selectLevel(world, level)(state).completed + } +} + +export function selectProgress() { + return (state) => { + return state.progress } } -export const { codeEdited } = progressSlice.actions +export const { codeEdited, levelCompleted } = progressSlice.actions