diff --git a/client/src/components/inventory.tsx b/client/src/components/inventory.tsx index cff2c1b..c89e043 100644 --- a/client/src/components/inventory.tsx +++ b/client/src/components/inventory.tsx @@ -6,8 +6,9 @@ import { faLock, faLockOpen, faBook, faHammer, faBan } from '@fortawesome/free-s import { GameIdContext } from '../app'; import Markdown from './markdown'; import { useLoadDocQuery, InventoryTile, LevelInfo, InventoryOverview } from '../state/api'; -import { selectInventory } from '../state/progress'; +import { selectDifficulty, selectInventory } from '../state/progress'; import { store } from '../state/store'; +import { useSelector } from 'react-redux'; export function Inventory({levelInfo, openDoc, enableAll=false} : { @@ -50,6 +51,8 @@ function InventoryList({items, docType, openDoc, defaultTab=null, level=undefine const gameId = React.useContext(GameIdContext) + const difficulty = useSelector(selectDifficulty(gameId)) + const categorySet = new Set() for (let item of items) { categorySet.add(item.category) @@ -87,7 +90,7 @@ function InventoryList({items, docType, openDoc, defaultTab=null, level=undefine ).filter(item => ((tab ?? categories[0]) == item.category)).map((item, i) => { return {openDoc(item.name, docType)}} - name={item.name} displayName={item.displayName} locked={item.locked} + name={item.name} displayName={item.displayName} locked={difficulty > 0 ? item.locked : false} disabled={item.disabled} newly={item.new} enableAll={enableAll}/> }) } diff --git a/client/src/components/level.tsx b/client/src/components/level.tsx index 117b0ab..9332eea 100644 --- a/client/src/components/level.tsx +++ b/client/src/components/level.tsx @@ -20,7 +20,7 @@ import { InfoProvider } from 'lean4web/client/src/editor/infoview'; import 'lean4web/client/src/editor/infoview.css' import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js' import './level.css' -import { useStore } from 'react-redux'; +import { useSelector, useStore } from 'react-redux'; import { EditorContext, ConfigContext, ProgressContext, VersionContext } from '../../../node_modules/lean4-infoview/src/infoview/contexts'; import { EditorConnection, EditorEvents } from '../../../node_modules/lean4-infoview/src/infoview/editorConnection'; import { EventEmitter } from '../../../node_modules/lean4-infoview/src/infoview/event'; @@ -36,7 +36,7 @@ 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, helpEdited, selectHelp } from '../state/progress'; +import { changedSelection, codeEdited, selectCode, selectSelections, progressSlice, selectCompleted, helpEdited, selectHelp, selectDifficulty } from '../state/progress'; import { DualEditor } from './infoview/main' import { DeletedHint, DeletedHints, Hints } from './hints'; import { DeletedChatContext, InputModeContext, MonacoEditorContext, ProofContext, ProofStep, SelectionContext } from './infoview/context'; @@ -389,6 +389,9 @@ function LevelAppBar({isLoading, levelId, worldId, levelTitle, toggleImpressum}) const gameId = React.useContext(GameIdContext) const gameInfo = useGetGameInfoQuery({game: gameId}) + const difficulty = useSelector(selectDifficulty(gameId)) + const completed = useAppSelector(selectCompleted(gameId, worldId, levelId)) + const { commandLineMode, setCommandLineMode } = React.useContext(InputModeContext) return
@@ -414,7 +417,8 @@ function LevelAppBar({isLoading, levelId, worldId, levelTitle, toggleImpressum}) } {levelId < gameInfo.data?.worldSize[worldId] && - } diff --git a/client/src/components/welcome.css b/client/src/components/welcome.css index c57f6e9..802b03d 100644 --- a/client/src/components/welcome.css +++ b/client/src/components/welcome.css @@ -20,7 +20,9 @@ } .welcome-text { - padding: 20px; + padding-left: 20px; + padding-right: 20px; + padding-bottom: 20px; } i { @@ -91,6 +93,10 @@ svg .level:hover .level-title { opacity: 1; } +svg .disabled { + cursor: default; +} + /******************/ /* Privacy Button */ /******************/ diff --git a/client/src/components/welcome.tsx b/client/src/components/welcome.tsx index 35122bf..34265ea 100644 --- a/client/src/components/welcome.tsx +++ b/client/src/components/welcome.tsx @@ -4,17 +4,17 @@ import { Link } from 'react-router-dom'; import { useNavigate } from 'react-router-dom'; import { useSelector } from 'react-redux'; import Split from 'react-split' -import { Box, Typography, CircularProgress } from '@mui/material'; +import { Box, Typography, CircularProgress, Slider } from '@mui/material'; import cytoscape, { LayoutOptions } from 'cytoscape' import klay from 'cytoscape-klay'; import './welcome.css' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faGlobe, faHome, faCircleInfo, faArrowRight, faArrowLeft, faShield, faRotateLeft } from '@fortawesome/free-solid-svg-icons' import { GameIdContext } from '../app'; -import { selectCompleted } from '../state/progress'; +import { selectCompleted, selectDifficulty } from '../state/progress'; import { useGetGameInfoQuery, useLoadInventoryOverviewQuery } from '../state/api'; import Markdown from './markdown'; -import WorldSelectionMenu from './world_selection_menu'; +import WorldSelectionMenu, { WelcomeMenu } from './world_selection_menu'; import {PrivacyPolicy} from './privacy_policy'; import { Button } from './button'; import { Documentation, Inventory } from './inventory'; @@ -31,12 +31,17 @@ const padding = 2000 // padding of the graphic (on a different scale) function LevelIcon({ worldId, levelId, position, completed, available }) { const gameId = React.useContext(GameIdContext) + const difficulty = useSelector(selectDifficulty(gameId)) + const x = s * position.x + Math.sin(levelId * 2 * Math.PI / N) * (R + 1.2*r + 2.4*r*Math.floor((levelId - 1)/N)) const y = s * position.y - Math.cos(levelId * 2 * Math.PI / N) * (R + 1.2*r + 2.4*r*Math.floor((levelId - 1)/N)) + let levelDisabled = (difficulty >= 2 && !(available || completed)) + // TODO: relative positioning? return ( - + @@ -58,6 +63,8 @@ function Welcome() { const inventory = useLoadInventoryOverviewQuery({game: gameId}) + const difficulty = useSelector(selectDifficulty(gameId)) + // When clicking on an inventory item, the inventory is overlayed by the item's doc. // If this state is set to a pair `(name, type)` then the according doc will be open. const [inventoryDoc, setInventoryDoc] = useState<{name: string, type: string}>(null) @@ -137,9 +144,13 @@ function Welcome() { nextLevel = 0 } + let worldDisabled = (difficulty >= 2 && !(worldUnlocked || worldCompleted)) + // Draw the worlds svgElements.push( - +
- + {gameInfo.data?.introduction}
diff --git a/client/src/components/world_selection_menu.css b/client/src/components/world_selection_menu.css index 5a55220..48e6d54 100644 --- a/client/src/components/world_selection_menu.css +++ b/client/src/components/world_selection_menu.css @@ -9,3 +9,34 @@ margin-right: .4em; margin-bottom: .2em; } + + +.world-selection-menu .slider-wrap { + display: inline-block; + width: 100%; + /* min-width: 16em; */ + padding-left: 3em; + padding-right: 3em; + margin-left: auto; + margin-right: auto; +} + + +/* Test for mobile `title`s */ +@media (pointer: coarse), (hover: none) { + [title] { + position: relative; + display: inline-flex; + justify-content: center; + } + [title]:focus::after { + content: attr(title); + position: absolute; + top: 90%; + color: #000; + background-color: #fff; + border: 1px solid; + width: fit-content; + padding: 3px; + } +} diff --git a/client/src/components/world_selection_menu.tsx b/client/src/components/world_selection_menu.tsx index 39d4280..e75c695 100644 --- a/client/src/components/world_selection_menu.tsx +++ b/client/src/components/world_selection_menu.tsx @@ -4,14 +4,15 @@ import * as React from 'react' import { useStore, useSelector } from 'react-redux'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faDownload, faUpload, faEraser } from '@fortawesome/free-solid-svg-icons' +import { faDownload, faUpload, faEraser, faGlobe, faHome, faArrowLeft } from '@fortawesome/free-solid-svg-icons' import './world_selection_menu.css' import { Button } from './button' import { GameIdContext } from '../app'; import { useAppDispatch, useAppSelector } from '../hooks'; -import { deleteProgress, selectProgress, loadProgress, GameProgressState } from '../state/progress'; +import { deleteProgress, selectProgress, loadProgress, GameProgressState, selectDifficulty, changedDifficulty } from '../state/progress'; +import { Slider } from '@mui/material'; /** Only to specify the types for `downloadFile` */ interface downloadFileParam { @@ -35,12 +36,35 @@ const downloadFile = ({ data, fileName, fileType } : downloadFileParam) => { a.remove() } +//
+// + +/** The menu that is shown next to the world selection graph */ +export function WelcomeMenu() { + + function label(x : number) { + return x == 0 ? 'Easy' : x == 1 ? 'Explorer' : 'Strict' + } + + + return +} + /** The menu that is shown next to the world selection graph */ -function WorldSelectionMenu() { +export function WorldSelectionMenu() { const [file, setFile] = React.useState(); const gameId = React.useContext(GameIdContext) const store = useStore() + const difficulty = useSelector(selectDifficulty(gameId)) + /* state variables to toggle the pop-up menus */ const [eraseMenu, setEraseMenu] = React.useState(false); @@ -92,10 +116,34 @@ function WorldSelectionMenu() { eraseProgress() } + function label(x : number) { + return x == 0 ? 'Playground' : x == 1 ? 'Explorer' : 'Strict' + } + return