Skip to content

Commit

Permalink
Merge remote-tracking branch 'wzixiao/mobile-option' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
joneugster committed Dec 13, 2023
2 parents 5aa0764 + 9f692cc commit beacdcb
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 35 deletions.
26 changes: 22 additions & 4 deletions client/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,34 @@ import '@fontsource/roboto/700.css';
import './css/reset.css';
import './css/app.css';
import { MobileContext } from './components/infoview/context';
import { useWindowDimensions } from './window_width';
import { useMobile } from './hooks';
import { AUTO_SWITCH_THRESHOLD, getWindowDimensions} from './state/preferences';
import { connection } from './connection';


export const GameIdContext = React.createContext<string>(undefined);

function App() {
const { mobile, setMobile, lockMobile, setLockMobile } = useMobile();

const params = useParams()
const gameId = "g/" + params.owner + "/" + params.repo
const {width, height} = useWindowDimensions()
const [mobile, setMobile] = React.useState(width < 800)

const automaticallyAdjustLayout = () => {
const {width} = getWindowDimensions()
setMobile(width < AUTO_SWITCH_THRESHOLD)
}

React.useEffect(()=>{
if (!lockMobile){
void automaticallyAdjustLayout()
window.addEventListener('resize', automaticallyAdjustLayout)

return () => {
window.removeEventListener('resize', automaticallyAdjustLayout)
}
}
}, [lockMobile])

React.useEffect(() => {
connection.startLeanClient(gameId);
Expand All @@ -27,7 +45,7 @@ function App() {
return (
<div className="app">
<GameIdContext.Provider value={gameId}>
<MobileContext.Provider value={{mobile, setMobile}}>
<MobileContext.Provider value={{mobile, setMobile, lockMobile, setLockMobile}}>
<Outlet />
</MobileContext.Provider>
</GameIdContext.Provider>
Expand Down
12 changes: 8 additions & 4 deletions client/src/components/app_bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as React from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faDownload, faUpload, faEraser, faBook, faBookOpen, faGlobe, faHome,
faArrowRight, faArrowLeft, faXmark, faBars, faCode,
faCircleInfo, faTerminal } from '@fortawesome/free-solid-svg-icons'
faCircleInfo, faTerminal, faMobileScreenButton, faDesktop, faGear } from '@fortawesome/free-solid-svg-icons'
import { GameIdContext } from "../app"
import { InputModeContext, MobileContext, WorldLevelIdContext } from "./infoview/context"
import { GameInfo, useGetGameInfoQuery } from '../state/api'
Expand Down Expand Up @@ -150,18 +150,19 @@ function InventoryButton({pageNumber, setPageNumber}) {
}

/** the navigation bar on the welcome page */
export function WelcomeAppBar({pageNumber, setPageNumber, gameInfo, toggleImpressum, toggleEraseMenu, toggleUploadMenu, toggleInfo} : {
export function WelcomeAppBar({pageNumber, setPageNumber, gameInfo, toggleImpressum, toggleEraseMenu, toggleUploadMenu, toggleInfo, togglePreferencesPopup} : {
pageNumber: number,
setPageNumber: any,
gameInfo: GameInfo,
toggleImpressum: any,
toggleEraseMenu: any,
toggleUploadMenu: any,
toggleInfo: any
toggleInfo: any,
togglePreferencesPopup: () => void;
}) {
const gameId = React.useContext(GameIdContext)
const gameProgress = useAppSelector(selectProgress(gameId))
const {mobile} = React.useContext(MobileContext)
const {mobile, setMobile} = React.useContext(MobileContext)
const [navOpen, setNavOpen] = React.useState(false)

return <div className="app-bar">
Expand Down Expand Up @@ -194,6 +195,9 @@ export function WelcomeAppBar({pageNumber, setPageNumber, gameInfo, toggleImpres
<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>
}
Expand Down
12 changes: 9 additions & 3 deletions client/src/components/infoview/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,18 @@ export const ProofStateContext = React.createContext<{
setProofState: () => {},
})

export const MobileContext = React.createContext<{
export interface IMobileContext {
mobile : boolean,
setMobile: React.Dispatch<React.SetStateAction<Boolean>>,
}>({
mobile : false,
lockMobile: boolean,
setLockMobile: React.Dispatch<React.SetStateAction<Boolean>>,
}

export const MobileContext = React.createContext<IMobileContext>({
mobile: false,
setMobile: () => {},
lockMobile: false,
setLockMobile: () => {}
})

export const WorldLevelIdContext = React.createContext<{
Expand Down
55 changes: 55 additions & 0 deletions client/src/components/popup/preferences.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as React from 'react'
import { Input, Typography } from '@mui/material'
import Markdown from '../markdown'
import Switch from '@mui/material/Switch';
import FormControlLabel from '@mui/material/FormControlLabel';

import { IMobileContext } from "../infoview/context"

interface PreferencesPopupProps extends IMobileContext{
handleClose: () => void
}

export function PreferencesPopup({ mobile, setMobile, lockMobile, setLockMobile, handleClose }: PreferencesPopupProps) {
return <div className="modal-wrapper">
<div className="modal-backdrop" onClick={handleClose} />
<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>Mobile layout</h3>
</div>
<div className='preferences-item'>
<FormControlLabel
control={
<Switch
checked={mobile}
onChange={() => setMobile(!mobile)}
name="checked"
color="primary"
/>
}
label="Enable"
labelPlacement="start"
/>
</div>
<div className='preferences-item'>
<FormControlLabel
control={
<Switch
checked={!lockMobile}
onChange={() => setLockMobile(!lockMobile)}
name="checked"
color="primary"
/>
}
label="Auto"
labelPlacement="start"
/>
</div>
</div>
</Typography>
</div>
</div>
}
11 changes: 9 additions & 2 deletions client/src/components/welcome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { InfoPopup } from './popup/game_info'
import { PrivacyPolicyPopup } from './popup/privacy_policy'
import { RulesHelpPopup } from './popup/rules_help'
import { UploadPopup } from './popup/upload'
import { PreferencesPopup} from "./popup/preferences"
import { WorldTreePanel } from './world_tree'

import '../css/welcome.css'
Expand Down Expand Up @@ -63,7 +64,7 @@ function IntroductionPanel({introduction, setPageNumber}: {introduction: string,
/** main page of the game showing among others the tree of worlds/levels */
function Welcome() {
const gameId = React.useContext(GameIdContext)
const {mobile} = React.useContext(MobileContext)
const {mobile, setMobile, lockMobile, setLockMobile} = React.useContext(MobileContext)
const gameInfo = useGetGameInfoQuery({game: gameId})
const inventory = useLoadInventoryOverviewQuery({game: gameId})

Expand All @@ -77,15 +78,20 @@ function Welcome() {
const [info, setInfo] = React.useState(false)
const [rulesHelp, setRulesHelp] = React.useState(false)
const [uploadMenu, setUploadMenu] = React.useState(false)
const [preferencesPopup, setPreferencesPopup] = React.useState(false)

function closeEraseMenu() {setEraseMenu(false)}
function closeImpressum() {setImpressum(false)}
function closeInfo() {setInfo(false)}
function closeRulesHelp() {setRulesHelp(false)}
function closeUploadMenu() {setUploadMenu(false)}
function closePreferencesPopup() {setPreferencesPopup(false)}
function toggleEraseMenu() {setEraseMenu(!eraseMenu)}
function toggleImpressum() {setImpressum(!impressum)}
function toggleInfo() {setInfo(!info)}
function toggleUploadMenu() {setUploadMenu(!uploadMenu)}
function togglePreferencesPopup() {setPreferencesPopup(!preferencesPopup)}


// set the window title
useEffect(() => {
Expand All @@ -101,7 +107,7 @@ function Welcome() {
: <>
<WelcomeAppBar pageNumber={pageNumber} setPageNumber={setPageNumber} gameInfo={gameInfo.data} toggleImpressum={toggleImpressum}
toggleEraseMenu={toggleEraseMenu} toggleUploadMenu={toggleUploadMenu}
toggleInfo={toggleInfo} />
toggleInfo={toggleInfo} togglePreferencesPopup={togglePreferencesPopup}/>
<div className="app-content">
{ mobile ?
<div className="welcome mobile">
Expand All @@ -128,6 +134,7 @@ function Welcome() {
{eraseMenu? <ErasePopup handleClose={closeEraseMenu}/> : null}
{uploadMenu? <UploadPopup handleClose={closeUploadMenu}/> : null}
{info ? <InfoPopup info={gameInfo.data?.info} handleClose={closeInfo}/> : null}
{preferencesPopup ? <PreferencesPopup mobile={mobile} setMobile={setMobile} lockMobile={lockMobile} setLockMobile={setLockMobile} handleClose={closePreferencesPopup}/> : null}
</>
}

Expand Down
24 changes: 24 additions & 0 deletions client/src/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
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`
export const useAppDispatch: () => AppDispatch = useDispatch
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,
};
};
21 changes: 21 additions & 0 deletions client/src/state/local_storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,24 @@ export async function saveState(state: any) {
// Ignore
}
}

const PREFERENCES_KEY = "preferences"

/** Load from browser storage */
export function loadPreferences() {
try {
const serializedState = localStorage.getItem(PREFERENCES_KEY);
return JSON.parse(serializedState)
} catch (e) {
return undefined;
}
}

export function savePreferences(state: any) {
try {
const serializedState = JSON.stringify(state)
localStorage.setItem(PREFERENCES_KEY, serializedState);
} catch (e) {
// Ignore
}
}
37 changes: 37 additions & 0 deletions client/src/state/preferences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { createSlice } from "@reduxjs/toolkit";

import { loadPreferences } from "./local_storage";

interface PreferencesState {
mobile: boolean;
lockMobile: boolean;
}

export function getWindowDimensions() {
const {innerWidth: width, innerHeight: height } = window
return {width, height}
}

const { width } = getWindowDimensions()

export const AUTO_SWITCH_THRESHOLD = 800

const initialState: PreferencesState = loadPreferences() ?? {
mobile: width < AUTO_SWITCH_THRESHOLD,
lockMobile: false
}

export const preferencesSlice = createSlice({
name: "preferences",
initialState,
reducers: {
setMobile: (state, action) => {
state.mobile = action.payload;
},
setLockMobile: (state, action) => {
state.lockMobile = action.payload;
},
},
});

export const { setMobile, setLockMobile } = preferencesSlice.actions;
5 changes: 4 additions & 1 deletion client/src/state/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import { debounce } from "debounce";
import { connection } from '../connection'
import { apiSlice } from './api'
import { progressSlice } from './progress'
import { saveState } from "./local_storage";
import { preferencesSlice } from "./preferences"
import { saveState, savePreferences } from "./local_storage";


export const store = configureStore({
reducer: {
[apiSlice.reducerPath]: apiSlice.reducer,
[progressSlice.name]: progressSlice.reducer,
[preferencesSlice.name]: preferencesSlice.reducer,
},
// Make connection available in thunks:
middleware: getDefaultMiddleware =>
Expand All @@ -31,6 +33,7 @@ export const store = configureStore({
store.subscribe(
debounce(() => {
saveState(store.getState()[progressSlice.name]);
savePreferences(store.getState()[preferencesSlice.name]);
}, 800)
);

Expand Down
21 changes: 0 additions & 21 deletions client/src/window_width.tsx

This file was deleted.

0 comments on commit beacdcb

Please sign in to comment.