From 4039fa9467baae1fbcb1f0b88a81796469f2cae0 Mon Sep 17 00:00:00 2001 From: Jon Eugster Date: Wed, 5 Jul 2023 18:00:50 +0200 Subject: [PATCH] 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",