make preferences work #179

pull/205/head
Jon Eugster 2 years ago
parent 27c661f08f
commit 8d0493acb5

@ -36,7 +36,7 @@ module.exports = {
}, },
lngs: ['en','de'], lngs: ['en','de'],
ns: [], ns: [],
defaultLng: 'en', defaultLng: 'en-GB',
defaultNs: 'translation', defaultNs: 'translation',
defaultValue: (lng, ns, key) => { defaultValue: (lng, ns, key) => {
if (lng === 'en') { if (lng === 'en') {

@ -1,4 +1,5 @@
{ {
"Tactics": "Taktiken",
"Lean Game Server": "Lean-Spieleserver", "Lean Game Server": "Lean-Spieleserver",
"<p>Game rules determine if it is allowed to skip levels and if the games runs checks to only allow unlocked tactics and theorems in proofs.</p><1>Note: \"Unlocked\" tactics (or theorems) are determined by two things: The set of minimal tactics needed to solve a level, plus any tactics you unlocked in another level. That means if you unlock <1>simp</1> in a level, you can use it henceforth in any level.</1><p>The options are:</p>": "<p>Die Spielregeln bestimmen ob es erlaubt ist, Levels zu überspringen und ob das Spiel überprüft welche Taktiken und Theoreme freigeschaltet sind und nur diese im Beweis akzeptiert.</p><1>Bemerkung: \"Freigeschaltete\" Taktiken (und Theoreme) werden durch zwei Faktoren bestimmt: The Menge der Taktiken die minimal notwending sind um den Level zu lösen und dazu die Menge aller Taktiken, die in einem anderen Level freigeschaltet wurden. Das bedeutet wenn <1>simp</1> in einem Level freigeschaltet wird, kann diese Taktik danach in jeglichen Levels verwendet werden.", "<p>Game rules determine if it is allowed to skip levels and if the games runs checks to only allow unlocked tactics and theorems in proofs.</p><1>Note: \"Unlocked\" tactics (or theorems) are determined by two things: The set of minimal tactics needed to solve a level, plus any tactics you unlocked in another level. That means if you unlock <1>simp</1> in a level, you can use it henceforth in any level.</1><p>The options are:</p>": "<p>Die Spielregeln bestimmen ob es erlaubt ist, Levels zu überspringen und ob das Spiel überprüft welche Taktiken und Theoreme freigeschaltet sind und nur diese im Beweis akzeptiert.</p><1>Bemerkung: \"Freigeschaltete\" Taktiken (und Theoreme) werden durch zwei Faktoren bestimmt: The Menge der Taktiken die minimal notwending sind um den Level zu lösen und dazu die Menge aller Taktiken, die in einem anderen Level freigeschaltet wurden. Das bedeutet wenn <1>simp</1> in einem Level freigeschaltet wird, kann diese Taktik danach in jeglichen Levels verwendet werden.",
"Game Rules": "Spielregeln", "Game Rules": "Spielregeln",
@ -7,5 +8,5 @@
"regular": "regulär", "regular": "regulär",
"relaxed": "relaxed", "relaxed": "relaxed",
"none": "keine", "none": "keine",
"Rules": "Regend" "Rules": "Regeln"
} }

@ -1,4 +1,5 @@
{ {
"Tactics": "Tactics",
"Lean Game Server": "Lean Game Server", "Lean Game Server": "Lean Game Server",
"<p>Game rules determine if it is allowed to skip levels and if the games runs checks to only allow unlocked tactics and theorems in proofs.</p><1>Note: \"Unlocked\" tactics (or theorems) are determined by two things: The set of minimal tactics needed to solve a level, plus any tactics you unlocked in another level. That means if you unlock <1>simp</1> in a level, you can use it henceforth in any level.</1><p>The options are:</p>": "<p>Game rules determine if it is allowed to skip levels and if the games runs checks to only allow unlocked tactics and theorems in proofs.</p><1>Note: \"Unlocked\" tactics (or theorems) are determined by two things: The set of minimal tactics needed to solve a level, plus any tactics you unlocked in another level. That means if you unlock <1>simp</1> in a level, you can use it henceforth in any level.</1><p>The options are:</p>", "<p>Game rules determine if it is allowed to skip levels and if the games runs checks to only allow unlocked tactics and theorems in proofs.</p><1>Note: \"Unlocked\" tactics (or theorems) are determined by two things: The set of minimal tactics needed to solve a level, plus any tactics you unlocked in another level. That means if you unlock <1>simp</1> in a level, you can use it henceforth in any level.</1><p>The options are:</p>": "<p>Game rules determine if it is allowed to skip levels and if the games runs checks to only allow unlocked tactics and theorems in proofs.</p><1>Note: \"Unlocked\" tactics (or theorems) are determined by two things: The set of minimal tactics needed to solve a level, plus any tactics you unlocked in another level. That means if you unlock <1>simp</1> in a level, you can use it henceforth in any level.</1><p>The options are:</p>",
"Game Rules": "Game Rules", "Game Rules": "Game Rules",

@ -10,6 +10,7 @@ import './css/reset.css';
import './css/app.css'; import './css/app.css';
import { PreferencesContext} from './components/infoview/context'; import { PreferencesContext} from './components/infoview/context';
import UsePreferences from "./state/hooks/use_preferences" import UsePreferences from "./state/hooks/use_preferences"
import i18n from './i18n';
export const GameIdContext = React.createContext<string>(undefined); export const GameIdContext = React.createContext<string>(undefined);
@ -18,12 +19,16 @@ function App() {
const params = useParams() const params = useParams()
const gameId = "g/" + params.owner + "/" + params.repo const gameId = "g/" + params.owner + "/" + params.repo
const {mobile, layout, isSavePreferences, setLayout, setIsSavePreferences} = UsePreferences() const {mobile, layout, isSavePreferences, language, setLayout, setIsSavePreferences, setLanguage} = UsePreferences()
React.useEffect(() => {
i18n.changeLanguage(language)
}, [language])
return ( return (
<div className="app"> <div className="app">
<GameIdContext.Provider value={gameId}> <GameIdContext.Provider value={gameId}>
<PreferencesContext.Provider value={{mobile, layout, isSavePreferences, setLayout, setIsSavePreferences}}> <PreferencesContext.Provider value={{mobile, layout, isSavePreferences, language, setLayout, setIsSavePreferences, setLanguage}}>
<React.Suspense> <React.Suspense>
<Outlet /> <Outlet />
</React.Suspense> </React.Suspense>

@ -14,6 +14,7 @@ import { useAppDispatch, useAppSelector } from '../hooks'
import { Button } from './button' import { Button } from './button'
import { downloadProgress } from './popup/erase' import { downloadProgress } from './popup/erase'
import ReactCountryFlag from "react-country-flag" import ReactCountryFlag from "react-country-flag"
import { t } from 'i18next'
/** navigation buttons for mobile welcome page to switch between intro/tree/inventory. */ /** navigation buttons for mobile welcome page to switch between intro/tree/inventory. */
function MobileNavButtons({pageNumber, setPageNumber}: function MobileNavButtons({pageNumber, setPageNumber}:
@ -119,28 +120,46 @@ function InputModeButton({setNavOpen, isDropdown}) {
</Button> </Button>
} }
/** button to toggle iimpressum popup */ /** button to toggle iimpressum popup
*
* Note: Do not translate "Impressum"!
*/
function ImpressumButton({setNavOpen, toggleImpressum, isDropdown}) { function ImpressumButton({setNavOpen, toggleImpressum, isDropdown}) {
return <Button className="btn btn-inverted toggle-width" return <Button className="btn btn-inverted"
title="information, Impressum, privacy policy" inverted="true" to="" onClick={(ev) => {toggleImpressum(ev); setNavOpen(false)}}> title="information, Impressum, privacy policy" inverted="true" to="" onClick={(ev) => {toggleImpressum(ev); setNavOpen(false)}}>
<FontAwesomeIcon icon={faCircleInfo} /> <FontAwesomeIcon icon={faCircleInfo} />
{isDropdown && <>&nbsp;Info &amp; Impressum</>} {isDropdown && <>&nbsp;Impressum</>}
</Button>
}
function PreferencesButton({setNavOpen, togglePreferencesPopup}) {
return <Button title="Preferences" inverted="true" to="" onClick={() => {togglePreferencesPopup(); setNavOpen(false)}}>
<FontAwesomeIcon icon={faGear} />&nbsp;{t("Preferences")}
</Button>
}
function GameInfoButton({setNavOpen, toggleInfo}) {
return <Button className="btn btn-inverted"
title="Game Info & Credits" inverted="true" to="" onClick={() => {toggleInfo(); setNavOpen(false)}}>
<FontAwesomeIcon icon={faCircleInfo} />&nbsp;Game Info
</Button>
}
function EraseButton ({setNavOpen, toggleEraseMenu}) {
return <Button title="Clear Progress" inverted="true" to="" onClick={() => {toggleEraseMenu(); setNavOpen(false)}}>
<FontAwesomeIcon icon={faEraser} />&nbsp;Erase
</Button>
}
function DownloadButton ({setNavOpen, gameId, gameProgress}) {
return <Button title="Download Progress" inverted="true" to="" onClick={(ev) => {downloadProgress(gameId, gameProgress, ev); setNavOpen(false)}}>
<FontAwesomeIcon icon={faDownload} />&nbsp;Download
</Button> </Button>
} }
/** button to toggle iimpressum popup */ function UploadButton ({setNavOpen, toggleUploadMenu}) {
function LanguageButton({setNavOpen, toggleLangNav, isDropdown}) { return <Button title="Load Progress from JSON" inverted="true" to="" onClick={() => {toggleUploadMenu(); setNavOpen(false)}}>
return <Button className="btn btn-inverted language-btn" <FontAwesomeIcon icon={faUpload} />&nbsp;Upload
title="language" inverted="true" to="" onClick={(ev) => {toggleLangNav(ev); setNavOpen(false)}}>
<ReactCountryFlag countryCode="GB"
className="emojiFlag"
style={{
height: '1.3em',
width: '1.6em',
}}
title="English"
/>
{isDropdown && <>&nbsp;Language</>}
</Button> </Button>
} }
@ -153,6 +172,12 @@ function HomeButton({isDropdown}) {
</Button> </Button>
} }
function LandingPageButton() {
return <Button inverted="false" title="back to games selection" to="/">
<FontAwesomeIcon icon={faArrowLeft} />&nbsp;<FontAwesomeIcon icon={faGlobe} />
</Button>
}
/** button in mobile level to toggle inventory. /** button in mobile level to toggle inventory.
* only displays a button if `setPageNumber` is set. * only displays a button if `setPageNumber` is set.
*/ */
@ -184,9 +209,7 @@ export function WelcomeAppBar({pageNumber, setPageNumber, gameInfo, toggleImpres
return <div className="app-bar"> return <div className="app-bar">
<div className='app-bar-left'> <div className='app-bar-left'>
<Button inverted="false" title="back to games selection" to="/"> <LandingPageButton />
<FontAwesomeIcon icon={faArrowLeft} />&nbsp;<FontAwesomeIcon icon={faGlobe} />
</Button>
<span className="app-bar-title"></span> <span className="app-bar-title"></span>
</div> </div>
<div> <div>
@ -197,33 +220,23 @@ export function WelcomeAppBar({pageNumber, setPageNumber, gameInfo, toggleImpres
<MenuButton navOpen={navOpen} setNavOpen={setNavOpen} /> <MenuButton navOpen={navOpen} setNavOpen={setNavOpen} />
</div> </div>
<div className={'menu dropdown' + (navOpen ? '' : ' hidden')}> <div className={'menu dropdown' + (navOpen ? '' : ' hidden')}>
<Button title="Game Info & Credits" inverted="true" to="" onClick={() => {toggleInfo(); setNavOpen(false)}}> <GameInfoButton setNavOpen={setNavOpen} toggleInfo={toggleInfo}/>
<FontAwesomeIcon icon={faCircleInfo} />&nbsp;Game Info <EraseButton setNavOpen={setNavOpen} toggleEraseMenu={toggleEraseMenu}/>
</Button> <DownloadButton setNavOpen={setNavOpen} gameId={gameId} gameProgress={gameProgress}/>
<Button title="Clear Progress" inverted="true" to="" onClick={() => {toggleEraseMenu(); setNavOpen(false)}}> <UploadButton setNavOpen={setNavOpen} toggleUploadMenu={toggleUploadMenu}/>
<FontAwesomeIcon icon={faEraser} />&nbsp;Erase <ImpressumButton setNavOpen={setNavOpen} toggleImpressum={toggleImpressum} isDropdown={true} />
</Button> <PreferencesButton setNavOpen={setNavOpen} togglePreferencesPopup={togglePreferencesPopup}/>
<Button title="Download Progress" inverted="true" to="" onClick={(ev) => {downloadProgress(gameId, gameProgress, ev); setNavOpen(false)}}>
<FontAwesomeIcon icon={faDownload} />&nbsp;Download
</Button>
<Button title="Load Progress from JSON" inverted="true" to="" onClick={() => {toggleUploadMenu(); setNavOpen(false)}}>
<FontAwesomeIcon icon={faUpload} />&nbsp;Upload
</Button>
<Button title="Impressum, privacy policy" inverted="true" to="" onClick={() => {toggleImpressum(); setNavOpen(false)}}>
<FontAwesomeIcon icon={faCircleInfo} />&nbsp;Impressum
</Button>
<Button title="Preferences" inverted="true" to="" onClick={() => {togglePreferencesPopup(); setNavOpen(false)}}>
<FontAwesomeIcon icon={faGear} />&nbsp;Preferences
</Button>
</div> </div>
</div> </div>
} }
/** the navigation bar in a level */ /** the navigation bar in a level */
export function LevelAppBar({isLoading, levelTitle, toggleImpressum, pageNumber=undefined, setPageNumber=undefined} : { export function LevelAppBar({isLoading, levelTitle, toggleImpressum, toggleInfo, togglePreferencesPopup, pageNumber=undefined, setPageNumber=undefined} : {
isLoading: boolean, isLoading: boolean,
levelTitle: string, levelTitle: string,
toggleImpressum: any, toggleImpressum: any,
toggleInfo: any,
togglePreferencesPopup: any,
pageNumber?: number, pageNumber?: number,
setPageNumber?: any, setPageNumber?: any,
}) { }) {
@ -253,15 +266,16 @@ export function LevelAppBar({isLoading, levelTitle, toggleImpressum, pageNumber=
<PreviousButton setNavOpen={setNavOpen} /> <PreviousButton setNavOpen={setNavOpen} />
<HomeButton isDropdown={true} /> <HomeButton isDropdown={true} />
<InputModeButton setNavOpen={setNavOpen} isDropdown={true}/> <InputModeButton setNavOpen={setNavOpen} isDropdown={true}/>
<GameInfoButton setNavOpen={setNavOpen} toggleInfo={toggleInfo}/>
<ImpressumButton setNavOpen={setNavOpen} toggleImpressum={toggleImpressum} isDropdown={true} /> <ImpressumButton setNavOpen={setNavOpen} toggleImpressum={toggleImpressum} isDropdown={true} />
<LanguageButton setNavOpen={setNavOpen} toggleLangNav={() => {}} isDropdown={true} /> <PreferencesButton setNavOpen={setNavOpen} togglePreferencesPopup={togglePreferencesPopup}/>
</div> </div>
</> : </> :
<> <>
{/* DESKTOP VERSION */} {/* DESKTOP VERSION */}
<div className='app-bar-left'> <div className='app-bar-left'>
<HomeButton isDropdown={false} /> <HomeButton isDropdown={false} />
<span className="app-bar-title">{worldTitle && `World: ${worldTitle}`}</span> <span className="app-bar-title">{worldTitle && `${t("World")}: ${worldTitle}`}</span>
</div> </div>
<div> <div>
<span className="app-bar-title">{levelTitle}</span> <span className="app-bar-title">{levelTitle}</span>
@ -270,8 +284,12 @@ export function LevelAppBar({isLoading, levelTitle, toggleImpressum, pageNumber=
<PreviousButton setNavOpen={setNavOpen} /> <PreviousButton setNavOpen={setNavOpen} />
<NextButton worldSize={gameInfo.data?.worldSize[worldId]} difficulty={difficulty} completed={completed} setNavOpen={setNavOpen} /> <NextButton worldSize={gameInfo.data?.worldSize[worldId]} difficulty={difficulty} completed={completed} setNavOpen={setNavOpen} />
<InputModeButton setNavOpen={setNavOpen} isDropdown={false}/> <InputModeButton setNavOpen={setNavOpen} isDropdown={false}/>
<ImpressumButton setNavOpen={setNavOpen} toggleImpressum={toggleImpressum} isDropdown={false} /> <MenuButton navOpen={navOpen} setNavOpen={setNavOpen}/>
<LanguageButton setNavOpen={setNavOpen} toggleLangNav={() => {}} isDropdown={false} /> </div>
<div className={'menu dropdown' + (navOpen ? '' : ' hidden')}>
<GameInfoButton setNavOpen={setNavOpen} toggleInfo={toggleInfo}/>
<ImpressumButton setNavOpen={setNavOpen} toggleImpressum={toggleImpressum} isDropdown={true} />
<PreferencesButton setNavOpen={setNavOpen} togglePreferencesPopup={togglePreferencesPopup}/>
</div> </div>
</> </>
} }

@ -79,15 +79,18 @@ export interface ProofStateProps {
export interface IPreferencesContext extends PreferencesState{ export interface IPreferencesContext extends PreferencesState{
mobile: boolean, // The variables that actually control the page 'layout' can only be changed through layout. mobile: boolean, // The variables that actually control the page 'layout' can only be changed through layout.
setLayout: React.Dispatch<React.SetStateAction<PreferencesState["layout"]>>; setLayout: React.Dispatch<React.SetStateAction<PreferencesState["layout"]>>;
setIsSavePreferences: React.Dispatch<React.SetStateAction<Boolean>>; setIsSavePreferences: React.Dispatch<React.SetStateAction<PreferencesState["isSavePreferences"]>>;
setLanguage: React.Dispatch<React.SetStateAction<PreferencesState["language"]>>;
} }
export const PreferencesContext = React.createContext<IPreferencesContext>({ export const PreferencesContext = React.createContext<IPreferencesContext>({
mobile: false, mobile: false,
layout: "auto", layout: "auto",
isSavePreferences: false, isSavePreferences: false,
language: "en",
setLayout: () => {}, setLayout: () => {},
setIsSavePreferences: () => {} setIsSavePreferences: () => {},
setLanguage: () => {},
}) })
export const WorldLevelIdContext = React.createContext<{ export const WorldLevelIdContext = React.createContext<{

@ -10,6 +10,7 @@ import { useLoadDocQuery, InventoryTile, LevelInfo, InventoryOverview, useLoadIn
import { selectDifficulty, selectInventory } from '../state/progress'; import { selectDifficulty, selectInventory } from '../state/progress';
import { store } from '../state/store'; import { store } from '../state/store';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { t } from 'i18next';
export function Inventory({levelInfo, openDoc, lemmaTab, setLemmaTab, enableAll=false} : export function Inventory({levelInfo, openDoc, lemmaTab, setLemmaTab, enableAll=false} :
{ {
@ -24,7 +25,7 @@ export function Inventory({levelInfo, openDoc, lemmaTab, setLemmaTab, enableAll=
<div className="inventory"> <div className="inventory">
{/* TODO: Click on Tactic: show info {/* TODO: Click on Tactic: show info
TODO: click on paste icon -> paste into command line */} TODO: click on paste icon -> paste into command line */}
<h2>Tactics</h2> <h2>{t("Tactics")}</h2>
{levelInfo?.tactics && {levelInfo?.tactics &&
<InventoryList items={levelInfo?.tactics} docType="Tactic" openDoc={openDoc} enableAll={enableAll}/> <InventoryList items={levelInfo?.tactics} docType="Tactic" openDoc={openDoc} enableAll={enableAll}/>
} }

@ -51,6 +51,8 @@ import { IConnectionProvider } from 'monaco-languageclient'
import { monacoSetup } from 'lean4web/client/src/monacoSetup' import { monacoSetup } from 'lean4web/client/src/monacoSetup'
import { onigasmH } from 'onigasm/lib/onigasmH' import { onigasmH } from 'onigasm/lib/onigasmH'
import { isLastStepWithErrors, lastStepHasErrors } from './infoview/goals' import { isLastStepWithErrors, lastStepHasErrors } from './infoview/goals'
import { InfoPopup } from './popup/game_info'
import { PreferencesPopup } from './popup/preferences'
monacoSetup() monacoSetup()
@ -60,17 +62,29 @@ function Level() {
const levelId = parseInt(params.levelId) const levelId = parseInt(params.levelId)
const worldId = params.worldId const worldId = params.worldId
const {layout, isSavePreferences, language, setLayout, setIsSavePreferences, setLanguage} = React.useContext(PreferencesContext)
const gameId = React.useContext(GameIdContext)
const gameInfo = useGetGameInfoQuery({game: gameId})
// pop-ups
const [impressum, setImpressum] = React.useState(false) const [impressum, setImpressum] = React.useState(false)
const [info, setInfo] = React.useState(false)
const [preferencesPopup, setPreferencesPopup] = React.useState(false)
const closeImpressum = () => { function closeImpressum() {setImpressum(false)}
setImpressum(false) function closeInfo() {setInfo(false)}
} function closePreferencesPopup() {setPreferencesPopup(false)}
function toggleImpressum() {setImpressum(!impressum)}
function toggleInfo() {setInfo(!info)}
function togglePreferencesPopup() {setPreferencesPopup(!preferencesPopup)}
return <WorldLevelIdContext.Provider value={{worldId, levelId}}> return <WorldLevelIdContext.Provider value={{worldId, levelId}}>
{levelId == 0 ? {levelId == 0 ?
<Introduction impressum={impressum} setImpressum={setImpressum} /> : <Introduction impressum={impressum} setImpressum={setImpressum} toggleInfo={toggleInfo} togglePreferencesPopup={togglePreferencesPopup} /> :
<PlayableLevel key={`${worldId}/${levelId}`} impressum={impressum} setImpressum={setImpressum} />} <PlayableLevel key={`${worldId}/${levelId}`} impressum={impressum} setImpressum={setImpressum} toggleInfo={toggleInfo} togglePreferencesPopup={togglePreferencesPopup}/>}
{impressum ? <PrivacyPolicyPopup handleClose={closeImpressum} /> : null} {impressum ? <PrivacyPolicyPopup handleClose={closeImpressum} /> : null}
{info ? <InfoPopup info={gameInfo.data?.info} handleClose={closeInfo}/> : null}
{preferencesPopup ? <PreferencesPopup layout={layout} isSavePreferences={isSavePreferences} setLayout={setLayout} setIsSavePreferences={setIsSavePreferences} handleClose={closePreferencesPopup} language={language} setLanguage={setLanguage}/> : null}
</WorldLevelIdContext.Provider> </WorldLevelIdContext.Provider>
} }
@ -190,7 +204,7 @@ function ExercisePanel({codeviewRef, visible=true}: {codeviewRef: React.MutableR
</div> </div>
} }
function PlayableLevel({impressum, setImpressum}) { function PlayableLevel({impressum, setImpressum, toggleInfo, togglePreferencesPopup}) {
const codeviewRef = useRef<HTMLDivElement>(null) const codeviewRef = useRef<HTMLDivElement>(null)
const gameId = React.useContext(GameIdContext) const gameId = React.useContext(GameIdContext)
const {worldId, levelId} = useContext(WorldLevelIdContext) const {worldId, levelId} = useContext(WorldLevelIdContext)
@ -396,7 +410,10 @@ function PlayableLevel({impressum, setImpressum}) {
isLoading={level.isLoading} isLoading={level.isLoading}
levelTitle={`${mobile ? '' : 'Level '}${levelId} / ${gameInfo.data?.worldSize[worldId]}` + levelTitle={`${mobile ? '' : 'Level '}${levelId} / ${gameInfo.data?.worldSize[worldId]}` +
(level?.data?.title && ` : ${level?.data?.title}`)} (level?.data?.title && ` : ${level?.data?.title}`)}
toggleImpressum={toggleImpressum} /> toggleImpressum={toggleImpressum}
toggleInfo={toggleInfo}
togglePreferencesPopup={togglePreferencesPopup}
/>
{mobile? {mobile?
// TODO: This is copied from the `Split` component below... // TODO: This is copied from the `Split` component below...
<> <>
@ -452,7 +469,7 @@ function IntroductionPanel({gameInfo}) {
export default Level export default Level
/** The site with the introduction text of a world */ /** The site with the introduction text of a world */
function Introduction({impressum, setImpressum}) { function Introduction({impressum, setImpressum, toggleInfo, togglePreferencesPopup}) {
const gameId = React.useContext(GameIdContext) const gameId = React.useContext(GameIdContext)
const {mobile} = useContext(PreferencesContext) const {mobile} = useContext(PreferencesContext)
@ -470,7 +487,7 @@ function Introduction({impressum, setImpressum}) {
} }
return <> return <>
<LevelAppBar isLoading={gameInfo.isLoading} levelTitle="Introduction" toggleImpressum={toggleImpressum}/> <LevelAppBar isLoading={gameInfo.isLoading} levelTitle="Introduction" toggleImpressum={toggleImpressum} toggleInfo={toggleInfo} togglePreferencesPopup={togglePreferencesPopup}/>
{gameInfo.isLoading ? {gameInfo.isLoading ?
<div className="app-content loading"><CircularProgress /></div> <div className="app-content loading"><CircularProgress /></div>
: mobile ? : mobile ?

@ -0,0 +1,14 @@
{
"languages": [
{
"iso": "en",
"flag": "GB",
"name": "English"
},
{
"iso": "de",
"flag": "DE",
"name": "Deutsch"
}
]
}

@ -1,9 +1,10 @@
import * as React from 'react' import * as React from 'react'
import { Input, MenuItem, Select, Typography } from '@mui/material' import { Input, MenuItem, Select, SelectChangeEvent, Typography } from '@mui/material'
import Markdown from '../markdown' import Markdown from '../markdown'
import { Switch, Button, ButtonGroup } from '@mui/material'; import { Switch, Button, ButtonGroup } from '@mui/material';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Slider from '@mui/material/Slider'; import Slider from '@mui/material/Slider';
import supportedLanguages from './language_config.json'
import FormControlLabel from '@mui/material/FormControlLabel'; import FormControlLabel from '@mui/material/FormControlLabel';
@ -14,7 +15,7 @@ interface PreferencesPopupProps extends Omit<IPreferencesContext, 'mobile'> {
handleClose: () => void handleClose: () => void
} }
export function PreferencesPopup({ layout, setLayout, isSavePreferences, setIsSavePreferences, handleClose }: PreferencesPopupProps) { export function PreferencesPopup({ layout, setLayout, isSavePreferences, language, setIsSavePreferences, handleClose, setLanguage }: PreferencesPopupProps) {
const marks = [ const marks = [
{ {
@ -38,6 +39,10 @@ export function PreferencesPopup({ layout, setLayout, isSavePreferences, setIsSa
setLayout(marks[value].key as IPreferencesContext["layout"]) setLayout(marks[value].key as IPreferencesContext["layout"])
} }
const handlerChangeLanguage = (ev: SelectChangeEvent<string>) => {
setLanguage(ev.target.value as IPreferencesContext["language"])
}
return <div className="modal-wrapper"> return <div className="modal-wrapper">
<div className="modal-backdrop" onClick={handleClose} /> <div className="modal-backdrop" onClick={handleClose} />
<div className="modal"> <div className="modal">
@ -52,11 +57,10 @@ export function PreferencesPopup({ layout, setLayout, isSavePreferences, setIsSa
control={ control={
<Box sx={{ width: 300 }}> <Box sx={{ width: 300 }}>
<Select <Select
value={'GB'} value={language}
label={"Language"}> label={"Language"}
<MenuItem value={'GB'}> onChange={handlerChangeLanguage}>
<ReactCountryFlag countryCode="GB"/>&nbsp;English {supportedLanguages.languages.map(lang => {return <MenuItem key={`menu-item-lang-${lang.iso}`} value={lang.iso}><ReactCountryFlag countryCode={lang.flag}/>&nbsp;{lang.name}</MenuItem>})}
</MenuItem>
</Select> </Select>
</Box> </Box>
} }

@ -65,7 +65,7 @@ function IntroductionPanel({introduction, setPageNumber}: {introduction: string,
function Welcome() { function Welcome() {
const gameId = React.useContext(GameIdContext) const gameId = React.useContext(GameIdContext)
const {mobile} = React.useContext(PreferencesContext) const {mobile} = React.useContext(PreferencesContext)
const {layout, isSavePreferences, setLayout, setIsSavePreferences} = React.useContext(PreferencesContext) const {layout, isSavePreferences, language, setLayout, setIsSavePreferences, setLanguage} = React.useContext(PreferencesContext)
const gameInfo = useGetGameInfoQuery({game: gameId}) const gameInfo = useGetGameInfoQuery({game: gameId})
const inventory = useLoadInventoryOverviewQuery({game: gameId}) const inventory = useLoadInventoryOverviewQuery({game: gameId})
@ -135,7 +135,7 @@ function Welcome() {
{eraseMenu? <ErasePopup handleClose={closeEraseMenu}/> : null} {eraseMenu? <ErasePopup handleClose={closeEraseMenu}/> : null}
{uploadMenu? <UploadPopup handleClose={closeUploadMenu}/> : null} {uploadMenu? <UploadPopup handleClose={closeUploadMenu}/> : null}
{info ? <InfoPopup info={gameInfo.data?.info} handleClose={closeInfo}/> : null} {info ? <InfoPopup info={gameInfo.data?.info} handleClose={closeInfo}/> : null}
{preferencesPopup ? <PreferencesPopup layout={layout} isSavePreferences={isSavePreferences} setLayout={setLayout} setIsSavePreferences={setIsSavePreferences} handleClose={closePreferencesPopup}/> : null} {preferencesPopup ? <PreferencesPopup layout={layout} isSavePreferences={isSavePreferences} setLayout={setLayout} setIsSavePreferences={setIsSavePreferences} handleClose={closePreferencesPopup} language={language} setLanguage={setLanguage}/> : null}
</> </>
} }

@ -4,6 +4,7 @@ import {
PreferencesState, PreferencesState,
setLayout as setPreferencesLayout, setLayout as setPreferencesLayout,
setIsSavePreferences as setPreferencesIsSavePreferences, setIsSavePreferences as setPreferencesIsSavePreferences,
setLanguage as setLanguagePreferences,
getWindowDimensions, getWindowDimensions,
AUTO_SWITCH_THRESHOLD AUTO_SWITCH_THRESHOLD
} from "../preferences"; } from "../preferences";
@ -19,6 +20,9 @@ const UsePreferences = () => {
const isSavePreferences = useAppSelector((state) => state.preferences.isSavePreferences); const isSavePreferences = useAppSelector((state) => state.preferences.isSavePreferences);
const setIsSavePreferences = (isSave: boolean) => dispatch(setPreferencesIsSavePreferences(isSave)) const setIsSavePreferences = (isSave: boolean) => dispatch(setPreferencesIsSavePreferences(isSave))
const language = useAppSelector((state) => state.preferences.language);
const setLanguage = (lang: string) => dispatch(setLanguagePreferences(lang))
const automaticallyAdjustLayout = () => { const automaticallyAdjustLayout = () => {
const {width} = getWindowDimensions() const {width} = getWindowDimensions()
setMobile(width < AUTO_SWITCH_THRESHOLD) setMobile(width < AUTO_SWITCH_THRESHOLD)
@ -35,7 +39,7 @@ const UsePreferences = () => {
} }
}, [layout]) }, [layout])
return {mobile, layout, isSavePreferences, setLayout, setIsSavePreferences} return {mobile, layout, isSavePreferences, language, setLayout, setIsSavePreferences, setLanguage}
} }
export default UsePreferences; export default UsePreferences;

@ -5,6 +5,7 @@ import { loadPreferences, removePreferences, savePreferences } from "./local_sto
export interface PreferencesState { export interface PreferencesState {
layout: "mobile" | "auto" | "desktop"; layout: "mobile" | "auto" | "desktop";
isSavePreferences: boolean; isSavePreferences: boolean;
language: string;
} }
export function getWindowDimensions() { export function getWindowDimensions() {
@ -16,7 +17,8 @@ export const AUTO_SWITCH_THRESHOLD = 800
const initialState: PreferencesState = loadPreferences() ??{ const initialState: PreferencesState = loadPreferences() ??{
layout: "auto", layout: "auto",
isSavePreferences: false isSavePreferences: false,
language: "en",
} }
export const preferencesSlice = createSlice({ export const preferencesSlice = createSlice({
@ -29,7 +31,10 @@ export const preferencesSlice = createSlice({
setIsSavePreferences: (state, action) => { setIsSavePreferences: (state, action) => {
state.isSavePreferences = action.payload; state.isSavePreferences = action.payload;
}, },
setLanguage: (state, action) => {
state.language = action.payload;
},
}, },
}); });
export const { setLayout, setIsSavePreferences } = preferencesSlice.actions; export const { setLayout, setIsSavePreferences, setLanguage } = preferencesSlice.actions;

Loading…
Cancel
Save