add checkbox status to problems, closes #56
This commit is contained in:
parent
dfaf9d11b6
commit
afd24c474b
|
@ -25,6 +25,10 @@ export class Problem {
|
|||
public difficulty: 'Very Easy' | 'Easy' | 'Normal' | 'Hard' | 'Very Hard' | 'Insane';
|
||||
public isIntro: boolean;
|
||||
|
||||
get uniqueID() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
constructor(
|
||||
public source: string,
|
||||
public name: string,
|
||||
|
|
|
@ -2,10 +2,10 @@ import './src/styles/main.css';
|
|||
import './src/styles/anchor.css';
|
||||
import * as React from 'react';
|
||||
import MDXProvider from './src/components/markdown/MDXProvider';
|
||||
import { UserSettingsProvider } from './src/context/UserSettingsContext';
|
||||
import { UserDataProvider } from './src/context/UserDataContext';
|
||||
|
||||
export const wrapRootElement = ({ element }) => (
|
||||
<MDXProvider>
|
||||
<UserSettingsProvider>{element}</UserSettingsProvider>
|
||||
<UserDataProvider>{element}</UserDataProvider>
|
||||
</MDXProvider>
|
||||
);
|
||||
|
|
|
@ -27,7 +27,7 @@ export const plugins = [
|
|||
resolve: `gatsby-remark-autolink-headers`,
|
||||
options: {
|
||||
// icon source: https://joshwcomeau.com/
|
||||
icon: `<svg fill="none" height="24" width="24" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle;"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>`,
|
||||
icon: `<svg fill="none" height="24" width="24" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" style="display: inline-block; vertical-align: middle;"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>`,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -53,7 +53,7 @@ export const plugins = [
|
|||
resolve: `gatsby-remark-autolink-headers`,
|
||||
options: {
|
||||
// icon source: https://joshwcomeau.com/
|
||||
icon: `<svg fill="none" height="24" width="24" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle;"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>`,
|
||||
icon: `<svg fill="none" height="24" width="24" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" style="display: inline-block; vertical-align: middle;"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>`,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import * as React from 'react';
|
||||
import MDXProvider from './src/components/markdown/MDXProvider';
|
||||
import { UserSettingsProvider } from './src/context/UserSettingsContext';
|
||||
import { UserDataProvider } from './src/context/UserDataContext';
|
||||
|
||||
export const wrapRootElement = ({ element }) => (
|
||||
<MDXProvider>
|
||||
<UserSettingsProvider>{element}</UserSettingsProvider>
|
||||
<UserDataProvider>{element}</UserDataProvider>
|
||||
</MDXProvider>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as React from 'react';
|
||||
import Transition from '../Transition';
|
||||
import { ModuleProgressOptions } from '../../context/UserSettingsContext';
|
||||
import { ModuleProgressOptions } from '../../context/UserDataContext';
|
||||
|
||||
const MarkCompleteButton = ({
|
||||
state,
|
||||
|
|
|
@ -14,7 +14,7 @@ import ContactUsSlideover from '../ContactUsSlideover';
|
|||
import MarkCompleteButton from './MarkCompleteButton';
|
||||
import ModuleConfetti from './ModuleConfetti';
|
||||
import TextTooltip from '../tooltip/TextTooltip';
|
||||
import UserSettingsContext from '../../context/UserSettingsContext';
|
||||
import UserDataContext, { UserLang } from '../../context/UserDataContext';
|
||||
import { NavLinkGroup, SidebarNav } from './SidebarNav/SidebarNav';
|
||||
import { graphqlToModuleLinks } from '../../utils';
|
||||
import ModuleLayoutContext from '../../context/ModuleLayoutContext';
|
||||
|
@ -114,21 +114,19 @@ const SidebarBottomButtons = ({ onContactUs }) => {
|
|||
java: 'Java',
|
||||
py: 'Python',
|
||||
};
|
||||
const nextLang = {
|
||||
const nextLang: { [key: string]: UserLang } = {
|
||||
showAll: 'cpp',
|
||||
cpp: 'java',
|
||||
java: 'py',
|
||||
py: 'cpp',
|
||||
};
|
||||
const userSettings = useContext(UserSettingsContext);
|
||||
const userSettings = useContext(UserDataContext);
|
||||
return (
|
||||
<>
|
||||
<div className="flex-shrink-0 border-t border-gray-200 flex">
|
||||
<button
|
||||
className="group flex-1 flex items-center p-4 text-sm leading-5 font-medium text-gray-600 hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:bg-gray-100 transition ease-in-out duration-150"
|
||||
onClick={() =>
|
||||
userSettings.setPrimaryLang(nextLang[userSettings.primaryLang])
|
||||
}
|
||||
onClick={() => userSettings.setLang(nextLang[userSettings.lang])}
|
||||
>
|
||||
<svg
|
||||
className="mr-4 h-6 w-6 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500 transition ease-in-out duration-150"
|
||||
|
@ -141,7 +139,7 @@ const SidebarBottomButtons = ({ onContactUs }) => {
|
|||
>
|
||||
<path d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
||||
</svg>
|
||||
Language: {languages[userSettings.primaryLang]}
|
||||
Language: {languages[userSettings.lang]}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-shrink-0 border-t border-gray-200 flex">
|
||||
|
@ -270,9 +268,7 @@ export default function ModuleLayout({
|
|||
module: ModuleInfo;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { userProgress, setModuleProgress, primaryLang } = useContext(
|
||||
UserSettingsContext
|
||||
);
|
||||
const { userProgress, setModuleProgress, lang } = useContext(UserDataContext);
|
||||
const [isMobileNavOpen, setIsMobileNavOpen] = useState(false);
|
||||
const [isContactUsActive, setIsContactUsActive] = useState(false);
|
||||
const [isConfettiActive, setIsConfettiActive] = useState(false);
|
||||
|
@ -280,7 +276,7 @@ export default function ModuleLayout({
|
|||
(userProgress && userProgress[module.id]) || 'Not Started';
|
||||
|
||||
const tableOfContents =
|
||||
primaryLang in module.toc ? module.toc[primaryLang] : module.toc['cpp'];
|
||||
lang in module.toc ? module.toc[lang] : module.toc['cpp'];
|
||||
|
||||
const data = useStaticQuery(graphql`
|
||||
query {
|
||||
|
@ -444,7 +440,7 @@ export default function ModuleLayout({
|
|||
>
|
||||
<div className="mx-auto">
|
||||
<div className="flex justify-center">
|
||||
<div className="flex-1 max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex-1 max-w-4xl px-4 sm:px-6 lg:px-8 w-0">
|
||||
<div className="hidden lg:block">
|
||||
<NavBar />
|
||||
</div>
|
||||
|
@ -531,7 +527,7 @@ export default function ModuleLayout({
|
|||
<NavBar alignNavButtonsRight={false} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden xl:block ml-6 w-64 mt-48">
|
||||
<div className="hidden xl:block ml-6 w-64 mt-48 flex-shrink-0">
|
||||
<TableOfContentsSidebar tableOfContents={tableOfContents} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -5,7 +5,7 @@ import styled from 'styled-components';
|
|||
import tw from 'twin.macro';
|
||||
import { useContext } from 'react';
|
||||
import ModuleLayoutContext from '../../../context/ModuleLayoutContext';
|
||||
import UserSettingsContext from '../../../context/UserSettingsContext';
|
||||
import UserDataContext from '../../../context/UserDataContext';
|
||||
|
||||
const LinkWithProgress = styled.span`
|
||||
${tw`block relative`}
|
||||
|
@ -88,7 +88,7 @@ const ItemLink = ({ link }: { link: ModuleLinkInfo }) => {
|
|||
}
|
||||
}, [isActive]);
|
||||
|
||||
const { userProgress } = useContext(UserSettingsContext);
|
||||
const { userProgress } = useContext(UserDataContext);
|
||||
const progress = userProgress[link.id] || 'Not Started';
|
||||
|
||||
let lineColorStyle = tw`bg-gray-200`;
|
||||
|
|
|
@ -25,6 +25,7 @@ const TableOfContentsBlock = ({
|
|||
curDepth = heading.depth;
|
||||
links.push(
|
||||
<Link
|
||||
key={heading.slug}
|
||||
to={'#' + heading.slug}
|
||||
className="block mb-2 transition duration-150 ease-in-out text-gray-600 hover:underline hover:text-blue-600"
|
||||
style={{
|
||||
|
|
|
@ -28,6 +28,7 @@ const TableOfContentsSidebar = ({
|
|||
curDepth = heading.depth;
|
||||
links.push(
|
||||
<Link
|
||||
key={heading.slug}
|
||||
to={'#' + heading.slug}
|
||||
className={
|
||||
'block mb-1 text-sm transition duration-150 ease-in-out ' +
|
||||
|
|
50
src/components/ProblemStatusCheckbox.tsx
Normal file
50
src/components/ProblemStatusCheckbox.tsx
Normal file
|
@ -0,0 +1,50 @@
|
|||
import * as React from 'react';
|
||||
import Tooltip from './tooltip/Tooltip';
|
||||
import { Problem } from '../../content/models';
|
||||
import { useContext } from 'react';
|
||||
import UserDataContext, {
|
||||
NEXT_PROBLEM_STATUS,
|
||||
ProblemStatus,
|
||||
} from '../context/UserDataContext';
|
||||
|
||||
export default function ProblemStatusCheckbox({
|
||||
problem,
|
||||
}: {
|
||||
problem: Problem;
|
||||
}) {
|
||||
const { problemStatus, setProblemStatus } = useContext(UserDataContext);
|
||||
let status: ProblemStatus =
|
||||
problemStatus[problem.uniqueID] || 'Not Attempted';
|
||||
const icon: { [key in ProblemStatus]: React.ReactNode } = {
|
||||
'Not Attempted': (
|
||||
<span className="inline-block h-6 w-6 rounded-full bg-gray-200 cursor-pointer" />
|
||||
),
|
||||
Solving: (
|
||||
<span className="inline-block h-6 w-6 rounded-full bg-yellow-300 cursor-pointer" />
|
||||
),
|
||||
Solved: (
|
||||
<span className="inline-block h-6 w-6 rounded-full bg-green-500 cursor-pointer" />
|
||||
),
|
||||
"Can't Solve": (
|
||||
<span className="inline-block h-6 w-6 rounded-full bg-red-500 cursor-pointer" />
|
||||
),
|
||||
Skipped: (
|
||||
<span className="inline-block h-6 w-6 rounded-full bg-blue-300 cursor-pointer" />
|
||||
),
|
||||
};
|
||||
const handleClick = () => {
|
||||
setProblemStatus(problem, NEXT_PROBLEM_STATUS[status]);
|
||||
};
|
||||
return (
|
||||
<Tooltip
|
||||
content={status}
|
||||
hideOnClick={false}
|
||||
type="compact"
|
||||
position="left"
|
||||
>
|
||||
<span onClick={handleClick} className="inline-block h-6 w-6">
|
||||
{icon[status]}
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import UserSettingsContext from '../../context/UserSettingsContext';
|
||||
import UserDataContext from '../../context/UserDataContext';
|
||||
import { useContext } from 'react';
|
||||
|
||||
export const IncompleteSection = props => {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import * as React from 'react';
|
||||
import UserSettingsContext from '../../context/UserSettingsContext';
|
||||
import UserDataContext from '../../context/UserDataContext';
|
||||
import { useContext } from 'react';
|
||||
|
||||
export const LanguageSection = props => {
|
||||
const userSettings = useContext(UserSettingsContext);
|
||||
let lang = userSettings.primaryLang;
|
||||
const userSettings = useContext(UserDataContext);
|
||||
let lang = userSettings.lang;
|
||||
|
||||
let sections = {};
|
||||
React.Children.map(props.children, child => {
|
||||
|
|
|
@ -4,6 +4,7 @@ import Transition from '../Transition';
|
|||
import Tooltip from '../tooltip/Tooltip';
|
||||
import TextTooltip from '../tooltip/TextTooltip';
|
||||
import { sourceTooltip } from './Resources';
|
||||
import ProblemStatusCheckbox from '../ProblemStatusCheckbox';
|
||||
|
||||
type ProblemsListComponentProps = {
|
||||
title?: string;
|
||||
|
@ -22,6 +23,9 @@ export function ProblemsListComponent(props: ProblemsListComponentProps) {
|
|||
<table className="w-full no-markdown">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="pl-4 md:pl-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||
Status
|
||||
</th>
|
||||
<th className="pl-4 md:px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||
Source
|
||||
</th>
|
||||
|
@ -146,6 +150,9 @@ export function ProblemComponent(props: ProblemComponentProps) {
|
|||
|
||||
return (
|
||||
<tr>
|
||||
<td className="pl-4 md:pl-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500 font-medium text-center">
|
||||
<ProblemStatusCheckbox problem={problem} />
|
||||
</td>
|
||||
<td className="pl-4 md:px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500 font-medium">
|
||||
{sourceTooltip.hasOwnProperty(problem.source) ? (
|
||||
<TextTooltip content={sourceTooltip[problem.source]}>
|
||||
|
|
|
@ -3,7 +3,7 @@ import Dots from '../Dots';
|
|||
import Tooltip from '../tooltip/Tooltip';
|
||||
import TextTooltip from '../tooltip/TextTooltip';
|
||||
import { useContext } from 'react';
|
||||
import UserSettingsContext from '../../context/UserSettingsContext';
|
||||
import UserDataContext from '../../context/UserDataContext';
|
||||
|
||||
export function ResourcesListComponent(props) {
|
||||
const embedded = props.embedded;
|
||||
|
@ -92,13 +92,13 @@ export const sourceTooltip = {
|
|||
};
|
||||
|
||||
export function ResourceComponent(props) {
|
||||
const userSettings = useContext(UserSettingsContext);
|
||||
const userSettings = useContext(UserDataContext);
|
||||
|
||||
const source = props.source;
|
||||
let url = props.url;
|
||||
if (!url) {
|
||||
if (source === 'IUSACO') {
|
||||
if (userSettings.primaryLang === 'java') {
|
||||
if (userSettings.lang === 'java') {
|
||||
url = 'https://darrenyao.com/usacobook/java.pdf';
|
||||
} else {
|
||||
url = 'https://darrenyao.com/usacobook/cpp.pdf';
|
||||
|
|
|
@ -15,11 +15,17 @@ const StyledTippy = styled(Tippy)`
|
|||
font-weight: normal !important;
|
||||
text-align: center;
|
||||
|
||||
& > .tippy-arrow::before {
|
||||
${p =>
|
||||
p.placement === 'top'
|
||||
? 'border-top-color'
|
||||
: 'border-bottom-color'}: #252f3f !important;
|
||||
&[data-placement^='top'] > .tippy-arrow::before {
|
||||
border-top-color: #252f3f !important;
|
||||
}
|
||||
&[data-placement^='bottom'] > .tippy-arrow::before {
|
||||
border-bottom-color: #252f3f !important;
|
||||
}
|
||||
&[data-placement^='left'] > .tippy-arrow::before {
|
||||
border-left-color: #252f3f !important;
|
||||
}
|
||||
&[data-placement^='right'] > .tippy-arrow::before {
|
||||
border-right-color: #252f3f !important;
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -28,9 +34,21 @@ const AsteriskTippy = styled(StyledTippy)`
|
|||
p.placement === 'top' ? 'translateY(10px)' : 'translateY(-7px)'};
|
||||
`;
|
||||
|
||||
const Tooltip = ({ children, content, position = 'top', type = 'normal' }) => {
|
||||
const CompactTippy = styled(StyledTippy)`
|
||||
font-size: 0.875rem !important;
|
||||
padding: 0rem;
|
||||
`;
|
||||
|
||||
const Tooltip = ({
|
||||
children,
|
||||
content,
|
||||
position = 'top',
|
||||
type = 'normal',
|
||||
...other
|
||||
}) => {
|
||||
let Component = StyledTippy;
|
||||
if (type === 'asterisk') Component = AsteriskTippy;
|
||||
else if (type === 'compact') Component = CompactTippy;
|
||||
return (
|
||||
<Component
|
||||
content={content}
|
||||
|
@ -38,6 +56,7 @@ const Tooltip = ({ children, content, position = 'top', type = 'normal' }) => {
|
|||
theme="material"
|
||||
duration={200}
|
||||
placement={position}
|
||||
{...other}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
|
|
136
src/context/UserDataContext.tsx
Normal file
136
src/context/UserDataContext.tsx
Normal file
|
@ -0,0 +1,136 @@
|
|||
import { createContext, useState } from 'react';
|
||||
import * as React from 'react';
|
||||
import { Problem } from '../../content/models';
|
||||
|
||||
export type ModuleProgress =
|
||||
| 'Not Started'
|
||||
| 'Reading'
|
||||
| 'Practicing'
|
||||
| 'Complete'
|
||||
| 'Skipped';
|
||||
export const ModuleProgressOptions: ModuleProgress[] = [
|
||||
'Not Started',
|
||||
'Reading',
|
||||
'Practicing',
|
||||
'Complete',
|
||||
'Skipped',
|
||||
];
|
||||
export type UserProgress = { [key: string]: ModuleProgress };
|
||||
export type UserLang = 'showAll' | 'cpp' | 'java' | 'py';
|
||||
export type ProblemStatus =
|
||||
| 'Not Attempted'
|
||||
| 'Solving'
|
||||
| 'Solved'
|
||||
| "Can't Solve"
|
||||
| 'Skipped';
|
||||
export const NEXT_PROBLEM_STATUS: { [key in ProblemStatus]: ProblemStatus } = {
|
||||
'Not Attempted': 'Solving',
|
||||
Solving: 'Solved',
|
||||
Solved: "Can't Solve",
|
||||
"Can't Solve": 'Skipped',
|
||||
Skipped: 'Not Attempted',
|
||||
};
|
||||
|
||||
const UserDataContext = createContext<{
|
||||
lang: UserLang;
|
||||
setLang: (lang: UserLang) => void;
|
||||
userProgress: UserProgress;
|
||||
setModuleProgress: (moduleID: string, progress: ModuleProgress) => void;
|
||||
problemStatus: { [key: string]: ProblemStatus };
|
||||
setProblemStatus: (problem: Problem, status: ProblemStatus) => void;
|
||||
}>({
|
||||
lang: 'showAll',
|
||||
setLang: null,
|
||||
userProgress: null,
|
||||
setModuleProgress: null,
|
||||
problemStatus: null,
|
||||
setProblemStatus: null,
|
||||
});
|
||||
|
||||
const langKey = 'guide:userData:lang';
|
||||
const getLangFromStorage = () => {
|
||||
let stickyValue = window.localStorage.getItem(langKey);
|
||||
let v = null;
|
||||
try {
|
||||
v = JSON.parse(stickyValue);
|
||||
} catch (e) {
|
||||
console.error("Couldn't parse user primary language", e);
|
||||
}
|
||||
if (v === 'cpp' || v === 'java' || v === 'py') return v;
|
||||
return 'cpp';
|
||||
};
|
||||
|
||||
const progressKey = 'guide:userData:progress';
|
||||
const getProgressFromStorage = () => {
|
||||
let stickyValue = window.localStorage.getItem(progressKey);
|
||||
let v = {};
|
||||
try {
|
||||
v = JSON.parse(stickyValue);
|
||||
} catch (e) {
|
||||
console.error("Couldn't parse user progress", e);
|
||||
}
|
||||
return v || {};
|
||||
};
|
||||
|
||||
const problemStatusKey = 'guide:userData:problemStatus';
|
||||
const getProblemStatusFromStorage = () => {
|
||||
let stickyValue = window.localStorage.getItem(problemStatusKey);
|
||||
let v = {};
|
||||
try {
|
||||
v = JSON.parse(stickyValue);
|
||||
} catch (e) {
|
||||
console.error("Couldn't parse problem status", e);
|
||||
}
|
||||
return v || {};
|
||||
};
|
||||
|
||||
export const UserDataProvider = ({ children }) => {
|
||||
const [lang, setLang] = useState<UserLang>('showAll');
|
||||
const [userProgress, setUserProgress] = useState<UserProgress>({});
|
||||
const [problemStatus, setProblemStatus] = useState<{
|
||||
[key: string]: ProblemStatus;
|
||||
}>({});
|
||||
|
||||
React.useEffect(() => {
|
||||
setLang(getLangFromStorage());
|
||||
setUserProgress(getProgressFromStorage());
|
||||
setProblemStatus(getProblemStatusFromStorage());
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<UserDataContext.Provider
|
||||
value={{
|
||||
lang: lang as UserLang,
|
||||
setLang: lang => {
|
||||
window.localStorage.setItem(langKey, JSON.stringify(lang));
|
||||
setLang(lang);
|
||||
},
|
||||
userProgress,
|
||||
setModuleProgress: (moduleID: string, progress: ModuleProgress) => {
|
||||
const newProgress = {
|
||||
...getProgressFromStorage(),
|
||||
[moduleID]: progress,
|
||||
};
|
||||
window.localStorage.setItem(progressKey, JSON.stringify(newProgress));
|
||||
setUserProgress(newProgress);
|
||||
},
|
||||
problemStatus,
|
||||
setProblemStatus: (problem, status) => {
|
||||
const newStatus = {
|
||||
...getProblemStatusFromStorage(),
|
||||
[problem.uniqueID]: status,
|
||||
};
|
||||
window.localStorage.setItem(
|
||||
problemStatusKey,
|
||||
JSON.stringify(newStatus)
|
||||
);
|
||||
setProblemStatus(newStatus);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</UserDataContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserDataContext;
|
|
@ -1,80 +0,0 @@
|
|||
import { createContext, useState } from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
export type ModuleProgress =
|
||||
| 'Not Started'
|
||||
| 'Reading'
|
||||
| 'Practicing'
|
||||
| 'Complete'
|
||||
| 'Skipped';
|
||||
export const ModuleProgressOptions: ModuleProgress[] = [
|
||||
'Not Started',
|
||||
'Reading',
|
||||
'Practicing',
|
||||
'Complete',
|
||||
'Skipped',
|
||||
];
|
||||
export type UserProgress = { [key: string]: ModuleProgress };
|
||||
export type UserLang = 'showAll' | 'cpp' | 'java' | 'py';
|
||||
|
||||
const UserSettingsContext = createContext<{
|
||||
primaryLang: UserLang;
|
||||
setPrimaryLang: (lang: string) => void;
|
||||
userProgress: UserProgress;
|
||||
setModuleProgress: (moduleID: string, progress: ModuleProgress) => void;
|
||||
}>({
|
||||
primaryLang: 'showAll',
|
||||
setPrimaryLang: null,
|
||||
userProgress: null,
|
||||
setModuleProgress: null,
|
||||
});
|
||||
|
||||
export const UserSettingsProvider = ({ children }) => {
|
||||
const langKey = 'guide:userSettings:primaryLang';
|
||||
const progressKey = 'guide:userSettings:progress';
|
||||
const [primaryLang, setPrimaryLang] = useState('showAll');
|
||||
const [userProgress, setUserProgress] = useState({});
|
||||
|
||||
React.useEffect(() => {
|
||||
let stickyValue = window.localStorage.getItem(langKey);
|
||||
let v = null;
|
||||
try {
|
||||
v = JSON.parse(stickyValue);
|
||||
} catch (e) {
|
||||
console.error("Couldn't parse user primary language", e);
|
||||
}
|
||||
if (v === 'cpp' || v === 'java' || v === 'py') setPrimaryLang(v);
|
||||
else setPrimaryLang('cpp');
|
||||
|
||||
stickyValue = window.localStorage.getItem(progressKey);
|
||||
v = {};
|
||||
try {
|
||||
v = JSON.parse(stickyValue);
|
||||
} catch (e) {
|
||||
console.error("Couldn't parse user progress", e);
|
||||
}
|
||||
setUserProgress(v || {});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<UserSettingsContext.Provider
|
||||
value={{
|
||||
primaryLang: primaryLang as UserLang,
|
||||
setPrimaryLang: lang => {
|
||||
window.localStorage.setItem(langKey, JSON.stringify(lang));
|
||||
setPrimaryLang(lang);
|
||||
},
|
||||
userProgress,
|
||||
setModuleProgress: (moduleID: string, progress: ModuleProgress) => {
|
||||
const newProgress = { ...userProgress, [moduleID]: progress };
|
||||
window.localStorage.setItem(progressKey, JSON.stringify(newProgress));
|
||||
setUserProgress(newProgress);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</UserSettingsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserSettingsContext;
|
Reference in a new issue