Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Basic User preferences #104

Merged
merged 5 commits into from
Feb 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 44 additions & 43 deletions src/header/components/ClearCacheDialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ const defaultState = {
};

export function ClearCacheDialog({isOpen, onClose}) {

const [state, setState] = useState(defaultState);

useEffect(() => {
Expand All @@ -66,46 +65,48 @@ export function ClearCacheDialog({isOpen, onClose}) {
const {anki, bunPro, wanikani} = state;
const error = [anki, bunPro, wanikani].filter(v => v).length === 0;

return <Dialog onClose={onClose} open={isOpen}>
<DialogTitle>Force Refresh</DialogTitle>
<DialogContent>
<FormControl
required
component="fieldset"
sx={{m: 3}}
variant="standard"
>
<FormHelperText>Which app should be refreshed?</FormHelperText>

<FormGroup>

<FormControlLabel
control={
<Checkbox checked={anki} onChange={handleChange} name="anki"/>
}
label="Anki"
/>
<FormControlLabel
control={
<Checkbox checked={bunPro} onChange={handleChange} name="bunPro"/>
}
label="BunPro"
/>
<FormControlLabel
control={
<Checkbox checked={wanikani} onChange={handleChange} name="wanikani"/>
}
label="Wanikani"
/>

</FormGroup>
</FormControl>

</DialogContent>
<DialogActions>
<Button onClick={onClose}>Cancel</Button>
<Button onClick={handleClear}
disabled={error}>Clear</Button>
</DialogActions>
</Dialog>;
return (
<Dialog onClose={onClose} open={isOpen}>
<DialogTitle>Force Refresh</DialogTitle>
<DialogContent>
<FormControl
required
component="fieldset"
sx={{m: 3}}
variant="standard"
>
<FormHelperText>Which app should be refreshed?</FormHelperText>

<FormGroup>

<FormControlLabel
control={
<Checkbox checked={anki} onChange={handleChange} name="anki"/>
}
label="Anki"
/>
<FormControlLabel
control={
<Checkbox checked={bunPro} onChange={handleChange} name="bunPro"/>
}
label="BunPro"
/>
<FormControlLabel
control={
<Checkbox checked={wanikani} onChange={handleChange} name="wanikani"/>
}
label="Wanikani"
/>

</FormGroup>
</FormControl>

</DialogContent>
<DialogActions>
<Button onClick={onClose}>Cancel</Button>
<Button onClick={handleClear}
disabled={error}>Clear</Button>
</DialogActions>
</Dialog>
);
}
31 changes: 26 additions & 5 deletions src/header/components/HeaderOptionMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import {useNavigate} from "react-router";
import {useBunProApiKey} from "../../hooks/useBunProApiKey.jsx";
import {useAppVersion} from "../../hooks/useAppVersion.jsx";
import {AppUrls} from "../../Constants.js";
import {Replay} from "@mui/icons-material";
import {AccountCircle, Replay} from "@mui/icons-material";
import {ClearCacheDialog} from "./ClearCacheDialog.jsx";
import UserPreferencesDialog from "./UserPreferencesDialog.jsx";

const iconPaddingRight = '7px';

Expand All @@ -23,6 +24,10 @@ const styles = {
color: '#5babf2',
marginRight: iconPaddingRight
},
preferencesOption: {
color: '#b0eaff',
marginRight: iconPaddingRight
},
versionText: {
fontSize: 'small',
color: 'gray',
Expand All @@ -41,7 +46,8 @@ const styles = {

function HeaderOptionMenu() {
const [anchorEl, setAnchorEl] = useState(null);
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [isClearCacheDialogOpen, setIsClearCacheDialogOpen] = useState(false);
const [isPreferencesDialogOpen, setIsPreferencesDialogOpen] = useState(false);
const open = Boolean(anchorEl);
const wanikaniApiKeyStore = useWanikaniApiKey();
const bunProApiKeyStore = useBunProApiKey();
Expand All @@ -59,7 +65,12 @@ function HeaderOptionMenu() {
}

const handleForceRefresh = () => {
setIsDialogOpen(true);
setIsClearCacheDialogOpen(true);
setAnchorEl(null);
}

const handleUserPreferences = () => {
setIsPreferencesDialogOpen(true);
setAnchorEl(null);
}

Expand Down Expand Up @@ -98,6 +109,11 @@ function HeaderOptionMenu() {
Force Refresh
</MenuItem>

<MenuItem onClick={handleUserPreferences}>
<AccountCircle fontSize={'small'} style={styles.preferencesOption}/>
Preferences
</MenuItem>

<MenuItem onClick={goToAboutPage} divider={true}>
<InfoIcon fontSize={'small'} style={{marginRight: iconPaddingRight}}/>
About
Expand All @@ -117,9 +133,14 @@ function HeaderOptionMenu() {

</Menu>

<ClearCacheDialog isOpen={isDialogOpen}
onClose={() => setIsDialogOpen(false)}
<ClearCacheDialog isOpen={isClearCacheDialogOpen}
onClose={() => setIsClearCacheDialogOpen(false)}
/>

<UserPreferencesDialog isOpen={isPreferencesDialogOpen}
onClose={() => setIsPreferencesDialogOpen(false)}
/>

</>
);
}
Expand Down
93 changes: 93 additions & 0 deletions src/header/components/UserPreferencesDialog.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import {
Button, Checkbox,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Tab,
Tabs, Tooltip, Typography
} from "@mui/material";
import React, {useState} from "react";
import {useUserPreferences} from "../../hooks/useUserPreferences.jsx";
import {HelpOutline} from "@mui/icons-material";

function WanikaniPreferences() {
const {wanikaniPreferences: preferences, updateWanikaniPreferences} = useUserPreferences();
return (
<>
<Typography variant={'h5'}>
Dashboard
</Typography>


<div style={{display: 'flex', alignItems: 'center'}}>
<Checkbox checked={preferences.showPreviousLevelByDefault}
size="small"
onClick={() => updateWanikaniPreferences({showPreviousLevelByDefault: !preferences.showPreviousLevelByDefault})}
/>
Show Previous Level by Default
<Tooltip
title={(
<>
If you have not completed all Radicals, Kanji, and Vocabulary on the previous level,
automatically select the 'Show Previous Level' checkbox.
</>
)}
>
<HelpOutline style={{paddingLeft: '5px'}} fontSize={'small'}/>
</Tooltip>
</div>


</>
);
}

function UserPreferencesDialog({isOpen, onClose}) {
const [tab, setTab] = useState(0);
return (
<Dialog onClose={onClose} open={isOpen} fullWidth={true} maxWidth={'md'}>
<DialogTitle>Preferences</DialogTitle>
<DialogContent>

<Tabs value={tab}
onChange={(_, i) => setTab(i)}
style={{marginBottom: '15px'}}
>
<Tab label="Anki" value={0}/>
<Tab label="BunPro" value={1}/>
<Tab label="Wanikani" value={2}/>
</Tabs>

<div>

{/* ANKI */}
{tab === 0 ? (
<>
No Anki Preferences are available yet
</>
) : null}

{/* BUNPRO */}
{tab === 1 ? (
<>
No BunPro Preferences are available yet
</>
) : null}

{/* WANIKANI */}
{tab === 2 ? (
<WanikaniPreferences/>
) : null}

</div>

</DialogContent>
<DialogActions>
<Button onClick={onClose} color={'primary'}>Close</Button>
</DialogActions>
</Dialog>
);
}

export default UserPreferencesDialog;
29 changes: 29 additions & 0 deletions src/hooks/useUserPreferences.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import create from 'zustand'
import {persist} from "zustand/middleware";

const globalDefaultPreferences = {};
const ankiDefaultPreferences = {};
const bunProDefaultPreferences = {};
const wanikaniDefaultPreferences = {
showPreviousLevelByDefault: true,
};

export const useUserPreferences = create(persist(
(set) => ({

globalPreferences: globalDefaultPreferences,
updateGlobalPreferences: (preferences) => set(() => ({globalPreferences: {...preferences}})),

ankiPreferences: ankiDefaultPreferences,
updateAnkiPreferences: (preferences) => set(() => ({ankiPreferences: {...preferences}})),

bunProPreferences: bunProDefaultPreferences,
updateBunProPreferences: (preferences) => set(() => ({bunProPreferences: {...preferences}})),

wanikaniPreferences: wanikaniDefaultPreferences,
updateWanikaniPreferences: (preferences) => set(() => ({wanikaniPreferences: {...preferences}})),

}),
{
name: 'user-preferences'
}));
22 changes: 22 additions & 0 deletions src/util/InFlightRequestManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
function InFlightRequestManager() {
let requests = {};

async function send(key, request) {
if (requests[key]) {
return await requests[key];
}
try {
let _request = request();
requests[key] = _request;
return await _request;
} finally {
delete request[key];
}
}

return {
send
};
}

export default InFlightRequestManager;
13 changes: 11 additions & 2 deletions src/wanikani/components/WanikaniActiveItemChart.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import WanikaniItemTile from "./WanikaniItemTile.jsx";
import {combineAssignmentAndSubject, isSubjectHidden} from "../service/WanikaniDataUtil.js";
import {getColorByWanikaniSubjectType} from "../service/WanikaniStyleUtil.js";
import {WanikaniColors} from "../../Constants.js";
import {useUserPreferences} from "../../hooks/useUserPreferences.jsx";

const defaultState = {
radicals: [],
Expand Down Expand Up @@ -124,22 +125,30 @@ function SubjectTile({subject}) {
}

function WanikaniLevelItemsChart({level}) {
const {wanikaniPreferences} = useUserPreferences();
const isFirstLoad = useRef(true);
const [isPreviousLevel, setIsPreviousLevel] = useState(true);
const [data, setData] = useState(defaultState);

useEffect(() => {
let isSubscribed = true;
const cleanUp = () => isSubscribed = false;

let _isFirstLoad = isFirstLoad.current
isFirstLoad.current = false;

if (_isFirstLoad && !wanikaniPreferences.showPreviousLevelByDefault) {
setIsPreviousLevel(false);
return cleanUp;
}

fetchData(level > 1 && isPreviousLevel ? level - 1 : level)
.then(d => {
if (!isSubscribed)
return;

if (_isFirstLoad &&
if (wanikaniPreferences.showPreviousLevelByDefault &&
_isFirstLoad &&
d.radicalsStarted === d.radicals.length &&
d.kanjiStarted === d.kanji.length &&
d.vocabularyStarted === d.vocabulary.length) {
Expand All @@ -151,7 +160,7 @@ function WanikaniLevelItemsChart({level}) {
})
.catch(console.error);

return () => isSubscribed = false;
return cleanUp;
}, [level, isPreviousLevel]);

return (
Expand Down
Loading