make preferences work #179
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
+7
-2
@@ -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 && <> Info & Impressum</>}
|
{isDropdown && <> Impressum</>}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|
||||||
/** button to toggle iimpressum popup */
|
function PreferencesButton({setNavOpen, togglePreferencesPopup}) {
|
||||||
function LanguageButton({setNavOpen, toggleLangNav, isDropdown}) {
|
return <Button title="Preferences" inverted="true" to="" onClick={() => {togglePreferencesPopup(); setNavOpen(false)}}>
|
||||||
return <Button className="btn btn-inverted language-btn"
|
<FontAwesomeIcon icon={faGear} /> {t("Preferences")}
|
||||||
title="language" inverted="true" to="" onClick={(ev) => {toggleLangNav(ev); setNavOpen(false)}}>
|
</Button>
|
||||||
<ReactCountryFlag countryCode="GB"
|
}
|
||||||
className="emojiFlag"
|
|
||||||
style={{
|
function GameInfoButton({setNavOpen, toggleInfo}) {
|
||||||
height: '1.3em',
|
return <Button className="btn btn-inverted"
|
||||||
width: '1.6em',
|
title="Game Info & Credits" inverted="true" to="" onClick={() => {toggleInfo(); setNavOpen(false)}}>
|
||||||
}}
|
<FontAwesomeIcon icon={faCircleInfo} /> Game Info
|
||||||
title="English"
|
</Button>
|
||||||
/>
|
}
|
||||||
{isDropdown && <> Language</>}
|
|
||||||
|
function EraseButton ({setNavOpen, toggleEraseMenu}) {
|
||||||
|
return <Button title="Clear Progress" inverted="true" to="" onClick={() => {toggleEraseMenu(); setNavOpen(false)}}>
|
||||||
|
<FontAwesomeIcon icon={faEraser} /> 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} /> Download
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
|
||||||
|
function UploadButton ({setNavOpen, toggleUploadMenu}) {
|
||||||
|
return <Button title="Load Progress from JSON" inverted="true" to="" onClick={() => {toggleUploadMenu(); setNavOpen(false)}}>
|
||||||
|
<FontAwesomeIcon icon={faUpload} /> Upload
|
||||||
</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} /> <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} /> <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} /> 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} /> 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} /> Download
|
|
||||||
</Button>
|
|
||||||
<Button title="Load Progress from JSON" inverted="true" to="" onClick={() => {toggleUploadMenu(); setNavOpen(false)}}>
|
|
||||||
<FontAwesomeIcon icon={faUpload} /> Upload
|
|
||||||
</Button>
|
|
||||||
<Button title="Impressum, privacy policy" inverted="true" to="" onClick={() => {toggleImpressum(); setNavOpen(false)}}>
|
|
||||||
<FontAwesomeIcon icon={faCircleInfo} /> Impressum
|
|
||||||
</Button>
|
|
||||||
<Button title="Preferences" inverted="true" to="" onClick={() => {togglePreferencesPopup(); setNavOpen(false)}}>
|
|
||||||
<FontAwesomeIcon icon={faGear} /> 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 [impressum, setImpressum] = React.useState(false)
|
const {layout, isSavePreferences, language, setLayout, setIsSavePreferences, setLanguage} = React.useContext(PreferencesContext)
|
||||||
|
const gameId = React.useContext(GameIdContext)
|
||||||
|
const gameInfo = useGetGameInfoQuery({game: gameId})
|
||||||
|
|
||||||
const closeImpressum = () => {
|
// pop-ups
|
||||||
setImpressum(false)
|
const [impressum, setImpressum] = React.useState(false)
|
||||||
}
|
const [info, setInfo] = React.useState(false)
|
||||||
|
const [preferencesPopup, setPreferencesPopup] = React.useState(false)
|
||||||
|
|
||||||
|
function closeImpressum() {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';
|
||||||
|
|
||||||
@@ -11,102 +12,105 @@ import { IPreferencesContext } from "../infoview/context"
|
|||||||
import ReactCountryFlag from 'react-country-flag';
|
import ReactCountryFlag from 'react-country-flag';
|
||||||
|
|
||||||
interface PreferencesPopupProps extends Omit<IPreferencesContext, 'mobile'> {
|
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 = [
|
||||||
{
|
{
|
||||||
value: 0,
|
value: 0,
|
||||||
label: 'Mobile',
|
label: 'Mobile',
|
||||||
key: "mobile"
|
key: "mobile"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 1,
|
value: 1,
|
||||||
label: 'Auto',
|
label: 'Auto',
|
||||||
key: "auto"
|
key: "auto"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 2,
|
value: 2,
|
||||||
label: 'Desktop',
|
label: 'Desktop',
|
||||||
key: "desktop"
|
key: "desktop"
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const handlerChangeLayout = (_: Event, value: number) => {
|
const handlerChangeLayout = (_: Event, value: number) => {
|
||||||
setLayout(marks[value].key as IPreferencesContext["layout"])
|
setLayout(marks[value].key as IPreferencesContext["layout"])
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="modal-wrapper">
|
const handlerChangeLanguage = (ev: SelectChangeEvent<string>) => {
|
||||||
<div className="modal-backdrop" onClick={handleClose} />
|
setLanguage(ev.target.value as IPreferencesContext["language"])
|
||||||
<div className="modal">
|
}
|
||||||
<div className="codicon codicon-close modal-close" onClick={handleClose}></div>
|
|
||||||
<Typography variant="body1" component="div" className="settings">
|
|
||||||
<div className='preferences-category'>
|
|
||||||
<div className='category-title'>
|
|
||||||
<h3>Language</h3>
|
|
||||||
</div>
|
|
||||||
<div className='preferences-item first leave-left-gap'>
|
|
||||||
<FormControlLabel
|
|
||||||
control={
|
|
||||||
<Box sx={{ width: 300 }}>
|
|
||||||
<Select
|
|
||||||
value={'GB'}
|
|
||||||
label={"Language"}>
|
|
||||||
<MenuItem value={'GB'}>
|
|
||||||
<ReactCountryFlag countryCode="GB"/> English
|
|
||||||
</MenuItem>
|
|
||||||
</Select>
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
label=""
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='preferences-category'>
|
|
||||||
<div className='category-title'>
|
|
||||||
<h3>Layout</h3>
|
|
||||||
</div>
|
|
||||||
<div className='preferences-item first leave-left-gap'>
|
|
||||||
<FormControlLabel
|
|
||||||
control={
|
|
||||||
<Box sx={{ width: 300 }}>
|
|
||||||
<Slider
|
|
||||||
aria-label="Always visible"
|
|
||||||
value={marks.find(item => item.key === layout).value}
|
|
||||||
step={1}
|
|
||||||
marks={marks}
|
|
||||||
max={2}
|
|
||||||
sx={{
|
|
||||||
'& .MuiSlider-track': { display: 'none', },
|
|
||||||
}}
|
|
||||||
onChange={handlerChangeLayout}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
label=""
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='preferences-category tail-category'>
|
return <div className="modal-wrapper">
|
||||||
<div className='preferences-item'>
|
<div className="modal-backdrop" onClick={handleClose} />
|
||||||
<FormControlLabel
|
<div className="modal">
|
||||||
control={
|
<div className="codicon codicon-close modal-close" onClick={handleClose}></div>
|
||||||
<Switch
|
<Typography variant="body1" component="div" className="settings">
|
||||||
checked={isSavePreferences}
|
<div className='preferences-category'>
|
||||||
onChange={() => setIsSavePreferences(!isSavePreferences)}
|
<div className='category-title'>
|
||||||
name="checked"
|
<h3>Language</h3>
|
||||||
color="primary"
|
</div>
|
||||||
/>
|
<div className='preferences-item first leave-left-gap'>
|
||||||
}
|
<FormControlLabel
|
||||||
label="Save my settings (in the browser store)"
|
control={
|
||||||
labelPlacement="end"
|
<Box sx={{ width: 300 }}>
|
||||||
/>
|
<Select
|
||||||
</div>
|
value={language}
|
||||||
</div>
|
label={"Language"}
|
||||||
</Typography>
|
onChange={handlerChangeLanguage}>
|
||||||
|
{supportedLanguages.languages.map(lang => {return <MenuItem key={`menu-item-lang-${lang.iso}`} value={lang.iso}><ReactCountryFlag countryCode={lang.flag}/> {lang.name}</MenuItem>})}
|
||||||
|
</Select>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
label=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className='preferences-category'>
|
||||||
|
<div className='category-title'>
|
||||||
|
<h3>Layout</h3>
|
||||||
|
</div>
|
||||||
|
<div className='preferences-item first leave-left-gap'>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Box sx={{ width: 300 }}>
|
||||||
|
<Slider
|
||||||
|
aria-label="Always visible"
|
||||||
|
value={marks.find(item => item.key === layout).value}
|
||||||
|
step={1}
|
||||||
|
marks={marks}
|
||||||
|
max={2}
|
||||||
|
sx={{
|
||||||
|
'& .MuiSlider-track': { display: 'none', },
|
||||||
|
}}
|
||||||
|
onChange={handlerChangeLayout}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
label=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='preferences-category tail-category'>
|
||||||
|
<div className='preferences-item'>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
checked={isSavePreferences}
|
||||||
|
onChange={() => setIsSavePreferences(!isSavePreferences)}
|
||||||
|
name="checked"
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Save my settings (in the browser store)"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user