code refactors

This commit is contained in:
Nathan Wang 2020-07-17 01:30:09 -07:00
parent 92dd089b9a
commit 2ea225cf71
17 changed files with 165 additions and 129 deletions

View file

@ -1,50 +1 @@
const sources = { export { Problem } from "../src/models/problem";
"AC": "https://atcoder.jp/",
"CC": "https://www.codechef.com/problems/",
"CSA": "https://csacademy.com/contest/archive/task/",
"DMOJ": "https://dmoj.ca/problem/",
"SPOJ": "https://www.spoj.com/problems/",
"YS": "https://judge.yosupo.jp/problem/",
"CF": "https://codeforces.com/",
"Bronze": "http://www.usaco.org/index.php?page=viewproblem2&cpid=",
"Silver": "http://www.usaco.org/index.php?page=viewproblem2&cpid=",
"Gold": "http://www.usaco.org/index.php?page=viewproblem2&cpid=",
"Old Bronze": "http://www.usaco.org/index.php?page=viewproblem2&cpid=",
"Old Silver": "http://www.usaco.org/index.php?page=viewproblem2&cpid=",
"Old Gold": "http://www.usaco.org/index.php?page=viewproblem2&cpid=",
"Plat": "http://www.usaco.org/index.php?page=viewproblem2&cpid=",
"Kattis": "https://open.kattis.com/problems/",
"CSES": "https://cses.fi/problemset/task/",
"LC": "https://leetcode.com/problems/",
"ojuz": "https://oj.uz/problem/view/",
"HR": "https://www.hackerrank.com/",
};
export class Problem {
public url: string;
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,
public id: string,
labels?: 'Very Easy' | 'Easy' | 'Normal' | 'Hard' | 'Very Hard' | 'Insane' | 'Intro|Very Easy' | 'Intro|Easy' | 'Intro|Normal' | 'Intro|Hard' | 'Intro|Very Hard' | 'Intro|Insane',
public starred?: boolean,
public tags?: string[],
public sketch?: string,
) {
this.isIntro = labels && labels.includes("Intro|");
if (labels) labels = labels.replace("Intro|", "") as any;
this.difficulty = labels as any;
if (!id.startsWith("http")) {
if (source in sources) {
this.url = sources[source] + id;
} else throw `URL ${id} is not valid. Did you make a typo in the problem source (${source}), or in the URL? Problem name: ${name}`
} else this.url = id;
}
}

View file

@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import { random, sample, range, getDiameter } from './Confetti.helpers'; import { random, sample, range, getDiameter } from './Confetti.helpers';
import { createCircle, createTriangle, createZigZag } from './confetti-shapes'; import { createCircle, createTriangle, createZigZag } from './confetti-shapes';
import Canvas from '../Canvas/Canvas'; import Canvas from './Canvas';
class Confetti extends Component { class Confetti extends Component {
static propTypes = { static propTypes = {

View file

@ -1,5 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { ModuleInfo } from '../module'; import { ModuleInfo } from '../models/module';
import { SECTION_LABELS } from '../../content/ordering'; import { SECTION_LABELS } from '../../content/ordering';
import SlideoverForm from './Slideover/SlideoverForm'; import SlideoverForm from './Slideover/SlideoverForm';
import { useState } from 'react'; import { useState } from 'react';
@ -83,7 +83,7 @@ export default function ContactUsSlideover({
React.useEffect(() => { React.useEffect(() => {
if (activeModule) if (activeModule)
setLocation( setLocation(
`${activeModule.title} - ${SECTION_LABELS[activeModule.division]}` `${activeModule.title} - ${SECTION_LABELS[activeModule.section]}`
); );
else setLocation(''); else setLocation('');
}, [activeModule]); }, [activeModule]);

View file

@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import Transition from '../Transition'; import Transition from '../Transition';
import { ModuleProgressOptions } from '../../context/UserDataContext'; import { ModuleProgressOptions } from '../../models/module';
const MarkCompleteButton = ({ const MarkCompleteButton = ({
state, state,

View file

@ -3,7 +3,11 @@ import Transition from '../Transition';
import { useContext, useRef, useState } from 'react'; import { useContext, useRef, useState } from 'react';
// @ts-ignore // @ts-ignore
import logo from '../../assets/logo.svg'; import logo from '../../assets/logo.svg';
import { ModuleFrequency, ModuleInfo, ModuleLinkInfo } from '../../module'; import {
ModuleFrequency,
ModuleInfo,
ModuleLinkInfo,
} from '../../models/module';
import { graphql, Link, useStaticQuery } from 'gatsby'; import { graphql, Link, useStaticQuery } from 'gatsby';
import MODULE_ORDERING, { import MODULE_ORDERING, {
Category, Category,
@ -86,10 +90,10 @@ const Breadcrumbs = () => {
/> />
</svg> </svg>
<Link <Link
to={`/${module.division}`} to={`/${module.section}`}
className="text-gray-500 hover:text-gray-700 transition duration-150 ease-in-out" className="text-gray-500 hover:text-gray-700 transition duration-150 ease-in-out"
> >
{SECTION_LABELS[module.division]} {SECTION_LABELS[module.section]}
</Link> </Link>
<svg <svg
className="flex-shrink-0 mx-2 h-5 w-5 text-gray-400" className="flex-shrink-0 mx-2 h-5 w-5 text-gray-400"
@ -170,7 +174,7 @@ const NavBar = ({ alignNavButtonsRight = true }) => {
const { module, moduleLinks } = moduleLayoutInfo; const { module, moduleLinks } = moduleLayoutInfo;
const sortedModuleLinks = React.useMemo(() => { const sortedModuleLinks = React.useMemo(() => {
let links: ModuleLinkInfo[] = []; let links: ModuleLinkInfo[] = [];
for (let group of MODULE_ORDERING[module.division]) { for (let group of MODULE_ORDERING[module.section]) {
for (let id of group.items) { for (let id of group.items) {
links.push(moduleLinks.find(x => x.id === id)); links.push(moduleLinks.find(x => x.id === id));
} }
@ -254,7 +258,7 @@ const renderPrerequisite = (prerequisite, moduleLinks: ModuleLinkInfo[]) => {
return ( return (
<li key={prerequisite}> <li key={prerequisite}>
<Link to={moduleLink.url} className="underline text-black"> <Link to={moduleLink.url} className="underline text-black">
{SECTION_LABELS[moduleLink.division]} - {moduleLink.title} {SECTION_LABELS[moduleLink.section]} - {moduleLink.title}
</Link> </Link>
</li> </li>
); );
@ -268,12 +272,15 @@ export default function ModuleLayout({
module: ModuleInfo; module: ModuleInfo;
children: React.ReactNode; children: React.ReactNode;
}) { }) {
const { userProgress, setModuleProgress, lang } = useContext(UserDataContext); const { userProgressOnModules, setModuleProgress, lang } = useContext(
UserDataContext
);
const [isMobileNavOpen, setIsMobileNavOpen] = useState(false); const [isMobileNavOpen, setIsMobileNavOpen] = useState(false);
const [isContactUsActive, setIsContactUsActive] = useState(false); const [isContactUsActive, setIsContactUsActive] = useState(false);
const [isConfettiActive, setIsConfettiActive] = useState(false); const [isConfettiActive, setIsConfettiActive] = useState(false);
const moduleProgress = const moduleProgress =
(userProgress && userProgress[module.id]) || 'Not Started'; (userProgressOnModules && userProgressOnModules[module.id]) ||
'Not Started';
const tableOfContents = const tableOfContents =
lang in module.toc ? module.toc[lang] : module.toc['cpp']; lang in module.toc ? module.toc[lang] : module.toc['cpp'];

View file

@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { Link } from 'gatsby'; import { Link } from 'gatsby';
import { ModuleLinkInfo } from '../../../module'; import { ModuleLinkInfo } from '../../../models/module';
import styled from 'styled-components'; import styled from 'styled-components';
import tw from 'twin.macro'; import tw from 'twin.macro';
import { useContext } from 'react'; import { useContext } from 'react';
@ -88,8 +88,8 @@ const ItemLink = ({ link }: { link: ModuleLinkInfo }) => {
} }
}, [isActive]); }, [isActive]);
const { userProgress } = useContext(UserDataContext); const { userProgressOnModules } = useContext(UserDataContext);
const progress = userProgress[link.id] || 'Not Started'; const progress = userProgressOnModules[link.id] || 'Not Started';
let lineColorStyle = tw`bg-gray-200`; let lineColorStyle = tw`bg-gray-200`;
let dotColorStyle = tw`bg-gray-200`; let dotColorStyle = tw`bg-gray-200`;

View file

@ -1,5 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { ModuleLinkInfo } from '../../../module'; import { ModuleLinkInfo } from '../../../models/module';
import { Link } from 'gatsby'; import { Link } from 'gatsby';
import ItemLink from './ItemLink'; import ItemLink from './ItemLink';
import Accordion from './Accordion'; import Accordion from './Accordion';
@ -16,13 +16,13 @@ export const SidebarNav = () => {
const { module, moduleLinks } = useContext(ModuleLayoutContext); const { module, moduleLinks } = useContext(ModuleLayoutContext);
const links: NavLinkGroup[] = React.useMemo(() => { const links: NavLinkGroup[] = React.useMemo(() => {
return MODULE_ORDERING[module.division].map((category: Category) => ({ return MODULE_ORDERING[module.section].map((category: Category) => ({
label: category.name, label: category.name,
children: category.items.map( children: category.items.map(
moduleID => moduleLinks.find(link => link.id === moduleID) // lol O(n^2)? moduleID => moduleLinks.find(link => link.id === moduleID) // lol O(n^2)?
), ),
})); }));
}, [module.division, moduleLinks]); }, [module.section, moduleLinks]);
return ( return (
<> <>

View file

@ -1,5 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { TOCHeading } from '../../module'; import { TOCHeading } from '../../models/module';
import { Link } from 'gatsby'; import { Link } from 'gatsby';
import { useActiveHash } from '../../hooks/useActiveHash'; import { useActiveHash } from '../../hooks/useActiveHash';
import { useMemo } from 'react'; import { useMemo } from 'react';

View file

@ -1,5 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { TOCHeading } from '../../module'; import { TOCHeading } from '../../models/module';
import { Link } from 'gatsby'; import { Link } from 'gatsby';
import { useActiveHash } from '../../hooks/useActiveHash'; import { useActiveHash } from '../../hooks/useActiveHash';
import { useMemo } from 'react'; import { useMemo } from 'react';

View file

@ -2,20 +2,20 @@ import * as React from 'react';
import Tooltip from './tooltip/Tooltip'; import Tooltip from './tooltip/Tooltip';
import { Problem } from '../../content/models'; import { Problem } from '../../content/models';
import { useContext } from 'react'; import { useContext } from 'react';
import UserDataContext, { import UserDataContext from '../context/UserDataContext';
NEXT_PROBLEM_STATUS, import { NEXT_PROBLEM_STATUS, ProblemProgress } from '../models/problem';
ProblemStatus,
} from '../context/UserDataContext';
export default function ProblemStatusCheckbox({ export default function ProblemStatusCheckbox({
problem, problem,
}: { }: {
problem: Problem; problem: Problem;
}) { }) {
const { problemStatus, setProblemStatus } = useContext(UserDataContext); const { userProgressOnProblems, setUserProgressOnProblems } = useContext(
let status: ProblemStatus = UserDataContext
problemStatus[problem.uniqueID] || 'Not Attempted'; );
const color: { [key in ProblemStatus]: string } = { let status: ProblemProgress =
userProgressOnProblems[problem.uniqueID] || 'Not Attempted';
const color: { [key in ProblemProgress]: string } = {
'Not Attempted': 'bg-gray-200', 'Not Attempted': 'bg-gray-200',
Solving: 'bg-yellow-300', Solving: 'bg-yellow-300',
Solved: 'bg-green-500', Solved: 'bg-green-500',
@ -23,7 +23,7 @@ export default function ProblemStatusCheckbox({
Skipped: 'bg-blue-300', Skipped: 'bg-blue-300',
}; };
const handleClick = () => { const handleClick = () => {
setProblemStatus(problem, NEXT_PROBLEM_STATUS[status]); setUserProgressOnProblems(problem, NEXT_PROBLEM_STATUS[status]);
}; };
return ( return (
<Tooltip <Tooltip

View file

@ -1,6 +1,6 @@
import { createContext } from 'react'; import { createContext } from 'react';
import * as React from 'react'; import * as React from 'react';
import { ModuleInfo, ModuleLinkInfo } from '../module'; import { ModuleInfo, ModuleLinkInfo } from '../models/module';
const ModuleLayoutContext = createContext<{ const ModuleLayoutContext = createContext<{
module: ModuleInfo; module: ModuleInfo;

View file

@ -1,50 +1,30 @@
import { createContext, useState } from 'react';
import * as React from 'react'; import * as React from 'react';
import { createContext, useState } from 'react';
import { Problem } from '../../content/models'; import { Problem } from '../../content/models';
import { ModuleProgress } from '../models/module';
import { ProblemProgress } from '../models/problem';
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 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<{ const UserDataContext = createContext<{
lang: UserLang; lang: UserLang;
setLang: (lang: UserLang) => void; setLang: (lang: UserLang) => void;
userProgress: UserProgress;
userProgressOnModules: { [key: string]: ModuleProgress };
setModuleProgress: (moduleID: string, progress: ModuleProgress) => void; setModuleProgress: (moduleID: string, progress: ModuleProgress) => void;
problemStatus: { [key: string]: ProblemStatus };
setProblemStatus: (problem: Problem, status: ProblemStatus) => void; userProgressOnProblems: { [key: string]: ProblemProgress };
setUserProgressOnProblems: (
problem: Problem,
status: ProblemProgress
) => void;
}>({ }>({
lang: 'showAll', lang: 'showAll',
setLang: null, setLang: null,
userProgress: null, userProgressOnModules: null,
setModuleProgress: null, setModuleProgress: null,
problemStatus: null, userProgressOnProblems: null,
setProblemStatus: null, setUserProgressOnProblems: null,
}); });
const langKey = 'guide:userData:lang'; const langKey = 'guide:userData:lang';
@ -86,9 +66,11 @@ const getProblemStatusFromStorage = () => {
export const UserDataProvider = ({ children }) => { export const UserDataProvider = ({ children }) => {
const [lang, setLang] = useState<UserLang>('showAll'); const [lang, setLang] = useState<UserLang>('showAll');
const [userProgress, setUserProgress] = useState<UserProgress>({}); const [userProgress, setUserProgress] = useState<{
[key: string]: ModuleProgress;
}>({});
const [problemStatus, setProblemStatus] = useState<{ const [problemStatus, setProblemStatus] = useState<{
[key: string]: ProblemStatus; [key: string]: ProblemProgress;
}>({}); }>({});
React.useEffect(() => { React.useEffect(() => {
@ -105,7 +87,7 @@ export const UserDataProvider = ({ children }) => {
window.localStorage.setItem(langKey, JSON.stringify(lang)); window.localStorage.setItem(langKey, JSON.stringify(lang));
setLang(lang); setLang(lang);
}, },
userProgress, userProgressOnModules: userProgress,
setModuleProgress: (moduleID: string, progress: ModuleProgress) => { setModuleProgress: (moduleID: string, progress: ModuleProgress) => {
const newProgress = { const newProgress = {
...getProgressFromStorage(), ...getProgressFromStorage(),
@ -114,8 +96,8 @@ export const UserDataProvider = ({ children }) => {
window.localStorage.setItem(progressKey, JSON.stringify(newProgress)); window.localStorage.setItem(progressKey, JSON.stringify(newProgress));
setUserProgress(newProgress); setUserProgress(newProgress);
}, },
problemStatus, userProgressOnProblems: problemStatus,
setProblemStatus: (problem, status) => { setUserProgressOnProblems: (problem, status) => {
const newStatus = { const newStatus = {
...getProblemStatusFromStorage(), ...getProblemStatusFromStorage(),
[problem.uniqueID]: status, [problem.uniqueID]: status,

View file

@ -1,12 +1,8 @@
export class ModuleLinkInfo { export class ModuleLinkInfo {
public url: string; public url: string;
constructor( constructor(public id: string, public section: string, public title: string) {
public id: string, this.url = `/${section}/${id}`;
public division: string,
public title: string
) {
this.url = `/${division}/${id}`;
} }
} }
@ -24,11 +20,10 @@ export type TableOfContents = {
py: TOCHeading[]; py: TOCHeading[];
}; };
// there's probably a way to do this without the duplicated types...
export class ModuleInfo extends ModuleLinkInfo { export class ModuleInfo extends ModuleLinkInfo {
constructor( constructor(
public id: string, public id: string,
public division: string, public section: string,
public title: string, public title: string,
public body: any, public body: any,
public author: string, public author: string,
@ -37,6 +32,21 @@ export class ModuleInfo extends ModuleLinkInfo {
public frequency: ModuleFrequency, public frequency: ModuleFrequency,
public toc: TableOfContents public toc: TableOfContents
) { ) {
super(id, division, title); super(id, section, title);
} }
} }
export type ModuleProgress =
| 'Not Started'
| 'Reading'
| 'Practicing'
| 'Complete'
| 'Skipped';
export const ModuleProgressOptions: ModuleProgress[] = [
'Not Started',
'Reading',
'Practicing',
'Complete',
'Skipped',
];

86
src/models/problem.ts Normal file
View file

@ -0,0 +1,86 @@
const sources = {
AC: 'https://atcoder.jp/',
CC: 'https://www.codechef.com/problems/',
CSA: 'https://csacademy.com/contest/archive/task/',
DMOJ: 'https://dmoj.ca/problem/',
SPOJ: 'https://www.spoj.com/problems/',
YS: 'https://judge.yosupo.jp/problem/',
CF: 'https://codeforces.com/',
Bronze: 'http://www.usaco.org/index.php?page=viewproblem2&cpid=',
Silver: 'http://www.usaco.org/index.php?page=viewproblem2&cpid=',
Gold: 'http://www.usaco.org/index.php?page=viewproblem2&cpid=',
'Old Bronze': 'http://www.usaco.org/index.php?page=viewproblem2&cpid=',
'Old Silver': 'http://www.usaco.org/index.php?page=viewproblem2&cpid=',
'Old Gold': 'http://www.usaco.org/index.php?page=viewproblem2&cpid=',
Plat: 'http://www.usaco.org/index.php?page=viewproblem2&cpid=',
Kattis: 'https://open.kattis.com/problems/',
CSES: 'https://cses.fi/problemset/task/',
LC: 'https://leetcode.com/problems/',
ojuz: 'https://oj.uz/problem/view/',
HR: 'https://www.hackerrank.com/',
};
export class Problem {
public url: string;
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,
public id: string,
labels?:
| 'Very Easy'
| 'Easy'
| 'Normal'
| 'Hard'
| 'Very Hard'
| 'Insane'
| 'Intro|Very Easy'
| 'Intro|Easy'
| 'Intro|Normal'
| 'Intro|Hard'
| 'Intro|Very Hard'
| 'Intro|Insane',
public starred?: boolean,
public tags?: string[],
public sketch?: string
) {
this.isIntro = labels && labels.includes('Intro|');
if (labels) labels = labels.replace('Intro|', '') as any;
this.difficulty = labels as any;
if (!id.startsWith('http')) {
if (source in sources) {
this.url = sources[source] + id;
} else
throw `URL ${id} is not valid. Did you make a typo in the problem source (${source}), or in the URL? Problem name: ${name}`;
} else this.url = id;
}
}
export type ProblemProgress =
| 'Not Attempted'
| 'Solving'
| 'Solved'
| "Can't Solve"
| 'Skipped';
export const NEXT_PROBLEM_STATUS: {
[key in ProblemProgress]: ProblemProgress;
} = {
'Not Attempted': 'Solving',
Solving: 'Solved',
Solved: "Can't Solve",
"Can't Solve": 'Skipped',
Skipped: 'Not Attempted',
};

View file

@ -15,7 +15,7 @@ export default function Template(props) {
return ( return (
<Layout> <Layout>
<SEO <SEO
title={`${module.title} (${SECTION_LABELS[module.division]})`} title={`${module.title} (${SECTION_LABELS[module.section]})`}
description={module.description} description={module.description}
/> />

View file

@ -1,5 +1,5 @@
import MODULE_ORDERING from '../content/ordering'; import MODULE_ORDERING from '../content/ordering';
import { ModuleInfo, ModuleLinkInfo } from './module'; import { ModuleInfo, ModuleLinkInfo } from './models/module';
export const getModule = (allModules, division) => { export const getModule = (allModules, division) => {
return MODULE_ORDERING[division].map(k => { return MODULE_ORDERING[division].map(k => {