Modify logic for all preferences

pull/181/head
ran 2 years ago
parent cd1d212a3c
commit 8c93b3c5b3

@ -8,39 +8,48 @@ import '@fontsource/roboto/700.css';
import './css/reset.css'; import './css/reset.css';
import './css/app.css'; import './css/app.css';
import { MobileContext } from './components/infoview/context'; import { MobileContext, PreferencesContext} from './components/infoview/context';
import { useMobile } from './hooks'; import { AUTO_SWITCH_THRESHOLD, getWindowDimensions, setLayout, setisSavePreferences, PreferencesState} from './state/preferences';
import { AUTO_SWITCH_THRESHOLD, getWindowDimensions} from './state/preferences'; import { useAppDispatch, useAppSelector } from './hooks';
export const GameIdContext = React.createContext<string>(undefined); export const GameIdContext = React.createContext<string>(undefined);
function App() { function App() {
const { mobile, setMobile, lockMobile, setLockMobile } = useMobile(); const dispatch = useAppDispatch()
const params = useParams() const params = useParams()
const gameId = "g/" + params.owner + "/" + params.repo const gameId = "g/" + params.owner + "/" + params.repo
// TODO:
const [mobile, setMobile] = React.useState<boolean>()
const layout = useAppSelector((state) => state.preferences.layout);
const changeLayout = (layout: PreferencesState["layout"]) => dispatch(setLayout(layout))
const isSavePreferences = useAppSelector((state) => state.preferences.isSavePreferences);
const changeIsSavePreferences = (isSave: boolean) => dispatch(setisSavePreferences(isSave))
const automaticallyAdjustLayout = () => { const automaticallyAdjustLayout = () => {
const {width} = getWindowDimensions() const {width} = getWindowDimensions()
setMobile(width < AUTO_SWITCH_THRESHOLD) setMobile(width < AUTO_SWITCH_THRESHOLD)
} }
React.useEffect(()=>{ React.useEffect(()=>{
if (!lockMobile){ if (layout === "auto"){
void automaticallyAdjustLayout() void automaticallyAdjustLayout()
window.addEventListener('resize', automaticallyAdjustLayout) window.addEventListener('resize', automaticallyAdjustLayout)
return () => { return () => window.removeEventListener('resize', automaticallyAdjustLayout)
window.removeEventListener('resize', automaticallyAdjustLayout) } else {
} setMobile(layout === "mobile")
} }
}, [lockMobile]) }, [layout])
return ( return (
<div className="app"> <div className="app">
<GameIdContext.Provider value={gameId}> <GameIdContext.Provider value={gameId}>
<MobileContext.Provider value={{mobile, setMobile, lockMobile, setLockMobile}}> <MobileContext.Provider value={{mobile, setMobile}}>
<Outlet /> <PreferencesContext.Provider value={{layout, isSavePreferences, setLayout: changeLayout, setIsSavePreferences: changeIsSavePreferences}}>
<Outlet />
</PreferencesContext.Provider>
</MobileContext.Provider> </MobileContext.Provider>
</GameIdContext.Provider> </GameIdContext.Provider>
</div> </div>

@ -5,6 +5,7 @@ import * as React from 'react';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js' import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'
import { InteractiveDiagnostic, InteractiveTermGoal } from '@leanprover/infoview-api'; import { InteractiveDiagnostic, InteractiveTermGoal } from '@leanprover/infoview-api';
import { GameHint, InteractiveGoal, InteractiveGoals } from './rpc_api'; import { GameHint, InteractiveGoal, InteractiveGoals } from './rpc_api';
import { PreferencesState } from '../../state/preferences';
export const MonacoEditorContext = React.createContext<monaco.editor.IStandaloneCodeEditor>( export const MonacoEditorContext = React.createContext<monaco.editor.IStandaloneCodeEditor>(
null as any) null as any)
@ -62,18 +63,26 @@ export const ProofStateContext = React.createContext<{
setProofState: () => {}, setProofState: () => {},
}) })
export interface IPreferencesContext extends PreferencesState{
setLayout: React.Dispatch<React.SetStateAction<PreferencesState["layout"]>>;
setIsSavePreferences: React.Dispatch<React.SetStateAction<Boolean>>;
}
export const PreferencesContext = React.createContext<IPreferencesContext>({
layout: "auto",
isSavePreferences: false,
setLayout: () => {},
setIsSavePreferences: () => {}
})
export interface IMobileContext { export interface IMobileContext {
mobile : boolean, mobile : boolean,
setMobile: React.Dispatch<React.SetStateAction<Boolean>>, setMobile: React.Dispatch<React.SetStateAction<Boolean>>,
lockMobile: boolean,
setLockMobile: React.Dispatch<React.SetStateAction<Boolean>>,
} }
export const MobileContext = React.createContext<IMobileContext>({ export const MobileContext = React.createContext<IMobileContext>({
mobile: false, mobile: false,
setMobile: () => {}, setMobile: () => {},
lockMobile: false,
setLockMobile: () => {}
}) })
export const WorldLevelIdContext = React.createContext<{ export const WorldLevelIdContext = React.createContext<{

@ -1,16 +1,20 @@
import * as React from 'react' import * as React from 'react'
import { Input, Typography } from '@mui/material' import { Input, Typography } from '@mui/material'
import Markdown from '../markdown' import Markdown from '../markdown'
import Switch from '@mui/material/Switch'; import { Switch, Button, ButtonGroup } from '@mui/material';
import FormControlLabel from '@mui/material/FormControlLabel'; import FormControlLabel from '@mui/material/FormControlLabel';
import { IMobileContext } from "../infoview/context" import { IMobileContext } from "../infoview/context"
import { PreferencesState } from "../../state/preferences"
interface PreferencesPopupProps extends IMobileContext{ interface PreferencesPopupProps extends PreferencesState{
handleClose: () => void handleClose: () => void,
} setLayout: (layout: "mobile" | "auto" | "desktop") => void,
setIsSavePreferences: (isSave: boolean) => void
}
export function PreferencesPopup({ mobile, setMobile, lockMobile, setLockMobile, handleClose }: PreferencesPopupProps) { export function PreferencesPopup({ layout, setLayout, isSavePreferences, setIsSavePreferences, handleClose }: PreferencesPopupProps) {
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">
@ -18,34 +22,35 @@ export function PreferencesPopup({ mobile, setMobile, lockMobile, setLockMobile,
<Typography variant="body1" component="div" className="settings"> <Typography variant="body1" component="div" className="settings">
<div className='preferences-category'> <div className='preferences-category'>
<div className='category-title'> <div className='category-title'>
<h3>Mobile layout</h3> <h3>Layout</h3>
</div> </div>
<div className='preferences-item'> <div className='preferences-item first leave-left-gap'>
<FormControlLabel <FormControlLabel
control={ control={
<Switch <ButtonGroup aria-label="outlined primary button group">
checked={mobile} <Button onClick={() => setLayout("mobile")} variant={layout === "mobile" ? "contained" : "outlined"}>Mobile</Button>
onChange={() => setMobile(!mobile)} <Button onClick={() => setLayout("auto")} variant={layout === "auto" ? "contained" : "outlined"}>Auto</Button>
name="checked" <Button onClick={() => setLayout("desktop")} variant={layout === "desktop" ? "contained" : "outlined"}>Desktop</Button>
color="primary" </ButtonGroup>
/>
} }
label="Enable" label=""
labelPlacement="start"
/> />
</div> </div>
</div>
<div className='preferences-category tail-category'>
<div className='preferences-item'> <div className='preferences-item'>
<FormControlLabel <FormControlLabel
control={ control={
<Switch <Switch
checked={!lockMobile} checked={isSavePreferences}
onChange={() => setLockMobile(!lockMobile)} onChange={() => setIsSavePreferences(!isSavePreferences)}
name="checked" name="checked"
color="primary" color="primary"
/> />
} }
label="Auto" label="Save my settings (in the browser store)"
labelPlacement="start" labelPlacement="end"
/> />
</div> </div>
</div> </div>

@ -10,7 +10,7 @@ import { useAppDispatch, useAppSelector } from '../hooks'
import { changedOpenedIntro, selectOpenedIntro } from '../state/progress' import { changedOpenedIntro, selectOpenedIntro } from '../state/progress'
import { useGetGameInfoQuery, useLoadInventoryOverviewQuery } from '../state/api' import { useGetGameInfoQuery, useLoadInventoryOverviewQuery } from '../state/api'
import { Button } from './button' import { Button } from './button'
import { MobileContext } from './infoview/context' import { MobileContext, PreferencesContext } from './infoview/context'
import { InventoryPanel } from './inventory' import { InventoryPanel } from './inventory'
import { ErasePopup } from './popup/erase' import { ErasePopup } from './popup/erase'
import { InfoPopup } from './popup/game_info' import { InfoPopup } from './popup/game_info'
@ -64,7 +64,9 @@ function IntroductionPanel({introduction, setPageNumber}: {introduction: string,
/** main page of the game showing among others the tree of worlds/levels */ /** main page of the game showing among others the tree of worlds/levels */
function Welcome() { function Welcome() {
const gameId = React.useContext(GameIdContext) const gameId = React.useContext(GameIdContext)
const {mobile, setMobile, lockMobile, setLockMobile} = React.useContext(MobileContext) const {mobile, setMobile} = React.useContext(MobileContext)
const {layout, isSavePreferences, setLayout, setIsSavePreferences} = React.useContext(PreferencesContext)
const gameInfo = useGetGameInfoQuery({game: gameId}) const gameInfo = useGetGameInfoQuery({game: gameId})
const inventory = useLoadInventoryOverviewQuery({game: gameId}) const inventory = useLoadInventoryOverviewQuery({game: gameId})
@ -134,7 +136,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 mobile={mobile} setMobile={setMobile} lockMobile={lockMobile} setLockMobile={setLockMobile} handleClose={closePreferencesPopup}/> : null} {preferencesPopup ? <PreferencesPopup layout={layout} isSavePreferences={isSavePreferences} setLayout={setLayout} setIsSavePreferences={setIsSavePreferences} handleClose={closePreferencesPopup}/> : null}
</> </>
} }

@ -11,11 +11,12 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faXmark, faCircleQuestion } from '@fortawesome/free-solid-svg-icons' import { faXmark, faCircleQuestion } from '@fortawesome/free-solid-svg-icons'
import { GameIdContext } from '../app' import { GameIdContext } from '../app'
import { useAppDispatch, useMobile } from '../hooks' import { useAppDispatch } from '../hooks'
import { selectDifficulty, changedDifficulty, selectCompleted } from '../state/progress' import { selectDifficulty, changedDifficulty, selectCompleted } from '../state/progress'
import { store } from '../state/store' import { store } from '../state/store'
import '../css/world_tree.css' import '../css/world_tree.css'
import { MobileContext } from './infoview/context'
// Settings for the world tree // Settings for the world tree
cytoscape.use( klay ) cytoscape.use( klay )
@ -197,7 +198,7 @@ export function WorldSelectionMenu({rulesHelp, setRulesHelp}) {
const gameId = React.useContext(GameIdContext) const gameId = React.useContext(GameIdContext)
const difficulty = useSelector(selectDifficulty(gameId)) const difficulty = useSelector(selectDifficulty(gameId))
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const { mobile } = useMobile() const { mobile } = React.useContext(MobileContext)
function label(x : number) { function label(x : number) {

@ -187,3 +187,15 @@ h5, h6 {
margin-left: 0.3rem; margin-left: 0.3rem;
margin-right: 0.3rem; margin-right: 0.3rem;
} }
.preferences-category.tail-category{
margin-top: 2em;
}
.preferences-item.first{
margin-top: 1em;
}
.preferences-item.leave-left-gap{
margin-left: 1em;
}

@ -1,30 +1,6 @@
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux' import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './state/store' import type { RootState, AppDispatch } from './state/store'
import { setMobile as setMobileState, setLockMobile as setLockMobileState} from "./state/preferences"
// Use throughout your app instead of plain `useDispatch` and `useSelector` // Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
export const useMobile = () => {
const dispatch = useAppDispatch();
const mobile = useAppSelector((state) => state.preferences.mobile);
const lockMobile = useAppSelector((state) => state.preferences.lockMobile);
const setMobile = (val: boolean) => {
dispatch(setMobileState(val));
};
const setLockMobile = (val: boolean) => {
dispatch(setLockMobileState(val));
};
return {
mobile,
setMobile,
lockMobile,
setLockMobile,
};
};

@ -57,3 +57,12 @@ export function savePreferences(state: any) {
// Ignore // Ignore
} }
} }
export function removePreferences() {
try {
localStorage.removeItem(PREFERENCES_KEY);
} catch (e) {
// Ignore
}
}

@ -1,10 +1,10 @@
import { createSlice } from "@reduxjs/toolkit"; import { createSlice } from "@reduxjs/toolkit";
import { loadPreferences } from "./local_storage"; import { loadPreferences, removePreferences, savePreferences } from "./local_storage";
interface PreferencesState { export interface PreferencesState {
mobile: boolean; layout: "mobile" | "auto" | "desktop";
lockMobile: boolean; isSavePreferences: boolean;
} }
export function getWindowDimensions() { export function getWindowDimensions() {
@ -12,26 +12,25 @@ export function getWindowDimensions() {
return {width, height} return {width, height}
} }
const { width } = getWindowDimensions()
export const AUTO_SWITCH_THRESHOLD = 800 export const AUTO_SWITCH_THRESHOLD = 800
const initialState: PreferencesState = loadPreferences() ?? { const initialState: PreferencesState = loadPreferences() ??{
mobile: width < AUTO_SWITCH_THRESHOLD, layout: "auto",
lockMobile: false isSavePreferences: false
} }
export const preferencesSlice = createSlice({ export const preferencesSlice = createSlice({
name: "preferences", name: "preferences",
initialState, initialState,
reducers: { reducers: {
setMobile: (state, action) => { setLayout: (state, action) => {
state.mobile = action.payload; state.layout = action.payload;
}, },
setLockMobile: (state, action) => { setisSavePreferences: (state, action) => {
state.lockMobile = action.payload; state.isSavePreferences = action.payload;
action.payload ? savePreferences(state) : removePreferences()
}, },
}, },
}); });
export const { setMobile, setLockMobile } = preferencesSlice.actions; export const { setLayout, setisSavePreferences } = preferencesSlice.actions;

@ -8,7 +8,7 @@ import { connection } from '../connection'
import { apiSlice } from './api' import { apiSlice } from './api'
import { progressSlice } from './progress' import { progressSlice } from './progress'
import { preferencesSlice } from "./preferences" import { preferencesSlice } from "./preferences"
import { saveState, savePreferences } from "./local_storage"; import { saveState, savePreferences, removePreferences} from "./local_storage";
export const store = configureStore({ export const store = configureStore({
@ -29,7 +29,9 @@ export const store = configureStore({
store.subscribe( store.subscribe(
debounce(() => { debounce(() => {
saveState(store.getState()[progressSlice.name]); saveState(store.getState()[progressSlice.name]);
savePreferences(store.getState()[preferencesSlice.name]);
const preferencesState= store.getState()[preferencesSlice.name]
preferencesState.isSavePreferences ? savePreferences(preferencesState) : removePreferences()
}, 800) }, 800)
); );

Loading…
Cancel
Save