diff --git a/client/src/components/GameMenu.tsx b/client/src/components/GameMenu.tsx index 970049a..ab0d41b 100644 --- a/client/src/components/GameMenu.tsx +++ b/client/src/components/GameMenu.tsx @@ -3,14 +3,31 @@ import { Button } from './Button' import { GameIdContext } from '../App'; import { useStore } from 'react-redux'; import { useAppDispatch, useAppSelector } from '../hooks'; +import { useSelector } from 'react-redux'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faDownload, faUpload, faEraser } from '@fortawesome/free-solid-svg-icons' -import { deleteProgress } from '../state/progress'; +import { deleteProgress, selectProgress, loadProgress, GameProgressState } from '../state/progress'; + +const downloadFile = ({ data, fileName, fileType }) => { + const blob = new Blob([data], { type: fileType }) + const a = document.createElement('a') + a.download = fileName + a.href = window.URL.createObjectURL(blob) + const clickEvt = new MouseEvent('click', { + view: window, + bubbles: true, + cancelable: true, + }) + a.dispatchEvent(clickEvt) + a.remove() +} function GameMenu() { + const [file, setFile] = React.useState(); + const gameId = React.useContext(GameIdContext) const store = useStore() @@ -18,24 +35,60 @@ function GameMenu() { const openEraseMenu = () => setEraseMenu(true); const closeEraseMenu = () => setEraseMenu(false); + const [uploadMenu, setUploadMenu] = React.useState(false); + const openUploadMenu = () => setUploadMenu(true); + const closeUploadMenu = () => setUploadMenu(false); + + const gameProgress = useSelector(selectProgress(gameId)) + const dispatch = useAppDispatch() - const downloadProgress = () => {}; - // const uploadProgress = () => {}; + const downloadProgress = (e) => { + e.preventDefault() + downloadFile({ + data: JSON.stringify(gameProgress), + fileName: `lean4game-${gameId}-${new Date().toLocaleDateString()}.json`, + fileType: 'text/json', + }) + }; + + const handleFileChange = (e) => { + if (e.target.files) { + setFile(e.target.files[0]); + } + + }; + + const uploadProgress = (e) => { + if (!file) { + return; + } + + const fileReader = new FileReader(); + fileReader.readAsText(file, "UTF-8"); + fileReader.onload = (e) => { + const data = JSON.parse(e.target.result.toString()) as GameProgressState; + console.debug("Json Data", data); + + dispatch(loadProgress({game: gameId, data: data})) + } + + closeUploadMenu() + } const eraseProgress = () => { dispatch(deleteProgress({game: gameId})) closeEraseMenu() } - const downloadAndErase = () => { - downloadProgress () + const downloadAndErase = (e) => { + downloadProgress(e) eraseProgress() } return } diff --git a/client/src/components/welcome.css b/client/src/components/welcome.css index 9d07d01..93d4889 100644 --- a/client/src/components/welcome.css +++ b/client/src/components/welcome.css @@ -23,6 +23,10 @@ padding: 20px; } +i { + font-style: italic; +} + h1 { font-size: 2em; margin: .67em 0; @@ -203,3 +207,10 @@ svg .world-title { margin-right: .4em; margin-bottom: .2em; } + +.modal a.download-link { + cursor: pointer; + font-style: italic; + text-decoration: underline dotted; + +} diff --git a/client/src/state/api.ts b/client/src/state/api.ts index 3569fcb..151af3b 100644 --- a/client/src/state/api.ts +++ b/client/src/state/api.ts @@ -50,7 +50,7 @@ const customBaseQuery = async ( let leanClient = await connection.startLeanClient(args.game) console.log(`Sending request ${args.method}`) let res = await leanClient.sendRequest(args.method, args.params) - console.log('Received response', res) + console.log('Received response') //, res) return {'data': res} } catch (e) { return {'error': e} diff --git a/client/src/state/progress.ts b/client/src/state/progress.ts index ce43b4f..72816ff 100644 --- a/client/src/state/progress.ts +++ b/client/src/state/progress.ts @@ -2,8 +2,13 @@ import { createSlice } from '@reduxjs/toolkit' import type { PayloadAction } from '@reduxjs/toolkit' import { loadState } from "./localStorage"; +export interface GameProgressState { + [world: string] : {[level: number]: LevelProgressState} + +} + interface ProgressState { - level: {[game: string]: {[world: string]: {[level: number]: LevelProgressState}}} + level: {[game: string]: GameProgressState} } interface Selection { selectionStartLineNumber: number, @@ -52,6 +57,10 @@ export const progressSlice = createSlice({ deleteProgress(state, action: PayloadAction<{game: string}>) { state.level[action.payload.game] = {} }, + loadProgress(state, action: PayloadAction<{game: string, data:GameProgressState}>) { + console.debug(`setting data to:\n ${action.payload.data}`) + state.level[action.payload.game] = action.payload.data + }, } }) @@ -82,10 +91,10 @@ export function selectCompleted(game: string, world: string, level: number) { } } -export function selectProgress() { +export function selectProgress(game: string) { return (state) => { - return state.progress + return state.progress.level[game] ?? null } } -export const { changedSelection, codeEdited, levelCompleted, deleteProgress } = progressSlice.actions +export const { changedSelection, codeEdited, levelCompleted, deleteProgress, loadProgress } = progressSlice.actions